From 3f5b5623c489f0cbd0ca252abcfbc6c27a8ba7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= Date: Fri, 22 Nov 2024 12:15:16 +0100 Subject: [PATCH 01/13] initial draft of the example --- .../021-imported-solid-model.py | 218 ++++++++++++++++++ src/ansys/acp/core/extras/example_helpers.py | 12 +- 2 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 examples/modeling_features/021-imported-solid-model.py diff --git a/examples/modeling_features/021-imported-solid-model.py b/examples/modeling_features/021-imported-solid-model.py new file mode 100644 index 0000000000..c203e63b96 --- /dev/null +++ b/examples/modeling_features/021-imported-solid-model.py @@ -0,0 +1,218 @@ +# Copyright (C) 2022 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _imported_solid_model_example: + +Imported Solid model +==================== + +This example guides you through the definition of an imported solid model. +It only shows the PyACP part of the setup. For a complete composite analysis, +see :ref:`pymapdl_workflow_example`. + +The example starts from an ACP model with layup. It shows how to: + +- Create an :class:`.ImportedSolidModel` from an external mesh. +- Map the layup onto different regions of the solid mesh. +- Configure the mapping options. + +In contrast to the :class:`.SolidModel`, the solid mesh of :class:`.ImportedSolidModel` +is loaded from an external source, such as a CDB file. The integrated mapping feature +of the :class:`.ImportedSolidModel` allows you to map the layup onto the solid mesh. +There are different options to control the mapping (e.g. scoping, element technology). + +It is recommended to look at the Ansys help for all the details. This example shows the +basic setup only. +""" +import os + +# %% +# Import the standard library and third-party dependencies. +import pathlib +import tempfile + +# %% +# Import the PyACP dependencies. +from ansys.acp.core import ACPWorkflow, FabricWithAngle, Lamina, PlyType, SymmetryType, launch_acp, ElementTechnology, LayupMappingRosetteSelectionMethod +from ansys.acp.core.extras import ExampleKeys, get_example_file +from ansys.acp.core.material_property_sets import ( + ConstantEngineeringConstants, + ConstantStrainLimits, + ConstantStressLimits, +) + +# sphinx_gallery_thumbnail_number = 2 + + +# %% +# Start ACP and load the model +# ---------------------------- +# %% +# Get the example file from the server. +tempdir = tempfile.TemporaryDirectory() +WORKING_DIR = pathlib.Path(tempdir.name) +input_file = get_example_file(ExampleKeys.IMPORTED_SOLID_MODEL_ACPH5, WORKING_DIR) + +# %% +# Launch the PyACP server and connect to it. +acp = launch_acp() + +# %% +# Define the input file and instantiate an ``ACPWorkflow`` instance. +workflow = ACPWorkflow.from_acph5_file( + acp=acp, + acph5_file_path=input_file, + local_working_directory=WORKING_DIR, +) + +model = workflow.model + +# %% +# Import external solid model +# --------------------------- +# +# Get the solid mesh file and create an ImportedSolidModel and +# load the initial mesh. + +solid_mesh_file = get_example_file(ExampleKeys.IMPORTED_SOLID_MODEL_SOLID_MESH, WORKING_DIR) +imported_solid_model = model.create_imported_solid_model( + name="Imported Solid Model", + external_path=solid_mesh_file, + format="ansys:h5", +) + +# Load the initial mesh and show the raw mesh without any mapping. +imported_solid_model.refresh() +imported_solid_model.import_initial_mesh() +print(imported_solid_model.solid_element_sets) +model.solid_mesh.to_pyvista().plot(show_edges=True) + +# %% +# Add mapping objects +# ------------------- +# +# Link the layup (plies) of the top skin of the sandwich +# with the corresponding named selections of the solid mesh +# and show the updated solid model. +solid_esets = imported_solid_model.solid_element_sets + +imported_solid_model.create_layup_mapping_object( + name="sandwich skin top", + element_technology=ElementTechnology.LAYERED_ELEMENT, + shell_element_sets=[model.element_sets["els_sandwich_skin_top"]], + entire_solid_mesh=False, + solid_element_sets=[solid_esets["mapping_target sandwich skin top"]], +) + +model.update() +model.solid_mesh.to_pyvista().plot(show_edges=True) + +# %% +# Add other mapping objects +imported_solid_model.create_layup_mapping_object( + name="sandwich skin bottom", + element_technology=ElementTechnology.LAYERED_ELEMENT, + shell_element_sets=[model.element_sets["els_sandwich_skin_bottom"]], + entire_solid_mesh=False, + solid_element_sets=[imported_solid_model.solid_element_sets["mapping_target sandwich skin bottom"]], +) + +imported_solid_model.create_layup_mapping_object( + name="stringer", + element_technology=ElementTechnology.LAYERED_ELEMENT, + shell_element_sets=[model.element_sets["els_stringer_skin_left"]], + entire_solid_mesh=False, + solid_element_sets=[solid_esets[v] for v in ['mapping_target stringer honeycomb', 'mapping_target stringer skin left', 'mapping_target stringer skin right']], +) + +imported_solid_model.create_layup_mapping_object( + name="bonding skin", + element_technology=ElementTechnology.LAYERED_ELEMENT, + shell_element_sets=[model.element_sets[v] for v in ['els_bonding_skin_left', 'els_bonding_skin_right']], + entire_solid_mesh=False, + solid_element_sets=[solid_esets[v] for v in ['mapping_target bonding skin left', 'mapping_target bonding skin right']], +) + +# %% +# Show intermediate result +model.update() +model.solid_mesh.to_pyvista().plot(show_edges=True) + +# %% +# The mapping can also be done for specific plies +# as shown for the core materials. +imported_solid_model.create_layup_mapping_object( + name="foam", + element_technology=ElementTechnology.LAYERED_ELEMENT, + shell_element_sets=[model.element_sets[v] for v in ['els_foam_core_left', 'els_foam_core_right']], + select_all_plies=False, + sequences=[model.modeling_groups["MG foam_core"]], + entire_solid_mesh=False, + solid_element_sets=[solid_esets["mapping_target foam core"]], + delete_lost_elements=False, + filler_material=model.materials["SAN Foam (81 kg m^-3)"], + rosettes=[model.rosettes["Global Coordinate System"]], + rosette_selection_method=LayupMappingRosetteSelectionMethod.MINIMUM_DISTANCE, +) + +imported_solid_model.create_layup_mapping_object( + name="honeycomb", + element_technology=ElementTechnology.LAYERED_ELEMENT, + shell_element_sets=[model.element_sets[v] for v in ['els_honeycomb_left', 'els_honeycomb_right']], + select_all_plies=False, + sequences=[model.modeling_groups["MG honeycomb_core"]], + entire_solid_mesh=False, + solid_element_sets=[solid_esets["mapping_target sandwich honeycomb"]], + delete_lost_elements=False, + filler_material=model.materials["Honeycomb"], + rosettes=[model.rosettes["Global Coordinate System"]], + rosette_selection_method=LayupMappingRosetteSelectionMethod.MINIMUM_DISTANCE, +) +model.update() +model.solid_mesh.to_pyvista().plot(show_edges=True) + +# %% +# Add filler mapping objects where the solid mesh is "filled" +# with a single material. No plies from the layup are used here. + +imported_solid_model.create_layup_mapping_object( + name="resin", + element_technology=ElementTechnology.LAYERED_ELEMENT, + shell_element_sets=[], + entire_solid_mesh=False, + solid_element_sets=[solid_esets[v] for v in ['mapping_target adhesive', 'mapping_target adhesive stringer root']], + delete_lost_elements=False, + filler_material=model.materials["Resin Epoxy"], + rosettes=[model.rosettes["Global Coordinate System"]], + rosette_selection_method=LayupMappingRosetteSelectionMethod.MINIMUM_DISTANCE, +) + +# %% +# Show final solid mesh with mapped layup +model.update() +model.solid_mesh.to_pyvista().plot(show_edges=True) + +# %% +# The solid mesh can be exported as CDB for MAPDL or to PyMechanical for further analysis. +# These workflows are shown in :ref:`pymapdl_workflow_example` and +# :ref:`pymechanical_solid_example`. diff --git a/src/ansys/acp/core/extras/example_helpers.py b/src/ansys/acp/core/extras/example_helpers.py index eed624ae3f..c375dde995 100644 --- a/src/ansys/acp/core/extras/example_helpers.py +++ b/src/ansys/acp/core/extras/example_helpers.py @@ -40,10 +40,10 @@ if TYPE_CHECKING: # pragma: no cover from ansys.acp.core import ACPWorkflow -_EXAMPLE_REPO = "https://github.com/ansys/example-data/raw/master/pyacp/" +# _EXAMPLE_REPO = "https://github.com/ansys/example-data/raw/master/pyacp/" -# _EXAMPLE_REPO = "D:\\ANSYSDev\\pyansys-example-data\\pyacp\\" +_EXAMPLE_REPO = "D:\\ANSYSDev\\pyansys-example-data\\pyacp\\" @dataclasses.dataclass @@ -66,6 +66,8 @@ class ExampleKeys(Enum): OPTIMIZATION_EXAMPLE_DAT = auto() CLASS40_AGDB = auto() MATERIALS_XML = auto() + IMPORTED_SOLID_MODEL_ACPH5 = auto() + IMPORTED_SOLID_MODEL_SOLID_MESH = auto() EXAMPLE_FILES: dict[ExampleKeys, _ExampleLocation] = { @@ -98,6 +100,12 @@ class ExampleKeys(Enum): ), ExampleKeys.CLASS40_AGDB: _ExampleLocation(directory="class40", filename="class40.agdb"), ExampleKeys.MATERIALS_XML: _ExampleLocation(directory="materials", filename="materials.engd"), + ExampleKeys.IMPORTED_SOLID_MODEL_ACPH5: _ExampleLocation( + directory="imported_solid_model", filename="t-joint-ACP-Pre.acph5" + ), + ExampleKeys.IMPORTED_SOLID_MODEL_SOLID_MESH: _ExampleLocation( + directory="imported_solid_model", filename="t-joint.solid.h5" + ), } From 8632647bbf41cc3d1f6cee40d2116429f59c64e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= Date: Fri, 22 Nov 2024 13:21:54 +0100 Subject: [PATCH 02/13] cosmitic changes --- .../021-imported-solid-model.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/examples/modeling_features/021-imported-solid-model.py b/examples/modeling_features/021-imported-solid-model.py index c203e63b96..a84899e301 100644 --- a/examples/modeling_features/021-imported-solid-model.py +++ b/examples/modeling_features/021-imported-solid-model.py @@ -26,8 +26,11 @@ Imported Solid model ==================== -This example guides you through the definition of an imported solid model. -It only shows the PyACP part of the setup. For a complete composite analysis, +This example guides you through the definition of an imported solid model +which allows to map the layup onto an external solid mesh. In this case, +the layup is mapped onto a t-joint which consists of different parts such as shell, +stringer, and bonding skin. +The example only shows the PyACP part of the setup. For a complete composite analysis, see :ref:`pymapdl_workflow_example`. The example starts from an ACP model with layup. It shows how to: @@ -53,15 +56,10 @@ # %% # Import the PyACP dependencies. -from ansys.acp.core import ACPWorkflow, FabricWithAngle, Lamina, PlyType, SymmetryType, launch_acp, ElementTechnology, LayupMappingRosetteSelectionMethod +from ansys.acp.core import ACPWorkflow, launch_acp, ElementTechnology, LayupMappingRosetteSelectionMethod from ansys.acp.core.extras import ExampleKeys, get_example_file -from ansys.acp.core.material_property_sets import ( - ConstantEngineeringConstants, - ConstantStrainLimits, - ConstantStressLimits, -) -# sphinx_gallery_thumbnail_number = 2 +# sphinx_gallery_thumbnail_number = 3 # %% @@ -104,9 +102,12 @@ # Load the initial mesh and show the raw mesh without any mapping. imported_solid_model.refresh() imported_solid_model.import_initial_mesh() -print(imported_solid_model.solid_element_sets) model.solid_mesh.to_pyvista().plot(show_edges=True) +# %% +# The solid element sets can be used as target for the mapping. +print(imported_solid_model.solid_element_sets) + # %% # Add mapping objects # ------------------- @@ -116,7 +117,7 @@ # and show the updated solid model. solid_esets = imported_solid_model.solid_element_sets -imported_solid_model.create_layup_mapping_object( +_ = imported_solid_model.create_layup_mapping_object( name="sandwich skin top", element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[model.element_sets["els_sandwich_skin_top"]], @@ -129,7 +130,7 @@ # %% # Add other mapping objects -imported_solid_model.create_layup_mapping_object( +_ = imported_solid_model.create_layup_mapping_object( name="sandwich skin bottom", element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[model.element_sets["els_sandwich_skin_bottom"]], @@ -137,7 +138,7 @@ solid_element_sets=[imported_solid_model.solid_element_sets["mapping_target sandwich skin bottom"]], ) -imported_solid_model.create_layup_mapping_object( +_ = imported_solid_model.create_layup_mapping_object( name="stringer", element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[model.element_sets["els_stringer_skin_left"]], @@ -145,7 +146,7 @@ solid_element_sets=[solid_esets[v] for v in ['mapping_target stringer honeycomb', 'mapping_target stringer skin left', 'mapping_target stringer skin right']], ) -imported_solid_model.create_layup_mapping_object( +_ = imported_solid_model.create_layup_mapping_object( name="bonding skin", element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[model.element_sets[v] for v in ['els_bonding_skin_left', 'els_bonding_skin_right']], @@ -161,7 +162,7 @@ # %% # The mapping can also be done for specific plies # as shown for the core materials. -imported_solid_model.create_layup_mapping_object( +_ = imported_solid_model.create_layup_mapping_object( name="foam", element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[model.element_sets[v] for v in ['els_foam_core_left', 'els_foam_core_right']], @@ -175,7 +176,7 @@ rosette_selection_method=LayupMappingRosetteSelectionMethod.MINIMUM_DISTANCE, ) -imported_solid_model.create_layup_mapping_object( +_ = imported_solid_model.create_layup_mapping_object( name="honeycomb", element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[model.element_sets[v] for v in ['els_honeycomb_left', 'els_honeycomb_right']], @@ -195,7 +196,7 @@ # Add filler mapping objects where the solid mesh is "filled" # with a single material. No plies from the layup are used here. -imported_solid_model.create_layup_mapping_object( +_ = imported_solid_model.create_layup_mapping_object( name="resin", element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[], From a8f333c541b242beb9488137c19f8341fa3ed070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= Date: Fri, 22 Nov 2024 16:07:36 +0100 Subject: [PATCH 03/13] run pre-commit hooks --- .../021-imported-solid-model.py | 46 +++++++++++++++---- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/examples/modeling_features/021-imported-solid-model.py b/examples/modeling_features/021-imported-solid-model.py index a84899e301..85280a5080 100644 --- a/examples/modeling_features/021-imported-solid-model.py +++ b/examples/modeling_features/021-imported-solid-model.py @@ -28,7 +28,7 @@ This example guides you through the definition of an imported solid model which allows to map the layup onto an external solid mesh. In this case, -the layup is mapped onto a t-joint which consists of different parts such as shell, +the layup is applied onto a t-joint which consists of different parts such as shell, stringer, and bonding skin. The example only shows the PyACP part of the setup. For a complete composite analysis, see :ref:`pymapdl_workflow_example`. @@ -43,11 +43,12 @@ is loaded from an external source, such as a CDB file. The integrated mapping feature of the :class:`.ImportedSolidModel` allows you to map the layup onto the solid mesh. There are different options to control the mapping (e.g. scoping, element technology). +Advanced features such as :class:`.CutOffGeometry` can be used as well. See +:ref:`solid_model_example` for more details. It is recommended to look at the Ansys help for all the details. This example shows the basic setup only. """ -import os # %% # Import the standard library and third-party dependencies. @@ -56,7 +57,12 @@ # %% # Import the PyACP dependencies. -from ansys.acp.core import ACPWorkflow, launch_acp, ElementTechnology, LayupMappingRosetteSelectionMethod +from ansys.acp.core import ( + ACPWorkflow, + ElementTechnology, + LayupMappingRosetteSelectionMethod, + launch_acp, +) from ansys.acp.core.extras import ExampleKeys, get_example_file # sphinx_gallery_thumbnail_number = 3 @@ -135,7 +141,9 @@ element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[model.element_sets["els_sandwich_skin_bottom"]], entire_solid_mesh=False, - solid_element_sets=[imported_solid_model.solid_element_sets["mapping_target sandwich skin bottom"]], + solid_element_sets=[ + imported_solid_model.solid_element_sets["mapping_target sandwich skin bottom"] + ], ) _ = imported_solid_model.create_layup_mapping_object( @@ -143,15 +151,27 @@ element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[model.element_sets["els_stringer_skin_left"]], entire_solid_mesh=False, - solid_element_sets=[solid_esets[v] for v in ['mapping_target stringer honeycomb', 'mapping_target stringer skin left', 'mapping_target stringer skin right']], + solid_element_sets=[ + solid_esets[v] + for v in [ + "mapping_target stringer honeycomb", + "mapping_target stringer skin left", + "mapping_target stringer skin right", + ] + ], ) _ = imported_solid_model.create_layup_mapping_object( name="bonding skin", element_technology=ElementTechnology.LAYERED_ELEMENT, - shell_element_sets=[model.element_sets[v] for v in ['els_bonding_skin_left', 'els_bonding_skin_right']], + shell_element_sets=[ + model.element_sets[v] for v in ["els_bonding_skin_left", "els_bonding_skin_right"] + ], entire_solid_mesh=False, - solid_element_sets=[solid_esets[v] for v in ['mapping_target bonding skin left', 'mapping_target bonding skin right']], + solid_element_sets=[ + solid_esets[v] + for v in ["mapping_target bonding skin left", "mapping_target bonding skin right"] + ], ) # %% @@ -165,7 +185,9 @@ _ = imported_solid_model.create_layup_mapping_object( name="foam", element_technology=ElementTechnology.LAYERED_ELEMENT, - shell_element_sets=[model.element_sets[v] for v in ['els_foam_core_left', 'els_foam_core_right']], + shell_element_sets=[ + model.element_sets[v] for v in ["els_foam_core_left", "els_foam_core_right"] + ], select_all_plies=False, sequences=[model.modeling_groups["MG foam_core"]], entire_solid_mesh=False, @@ -179,7 +201,9 @@ _ = imported_solid_model.create_layup_mapping_object( name="honeycomb", element_technology=ElementTechnology.LAYERED_ELEMENT, - shell_element_sets=[model.element_sets[v] for v in ['els_honeycomb_left', 'els_honeycomb_right']], + shell_element_sets=[ + model.element_sets[v] for v in ["els_honeycomb_left", "els_honeycomb_right"] + ], select_all_plies=False, sequences=[model.modeling_groups["MG honeycomb_core"]], entire_solid_mesh=False, @@ -201,7 +225,9 @@ element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[], entire_solid_mesh=False, - solid_element_sets=[solid_esets[v] for v in ['mapping_target adhesive', 'mapping_target adhesive stringer root']], + solid_element_sets=[ + solid_esets[v] for v in ["mapping_target adhesive", "mapping_target adhesive stringer root"] + ], delete_lost_elements=False, filler_material=model.materials["Resin Epoxy"], rosettes=[model.rosettes["Global Coordinate System"]], From 873d0be5e629f7321728c6f7e5e9e8340cf38121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= Date: Mon, 25 Nov 2024 14:20:53 +0100 Subject: [PATCH 04/13] run pre-commit checks and fix a wrong resolved merge conflict --- .../021-imported-solid-model.py | 53 ++++++++++++++----- src/ansys/acp/core/extras/example_helpers.py | 1 + 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/examples/modeling_features/021-imported-solid-model.py b/examples/modeling_features/021-imported-solid-model.py index 85280a5080..bfc9133e0f 100644 --- a/examples/modeling_features/021-imported-solid-model.py +++ b/examples/modeling_features/021-imported-solid-model.py @@ -26,25 +26,21 @@ Imported Solid model ==================== -This example guides you through the definition of an imported solid model -which allows to map the layup onto an external solid mesh. In this case, -the layup is applied onto a t-joint which consists of different parts such as shell, -stringer, and bonding skin. +This example guides you through the definition of an :class:`.ImportedSolidModel` +which allows to map the layup onto an external solid mesh. +In contrast to the :class:`.SolidModel`, the raw solid mesh of +:class:`.ImportedSolidModel` is loaded from an external source, such as a CDB file. +In this example, the layup is applied onto a t-joint which consists of different +parts such as shell, stringer, and bonding skins. The example only shows the PyACP part of the setup. For a complete composite analysis, see :ref:`pymapdl_workflow_example`. -The example starts from an ACP model with layup. It shows how to: +This example starts from an ACP model with layup. It shows how to: - Create an :class:`.ImportedSolidModel` from an external mesh. -- Map the layup onto different regions of the solid mesh. -- Configure the mapping options. - -In contrast to the :class:`.SolidModel`, the solid mesh of :class:`.ImportedSolidModel` -is loaded from an external source, such as a CDB file. The integrated mapping feature -of the :class:`.ImportedSolidModel` allows you to map the layup onto the solid mesh. -There are different options to control the mapping (e.g. scoping, element technology). -Advanced features such as :class:`.CutOffGeometry` can be used as well. See -:ref:`solid_model_example` for more details. +- Define the :class:`.LayupMappingObject` to apply the layup onto the solid mesh. +- Scope plies to specific parts of the solid mesh. +- Visualize the mapped layup. It is recommended to look at the Ansys help for all the details. This example shows the basic setup only. @@ -55,6 +51,8 @@ import pathlib import tempfile +import pyvista + # %% # Import the PyACP dependencies. from ansys.acp.core import ( @@ -239,7 +237,34 @@ model.update() model.solid_mesh.to_pyvista().plot(show_edges=True) + +# %% +# Show extent and thickness of mapped plies +# ----------------------------------------- +# +# Use :meth:`.print_model` to get the list of plies. +ap = ( + model.modeling_groups["MG bonding_skin_right"] + .modeling_plies["ModelingPly.26"] + .production_plies["ProductionPly.33"] + .analysis_plies["P1L1__ModelingPly.26"] +) +thickness_data = ap.elemental_data.thickness +thickness_pyvista_mesh = thickness_data.get_pyvista_mesh(mesh=ap.solid_mesh) # type: ignore +plotter = pyvista.Plotter() +plotter.add_mesh(thickness_pyvista_mesh) +plotter.add_mesh(model.solid_mesh.to_pyvista(), opacity=0.2, show_edges=True) +plotter.show() + + # %% +# Other features +# -------------- +# +# The :class:`.CutOffGeometry` can be used in combination witt the :class:`.ImportedSolidModel` +# as well. See example :ref:`solid_model_example` for more details. +# More plotting capabilities are shown in the example :ref:`solid_model_example` as well. +# # The solid mesh can be exported as CDB for MAPDL or to PyMechanical for further analysis. # These workflows are shown in :ref:`pymapdl_workflow_example` and # :ref:`pymechanical_solid_example`. diff --git a/src/ansys/acp/core/extras/example_helpers.py b/src/ansys/acp/core/extras/example_helpers.py index 176efd01ba..46ccb77909 100644 --- a/src/ansys/acp/core/extras/example_helpers.py +++ b/src/ansys/acp/core/extras/example_helpers.py @@ -109,6 +109,7 @@ class ExampleKeys(Enum): ), ExampleKeys.IMPORTED_SOLID_MODEL_SOLID_MESH: _ExampleLocation( directory="imported_solid_model", filename="t-joint.solid.h5" + ), ExampleKeys.SNAP_TO_GEOMETRY: _ExampleLocation( directory="geometries", filename="snap_to_geometry.stp" ), From d00d8f21f75ec6a3f7c96715a89e5902c3853242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= Date: Mon, 25 Nov 2024 14:28:35 +0100 Subject: [PATCH 05/13] revert temporary change --- src/ansys/acp/core/extras/example_helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ansys/acp/core/extras/example_helpers.py b/src/ansys/acp/core/extras/example_helpers.py index 46ccb77909..c9e9678ab2 100644 --- a/src/ansys/acp/core/extras/example_helpers.py +++ b/src/ansys/acp/core/extras/example_helpers.py @@ -40,10 +40,10 @@ if TYPE_CHECKING: # pragma: no cover from ansys.acp.core import ACPWorkflow -# _EXAMPLE_REPO = "https://github.com/ansys/example-data/raw/master/pyacp/" +_EXAMPLE_REPO = "https://github.com/ansys/example-data/raw/master/pyacp/" -_EXAMPLE_REPO = "D:\\ANSYSDev\\pyansys-example-data\\pyacp\\" +# _EXAMPLE_REPO = "D:\\ANSYSDev\\pyansys-example-data\\pyacp\\" @dataclasses.dataclass From 2b71fbe5ac0c2d92de08ad9a9954f9609a822a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= Date: Mon, 25 Nov 2024 20:29:34 +0100 Subject: [PATCH 06/13] fix reference to print_model --- examples/modeling_features/021-imported-solid-model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/modeling_features/021-imported-solid-model.py b/examples/modeling_features/021-imported-solid-model.py index bfc9133e0f..5012b8c42d 100644 --- a/examples/modeling_features/021-imported-solid-model.py +++ b/examples/modeling_features/021-imported-solid-model.py @@ -242,7 +242,7 @@ # Show extent and thickness of mapped plies # ----------------------------------------- # -# Use :meth:`.print_model` to get the list of plies. +# Use :func:`.print_model` to get the list of plies. ap = ( model.modeling_groups["MG bonding_skin_right"] .modeling_plies["ModelingPly.26"] From 3b8cb3f2bd7221416aeb517ed901e0dd4112e470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= Date: Tue, 26 Nov 2024 09:29:49 +0100 Subject: [PATCH 07/13] upload solid mesh file to server --- .../modeling_features/021-imported-solid-model.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/modeling_features/021-imported-solid-model.py b/examples/modeling_features/021-imported-solid-model.py index 5012b8c42d..11f96e0e28 100644 --- a/examples/modeling_features/021-imported-solid-model.py +++ b/examples/modeling_features/021-imported-solid-model.py @@ -96,10 +96,11 @@ # Get the solid mesh file and create an ImportedSolidModel and # load the initial mesh. -solid_mesh_file = get_example_file(ExampleKeys.IMPORTED_SOLID_MODEL_SOLID_MESH, WORKING_DIR) +local_solid_mesh_file = get_example_file(ExampleKeys.IMPORTED_SOLID_MODEL_SOLID_MESH, WORKING_DIR) +remote_solid_mesh_file = acp.upload_file(local_solid_mesh_file) imported_solid_model = model.create_imported_solid_model( name="Imported Solid Model", - external_path=solid_mesh_file, + external_path=remote_solid_mesh_file, format="ansys:h5", ) @@ -242,7 +243,11 @@ # Show extent and thickness of mapped plies # ----------------------------------------- # -# Use :func:`.print_model` to get the list of plies. +# Use :func:`.print_model` to get the list of plies. After identifying the ply +# of interest, for example the thickness can be visualized. Note that +# only ply-wise data of :class:`.AnalysisPly` can be visualized on the +# solid mesh. :class:`.ProductionPly` and :class:`.ModelingPly` cannot +# be visualized on the solid mesh. ap = ( model.modeling_groups["MG bonding_skin_right"] .modeling_plies["ModelingPly.26"] From 9d2b922b2ef52121b10c69f1959307de0bb94a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= Date: Tue, 26 Nov 2024 17:28:22 +0100 Subject: [PATCH 08/13] Add solid_mesh to SolidElementSet and finalize the example. --- .../021-imported-solid-model.py | 36 +++++++++---------- .../_tree_objects/imported_solid_model.py | 3 +- .../core/_tree_objects/solid_element_set.py | 3 ++ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/examples/modeling_features/021-imported-solid-model.py b/examples/modeling_features/021-imported-solid-model.py index 11f96e0e28..94ec46f53e 100644 --- a/examples/modeling_features/021-imported-solid-model.py +++ b/examples/modeling_features/021-imported-solid-model.py @@ -55,12 +55,7 @@ # %% # Import the PyACP dependencies. -from ansys.acp.core import ( - ACPWorkflow, - ElementTechnology, - LayupMappingRosetteSelectionMethod, - launch_acp, -) +from ansys.acp.core import ElementTechnology, LayupMappingRosetteSelectionMethod, launch_acp from ansys.acp.core.extras import ExampleKeys, get_example_file # sphinx_gallery_thumbnail_number = 3 @@ -80,14 +75,8 @@ acp = launch_acp() # %% -# Define the input file and instantiate an ``ACPWorkflow`` instance. -workflow = ACPWorkflow.from_acph5_file( - acp=acp, - acph5_file_path=input_file, - local_working_directory=WORKING_DIR, -) - -model = workflow.model +# Load the model from an acph5 file +model = acp.import_model(input_file) # %% # Import external solid model @@ -100,18 +89,25 @@ remote_solid_mesh_file = acp.upload_file(local_solid_mesh_file) imported_solid_model = model.create_imported_solid_model( name="Imported Solid Model", - external_path=remote_solid_mesh_file, - format="ansys:h5", ) # Load the initial mesh and show the raw mesh without any mapping. -imported_solid_model.refresh() +imported_solid_model.refresh(path=remote_solid_mesh_file, format="ansys:h5") imported_solid_model.import_initial_mesh() model.solid_mesh.to_pyvista().plot(show_edges=True) # %% -# The solid element sets can be used as target for the mapping. -print(imported_solid_model.solid_element_sets) +# The solid element sets are used as target for the mapping later. +# Here is the full list and one is visualized. +imported_solid_model.solid_element_sets.keys() + +solid_eset_mesh = imported_solid_model.solid_element_sets[ + "mapping_target bonding skin right" +].solid_mesh +plotter = pyvista.Plotter() +plotter.add_mesh(solid_eset_mesh.to_pyvista()) +plotter.add_mesh(model.solid_mesh.to_pyvista(), opacity=0.2, show_edges=False) +plotter.show() # %% # Add mapping objects @@ -258,7 +254,7 @@ thickness_pyvista_mesh = thickness_data.get_pyvista_mesh(mesh=ap.solid_mesh) # type: ignore plotter = pyvista.Plotter() plotter.add_mesh(thickness_pyvista_mesh) -plotter.add_mesh(model.solid_mesh.to_pyvista(), opacity=0.2, show_edges=True) +plotter.add_mesh(model.solid_mesh.to_pyvista(), opacity=0.2, show_edges=False) plotter.show() diff --git a/src/ansys/acp/core/_tree_objects/imported_solid_model.py b/src/ansys/acp/core/_tree_objects/imported_solid_model.py index bca3e1ec96..c6542f4b5b 100644 --- a/src/ansys/acp/core/_tree_objects/imported_solid_model.py +++ b/src/ansys/acp/core/_tree_objects/imported_solid_model.py @@ -354,8 +354,9 @@ def _create_stub(self) -> imported_solid_model_pb2_grpc.ObjectServiceStub: layup_mapping_object_pb2_grpc.ObjectServiceStub, ) - def refresh(self, path: _PATH) -> None: + def refresh(self, path: _PATH, format: SolidModelImportFormat) -> None: """Re-import the solid model from the external file.""" + self.format = format self.external_path = self._server_wrapper.auto_upload(path) with wrap_grpc_errors(): self._get_stub().Refresh( # type: ignore diff --git a/src/ansys/acp/core/_tree_objects/solid_element_set.py b/src/ansys/acp/core/_tree_objects/solid_element_set.py index cafbf1d6c2..501beee85b 100644 --- a/src/ansys/acp/core/_tree_objects/solid_element_set.py +++ b/src/ansys/acp/core/_tree_objects/solid_element_set.py @@ -31,6 +31,7 @@ from .._utils.property_protocols import ReadOnlyProperty from ._elemental_or_nodal_data import ElementalData, NodalData from ._grpc_helpers.property_helper import grpc_data_property_read_only, mark_grpc_properties +from ._mesh_data import solid_mesh_property from .base import IdTreeObject, ReadOnlyTreeObject from .enums import status_type_from_pb from .object_registry import register @@ -76,3 +77,5 @@ def _create_stub(self) -> solid_element_set_pb2_grpc.ObjectServiceStub: element_labels: ReadOnlyProperty[tuple[int, ...]] = grpc_data_property_read_only( "properties.element_labels", from_protobuf=to_tuple_from_1D_array ) + + solid_mesh = solid_mesh_property From 92316b29c95cc543df46172b77a748e4696dee19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= Date: Wed, 27 Nov 2024 07:19:24 +0100 Subject: [PATCH 09/13] Fix type check --- src/ansys/acp/core/_tree_objects/imported_solid_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/acp/core/_tree_objects/imported_solid_model.py b/src/ansys/acp/core/_tree_objects/imported_solid_model.py index c6542f4b5b..7264a80765 100644 --- a/src/ansys/acp/core/_tree_objects/imported_solid_model.py +++ b/src/ansys/acp/core/_tree_objects/imported_solid_model.py @@ -354,7 +354,7 @@ def _create_stub(self) -> imported_solid_model_pb2_grpc.ObjectServiceStub: layup_mapping_object_pb2_grpc.ObjectServiceStub, ) - def refresh(self, path: _PATH, format: SolidModelImportFormat) -> None: + def refresh(self, path: _PATH, format: SolidModelImportFormat) -> None: # type: ignore """Re-import the solid model from the external file.""" self.format = format self.external_path = self._server_wrapper.auto_upload(path) From eb777190bc4654c759eec990e6b326aebe9672d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= Date: Wed, 27 Nov 2024 07:46:41 +0100 Subject: [PATCH 10/13] Make format of the refresh method optional --- .../acp/core/_tree_objects/imported_solid_model.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ansys/acp/core/_tree_objects/imported_solid_model.py b/src/ansys/acp/core/_tree_objects/imported_solid_model.py index 7264a80765..0effc59ba7 100644 --- a/src/ansys/acp/core/_tree_objects/imported_solid_model.py +++ b/src/ansys/acp/core/_tree_objects/imported_solid_model.py @@ -354,9 +354,14 @@ def _create_stub(self) -> imported_solid_model_pb2_grpc.ObjectServiceStub: layup_mapping_object_pb2_grpc.ObjectServiceStub, ) - def refresh(self, path: _PATH, format: SolidModelImportFormat) -> None: # type: ignore - """Re-import the solid model from the external file.""" - self.format = format + def refresh(self, path: _PATH, format: SolidModelImportFormat | None = None) -> None: # type: ignore + """ + Re-import the solid model from the external file. + + The ``format`` parameter is only used to switch it. Otherwise, the current value is used. + """ + if format is not None: + self.format = format self.external_path = self._server_wrapper.auto_upload(path) with wrap_grpc_errors(): self._get_stub().Refresh( # type: ignore From 9dfd0e8ab9d7847e4ee248b254cc5e6cea2d9dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= Date: Wed, 27 Nov 2024 08:49:37 +0100 Subject: [PATCH 11/13] Fix file handling --- .../modeling_features/021-imported-solid-model.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/examples/modeling_features/021-imported-solid-model.py b/examples/modeling_features/021-imported-solid-model.py index 94ec46f53e..e99e025cec 100644 --- a/examples/modeling_features/021-imported-solid-model.py +++ b/examples/modeling_features/021-imported-solid-model.py @@ -82,17 +82,13 @@ # Import external solid model # --------------------------- # -# Get the solid mesh file and create an ImportedSolidModel and -# load the initial mesh. - -local_solid_mesh_file = get_example_file(ExampleKeys.IMPORTED_SOLID_MODEL_SOLID_MESH, WORKING_DIR) -remote_solid_mesh_file = acp.upload_file(local_solid_mesh_file) +# Get the solid mesh file and create an ImportedSolidModel, +# load the initial mesh and show the raw mesh without any mapping. +solid_mesh_file = get_example_file(ExampleKeys.IMPORTED_SOLID_MODEL_SOLID_MESH, WORKING_DIR) imported_solid_model = model.create_imported_solid_model( name="Imported Solid Model", ) - -# Load the initial mesh and show the raw mesh without any mapping. -imported_solid_model.refresh(path=remote_solid_mesh_file, format="ansys:h5") +imported_solid_model.refresh(path=solid_mesh_file, format="ansys:h5") imported_solid_model.import_initial_mesh() model.solid_mesh.to_pyvista().plot(show_edges=True) From 7b7c80f12b4c2247c47f5df3c35a8213c43ee8fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= Date: Wed, 27 Nov 2024 09:06:34 +0100 Subject: [PATCH 12/13] Update doc string of refresh methods of ImportedSolidModel and CADGeometry --- .../modeling_features/021-imported-solid-model.py | 14 +++++++------- src/ansys/acp/core/_tree_objects/cad_geometry.py | 8 +++++++- .../acp/core/_tree_objects/imported_solid_model.py | 8 +++++++- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/examples/modeling_features/021-imported-solid-model.py b/examples/modeling_features/021-imported-solid-model.py index e99e025cec..ab5de525c9 100644 --- a/examples/modeling_features/021-imported-solid-model.py +++ b/examples/modeling_features/021-imported-solid-model.py @@ -114,7 +114,7 @@ # and show the updated solid model. solid_esets = imported_solid_model.solid_element_sets -_ = imported_solid_model.create_layup_mapping_object( +imported_solid_model.create_layup_mapping_object( name="sandwich skin top", element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[model.element_sets["els_sandwich_skin_top"]], @@ -127,7 +127,7 @@ # %% # Add other mapping objects -_ = imported_solid_model.create_layup_mapping_object( +imported_solid_model.create_layup_mapping_object( name="sandwich skin bottom", element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[model.element_sets["els_sandwich_skin_bottom"]], @@ -137,7 +137,7 @@ ], ) -_ = imported_solid_model.create_layup_mapping_object( +imported_solid_model.create_layup_mapping_object( name="stringer", element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[model.element_sets["els_stringer_skin_left"]], @@ -152,7 +152,7 @@ ], ) -_ = imported_solid_model.create_layup_mapping_object( +imported_solid_model.create_layup_mapping_object( name="bonding skin", element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[ @@ -173,7 +173,7 @@ # %% # The mapping can also be done for specific plies # as shown for the core materials. -_ = imported_solid_model.create_layup_mapping_object( +imported_solid_model.create_layup_mapping_object( name="foam", element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[ @@ -189,7 +189,7 @@ rosette_selection_method=LayupMappingRosetteSelectionMethod.MINIMUM_DISTANCE, ) -_ = imported_solid_model.create_layup_mapping_object( +imported_solid_model.create_layup_mapping_object( name="honeycomb", element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[ @@ -211,7 +211,7 @@ # Add filler mapping objects where the solid mesh is "filled" # with a single material. No plies from the layup are used here. -_ = imported_solid_model.create_layup_mapping_object( +imported_solid_model.create_layup_mapping_object( name="resin", element_technology=ElementTechnology.LAYERED_ELEMENT, shell_element_sets=[], diff --git a/src/ansys/acp/core/_tree_objects/cad_geometry.py b/src/ansys/acp/core/_tree_objects/cad_geometry.py index 04762d901a..a801b6d3f3 100644 --- a/src/ansys/acp/core/_tree_objects/cad_geometry.py +++ b/src/ansys/acp/core/_tree_objects/cad_geometry.py @@ -180,7 +180,13 @@ def visualization_mesh(self) -> TriangleMesh: ) def refresh(self, path: PATH) -> None: - """Reload the geometry from its external source.""" + """Reload the geometry from its external source. + + Parameters + ---------- + path : + Path of the new input file. + """ self.external_path = self._server_wrapper.auto_upload(path) stub = cast(cad_geometry_pb2_grpc.ObjectServiceStub, self._get_stub()) with wrap_grpc_errors(): diff --git a/src/ansys/acp/core/_tree_objects/imported_solid_model.py b/src/ansys/acp/core/_tree_objects/imported_solid_model.py index 0effc59ba7..55f534b985 100644 --- a/src/ansys/acp/core/_tree_objects/imported_solid_model.py +++ b/src/ansys/acp/core/_tree_objects/imported_solid_model.py @@ -358,7 +358,13 @@ def refresh(self, path: _PATH, format: SolidModelImportFormat | None = None) -> """ Re-import the solid model from the external file. - The ``format`` parameter is only used to switch it. Otherwise, the current value is used. + Parameters + ---------- + path : + Path of the new input file. + format : + Switch format of the input file. Optional, uses the current format of the + imported solid model if not specified. """ if format is not None: self.format = format From a9fb0aecd1675c841975a277a845e484c41ec4b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Roos?= Date: Wed, 27 Nov 2024 11:49:33 +0100 Subject: [PATCH 13/13] Add unit tests for the edited and enhanced functions. --- tests/unittests/test_imported_solid_model.py | 16 +++++++++++++--- tests/unittests/test_solid_element_set.py | 3 +++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/unittests/test_imported_solid_model.py b/tests/unittests/test_imported_solid_model.py index f676459ea9..16e7229880 100644 --- a/tests/unittests/test_imported_solid_model.py +++ b/tests/unittests/test_imported_solid_model.py @@ -250,11 +250,21 @@ def test_import_initial_mesh(acp_instance, parent_object): model.update() with tempfile.TemporaryDirectory() as tmp_dir: - out_path = pathlib.Path(tmp_dir) / f"out_file.h5" - solid_model.export(path=out_path, format=pyacp.SolidModelExportFormat.ANSYS_H5) + out_path_h5 = pathlib.Path(tmp_dir) / f"out_file.h5" + solid_model.export(path=out_path_h5, format=pyacp.SolidModelExportFormat.ANSYS_H5) + out_path_cdb = pathlib.Path(tmp_dir) / f"out_file.cdb" + solid_model.export(path=out_path_cdb, format=pyacp.SolidModelExportFormat.ANSYS_CDB) imported_solid_model = model.create_imported_solid_model( - external_path=acp_instance.upload_file(out_path), + external_path=acp_instance.upload_file(out_path_h5), format=pyacp.SolidModelImportFormat.ANSYS_H5, ) imported_solid_model.import_initial_mesh() + + # refresh from external source with the same format + imported_solid_model.refresh(out_path_h5) + imported_solid_model.import_initial_mesh() + + # refresh from external source where the format is different + imported_solid_model.refresh(out_path_cdb, format=pyacp.SolidModelImportFormat.ANSYS_CDB) + imported_solid_model.import_initial_mesh() diff --git a/tests/unittests/test_solid_element_set.py b/tests/unittests/test_solid_element_set.py index 82e4556d30..c277288cdd 100644 --- a/tests/unittests/test_solid_element_set.py +++ b/tests/unittests/test_solid_element_set.py @@ -89,3 +89,6 @@ def test_properties(self, parent_object, properties): ref_values = properties[solid_element_set.id] for prop, value in ref_values.items(): assert getattr(solid_element_set, prop) == value + + assert solid_element_set.solid_mesh is not None + assert solid_element_set.solid_mesh.element_labels == (2,)