diff --git a/.editorconfig b/.editorconfig index e7c99f5d9..c300dcab8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,4 @@ # EditorConfig is awesome: http://EditorConfig.org - root = true [*] @@ -9,7 +8,7 @@ insert_final_newline = true indent_style = space indent_size = 4 charset = utf-8 -max_line_length = 120 +max_line_length = 180 [*.{bat,cmd,ps1}] end_of_line = crlf @@ -17,13 +16,12 @@ end_of_line = crlf [*.md] trim_trailing_whitespace = false +[*.yml] +indent_size = 2 + [Makefile] indent_style = tab indent_size = 4 -[*.yml] -indent_style = space -indent_size = 2 - [LICENSE] insert_final_newline = false diff --git a/CHANGELOG.md b/CHANGELOG.md index f85771c2e..88361aae8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,4 +18,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed -* Support for python below 3.8 \ No newline at end of file +* Support for python below 3.8 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cbe992ce5..136be039d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,61 +1,3 @@ # Contributing -Contributions are welcome and very much appreciated! - - -## Code contributions - -We accept code contributions through pull requests. -In short, this is how that works. - -1. Fork [the repository](https://github.com/compas-dev/compas_fea2) and clone the fork. -2. Create a virtual environment using your tool of choice (e.g. `virtualenv`, `conda`, etc). -3. Install development dependencies: - -```bash - $ pip install -r requirements-dev.txt -``` - -4. Make sure all tests pass: - -```bash - $ invoke test -``` - -5. Start making your changes to the **master** branch (or branch off of it). -6. Make sure all tests still pass: - -```bash - $ invoke test -``` - -7. Add yourself to the *Contributors* section of `AUTHORS.md`. -8. Commit your changes and push your branch to GitHub. -9. Create a [pull request](https://help.github.com/articles/about-pull-requests/) through the GitHub website. - - -During development, use [pyinvoke](http://docs.pyinvoke.org/) tasks on the -command line to ease recurring operations: - -* `invoke clean`: Clean all generated artifacts. -* `invoke check`: Run various code and documentation style checks. -* `invoke docs`: Generate documentation. -* `invoke test`: Run all tests and checks in one swift command. -* `invoke`: Show available tasks. - - -## Bug reports - -When [reporting a bug](https://github.com/compas-dev/compas_fea2/issues) please include: - -* Operating system name and version. -* Any details about your local setup that might be helpful in troubleshooting. -* Detailed steps to reproduce the bug. - - -## Feature requests - -When [proposing a new feature](https://github.com/compas-dev/compas_fea2/issues) please include: - -* Explain in detail how it would work. -* Keep the scope as narrow as possible, to make it easier to implement. +Coming soon... diff --git a/docs/_images/CollaborationWorkflow.jpg b/docs/_images/CollaborationWorkflow.jpg new file mode 100644 index 000000000..3df9de11a Binary files /dev/null and b/docs/_images/CollaborationWorkflow.jpg differ diff --git a/docs/_images/CollaborationWorkflow_example.jpg b/docs/_images/CollaborationWorkflow_example.jpg new file mode 100644 index 000000000..ef82f5ba8 Binary files /dev/null and b/docs/_images/CollaborationWorkflow_example.jpg differ diff --git a/docs/_images/examples/principal_stress_cantilever.png b/docs/_images/examples/principal_stress_cantilever.png deleted file mode 100644 index 2178c1240..000000000 Binary files a/docs/_images/examples/principal_stress_cantilever.png and /dev/null differ diff --git a/docs/_images/examples/principal_stress_script.png b/docs/_images/examples/principal_stress_script.png deleted file mode 100644 index 9f47855c2..000000000 Binary files a/docs/_images/examples/principal_stress_script.png and /dev/null differ diff --git a/docs/_images/fork.png b/docs/_images/fork.png new file mode 100644 index 000000000..32c3a360f Binary files /dev/null and b/docs/_images/fork.png differ diff --git a/docs/_images/workflow_1.png b/docs/_images/workflow_1.png new file mode 100644 index 000000000..115898b70 Binary files /dev/null and b/docs/_images/workflow_1.png differ diff --git a/docs/_images/workflow_2.jpg b/docs/_images/workflow_2.jpg new file mode 100644 index 000000000..c918d5f15 Binary files /dev/null and b/docs/_images/workflow_2.jpg differ diff --git a/docs/_static/compas.css b/docs/_static/compas.css new file mode 100644 index 000000000..b5bb915d4 --- /dev/null +++ b/docs/_static/compas.css @@ -0,0 +1,30 @@ +html[data-theme="light"] { + --pst-color-primary: #0092d2; + --pst-color-info: #0092d2; + --pst-color-text-muted: #888; +} + +body { + line-height: 1.75; + font-weight: 300; +} + +.bd-article-container h1 { + color: #0092d2; +} + +.navbar-brand .logo__image { + height: 36px !important; +} + +.bd-header { + box-shadow: none; + border-bottom: 1px solid var(--pst-color-shadow); +} + +#rtd-footer-container { + height: 0px; + bottom: 0 !important; + margin: 0 !important; + display: none; +} diff --git a/docs/_static/compas.ico b/docs/_static/compas.ico new file mode 100644 index 000000000..ec6b1f27b Binary files /dev/null and b/docs/_static/compas.ico differ diff --git a/docs/_static/compas_icon.png b/docs/_static/compas_icon.png new file mode 100644 index 000000000..f112c7b3c Binary files /dev/null and b/docs/_static/compas_icon.png differ diff --git a/docs/_static/compas_icon_white.png b/docs/_static/compas_icon_white.png new file mode 100644 index 000000000..2c905bff9 Binary files /dev/null and b/docs/_static/compas_icon_white.png differ diff --git a/docs/_static/compas_white.ico b/docs/_static/compas_white.ico new file mode 100644 index 000000000..381bd9414 Binary files /dev/null and b/docs/_static/compas_white.ico differ diff --git a/docs/_static/versions.json b/docs/_static/versions.json new file mode 100644 index 000000000..d701ac20a --- /dev/null +++ b/docs/_static/versions.json @@ -0,0 +1,7 @@ +[ + { + "name": "latest", + "version": "unreleased", + "url": "https://compas.dev/compas_libigl/latest/" + } +] \ No newline at end of file diff --git a/docs/_templates/PLACEHOLDER b/docs/_templates/PLACEHOLDER deleted file mode 100644 index 27c4a9e40..000000000 --- a/docs/_templates/PLACEHOLDER +++ /dev/null @@ -1 +0,0 @@ -# template files for Sphinx diff --git a/docs/_templates/autosummary/base.rst b/docs/_templates/autosummary/base.rst deleted file mode 100644 index d7b3b9e54..000000000 --- a/docs/_templates/autosummary/base.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. rst-class:: detail - -{{ objname }} -{{ underline }} - -.. currentmodule:: {{ module }} - -.. auto{{ objtype }}:: {{ objname }} diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst deleted file mode 100644 index 6a7bc1818..000000000 --- a/docs/_templates/autosummary/class.rst +++ /dev/null @@ -1,60 +0,0 @@ -.. rst-class:: detail - -{{ objname }} -{{ underline }} - -.. currentmodule:: {{ module }} - -.. autoclass:: {{ objname }} - - {% block attributes %} - {% if attributes %} - - .. rubric:: Attributes - - .. autosummary:: - {% for item in attributes %} - {%- if item not in inherited_members %} - ~{{ name }}.{{ item }} - {%- endif %} - {%- endfor %} - - .. rubric:: Inherited Attributes - - .. autosummary:: - {% for item in attributes %} - {%- if item in inherited_members %} - ~{{ name }}.{{ item }} - {%- endif %} - {%- endfor %} - - {% endif %} - {% endblock %} - - {% block methods %} - {% if methods %} - - .. rubric:: Methods - - .. autosummary:: - :toctree: - - {% for item in methods %} - {%- if item not in inherited_members %} - ~{{ name }}.{{ item }} - {%- endif %} - {%- endfor %} - - .. rubric:: Inherited Methods - - .. autosummary:: - :toctree: - - {% for item in methods %} - {%- if item in inherited_members %} - ~{{ name }}.{{ item }} - {%- endif %} - {%- endfor %} - - {% endif %} - {% endblock %} diff --git a/docs/_templates/autosummary/method.rst b/docs/_templates/autosummary/method.rst deleted file mode 100644 index d7b3b9e54..000000000 --- a/docs/_templates/autosummary/method.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. rst-class:: detail - -{{ objname }} -{{ underline }} - -.. currentmodule:: {{ module }} - -.. auto{{ objtype }}:: {{ objname }} diff --git a/docs/_templates/autosummary/module.rst b/docs/_templates/autosummary/module.rst deleted file mode 100644 index 6ca6bbfe9..000000000 --- a/docs/_templates/autosummary/module.rst +++ /dev/null @@ -1,39 +0,0 @@ -.. rst-class:: detail - -{{ fullname }} -{{ underline }} - -.. automodule:: {{ fullname }} - - {% block functions %} - {% if functions %} - .. rubric:: Functions - - .. autosummary:: - {% for item in functions %} - {{ item }} - {%- endfor %} - {% endif %} - {% endblock %} - - {% block classes %} - {% if classes %} - .. rubric:: Classes - - .. autosummary:: - {% for item in classes %} - {{ item }} - {%- endfor %} - {% endif %} - {% endblock %} - - {% block exceptions %} - {% if exceptions %} - .. rubric:: Exceptions - - .. autosummary:: - {% for item in exceptions %} - {{ item }} - {%- endfor %} - {% endif %} - {% endblock %} diff --git a/docs/_templates/compas-sidebar-footer.html b/docs/_templates/compas-sidebar-footer.html new file mode 100644 index 000000000..af7e76259 --- /dev/null +++ b/docs/_templates/compas-sidebar-footer.html @@ -0,0 +1,5 @@ +
\ No newline at end of file diff --git a/docs/_templates/navbar-nav.html b/docs/_templates/navbar-nav.html new file mode 100644 index 000000000..c5d0efadc --- /dev/null +++ b/docs/_templates/navbar-nav.html @@ -0,0 +1,11 @@ + diff --git a/docs/_templates/sbt-sidebar-nav.html b/docs/_templates/sbt-sidebar-nav.html new file mode 100644 index 000000000..8f41a325c --- /dev/null +++ b/docs/_templates/sbt-sidebar-nav.html @@ -0,0 +1,24 @@ + diff --git a/docs/advanced.backends.rst b/docs/advanced.backends.rst new file mode 100644 index 000000000..6ef3472d6 --- /dev/null +++ b/docs/advanced.backends.rst @@ -0,0 +1,3 @@ +****************** +Advanced: Backends +****************** diff --git a/docs/advanced.results.rst b/docs/advanced.results.rst new file mode 100644 index 000000000..d5d5dd9ea --- /dev/null +++ b/docs/advanced.results.rst @@ -0,0 +1,3 @@ +***************** +Advanced: Results +***************** diff --git a/docs/advanced.visualisation.rst b/docs/advanced.visualisation.rst new file mode 100644 index 000000000..a25a4e6cd --- /dev/null +++ b/docs/advanced.visualisation.rst @@ -0,0 +1,3 @@ +*********************** +Advanced: Visualisation +*********************** diff --git a/docs/api/compas_fea2.UI.rst b/docs/api/compas_fea2.UI.rst deleted file mode 100644 index ef258676c..000000000 --- a/docs/api/compas_fea2.UI.rst +++ /dev/null @@ -1,2 +0,0 @@ - -.. automodule:: compas_fea2.UI diff --git a/docs/api/compas_fea2.job.rst b/docs/api/compas_fea2.job.rst index 8d5719e38..05a1e9893 100644 --- a/docs/api/compas_fea2.job.rst +++ b/docs/api/compas_fea2.job.rst @@ -1,2 +1,11 @@ +******************************************************************************** +job +******************************************************************************** -.. automodule:: compas_fea2.job +.. currentmodule:: compas_fea2.job + +.. autosummary:: + :toctree: generated/ + + InputFile + ParametersFile diff --git a/docs/api/compas_fea2.model.rst b/docs/api/compas_fea2.model.rst index 2b843fa19..546320d94 100644 --- a/docs/api/compas_fea2.model.rst +++ b/docs/api/compas_fea2.model.rst @@ -1,2 +1,156 @@ +******************************************************************************** +model +******************************************************************************** -.. automodule:: compas_fea2.model +.. currentmodule:: compas_fea2.model + +Model +===== + +.. autosummary:: + :toctree: generated/ + + Model + +Parts +===== + +.. autosummary:: + :toctree: generated/ + + DeformablePart + RigidPart + +Nodes +===== + +.. autosummary:: + :toctree: generated/ + + Node + +Elements +======== + +.. autosummary:: + :toctree: generated/ + + Element + MassElement + BeamElement + SpringElement + TrussElement + StrutElement + TieElement + ShellElement + MembraneElement + Element3D + TetrahedronElement + HexahedronElement + +Releases +======== + +.. autosummary:: + :toctree: generated/ + + BeamEndRelease + BeamEndPinRelease + BeamEndSliderRelease + +Constraints +=========== + +.. autosummary:: + :toctree: generated/ + + Constraint + MultiPointConstraint + TieMPC + BeamMPC + TieConstraint + +Materials +========= + +.. autosummary:: + :toctree: generated/ + + Material + UserMaterial + Stiff + ElasticIsotropic + ElasticOrthotropic + ElasticPlastic + Concrete + ConcreteSmearedCrack + ConcreteDamagedPlasticity + Steel + Timber + +Sections +======== + +.. autosummary:: + :toctree: generated/ + + Section + BeamSection + SpringSection + AngleSection + BoxSection + CircularSection + HexSection + ISection + PipeSection + RectangularSection + ShellSection + MembraneSection + SolidSection + TrapezoidalSection + TrussSection + StrutSection + TieSection + MassSection + +Boundary Conditions +=================== + +.. autosummary:: + :toctree: generated/ + + BoundaryCondition + GeneralBC + FixedBC + PinnedBC + ClampBCXX + ClampBCYY + ClampBCZZ + RollerBCX + RollerBCY + RollerBCZ + RollerBCXY + RollerBCYZ + RollerBCXZ + +Initial Conditions +================== + +.. autosummary:: + :toctree: generated/ + + InitialCondition + InitialTemperatureField + InitialStressField + +Groups +====== + +.. autosummary:: + :toctree: generated/ + + Group + NodesGroup + ElementsGroup + FacesGroup + PartsGroup diff --git a/docs/tutorial.rst b/docs/api/compas_fea2.postprocess.rst similarity index 54% rename from docs/tutorial.rst rename to docs/api/compas_fea2.postprocess.rst index e6ca31ca5..f8d4ac498 100644 --- a/docs/tutorial.rst +++ b/docs/api/compas_fea2.postprocess.rst @@ -1,11 +1,13 @@ ******************************************************************************** -Tutorial +postprocess ******************************************************************************** -.. toctree:: - :maxdepth: 1 - :titlesonly: - :glob: +.. currentmodule:: compas_fea2.postprocess - tutorial/** +Stresses +======== +.. autosummary:: + :toctree: generated/ + + principal_stresses diff --git a/docs/api/compas_fea2.problem.rst b/docs/api/compas_fea2.problem.rst index 899fe9811..afce98e50 100644 --- a/docs/api/compas_fea2.problem.rst +++ b/docs/api/compas_fea2.problem.rst @@ -1,2 +1,82 @@ +******************************************************************************** +problem +******************************************************************************** -.. automodule:: compas_fea2.problem +.. currentmodule:: compas_fea2.problem + +Problem +======= + +.. autosummary:: + :toctree: generated/ + + Problem + +Steps +===== + +.. autosummary:: + :toctree: generated/ + + _Step + _GeneralStep + _Perturbation + ModalAnalysis + ComplexEigenValue + StaticStep + LinearStaticPerturbation + BucklingAnalysis + DynamicStep + QuasiStaticStep + DirectCyclicStep + +Prescribed Fields +================= + +.. autosummary:: + :toctree: generated/ + + _PrescribedField + PrescribedTemperatureField + +Loads +===== + +.. autosummary:: + :toctree: generated/ + + _Load + PrestressLoad + PointLoad + LineLoad + AreaLoad + GravityLoad + TributaryLoad + HarmonicPointLoad + HarmonicPressureLoad + ThermalLoad + +Displacements +============= + +.. autosummary:: + :toctree: generated/ + + GeneralDisplacement + +Load Patterns +============= + +.. autosummary:: + :toctree: generated/ + + Pattern + +Outputs +======= + +.. autosummary:: + :toctree: generated/ + + FieldOutput + HistoryOutput diff --git a/docs/api/compas_fea2.results.rst b/docs/api/compas_fea2.results.rst index 102f009fe..cc18f2bb9 100644 --- a/docs/api/compas_fea2.results.rst +++ b/docs/api/compas_fea2.results.rst @@ -1,2 +1,11 @@ +******************************************************************************** +results +******************************************************************************** -.. automodule:: compas_fea2.results +.. currentmodule:: compas_fea2.results + +.. autosummary:: + :toctree: generated/ + + Results + NodeFieldResults diff --git a/docs/api/compas_fea2.rst b/docs/api/compas_fea2.rst index dd29a37e2..4b480479a 100644 --- a/docs/api/compas_fea2.rst +++ b/docs/api/compas_fea2.rst @@ -1,2 +1,17 @@ +******************************************************************************** +compas_fea2 +******************************************************************************** -.. automodule:: compas_fea2 +.. currentmodule:: compas_fea2 + + +.. toctree:: + :maxdepth: 1 + + compas_fea2.model + compas_fea2.problem + compas_fea2.results + compas_fea2.job + compas_fea2.postprocess + compas_fea2.utilities + compas_fea2.units diff --git a/docs/api/compas_fea2.units.rst b/docs/api/compas_fea2.units.rst index ab738c6ce..d4be026d2 100644 --- a/docs/api/compas_fea2.units.rst +++ b/docs/api/compas_fea2.units.rst @@ -1,2 +1,5 @@ +******************************************************************************** +Units +******************************************************************************** -.. automodule:: compas_fea2.units +compas_fe2 can use Pint for units consistency. diff --git a/docs/api/compas_fea2.utilities.rst b/docs/api/compas_fea2.utilities.rst index fb238248b..e6117aecf 100644 --- a/docs/api/compas_fea2.utilities.rst +++ b/docs/api/compas_fea2.utilities.rst @@ -1,2 +1,25 @@ +******************************************************************************** +Utilities +******************************************************************************** -.. automodule:: compas_fea2.utilities +.. currentmodule:: compas_fea2.utilities + + +Functions +========= + +.. autosummary:: + :toctree: generated/ + + colorbar + combine_all_sets + group_keys_by_attribute + group_keys_by_attributes + identify_ranges + mesh_from_shell_elements + network_order + normalise_data + principal_stresses + process_data + postprocess + plotvoxels diff --git a/docs/backends.rst b/docs/backends.rst deleted file mode 100644 index 11da3b52e..000000000 --- a/docs/backends.rst +++ /dev/null @@ -1,14 +0,0 @@ -******************************************************************************** -Backends -******************************************************************************** - -In the future, you will find here information about the supported backends... - -Abaqus -====== -.. toctree:: - :maxdepth: 1 - :titlesonly: - :glob: - - backends/** diff --git a/docs/backends/backends.rst b/docs/backends/backends.rst deleted file mode 100644 index 5d0eaa672..000000000 --- a/docs/backends/backends.rst +++ /dev/null @@ -1,5 +0,0 @@ -******************************************************************************** -Abaqus -******************************************************************************** - -Abaqus diff --git a/docs/basics.analysis.rst b/docs/basics.analysis.rst new file mode 100644 index 000000000..e38eb8525 --- /dev/null +++ b/docs/basics.analysis.rst @@ -0,0 +1,3 @@ +**************** +Basics: Analysis +**************** diff --git a/docs/basics.model.rst b/docs/basics.model.rst new file mode 100644 index 000000000..52d1c0f85 --- /dev/null +++ b/docs/basics.model.rst @@ -0,0 +1,3 @@ +************* +Basics: Model +************* diff --git a/docs/basics.problem.rst b/docs/basics.problem.rst new file mode 100644 index 000000000..4f87de385 --- /dev/null +++ b/docs/basics.problem.rst @@ -0,0 +1,3 @@ +*************** +Basics: Problem +*************** diff --git a/docs/conf.py b/docs/conf.py index 765d5f9c5..673e53176 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,56 +1,88 @@ +# flake8: noqa # -*- coding: utf-8 -*- # If your documentation needs a minimal Sphinx version, state it here. # -# needs_sphinx = '1.0' +# needs_sphinx = "1.0" -import sys -import os import inspect import importlib - -import sphinx_compas_theme -from sphinx.ext.napoleon.docstring import NumpyDocstring - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../src')) +import re +import sphinx_compas_theme # this is a temp solution # -- General configuration ------------------------------------------------ -project = 'compas_fea2' -copyright = 'Block Research Group - ETH Zurich' -author = 'Francesco Ranaudo' -release = '0.1.0' -version = '.'.join(release.split('.')[0:2]) - -master_doc = 'index' -source_suffix = ['.rst', ] -templates_path = sphinx_compas_theme.get_autosummary_templates_path() -exclude_patterns = [] +project = "COMPAS FEA2" +copyright = "COMPAS Association" +author = "Francesco Ranaudo" +package = "compas_fea2" +organization = "compas-dev" + + +def get_latest_version(): + with open("../CHANGELOG.md", "r") as file: + content = file.read() + pattern = re.compile(r"## (Unreleased|\[\d+\.\d+\.\d+\])") + versions = pattern.findall(content) + latest_version = versions[0] if versions else None + if latest_version and latest_version.startswith("[") and latest_version.endswith("]"): + latest_version = latest_version[1:-1] + return latest_version + + +latest_version = get_latest_version() +if latest_version == "Unreleased": + release = "Unreleased" + version = "latest" +else: + release = latest_version + version = ".".join(release.split(".")[0:2]) # type: ignore + +master_doc = "index" +source_suffix = { + ".rst": "restructuredtext", + ".md": "markdown", +} +templates_path = sphinx_compas_theme.get_autosummary_templates_path() + ["_templates"] +exclude_patterns = ["_build", "**.ipynb_checkpoints", "_notebooks", "**/__temp"] -pygments_style = 'sphinx' -show_authors = True add_module_names = True -language = 'en' +language = "en" # -- Extension configuration ------------------------------------------------ extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.mathjax', - 'sphinx.ext.napoleon', - 'sphinx.ext.viewcode', - 'sphinx.ext.githubpages', - 'matplotlib.sphinxext.plot_directive', - 'nbsphinx', - # 'sphinxcontrib.gist' + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.mathjax", + "sphinx.ext.extlinks", + "sphinx.ext.githubpages", + "sphinx.ext.coverage", + "sphinx.ext.autodoc.typehints", + "sphinx_design", + "sphinx_inline_tabs", + "sphinx_togglebutton", + "sphinx_remove_toctrees", + "sphinx_copybutton", + "numpydoc", ] +numpydoc_show_class_members = False +numpydoc_class_members_toctree = False +numpydoc_attributes_as_param_list = True + # autodoc options +autodoc_type_aliases = {} + +# this does not work properly yet +# autodoc_typehints = "none" +# autodoc_typehints_format = "short" +autodoc_typehints_description_target = "documented" + autodoc_mock_imports = [ "System", "clr", @@ -61,21 +93,21 @@ "rhinoscriptsyntax", "bpy", "bmesh", - "mathutils" + "mathutils", ] -autodoc_default_flags = [ - 'undoc-members', - 'show-inheritance', -] +autodoc_default_options = { + "undoc-members": True, + "show-inheritance": True, +} -autodoc_member_order = 'alphabetical' +autodoc_member_order = "groupwise" -autoclass_content = 'class' +autoclass_content = "class" def skip(app, what, name, obj, would_skip, options): - if name.startswith('_'): + if name.startswith("_"): return True return would_skip @@ -83,45 +115,31 @@ def skip(app, what, name, obj, would_skip, options): def setup(app): app.connect("autodoc-skip-member", skip) -# autosummary options +# autosummary options autosummary_generate = True +autosummary_mock_imports = [ + "System", + "clr", + "Eto", + "Rhino", + "Grasshopper", + "scriptcontext", + "rhinoscriptsyntax", + "bpy", + "bmesh", + "mathutils", +] -# napoleon options +# graph options -napoleon_google_docstring = True -napoleon_numpy_docstring = True -napoleon_include_init_with_doc = False -napoleon_include_private_with_doc = True -napoleon_include_special_with_doc = True -napoleon_use_admonition_for_examples = False -napoleon_use_admonition_for_notes = False -napoleon_use_admonition_for_references = False -napoleon_use_ivar = False -napoleon_use_param = False -napoleon_use_rtype = False +# plot options +plot_include_source = False plot_html_show_source_link = False plot_html_show_formats = False - -# docstring sections - - -def parse_attributes_section(self, section): - return self._format_fields("Attributes", self._consume_fields()) - - -NumpyDocstring._parse_attributes_section = parse_attributes_section - - -def patched_parse(self): - self._sections["attributes"] = self._parse_attributes_section - self._unpatched_parse() - - -NumpyDocstring._unpatched_parse = NumpyDocstring._parse -NumpyDocstring._parse = patched_parse +plot_formats = ["png"] # intersphinx options @@ -134,71 +152,169 @@ def patched_parse(self): def linkcode_resolve(domain, info): - if domain != 'py': + if domain != "py": return None - if not info['module']: + if not info["module"]: return None - if not info['fullname']: + if not info["fullname"]: return None - package = info['module'].split('.')[0] - if not package.startswith('compas_fea2'): + package = info["module"].split(".")[0] + if not package.startswith(package): return None - module = importlib.import_module(info['module']) - parts = info['fullname'].split('.') + module = importlib.import_module(info["module"]) + parts = info["fullname"].split(".") if len(parts) == 1: - obj = getattr(module, info['fullname']) - filename = inspect.getmodule(obj).__name__.replace('.', '/') + obj = getattr(module, info["fullname"]) + mod = inspect.getmodule(obj) + if not mod: + return None + filename = mod.__name__.replace(".", "/") lineno = inspect.getsourcelines(obj)[1] elif len(parts) == 2: obj_name, attr_name = parts obj = getattr(module, obj_name) attr = getattr(obj, attr_name) if inspect.isfunction(attr): - filename = inspect.getmodule(obj).__name__.replace('.', '/') + mod = inspect.getmodule(attr) + if not mod: + return None + filename = mod.__name__.replace(".", "/") lineno = inspect.getsourcelines(attr)[1] else: return None else: return None - return f"https://github.com/compas-dev/compas_fea2/blob/master/src/{filename}.py#L{lineno}" + return f"https://github.com/{organization}/{package}/blob/main/src/{filename}.py#L{lineno}" + # extlinks -extlinks = {} +extlinks = { + "rhino": ("https://developer.rhino3d.com/api/RhinoCommon/html/T_%s.htm", "%s"), + "blender": ("https://docs.blender.org/api/2.93/%s.html", "%s"), +} -# intersphinx options +# from pytorch -intersphinx_mapping = { - 'python': ('https://docs.python.org/', None), - 'compas': ('https://compas-dev.github.io/main', 'https://compas-dev.github.io/main/objects.inv'), -} +from sphinx.writers import html, html5 + + +def replace(Klass): + old_call = Klass.visit_reference + + def visit_reference(self, node): + if "refuri" in node: + refuri = node.get("refuri") + if "generated" in refuri: + href_anchor = refuri.split("#") + if len(href_anchor) > 1: + href = href_anchor[0] + anchor = href_anchor[1] + page = href.split("/")[-1] + parts = page.split(".") + if parts[-1] == "html": + pagename = ".".join(parts[:-1]) + if anchor == pagename: + node["refuri"] = href + return old_call(self, node) + + Klass.visit_reference = visit_reference +replace(html.HTMLTranslator) +replace(html5.HTML5Translator) + # -- Options for HTML output ---------------------------------------------- -html_theme = 'compaspkg' -html_theme_path = sphinx_compas_theme.get_html_theme_path() +html_theme = "pydata_sphinx_theme" +html_logo = "_static/compas_icon.png" +html_title = project +html_favicon = "_static/compas.ico" html_theme_options = { - 'package_name': 'compas_fea2', - 'package_title': project, - 'package_version': release, - "package_author": "Francesco Ranaudo", - "package_docs": "https://compas-dev.github.io/compas_fea2/", - "package_repo": "https://github.com/compas-dev/compas_fea2", - "package_old_versions_txt": "https://compas-dev.github.io/compas_fea2/doc_versions.txt" + "logo": { + "text": project, + "image_light": "_static/compas_icon.png", + "image_dark": "_static/compas_icon_white.png", + }, + "switcher": { + "json_url": f"https://raw.githubusercontent.com/{organization}/{package}/gh-pages/versions.json", + "version_match": version, + }, + "check_switcher": False, + "navigation_depth": 3, + "show_nav_level": 1, + "show_toc_level": 2, + "pygment_light_style": "default", + "pygment_dark_style": "monokai", +} + +html_theme_options["icon_links"] = [ + { + "name": "GitHub", + "url": f"https://github.com/{organization}/{package}", + "icon": "fa-brands fa-github", + "type": "fontawesome", + }, + { + "name": "Discourse", + "url": "http://forum.compas-framework.org/", + "icon": "fa-brands fa-discourse", + "type": "fontawesome", + }, + { + "name": "PyPI", + "url": f"https://pypi.org/project/{package}/", + "icon": "fa-brands fa-python", + "type": "fontawesome", + }, +] + +html_theme_options["navbar_start"] = [ + "navbar-logo", +] + +html_theme_options["navbar_end"] = [ + "version-switcher", + "theme-switcher", + "navbar-icon-links", +] + +html_theme_options["navbar_persistent"] = ["search-button"] +html_theme_options["navbar_align"] = "content" +html_theme_options["secondary_sidebar_items"] = [ + "page-toc", + "edit-this-page", + "sourcelink", +] + +html_sidebars = { + "**": [ + "sbt-sidebar-nav.html", + "compas-sidebar-footer.html", + ] +} + +html_context = { + "github_url": "https://github.com", + "github_user": organization, + "github_repo": package, + "github_version": "main", + "doc_path": "docs", + "default_theme": "light", } -html_context = {} -html_static_path = sphinx_compas_theme.get_html_static_path() +html_static_path = ["_static"] +html_css_files = ["compas.css"] html_extra_path = [] html_last_updated_fmt = "" html_copy_source = False -html_show_sourcelink = False +html_show_sourcelink = True html_permalinks = False +html_permalinks_icon = "" html_compact_lists = True diff --git a/docs/devguide.rst b/docs/devguide.rst deleted file mode 100644 index 9e833d9e5..000000000 --- a/docs/devguide.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Developer Guide -******************************************************************************** - -.. toctree:: - :maxdepth: 1 - :titlesonly: - :glob: - - devguide/overview - diff --git a/docs/devguide/overview.rst b/docs/devguide/overview.rst deleted file mode 100644 index fd557dec6..000000000 --- a/docs/devguide/overview.rst +++ /dev/null @@ -1,137 +0,0 @@ -******************************************************************************** -Overview -******************************************************************************** - -Some light reading :) - -https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging -https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes - -https://git-scm.com/docs/git-pull -https://git-scm.com/docs/git-rebase - - -Fork the repo -============= - -All contributions have to be made via pull requests from a fork of the repo. -To fork the ``compas_fea2`` repo, go to GitHub, click the "Fork" button -and select an account to host the fork. - - -Clone the fork -============== - -.. code-block:: bash - - git clone https://github.com/Hello World!
""" + # ============================================================================== # linear elastic # ============================================================================== -@extend_docstring(_Material) -class ElasticOrthotropic(_Material): - """ - ElasticOrthotropic material - =========================== - Elastic, orthotropic and homogeneous material - Additional paramenters and attributes: + +class ElasticOrthotropic(Material): + """Elastic, orthotropic and homogeneous material. Parameters ---------- @@ -162,15 +147,26 @@ def __str__(self): Gxy : {} Gyz : {} Gzx : {} -""".format(self.__class__.__name__, len(self.__class__.__name__) * '-', - self.name, self.density, self.expansion, - self.Ex, self.Ey, self.Ez, self.vxy, self.vyz, self.vzx, self.Gxy, self.Gyz, self.Gzx) - -@extend_docstring(_Material) -class ElasticIsotropic(_Material): - """ - Elastic, isotropic and homogeneous material - =========================================== +""".format( + self.__class__.__name__, + len(self.__class__.__name__) * "-", + self.name, + self.density, + self.expansion, + self.Ex, + self.Ey, + self.Ez, + self.vxy, + self.vyz, + self.vzx, + self.Gxy, + self.Gyz, + self.Gzx, + ) + + +class ElasticIsotropic(Material): + """Elastic, isotropic and homogeneous material Parameters ---------- @@ -187,6 +183,7 @@ class ElasticIsotropic(_Material): Poisson's ratio v. G : float Shear modulus (automatically computed from E and v) + """ def __init__(self, *, E, v, density, expansion=None, name=None, **kwargs): @@ -205,15 +202,18 @@ def __str__(self): E : {} v : {} G : {} -""".format(self.name, self.density, self.expansion, self.E, self.v, self.G) +""".format( + self.name, self.density, self.expansion, self.E, self.v, self.G + ) @property def G(self): return 0.5 * self.E / (1 + self.v) -class Stiff(_Material): - """Elastic, very stiff and massless material. - """ + +class Stiff(Material): + """Elastic, very stiff and massless material.""" + def __init__(self, *, density, expansion=None, name=None, **kwargs): raise NotImplementedError() @@ -221,15 +221,10 @@ def __init__(self, *, density, expansion=None, name=None, **kwargs): # ============================================================================== # non-linear general # ============================================================================== -@extend_docstring(_Material) -class ElasticPlastic(ElasticIsotropic): - """ - ElasticPlastic - ============== - Elastic and plastic, isotropic and homogeneous material. - Additional parameters and attributes. +class ElasticPlastic(ElasticIsotropic): + """Elastic and plastic, isotropic and homogeneous material. Parameters ---------- @@ -271,18 +266,22 @@ def __str__(self): G : {} strain_stress : {} -""".format(self.name, self.density, self.expansion, self.E, self.v, self.G, self.strain_stress) +""".format( + self.name, self.density, self.expansion, self.E, self.v, self.G, self.strain_stress + ) # ============================================================================== # User-defined Materials # ============================================================================== + + class UserMaterial(FEAData): - """ User Defined Material. Tho implement this type of material, a + """User Defined Material. Tho implement this type of material, a separate subroutine is required """ def __init__(self, name=None, **kwargs): super(UserMaterial, self).__init__(self, name=name, **kwargs) - raise NotImplementedError('This class is not available for the selected backend plugin') + raise NotImplementedError("This class is not available for the selected backend plugin") diff --git a/src/compas_fea2/model/materials/steel.py b/src/compas_fea2/model/materials/steel.py index 561e30b48..0a0eeecb5 100644 --- a/src/compas_fea2/model/materials/steel.py +++ b/src/compas_fea2/model/materials/steel.py @@ -3,16 +3,14 @@ from __future__ import print_function from compas_fea2 import units -from .material import _Material, ElasticIsotropic +from .material import ElasticIsotropic class Steel(ElasticIsotropic): """Bi-linear steel with given yield stress. - """ - __doc__ += _Material.__doc__ - __doc__ += """ - Additional Parameters - --------------------- + + Parameters + ---------- E : float Young's modulus E. v : float @@ -43,8 +41,8 @@ class Steel(ElasticIsotropic): """ - def __init__(self, *, fy, fu, eu, E, v, density, name=None, **kwargs): - super(Steel, self).__init__(E=E, v=v, density=density, name=name, **kwargs) + def __init__(self, *, fy, fu, eu, E, v, density, **kwargs): + super(Steel, self).__init__(E=E, v=v, density=density, **kwargs) fu = fu or fy @@ -65,8 +63,8 @@ def __init__(self, *, fy, fu, eu, E, v, density, name=None, **kwargs): self.ep = ep self.E = E self.v = v - self.tension = {'f': f, 'e': e} - self.compression = {'f': fc, 'e': ec} + self.tension = {"f": f, "e": e} + self.compression = {"f": fc, "e": ec} def __str__(self): return """ @@ -82,17 +80,19 @@ def __str__(self): v : {:.2f} eu : {:.2f} ep : {:.2f} -""".format(self.name, - (self.density * units['kg/m**2']), - (self.E * units.pascal).to('GPa'), - (self.G * units.pascal).to('GPa'), - (self.fy * units.pascal).to('MPa'), - (self.fu * units.pascal).to('MPa'), - (self.v * units.dimensionless), - (self.eu * units.dimensionless), - (self.ep * units.dimensionless)) +""".format( + self.name, + (self.density * units["kg/m**2"]), + (self.E * units.pascal).to("GPa"), + (self.G * units.pascal).to("GPa"), + (self.fy * units.pascal).to("MPa"), + (self.fu * units.pascal).to("MPa"), + (self.v * units.dimensionless), + (self.eu * units.dimensionless), + (self.ep * units.dimensionless), + ) - #TODO check values and make unit independent + # TODO check values and make unit independent @classmethod def S355(cls): """Steel S355. diff --git a/src/compas_fea2/model/model.py b/src/compas_fea2/model/model.py index 20c092ea3..189914191 100644 --- a/src/compas_fea2/model/model.py +++ b/src/compas_fea2/model/model.py @@ -5,37 +5,30 @@ import importlib import gc import pathlib -from typing import Callable, Iterable, Type -import pint from itertools import groupby -from pathlib import Path, PurePath +from pathlib import Path import os import pickle import compas_fea2 from compas_fea2.utilities._utils import timer -from compas_fea2.utilities._utils import part_method, get_docstring, problem_method, extend_docstring + +# from compas_fea2.utilities._utils import part_method, get_docstring, problem_method from compas_fea2.base import FEAData -from compas_fea2.model.parts import _Part, DeformablePart, RigidPart +from compas_fea2.model.parts import Part, RigidPart from compas_fea2.model.nodes import Node -from compas_fea2.model.elements import _Element -from compas_fea2.model.bcs import _BoundaryCondition -from compas_fea2.model.ics import _InitialCondition, InitialStressField -from compas_fea2.model.groups import _Group, NodesGroup, PartsGroup, ElementsGroup, FacesGroup -from compas_fea2.model.constraints import _Constraint, TieMPC, BeamMPC - +from compas_fea2.model.elements import Element +from compas_fea2.model.bcs import BoundaryCondition +from compas_fea2.model.ics import InitialCondition +from compas_fea2.model.groups import Group, NodesGroup, PartsGroup, ElementsGroup -from compas_fea2.units import units class Model(FEAData): """Class representing an FEA model. Parameters ---------- - name : str, optional - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. description : str, optional Some description of the model, by default ``None``. This will be added to the input file and can be useful for future reference. @@ -45,9 +38,6 @@ class Model(FEAData): Attributes ---------- - name : str - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. description : str Some description of the model. This will be added to the input file and can be useful for future reference. @@ -66,7 +56,7 @@ class Model(FEAData): The constraints of the model. partgroups : Set[:class:`compas_fea2.model.PartsGroup`] The part groups of the model. - materials : Set[:class:`compas_fea2.model.materials._Material] + materials : Set[:class:`compas_fea2.model.materials.Material] The materials assigned in the model. sections : Set[:class:`compas_fea2.model._Section] The sections assigned in the model. @@ -74,10 +64,11 @@ class Model(FEAData): The problems added to the model. path : ::class::`pathlib.Path` Path to the main folder where the problems' results are stored. + """ - def __init__(self, *, name=None, description=None, author=None, **kwargs): - super(Model, self).__init__(name=name, **kwargs) + def __init__(self, description=None, author=None, **kwargs): + super(Model, self).__init__(**kwargs) self.description = description self.author = author self._parts = set() @@ -137,36 +128,37 @@ def loads(self): @property def path(self): return self._path + @path.setter def path(self, value): if not isinstance(value, Path): try: value = Path(value) - except: - raise ValueError('the path provided is not valid.') + except Exception: + raise ValueError("the path provided is not valid.") self._path = value.joinpath(self.name) @property def nodes(self): - node_set=set() + node_set = set() for part in self.parts: node_set.update(part.nodes) return node_set @property def elements(self): - element_set=set() + element_set = set() for part in self.parts: element_set.update(part.elements) return element_set + # ========================================================================= # Constructor methods # ========================================================================= @staticmethod - @timer(message='Model loaded from cfm file in ') + @timer(message="Model loaded from cfm file in ") def from_cfm(path): - # type: (str) -> Model """Imports a Problem object from an .cfm file through Pickle. Parameters @@ -178,19 +170,20 @@ def from_cfm(path): ------- :class:`compas_fea2.model.Model` The imported model. + """ - with open(path, 'rb') as f: + with open(path, "rb") as f: try: # disable garbage collector gc.disable() model = pickle.load(f) # enable garbage collector again gc.enable() - except: + except Exception: gc.enable() - raise RuntimeError('Model not created!') + raise RuntimeError("Model not created!") model.path = os.sep.join(os.path.split(path)[0].split(os.sep)[:-1]) - #check if the problems' results are stored in the same location + # check if the problems' results are stored in the same location for problem in model.problems: if not os.path.exists(os.path.join(model.path, problem.name)): print(f"WARNING! - Problem {problem.name} results not found! move the results folder in {model.path}") @@ -207,7 +200,6 @@ def to_json(self): raise NotImplementedError() def to_cfm(self, path): - # type: (Path) -> None """Exports the Model object to an .cfm file through Pickle. Parameters @@ -218,22 +210,22 @@ def to_cfm(self, path): Returns ------- None + """ if not isinstance(path, Path): path = Path(path) - if not path.suffix == '.cfm': + if not path.suffix == ".cfm": raise ValueError("Please provide a valid path including the name of the file.") pathlib.Path(path.parent.absolute()).mkdir(parents=True, exist_ok=True) - with open(path, 'wb') as f: + with open(path, "wb") as f: pickle.dump(self, f) - print('Model saved to: {}'.format(path)) + print("Model saved to: {}".format(path)) # ========================================================================= # Parts methods # ========================================================================= def find_part_by_name(self, name, casefold=False): - # type: (str, bool) -> DeformablePart """Find if there is a part with a given name in the model. Parameters @@ -255,7 +247,6 @@ def find_part_by_name(self, name, casefold=False): return part def contains_part(self, part): - # type: (DeformablePart) -> DeformablePart """Verify that the model contains a specific part. Parameters @@ -270,16 +261,15 @@ def contains_part(self, part): return part in self.parts def add_part(self, part): - # type: (DeformablePart) -> DeformablePart """Adds a DeformablePart to the Model. Parameters ---------- - part : :class:`compas_fea2.model._Part` + part : :class:`compas_fea2.model.Part` Returns ------- - :class:`compas_fea2.model._Part` + :class:`compas_fea2.model.Part` Raises ------ @@ -287,7 +277,7 @@ def add_part(self, part): If the part is not a part. """ - if not isinstance(part, _Part): + if not isinstance(part, Part): raise TypeError("{!r} is not a part.".format(part)) if self.contains_part(part): @@ -314,7 +304,6 @@ def add_part(self, part): return part def add_parts(self, parts): - # type: (list) -> list """Add multiple parts to the model. Parameters @@ -332,58 +321,58 @@ def add_parts(self, parts): # Nodes methods # ========================================================================= - @get_docstring(_Part) - @part_method + # @get_docstring(Part) + # @part_method def find_node_by_key(self, key): pass - @get_docstring(_Part) - @part_method + # @get_docstring(Part) + # @part_method def find_nodes_by_name(self, name): pass - @get_docstring(_Part) - @part_method + # @get_docstring(Part) + # @part_method def find_nodes_by_location(self, point, distance, plane=None): pass - @get_docstring(_Part) - @part_method + # @get_docstring(Part) + # @part_method def find_closest_nodes_to_point(self, point, distance, number_of_nodes=1, plane=None): pass - @get_docstring(_Part) - @part_method + # @get_docstring(Part) + # @part_method def find_nodes_around_node(self, node, distance): pass - @get_docstring(_Part) - @part_method + # @get_docstring(Part) + # @part_method def find_closest_nodes_to_node(self, node, distance, number_of_nodes=1, plane=None): pass - @get_docstring(_Part) - @part_method + # @get_docstring(Part) + # @part_method def find_nodes_by_attribute(self, attr, value, tolerance): pass - @get_docstring(_Part) - @part_method + # @get_docstring(Part) + # @part_method def find_nodes_on_plane(self, plane): pass - @get_docstring(_Part) - @part_method + # @get_docstring(Part) + # @part_method def find_nodes_in_polygon(self, polygon, tolerance=1.1): pass - @get_docstring(_Part) - @part_method + # @get_docstring(Part) + # @part_method def find_nodes_where(self, conditions): pass - @get_docstring(_Part) - @part_method + # @get_docstring(Part) + # @part_method def contains_node(self, node): pass @@ -391,19 +380,20 @@ def contains_node(self, node): # Nodes methods # ========================================================================= - @get_docstring(_Part) - @part_method + # @get_docstring(Part) + # @part_method def find_element_by_key(self, key): pass - @get_docstring(_Part) - @part_method + # @get_docstring(Part) + # @part_method def find_elements_by_name(self, name): pass # ========================================================================= # Groups methods # ========================================================================= + def add_parts_group(self, group): """Add a PartsGroup object to the Model. @@ -416,9 +406,10 @@ def add_parts_group(self, group): ------- :class:`compas_fea2.model.PartsGroup` The added group. + """ if not isinstance(group, PartsGroup): - raise TypeError('Only PartsGroups can be added to a model') + raise TypeError("Only PartsGroups can be added to a model") self.partgroups.add(group) group._registration = self # FIXME wrong because the members of the group might have a different registation return group @@ -435,6 +426,7 @@ def add_parts_groups(self, groups): ------- [:class:`compas_fea2.model.PartsGroup`] The list with the added groups. + """ return [self.add_parts_group(group) for group in groups] @@ -452,66 +444,75 @@ def group_parts_where(self, attr, value): ------- :class:`compas_fea2.model.PartsGroup` The group with the matching parts. + """ return self.add_parts_group(PartsGroup(parts=set(filter(lambda p: getattr(p, attr) == value), self.parts))) - # ========================================================================= # BCs methods # ========================================================================= - def add_bcs(self, bc, nodes, axes='global'): - # type: (_BoundaryCondition, Node, str) -> _BoundaryCondition - """Add a :class:`compas_fea2.model._BoundaryCondition` to the model. - - Note - ---- - Currently global axes are used in the Boundary Conditions definition. + def add_bcs(self, bc, nodes, axes="global"): + """Add a :class:`compas_fea2.model.BoundaryCondition` to the model. Parameters ---------- - bc : :class:`compas_fea2.model._BoundaryCondition` + bc : :class:`compas_fea2.model.BoundaryCondition` Boundary condition object to add to the model. nodes : list[:class:`compas_fea2.model.Node`] or :class:`compas_fea2.model.NodesGroup` List or Group with the nodes where the boundary condition is assigned. Returns ------- - :class:`compas_fea2.model._BoundaryCondition` + :class:`compas_fea2.model.BoundaryCondition` + + Notes + ----- + Currently global axes are used in the Boundary Conditions definition. """ - if isinstance(nodes, _Group): + if isinstance(nodes, Group): nodes = nodes._members if isinstance(nodes, Node): nodes = [nodes] - if not isinstance(bc, _BoundaryCondition): - raise TypeError('{!r} is not a Boundary Condition.'.format(bc)) + if not isinstance(bc, BoundaryCondition): + raise TypeError("{!r} is not a Boundary Condition.".format(bc)) for node in nodes: if not isinstance(node, Node): - raise TypeError('{!r} is not a Node.'.format(node)) + raise TypeError("{!r} is not a Node.".format(node)) if not node.part: - raise ValueError('{!r} is not registered to any part.'.format(node)) - elif not node.part in self.parts: - raise ValueError('{!r} belongs to a part not registered to this model.'.format(node)) + raise ValueError("{!r} is not registered to any part.".format(node)) + elif node.part not in self.parts: + raise ValueError("{!r} belongs to a part not registered to this model.".format(node)) if isinstance(node.part, RigidPart): if len(nodes) != 1 or not node.is_reference: - raise ValueError('For rigid parts bundary conditions can be assigned only to the reference point') + raise ValueError("For rigid parts bundary conditions can be assigned only to the reference point") node._bc = bc - self._bcs[bc]=set(nodes) + self._bcs[bc] = set(nodes) bc._registration = self return bc - def _add_bc_type(self, bc_type, nodes, axes='global'): - # type: (str, Node, str) -> _BoundaryCondition - """Add a :class:`compas_fea2.model._BoundaryCondition` by type. + def _add_bc_type(self, bc_type, nodes, axes="global"): + """Add a :class:`compas_fea2.model.BoundaryCondition` by type. - Note - ---- + Parameters + ---------- + name : str + name of the boundary condition + bc_type : str + one of the boundary condition types specified above + nodes : list[:class:`compas_fea2.model.Node`] or :class:`compas_fea2.model.NodesGroup` + List or Group with the nodes where the boundary condition is assigned. + axes : str, optional + [axes of the boundary condition, by default 'global' + + Notes + ----- The bc_type must be one of the following: .. csv-table:: @@ -529,29 +530,25 @@ def _add_bc_type(self, bc_type, nodes, axes='global'): rollerYZ, :class:`compas_fea2.model.bcs.RollerBCYZ` rollerXZ, :class:`compas_fea2.model.bcs.RollerBCXZ` - - Parameters - ---------- - name : str - name of the boundary condition - bc_type : str - one of the boundary condition types specified above - nodes : list[:class:`compas_fea2.model.Node`] or :class:`compas_fea2.model.NodesGroup` - List or Group with the nodes where the boundary condition is assigned. - axes : str, optional - [axes of the boundary condition, by default 'global' """ - types = {'fix': 'FixedBC', 'fixXX': 'FixedBCXX', 'fixYY': 'FixedBCYY', - 'fixZZ': 'FixedBCZZ', 'pin': 'PinnedBC', 'rollerX': 'RollerBCX', - 'rollerY': 'RollerBCY', 'rollerZ': 'RollerBCZ', 'rollerXY': 'RollerBCXY', - 'rollerYZ': 'RollerBCYZ', 'rollerXZ': 'RollerBCXZ', - } - m = importlib.import_module('compas_fea2.model.bcs') + types = { + "fix": "FixedBC", + "fixXX": "FixedBCXX", + "fixYY": "FixedBCYY", + "fixZZ": "FixedBCZZ", + "pin": "PinnedBC", + "rollerX": "RollerBCX", + "rollerY": "RollerBCY", + "rollerZ": "RollerBCZ", + "rollerXY": "RollerBCXY", + "rollerYZ": "RollerBCYZ", + "rollerXZ": "RollerBCXZ", + } + m = importlib.import_module("compas_fea2.model.bcs") bc = getattr(m, types[bc_type])() return self.add_bcs(bc, nodes, axes) - def add_fix_bc(self, nodes, axes='global'): - # type: (Node, str) -> _BoundaryCondition + def add_fix_bc(self, nodes, axes="global"): """Add a :class:`compas_fea2.model.FixedBC` to the nodes in a part. Parameters @@ -562,11 +559,11 @@ def add_fix_bc(self, nodes, axes='global'): List or Group with the nodes where the boundary condition is assigned. axes : str, optional [axes of the boundary condition, by default 'global' + """ - return self._add_bc_type('fix', nodes, axes) + return self._add_bc_type("fix", nodes, axes) - def add_pin_bc(self, nodes, axes='global'): - # type: (Node, str) -> _BoundaryCondition + def add_pin_bc(self, nodes, axes="global"): """Add a pinned boundary condition type to some nodes in a part. Parameters @@ -577,11 +574,11 @@ def add_pin_bc(self, nodes, axes='global'): List or Group with the nodes where the boundary condition is assigned. axes : str, optional [axes of the boundary condition, by default 'global' + """ - return self._add_bc_type('pin', nodes, axes) + return self._add_bc_type("pin", nodes, axes) - def add_clampXX_bc(self, nodes, axes='global'): - # type: (Node, str) -> _BoundaryCondition + def add_clampXX_bc(self, nodes, axes="global"): """Add a fixed boundary condition type free about XX to some nodes in a part. Parameters @@ -592,11 +589,11 @@ def add_clampXX_bc(self, nodes, axes='global'): List or Group with the nodes where the boundary condition is assigned. axes : str, optional [axes of the boundary condition, by default 'global' + """ - return self._add_bc_type('clampXX', nodes, axes) + return self._add_bc_type("clampXX", nodes, axes) - def add_clampYY_bc(self, nodes, axes='global'): - # type: (Node, str) -> _BoundaryCondition + def add_clampYY_bc(self, nodes, axes="global"): """Add a fixed boundary condition free about YY type to some nodes in a part. Parameters @@ -607,11 +604,11 @@ def add_clampYY_bc(self, nodes, axes='global'): List or Group with the nodes where the boundary condition is assigned. axes : str, optional [axes of the boundary condition, by default 'global' + """ - return self._add_bc_type('clampYY', nodes, axes) + return self._add_bc_type("clampYY", nodes, axes) - def add_clampZZ_bc(self, nodes, axes='global'): - # type: (Node, str) -> _BoundaryCondition + def add_clampZZ_bc(self, nodes, axes="global"): """Add a fixed boundary condition free about ZZ type to some nodes in a part. Parameters @@ -622,11 +619,11 @@ def add_clampZZ_bc(self, nodes, axes='global'): List or Group with the nodes where the boundary condition is assigned. axes : str, optional [axes of the boundary condition, by default 'global' + """ - return self._add_bc_type('clampZZ', nodes, axes) + return self._add_bc_type("clampZZ", nodes, axes) - def add_rollerX_bc(self, nodes, axes='global'): - # type: (Node, str) -> _BoundaryCondition + def add_rollerX_bc(self, nodes, axes="global"): """Add a roller free on X boundary condition type to some nodes in a part. Parameters @@ -637,11 +634,11 @@ def add_rollerX_bc(self, nodes, axes='global'): List or Group with the nodes where the boundary condition is assigned. axes : str, optional [axes of the boundary condition, by default 'global' + """ - return self._add_bc_type('rollerX', nodes, axes) + return self._add_bc_type("rollerX", nodes, axes) - def add_rollerY_bc(self, nodes, axes='global'): - # type: (Node, str) -> _BoundaryCondition + def add_rollerY_bc(self, nodes, axes="global"): """Add a roller free on Y boundary condition type to some nodes in a part. Parameters @@ -652,11 +649,11 @@ def add_rollerY_bc(self, nodes, axes='global'): List or Group with the nodes where the boundary condition is assigned. axes : str, optional [axes of the boundary condition, by default 'global' + """ - return self._add_bc_type('rollerY', nodes, axes) + return self._add_bc_type("rollerY", nodes, axes) - def add_rollerZ_bc(self, nodes, axes='global'): - # type: (Node, str) -> _BoundaryCondition + def add_rollerZ_bc(self, nodes, axes="global"): """Add a roller free on Z boundary condition type to some nodes in a part. Parameters @@ -667,11 +664,11 @@ def add_rollerZ_bc(self, nodes, axes='global'): List or Group with the nodes where the boundary condition is assigned. axes : str, optional [axes of the boundary condition, by default 'global' + """ - return self._add_bc_type('rollerZ', nodes, axes) + return self._add_bc_type("rollerZ", nodes, axes) - def add_rollerXY_bc(self, nodes, axes='global'): - # type: (Node, str) -> _BoundaryCondition + def add_rollerXY_bc(self, nodes, axes="global"): """Add a roller free on XY boundary condition type to some nodes in a part. Parameters @@ -682,11 +679,11 @@ def add_rollerXY_bc(self, nodes, axes='global'): List or Group with the nodes where the boundary condition is assigned. axes : str, optional [axes of the boundary condition, by default 'global' + """ - return self._add_bc_type('rollerXY', nodes, axes) + return self._add_bc_type("rollerXY", nodes, axes) - def add_rollerXZ_bc(self, nodes, axes='global'): - # type: (Node, str) -> _BoundaryCondition + def add_rollerXZ_bc(self, nodes, axes="global"): """Add a roller free on XZ boundary condition type to some nodes in a part. Parameters @@ -697,11 +694,11 @@ def add_rollerXZ_bc(self, nodes, axes='global'): List or Group with the nodes where the boundary condition is assigned. axes : str, optional [axes of the boundary condition, by default 'global' + """ - return self._add_bc_type('rollerXZ', nodes, axes) + return self._add_bc_type("rollerXZ", nodes, axes) - def add_rollerYZ_bc(self, nodes, axes='global'): - # type: (Node, str) -> _BoundaryCondition + def add_rollerYZ_bc(self, nodes, axes="global"): """Add a roller free on YZ boundary condition type to some nodes in a part. Parameters @@ -712,8 +709,9 @@ def add_rollerYZ_bc(self, nodes, axes='global'): List or Group with the nodes where the boundary condition is assigned. axes : str, optional [axes of the boundary condition, by default 'global' + """ - return self._add_bc_type('rollerYZ', nodes, axes) + return self._add_bc_type("rollerYZ", nodes, axes) def remove_bcs(self, nodes): """Release a node previously restrained. @@ -726,6 +724,7 @@ def remove_bcs(self, nodes): Returns ------- None + """ if isinstance(nodes, Node): @@ -738,7 +737,6 @@ def remove_bcs(self, nodes): else: print("WARNING: {!r} was not restrained. skipped!".format(node)) - def remove_all_bcs(self): """Removes all the boundary conditions from the Model. @@ -749,6 +747,7 @@ def remove_all_bcs(self): Returns ------- None + """ for _, nodes in self.bcs.items(): self.remove_bcs(nodes) @@ -759,32 +758,31 @@ def remove_all_bcs(self): # ============================================================================== def _add_ics(self, ic, group): - # type: (_InitialCondition, _Group, str) -> list - """Add a :class:`compas_fea2.model._InitialCondition` to the model. + """Add a :class:`compas_fea2.model.InitialCondition` to the model. Parameters ---------- - ic : :class:`compas_fea2.model._InitialCondition` + ic : :class:`compas_fea2.model.InitialCondition` Initial condition object to add to the model. - group : :class:`compas_fea2.model._Group` + group : :class:`compas_fea2.model.Group` Group of Nodes/Elements where the initial condition is assigned. Returns ------- - :class:`compas_fea2.model._InitialCondition` + :class:`compas_fea2.model.InitialCondition` """ group.part.add_group(group) - if not isinstance(ic, _InitialCondition): - raise TypeError('{!r} is not a _InitialCondition.'.format(ic)) + if not isinstance(ic, InitialCondition): + raise TypeError("{!r} is not a InitialCondition.".format(ic)) for member in group.members: - if not isinstance(member, (Node, _Element)): - raise TypeError('{!r} is not a Node or an Element.'.format(member)) + if not isinstance(member, (Node, Element)): + raise TypeError("{!r} is not a Node or an Element.".format(member)) if not member.part: - raise ValueError('{!r} is not registered to any part.'.format(member)) - elif not member.part in self.parts: - raise ValueError('{!r} belongs to a part not registered to this model.'.format(member)) + raise ValueError("{!r} is not registered to any part.".format(member)) + elif member.part not in self.parts: + raise ValueError("{!r} belongs to a part not registered to this model.".format(member)) member._ic = ic self._ics[ic] = group.members @@ -792,55 +790,51 @@ def _add_ics(self, ic, group): return ic - def add_nodes_ics(self, ic, nodes): - # type: (_InitialCondition, Node, str) -> list - """Add a :class:`compas_fea2.model._InitialCondition` to the model. + """Add a :class:`compas_fea2.model.InitialCondition` to the model. Parameters ---------- - ic : :class:`compas_fea2.model._InitialCondition` + ic : :class:`compas_fea2.model.InitialCondition` Initial condition object to add to the model. nodes : list[:class:`compas_fea2.model.Node`] or :class:`compas_fea2.model.NodesGroup` List or Group with the nodes where the initial condition is assigned. Returns ------- - list[:class:`compas_fea2.model._InitialCondition`] + list[:class:`compas_fea2.model.InitialCondition`] """ if not isinstance(nodes, NodesGroup): - raise TypeError('{} is not a group of nodes'.format(nodes)) + raise TypeError("{} is not a group of nodes".format(nodes)) self._add_ics(ic, nodes) return ic def add_elements_ics(self, ic, elements): - # type: (_InitialCondition, Node, str) -> list - """Add a :class:`compas_fea2.model._InitialCondition` to the model. + """Add a :class:`compas_fea2.model.InitialCondition` to the model. Parameters ---------- - ic : :class:`compas_fea2.model._InitialCondition` + ic : :class:`compas_fea2.model.InitialCondition` Initial condition object to add to the model. elements : :class:`compas_fea2.model.ElementsGroup` List or Group with the elements where the initial condition is assigned. Returns ------- - :class:`compas_fea2.model._InitialCondition` + :class:`compas_fea2.model.InitialCondition` """ if not isinstance(elements, ElementsGroup): - raise TypeError('{} is not a group of elements'.format(elements)) + raise TypeError("{} is not a group of elements".format(elements)) self._add_ics(ic, elements) return ic # ============================================================================== # Summary # ============================================================================== - # TODO add shor/ long + def summary(self): - # type: () -> str """Prints a summary of the Model object. Parameters @@ -851,25 +845,41 @@ def summary(self): ------- str Model summary - """ - parts_info = ['\n'.join(['{}'.format(part.name), - ' # of nodes: {}'.format(len(part.nodes)), - ' # of elements: {}'.format(len(part.elements)), - ' is_rigid : {}'.format('True' if isinstance(part, RigidPart) else 'False')]) for part in self.parts] - constraints_info = '\n'.join([e.__repr__() for e in self.constraints]) + """ + parts_info = [ + "\n".join( + [ + "{}".format(part.name), + " # of nodes: {}".format(len(part.nodes)), + " # of elements: {}".format(len(part.elements)), + " is_rigid : {}".format("True" if isinstance(part, RigidPart) else "False"), + ] + ) + for part in self.parts + ] + + constraints_info = "\n".join([e.__repr__() for e in self.constraints]) bc_info = [] for bc, nodes in self.bcs.items(): for part, part_nodes in groupby(nodes, lambda n: n.part): - bc_info.append('{}: \n{}'.format(part.name, '\n'.join([' {!r} - # of restrained nodes {}'.format(bc, len(list(part_nodes)))]))) - bc_info = '\n'.join(bc_info) + bc_info.append( + "{}: \n{}".format( + part.name, "\n".join([" {!r} - # of restrained nodes {}".format(bc, len(list(part_nodes)))]) + ) + ) + bc_info = "\n".join(bc_info) ic_info = [] for ic, nodes in self.ics.items(): for part, part_nodes in groupby(nodes, lambda n: n.part): - ic_info.append('{}: \n{}'.format(part.name, '\n'.join([' {!r} - # of restrained nodes {}'.format(ic, len(list(part_nodes)))]))) - ic_info = '\n'.join(ic_info) + ic_info.append( + "{}: \n{}".format( + part.name, "\n".join([" {!r} - # of restrained nodes {}".format(ic, len(list(part_nodes)))]) + ) + ) + ic_info = "\n".join(ic_info) data = """ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -894,14 +904,15 @@ def summary(self): Initial Conditions ------------------ {} -""".format(self.name, - self.description or 'N/A', - self.author or 'N/A', - '\n'.join(parts_info), - constraints_info or 'N/A', - bc_info or 'N/A', - ic_info or 'N/A' - ) +""".format( + self.name, + self.description or "N/A", + self.author or "N/A", + "\n".join(parts_info), + constraints_info or "N/A", + bc_info or "N/A", + ic_info or "N/A", + ) print(data) return data @@ -909,32 +920,31 @@ def summary(self): # Save model file # ============================================================================== - def check(self, type='quick'): + def check(self, type="quick"): """Check for possible problems in the model - Warning - ------- - WIP! It is better if you check yourself... + Parameters + ---------- + type : str, optional + *quick* or *deep* check, by default 'quick' - Parameters - ---------- - type : str, optional - *quick* or *deep* check, by default 'quick' + Returns + ------- + str + report - Returns - ------- - str - report - """ + Warning + ------- + WIP! It is better if you check yourself... + + """ def _check_units(self): - """Check if the units are consistent. - """ + """Check if the units are consistent.""" raise NotImplementedError def _check_bcs(self): - """Check if the units are consistent. - """ + """Check if the units are consistent.""" raise NotImplementedError raise NotImplementedError @@ -960,9 +970,10 @@ def add_problem(self, problem): ------ TypeError if problem is not type :class:`compas_fea2.problem.Problem` + """ if not isinstance(problem, compas_fea2.problem.Problem): - raise TypeError('{} is not a Problem'.format(problem)) + raise TypeError("{} is not a Problem".format(problem)) self._problems.add(problem) problem._registration = self return problem @@ -984,6 +995,7 @@ def add_problems(self, problems): ------ TypeError if a problem is not type :class:`compas_fea2.problem.Problem` + """ return [self.add_problem(problem) for problem in problems] @@ -999,6 +1011,7 @@ def find_problem_by_name(self, name): ------- :class:`compas_fea2.problem.Problem` The problem + """ for problem in self.problems: if problem.name == name: @@ -1009,84 +1022,96 @@ def find_problem_by_name(self, name): # ========================================================================= # @get_docstring(Problem) - @problem_method + # # @problem_method def write_input_file(self, problems=None, path=None, *args, **kwargs): pass - #@get_docstring(Problem) - @problem_method + # @get_docstring(Problem) + # # @problem_method def analyse(self, problems=None, *args, **kwargs): pass - #@get_docstring(Problem) - @problem_method + # @get_docstring(Problem) + # # @problem_method def analyze(self, problems=None, *args, **kwargs): pass - #@get_docstring(Problem) - @problem_method + # @get_docstring(Problem) + # # @problem_method def restart_analysis(self, problem, start, steps, **kwargs): pass - #@get_docstring(Problem) - @problem_method + # @get_docstring(Problem) + # # @problem_method def analyse_and_extract(self, problems=None, path=None, *args, **kwargs): pass - #@get_docstring(Problem) - @problem_method + # @get_docstring(Problem) + # # @problem_method def analyse_and_store(self, problems=None, memory_only=False, *args, **kwargs): pass - #@get_docstring(Problem) - @problem_method + # @get_docstring(Problem) + # # @problem_method def store_results_in_model(self, problems=None, *args, **kwargs): pass # ============================================================================== # Results methods # ============================================================================== - #@get_docstring(Problem) - @problem_method + + # @get_docstring(Problem) + # # @problem_method def get_reaction_forces_sql(self, *, problem=None, step=None): pass - #@get_docstring(Problem) - @problem_method + # @get_docstring(Problem) + # # @problem_method def get_reaction_moments_sql(self, problem, step=None): pass - #@get_docstring(Problem) - @problem_method + # @get_docstring(Problem) + # @problem_method def get_displacements_sql(self, problem, step=None): pass - #@get_docstring(Problem) - @problem_method - def get_max_displacement_sql(self, problem, step=None, component='magnitude'): + # @get_docstring(Problem) + # @problem_method + def get_max_displacement_sql(self, problem, step=None, component="magnitude"): pass - #@get_docstring(Problem) - @problem_method - def get_min_displacement_sql(self, problem, step=None, component='magnitude'): + # @get_docstring(Problem) + # @problem_method + def get_min_displacement_sql(self, problem, step=None, component="magnitude"): pass - #@get_docstring(Problem) - @problem_method + # @get_docstring(Problem) + # @problem_method def get_displacement_at_nodes_sql(self, problem, nodes, steps=None): pass - @problem_method + # @problem_method def get_displacement_at_point_sql(self, problem, point, steps=None): pass # ============================================================================== # Viewer # ============================================================================== - def show(self, width=1600, height=900, scale_factor=1., parts=None, - solid=True, draw_nodes=False, node_labels=False, - draw_bcs=1., draw_constraints=True, **kwargs): - """WIP + + def show( + self, + width=1600, + height=900, + scale_factor=1.0, + parts=None, + solid=True, + draw_nodes=False, + node_labels=False, + draw_bcs=1.0, + draw_constraints=True, + **kwargs, + ): + """Visualise the model in the viewer. Parameters ---------- @@ -1108,19 +1133,16 @@ def show(self, width=1600, height=900, scale_factor=1., parts=None, _description_, by default 1. draw_constraints : bool, optional _description_, by default True + """ from compas_fea2.UI.viewer import FEA2Viewer - from compas.geometry import Point, Vector parts = parts or self.parts v = FEA2Viewer(width, height, scale_factor=scale_factor) - v.draw_parts(parts, - draw_nodes, - node_labels, - solid) + v.draw_parts(parts, draw_nodes, node_labels, solid) if draw_bcs: v.draw_bcs(self, parts, draw_bcs) @@ -1130,6 +1152,6 @@ def show(self, width=1600, height=900, scale_factor=1., parts=None, v.show() - @problem_method + # @problem_method def show_displacements(self, problem, *args, **kwargs): pass diff --git a/src/compas_fea2/model/nodes.py b/src/compas_fea2/model/nodes.py index 1a01108ce..aa307467e 100644 --- a/src/compas_fea2/model/nodes.py +++ b/src/compas_fea2/model/nodes.py @@ -6,17 +6,11 @@ from compas.geometry import Point from compas_fea2.base import FEAData -from .bcs import _BoundaryCondition import compas_fea2 -class Node(FEAData): - """Initialises base Node object. - Note - ---- - Nodes are registered to a :class:`compas_fea2.model.DeformablePart` object and can - belong to only one Part. Every time a node is added to a Part, it gets - registered to that Part. +class Node(FEAData): + """Class representing a Node object. Parameters ---------- @@ -69,6 +63,12 @@ class Node(FEAData): temperature : float The temperature at the Node. + Notes + ----- + Nodes are registered to a :class:`compas_fea2.model.DeformablePart` object and can + belong to only one Part. Every time a node is added to a Part, it gets + registered to that Part. + Examples -------- >>> node = Node(xyz=(1.0, 2.0, 3.0)) @@ -85,9 +85,9 @@ def __init__(self, xyz, mass=None, temperature=None, name=None, **kwargs): self._z = xyz[2] self._bc = None - self._dof = {'x': True, 'y': True, 'z': True, 'xx': True, 'yy': True, 'zz': True} + self._dof = {"x": True, "y": True, "z": True, "xx": True, "yy": True, "zz": True} - self._mass = mass if isinstance(mass, tuple) else tuple([mass]*3) + self._mass = mass if isinstance(mass, tuple) else tuple([mass] * 3) self._temperature = temperature self._on_boundary = None @@ -115,8 +115,8 @@ def xyz(self): @xyz.setter def xyz(self, value): - if len(value)!=3: - raise ValueError('Provide a 3 element touple or list') + if len(value) != 3: + raise ValueError("Provide a 3 element touple or list") self._x = value[0] self._y = value[1] self._z = value[2] @@ -151,7 +151,7 @@ def mass(self): @mass.setter def mass(self, value): - self._mass = value if isinstance(value, tuple) else tuple([value]*3) + self._mass = value if isinstance(value, tuple) else tuple([value] * 3) @property def temperature(self): @@ -168,7 +168,7 @@ def gkey(self): @property def dof(self): if self.bc: - return {attr: not bool(getattr(self.bc, attr)) for attr in ['x', 'y', 'z', 'xx', 'yy', 'zz']} + return {attr: not bool(getattr(self.bc, attr)) for attr in ["x", "y", "z", "xx", "yy", "zz"]} else: return self._dof diff --git a/src/compas_fea2/model/parts.py b/src/compas_fea2/model/parts.py index f67b00ccb..bb7dd7f2c 100644 --- a/src/compas_fea2/model/parts.py +++ b/src/compas_fea2/model/parts.py @@ -3,35 +3,38 @@ from __future__ import print_function from math import sqrt -from compas.geometry import Point, Plane, Frame, Polygon +from compas.geometry import Point, Plane, Frame from compas.geometry import Transformation, Scale -from compas.geometry import normalize_vector from compas.geometry import distance_point_point_sqrd from compas.geometry import is_point_in_polygon_xy from compas.geometry import is_point_on_plane from compas.utilities import geometric_key from compas.geometry import Vector -from compas.geometry import sum_vectors import compas_fea2 from compas_fea2.base import FEAData from .nodes import Node -from .elements import _Element, _Element2D, _Element1D, _Element3D, BeamElement, HexahedronElement, ShellElement, TetrahedronElement, Face -from .materials import _Material -from .sections import _Section, ShellSection, SolidSection -from .releases import _BeamEndRelease, BeamEndPinRelease +from .elements import ( + Element, + Element2D, + Element1D, + Element3D, + BeamElement, + HexahedronElement, + ShellElement, + TetrahedronElement, +) +from .materials import Material +from .sections import Section, ShellSection, SolidSection +from .releases import BeamEndRelease from .groups import NodesGroup, ElementsGroup, FacesGroup -from .ics import InitialStressField from compas_fea2.utilities._utils import timer -class _Part(FEAData): - """ - Note - ---- - Parts are registered to a :class:`compas_fea2.model.Model`. +class Part(FEAData): + """Base class for Parts. Parameters ---------- @@ -52,13 +55,13 @@ class _Part(FEAData): Number of nodes in the part. gkey_node : {gkey : :class:`compas_fea2.model.Node`} Dictionary that associates each node and its geometric key} - materials : Set[:class:`compas_fea2.model._Material`] + materials : Set[:class:`compas_fea2.model.Material`] The materials belonging to the part. - sections : Set[:class:`compas_fea2.model._Section`] + sections : Set[:class:`compas_fea2.model.Section`] The sections belonging to the part. - elements : Set[:class:`compas_fea2.model._Element`] + elements : Set[:class:`compas_fea2.model.Element`] The elements belonging to the part. - element_types : {:class:`compas_fea2.model._Element` : [:class:`compas_fea2.model._Element`]] + element_types : {:class:`compas_fea2.model.Element` : [:class:`compas_fea2.model.Element`]] Dictionary with the elements of the part for each element type. element_count : int Number of elements in the part @@ -72,10 +75,15 @@ class _Part(FEAData): The outer boundary mesh enveloping the Part. discretized_boundary_mesh : :class:`compas.datastructures.Mesh` The discretized outer boundary mesh enveloping the Part. + + Notes + ----- + Parts are registered to a :class:`compas_fea2.model.Model`. + """ def __init__(self, name=None, **kwargs): - super(_Part, self).__init__(name=name, **kwargs) + super(Part, self).__init__(name=name, **kwargs) self._nodes = set() self._gkey_node = {} self._sections = set() @@ -137,7 +145,7 @@ def discretized_boundary_mesh(self): @property def volume(self): - self._volume = 0. + self._volume = 0.0 for element in self.elements: if element.volume: self._volume += element.volume @@ -153,40 +161,39 @@ def results(self): @property def nodes_count(self): - return len(self.nodes)-1 + return len(self.nodes) - 1 @property def elements_count(self): - return len(self.elements)-1 + return len(self.elements) - 1 @property def element_types(self): element_types = {} for element in self.elements: - element_types.setdefault(type(element),[]).append(element) + element_types.setdefault(type(element), []).append(element) return element_types + # def __str__(self): + # return """ + # {} + # {} + # name : {} -# def __str__(self): -# return """ -# {} -# {} -# name : {} - -# number of elements : {} -# number of nodes : {} -# """.format(self.__class__.__name__, -# len(self.__class__.__name__) * '-', -# self.name, -# self.elements_count, -# self.nodes_count) + # number of elements : {} + # number of nodes : {} + # """.format(self.__class__.__name__, + # len(self.__class__.__name__) * '-', + # self.name, + # self.elements_count, + # self.nodes_count) # ========================================================================= # Constructor methods # ========================================================================= @classmethod - def from_compas_line(cls, line, element_model='BeamElement', section=None, name=None, **kwargs): + def from_compas_line(cls, line, element_model="BeamElement", section=None, name=None, **kwargs): """Generate a part from a class:`compas.geometry.Line`. Parameters @@ -204,19 +211,22 @@ def from_compas_line(cls, line, element_model='BeamElement', section=None, name= ------- class:`compas_fea2.model.Part` The part. + """ import compas_fea2 + prt = cls(name=name) element = getattr(compas_fea2.model, element_model)(nodes=[Node(line.start), Node(line.end)], section=section) - if not isinstance(element, _Element1D): + if not isinstance(element, Element1D): raise ValueError("Provide a 1D element") prt.add_element(element) return prt @classmethod - @timer(message='compas Mesh successfully imported in ') + @timer(message="compas Mesh successfully imported in ") def shell_from_compas_mesh(cls, mesh, section, name=None, **kwargs): """Creates a DeformablePart object from a :class:`compas.datastructures.Mesh`. + To each face of the mesh is assigned a :class:`compas_fea2.model.ShellElement` objects. Currently, the same section is applied to all the elements. @@ -231,7 +241,7 @@ def shell_from_compas_mesh(cls, mesh, section, name=None, **kwargs): automatically. """ - implementation = kwargs.get('implementation', None) + implementation = kwargs.get("implementation", None) part = cls(name, **kwargs) vertex_node = {vertex: part.add_node(Node(mesh.vertex_coordinates(vertex))) for vertex in mesh.vertices()} @@ -250,8 +260,8 @@ def shell_from_compas_mesh(cls, mesh, section, name=None, **kwargs): # @timer(message='part successfully imported from gmsh model in ') def from_gmsh(cls, gmshModel, name=None, **kwargs): """Create a Part object from a gmshModel object. According to the - `section` type provided, :class:`compas_fea2.model._Element2D` or - :class:`compas_fea2.model._Element3D` elements are cretated. + `section` type provided, :class:`compas_fea2.model.Element2D` or + :class:`compas_fea2.model.Element3D` elements are cretated. The same section is applied to all the elements. Note @@ -284,7 +294,7 @@ def from_gmsh(cls, gmshModel, name=None, **kwargs): Returns ------- - :class:`compas_fea2.model._Part` + :class:`compas_fea2.model.Part` The part meshed. References @@ -304,47 +314,44 @@ def from_gmsh(cls, gmshModel, name=None, **kwargs): part = cls(name=name) # add nodes gmsh_nodes = gmshModel.mesh.get_nodes() - node_coords = gmsh_nodes[1].reshape((-1, 3), order='C') + node_coords = gmsh_nodes[1].reshape((-1, 3), order="C") fea2_nodes = [part.add_node(Node(coords.tolist())) for coords in node_coords] # add elements gmsh_elements = gmshModel.mesh.get_elements() - section = kwargs.get('section', None) - split = kwargs.get('split', False) - verbose = kwargs.get('verbose', False) - rigid = kwargs.get('rigid', False) - implementation = kwargs.get('implementation', None) + section = kwargs.get("section", None) + split = kwargs.get("split", False) + verbose = kwargs.get("verbose", False) + rigid = kwargs.get("rigid", False) + implementation = kwargs.get("implementation", None) dimension = 2 if isinstance(section, SolidSection) else 1 - ntags_per_element = np.split(gmsh_elements[2][dimension]-1, - len(gmsh_elements[1][dimension])) # gmsh keys start from 1 + ntags_per_element = np.split( + gmsh_elements[2][dimension] - 1, len(gmsh_elements[1][dimension]) + ) # gmsh keys start from 1 for ntags in ntags_per_element: if split: - raise NotImplementedError('this feature is under development') + raise NotImplementedError("this feature is under development") element_nodes = [fea2_nodes[ntag] for ntag in ntags] if ntags.size == 3: - k = part.add_element(ShellElement(nodes=element_nodes, - section=section, - rigid=rigid, - implementation=implementation)) + k = part.add_element( + ShellElement(nodes=element_nodes, section=section, rigid=rigid, implementation=implementation) + ) elif ntags.size == 4: if isinstance(section, ShellSection): - k = part.add_element(ShellElement(nodes=element_nodes, - section=section, - rigid=rigid, - implementation=implementation)) + k = part.add_element( + ShellElement(nodes=element_nodes, section=section, rigid=rigid, implementation=implementation) + ) else: - k = part.add_element(TetrahedronElement(nodes=element_nodes, - section=section)) + k = part.add_element(TetrahedronElement(nodes=element_nodes, section=section)) elif ntags.size == 8: - k = part.add_element(HexahedronElement(nodes=element_nodes, - section=section)) + k = part.add_element(HexahedronElement(nodes=element_nodes, section=section)) else: - raise NotImplementedError('Element with {} nodes not supported'.format(ntags.size)) + raise NotImplementedError("Element with {} nodes not supported".format(ntags.size)) if verbose: - print('element {} added'.format(k)) + print("element {} added".format(k)) return part @@ -369,11 +376,12 @@ def from_boundary_mesh(cls, boundary_mesh, name=None, **kwargs): """ from compas_gmsh.models import MeshModel - target_mesh_size = kwargs.get('target_mesh_size', 1) - mesh_size_at_vertices = kwargs.get('mesh_size_at_vertices', None) - target_point_mesh_size = kwargs.get('target_point_mesh_size', None) - meshsize_max = kwargs.get('meshsize_max', None) - meshsize_min = kwargs.get('meshsize_min', None) + + target_mesh_size = kwargs.get("target_mesh_size", 1) + mesh_size_at_vertices = kwargs.get("mesh_size_at_vertices", None) + target_point_mesh_size = kwargs.get("target_point_mesh_size", None) + meshsize_max = kwargs.get("meshsize_max", None) + meshsize_min = kwargs.get("meshsize_min", None) gmshModel = MeshModel.from_mesh(boundary_mesh, targetlength=target_mesh_size) @@ -401,9 +409,9 @@ def from_boundary_mesh(cls, boundary_mesh, name=None, **kwargs): gmshModel.generate_mesh(2) part._discretized_boundary_mesh = gmshModel.mesh_to_compas() - del(gmshModel) + del gmshModel - if kwargs.get('rigid', False): + if kwargs.get("rigid", False): point = boundary_mesh.centroid() part.reference_point = Node(xyz=[point.x, point.y, point.z]) @@ -417,6 +425,7 @@ def from_boundary_mesh(cls, boundary_mesh, name=None, **kwargs): # ========================================================================= # Nodes methods # ========================================================================= + def find_node_by_key(self, key): # type: (int) -> Node """Retrieve a node in the model using its key. @@ -430,6 +439,7 @@ def find_node_by_key(self, key): ------- :class:`compas_fea2.model.Node` The corresponding node. + """ for node in self.nodes: if node.key == key: @@ -471,12 +481,11 @@ def find_nodes_by_location(self, point, distance, plane=None, report=False, **kw list[:class:`compas_fea2.model.Node`] """ - d2 = distance ** 2 + d2 = distance**2 nodes = self.find_nodes_on_plane(plane) if plane else self.nodes if report: - return {node: sqrt(distance) for node in nodes if (distance := distance_point_point_sqrd(node.xyz, point)) < d2} - else: - return [node for node in nodes if (distance := distance_point_point_sqrd(node.xyz, point)) < d2] + return {node: sqrt(distance) for node in nodes if distance_point_point_sqrd(node.xyz, point) < d2} + return [node for node in nodes if distance_point_point_sqrd(node.xyz, point) < d2] def find_closest_nodes_to_point(self, point, distance, number_of_nodes=1, plane=None): # type: (Point, float, int, Plane) -> list(Node) @@ -520,6 +529,7 @@ def find_nodes_around_node(self, node, distance, plane=None): ------- [:class:`compas_fea2.model.Node] The nodes around the given node + """ nodes = self.find_nodes_by_location(node.xyz, distance, plane, report=True) if node in nodes: @@ -555,10 +565,6 @@ def find_nodes_by_attribute(self, attr, value, tolerance=0.001): # type: (str, float, float) -> list(Node) """Find all nodes with a given value for a the given attribute. - Note - ---- - Only numeric attributes are supported. - Parameters ---------- attr : str @@ -570,6 +576,10 @@ def find_nodes_by_attribute(self, attr, value, tolerance=0.001): ------- list[:class:`compas_fea2.model.Node`] + Notes + ----- + Only numeric attributes are supported. + """ return list(filter(lambda x: abs(getattr(x, attr) - value) <= tolerance, self.nodes)) @@ -602,22 +612,22 @@ def find_nodes_in_polygon(self, polygon, tolerance=1.1): ------- [:class:`compas_fea2.model.Node] List with the nodes contained in the polygon. + """ # TODO quick fix...change! - if not hasattr(polygon, 'plane'): + if not hasattr(polygon, "plane"): try: polygon.plane = Frame.from_points(*polygon.points[:3]) - except: + except Exception: polygon.plane = Frame.from_points(*polygon.points[-3:]) - S = Scale.from_factors([tolerance]*3, polygon.plane) + S = Scale.from_factors([tolerance] * 3, polygon.plane) T = Transformation.from_frame_to_frame(polygon.plane, Frame.worldXY()) nodes_on_plane = self.find_nodes_on_plane(Plane.from_frame(polygon.plane)) polygon_xy = polygon.transformed(S) polygon_xy = polygon.transformed(T) return list(filter(lambda x: is_point_in_polygon_xy(Point(*x.xyz).transformed(T), polygon_xy), nodes_on_plane)) - # TODO quite slow...check how to make it faster def find_nodes_where(self, conditions): # type: (list(str)) -> list(Node) @@ -632,8 +642,10 @@ def find_nodes_where(self, conditions): ------- [Node] List with the nodes matching the criteria. + """ import re + nodes = [] for condition in conditions: # limit the serch to the already found nodes @@ -642,7 +654,9 @@ def find_nodes_where(self, conditions): eval(condition) except NameError as ne: var_name = re.findall(r"'([^']*)'", str(ne))[0] - nodes.append(set(filter(lambda n: eval(condition.replace(var_name, str(getattr(n, var_name)))), part_nodes))) + nodes.append( + set(filter(lambda n: eval(condition.replace(var_name, str(getattr(n, var_name)))), part_nodes)) + ) return list(set.intersection(*nodes)) def contains_node(self, node): @@ -664,10 +678,6 @@ def add_node(self, node): # type: (Node) -> Node """Add a node to the part. - Note - ---- - By adding a Node to the part, it gets registered to the part. - Parameters ---------- node : :class:`compas_fea2.model.Node` @@ -683,6 +693,10 @@ def add_node(self, node): TypeError If the node is not a node. + Notes + ----- + By adding a Node to the part, it gets registered to the part. + Examples -------- >>> part = DeformablePart() @@ -691,17 +705,17 @@ def add_node(self, node): """ if not isinstance(node, Node): - raise TypeError('{!r} is not a node.'.format(node)) + raise TypeError("{!r} is not a node.".format(node)) if self.contains_node(node): if compas_fea2.VERBOSE: - print('NODE SKIPPED: Node {!r} already in part.'.format(node)) + print("NODE SKIPPED: Node {!r} already in part.".format(node)) return if not compas_fea2.POINT_OVERLAP: if self.find_nodes_by_location(node.xyz, distance=compas_fea2.GLOBAL_TOLERANCE): if compas_fea2.VERBOSE: - print('NODE SKIPPED: Part {!r} has already a node at {}.'.format(self, node.xyz)) + print("NODE SKIPPED: Part {!r} has already a node at {}.".format(self, node.xyz)) return node._key = len(self._nodes) @@ -709,7 +723,7 @@ def add_node(self, node): self._gkey_node[node.gkey] = node node._registration = self if compas_fea2.VERBOSE: - print('Node {!r} registered to {!r}.'.format(node, self)) + print("Node {!r} registered to {!r}.".format(node, self)) return node def add_nodes(self, nodes): @@ -748,6 +762,7 @@ def remove_node(self, node): ---------- node : :class:`compas_fea2.model.Node` The node to remove + """ # type: (Node) -> None if self.contains_node(node): @@ -755,7 +770,7 @@ def remove_node(self, node): self._gkey_node.pop(node.gkey) node._registration = None if compas_fea2.VERBOSE: - print('Node {!r} removed from {!r}.'.format(node, self)) + print("Node {!r} removed from {!r}.".format(node, self)) def remove_nodes(self, nodes): """Remove multiple :class:`compas_fea2.model.Node` from the part. @@ -766,8 +781,9 @@ def remove_nodes(self, nodes): Parameters ---------- - nodes : []:class:`compas_fea2.model.Node`] + nodes : [:class:`compas_fea2.model.Node`] List with the nodes to remove + """ for node in nodes: self.remove_node(node) @@ -783,28 +799,29 @@ def is_node_on_boundary(self, node, precision=None): precision : ?? ??? - Note - ---- - The `discretized_boundary_mesh` of the part must have been previously - defined. - Returns ------- bool `True` if the node is on the boundary, `False` otherwise. + + Notes + ----- + The `discretized_boundary_mesh` of the part must have been previously defined. + """ if not self.discretized_boundary_mesh: raise AttributeError("The discretized_boundary_mesh has not been defined") if not node.on_boundary: - node._on_boundary = True if geometric_key( - node.xyz, precision) in self.discretized_boundary_mesh.gkey_vertex() else False + node._on_boundary = ( + True if geometric_key(node.xyz, precision) in self.discretized_boundary_mesh.gkey_vertex() else False + ) return node.on_boundary # ========================================================================= # Elements methods # ========================================================================= def find_element_by_key(self, key): - # type: (int) -> _Element + # type: (int) -> Element """Retrieve an element in the model using its key. Parameters @@ -814,15 +831,16 @@ def find_element_by_key(self, key): Returns ------- - :class:`compas_fea2.model._Element` + :class:`compas_fea2.model.Element` The corresponding element. + """ for element in self.elements: if element.key == key: return element def find_elements_by_name(self, name): - # type: (str) -> list(_Element) + # type: (str) -> list(Element) """Find all elements with a given name. Parameters @@ -831,18 +849,18 @@ def find_elements_by_name(self, name): Returns ------- - list[:class:`compas_fea2.model._Element`] + list[:class:`compas_fea2.model.Element`] """ return [element for element in self.elements if element.name == name] def contains_element(self, element): - # type: (_Element) -> _Element + # type: (Element) -> Element """Verify that the part contains a specific element. Parameters ---------- - element : :class:`compas_fea2.model._Element` + element : :class:`compas_fea2.model.Element` Returns ------- @@ -852,17 +870,17 @@ def contains_element(self, element): return element in self.elements def add_element(self, element): - # type: (_Element) -> _Element + # type: (Element) -> Element """Add an element to the part. Parameters ---------- - element : :class:`compas_fea2.model._Element` + element : :class:`compas_fea2.model.Element` The element instance. Returns ------- - :class:`compas_fea2.model._Element` + :class:`compas_fea2.model.Element` Raises ------ @@ -870,8 +888,8 @@ def add_element(self, element): If the element is not an element. """ - if not isinstance(element, _Element): - raise TypeError('{!r} is not an element.'.format(element)) + if not isinstance(element, Element): + raise TypeError("{!r} is not an element.".format(element)) if self.contains_element(element): if compas_fea2.VERBOSE: @@ -879,11 +897,11 @@ def add_element(self, element): return self.add_nodes(element.nodes) - if hasattr(element, 'section'): + if hasattr(element, "section"): if element.section: self.add_section(element.section) - if hasattr(element.section, 'material'): + if hasattr(element.section, "material"): if element.section.material: self.add_material(element.section.material) @@ -891,26 +909,26 @@ def add_element(self, element): self.elements.add(element) element._registration = self if compas_fea2.VERBOSE: - print('Element {!r} registered to {!r}.'.format(element, self)) + print("Element {!r} registered to {!r}.".format(element, self)) return element def add_elements(self, elements): - # type: (_Element) -> list(_Element) + # type: (Element) -> list(Element) """Add multiple elements to the part. Parameters ---------- - elements : list[:class:`compas_fea2.model._Element`] + elements : list[:class:`compas_fea2.model.Element`] Return ------ - list[:class:`compas_fea2.model._Element`] + list[:class:`compas_fea2.model.Element`] """ return [self.add_element(element) for element in elements] def remove_element(self, element): - """Remove a :class:`compas_fea2.model._Element` from the part. + """Remove a :class:`compas_fea2.model.Element` from the part. Warning ------- @@ -918,27 +936,29 @@ def remove_element(self, element): Parameters ---------- - element : :class:`compas_fea2.model._Element` + element : :class:`compas_fea2.model.Element` The element to remove + """ - # type: (_Element) -> None + # type: (Element) -> None if self.contains_node(element): self.elements.pop(element) element._registration = None if compas_fea2.VERBOSE: - print('Element {!r} removed from {!r}.'.format(element, self)) + print("Element {!r} removed from {!r}.".format(element, self)) def remove_elements(self, elements): - """Remove multiple :class:`compas_fea2.model._Element` from the part. - - Warning - ------- - Removing elements can cause inconsistencies. + """Remove multiple :class:`compas_fea2.model.Element` from the part. Parameters ---------- - elements : []:class:`compas_fea2.model._Element`] + elements : []:class:`compas_fea2.model.Element`] List with the elements to remove + + Warnings + -------- + Removing elements can cause inconsistencies. + """ for element in elements: self.remove_element(element) @@ -948,15 +968,16 @@ def is_element_on_boundary(self, element): Parameters ---------- - element : :class:`compas_fea2.model._Element` + element : :class:`compas_fea2.model.Element` The element to check. Returns ------- bool ``True`` if the element is on the boundary. + """ - # type: (_Element) -> bool + # type: (Element) -> bool from compas.geometry import centroid_points if element.on_boundary is None: @@ -964,13 +985,20 @@ def is_element_on_boundary(self, element): centroid_face = {} for face in self._discretized_boundary_mesh.faces(): centroid_face[geometric_key(self._discretized_boundary_mesh.face_centroid(face))] = face - if isinstance(element, _Element3D): - if any(geometric_key(centroid_points([node.xyz for node in face.nodes])) in self._discretized_boundary_mesh.centroid_face for face in element.faces): + if isinstance(element, Element3D): + if any( + geometric_key(centroid_points([node.xyz for node in face.nodes])) + in self._discretized_boundary_mesh.centroid_face + for face in element.faces + ): element.on_boundary = True else: element.on_boundary = False - elif isinstance(element, _Element2D): - if geometric_key(centroid_points([node.xyz for node in element.nodes])) in self._discretized_boundary_mesh.centroid_face: + elif isinstance(element, Element2D): + if ( + geometric_key(centroid_points([node.xyz for node in element.nodes])) + in self._discretized_boundary_mesh.centroid_face + ): element.on_boundary = True else: element.on_boundary = False @@ -984,10 +1012,6 @@ def find_faces_on_plane(self, plane): # type: (Plane) -> list(Face) """Find the face of the elements that belongs to a given plane, if any. - Note - ---- - The search is limited to solid elements. - Parameters ---------- plane : :class:`compas.geometry.Plane` @@ -997,9 +1021,16 @@ def find_faces_on_plane(self, plane): ------- [:class:`compas_fea2.model.Face`] list with the faces belonging to the given plane. + + Notes + ----- + The search is limited to solid elements. + """ faces = [] - for element in filter(lambda x: isinstance(x, (_Element2D, _Element3D)) and self.is_element_on_boundary(x), self._elements): + for element in filter( + lambda x: isinstance(x, (Element2D, Element3D)) and self.is_element_on_boundary(x), self._elements + ): for face in element.faces: if all([is_point_on_plane(node.xyz, plane) for node in face.nodes]): faces.append(face) @@ -1042,7 +1073,7 @@ def contains_group(self, group): elif isinstance(group, FacesGroup): return group in self._facesgroups else: - raise TypeError('{!r} is not a valid Group'.format(group)) + raise TypeError("{!r} is not a valid Group".format(group)) def add_group(self, group): """Add a node or element group to the part. @@ -1051,8 +1082,8 @@ def add_group(self, group): ---------- group : :class:`compas_fea2.model.NodeGroup` | :class:`compas_fea2.model.ElementGroup` - Return - ------ + Returns + ------- None Raises @@ -1089,8 +1120,8 @@ def add_groups(self, groups): ---------- groups : list[:class:`compas_fea2.model.Group`] - Return - ------ + Returns + ------- list[:class:`compas_fea2.model.Group`] """ @@ -1100,7 +1131,7 @@ def add_groups(self, groups): # Results methods # ============================================================================== - def sorted_nodes_by_displacement(self, problem, step=None, component='length'): + def sorted_nodes_by_displacement(self, problem, step=None, component="length"): """Return a list with the nodes sorted by their displacement Parameters @@ -1117,11 +1148,12 @@ def sorted_nodes_by_displacement(self, problem, step=None, component='length'): ------- [:class:`compas_fea2.model.Node`] The node sorted by displacment (ascending). + """ step = step or problem._steps_order[-1] - return sorted(self.nodes, key=lambda n: getattr(Vector(*n.results[problem][step].get('U', None)), component)) + return sorted(self.nodes, key=lambda n: getattr(Vector(*n.results[problem][step].get("U", None)), component)) - def get_max_displacement(self, problem, step=None, component='length'): + def get_max_displacement(self, problem, step=None, component="length"): """Retrieve the node with the maximum displacement Parameters @@ -1138,13 +1170,14 @@ def get_max_displacement(self, problem, step=None, component='length'): ------- :class:`compas_fea2.model.Node`, float The node and the displacement + """ step = step or problem._steps_order[-1] node = self.sorted_nodes_by_displacement(problem=problem, step=step, component=component)[-1] - displacement = getattr(Vector(*node.results[problem][step].get('U', None)), component) + displacement = getattr(Vector(*node.results[problem][step].get("U", None)), component) return node, displacement - def get_min_displacement(self, problem, step=None, component='length'): + def get_min_displacement(self, problem, step=None, component="length"): """Retrieve the node with the minimum displacement Parameters @@ -1161,13 +1194,14 @@ def get_min_displacement(self, problem, step=None, component='length'): ------- :class:`compas_fea2.model.Node`, float The node and the displacement + """ step = step or problem._steps_order[-1] node = self.sorted_nodes_by_displacement(problem=problem, step=step, component=component)[0] - displacement = getattr(Vector(*node.results[problem][step].get('U', None)), component) + displacement = getattr(Vector(*node.results[problem][step].get("U", None)), component) return node, displacement - def get_average_displacement_at_point(self, problem, point, distance, step=None, component='length', project=False): + def get_average_displacement_at_point(self, problem, point, distance, step=None, component="length", project=False): """Compute the average displacement around a point Parameters @@ -1184,27 +1218,27 @@ def get_average_displacement_at_point(self, problem, point, distance, step=None, ------- :class:`compas_fea2.model.Node`, float The node and the displacement + """ step = step or problem._steps_order[-1] nodes = self.find_nodes_by_location(point=point, distance=distance, report=True) if nodes: - displacements = [getattr(Vector(*node.results[problem][step].get('U', None)), component) for node in nodes] - return point, sum(displacements)/len(displacements) + displacements = [getattr(Vector(*node.results[problem][step].get("U", None)), component) for node in nodes] + return point, sum(displacements) / len(displacements) -class DeformablePart(_Part): +class DeformablePart(Part): """Deformable part. - """ - __doc__ += _Part.__doc__ - __doc__ += """ - Additional Attributes - --------------------- - materials : Set[:class:`compas_fea2.model._Material`] + + Attributes + ---------- + materials : Set[:class:`compas_fea2.model.Material`] The materials belonging to the part. - sections : Set[:class:`compas_fea2.model._Section`] + sections : Set[:class:`compas_fea2.model.Section`] The sections belonging to the part. - releases : Set[:class:`compas_fea2.model._BeamEndRelease`] + releases : Set[:class:`compas_fea2.model.BeamEndRelease`] The releases belonging to the part. + """ def __init__(self, name=None, **kwargs): @@ -1213,15 +1247,15 @@ def __init__(self, name=None, **kwargs): self._sections = set() self._releases = set() - @ property + @property def materials(self): return self._materials - @ property + @property def sections(self): return self._sections - @ property + @property def releases(self): return self._releases @@ -1229,10 +1263,11 @@ def releases(self): # Constructor methods # ========================================================================= - @ classmethod - @ timer(message='compas Mesh successfully imported in ') + @classmethod + @timer(message="compas Mesh successfully imported in ") def frame_from_compas_mesh(cls, mesh, section, name=None, **kwargs): """Creates a DeformablePart object from a a :class:`compas.datastructures.Mesh`. + To each edge of the mesh is assigned a :class:`compas_fea2.model.BeamElement`. Currently, the same section is applied to all the elements. @@ -1251,7 +1286,7 @@ def frame_from_compas_mesh(cls, mesh, section, name=None, **kwargs): for edge in mesh.edges(): nodes = [vertex_node[vertex] for vertex in edge] - v = mesh.edge_direction(*edge) + v = list(mesh.edge_direction(edge)) v.append(v.pop(0)) part.add_element(BeamElement(nodes=[*nodes], section=section, frame=v)) @@ -1260,17 +1295,15 @@ def frame_from_compas_mesh(cls, mesh, section, name=None, **kwargs): return part - @ classmethod + @classmethod # @timer(message='part successfully imported from gmsh model in ') def from_gmsh(cls, gmshModel, section, name=None, **kwargs): - """ - """ + """ """ return super().from_gmsh(gmshModel, name=name, section=section, **kwargs) - @ classmethod + @classmethod def from_boundary_mesh(cls, boundary_mesh, section, name=None, **kwargs): - """ - """ + """ """ return super().from_boundary_mesh(boundary_mesh, section=section, name=name, **kwargs) # ========================================================================= @@ -1278,7 +1311,7 @@ def from_boundary_mesh(cls, boundary_mesh, section, name=None, **kwargs): # ========================================================================= def find_materials_by_name(self, name): - # type: (str) -> list(_Material) + # type: (str) -> list(Material) """Find all materials with a given name. Parameters @@ -1293,7 +1326,7 @@ def find_materials_by_name(self, name): return [material for material in self.materials if material.name == name] def contains_material(self, material): - # type: (_Material) -> _Material + # type: (Material) -> Material """Verify that the part contains a specific material. Parameters @@ -1308,7 +1341,7 @@ def contains_material(self, material): return material in self.materials def add_material(self, material): - # type: (_Material) -> _Material + # type: (Material) -> Material """Add a material to the part so that it can be referenced in section and element definitions. Parameters @@ -1325,12 +1358,12 @@ def add_material(self, material): If the material is not a material. """ - if not isinstance(material, _Material): - raise TypeError('{!r} is not a material.'.format(material)) + if not isinstance(material, Material): + raise TypeError("{!r} is not a material.".format(material)) if self.contains_material(material): if compas_fea2.VERBOSE: - print('SKIPPED: Material {!r} already in part.'.format(material)) + print("SKIPPED: Material {!r} already in part.".format(material)) return material._key = len(self._materials) @@ -1339,7 +1372,7 @@ def add_material(self, material): return material def add_materials(self, materials): - # type: (_Material) -> list(_Material) + # type: (Material) -> list(Material) """Add multiple materials to the part. Parameters @@ -1358,7 +1391,7 @@ def add_materials(self, materials): # ========================================================================= def find_sections_by_name(self, name): - # type: (str) -> list(_Section) + # type: (str) -> list(Section) """Find all sections with a given name. Parameters @@ -1373,7 +1406,7 @@ def find_sections_by_name(self, name): return [section for section in self.sections if section.name == name] def contains_section(self, section): - # type: (_Section) -> _Section + # type: (Section) -> Section """Verify that the part contains a specific section. Parameters @@ -1388,7 +1421,7 @@ def contains_section(self, section): return section in self.sections def add_section(self, section): - # type: (_Section) -> _Section + # type: (Section) -> Section """Add a section to the part so that it can be referenced in element definitions. Parameters @@ -1405,8 +1438,8 @@ def add_section(self, section): If the section is not a section. """ - if not isinstance(section, _Section): - raise TypeError('{!r} is not a section.'.format(section)) + if not isinstance(section, Section): + raise TypeError("{!r} is not a section.".format(section)) if self.contains_section(section): if compas_fea2.VERBOSE: @@ -1420,7 +1453,7 @@ def add_section(self, section): return section def add_sections(self, sections): - # type: (list(_Section)) -> _Section + # type: (list(Section)) -> Section """Add multiple sections to the part. Parameters @@ -1439,8 +1472,7 @@ def add_sections(self, sections): # ========================================================================= def add_beam_release(self, element, location, release): - """Add a :class:`compas_fea2.model._BeamEndRelease` to an element in the - part. + """Add a :class:`compas_fea2.model.BeamEndRelease` to an element in the part. Parameters ---------- @@ -1448,73 +1480,72 @@ def add_beam_release(self, element, location, release): The element to release. location : str 'start' or 'end'. - release : :class:`compas_fea2.model._BeamEndRelease` + release : :class:`compas_fea2.model.BeamEndRelease` Release type to apply. + """ - if not isinstance(release, _BeamEndRelease): - raise TypeError('{!r} is not a beam release element.'.format(release)) + if not isinstance(release, BeamEndRelease): + raise TypeError("{!r} is not a beam release element.".format(release)) release.element = element release.location = location self._releases.add(release) return release -class RigidPart(_Part): +class RigidPart(Part): """Rigid part. - """ - __doc__ += _Part.__doc__ - __doc__ += """ - Addtional Attributes - -------------------- + + Attributes + ---------- reference_point : :class:`compas_fea2.model.Node` A node acting as a reference point for the part, by default `None`. This is required if the part is rigid as it controls its movement in space. + """ def __init__(self, reference_point=None, name=None, **kwargs): super(RigidPart, self).__init__(name=name, **kwargs) self._reference_point = reference_point - @ property + @property def reference_point(self): return self._reference_point - @ reference_point.setter + @reference_point.setter def reference_point(self, value): self._reference_point = self.add_node(value) value._is_reference = True - @ classmethod + @classmethod # @timer(message='part successfully imported from gmsh model in ') def from_gmsh(cls, gmshModel, name=None, **kwargs): - """ - """ - kwargs['rigid'] = True + """ """ + kwargs["rigid"] = True return super().from_gmsh(gmshModel, name=name, **kwargs) - @ classmethod + @classmethod def from_boundary_mesh(cls, boundary_mesh, name=None, **kwargs): - """ - """ - kwargs['rigid'] = True + """ """ + kwargs["rigid"] = True return super().from_boundary_mesh(boundary_mesh, name=name, **kwargs) + # ========================================================================= # Elements methods # ========================================================================= # TODO this can be removed and the checks on the rigid part can be done in _part def add_element(self, element): - # type: (_Element) -> _Element + # type: (Element) -> Element """Add an element to the part. Parameters ---------- - element : :class:`compas_fea2.model._Element` + element : :class:`compas_fea2.model.Element` The element instance. Returns ------- - :class:`compas_fea2.model._Element` + :class:`compas_fea2.model.Element` Raises ------ @@ -1522,8 +1553,8 @@ def add_element(self, element): If the element is not an element. """ - if not hasattr(element, 'rigid'): - raise TypeError('The element type cannot be assigned to a RigidPart') - if not getattr(element, 'rigid'): - raise TypeError('Rigid parts can only have rigid elements') + if not hasattr(element, "rigid"): + raise TypeError("The element type cannot be assigned to a RigidPart") + if not getattr(element, "rigid"): + raise TypeError("Rigid parts can only have rigid elements") return super().add_element(element) diff --git a/src/compas_fea2/model/releases.py b/src/compas_fea2/model/releases.py index 317e2715d..e1b0bfe17 100644 --- a/src/compas_fea2/model/releases.py +++ b/src/compas_fea2/model/releases.py @@ -3,18 +3,14 @@ from __future__ import print_function from compas_fea2.base import FEAData -from compas.geometry import Frame import compas_fea2.model -class _BeamEndRelease(FEAData): +class BeamEndRelease(FEAData): """Assign a general end release to a `compas_fea2.model.BeamElement`. Parameters ---------- - name : str, optional - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. n : bool, optional Release displacements along the local axial direction, by default False v1 : bool, optional @@ -30,9 +26,6 @@ class _BeamEndRelease(FEAData): Attributes ---------- - name : str - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. location : str 'start' or 'end' element : :class:`compas_fea2.model.BeamElement` @@ -52,8 +45,8 @@ class _BeamEndRelease(FEAData): """ - def __init__(self, n=False, v1=False, v2=False, m1=False, m2=False, t=False, name=None, **kwargs): - super(_BeamEndRelease, self).__init__(name, **kwargs) + def __init__(self, n=False, v1=False, v2=False, m1=False, m2=False, t=False, **kwargs): + super(BeamEndRelease, self).__init__(**kwargs) self._element = None self._location = None @@ -71,7 +64,7 @@ def element(self): @element.setter def element(self, value): if not isinstance(value, compas_fea2.model.BeamElement): - raise TypeError('{!r} is not a beam element.'.format(value)) + raise TypeError("{!r} is not a beam element.".format(value)) self._element = value @property @@ -80,12 +73,12 @@ def location(self): @location.setter def location(self, value): - if not value in ('start', 'end'): - raise TypeError('the location can be either `start` or `end`') + if value not in ("start", "end"): + raise TypeError("the location can be either `start` or `end`") self._location = value -class BeamEndPinRelease(_BeamEndRelease): +class BeamEndPinRelease(BeamEndRelease): """Assign a pin end release to a `compas_fea2.model.BeamElement`. Parameters @@ -96,13 +89,14 @@ class BeamEndPinRelease(_BeamEndRelease): Release rotations about local 2 direction, by default False t : bool, optional Release rotations about local axial direction (torsion), by default False + """ - def __init__(self, m1=False, m2=False, t=False, name=None, **kwargs): - super(BeamEndPinRelease, self).__init__(n=False, v1=False, v2=False, m1=m1, m2=m2, t=t, name=name, **kwargs) + def __init__(self, m1=False, m2=False, t=False, **kwargs): + super(BeamEndPinRelease, self).__init__(n=False, v1=False, v2=False, m1=m1, m2=m2, t=t, **kwargs) -class BeamEndSliderRelease(_BeamEndRelease): +class BeamEndSliderRelease(BeamEndRelease): """Assign a slider end release to a `compas_fea2.model.BeamElement`. Parameters @@ -111,8 +105,8 @@ class BeamEndSliderRelease(_BeamEndRelease): Release displacements along local 1 direction, by default False v2 : bool, optional Release displacements along local 2 direction, by default False + """ - def __init__(self, v1=False, v2=False, name=None, **kwargs): - super(BeamEndSliderRelease, self).__init__(v1=v1, v2=v2, - n=False, m1=False, m2=False, t=False, name=name, **kwargs) + def __init__(self, v1=False, v2=False, **kwargs): + super(BeamEndSliderRelease, self).__init__(v1=v1, v2=v2, n=False, m1=False, m2=False, t=False, **kwargs) diff --git a/src/compas_fea2/model/sections.py b/src/compas_fea2/model/sections.py index 82c6bb84e..33e37f987 100644 --- a/src/compas_fea2/model/sections.py +++ b/src/compas_fea2/model/sections.py @@ -2,45 +2,39 @@ from __future__ import division from __future__ import print_function -from abc import abstractmethod from math import pi from compas_fea2 import units from compas_fea2.base import FEAData -from .materials import _Material +from .materials import Material -class _Section(FEAData): +class Section(FEAData): """Base class for sections. - Note - ---- - Sections are registered to a :class:`compas_fea2.model.Model` and can be assigned - to elements in different Parts. - Parameters ---------- - name : str, optional - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. - material : :class:`~compas_fea2.model._Material` + material : :class:`~compas_fea2.model.Material` A material definition. Attributes ---------- - name : str - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. key : int, read-only Identifier index of the section in the parent Model. - material : :class:`~compas_fea2.model._Material` + material : :class:`~compas_fea2.model.Material` The material associated with the section. model : :class:`compas_fea2.model.Model` The model where the section is assigned. + + Notes + ----- + Sections are registered to a :class:`compas_fea2.model.Model` and can be assigned + to elements in different Parts. + """ - def __init__(self, material, name=None, **kwargs): - super(_Section, self).__init__(name=name, **kwargs) + def __init__(self, material, **kwargs): + super(Section, self).__init__(**kwargs) self._key = None self._material = material @@ -55,8 +49,8 @@ def material(self): @material.setter def material(self, value): if value: - if not isinstance(value, _Material): - raise ValueError('Material must be of type `compas_fea2.model._Material`.') + if not isinstance(value, Material): + raise ValueError("Material must be of type `compas_fea2.model.Material`.") self._material = value @property @@ -70,36 +64,35 @@ def __str__(self): model : {!r} key : {} material : {!r} -""".format(self.name, '-'*len(self.name), self.model, self.key, self.material) +""".format( + self.name, "-" * len(self.name), self.model, self.key, self.material + ) # ============================================================================== # 0D # ============================================================================== + class MassSection(FEAData): """Section for point mass elements. Parameters ---------- - name : str, optional - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. mass : float Point mass value. Attributes ---------- - name : str - Uniqe identifier. key : int, read-only Identifier of the element in the parent part. mass : float Point mass value. + """ - def __init__(self, mass, name=None, **kwargs): - super(MassSection, self).__init__(name=name, **kwargs) + def __init__(self, mass, **kwargs): + super(MassSection, self).__init__(**kwargs) self.mass = mass self._key = None @@ -113,7 +106,9 @@ def __str__(self): --------{} model : {!r} mass : {} -""".format(self.name, '-'*len(self.name), self.model, self.mass) +""".format( + self.name, "-" * len(self.name), self.model, self.mass + ) class SpringSection(FEAData): @@ -121,9 +116,6 @@ class SpringSection(FEAData): Parameters ---------- - name : str, optional - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. forces : dict Forces data for non-linear springs. displacements : dict @@ -133,8 +125,6 @@ class SpringSection(FEAData): Attributes ---------- - name : str - Uniqe identifier. key : int, read-only Identifier of the element in the parent part. forces : dict @@ -144,17 +134,17 @@ class SpringSection(FEAData): stiffness : dict Elastic stiffness for linear springs. - Note - ---- + Notes + ----- - Force and displacement data should range from negative to positive values. - Requires either a stiffness dict for linear springs, or forces and displacement lists for non-linear springs. - Directions are 'axial', 'lateral', 'rotation'. """ - def __init__(self, forces=None, displacements=None, stiffness=None, name=None, **kwargs): - super(SpringSection, self).__init__(name=name, **kwargs) - #TODO would be good to know the structure of these dicts and validate + def __init__(self, forces=None, displacements=None, stiffness=None, **kwargs): + super(SpringSection, self).__init__(**kwargs) + # TODO would be good to know the structure of these dicts and validate self.forces = forces or {} self.displacements = displacements or {} self.stiffness = stiffness or {} @@ -168,14 +158,17 @@ def __str__(self): forces : {} displ : {} stiffness : {} -""".format(self.name, self.forces, self.displacements, self.stiffness) +""".format( + self.name, self.forces, self.displacements, self.stiffness + ) # ============================================================================== # 1D # ============================================================================== -class BeamSection(_Section): + +class BeamSection(Section): """Custom section for beam elements. Parameters @@ -198,11 +191,8 @@ class BeamSection(_Section): ??? gw : float ??? - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str, optional - Section name. If not provided, a unique identifier is automatically - assigned. Attributes ---------- @@ -224,16 +214,13 @@ class BeamSection(_Section): ??? gw : float ??? - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str - Section name. If not provided, a unique identifier is automatically - assigned. """ - def __init__(self, *, A, Ixx, Iyy, Ixy, Avx, Avy, J, g0, gw, material, name=None, **kwargs): - super(BeamSection, self).__init__(material=material, name=name, **kwargs) + def __init__(self, *, A, Ixx, Iyy, Ixy, Avx, Avy, J, g0, gw, material, **kwargs): + super(BeamSection, self).__init__(material=material, **kwargs) self.A = A self.Ixx = Ixx self.Iyy = Iyy @@ -260,28 +247,26 @@ def __str__(self): J : {} g0 : {} gw : {} -""".format(self.__class__.__name__, - len(self.__class__.__name__) * '-', - self.name, - self.material, - (self.A * units['m**2']), - (self.Ixx * units['m**4']), - (self.Iyy * units['m**4']), - (self.Ixy * units['m**4']), - (self.Avx * units['m**2']), - (self.Avy * units['m**2']), - self.J, - self.g0, - self.gw) +""".format( + self.__class__.__name__, + len(self.__class__.__name__) * "-", + self.name, + self.material, + (self.A * units["m**2"]), + (self.Ixx * units["m**4"]), + (self.Iyy * units["m**4"]), + (self.Ixy * units["m**4"]), + (self.Avx * units["m**2"]), + (self.Avy * units["m**2"]), + self.J, + self.g0, + self.gw, + ) class AngleSection(BeamSection): """Uniform thickness angle cross-section for beam elements. - Warning - ------- - - Ixy not yet calculated. - Parameters ---------- w : float @@ -290,7 +275,7 @@ class AngleSection(BeamSection): Height. t : float Thickness. - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. name : str, optional Section name. If not provided, a unique identifier is automatically @@ -322,49 +307,56 @@ class AngleSection(BeamSection): ??? gw : float ??? - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. name : str Section name. If not provided, a unique identifier is automatically assigned. + Warnings + -------- + - Ixy not yet calculated. + """ - def __init__(self, w, h, t, material, name=None, **kwargs): + def __init__(self, w, h, t, material, **kwargs): self.w = w self.h = h self.t = t - p = 2. * (w + h - t) + p = 2.0 * (w + h - t) xc = (w**2 + h * t - t**2) / p yc = (h**2 + w * t - t**2) / p A = t * (w + h - t) - Ixx = (1. / 3) * (w * h**3 - (w - t) * (h - t)**3) - self.A * (h - yc)**2 - Iyy = (1. / 3) * (h * w**3 - (h - t) * (w - t)**3) - self.A * (w - xc)**2 + Ixx = (1.0 / 3) * (w * h**3 - (w - t) * (h - t) ** 3) - self.A * (h - yc) ** 2 + Iyy = (1.0 / 3) * (h * w**3 - (h - t) * (w - t) ** 3) - self.A * (w - xc) ** 2 Ixy = 0 - J = (1. / 3) * (h + w - t) * t**3 + J = (1.0 / 3) * (h + w - t) * t**3 Avx = 0 Avy = 0 g0 = 0 gw = 0 - super(AngleSection, self).__init__(A=A, Ixx=Ixx, Iyy=Iyy, Ixy=Ixy, - Avx=Avx, Avy=Avy, J=J, g0=g0, gw=gw, material=material, name=name, **kwargs) + super(AngleSection, self).__init__( + A=A, + Ixx=Ixx, + Iyy=Iyy, + Ixy=Ixy, + Avx=Avx, + Avy=Avy, + J=J, + g0=g0, + gw=gw, + material=material, + **kwargs, + ) # TODO implement different thickness along the 4 sides class BoxSection(BeamSection): """Hollow rectangular box cross-section for beam elements. - Note - ---- - Currently you can only specify the thickness of the flanges and the webs. - - Warning - ------- - - Ixy not yet calculated. - Parameters ---------- w : float @@ -375,11 +367,8 @@ class BoxSection(BeamSection): Web thickness. tf : float Flange thickness. - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str, optional - Section name. If not provided, a unique identifier is automatically - assigned. Attributes ---------- @@ -409,15 +398,20 @@ class BoxSection(BeamSection): ??? gw : float ??? - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str - Section name. If not provided, a unique identifier is automatically - assigned. + + Notes + ----- + Currently you can only specify the thickness of the flanges and the webs. + + Warnings + -------- + - Ixy not yet calculated. """ - def __init__(self, w, h, tw, tf, material, name=None, **kwargs): + def __init__(self, w, h, tw, tf, material, **kwargs): self.w = w self.h = h self.tw = tw @@ -427,8 +421,8 @@ def __init__(self, w, h, tw, tf, material, name=None, **kwargs): p = 2 * ((h - tf) / tw + (w - tw) / tf) A = w * h - (w - 2 * tw) * (h - 2 * tf) - Ixx = (w * h**3) / 12. - ((w - 2 * tw) * (h - 2 * tf)**3) / 12. - Iyy = (h * w**3) / 12. - ((h - 2 * tf) * (w - 2 * tw)**3) / 12. + Ixx = (w * h**3) / 12.0 - ((w - 2 * tw) * (h - 2 * tf) ** 3) / 12.0 + Iyy = (h * w**3) / 12.0 - ((h - 2 * tf) * (w - 2 * tw) ** 3) / 12.0 Ixy = 0 Avx = 0 Avy = 0 @@ -436,8 +430,19 @@ def __init__(self, w, h, tw, tf, material, name=None, **kwargs): g0 = 0 gw = 0 - super(BoxSection, self).__init__(A=A, Ixx=Ixx, Iyy=Iyy, Ixy=Ixy, - Avx=Avx, Avy=Avy, J=J, g0=g0, gw=gw, material=material, name=name, **kwargs) + super(BoxSection, self).__init__( + A=A, + Ixx=Ixx, + Iyy=Iyy, + Ixy=Ixy, + Avx=Avx, + Avy=Avy, + J=J, + g0=g0, + gw=gw, + material=material, + **kwargs, + ) class CircularSection(BeamSection): @@ -447,11 +452,8 @@ class CircularSection(BeamSection): ---------- r : float Radius. - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str, optional - Section name. If not provided, a unique identifier is automatically - assigned. Attributes ---------- @@ -475,19 +477,17 @@ class CircularSection(BeamSection): ??? gw : float ??? - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str - Section name. If not provided, a unique identifier is automatically - assigned. + """ - def __init__(self, r, material, name=None, **kwargs): + def __init__(self, r, material, **kwargs): self.r = r D = 2 * r A = 0.25 * pi * D**2 - Ixx = Iyy = (pi * D**4) / 64. + Ixx = Iyy = (pi * D**4) / 64.0 Ixy = 0 Avx = 0 Avy = 0 @@ -495,8 +495,19 @@ def __init__(self, r, material, name=None, **kwargs): g0 = 0 gw = 0 - super(CircularSection, self).__init__(A=A, Ixx=Ixx, Iyy=Iyy, Ixy=Ixy, - Avx=Avx, Avy=Avy, J=J, g0=g0, gw=gw, material=material, name=name, **kwargs) + super(CircularSection, self).__init__( + A=A, + Ixx=Ixx, + Iyy=Iyy, + Ixy=Ixy, + Avx=Avx, + Avy=Avy, + J=J, + g0=g0, + gw=gw, + material=material, + **kwargs, + ) class HexSection(BeamSection): @@ -535,24 +546,18 @@ class HexSection(BeamSection): ??? gw : float ??? - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str - Section name. If not provided, a unique identifier is automatically - assigned. + """ - def __init__(self, r, t, material, name=None, **kwargs): - raise NotImplementedError('This section is not available for the selected backend') + def __init__(self, r, t, material, **kwargs): + raise NotImplementedError("This section is not available for the selected backend") class ISection(BeamSection): """Equal flanged I-section for beam elements. - Note - ---- - Currently you the thickness of the two flanges is the same. - Parameters ---------- w : float @@ -563,11 +568,8 @@ class ISection(BeamSection): Web thickness. tf : float Flange thickness. - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str, optional - Section name. If not provided, a unique identifier is automatically - assigned. Attributes ---------- @@ -597,31 +599,44 @@ class ISection(BeamSection): ??? gw : float ??? - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str - Section name. If not provided, a unique identifier is automatically - assigned. + + Notes + ----- + Currently you the thickness of the two flanges is the same. + """ - def __init__(self, w, h, tw, tf, material, name=None, **kwargs): + def __init__(self, w, h, tw, tf, material, **kwargs): self.w = w self.h = h self.tw = tw self.tf = tf A = 2 * w * tf + (h - 2 * tf) * tw - Ixx = (tw * (h - 2 * tf)**3) / 12. + 2 * ((tf**3) * w / 12. + w * tf * (h / 2. - tf / 2.)**2) - Iyy = ((h - 2 * tf) * tw**3) / 12. + 2 * ((w**3) * tf / 12.) + Ixx = (tw * (h - 2 * tf) ** 3) / 12.0 + 2 * ((tf**3) * w / 12.0 + w * tf * (h / 2.0 - tf / 2.0) ** 2) + Iyy = ((h - 2 * tf) * tw**3) / 12.0 + 2 * ((w**3) * tf / 12.0) Ixy = 0 Avx = 0 Avy = 0 - J = (1. / 3) * (2 * w * tf**3 + (h - tf) * tw**3) + J = (1.0 / 3) * (2 * w * tf**3 + (h - tf) * tw**3) g0 = 0 gw = 0 - super(ISection, self).__init__(A=A, Ixx=Ixx, Iyy=Iyy, Ixy=Ixy, - Avx=Avx, Avy=Avy, J=J, g0=g0, gw=gw, material=material, name=name, **kwargs) + super(ISection, self).__init__( + A=A, + Ixx=Ixx, + Iyy=Iyy, + Ixy=Ixy, + Avx=Avx, + Avy=Avy, + J=J, + g0=g0, + gw=gw, + material=material, + **kwargs, + ) class PipeSection(BeamSection): @@ -633,11 +648,8 @@ class PipeSection(BeamSection): Outer radius. t : float Wall thickness. - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str, optional - Section name. If not provided, a unique identifier is automatically - assigned. Attributes ---------- @@ -663,30 +675,39 @@ class PipeSection(BeamSection): ??? gw : float ??? - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str - Section name. If not provided, a unique identifier is automatically - assigned. + """ - def __init__(self, r, t, material, name=None, **kwargs): + def __init__(self, r, t, material, **kwargs): self.r = r self.t = t D = 2 * r - A = 0.25 * pi * (D**2 - (D - 2 * t)**2) - Ixx = Iyy = 0.25 * pi * (r**4 - (r - t)**4) + A = 0.25 * pi * (D**2 - (D - 2 * t) ** 2) + Ixx = Iyy = 0.25 * pi * (r**4 - (r - t) ** 4) Ixy = 0 Avx = 0 Avy = 0 - J = (2. / 3) * pi * (r + 0.5 * t) * t**3 + J = (2.0 / 3) * pi * (r + 0.5 * t) * t**3 g0 = 0 gw = 0 - super(PipeSection, self).__init__(A=A, Ixx=Ixx, Iyy=Iyy, Ixy=Ixy, - Avx=Avx, Avy=Avy, J=J, g0=g0, gw=gw, material=material, name=name, **kwargs) + super(PipeSection, self).__init__( + A=A, + Ixx=Ixx, + Iyy=Iyy, + Ixy=Ixy, + Avx=Avx, + Avy=Avy, + J=J, + g0=g0, + gw=gw, + material=material, + **kwargs, + ) class RectangularSection(BeamSection): @@ -698,11 +719,8 @@ class RectangularSection(BeamSection): Width. h : float Height. - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str, optional - Section name. If not provided, a unique identifier is automatically - assigned. Attributes ---------- @@ -728,15 +746,12 @@ class RectangularSection(BeamSection): ??? gw : float ??? - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str - Section name. If not provided, a unique identifier is automatically - assigned. """ - def __init__(self, w, h, material, name=None, **kwargs): + def __init__(self, w, h, material, **kwargs): self.w = w self.h = h @@ -744,8 +759,8 @@ def __init__(self, w, h, material, name=None, **kwargs): l2 = min([w, h]) A = w * h - Ixx = (1 / 12.) * w * h**3 - Iyy = (1 / 12.) * h * w**3 + Ixx = (1 / 12.0) * w * h**3 + Iyy = (1 / 12.0) * h * w**3 Ixy = 0 Avy = 0.833 * A Avx = 0.833 * A @@ -753,17 +768,24 @@ def __init__(self, w, h, material, name=None, **kwargs): g0 = 0 gw = 0 - super(RectangularSection, self).__init__(A=A, Ixx=Ixx, Iyy=Iyy, Ixy=Ixy, - Avx=Avx, Avy=Avy, J=J, g0=g0, gw=gw, material=material, name=name, **kwargs) + super(RectangularSection, self).__init__( + A=A, + Ixx=Ixx, + Iyy=Iyy, + Ixy=Ixy, + Avx=Avx, + Avy=Avy, + J=J, + g0=g0, + gw=gw, + material=material, + **kwargs, + ) class TrapezoidalSection(BeamSection): """Solid trapezoidal cross-section for beam elements. - Warning - ------- - - J not yet calculated. - Parameters ---------- w1 : float @@ -772,11 +794,8 @@ class TrapezoidalSection(BeamSection): Width at top. h : float Height. - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str, optional - Section name. If not provided, a unique identifier is automatically - assigned. Attributes ---------- @@ -804,15 +823,16 @@ class TrapezoidalSection(BeamSection): ??? gw : float ??? - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str - Section name. If not provided, a unique identifier is automatically - assigned. + + Warnings + -------- + - J not yet calculated. """ - def __init__(self, w1, w2, h, material, name=None, **kwargs): + def __init__(self, w1, w2, h, material, **kwargs): self.w1 = w1 self.w2 = w2 self.h = h @@ -820,8 +840,8 @@ def __init__(self, w1, w2, h, material, name=None, **kwargs): # c = (h * (2 * w2 + w1)) / (3. * (w1 + w2)) # NOTE: not used A = 0.5 * (w1 + w2) * h - Ixx = (1 / 12.) * (3 * w2 + w1) * h**3 - Iyy = (1 / 48.) * h * (w1 + w2) * (w2**2 + 7 * w1**2) + Ixx = (1 / 12.0) * (3 * w2 + w1) * h**3 + Iyy = (1 / 48.0) * h * (w1 + w2) * (w2**2 + 7 * w1**2) Ixy = 0 Avx = 0 Avy = 0 @@ -829,8 +849,19 @@ def __init__(self, w1, w2, h, material, name=None, **kwargs): g0 = 0 gw = 0 - super(TrapezoidalSection, self).__init__(A=A, Ixx=Ixx, Iyy=Iyy, Ixy=Ixy, - Avx=Avx, Avy=Avy, J=J, g0=g0, gw=gw, material=material, name=name, **kwargs) + super(TrapezoidalSection, self).__init__( + A=A, + Ixx=Ixx, + Iyy=Iyy, + Ixy=Ixy, + Avx=Avx, + Avy=Avy, + J=J, + g0=g0, + gw=gw, + material=material, + **kwargs, + ) class TrussSection(BeamSection): @@ -840,11 +871,8 @@ class TrussSection(BeamSection): ---------- A : float Area. - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str, optional - Section name. If not provided, a unique identifier is automatically - assigned. Attributes ---------- @@ -866,15 +894,12 @@ class TrussSection(BeamSection): ??? gw : float ??? - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str - Section name. If not provided, a unique identifier is automatically - assigned. """ - def __init__(self, A, material, name=None, **kwargs): + def __init__(self, A, material, **kwargs): Ixx = 0 Iyy = 0 Ixy = 0 @@ -883,8 +908,19 @@ def __init__(self, A, material, name=None, **kwargs): J = 0 g0 = 0 gw = 0 - super(TrussSection, self).__init__(A=A, Ixx=Ixx, Iyy=Iyy, Ixy=Ixy, - Avx=Avx, Avy=Avy, J=J, g0=g0, gw=gw, material=material, name=name, **kwargs) + super(TrussSection, self).__init__( + A=A, + Ixx=Ixx, + Iyy=Iyy, + Ixy=Ixy, + Avx=Avx, + Avy=Avy, + J=J, + g0=g0, + gw=gw, + material=material, + **kwargs, + ) class StrutSection(TrussSection): @@ -894,11 +930,8 @@ class StrutSection(TrussSection): ---------- A : float Area. - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str, optional - Section name. If not provided, a unique identifier is automatically - assigned. Attributes ---------- @@ -920,16 +953,13 @@ class StrutSection(TrussSection): ??? gw : float ??? - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str - Section name. If not provided, a unique identifier is automatically - assigned. """ - def __init__(self, A, material, name=None, **kwargs): - super(StrutSection, self).__init__(A=A, material=material, name=name, **kwargs) + def __init__(self, A, material, **kwargs): + super(StrutSection, self).__init__(A=A, material=material, **kwargs) class TieSection(TrussSection): @@ -939,11 +969,8 @@ class TieSection(TrussSection): ---------- A : float Area. - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str, optional - Section name. If not provided, a unique identifier is automatically - assigned. Attributes ---------- @@ -965,78 +992,65 @@ class TieSection(TrussSection): ??? gw : float ??? - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str - Section name. If not provided, a unique identifier is automatically - assigned. + """ - def __init__(self, A, material, name=None, **kwargs): - super(TieSection, self).__init__(A=A, material=material, name=name, **kwargs) + def __init__(self, A, material, **kwargs): + super(TieSection, self).__init__(A=A, material=material, **kwargs) # ============================================================================== # 2D # ============================================================================== -class ShellSection(_Section): + +class ShellSection(Section): """Section for shell elements. Parameters ---------- t : float Thickness. - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str, optional - Section name. If not provided, a unique identifier is automatically - assigned. Attributes ---------- t : float Thickness. - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str - Section name. If not provided, a unique identifier is automatically - assigned. """ - def __init__(self, t, material, name=None, **kwargs): - super(ShellSection, self).__init__(material=material, name=name, **kwargs) + def __init__(self, t, material, **kwargs): + super(ShellSection, self).__init__(material=material, **kwargs) self.t = t -class MembraneSection(_Section): +class MembraneSection(Section): """Section for membrane elements. Parameters ---------- t : float Thickness. - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str, optional - Section name. If not provided, a unique identifier is automatically - assigned. Attributes ---------- t : float Thickness. - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str - Section name. If not provided, a unique identifier is automatically - assigned. """ - def __init__(self, t, material, name=None, **kwargs): - super(MembraneSection, self).__init__(material=material, name=name, **kwargs) + def __init__(self, t, material, **kwargs): + super(MembraneSection, self).__init__(material=material, **kwargs) self.t = t @@ -1044,25 +1058,21 @@ def __init__(self, t, material, name=None, **kwargs): # 3D # ============================================================================== -class SolidSection(_Section): + +class SolidSection(Section): """Section for solid elements. Parameters ---------- - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str, optional - Section name. If not provided, a unique identifier is automatically - assigned. Attributes ---------- - material : :class:`compas_fea2.model._Material` + material : :class:`compas_fea2.model.Material` The section material. - name : str - Section name. If not provided, a unique identifier is automatically - assigned. + """ - def __init__(self, material, name=None, **kwargs): - super(SolidSection, self).__init__(material=material, name=name, **kwargs) + def __init__(self, material, **kwargs): + super(SolidSection, self).__init__(material=material, **kwargs) diff --git a/src/compas_fea2/postprocess/__init__.py b/src/compas_fea2/postprocess/__init__.py index c6a1fea46..eb8d2f3ee 100644 --- a/src/compas_fea2/postprocess/__init__.py +++ b/src/compas_fea2/postprocess/__init__.py @@ -1,19 +1,3 @@ -""" -******************************************************************************** -postprocess -******************************************************************************** - -.. currentmodule:: compas_fea2.postprocess - -Stresses -======== - -.. autosummary:: - :toctree: generated/ - - principal_stresses - -""" from __future__ import absolute_import from __future__ import division from __future__ import print_function @@ -22,5 +6,5 @@ __all__ = [ - 'principal_stresses', + "principal_stresses", ] diff --git a/src/compas_fea2/problem/__init__.py b/src/compas_fea2/problem/__init__.py index e0320f0c1..5d8942278 100644 --- a/src/compas_fea2/problem/__init__.py +++ b/src/compas_fea2/problem/__init__.py @@ -1,86 +1,3 @@ -""" -******************************************************************************** -problem -******************************************************************************** - -.. currentmodule:: compas_fea2.problem - -Problem -======= - -.. autosummary:: - :toctree: generated/ - - Problem - -Steps -===== - -.. autosummary:: - :toctree: generated/ - - _Step - _GeneralStep - _Perturbation - ModalAnalysis - ComplexEigenValue - StaticStep - LinearStaticPerturbation - BucklingAnalysis - DynamicStep - QuasiStaticStep - DirectCyclicStep - -Prescribed Fields -================= - -.. autosummary:: - :toctree: generated/ - - _PrescribedField - PrescribedTemperatureField - -Loads -===== - -.. autosummary:: - :toctree: generated/ - - _Load - PrestressLoad - PointLoad - LineLoad - AreaLoad - GravityLoad - TributaryLoad - HarmonicPointLoad - HarmonicPressureLoad - ThermalLoad - -Displacements -============= - -.. autosummary:: - :toctree: generated/ - - GeneralDisplacement - -Load Patterns -============= -.. autosummary:: - :toctree: generated/ - - Pattern - -Outputs -======= - -.. autosummary:: - :toctree: generated/ - - FieldOutput - HistoryOutput -""" from __future__ import absolute_import from __future__ import division from __future__ import print_function @@ -121,45 +38,36 @@ DirectCyclicStep, ) -from .outputs import ( - FieldOutput, - HistoryOutput -) +from .outputs import FieldOutput, HistoryOutput __all__ = [ - 'Problem', - - 'GeneralDisplacement', - - '_Load', - 'PrestressLoad', - 'PointLoad', - 'LineLoad', - 'AreaLoad', - 'GravityLoad', - 'TributaryLoad', - 'HarmonicPointLoad', - 'HarmonicPressureLoad', - 'ThermalLoad', - - 'PrescribedTemperatureField', - - 'DeadLoad', - 'LiveLoad', - 'SuperImposedDeadLoad', - - '_Step', - '_GeneralStep', - '_Perturbation', - 'ModalAnalysis', - 'ComplexEigenValue', - 'StaticStep', - 'LinearStaticPerturbation', - 'BucklingAnalysis', - 'DynamicStep', - 'QuasiStaticStep', - 'DirectCyclicStep', - - 'FieldOutput', - 'HistoryOutput', + "Problem", + "GeneralDisplacement", + "_Load", + "PrestressLoad", + "PointLoad", + "LineLoad", + "AreaLoad", + "GravityLoad", + "TributaryLoad", + "HarmonicPointLoad", + "HarmonicPressureLoad", + "ThermalLoad", + "PrescribedTemperatureField", + "DeadLoad", + "LiveLoad", + "SuperImposedDeadLoad", + "_Step", + "_GeneralStep", + "_Perturbation", + "ModalAnalysis", + "ComplexEigenValue", + "StaticStep", + "LinearStaticPerturbation", + "BucklingAnalysis", + "DynamicStep", + "QuasiStaticStep", + "DirectCyclicStep", + "FieldOutput", + "HistoryOutput", ] diff --git a/src/compas_fea2/problem/patterns.py b/src/compas_fea2/problem/patterns.py index 002c3415a..a931e7ccb 100644 --- a/src/compas_fea2/problem/patterns.py +++ b/src/compas_fea2/problem/patterns.py @@ -8,7 +8,6 @@ class Pattern(FEAData): - def __init__(self, value, distribution, name=None, **kwargs): """A pattern is the spatial distribution of a specific set of forces, displacements, temperatures, and other effects which act on a structure. @@ -20,7 +19,7 @@ def __init__(self, value, distribution, name=None, **kwargs): value : :class:`compas_fea2.problem._Load` | :class:`compas_fea2.problem.GeneralDisplacement` The load/displacement of the pattern distribution : list - list of :class:`compas_fea2.model.Node` or :class:`compas_fea2.model._Element` + list of :class:`compas_fea2.model.Node` or :class:`compas_fea2.model.Element` name : str Uniqe identifier. If not provided it is automatically generated. Set a name if you want a more human-readable input file. @@ -30,7 +29,7 @@ def __init__(self, value, distribution, name=None, **kwargs): value : :class:`compas_fea2.problem._Load` The load of the pattern distribution : list - list of :class:`compas_fea2.model.Node` or :class:`compas_fea2.model._Element` + list of :class:`compas_fea2.model.Node` or :class:`compas_fea2.model.Element` name : str Uniqe identifier. """ diff --git a/src/compas_fea2/problem/steps/step.py b/src/compas_fea2/problem/steps/step.py index e745f5bba..a36efeeda 100644 --- a/src/compas_fea2/problem/steps/step.py +++ b/src/compas_fea2/problem/steps/step.py @@ -10,7 +10,7 @@ from compas_fea2.base import FEAData from compas_fea2.model.nodes import Node -from compas_fea2.model.elements import _Element +from compas_fea2.model.elements import Element from compas_fea2.problem.loads import _Load from compas_fea2.problem.loads import GravityLoad @@ -128,13 +128,13 @@ def add_output(self, output): elif isinstance(output, HistoryOutput): self._history_outputs.add(output) else: - raise TypeError('{!r} is not an _Output.'.format(output)) + raise TypeError("{!r} is not an _Output.".format(output)) return output # ========================================================================== # Results methods # ========================================================================== - @timer(message='Step results copied in the model in ') + @timer(message="Step results copied in the model in ") def _store_results_in_model(self, fields=None): """Copy the results for the step in the model object at the nodal and element level. @@ -153,34 +153,34 @@ def _store_results_in_model(self, fields=None): import sqlalchemy as db engine, connection, metadata = create_connection_sqlite3(self.problem.path_results) - FIELDS = get_database_table(engine, metadata, 'fiedls') + FIELDS = get_database_table(engine, metadata, "fiedls") if not fields: field_column = FIELDS.query.all() - fields=[field for field in field_column.field] + fields = [field for field in field_column.field] for field in fields: field_table = get_database_table(engine, metadata, field) _, results = get_all_field_results(engine, metadata, field, field_table) for row in results: part = self.problem.model.find_part_by_name(row[0]) - if row[2] == 'NODAL': + if row[2] == "NODAL": node_element = part.find_node_by_key(row[3]) else: - raise NotImplementedError('elements not supported yet') + raise NotImplementedError("elements not supported yet") node_element._results.setdefault(self.problem, {})[self] = res_field - step_results = results[self.name] # Get part results for part_name, part_results in step_results.items(): # Get node/element results for result_type, nodes_elements_results in part_results.items(): - if result_type not in ['nodes', 'elements']: + if result_type not in ["nodes", "elements"]: continue # nodes_elements = getattr(self.model.find_part_by_name(part_name, casefold=True), result_type) - func = getattr(self.model.find_part_by_name(part_name, casefold=True), - 'find_{}_by_key'.format(result_type[:-1])) + func = getattr( + self.model.find_part_by_name(part_name, casefold=True), "find_{}_by_key".format(result_type[:-1]) + ) # Get field results for key, res_field in nodes_elements_results.items(): node_element = func(key) @@ -190,6 +190,7 @@ def _store_results_in_model(self, fields=None): continue node_element._results.setdefault(self.problem, {})[self] = res_field + # ============================================================================== # General Steps # ============================================================================== @@ -260,7 +261,18 @@ class _GeneralStep(_Step): Dictionary of the prescribed fields assigned to each part in the model in the step. """ - def __init__(self, max_increments, initial_inc_size, min_inc_size, time, nlgeom=False, modify=False, restart=False, name=None, **kwargs): + def __init__( + self, + max_increments, + initial_inc_size, + min_inc_size, + time, + nlgeom=False, + modify=False, + restart=False, + name=None, + **kwargs + ): super(_GeneralStep, self).__init__(name=name, **kwargs) self._max_increments = max_increments @@ -275,13 +287,13 @@ def __init__(self, max_increments, initial_inc_size, min_inc_size, time, nlgeom= def __rmul__(self, other): if not isinstance(other, (float, int)): - raise TypeError('Step multiplication only allowed with real numbers') + raise TypeError("Step multiplication only allowed with real numbers") step_copy = copy.copy(self) step_copy._patterns = set() for pattern in self._patterns: pattern_copy = copy.copy(pattern) load_copy = copy.copy(pattern.load) - pattern_copy._load = other*load_copy + pattern_copy._load = other * load_copy step_copy._add_pattern(pattern_copy) return step_copy @@ -334,7 +346,7 @@ def restart(self, value): # ========================================================================= def _add_pattern(self, load_pattern): - # type: (_Load, Node | _Element) -> _Load + # type: (_Load, Node | Element) -> _Load """Add a general load pattern to the Step object. Warning @@ -356,12 +368,12 @@ def _add_pattern(self, load_pattern): """ if not isinstance(load_pattern, Pattern): - raise TypeError('{!r} is not a LoadPattern.'.format(load_pattern)) + raise TypeError("{!r} is not a LoadPattern.".format(load_pattern)) if self.problem: if self.model: if not list(load_pattern.distribution).pop().model == self.model: - raise ValueError('The load pattern is not applied to a valid reagion of {!r}'.format(self.model)) + raise ValueError("The load pattern is not applied to a valid reagion of {!r}".format(self.model)) # store location in step self._patterns.add(load_pattern) @@ -370,7 +382,7 @@ def _add_pattern(self, load_pattern): return load_pattern def _add_patterns(self, load_patterns): - # type: (_Load, Node | _Element) -> list(_Load) + # type: (_Load, Node | Element) -> list(_Load) """Add a load to multiple locations. Parameters diff --git a/src/compas_fea2/results/__init__.py b/src/compas_fea2/results/__init__.py index 4f595642d..f4b7c0e77 100644 --- a/src/compas_fea2/results/__init__.py +++ b/src/compas_fea2/results/__init__.py @@ -1,27 +1,11 @@ -""" -******************************************************************************** -results -******************************************************************************** - -.. currentmodule:: compas_fea2.results - -.. autosummary:: - :toctree: generated/ - - Results - NodeFieldResults - -""" from __future__ import absolute_import from __future__ import division from __future__ import print_function from .results import Results, NodeFieldResults -from .sql_wrapper import (create_connection_sqlite3, - get_database_table, - ) +from .sql_wrapper import ( + create_connection_sqlite3, + get_database_table, +) -__all__ = [ - 'Results', - 'NodeFieldResults' -] +__all__ = ["Results", "NodeFieldResults"] diff --git a/src/compas_fea2/results/results.py b/src/compas_fea2/results/results.py index a03ea594e..11c64a049 100644 --- a/src/compas_fea2/results/results.py +++ b/src/compas_fea2/results/results.py @@ -47,29 +47,25 @@ def location(self): @property def vector(self): - if len(self.components)==3: + if len(self.components) == 3: return Vector(*list(self.components.values())) @property def value(self): return self.vector.length - def to_file(self, *args, **kwargs): raise NotImplementedError("this function is not available for the selected backend") + class FieldResults(FEAData): def __init__(self, field_name, step, name=None, *args, **kwargs): super(FieldResults, self).__init__(name, *args, **kwargs) self._registration = step self._db_connection = create_connection(self.problem.path_db) self._field_name = field_name - self._components = get_field_labels(*self.db_connection, - self.field_name, - 'components') - self._invariants = get_field_labels(*self.db_connection, - self.field_name, - 'invariants') + self._components = get_field_labels(*self.db_connection, self.field_name, "components") + self._invariants = get_field_labels(*self.db_connection, self.field_name, "invariants") @property def step(self): @@ -91,7 +87,6 @@ def db_connection(self): def db_connection(self, path_db): self._db_connection = create_connection(path_db) - def _get_field_results(self, field): """_summary_ @@ -135,7 +130,7 @@ class NodeFieldResults(FieldResults): def __init__(self, field_name, step, name=None, *args, **kwargs): super(NodeFieldResults, self).__init__(field_name, step, name, *args, **kwargs) self._results = self._link_field_results_to_model(self._get_field_results(field=self.field_name)[1]) - if len(self.results)!=len(self.model.nodes): + if len(self.results) != len(self.model.nodes): raise ValueError('The requested field is not defined at the nodes. Try "show_elements_field" instead".') self._max_components = {c: self._get_limit("MAX", component=c)[0] for c in self._components} self._min_components = {c: self._get_limit("MIN", component=c)[0] for c in self._components} @@ -160,10 +155,11 @@ def results(self): @property def max(self): - return self._max_invariants['magnitude'][0] + return self._max_invariants["magnitude"][0] + @property def min(self): - return self._min_invariants['magnitude'][0] + return self._min_invariants["magnitude"][0] def _link_field_results_to_model(self, field_results): """Converts the values of the results string to actual nodes of the @@ -197,15 +193,15 @@ def _link_field_results_to_model(self, field_results): print("Part {} not found in model".format(row[0])) continue result = Results( - location=part.find_node_by_key(row[2]), - components={col_names[i]: row[i] for i in range(3, len(self.components)+3)}, - invariants={col_names[i]: row[i] for i in range(len(self.components)+3, len(row))} + location=part.find_node_by_key(row[2]), + components={col_names[i]: row[i] for i in range(3, len(self.components) + 3)}, + invariants={col_names[i]: row[i] for i in range(len(self.components) + 3, len(row))}, ) results.append(result) return results def _get_limit(self, limit="MAX", component="magnitude"): - if component not in self.components+self.invariants: + if component not in self.components + self.invariants: raise ValueError( "The specified component is not valid. Choose from {}".format(self._components + self.invariants) ) diff --git a/src/compas_fea2/units/__init__.py b/src/compas_fea2/units/__init__.py index 2957ba85a..ee8fc0be5 100644 --- a/src/compas_fea2/units/__init__.py +++ b/src/compas_fea2/units/__init__.py @@ -1,17 +1,10 @@ -""" -******************************************************************************** -Units -******************************************************************************** - -compas_fe2 can use Pint for units consistency. - -""" - import os from pint import UnitRegistry + HERE = os.path.dirname(__file__) # U.define('@alias pascal = Pa') -def units(system='SI'): - return UnitRegistry(os.path.join(HERE, 'fea2_en.txt'), system=system) + +def units(system="SI"): + return UnitRegistry(os.path.join(HERE, "fea2_en.txt"), system=system) diff --git a/src/compas_fea2/utilities/__init__.py b/src/compas_fea2/utilities/__init__.py index 528baa0c7..e11cace39 100644 --- a/src/compas_fea2/utilities/__init__.py +++ b/src/compas_fea2/utilities/__init__.py @@ -1,32 +1,3 @@ -""" -******************************************************************************** -Utilities -******************************************************************************** - -.. currentmodule:: compas_fea2.utilities - - -Functions -========= - -.. autosummary:: - :toctree: generated/ - - colorbar - combine_all_sets - group_keys_by_attribute - group_keys_by_attributes - identify_ranges - mesh_from_shell_elements - network_order - normalise_data - principal_stresses - process_data - postprocess - plotvoxels - - -""" from __future__ import absolute_import from __future__ import division from __future__ import print_function @@ -43,20 +14,20 @@ principal_stresses, process_data, postprocess, - plotvoxels + plotvoxels, ) __all__ = [ - 'colorbar', - 'combine_all_sets', - 'group_keys_by_attribute', - 'group_keys_by_attributes', - 'identify_ranges', - 'mesh_from_shell_elements', - 'network_order', - 'normalise_data', - 'principal_stresses', - 'process_data', - 'postprocess', - 'plotvoxels' + "colorbar", + "combine_all_sets", + "group_keys_by_attribute", + "group_keys_by_attributes", + "identify_ranges", + "mesh_from_shell_elements", + "network_order", + "normalise_data", + "principal_stresses", + "process_data", + "postprocess", + "plotvoxels", ] diff --git a/tasks.py b/tasks.py index e1531bdea..d1bbbc512 100644 --- a/tasks.py +++ b/tasks.py @@ -17,17 +17,14 @@ docs.linkcheck, tests.test, tests.testdocs, - build.build_ghuser_components, + tests.testcodeblocks, build.prepare_changelog, build.clean, build.release, + build.build_ghuser_components, ) ns.configure( { "base_folder": os.path.dirname(__file__), - "ghuser": { - "source_dir": "src/compas_fea2/ghpython/components", - "target_dir": "src/compas_fea2/ghpython/components/ghuser", - }, } ) diff --git a/tests/test_placeholder.py b/tests/test_placeholder.py index 601241aae..3ada1ee4e 100644 --- a/tests/test_placeholder.py +++ b/tests/test_placeholder.py @@ -1,2 +1,2 @@ def test_placeholder(): - assert True \ No newline at end of file + assert True