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//compas_fea2.git - - -Create a dev environment -======================== - -The recommended way to set up a development environment is with ``conda``. -Make sure to activate the environment before using it... - -.. code-block:: bash - - conda create -n fea2-dev python=3.8 --yes - conda activate fea2 - - -Install the requirements -======================== - -.. code-block:: bash - - pip install -r requirements-dev.txt - -.. note:: - - Note that this will also install ``compas`` and ``openseespy``, - and add an editable install of your fork of ``compas_fea2`` to the environment. - - -Run all checks and tests -======================== - -Before starting to work on your contribution, -it is generally a good idea to run all tests and checks -to make sure you have a healthy clone of the repo. - -.. code-block:: bash - - python -m compas_fea2.test - -.. note:: - - Note that the testing framework is currently not available yet. - - -Create a branch for your contribution -===================================== - -Create and ``checkout`` a new branch on the forked repo. - -.. code-block:: bash - - git branch my-awesome-contribution - git checkout my-awesome-contribution - - -Start making changes -==================== - -This is all you! - -Make sure to commit your changes regularly. -This makes it easier to undo if you change your mind about something... - -Also push the commits regularly to your remote fork. -This way you have plenty of backups in case your computer blows up :) - -.. code-block:: bash - - git commit -a -m "Some meaningful description of awesomeness" - - -Rebase on latest master/main -============================ - -Once you are done, the process of merging your contribution -into ``compas_fea2`` is much simpler if you rebase the contribution branch -of your fork onto the main branch of ``compas_fea2`` before submitting the PR. - -.. note:: - - This is a lot simpler using a Git GUI Client such as - SourceTree, SmartGit or GitKraken than on the command line... - - -Run all checks and tests -======================== - -Before pushing your local fork branch to the remote fork repo -make sure all tests and check still pass -and make changes if necessary. - -.. code-block:: bash - - python -m compas_fea2.test - -.. note:: - - Note that the testing framework is currently not available yet. - - -Push to remote fork -=================== - -Once all your changes have been commited, -the contribution bracnh is rebased onto the main branch of ``compaS_fea2``, -and all tests and checks pass, -push the local branch to the remote fork. - -.. code-block:: bash - - git push diff --git a/docs/gettingstarted.rst b/docs/gettingstarted.installation.rst similarity index 52% rename from docs/gettingstarted.rst rename to docs/gettingstarted.installation.rst index 29bf7c7d8..b1582cb3a 100644 --- a/docs/gettingstarted.rst +++ b/docs/gettingstarted.installation.rst @@ -1,21 +1,6 @@ ******************************************************************************** -Getting Started -******************************************************************************** - -Requirements -============ - -To use ``compas_fea2`` to run analyses must have at least one -of the supported backends installed. - -* Abaqus -* ANSYS -* SOFiSTiK -* OpenSEES - - Installation -============ +******************************************************************************** The recommended way to install ``compas_fea2`` is in in a dedicated ``conda`` environment. @@ -32,21 +17,3 @@ to verify that you have a functional setup with at least one working backend. .. code-block:: bash python -m compas_fea2.test - - -First steps -=========== - -The tutorial and examples are a good place to start exploring. - -* Tutorial -* Examples - - -Known issues -============ - -Currently none :) - -If you do find problems, help us solving them by filing a bug report -on the `Issue Tracker `_ of the repo. diff --git a/docs/intro.rst b/docs/gettingstarted.intro.rst similarity index 97% rename from docs/intro.rst rename to docs/gettingstarted.intro.rst index a6eaf4be2..58a4a7176 100644 --- a/docs/intro.rst +++ b/docs/gettingstarted.intro.rst @@ -2,8 +2,6 @@ Introduction ******************************************************************************** -.. rst-class:: lead - Plug-in architecture ==================== @@ -25,10 +23,11 @@ Workflow The image below describes a general FEA workflow: -.. figure:: /_images/workflow_1.png +.. figure:: /_images/basic_workflow.png :figclass: figure :class: figure-img img-fluid + Collaboration Workflow ====================== @@ -52,9 +51,9 @@ engineer using blender and ansys: :class: figure-img img-fluid - Units ===== + Before starting any model, you need to decide which system of units you will use. ``compas_fea2`` has no built-in system of units. diff --git a/docs/api.rst b/docs/gettingstarted.requirements.rst similarity index 52% rename from docs/api.rst rename to docs/gettingstarted.requirements.rst index b70ed8161..d15065b51 100644 --- a/docs/api.rst +++ b/docs/gettingstarted.requirements.rst @@ -1,8 +1,11 @@ ******************************************************************************** -API Reference +Requirements ******************************************************************************** -.. toctree:: - :maxdepth: 2 +To use ``compas_fea2`` to run analyses must have at least one +of the supported backends installed. - api/compas_fea2 +* Abaqus +* ANSYS +* SOFiSTiK +* OpenSEES diff --git a/docs/index.rst b/docs/index.rst index ffae96d13..077be4409 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,15 +1,58 @@ +:html_theme.sidebar_secondary.remove: + ******************************************************************************** -compas_fea2 +COMPAS FEA2 Documentation ******************************************************************************** +.. rst-class:: lead + +COMPAS FEA2 is a framework for Finite Element Analysis (FEA) written in Python. +It provides a high-level interface to various open-source and commercial FEA software, +with a unified API that is easy to use and extend. + +Table of Contents +================= + +.. toctree:: + :maxdepth: 2 + :caption: Getting Started + + gettingstarted.intro + gettingstarted.requirements + gettingstarted.installation + +.. toctree:: + :maxdepth: 2 + :caption: Basics + + basics.model + basics.problem + basics.analysis + .. toctree:: - :maxdepth: 3 - :titlesonly: + :maxdepth: 2 + :caption: Advanced + + advanced.backends + advanced.results + advanced.visualisation + +.. toctree:: + :maxdepth: 2 + :caption: API Reference + + api/compas_fea2 + +.. toctree:: + :maxdepth: 2 + :caption: Miscellaneous + + license + + +Indices and tables +================== - intro - gettingstarted - backends - tutorial - api - devguide - license +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/tutorial/umat.rst b/docs/tutorial/umat.rst deleted file mode 100644 index 77297df42..000000000 --- a/docs/tutorial/umat.rst +++ /dev/null @@ -1,8 +0,0 @@ -******************************************************************************** -User Material subroutines -******************************************************************************** - -In case you want to use user material subroutines in Abaqus you can follow the following guide: -(original source: https://gist.github.com/franaudo/72362784ded685e4cb381e57020c9ec7) - -.. gist:: https://gist.github.com/franaudo/72362784ded685e4cb381e57020c9ec7 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index fd052f9be..dba4472f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,12 +4,9 @@ line-length = 120 [tool.pytest.ini_options] minversion = "6.0" testpaths = ["tests", "src/compas_fea2"] -python_files = [ - "test_*.py", - "tests.py" -] +python_files = ["test_*.py", "tests.py"] addopts = "-ra --strict --doctest-modules --doctest-glob=*.rst --tb=short" -doctest_optionflags= "NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL ALLOW_UNICODE ALLOW_BYTES NUMBER" +doctest_optionflags = "NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL ALLOW_UNICODE ALLOW_BYTES NUMBER" filterwarnings = "ignore::DeprecationWarning" [tool.isort] @@ -24,4 +21,3 @@ known_first_party = "compas_fea2" default_section = "THIRDPARTY" forced_separate = "test_compas_fea2" skip = ["__init__.py"] - diff --git a/requirements-dev.txt b/requirements-dev.txt index 12c09d678..014564f89 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,36 +1,27 @@ attrs >=17.4 -autopep8 -black +black ==22.12.0 bump2version >=1.0.1 check-manifest >=0.36 compas_invocations doc8 flake8 -graphviz invoke >=0.14 -ipykernel -ipython >=5.8 isort -m2r2 -matplotlib -nbsphinx +jinja2 >= 3.0 +numpydoc +pydata-sphinx-theme pydocstyle -pytest >=3.2 -sphinx >=3.4 +pytest +pytest-mock +sphinx ==4.5 sphinx_compas_theme >=0.15.18 +sphinx-design +sphinx-inline-tabs +sphinx-togglebutton +sphinx-remove-toctrees +sphinx-copybutton +sphinxcontrib-bibtex +sphinxcontrib-youtube twine wheel - -# sphinxcontrib.gist - -# fea2_extensions -compas>=1.0 -compas_gmsh -Click -pint -python-dotenv -sqlalchemy - -e . - - diff --git a/requirements.txt b/requirements.txt index 258b23d5e..4ba80d7e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ Click pint python-dotenv sqlalchemy==1.4 +openseespy diff --git a/setup.cfg b/setup.cfg index a443ffd9b..12e54eb8b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,12 +2,42 @@ universal = 1 [flake8] -max-line-length = 120 +max-line-length = 180 exclude = */migrations/* [doc8] -max-line-length = 120 +max-line-length = 180 ignore = D001 [pydocstyle] convention = numpy + +[tool:pytest] +testpaths = + tests + src/compas_fea2 +norecursedirs = + migrations +python_files = + test_*.py + *_test.py + tests.py +addopts = + -ra + --strict + --doctest-glob=\*.rst + --tb=short +doctest_optionflags = + NORMALIZE_WHITESPACE + IGNORE_EXCEPTION_DETAIL + ALLOW_UNICODE + ALLOW_BYTES + NUMBER + +[isort] +force_single_line = True +line_length = 180 +known_first_party = compas_fea2 +default_section = THIRDPARTY +forced_separate = test_compas_fea2 +skip = migrations, __init__.py diff --git a/setup.py b/setup.py index 113a6930b..1a4191226 100644 --- a/setup.py +++ b/setup.py @@ -16,55 +16,51 @@ def read(*names, **kwargs): - return io.open( - path.join(here, *names), - encoding=kwargs.get('encoding', 'utf8') - ).read() + return io.open(path.join(here, *names), encoding=kwargs.get("encoding", "utf8")).read() -long_description = read('README.md') -requirements = read('requirements.txt').split('\n') +long_description = read("README.md") +requirements = read("requirements.txt").split("\n") optional_requirements = {} setup( - name='compas_fea2', - version='0.1.0', - description='2nd generation of compas_fea', + name="compas_fea2", + version="0.1.0", + description="2nd generation of compas_fea", long_description=long_description, - long_description_content_type='text/markdown', - url='https://github.com/fea2/compas_fea2', - author='Francesco Ranaudo', - author_email='ranaudo@arch.ethz.ch', - license='MIT license', + long_description_content_type="text/markdown", + url="https://github.com/fea2/compas_fea2", + author="Francesco Ranaudo", + author_email="ranaudo@arch.ethz.ch", + license="MIT license", classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'Topic :: Scientific/Engineering', - 'License :: OSI Approved :: MIT License', - 'Operating System :: Unix', - 'Operating System :: POSIX', - 'Operating System :: Microsoft :: Windows', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: Implementation :: CPython', + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Topic :: Scientific/Engineering", + "License :: OSI Approved :: MIT License", + "Operating System :: Unix", + "Operating System :: POSIX", + "Operating System :: Microsoft :: Windows", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: Implementation :: CPython", ], keywords=[], project_urls={}, - packages=['compas_fea2'], - package_dir={'': 'src'}, + packages=["compas_fea2"], + package_dir={"": "src"}, package_data={}, data_files=[], include_package_data=True, zip_safe=False, install_requires=requirements, - python_requires='>=3.8', + python_requires=">=3.8", extras_require=optional_requirements, entry_points={ - 'console_scripts': [ - "fea2=compas_fea2.cli:main"], + "console_scripts": ["fea2=compas_fea2.cli:main"], }, ext_modules=[], ) diff --git a/src/compas_fea2/UI/__init__.py b/src/compas_fea2/UI/__init__.py index 6b421f33d..a435eef0a 100644 --- a/src/compas_fea2/UI/__init__.py +++ b/src/compas_fea2/UI/__init__.py @@ -1,20 +1,3 @@ -""" -******************************************************************************** -User Interfaces -******************************************************************************** - -.. currentmodule:: compas_fea2.UI - -compas_view2 -============ - -.. autosummary:: - :toctree: generated/ - - FEA2Viewer - -""" - from __future__ import absolute_import from __future__ import division from __future__ import print_function @@ -22,5 +5,5 @@ from .viewer import FEA2Viewer __all__ = [ - 'FEA2Viewer', + "FEA2Viewer", ] diff --git a/src/compas_fea2/UI/viewer/shapes.py b/src/compas_fea2/UI/viewer/shapes.py index d90a04f9d..4bb4fd9e1 100644 --- a/src/compas_fea2/UI/viewer/shapes.py +++ b/src/compas_fea2/UI/viewer/shapes.py @@ -2,9 +2,10 @@ from compas.geometry import Circle from compas.geometry import Plane from compas.geometry import Box +from compas.geometry import Frame -class BCShape(): +class BCShape: def __init__(self, xyz, direction, scale): self.x, self.y, self.z = xyz self.direction = direction @@ -14,20 +15,21 @@ def __init__(self, xyz, direction, scale): class PinBCShape(BCShape): def __init__(self, xyz, direction=[0, 0, 1], scale=1): super(PinBCShape, self).__init__(xyz, direction, scale) - self.height = 0.4*self.scale - self.diameter = 0.4*self.scale + self.height = 0.4 * self.scale + self.diameter = 0.4 * self.scale # FIXME this is wrong because it should follow the normal - self.plane = Plane([self.x, self.y, self.z-self.height], direction) - self.circle = Circle(self.plane, self.diameter) - self.shape = Cone(self.circle, self.height) + # self.plane = Plane([self.x, self.y, self.z - self.height], direction) + # self.circle = Circle(self.plane, self.diameter) + frame = Frame([self.x, self.y, self.z - self.height], [1, 0, 0], [0, 1, 0]) + self.shape = Cone(0.5 * self.diameter, self.height, frame=frame) class FixBCShape(BCShape): def __init__(self, xyz, scale=1): super(FixBCShape, self).__init__(xyz, [0, 0, 1], scale) - self.height = 0.8*self.scale - self.shape = Box(([self.x, self.y, self.z-self.height/4], [1, 0, 0], - [0, 1, 0]), self.height, self.height, self.height/2) + self.height = 0.8 * self.scale + frame = Frame([self.x, self.y, self.z - self.height], [1, 0, 0], [0, 1, 0]) + self.shape = Box(self.height, self.height, self.height / 2, frame=frame) class MomentShape(BCShape): diff --git a/src/compas_fea2/UI/viewer/viewer.py b/src/compas_fea2/UI/viewer/viewer.py index 876c2f15d..a269608cc 100644 --- a/src/compas_fea2/UI/viewer/viewer.py +++ b/src/compas_fea2/UI/viewer/viewer.py @@ -16,7 +16,7 @@ from compas_fea2.UI.viewer.shapes import PinBCShape from compas_fea2.UI.viewer.shapes import FixBCShape from compas_fea2.model.elements import ShellElement -from compas_fea2.model.elements import _Element3D +from compas_fea2.model.elements import Element3D from compas_fea2.model.elements import BeamElement from compas_fea2.model.bcs import FixedBC, PinnedBC @@ -30,7 +30,7 @@ def hextorgb(hex): return tuple(i / 255 for i in hex_to_rgb(hex)) -class FEA2Viewer(): +class FEA2Viewer: """Wrapper for the compas_view2 viewer app. Parameters @@ -52,14 +52,14 @@ def __init__(self, width=800, height=500, **kwargs): self.height = height self.app = App(width=width, height=height) - sf = kwargs.get('scale_factor',1) + sf = kwargs.get("scale_factor", 1) - self.app.view.camera.target = [3000*sf, 3000*sf, 1000*sf] - self.app.view.camera.position = [7000*sf, 7000*sf, 5000*sf] - self.app.view.camera.near = 1*sf - self.app.view.camera.far = 100000*sf - self.app.view.camera.scale = 1000*sf - self.app.view.grid.cell_size = 1000*sf + self.app.view.camera.target = [3000 * sf, 3000 * sf, 1000 * sf] + self.app.view.camera.position = [7000 * sf, 7000 * sf, 5000 * sf] + self.app.view.camera.near = 1 * sf + self.app.view.camera.far = 100000 * sf + self.app.view.camera.scale = 1000 * sf + self.app.view.grid.cell_size = 1000 * sf def draw_mesh(self, mesh): self.app.add(mesh, use_vertex_color=True) @@ -84,7 +84,7 @@ def draw_parts(self, parts, draw_nodes=False, node_labels=False, solid=False): parts = parts if isinstance(parts, Iterable) else [parts] for part in parts: if solid: - self.draw_solid_elements(filter(lambda x: isinstance(x, _Element3D), part.elements), draw_nodes) + self.draw_solid_elements(filter(lambda x: isinstance(x, Element3D), part.elements), draw_nodes) else: if part.discretized_boundary_mesh: self.app.add(part.discretized_boundary_mesh, use_vertex_color=True) @@ -104,7 +104,7 @@ def draw_nodes(self, nodes, node_lables): If `True` add the nodes. """ pts = [node.point for node in nodes] - self.app.add(pts, colors=[hextorgb("#386641")]*len(pts)) + self.app.add(pts, colors=[hextorgb("#386641")] * len(pts)) for node in nodes: if node_lables: @@ -116,7 +116,7 @@ def draw_solid_elements(self, elements, show_vertices=True): Parameters ---------- - elements : :class:`compas_fea2.model.ShellElement` | :class:`compas_fea2.model._Element3D` | :class:`compas_fea2.model.BeamElement` + elements : :class:`compas_fea2.model.ShellElement` | :class:`compas_fea2.model.Element3D` | :class:`compas_fea2.model.BeamElement` _description_ show_vertices : bool, optional If `True` show the vertices of the elements, by default True @@ -127,14 +127,14 @@ def draw_solid_elements(self, elements, show_vertices=True): pts = [node.point for node in element.nodes] collection_items.append(Polyhedron(pts, list(element._face_indices.values()))) if collection_items: - self.app.add(Collection(collection_items), facecolor=(.9, .9, .9)) + self.app.add(Collection(collection_items), facecolor=(0.9, 0.9, 0.9)) def draw_shell_elements(self, elements, show_vertices=True): """Draw the elements of a part. Parameters ---------- - elements : :class:`compas_fea2.model.ShellElement` | :class:`compas_fea2.model._Element3D` | :class:`compas_fea2.model.BeamElement` + elements : :class:`compas_fea2.model.ShellElement` | :class:`compas_fea2.model.Element3D` | :class:`compas_fea2.model.BeamElement` _description_ show_vertices : bool, optional If `True` show the vertices of the elements, by default True @@ -150,7 +150,7 @@ def draw_shell_elements(self, elements, show_vertices=True): else: raise NotImplementedError("only 3 and 4 vertices shells supported at the moment") if collection_items: - self.app.add(Collection(collection_items), facecolor=(.9, .9, .9)) + self.app.add(Collection(collection_items), facecolor=(0.9, 0.9, 0.9)) def draw_beam_elements(self, elements, show_vertices=True): """Draw the elements of a part. @@ -170,7 +170,7 @@ def draw_beam_elements(self, elements, show_vertices=True): if collection_items: self.app.add(Collection(collection_items), linewidth=10) - def draw_bcs(self, model, parts=None, scale_factor=1.): + def draw_bcs(self, model, parts=None, scale_factor=1.0): """Draw the support boundary conditions. Parameters @@ -199,7 +199,7 @@ def draw_bcs(self, model, parts=None, scale_factor=1.): if bcs_collection: self.app.add(Collection(bcs_collection), facecolor=(1, 0, 0), opacity=0.5) - def draw_loads(self, step, scale_factor=1., app_point='end'): + def draw_loads(self, step, scale_factor=1.0, app_point="end"): """Draw the applied loads for given steps. Parameters @@ -213,14 +213,18 @@ def draw_loads(self, step, scale_factor=1., app_point='end'): if isinstance(step, _GeneralStep): for pattern in step._patterns: if isinstance(pattern.load, PointLoad): - vector = Vector(x=pattern.load.components['x'] or 0., - y=pattern.load.components['y'] or 0., - z=pattern.load.components['z'] or 0.) + vector = Vector( + x=pattern.load.components["x"] or 0.0, + y=pattern.load.components["y"] or 0.0, + z=pattern.load.components["z"] or 0.0, + ) if vector.length == 0: continue vector.scale(scale_factor) - if app_point=='end': - pts = [[node.x-vector.x, node.y-vector.y, node.z-vector.z] for node in pattern.distribution] + if app_point == "end": + pts = [ + [node.x - vector.x, node.y - vector.y, node.z - vector.z] for node in pattern.distribution + ] else: pts = [node.point for node in pattern.distribution] # TODO add moment components xx, yy, zz @@ -244,26 +248,22 @@ def draw_nodes_vector(self, pts, vectors, colors=None): arrows = [] arrows_properties = [] if not colors: - colors = [(0, 1, 0)]*len(pts) + colors = [(0, 1, 0)] * len(pts) for pt, vector, color in zip(pts, vectors, colors): - arrows.append(Arrow(pt, vector, - head_portion=0.3, head_width=0.15, body_width=0.05)) - arrows_properties.append({"u": 3, - "show_lines": False, - "facecolor": color}) + arrows.append(Arrow(pt, vector, head_portion=0.3, head_width=0.15, body_width=0.05)) + arrows_properties.append({"u": 3, "show_lines": False, "facecolor": color}) if arrows: self.app.add(Collection(arrows, arrows_properties)) def show(self): - """Display the viewport. - """ + """Display the viewport.""" self.app.show() def dynamic_show(self): - """Display the viewport dynamically. - """ + """Display the viewport dynamically.""" self.app.run() + # class BeamViewer(): # pass diff --git a/src/compas_fea2/__init__.py b/src/compas_fea2/__init__.py index bda49fe95..fe35de040 100644 --- a/src/compas_fea2/__init__.py +++ b/src/compas_fea2/__init__.py @@ -1,42 +1,8 @@ -""" -******************************************************************************** -compas_fea2 -******************************************************************************** - -.. currentmodule:: compas_fea2 - - -Core Packages -============= - -.. toctree:: - :maxdepth: 1 - - compas_fea2.model - compas_fea2.problem - compas_fea2.results - compas_fea2.job - compas_fea2.postprocess - compas_fea2.utilities - compas_fea2.units - -User Interfaces -=============== - -.. toctree:: - :maxdepth: 1 - - compas_fea2.cli - compas_fea2.UI - - -""" import os from collections import defaultdict - -import os from dotenv import load_dotenv + __author__ = ["Francesco Ranaudo"] __copyright__ = "Block Research Group" __license__ = "MIT License" @@ -52,9 +18,9 @@ DOCS = os.path.abspath(os.path.join(HOME, "docs")) TEMP = os.path.abspath(os.path.join(HOME, "temp")) -def init_fea2(verbose=False, point_overlap=True, global_tolerance=1, precision='3f'): - """Create a default environment file if it doesn't exist and loads its - variables. + +def init_fea2(verbose=False, point_overlap=True, global_tolerance=1, precision="3f"): + """Create a default environment file if it doesn't exist and loads its variables. Parameters ---------- @@ -66,32 +32,40 @@ def init_fea2(verbose=False, point_overlap=True, global_tolerance=1, precision=' Tolerance for the model, by default 1 precision : str, optional Values approximation, by default '3f' + """ env_path = os.path.abspath(os.path.join(HERE, ".env")) if not os.path.exists(env_path): with open(env_path, "x") as f: - f.write('\n'.join([ - "VERBOSE={}".format(verbose), - "POINT_OVERLAP={}".format(point_overlap), - "GLOBAL_TOLERANCE={}".format(point_overlap), - "PRECISION={}".format(precision) - ])) + f.write( + "\n".join( + [ + "VERBOSE={}".format(verbose), + "POINT_OVERLAP={}".format(point_overlap), + "GLOBAL_TOLERANCE={}".format(point_overlap), + "PRECISION={}".format(precision), + ] + ) + ) load_dotenv(env_path) + if not load_dotenv(): init_fea2() -VERBOSE = os.getenv('VERBOSE').lower() == 'true' -POINT_OVERLAP = os.getenv('POINT_OVERLAP').lower() == 'true' -GLOBAL_TOLERANCE = os.getenv('GLOBAL_TOLERANCE') -PRECISION = os.getenv('PRECISION') +VERBOSE = os.getenv("VERBOSE").lower() == "true" +POINT_OVERLAP = os.getenv("POINT_OVERLAP").lower() == "true" +GLOBAL_TOLERANCE = os.getenv("GLOBAL_TOLERANCE") +PRECISION = os.getenv("PRECISION") BACKEND = None BACKENDS = defaultdict(dict) + def set_precision(precision): global PRECISION PRECISION = precision + # pluggable function to be def _register_backend(): """Create the class registry for the plugin. @@ -103,6 +77,7 @@ def _register_backend(): """ raise NotImplementedError + def set_backend(plugin): """Set the backend plugin to be used. @@ -118,16 +93,17 @@ def set_backend(plugin): If the plugin library is not found. """ import importlib + global BACKEND BACKEND = plugin try: importlib.import_module(plugin)._register_backend() except ImportError: - print('backend plugin not found. Make sure that you have installed it before.') + print("backend plugin not found. Make sure that you have installed it before.") + def _get_backend_implementation(cls): return BACKENDS[BACKEND].get(cls) __all__ = ["HOME", "DATA", "DOCS", "TEMP"] - diff --git a/src/compas_fea2/base.py b/src/compas_fea2/base.py index 47de12fc6..01632301f 100644 --- a/src/compas_fea2/base.py +++ b/src/compas_fea2/base.py @@ -2,14 +2,14 @@ from __future__ import absolute_import from __future__ import division +from typing import Iterable from compas.data import Data import compas_fea2 import importlib -import uuid -from typing import Iterable from abc import abstractmethod + class FEAData(Data): """Base class for all FEA model objects. @@ -20,59 +20,62 @@ class FEAData(Data): in a model and/or problem summary, and for their representation in software-specific calculation files. - Examples - -------- - >>> + Parameters + ---------- + name : str, optional + The name of the object, by default None. If not provided, one is automatically + generated. - """ + Attributes + ---------- + name : str + The name of the object. + registration : compas_fea2 object + The mother object where this object is registered to. - def __init__(self, name=None, *args, **kwargs): - """Base class for all FEA2 objects. - - Parameters - ---------- - name : str, optional - The name of the object, by default None. If not provided, one is automatically - generated. - - Attributes - ---------- - name : str - The name of the object. - registration : compas_fea2 object - The mother object where this object is registered to. - """ - super().__init__() - # NOTE the names length in abaqus is limited to 80 characters - self.uid = uuid.uuid4() - self._name = name or ''.join([c for c in type(self).__name__ if c.isupper()])+"_"+str(id(self)) - self._registration = None + """ def __new__(cls, *args, **kwargs): - """Try to get the backend plug-in implementation, otherwise use the base - one. - """ imp = compas_fea2._get_backend_implementation(cls) if not imp: return super(FEAData, cls).__new__(cls) return super(FEAData, imp).__new__(imp) + def __init__(self, name=None, **kwargs): + super().__init__(name=name, **kwargs) + self._name = name or "".join([c for c in type(self).__name__ if c.isupper()]) + "_" + str(id(self)) + self._registration = None + def __repr__(self): - return '{0}({1})'.format(self.__class__.__name__, id(self)) + return "{0}({1})".format(self.__class__.__name__, id(self)) + + def __str__(self): + title = "compas_fea2 {0} object".format(self.__class__.__name__) + separator = "-" * (len(title)) + data_extended = [] + for a in list( + filter(lambda a: not a.startswith("__") and not a.startswith("_") and a != "jsondefinitions", dir(self)) + ): + try: + attr = getattr(self, a) + if not callable(attr): + if not isinstance(attr, Iterable): + data_extended.append("{0:<15} : {1}".format(a, attr.__repr__())) + else: + data_extended.append("{0:<15} : {1}".format(a, len(attr))) + except Exception: + pass + return """\n{}\n{}\n{}\n""".format(title, separator, "\n".join(data_extended)) @abstractmethod def jobdata(self, *args, **kwargs): """Generate the job data for the backend-specific input file.""" - raise NotImplementedError('This function is not available in the selected plugin.') + raise NotImplementedError("This function is not available in the selected plugin.") @classmethod def from_name(cls, name, **kwargs): """Create an instance of a class of the registered plugin from its name. - Note - ---- - By convention, only hidden class can be called by this method. - Parameters ---------- name : str @@ -82,53 +85,13 @@ def from_name(cls, name, **kwargs): ------- obj The wanted object - """ - obj = cls(**kwargs) - module_info = obj.__module__.split('.') - obj = getattr(importlib.import_module('.'.join([*module_info[:-1]])), '_'+name) - return obj(**kwargs) - - def data(self): - pass - - def __str__(self): - """String representation of the object. - - This method is used to explicitly convert the object to a string, with :func:``str``, - or implicitly, using the print function. - - Returns - ------- - str - - Examples - -------- - Convert the object to a string. - This returns a value. - >>> s = str(obj) - >>> s - '...' - - Print the object. - This does not return a value. + Notes + ----- + By convention, only hidden class can be called by this method. - >>> p = print(obj) - '...' - >>> p - None """ - title = 'compas_fea2 {0} object'.format(self.__class__.__name__) - separator = '-' * (len(title)) - data_extended = [] - for a in list(filter(lambda a: not a.startswith('__') and not a.startswith('_') and a != 'jsondefinitions', dir(self))): - try: - attr = getattr(self, a) - if not callable(attr): - if not isinstance(attr, Iterable): - data_extended.append('{0:<15} : {1}'.format(a, attr.__repr__())) - else: - data_extended.append('{0:<15} : {1}'.format(a, len(attr))) - except Exception: - pass - return """\n{}\n{}\n{}\n""".format(title, separator, '\n'.join(data_extended)) + obj = cls(**kwargs) + module_info = obj.__module__.split(".") + obj = getattr(importlib.import_module(".".join([*module_info[:-1]])), "_" + name) + return obj(**kwargs) diff --git a/src/compas_fea2/cli/cli.py b/src/compas_fea2/cli.py similarity index 75% rename from src/compas_fea2/cli/cli.py rename to src/compas_fea2/cli.py index b42b9048d..a03f4efa1 100644 --- a/src/compas_fea2/cli/cli.py +++ b/src/compas_fea2/cli.py @@ -4,12 +4,9 @@ import sys import os import click -from compas_fea2 import HOME - -import os import json -import sys +from compas_fea2 import HOME from fea2_extension.main import init_plugin @@ -31,8 +28,8 @@ def one_o_one(): @main.command() -@click.option('--clean', default='False', help='remove existing directories') -@click.argument('backend') +@click.option("--clean", default="False", help="remove existing directories") +@click.argument("backend") def init_backend(backend, clean): """Initialize a bare backend module.\n backend : txt\n @@ -41,11 +38,12 @@ def init_backend(backend, clean): init_plugin(HOME, backend, clean) backend = backend.lower() + @main.command() # @click.option('--clean', default='False', help='remove existing directories') -@click.argument('backend') -@click.argument('setting') -@click.argument('value') +@click.argument("backend") +@click.argument("setting") +@click.argument("value") def change_settings(backend, setting, value): """Change a setting for the specified backend.\n backend : txt\n @@ -55,15 +53,16 @@ def change_settings(backend, setting, value): value : txt\n The new value for the setting. """ - backend_settings = os.path.join(HOME, 'src', 'compas_fea2', 'backends', backend.lower(), 'settings.json') + backend_settings = os.path.join(HOME, "src", "compas_fea2", "backends", backend.lower(), "settings.json") - with open(backend_settings, 'r') as f: + with open(backend_settings, "r") as f: settings = json.load(f) - with open(backend_settings, 'w') as f: - settings[setting]=value + with open(backend_settings, "w") as f: + settings[setting] = value json.dump(settings, f) + # -------------------------------- DEBUG ----------------------------------# if __name__ == "__main__": sys.exit(main.init_backend()) diff --git a/src/compas_fea2/cli/__init__.py b/src/compas_fea2/cli/__init__.py deleted file mode 100644 index 174b9a64e..000000000 --- a/src/compas_fea2/cli/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -******************************************************************************** -Command Line Interface -******************************************************************************** - -.. currentmodule:: compas_fea2.cli -""" - -from .cli import * diff --git a/src/compas_fea2/job/__init__.py b/src/compas_fea2/job/__init__.py index 21231b685..ba7b1af49 100644 --- a/src/compas_fea2/job/__init__.py +++ b/src/compas_fea2/job/__init__.py @@ -1,17 +1,3 @@ -""" -******************************************************************************** -job -******************************************************************************** - -.. currentmodule:: compas_fea2.job - -.. autosummary:: - :toctree: generated/ - - InputFile - ParametersFile - -""" from __future__ import absolute_import from __future__ import division from __future__ import print_function @@ -19,7 +5,4 @@ from .input_file import InputFile from .input_file import ParametersFile -__all__ = [ - 'InputFile', - 'ParametersFile' -] +__all__ = ["InputFile", "ParametersFile"] diff --git a/src/compas_fea2/job/input_file.py b/src/compas_fea2/job/input_file.py index 5b10e778d..cb422a90c 100644 --- a/src/compas_fea2/job/input_file.py +++ b/src/compas_fea2/job/input_file.py @@ -4,7 +4,6 @@ import os from compas_fea2.base import FEAData -from compas_fea2.utilities._utils import timer class InputFile(FEAData): @@ -27,6 +26,7 @@ class InputFile(FEAData): The model associated to the Problem. path : str Complete path to the input file. + """ def __init__(self, name=None, **kwargs): @@ -48,7 +48,6 @@ def model(self): def path(self): return self._path - @classmethod def from_problem(cls, problem): """Create an InputFile object from a :class:`compas_fea2.problem.Problem` @@ -62,17 +61,19 @@ def from_problem(cls, problem): ------- obj InputFile for the analysis. + """ input_file = cls() input_file._registration = problem input_file._job_name = problem._name - input_file._file_name = '{}.{}'.format(problem._name, input_file._extension) + input_file._file_name = "{}.{}".format(problem._name, input_file._extension) input_file._path = problem.path.joinpath(input_file._file_name) return input_file # ============================================================================== # General methods # ============================================================================== + def write_to_file(self, path=None): """Writes the InputFile to a file in a specified location. @@ -86,19 +87,20 @@ def write_to_file(self, path=None): ------- str Information about the results of the writing process. + """ path = path or self.problem.path if not path: - raise ValueError('A path to the folder for the input file must be provided') + raise ValueError("A path to the folder for the input file must be provided") file_path = os.path.join(path, self._file_name) - with open(file_path, 'w') as f: + with open(file_path, "w") as f: f.writelines(self.jobdata()) - print('Input file generated in: {}'.format(file_path)) + print("Input file generated in: {}".format(file_path)) class ParametersFile(InputFile): - """ - """ + """""" + def __init__(self, name=None, **kwargs): super(ParametersFile, self).__init__(name, **kwargs) raise NotImplementedError() diff --git a/src/compas_fea2/model/__init__.py b/src/compas_fea2/model/__init__.py index fb0dd2f42..f7379bd4c 100644 --- a/src/compas_fea2/model/__init__.py +++ b/src/compas_fea2/model/__init__.py @@ -1,162 +1,3 @@ -""" -******************************************************************************** -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 - -""" from __future__ import absolute_import from __future__ import division from __future__ import print_function @@ -168,7 +9,7 @@ ) from .nodes import Node from .elements import ( - _Element, + Element, MassElement, BeamElement, SpringElement, @@ -177,12 +18,12 @@ TieElement, ShellElement, MembraneElement, - _Element3D, + Element3D, TetrahedronElement, HexahedronElement, ) from .materials import ( - _Material, + Material, Concrete, ConcreteSmearedCrack, ConcreteDamagedPlasticity, @@ -195,7 +36,7 @@ Timber, ) from .sections import ( - _Section, + Section, MassSection, BeamSection, SpringSection, @@ -215,26 +56,26 @@ TieSection, ) from .constraints import ( - _Constraint, - _MultiPointConstraint, + Constraint, + MultiPointConstraint, TieMPC, BeamMPC, TieConstraint, ) from .groups import ( - _Group, + Group, NodesGroup, ElementsGroup, FacesGroup, PartsGroup, ) from .releases import ( - _BeamEndRelease, + BeamEndRelease, BeamEndPinRelease, BeamEndSliderRelease, ) from .bcs import ( - _BoundaryCondition, + BoundaryCondition, GeneralBC, FixedBC, PinnedBC, @@ -250,95 +91,87 @@ ) from .ics import ( - _InitialCondition, + InitialCondition, InitialTemperatureField, InitialStressField, ) -__all__ = [ - 'Model', - - 'DeformablePart', - 'RigidPart', - 'Node', - - '_Element', - 'MassElement', - 'BeamElement', - 'SpringElement', - 'TrussElement', - 'StrutElement', - 'TieElement', - 'ShellElement', - 'MembraneElement', - '_Element3D', - 'TetrahedronElement', - 'HexahedronElement', - - '_Material', - 'UserMaterial', - 'Concrete', - 'ConcreteSmearedCrack', - 'ConcreteDamagedPlasticity', - 'ElasticIsotropic', - 'Stiff', - 'ElasticOrthotropic', - 'ElasticPlastic', - 'Steel', - 'Timber', - - 'HardContactFrictionPenalty', - 'HardContactNoFriction', - 'HardContactRough', - - '_Section', - 'MassSection', - 'BeamSection', - 'SpringSection', - 'AngleSection', - 'BoxSection', - 'CircularSection', - 'HexSection', - 'ISection', - 'PipeSection', - 'RectangularSection', - 'ShellSection', - 'MembraneSection', - 'SolidSection', - 'TrapezoidalSection', - 'TrussSection', - 'StrutSection', - 'TieSection', - '_Constraint', - '_MultiPointConstraint', - 'TieMPC', - 'BeamMPC', - 'TieConstraint', - - '_BeamEndRelease', - 'BeamEndPinRelease', - - '_Group', - 'NodesGroup', - 'ElementsGroup', - 'FacesGroup', - 'PartsGroup', - - '_BoundaryCondition', - 'GeneralBC', - 'FixedBC', - 'PinnedBC', - 'ClampBCXX', - 'ClampBCYY', - 'ClampBCZZ', - 'RollerBCX', - 'RollerBCY', - 'RollerBCZ', - 'RollerBCXY', - 'RollerBCYZ', - 'RollerBCXZ', - - '_InitialCondition', - 'InitialTemperatureField', - 'InitialStressField', +__all__ = [ + "Model", + "DeformablePart", + "RigidPart", + "Node", + "Element", + "MassElement", + "BeamElement", + "SpringElement", + "TrussElement", + "StrutElement", + "TieElement", + "ShellElement", + "MembraneElement", + "Element3D", + "TetrahedronElement", + "HexahedronElement", + "Material", + "UserMaterial", + "Concrete", + "ConcreteSmearedCrack", + "ConcreteDamagedPlasticity", + "ElasticIsotropic", + "Stiff", + "ElasticOrthotropic", + "ElasticPlastic", + "Steel", + "Timber", + "HardContactFrictionPenalty", + "HardContactNoFriction", + "HardContactRough", + "Section", + "MassSection", + "BeamSection", + "SpringSection", + "AngleSection", + "BoxSection", + "CircularSection", + "HexSection", + "ISection", + "PipeSection", + "RectangularSection", + "ShellSection", + "MembraneSection", + "SolidSection", + "TrapezoidalSection", + "TrussSection", + "StrutSection", + "TieSection", + "Constraint", + "MultiPointConstraint", + "TieMPC", + "BeamMPC", + "TieConstraint", + "BeamEndRelease", + "BeamEndPinRelease", + "BeamEndSliderRelease", + "Group", + "NodesGroup", + "ElementsGroup", + "FacesGroup", + "PartsGroup", + "BoundaryCondition", + "GeneralBC", + "FixedBC", + "PinnedBC", + "ClampBCXX", + "ClampBCYY", + "ClampBCZZ", + "RollerBCX", + "RollerBCY", + "RollerBCZ", + "RollerBCXY", + "RollerBCYZ", + "RollerBCXZ", + "InitialCondition", + "InitialTemperatureField", + "InitialStressField", ] diff --git a/src/compas_fea2/model/bcs.py b/src/compas_fea2/model/bcs.py index 4a60fcb6c..410633ad3 100644 --- a/src/compas_fea2/model/bcs.py +++ b/src/compas_fea2/model/bcs.py @@ -4,53 +4,46 @@ from compas_fea2.base import FEAData -docs = """ -Note ----- -BoundaryConditions are registered to a :class:`compas_fea2.model.Model`. - -Warning -------- -The `axes` parameter is WIP. Currently only global axes can be used. - -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. -axes : str, optional - The refernce axes. - -Attributes ----------- -name : str - Uniqe identifier. -x : bool - Restrain translations along the x axis. -y : bool - Restrain translations along the y axis. -z : bool - Restrain translations along the z axis. -xx : bool - Restrain rotations around the x axis. -yy : bool - Restrain rotations around the y axis. -zz : bool - Restrain rotations around the z axis. -components : dict - Dictionary with component-value pairs summarizing the boundary condition. -axes : str - The refernce axes. -""" - - -class _BoundaryCondition(FEAData): + +class BoundaryCondition(FEAData): """Base class for all zero-valued boundary conditions. + + Parameters + ---------- + axes : str, optional + The refernce axes. + + Attributes + ---------- + x : bool + Restrain translations along the x axis. + y : bool + Restrain translations along the y axis. + z : bool + Restrain translations along the z axis. + xx : bool + Restrain rotations around the x axis. + yy : bool + Restrain rotations around the y axis. + zz : bool + Restrain rotations around the z axis. + components : dict + Dictionary with component-value pairs summarizing the boundary condition. + axes : str + The reference axes. + + Notes + ----- + BoundaryConditions are registered to a :class:`compas_fea2.model.Model`. + + Warnings + -------- + The `axes` parameter is WIP. Currently only global axes can be used. + """ - __doc__ += docs - def __init__(self, axes='global', name=None, **kwargs): - super(_BoundaryCondition, self).__init__(name=name, **kwargs) + def __init__(self, axes="global", **kwargs): + super(BoundaryCondition, self).__init__(**kwargs) self._axes = axes self._x = False self._y = False @@ -93,32 +86,31 @@ def axes(self, value): @property def components(self): - return {c: getattr(self, c) for c in ['x', 'y', 'z', 'xx', 'yy', 'zz']} - + return {c: getattr(self, c) for c in ["x", "y", "z", "xx", "yy", "zz"]} + + +class GeneralBC(BoundaryCondition): + """Customized boundary condition. + + Parameters + ---------- + x : bool + Restrain translations along the x axis. + y : bool + Restrain translations along the y axis. + z : bool + Restrain translations along the z axis. + xx : bool + Restrain rotations around the x axis. + yy : bool + Restrain rotations around the y axis. + zz : bool + Restrain rotations around the z axis. -class GeneralBC(_BoundaryCondition): - """Costumized boundary condition. - """ - __doc__ += docs - __doc__ += """ -Additional Parameters ---------------------- -x : bool - Restrain translations along the x axis. -y : bool - Restrain translations along the y axis. -z : bool - Restrain translations along the z axis. -xx : bool - Restrain rotations around the x axis. -yy : bool - Restrain rotations around the y axis. -zz : bool - Restrain rotations around the z axis. """ - def __init__(self, name=None, x=False, y=False, z=False, xx=False, yy=False, zz=False, **kwargs): - super(GeneralBC, self).__init__(name=name, **kwargs) + def __init__(self, x=False, y=False, z=False, xx=False, yy=False, zz=False, **kwargs): + super(GeneralBC, self).__init__(**kwargs) self._x = x self._y = y self._z = z @@ -127,13 +119,11 @@ def __init__(self, name=None, x=False, y=False, z=False, xx=False, yy=False, zz= self._zz = zz -class FixedBC(_BoundaryCondition): - """A fixed nodal displacement boundary condition. - """ - __doc__ += docs +class FixedBC(BoundaryCondition): + """A fixed nodal displacement boundary condition.""" - def __init__(self, name=None, **kwargs): - super(FixedBC, self).__init__(name=name, **kwargs) + def __init__(self, **kwargs): + super(FixedBC, self).__init__(**kwargs) self._x = True self._y = True self._z = True @@ -142,104 +132,84 @@ def __init__(self, name=None, **kwargs): self._zz = True -class PinnedBC(_BoundaryCondition): - """A pinned nodal displacement boundary condition. - """ - __doc__ += docs +class PinnedBC(BoundaryCondition): + """A pinned nodal displacement boundary condition.""" - def __init__(self, name=None, **kwargs): - super(PinnedBC, self).__init__(name=name, **kwargs) + def __init__(self, **kwargs): + super(PinnedBC, self).__init__(**kwargs) self._x = True self._y = True self._z = True class ClampBCXX(PinnedBC): - """A pinned nodal displacement boundary condition clamped in XX. - """ - __doc__ += docs + """A pinned nodal displacement boundary condition clamped in XX.""" - def __init__(self, name=None, **kwargs): - super(ClampBCXX, self).__init__(name=name, **kwargs) + def __init__(self, **kwargs): + super(ClampBCXX, self).__init__(**kwargs) self._xx = True class ClampBCYY(PinnedBC): - """A pinned nodal displacement boundary condition clamped in YY. - """ - __doc__ += docs + """A pinned nodal displacement boundary condition clamped in YY.""" - def __init__(self, name=None, **kwargs): - super(ClampBCYY, self).__init__(name=name, **kwargs) + def __init__(self, **kwargs): + super(ClampBCYY, self).__init__(**kwargs) self._yy = True class ClampBCZZ(PinnedBC): - """A pinned nodal displacement boundary condition clamped in ZZ. - """ - __doc__ += docs + """A pinned nodal displacement boundary condition clamped in ZZ.""" - def __init__(self, name=None, **kwargs): - super(ClampBCZZ, self).__init__(name=name, **kwargs) + def __init__(self, **kwargs): + super(ClampBCZZ, self).__init__(**kwargs) self._zz = True class RollerBCX(PinnedBC): - """A pinned nodal displacement boundary condition released in X. - """ - __doc__ += docs + """A pinned nodal displacement boundary condition released in X.""" - def __init__(self, name=None, **kwargs): - super(RollerBCX, self).__init__(name=name, **kwargs) + def __init__(self, **kwargs): + super(RollerBCX, self).__init__(**kwargs) self._x = False class RollerBCY(PinnedBC): - """A pinned nodal displacement boundary condition released in Y. - """ - __doc__ += docs + """A pinned nodal displacement boundary condition released in Y.""" - def __init__(self, name=None, **kwargs): - super(RollerBCY, self).__init__(name=name, **kwargs) + def __init__(self, **kwargs): + super(RollerBCY, self).__init__(**kwargs) self._y = False class RollerBCZ(PinnedBC): - """A pinned nodal displacement boundary condition released in Z. - """ - __doc__ += docs + """A pinned nodal displacement boundary condition released in Z.""" - def __init__(self, name=None, **kwargs): - super(RollerBCZ, self).__init__(name=name, **kwargs) + def __init__(self, **kwargs): + super(RollerBCZ, self).__init__(**kwargs) self._z = False class RollerBCXY(PinnedBC): - """A pinned nodal displacement boundary condition released in X and Y. - """ - __doc__ += docs + """A pinned nodal displacement boundary condition released in X and Y.""" - def __init__(self, name=None, **kwargs): - super(RollerBCXY, self).__init__(name=name, **kwargs) + def __init__(self, **kwargs): + super(RollerBCXY, self).__init__(**kwargs) self._x = False self._y = False class RollerBCYZ(PinnedBC): - """A pinned nodal displacement boundary condition released in Y and Z. - """ - __doc__ += docs + """A pinned nodal displacement boundary condition released in Y and Z.""" - def __init__(self, name=None, **kwargs): - super(RollerBCYZ, self).__init__(name=name, **kwargs) + def __init__(self, **kwargs): + super(RollerBCYZ, self).__init__(**kwargs) self._y = False self._z = False class RollerBCXZ(PinnedBC): - """A pinned nodal displacement boundary condition released in X and Z. - """ - __doc__ += docs + """A pinned nodal displacement boundary condition released in X and Z.""" def __init__(self, name=None, **kwargs): super(RollerBCXZ, self).__init__(name=name, **kwargs) diff --git a/src/compas_fea2/model/connectors.py b/src/compas_fea2/model/connectors.py index c96afa74e..6d0e1b688 100644 --- a/src/compas_fea2/model/connectors.py +++ b/src/compas_fea2/model/connectors.py @@ -5,35 +5,23 @@ from compas_fea2.base import FEAData -class _Connector(FEAData): - """Initialises base Connector object. A Connector links a node to one or more - other nodes in the model. +class Connector(FEAData): + """Base class for connectors. - Note - ---- - Connectors are registered to a :class:`compas_fea2.model.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. + A Connector links a node to one or more other nodes in the model. - Attributes - ---------- - name : str - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. + Notes + ----- + Connectors are registered to a :class:`compas_fea2.model.Model`. """ - def __init__(self, *, name=None, **kwargs): - super(_Connector, self).__init__(name, **kwargs) + def __init__(self, **kwargs): + super(Connector, self).__init__(**kwargs) -class Spring(_Connector): - """Elastic spring connector. - """ - __doc__ += _Connector.__doc__ - def __init__(self, name=None, **kwargs): - super(Spring, self).__init__(name=name, **kwargs) +class Spring(Connector): + """Elastic spring connector.""" + + def __init__(self, **kwargs): + super(Spring, self).__init__(**kwargs) diff --git a/src/compas_fea2/model/constraints.py b/src/compas_fea2/model/constraints.py index 3d0a146ea..73a4f8315 100644 --- a/src/compas_fea2/model/constraints.py +++ b/src/compas_fea2/model/constraints.py @@ -5,42 +5,24 @@ from compas_fea2.base import FEAData -class _Constraint(FEAData): - """A constraint removes degree of freedom of nodes in the model. +class Constraint(FEAData): + """Base class for constraints. - Note - ---- - Constraints are registered to a :class:`compas_fea2.model.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. - - Attributes - ---------- - name : str - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. + A constraint removes degree of freedom of nodes in the model. """ - def __init__(self, *, name=None, **kwargs): - super(_Constraint, self).__init__(name, **kwargs) + def __init__(self, **kwargs): + super(Constraint, self).__init__(**kwargs) + # ------------------------------------------------------------------------------ # MPC # ------------------------------------------------------------------------------ -class _MultiPointConstraint(_Constraint): - """A MultiPointContrstaint (MPC) links a node (master) to other nodes - (slaves) in the model. - - Note - ---- - Constraints are registered to a :class:`compas_fea2.model.Model`. +class MultiPointConstraint(Constraint): + """A MultiPointContrstaint (MPC) links a node (master) to other nodes (slaves) in the model. Parameters ---------- @@ -50,47 +32,38 @@ class _MultiPointConstraint(_Constraint): List or Group of nodes that act as slaves. tol : float Constraint tolerance, distance limit between master and slaves. - name : str,optional - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. Attributes ---------- - name : str - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. master : :class:`compas_fea2.model.Node` Node that act as master. slaves : [:class:`compas_fea2.model.Node`] | :class:`compas_fea2.model.NodesGroup` List or Group of nodes that act as slaves. tol : float Constraint tolerance, distance limit between master and slaves. + + Notes + ----- + Constraints are registered to a :class:`compas_fea2.model.Model`. + """ - def __init__(self, constraint_type, name=None, **kwargs): - super(_MultiPointConstraint, self).__init__(name=name, **kwargs) + def __init__(self, constraint_type, **kwargs): + super(MultiPointConstraint, self).__init__(**kwargs) self.constraint_type = constraint_type -class TieMPC(_MultiPointConstraint): - """Tie MPC that constraints axial translations. - """ - __doc__ += _MultiPointConstraint.__doc__ +class TieMPC(MultiPointConstraint): + """Tie MPC that constraints axial translations.""" -class BeamMPC(_MultiPointConstraint): - """Beam MPC that constraints axial translations and rotations. - """ - __doc__ += _MultiPointConstraint.__doc__ +class BeamMPC(MultiPointConstraint): + """Beam MPC that constraints axial translations and rotations.""" -#TODO check! -class _SurfaceConstraint(_Constraint): - """A SurfaceContrstaint links a surface (master) to another surface (slave) - in the model. - Note - ---- - Constraints are registered to a :class:`compas_fea2.model.Model`. +# TODO check! +class SurfaceConstraint(Constraint): + """A SurfaceContrstaint links a surface (master) to another surface (slave) in the model. Parameters ---------- @@ -100,23 +73,18 @@ class _SurfaceConstraint(_Constraint): List or Group of nodes that act as slaves. tol : float Constraint tolerance, distance limit between master and slaves. - name : str,optional - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. Attributes ---------- - name : str - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. master : :class:`compas_fea2.model.Node` Node that act as master. slaves : [:class:`compas_fea2.model.Node`] | :class:`compas_fea2.model.NodesGroup` List or Group of nodes that act as slaves. tol : float Constraint tolerance, distance limit between master and slaves. - """ -class TieConstraint(_SurfaceConstraint): - """Tie constraint between two surfaces. """ + + +class TieConstraint(SurfaceConstraint): + """Tie constraint between two surfaces.""" diff --git a/src/compas_fea2/model/elements.py b/src/compas_fea2/model/elements.py index 240ec87ea..7e57d45cd 100644 --- a/src/compas_fea2/model/elements.py +++ b/src/compas_fea2/model/elements.py @@ -7,34 +7,16 @@ from compas.geometry import Frame from compas.geometry import Plane from compas.utilities import pairwise -from compas.datastructures import Mesh from compas.geometry import Polygon -from compas.datastructures import mesh_thicken -import compas_fea2 from compas_fea2.base import FEAData - -class _Element(FEAData): +class Element(FEAData): """Initialises a base Element object. - Note - ---- - Elements are registered to the same :class:`compas_fea2.model._Part` as well - as its nodes and can belong to only one Part. - - Warning - ------- - When an Element is added to a Part, the nodes of the elements are also added - and registered to the same part. This might change the original registration - of the nodes! - 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. nodes : list[:class:`compas_fea2.model.Node`] Ordered list of node identifiers to which the element connects. section : :class:`compas_fea2.model._Section` @@ -47,8 +29,6 @@ class _Element(FEAData): Attributes ---------- - name : str - Uniqe identifier. key : int, read-only Identifier of the element in the parent part. nodes : list[:class:`compas_fea2.model.Node`] @@ -78,11 +58,23 @@ class _Element(FEAData): rigid : bool, read-only Define the element as rigid (no deformations allowed) or not. For Rigid elements sections are not needed. + + Notes + ----- + Elements and their nodes are registered to the same :class:`compas_fea2.model._Part` and can belong to only one Part. + + Warnings + -------- + When an Element is added to a Part, the nodes of the elements are also added + and registered to the same part. This might change the original registration + of the nodes! + """ -# FIXME frame and orientations are a bit different concepts. find a way to unify them - def __init__(self, *, nodes, section, frame=None, implementation=None, name=None, **kwargs): - super(_Element, self).__init__(name, **kwargs) + # FIXME frame and orientations are a bit different concepts. find a way to unify them + + def __init__(self, nodes, section, frame=None, implementation=None, **kwargs): + super(Element, self).__init__(**kwargs) self._nodes = self._check_nodes(nodes) self._registration = nodes[0]._registration self._section = section @@ -90,10 +82,8 @@ def __init__(self, *, nodes, section, frame=None, implementation=None, name=None self._implementation = implementation self._on_boundary = None self._key = None - self._area = None self._volume = None - self._results = {} self._rigid = False @@ -119,7 +109,7 @@ def nodes(self, value): @property def nodes_key(self): - return '-'.join(sorted([str(node.key) for node in self.nodes], key=int)) + return "-".join(sorted([str(node.key) for node in self.nodes], key=int)) @property def section(self): @@ -153,7 +143,7 @@ def on_boundary(self, value): def _check_nodes(self, nodes): if len(set([node._registration for node in nodes])) != 1: - raise ValueError('At least one of node is registered to a different part or not registered') + raise ValueError("At least one of node is registered to a different part or not registered") return nodes @property @@ -172,25 +162,26 @@ def results(self): def rigid(self): return self._rigid + # ============================================================================== # 0D elements # ============================================================================== -class MassElement(_Element): - """A 0D element for concentrated point mass. - """ +class MassElement(Element): + """A 0D element for concentrated point mass.""" # ============================================================================== # 1D elements # ============================================================================== -class _Element1D(_Element): - """Element with 1 dimension. - """ -class BeamElement(_Element1D): +class Element1D(Element): + """Element with 1 dimension.""" + + +class BeamElement(Element1D): """A 1D element that resists axial, shear, bending and torsion. A beam element is a one-dimensional line element in three-dimensional space @@ -201,40 +192,60 @@ class BeamElement(_Element1D): """ -class SpringElement(_Element1D): - """A 1D spring element. - """ +class SpringElement(Element1D): + """A 1D spring element.""" -class TrussElement(_Element1D): - """A 1D element that resists axial loads. - """ +class TrussElement(Element1D): + """A 1D element that resists axial loads.""" class StrutElement(TrussElement): - """A truss element that resists axial compressive loads. - """ + """A truss element that resists axial compressive loads.""" class TieElement(TrussElement): - """A truss element that resists axial tensile loads. - """ + """A truss element that resists axial tensile loads.""" # ============================================================================== # 2D elements # ============================================================================== + class Face(FEAData): - """_summary_ + """Element representing a face. Parameters ---------- - FEAData : _type_ - _description_ + nodes : list[:class:`compas_fea2.model.Node`] + Ordered list of node identifiers to which the element connects. + tag : str + The tag of the face. + element : :class:`compas_fea2.model.Element` + The element to which the face belongs. + + Attributes + ---------- + nodes : list[:class:`compas_fea2.model.Node`] + Nodes to which the element is connected. + tag : str + The tag of the face. + element : :class:`compas_fea2.model.Element` + The element to which the face belongs. + plane : :class:`compas.geometry.Plane` + The plane of the face. + polygon : :class:`compas.geometry.Polygon` + The polygon of the face. + area : float + The area of the face. + results : dict + Dictionary with results of the face. + """ - def __init__(self, *, nodes, tag, element=None, name=None): - super(Face, self).__init__(name) + + def __init__(self, nodes, tag, element=None, **kwargs): + super(Face, self).__init__(**kwargs) self._nodes = nodes self._tag = tag self._plane = Plane.from_three_points(*[node.xyz for node in nodes[:3]]) # TODO check when more than 3 nodes @@ -267,26 +278,30 @@ def polygon(self): @property def area(self): - """The area property.""" return self.polygon.area -class _Element2D(_Element): + +class Element2D(Element): """Element with 2 dimensions. - """ - __doc__ += _Element.__doc__ - __doc__ +=""" - Additional Parameters - --------------------- + + Parameters + ---------- faces : [:class:`compas_fea2.model.elements.Face] The faces of the element. faces : dict Dictionary providing for each face the node indices. For example: {'s1': (0,1,2), ...} + """ - def __init__(self, *, nodes, frame, section=None, implementation=None, rigid=False, name=None, **kwargs): - super(_Element2D, self).__init__(nodes=nodes, section=section, - frame=frame, implementation=implementation, name=name, **kwargs) + def __init__(self, nodes, frame, section=None, implementation=None, rigid=False, **kwargs): + super(Element2D, self).__init__( + nodes=nodes, + section=section, + frame=frame, + implementation=implementation, + **kwargs, + ) self._faces = None self._face_indices = None @@ -312,8 +327,7 @@ def faces(self): @property def volume(self): - return self._faces[0].area*self.section.t - + return self._faces[0].area * self.section.t def _construct_faces(self, face_indices): """Construct the face-nodes dictionary. @@ -329,10 +343,13 @@ def _construct_faces(self, face_indices): dict Dictionary with face names and the corresponding nodes. """ - return [Face(nodes=itemgetter(*indices)(self.nodes), tag=name, element=self) for name, indices in face_indices.items()] + return [ + Face(nodes=itemgetter(*indices)(self.nodes), tag=name, element=self) + for name, indices in face_indices.items() + ] -class ShellElement(_Element2D): +class ShellElement(Element2D): """A 2D element that resists axial, shear, bending and torsion. Shell elements are used to model structures in which one dimension, the @@ -340,22 +357,25 @@ class ShellElement(_Element2D): """ - def __init__(self, *, nodes, frame=None, section=None, implementation=None, rigid=False, name=None, **kwargs): - super(ShellElement, self).__init__(nodes=nodes, frame=frame, section=section, - implementation=implementation, rigid=rigid, name=name, **kwargs) - - self._face_indices = { - 'SPOS': tuple(range(len(nodes))), - 'SNEG': tuple(range(len(nodes)))[::-1] - } + def __init__(self, nodes, frame=None, section=None, implementation=None, rigid=False, **kwargs): + super(ShellElement, self).__init__( + nodes=nodes, + frame=frame, + section=section, + implementation=implementation, + rigid=rigid, + **kwargs, + ) + + self._face_indices = {"SPOS": tuple(range(len(nodes))), "SNEG": tuple(range(len(nodes)))[::-1]} self._faces = self._construct_faces(self._face_indices) -class MembraneElement(_Element2D): +class MembraneElement(Element2D): """A shell element that resists only axial loads. - Note - ---- + Notes + ----- Membrane elements are used to represent thin surfaces in space that offer strength in the plane of the element but have no bending stiffness; for example, the thin rubber sheet that forms a balloon. In addition, they are @@ -373,7 +393,7 @@ class MembraneElement(_Element2D): # TODO add picture with node lables convention -class _Element3D(_Element): +class Element3D(Element): """A 3D element that resists axial, shear, bending and torsion. Solid (continuum) elements can be used for linear analysis and for complex nonlinear analyses involving contact, plasticity, and large @@ -384,13 +404,17 @@ class _Element3D(_Element): """ - def __init__(self, *, nodes, section, implementation=None, name=None, **kwargs): - super(_Element3D, self).__init__(nodes=nodes, section=section, frame=None, - implementation=implementation, name=name, **kwargs) + def __init__(self, nodes, section, implementation=None, **kwargs): + super(Element3D, self).__init__( + nodes=nodes, + section=section, + frame=None, + implementation=implementation, + **kwargs, + ) self._face_indices = None self._faces = None - @property def nodes(self): return self._nodes @@ -421,44 +445,49 @@ def _construct_faces(self, face_indices): ------- dict Dictionary with face names and the corresponding nodes. + """ - return [Face(nodes=itemgetter(*indices)(self.nodes), tag=name, element=self) for name, indices in face_indices.items()] + return [ + Face(nodes=itemgetter(*indices)(self.nodes), tag=name, element=self) + for name, indices in face_indices.items() + ] @property def area(self): return self._area @classmethod - def from_polyhedron(cls, polyhedron, section, implementation=None, name=None, **kwargs): - # m = importlib.import_module('.'.join(cls.__module__.split('.')[:-1])) + def from_polyhedron(cls, polyhedron, section, implementation=None, **kwargs): from compas_fea2.model import Node - element = cls([Node(vertex) for vertex in polyhedron.vertices], section, implementation, name, **kwargs) + + element = cls([Node(vertex) for vertex in polyhedron.vertices], section, implementation, **kwargs) return element -class TetrahedronElement(_Element3D): +class TetrahedronElement(Element3D): """A Solid element with 4 faces. - Note - ---- + Notes + ----- The face labels are as follows: - - S1: (0, 1, 2) - - S2: (0, 1, 3) - - S3: (1, 2, 3) - - S4: (0, 2, 3) + + - S1: (0, 1, 2) + - S2: (0, 1, 3) + - S3: (1, 2, 3) + - S4: (0, 2, 3) where the number is the index of the the node in the nodes list + """ - def __init__(self, *, nodes, section, implementation=None, name=None, **kwargs): - super(TetrahedronElement, self).__init__(nodes=nodes, section=section, - implementation=implementation, name=name, **kwargs) - self._face_indices = { - 's1': (0, 1, 2), - 's2': (0, 1, 3), - 's3': (1, 2, 3), - 's4': (0, 2, 3) - } + def __init__(self, *, nodes, section, implementation=None, **kwargs): + super(TetrahedronElement, self).__init__( + nodes=nodes, + section=section, + implementation=implementation, + **kwargs, + ) + self._face_indices = {"s1": (0, 1, 2), "s2": (0, 1, 3), "s3": (1, 2, 3), "s4": (0, 2, 3)} self._faces = self._construct_faces(self._face_indices) @property @@ -471,46 +500,57 @@ def edges(self): seen.add((v, u)) yield u, v - #TODO use compas funcitons to compute differences and det + # TODO use compas funcitons to compute differences and det @property def volume(self): """The volume property.""" def determinant_3x3(m): - return (m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) - - m[1][0] * (m[0][1] * m[2][2] - m[0][2] * m[2][1]) + - m[2][0] * (m[0][1] * m[1][2] - m[0][2] * m[1][1])) - + return ( + m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) + - m[1][0] * (m[0][1] * m[2][2] - m[0][2] * m[2][1]) + + m[2][0] * (m[0][1] * m[1][2] - m[0][2] * m[1][1]) + ) def subtract(a, b): - return (a[0] - b[0], - a[1] - b[1], - a[2] - b[2]) + return (a[0] - b[0], a[1] - b[1], a[2] - b[2]) nodes_coord = [node.xyz for node in self.nodes] - a, b, c ,d = nodes_coord - return abs(determinant_3x3((subtract(a, b), - subtract(b, c), - subtract(c, d), - ))) / 6.0 - -class PentahedronElement(_Element3D): - """A Solid element with 5 faces (extruded triangle). - """ - - -class HexahedronElement(_Element3D): - """A Solid cuboid element with 6 faces (extruded rectangle). - """ - - def __init__(self, *, nodes, section, implementation=None, name=None, **kwargs): - super(HexahedronElement, self).__init__(nodes=nodes, section=section, - implementation=implementation, name=name, **kwargs) - self._faces_indices = {'s1': (0, 1, 2, 3), - 's2': (4, 5, 6, 7), - 's3': (0, 1, 4, 5), - 's4': (1, 2, 5, 6), - 's5': (2, 3, 6, 7), - 's6': (0, 3, 4, 7) - } + a, b, c, d = nodes_coord + return ( + abs( + determinant_3x3( + ( + subtract(a, b), + subtract(b, c), + subtract(c, d), + ) + ) + ) + / 6.0 + ) + + +class PentahedronElement(Element3D): + """A Solid element with 5 faces (extruded triangle).""" + + +class HexahedronElement(Element3D): + """A Solid cuboid element with 6 faces (extruded rectangle).""" + + def __init__(self, nodes, section, implementation=None, **kwargs): + super(HexahedronElement, self).__init__( + nodes=nodes, + section=section, + implementation=implementation, + **kwargs, + ) + self._faces_indices = { + "s1": (0, 1, 2, 3), + "s2": (4, 5, 6, 7), + "s3": (0, 1, 4, 5), + "s4": (1, 2, 5, 6), + "s5": (2, 3, 6, 7), + "s6": (0, 3, 4, 7), + } self._faces = self._construct_faces(self._face_indices) diff --git a/src/compas_fea2/model/groups.py b/src/compas_fea2/model/groups.py index 51f1f2a3f..e115c2af8 100644 --- a/src/compas_fea2/model/groups.py +++ b/src/compas_fea2/model/groups.py @@ -8,7 +8,7 @@ # TODO change lists to sets -class _Group(FEAData): +class Group(FEAData): """Base class for all groups. Parameters @@ -16,20 +16,16 @@ class _Group(FEAData): members : set, optional Set with the members belonging to the group. These can be either node, elements, faces or parts. By default ``None``. - name : str, optional - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. Attributes ---------- registration : :class:`compas_fea2.model.DeformablePart` | :class:`compas_fea2.model.Model` The parent object where the members of the Group belong. - name : str - Uniqe identifier. + """ - def __init__(self, members=None, name=None, **kwargs): - super(_Group, self).__init__(name=name, **kwargs) + def __init__(self, members=None, **kwargs): + super(Group, self).__init__(**kwargs) self._members = set() if not members else self._check_members(members) def __str__(self): @@ -38,39 +34,42 @@ def __str__(self): {} name : {} # of members : {} -""".format(self.__class__.__name__, - len(self.__class__.__name__) * '-', - self.name, - len(self._members)) +""".format( + self.__class__.__name__, len(self.__class__.__name__) * "-", self.name, len(self._members) + ) def _check_member(self, member): if not isinstance(self, FacesGroup): if member._registration != self._registration: - raise ValueError('{} is registered to a different object'.format(member)) + raise ValueError("{} is registered to a different object".format(member)) return member def _check_members(self, members): if not members or not isinstance(members, Iterable): - raise ValueError('{} must be a not empty iterable'.format(members)) + raise ValueError("{} must be a not empty iterable".format(members)) # FIXME combine in more pythonic way if isinstance(self, FacesGroup): if len(set([member.element._registration for member in members])) != 1: raise ValueError( - 'At least one of the members to add is registered to a different object or not registered') + "At least one of the members to add is registered to a different object or not registered" + ) if self._registration: if list(members).pop().element._registration != self._registration: raise ValueError( - 'At least one of the members to add is registered to a different object than the group') + "At least one of the members to add is registered to a different object than the group" + ) else: self._registration = list(members).pop().element._registration else: if len(set([member._registration for member in members])) != 1: raise ValueError( - 'At least one of the members to add is registered to a different object or not registered') + "At least one of the members to add is registered to a different object or not registered" + ) if self._registration: if list(members).pop()._registration != self._registration: raise ValueError( - 'At least one of the members to add is registered to a different object than the group') + "At least one of the members to add is registered to a different object than the group" + ) else: self._registration = list(members).pop()._registration return members @@ -110,36 +109,32 @@ def _add_members(self, members): return members -class NodesGroup(_Group): +class NodesGroup(Group): """Base class nodes groups. - Note - ---- - NodesGroups are registered to the same :class:`compas_fea2.model._Part` as its nodes - and can belong to only one Part. - 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. nodes : list[:class:`compas_fea2.model.Node`] The nodes belonging to the group. Attributes ---------- - name : str - Uniqe identifier. nodes : list[:class:`compas_fea2.model.Node`] The nodes belonging to the group. part : :class:`compas_fea2.model._Part` The part where the group is registered, by default `None`. model : :class:`compas_fea2.model.Model` The model where the group is registered, by default `None`. + + Notes + ----- + NodesGroups are registered to the same :class:`compas_fea2.model._Part` as its nodes + and can belong to only one Part. + """ - def __init__(self, *, nodes, name=None, **kwargs): - super(NodesGroup, self).__init__(members=nodes, name=name, **kwargs) + def __init__(self, nodes, **kwargs): + super(NodesGroup, self).__init__(members=nodes, **kwargs) @property def part(self): @@ -184,37 +179,32 @@ def add_nodes(self, nodes): return self._add_members(nodes) -class ElementsGroup(_Group): +class ElementsGroup(Group): """Base class for elements groups. - Note - ---- - ElementsGroups are registered to the same :class:`compas_fea2.model.DeformablePart` as - its elements and can belong to only one DeformablePart. - 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. elements : list[:class:`compas_fea2.model.Element`] The elements belonging to the group. Attributes ---------- - name : str - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. elements : list[:class:`compas_fea2.model.Element`] The elements belonging to the group. part : :class:`compas_fea2.model._Part` The part where the group is registered, by default `None`. model : :class:`compas_fea2.model.Model` The model where the group is registered, by default `None`. + + Notes + ----- + ElementsGroups are registered to the same :class:`compas_fea2.model.DeformablePart` as + its elements and can belong to only one DeformablePart. + """ - def __init__(self, *, elements, name=None, **kwargs): - super(ElementsGroup, self).__init__(members=elements, name=name, **kwargs) + def __init__(self, elements, **kwargs): + super(ElementsGroup, self).__init__(members=elements, **kwargs) @property def part(self): @@ -233,13 +223,14 @@ def add_element(self, element): Parameters ---------- - element : :class:`compas_fea2.model._Element` + element : :class:`compas_fea2.model.Element` The element to add. Returns ------- - :class:`compas_fea2.model._Element` + :class:`compas_fea2.model.Element` The element added. + """ return self._add_member(element) @@ -248,25 +239,21 @@ def add_elements(self, elements): Parameters ---------- - elements : [:class:`compas_fea2.model._Element`] + elements : [:class:`compas_fea2.model.Element`] The elements to add. Returns ------- - [:class:`compas_fea2.model._Element`] + [:class:`compas_fea2.model.Element`] The elements added. + """ return self._add_members(elements) -class FacesGroup(_Group): +class FacesGroup(Group): """Base class elements faces groups. - Note - ---- - FacesGroups are registered to the same :class:`compas_fea2.model.DeformablePart` as the - elements of its faces. - Parameters ---------- name : str, optional @@ -288,6 +275,12 @@ class FacesGroup(_Group): The part where the group is registered, by default `None`. model : :class:`compas_fea2.model.Model` The model where the group is registered, by default `None`. + + Notes + ----- + FacesGroups are registered to the same :class:`compas_fea2.model.DeformablePart` as the + elements of its faces. + """ def __init__(self, *, faces, name=None, **kwargs): @@ -325,6 +318,7 @@ def add_face(self, face): ------- :class:`compas_fea2.model.Face` The element added. + """ return self._add_member(face) @@ -340,16 +334,13 @@ def add_faces(self, faces): ------- [:class:`compas_fea2.model.Face`] The faces added. + """ return self._add_members(faces) -class PartsGroup(_Group): - """Base class for parts groups. - Note - ---- - PartsGroups are registered to the same :class:`compas_fea2.model.Model` as its - parts and can belong to only one Model. +class PartsGroup(Group): + """Base class for parts groups. Parameters ---------- @@ -365,10 +356,16 @@ class PartsGroup(_Group): The parts belonging to the group. model : :class:`compas_fea2.model.Model` The model where the group is registered, by default `None`. + + Notes + ----- + PartsGroups are registered to the same :class:`compas_fea2.model.Model` as its + parts and can belong to only one Model. + """ def __init__(self, *, parts, name=None, **kwargs): - super(PartsGroup, self).__init__(members=parts, name=name, **kwargs) + super(PartsGroup, self).__init__(members=parts, name=name, **kwargs) @property def model(self): diff --git a/src/compas_fea2/model/ics.py b/src/compas_fea2/model/ics.py index 1754b5edb..03202e8d9 100644 --- a/src/compas_fea2/model/ics.py +++ b/src/compas_fea2/model/ics.py @@ -5,56 +5,43 @@ from compas_fea2.base import FEAData -class _InitialCondition(FEAData): +class InitialCondition(FEAData): """Base class for all predefined initial conditions. - Note - ---- + Notes + ----- InitialConditions are registered to a :class:`compas_fea2.model.Model`. The same InitialCondition can be assigned to Nodes or Elements in multiple 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. - - Attributes - ---------- - name : str - Uniqe identifier. """ - def __init__(self, name=None, **kwargs): - super(_InitialCondition, self).__init__(name=name, **kwargs) + def __init__(self, **kwargs): + super(InitialCondition, self).__init__(**kwargs) -#FIXME this is not really a field in the sense that it is only applied to 1 node/element -class InitialTemperatureField(_InitialCondition): - """Temperature field. - Note - ---- - InitialConditions are registered to a :class:`compas_fea2.model.Model`. The - same InitialCondition can be assigned to Nodes or Elements in multiple Parts +# FIXME this is not really a field in the sense that it is only applied to 1 node/element +class InitialTemperatureField(InitialCondition): + """Temperature field. Parameters ---------- temperature : float The temperature value. - name : str, optional - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. Attributes ---------- temperature : float The temperature value. - name : str - Uniqe identifier. + + Notes + ----- + InitialConditions are registered to a :class:`compas_fea2.model.Model`. The + same InitialCondition can be assigned to Nodes or Elements in multiple Parts + """ - def __init__(self, temperature, name=None, **kwargs): - super(InitialTemperatureField, self).__init__(name, **kwargs) + def __init__(self, temperature, **kwargs): + super(InitialTemperatureField, self).__init__(**kwargs) self._t = temperature @property @@ -66,32 +53,28 @@ def temperature(self, value): self._t = value -class InitialStressField(_InitialCondition): +class InitialStressField(InitialCondition): """Stress field. - Note - ---- - InitialConditions are registered to a :class:`compas_fea2.model.Model`. The - same InitialCondition can be assigned to Nodes or Elements in multiple Parts - Parameters ---------- stress : touple(float, float, float) The stress values. - name : str, optional - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. Attributes ---------- stress : touple(float, float, float) The stress values. - name : str - Uniqe identifier. + + Notes + ----- + InitialConditions are registered to a :class:`compas_fea2.model.Model` + The same InitialCondition can be assigned to Nodes or Elements in multiple Parts. + """ - def __init__(self, stress, name=None, **kwargs): - super(InitialStressField, self).__init__(name, **kwargs) + def __init__(self, stress, **kwargs): + super(InitialStressField, self).__init__(**kwargs) self._s = stress @property diff --git a/src/compas_fea2/model/materials/__init__.py b/src/compas_fea2/model/materials/__init__.py index f4a43d8db..16849ed8c 100644 --- a/src/compas_fea2/model/materials/__init__.py +++ b/src/compas_fea2/model/materials/__init__.py @@ -11,7 +11,7 @@ .. autosummry:: :toctree: generated/ - _Material + Material ElasticIsotropic ElasticOrthotropic ElasticPlastic @@ -56,7 +56,7 @@ from .concrete import ConcreteSmearedCrack # noqa : F401 # Basic Materials -from .material import _Material # noqa : F401 +from .material import Material # noqa : F401 from .material import ElasticIsotropic # noqa : F401 from .material import ElasticOrthotropic # noqa : F401 from .material import ElasticPlastic # noqa : F401 diff --git a/src/compas_fea2/model/materials/concrete.py b/src/compas_fea2/model/materials/concrete.py index cd07c24b6..da001b4ad 100644 --- a/src/compas_fea2/model/materials/concrete.py +++ b/src/compas_fea2/model/materials/concrete.py @@ -3,22 +3,11 @@ from __future__ import print_function from math import log -from .material import _Material -from ...utilities._utils import extend_docstring +from .material import Material -@extend_docstring(_Material) -class Concrete(_Material): - """ - Concrete - ======== - Elastic and plastic-cracking Eurocode based concrete material - - Note - ---- - The concrete model is based on Eurocode 2 up to fck=90 MPa. - - Additional Parameters and attributes +class Concrete(Material): + """Elastic and plastic-cracking Eurocode based concrete material Parameters ---------- @@ -45,22 +34,27 @@ class Concrete(_Material): Parameters for modelling the tension side of the stess--strain curve compression : dict Parameters for modelling the tension side of the stess--strain curve + + Notes + ----- + The concrete model is based on Eurocode 2 up to fck=90 MPa. + """ - def __init__(self, *, fck, v=0.2, density=2400, fr=None, name=None, **kwargs): - super(Concrete, self).__init__(density=density, name=name, **kwargs) + def __init__(self, *, fck, v=0.2, density=2400, fr=None, name=None, **kwargs): + super(Concrete, self).__init__(density=density, name=name, **kwargs) de = 0.0001 fcm = fck + 8 - Ecm = 22 * 10**3 * (fcm / 10)**0.3 + Ecm = 22 * 10**3 * (fcm / 10) ** 0.3 ec1 = min(0.7 * fcm**0.31, 2.8) * 0.001 - ecu1 = 0.0035 if fck < 50 else (2.8 + 27 * ((98 - fcm) / 100.)**4) * 0.001 + ecu1 = 0.0035 if fck < 50 else (2.8 + 27 * ((98 - fcm) / 100.0) ** 4) * 0.001 k = 1.05 * Ecm * ec1 / fcm e = [i * de for i in range(int(ecu1 / de) + 1)] ec = [ei - e[1] for ei in e[1:]] - fctm = 0.3 * fck**(2 / 3) if fck <= 50 else 2.12 * log(1 + fcm / 10) - f = [10**6 * fcm * (k * (ei / ec1) - (ei / ec1)**2) / (1 + (k - 2) * (ei / ec1)) for ei in e] + fctm = 0.3 * fck ** (2 / 3) if fck <= 50 else 2.12 * log(1 + fcm / 10) + f = [10**6 * fcm * (k * (ei / ec1) - (ei / ec1) ** 2) / (1 + (k - 2) * (ei / ec1)) for ei in e] E = f[1] / e[1] ft = [1.0, 0.0] @@ -76,8 +70,8 @@ def __init__(self, *, fck, v=0.2, density=2400, fr=None, name=None, **kwargs): self.et = et self.fr = fr # TODO these necessary if we have the above? - self.tension = {'f': ft, 'e': et} - self.compression = {'f': f[1:], 'e': ec} + self.tension = {"f": ft, "e": et} + self.compression = {"f": f[1:], "e": ec} @property def G(self): @@ -95,18 +89,16 @@ def __str__(self): G : {} fck : {} fr : {} -""".format(self.name, self.density, self.E, self.v, self.G, self.fck, self.fr) +""".format( + self.name, self.density, self.E, self.v, self.G, self.fck, self.fr + ) -@extend_docstring(_Material) -class ConcreteSmearedCrack(_Material): - """ - ConcreteSmearedCrack - ==================== - Elastic and plastic, cracking concrete material. +class ConcreteSmearedCrack(Material): + """Elastic and plastic, cracking concrete material. - Additional Parameters and Attributes - ------------------------------------ + Parameters + ---------- E : float Young's modulus E. v : float @@ -122,8 +114,8 @@ class ConcreteSmearedCrack(_Material): fr : list Failure ratios. - Additional Attributes - --------------------- + Attributes + ---------- E : float Young's modulus E. v : float @@ -144,10 +136,11 @@ class ConcreteSmearedCrack(_Material): Parameters for modelling the tension side of the stess--strain curve compression : dict Parameters for modelling the tension side of the stess--strain curve + """ - def __init__(self, *, E, v, density, fc, ec, ft, et, fr=[1.16, 0.0836], name=None, **kwargs): - super(ConcreteSmearedCrack, self).__init__(density=density, name=name, **kwargs) + def __init__(self, *, E, v, density, fc, ec, ft, et, fr=[1.16, 0.0836], **kwargs): + super(ConcreteSmearedCrack, self).__init__(density=density, **kwargs) self.E = E self.v = v @@ -157,8 +150,8 @@ def __init__(self, *, E, v, density, fc, ec, ft, et, fr=[1.16, 0.0836], name=Non self.et = et self.fr = fr # are these necessary if we have the above? - self.tension = {'f': ft, 'e': et} - self.compression = {'f': fc, 'e': ec} + self.tension = {"f": ft, "e": et} + self.compression = {"f": fc, "e": ec} @property def G(self): @@ -179,16 +172,16 @@ def __str__(self): ft : {} et : {} fr : {} -""".format(self.name, self.density, self.E, self.v, self.G, self.fc, self.ec, self.ft, self.et, self.fr) +""".format( + self.name, self.density, self.E, self.v, self.G, self.fc, self.ec, self.ft, self.et, self.fr + ) -class ConcreteDamagedPlasticity(_Material): +class ConcreteDamagedPlasticity(Material): """Damaged plasticity isotropic and homogeneous material. - """ - __doc__ += _Material.__doc__ - __doc__ += """ - Additional Parameters - --------------------- + + Parameters + ---------- E : float Young's modulus E. v : float @@ -200,8 +193,8 @@ class ConcreteDamagedPlasticity(_Material): stiffening : list Tension stiffening parameters. - Additional Attributes - --------------------- + Attributes + ---------- E : float Young's modulus E. v : float @@ -217,8 +210,8 @@ class ConcreteDamagedPlasticity(_Material): """ - def __init__(self, *, E, v, density, damage, hardening, stiffening, name=None, **kwargs): - super(ConcreteDamagedPlasticity, self).__init__(density=density, name=name, **kwargs) + def __init__(self, *, E, v, density, damage, hardening, stiffening, **kwargs): + super(ConcreteDamagedPlasticity, self).__init__(density=density, **kwargs) self.E = E self.v = v diff --git a/src/compas_fea2/model/materials/material.py b/src/compas_fea2/model/materials/material.py index 9ac78b513..74d76aeb2 100644 --- a/src/compas_fea2/model/materials/material.py +++ b/src/compas_fea2/model/materials/material.py @@ -3,19 +3,10 @@ from __future__ import print_function from compas_fea2.base import FEAData -from compas_fea2.utilities._utils import extend_docstring -class _Material(FEAData): - """ - Note - ---- - Materials are registered to a :class:`compas_fea2.model.Model`. The same - material can be assigned to multiple sections and in different elements and - parts. - - Basic Material parameters and attributes - ======================================== +class Material(FEAData): + """Base class for representing materials. Parameters ---------- @@ -23,20 +14,9 @@ class _Material(FEAData): Density of the material. expansion : float, optional Thermal expansion coefficient, by default None. - name : str, optional - Uniqe identifier. If not provided it is automatically generated. Set a - name if you want a more human-readable input file. - - Other Parameters - ---------------- - **kwargs : dict - Backend dependent keyword arguments. - See the individual backends for more information. Attributes ---------- - name : str, optional - Uniqe identifier. density : float Density of the material. expansion : float @@ -47,10 +27,16 @@ class _Material(FEAData): model : :class:`compas_fea2.model.Model` The Model where the material is assigned. + Notes + ----- + Materials are registered to a :class:`compas_fea2.model.Model`. The same + material can be assigned to multiple sections and in different elements and + parts. + """ - def __init__(self, *, density, expansion=None, name=None, **kwargs): - super(_Material, self).__init__(name=name, **kwargs) + def __init__(self, *, density, expansion=None, **kwargs): + super(Material, self).__init__(**kwargs) self.density = density self.expansion = expansion self._key = None @@ -70,7 +56,9 @@ def __str__(self): name : {} density : {} expansion : {} -""".format(self.__class__.__name__, len(self.__class__.__name__) * '-', self.name, self.density, self.expansion) +""".format( + self.__class__.__name__, len(self.__class__.__name__) * "-", self.name, self.density, self.expansion + ) def __html__(self): return """ @@ -78,17 +66,14 @@ def __html__(self):

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