diff --git a/examples/modeling_features/01-sandwich-panel-layup.py b/examples/modeling_features/01-sandwich-panel-layup.py index 3d19baf950..b2d37ea992 100644 --- a/examples/modeling_features/01-sandwich-panel-layup.py +++ b/examples/modeling_features/01-sandwich-panel-layup.py @@ -23,8 +23,8 @@ """ .. _sandwich_panel: -Sandwich panel example -====================== +Sandwich panel +============== This example defines a composite lay-up for a sandwich panel using PyACP. It only shows the PyACP part of the setup. For a complete composite analysis, diff --git a/examples/modeling_features/02-simple-selection-rules.py b/examples/modeling_features/02-simple-selection-rules.py index 759caa7b85..5bb07793e4 100644 --- a/examples/modeling_features/02-simple-selection-rules.py +++ b/examples/modeling_features/02-simple-selection-rules.py @@ -23,8 +23,8 @@ """ .. _basic_selection_rules_example: -Basic selection rules example -============================= +Basic selection rules +===================== This example shows the basic usage of selection rules, which enable you to select elements through geometrical operations and thus to shape plies. The example only shows the PyACP part of the setup. diff --git a/examples/modeling_features/020-solid_model.py b/examples/modeling_features/020-solid_model.py index 9a62941a45..44f42335e7 100644 --- a/examples/modeling_features/020-solid_model.py +++ b/examples/modeling_features/020-solid_model.py @@ -52,7 +52,7 @@ get_directions_plotter, launch_acp, ) -from ansys.acp.core.extras import ExampleKeys, get_example_file +from ansys.acp.core.extras import FLAT_PLATE_SOLID_CAMERA, ExampleKeys, get_example_file # sphinx_gallery_thumbnail_number = 4 @@ -92,7 +92,23 @@ ) model.update() -model.solid_mesh.to_pyvista().plot(show_edges=True) + + +def plot_model_with_geometry(cad_geometry: CADGeometry | None, cad_geom_opacity: float = 0.1): + """Plot solid model and geometry.""" + plotter = pyvista.Plotter() + if cad_geometry: + geom_mesh = cad_geometry.visualization_mesh.to_pyvista() + plotter.add_mesh(geom_mesh, color="green", opacity=cad_geom_opacity) + edges = geom_mesh.extract_feature_edges() + plotter.add_mesh(edges, color="white", line_width=4) + plotter.add_mesh(edges, color="black", line_width=2) + plotter.add_mesh(model.solid_mesh.to_pyvista(), show_edges=True) + plotter.camera_position = FLAT_PLATE_SOLID_CAMERA + plotter.show() + + +plot_model_with_geometry(None) def create_virtual_geometry_from_file( @@ -109,18 +125,6 @@ def create_virtual_geometry_from_file( return geometry_obj, virtual_geometry -def plot_model_with_geometry(cad_geometry: CADGeometry, cad_geom_opacity: float = 0.1): - """Plot solid model and geometry.""" - plotter = pyvista.Plotter() - geom_mesh = cad_geometry.visualization_mesh.to_pyvista() - plotter.add_mesh(geom_mesh, color="green", opacity=cad_geom_opacity) - edges = geom_mesh.extract_feature_edges() - plotter.add_mesh(edges, color="white", line_width=4) - plotter.add_mesh(edges, color="black", line_width=2) - plotter.add_mesh(model.solid_mesh.to_pyvista(), show_edges=True) - plotter.show() - - # %% # Snap the top to a geometry # -------------------------- @@ -160,7 +164,7 @@ def plot_model_with_geometry(cad_geometry: CADGeometry, cad_geom_opacity: float depth=0.6, ) model.update() -model.solid_mesh.to_pyvista().plot(show_edges=True) +plot_model_with_geometry(None) # %% # Cut-off an edge @@ -208,7 +212,8 @@ def plot_model_with_geometry(cad_geometry: CADGeometry, cad_geom_opacity: float length_factor=10.0, culling_factor=10, ) -direction_plotter.add_mesh(model.solid_mesh.to_pyvista(), opacity=0.2, show_edges=True) +direction_plotter.add_mesh(model.solid_mesh.to_pyvista(), opacity=0.2, show_edges=False) +direction_plotter.camera_position = FLAT_PLATE_SOLID_CAMERA direction_plotter.show() # %% @@ -218,5 +223,6 @@ def plot_model_with_geometry(cad_geometry: CADGeometry, cad_geom_opacity: float 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.camera_position = FLAT_PLATE_SOLID_CAMERA plotter.show() diff --git a/examples/modeling_features/021-imported-solid-model.py b/examples/modeling_features/021-imported-solid-model.py index ab5de525c9..c14cd7c86f 100644 --- a/examples/modeling_features/021-imported-solid-model.py +++ b/examples/modeling_features/021-imported-solid-model.py @@ -23,7 +23,7 @@ """ .. _imported_solid_model_example: -Imported Solid model +Imported solid model ==================== This example guides you through the definition of an :class:`.ImportedSolidModel` @@ -58,7 +58,7 @@ from ansys.acp.core import ElementTechnology, LayupMappingRosetteSelectionMethod, launch_acp from ansys.acp.core.extras import ExampleKeys, get_example_file -# sphinx_gallery_thumbnail_number = 3 +# sphinx_gallery_thumbnail_number = 4 # %% @@ -262,6 +262,9 @@ # 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. # +# An example of an :class:`.ImportedSolidModel` in combination with :class:`.ImportedModelingPly` +# is shown in :ref:`imported_plies_example`. +# # 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/examples/modeling_features/03-advanced-selection-rules.py b/examples/modeling_features/03-advanced-selection-rules.py index 17a0743697..2386f796fe 100644 --- a/examples/modeling_features/03-advanced-selection-rules.py +++ b/examples/modeling_features/03-advanced-selection-rules.py @@ -23,8 +23,8 @@ """ .. _advanced_selection_rules_example: -Advanced selection rules example -================================ +Advanced selection rules +======================== This example shows how to use advanced rules, including the geometrical, cut-off, and variable offset rules. It also demonstrates how rules can be templated diff --git a/examples/modeling_features/04-layup-thickness-definitions.py b/examples/modeling_features/04-layup-thickness-definitions.py index b0a3884e70..b5db53c001 100644 --- a/examples/modeling_features/04-layup-thickness-definitions.py +++ b/examples/modeling_features/04-layup-thickness-definitions.py @@ -23,8 +23,8 @@ """ .. _thickness_definition_example: -Thickness definition example -============================ +Thickness definition +==================== This example shows how the thickness of a ply can be defined by a geometry or a lookup table. The example only shows the PyACP part of the setup. For a complete composite analysis, @@ -46,7 +46,7 @@ # %% # Import the PyACP dependencies. from ansys.acp.core import DimensionType, ThicknessType, launch_acp -from ansys.acp.core.extras import ExampleKeys, get_example_file +from ansys.acp.core.extras import FLAT_PLATE_SOLID_CAMERA, ExampleKeys, get_example_file # sphinx_gallery_thumbnail_number = 2 @@ -117,7 +117,7 @@ plotter.add_mesh(edges, color="black", line_width=2) # Plot the ply thickness plotter.add_mesh(model.elemental_data.thickness.get_pyvista_mesh(mesh=model.mesh), show_edges=True) - +plotter.camera_position = FLAT_PLATE_SOLID_CAMERA plotter.show() # %% diff --git a/examples/modeling_features/05-rosettes-ply-directions.py b/examples/modeling_features/05-rosettes-ply-directions.py index b49c921f52..497e0ccb92 100644 --- a/examples/modeling_features/05-rosettes-ply-directions.py +++ b/examples/modeling_features/05-rosettes-ply-directions.py @@ -23,8 +23,8 @@ """ .. _rosette_example: -Rosette example -=============== +Rosette +======= This example illustrates how you can use rosettes to define the reference directions of a ply. It only shows the PyACP part of the setup. For a complete composite analysis, diff --git a/examples/modeling_features/050-composite_cae_h5.py b/examples/modeling_features/050-composite_cae_h5.py new file mode 100644 index 0000000000..525c9ef21f --- /dev/null +++ b/examples/modeling_features/050-composite_cae_h5.py @@ -0,0 +1,238 @@ +# 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. + +""" +.. _composite_cae_h5_example: + +HDF5 Composite CAE +================== + +The HDF5 Composite CAE interface of PyACP is demonstrated in +this example. It shows how to write (export) and read (import) +layup data to and from a HDF5 Composite CAE file, respectively. +The HDF5 Composite CAE format is a vendor independent format +to exchange composite layup information between CAE tools. + +This examples demonstrates how to: +- Load and manipulate a model +- Export data to a HDF5 Composite CAE file +- Import and map layup from HDF5 Composite CAE onto a different model (mesh) +- Export data with ply offsets (3D plies) +- Import a layup as :class:`.ImportedModelingPly` +- Import HDF5 Composite CAE with 3D plies and map the layup onto an :class:`.ImportedSolidModel` + +""" + +# %% +# Import the standard library and third-party dependencies. +import pathlib +import tempfile + +import pyvista + +# %% +# Import the PyACP dependencies. +from ansys.acp.core import ( + HDF5CompositeCAEProjectionMode, + LinkedSelectionRule, + OffsetType, + launch_acp, +) +from ansys.acp.core.extras import ( + FLAT_PLATE_SHELL_CAMERA, + FLAT_PLATE_SOLID_CAMERA, + ExampleKeys, + get_example_file, +) + +# 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) +acph5_input_file = get_example_file(ExampleKeys.BASIC_FLAT_PLATE_ACPH5, WORKING_DIR) + +# %% +# Launch the PyACP server and connect to it. +acp = launch_acp() + +# %% +# Load the model from an acph5 file +model = acp.import_model(acph5_input_file) + +# %% +# Crop some plies in order to generate a variable laminate +pr_x = model.create_parallel_selection_rule( + name="x axis", + direction=(1, 0, 0), + lower_limit=0.0025, + upper_limit=0.0075, +) +pr_z = model.create_parallel_selection_rule( + name="z axis", + direction=(0, 0, 1), + lower_limit=0.0015, + upper_limit=0.0085, +) +boolean_rule = model.create_boolean_selection_rule( + name="boolean rule", + selection_rules=[LinkedSelectionRule(pr_x), LinkedSelectionRule(pr_z)], +) + +for ply_name in ["ply_1_45_UD", "ply_2_-45_UD", "ply_3_45_UD", "ply_4_-45_UD"]: + ply = model.modeling_groups["modeling_group"].modeling_plies[ply_name] + ply.selection_rules = [LinkedSelectionRule(boolean_rule)] + +model.update() + +# %% +# Plot the thickness distribution +thickness = model.elemental_data.thickness +assert thickness is not None +thickness.get_pyvista_mesh(mesh=model.mesh).plot(show_edges=True) + +# %% +# Write HDF5 Composite CAE file +# ----------------------------- +# +# Export the entire layup to a HDF5 Composite CAE file. +h5_output_file = WORKING_DIR / "hdf5_composite_cae.h5" +model.export_hdf5_composite_cae( + path=h5_output_file, +) + +# %% +# Load HDF5 Composite CAE file into a different model +# --------------------------------------------------- +# +# A new acp model is created by importing a refined mesh of the same geometry. +# Both meshes (initial mesh in blue, refined one in red) are shown below. +dat_input_file_refined = get_example_file(ExampleKeys.BASIC_FLAT_PLATE_REFINED_DAT, WORKING_DIR) +refined_model = acp.import_model(path=dat_input_file_refined, format="ansys:dat") + +plotter = pyvista.Plotter() +plotter.add_mesh( + model.shell_mesh.to_pyvista(), + color="blue", + edge_color="blue", + show_edges=True, + style="wireframe", + line_width=4, +) +plotter.add_mesh( + refined_model.shell_mesh.to_pyvista(), + color="red", + edge_color="red", + show_edges=True, + style="wireframe", + line_width=2, +) +plotter.camera_position = FLAT_PLATE_SHELL_CAMERA +plotter.show() + +# %% +# Import the HDF5 Composite CAE file which is then automatically mapped +# onto the refined mesh. In this example, the default settings +# (tolerances, etc.) are used. +refined_model.import_hdf5_composite_cae( + path=h5_output_file, +) +refined_model.update() + +# %% +# Plot the thickness distribution on the refined model +thickness = refined_model.elemental_data.thickness +assert thickness is not None +thickness.get_pyvista_mesh(mesh=refined_model.mesh).plot(show_edges=True) + +# %% +# 3D plies with ply-offsets +# ------------------------- +# +# The HDF5 Composite CAE interface also allows to export the 3D plies +# (plies with offsets) which can then be used to create +# imported modeling plies. The initial model is used to +# write a new HDF5 with ``layup_representation_3d`` enabled. +h5_output_file_3D = WORKING_DIR / "hdf5_composite_cae_3D.h5" +model.export_hdf5_composite_cae( + path=h5_output_file_3D, + layup_representation_3d=True, + offset_type=OffsetType.BOTTOM_OFFSET, +) + +# %% +# A new acp model is created to properly separate the different workflows. +refined_model_3D = acp.import_model(path=dat_input_file_refined, format="ansys:dat") +refined_model_3D.import_hdf5_composite_cae( + path=h5_output_file_3D, projection_mode=HDF5CompositeCAEProjectionMode.SOLID +) + +# %% +# An imported solid model is required for the 3D workflow (with imported modeling plies). +# Details about :class:`.ImportedSolidModel` and :class:`.ImportedModelingPly` can be found +# in the examples :ref:`imported_solid_model_example` and :ref:`imported_plies_example`. +local_solid_mesh_file = get_example_file(ExampleKeys.BASIC_FLAT_PLATE_SOLID_MESH_CDB, WORKING_DIR) +remote_solid_mesh_file = acp.upload_file(local_solid_mesh_file) +imported_solid_model = refined_model_3D.create_imported_solid_model( + name="Imported Solid Model", + external_path=remote_solid_mesh_file, + format="ansys:cdb", +) + +# %% +# The :class:`.LayupMappingObject` is used to configure the mapping of the imported plies +# onto the imported solid model. +imported_solid_model.create_layup_mapping_object( + name="Map imported plies", + use_imported_plies=True, # enable imported plies + select_all_plies=True, # select all plies + scale_ply_thicknesses=True, + entire_solid_mesh=True, + delete_lost_elements=True, # elements without plies are deleted +) +refined_model_3D.update() + +# %% +# The mapped top layer of the imported laminate is shown below. +# Note that the solid elements which do not intersect with the +# layup are deleted in this example. +imported_analysis_ply = ( + refined_model_3D.imported_modeling_groups["modeling_group"] + .imported_modeling_plies["ply_5_0_UD"] + .imported_production_plies["ImportedProductionPly.6"] + .imported_analysis_plies["P1L1__ply_5_0_UD"] +) +plotter = pyvista.Plotter() +plotter.add_mesh(imported_analysis_ply.solid_mesh.to_pyvista(), show_edges=True) +plotter.add_mesh(refined_model_3D.solid_mesh.to_pyvista(), opacity=0.2, show_edges=False) +plotter.camera_position = FLAT_PLATE_SOLID_CAMERA +plotter.show() + +# %% +# Note that the visualization of imported plies and imported solid model +# is limited. As an alternative, you can save the model and review it +# in ACP standalone. diff --git a/examples/modeling_features/06-ply-direction-lookup-table.py b/examples/modeling_features/06-ply-direction-lookup-table.py index 38813bd603..72e5d910f3 100644 --- a/examples/modeling_features/06-ply-direction-lookup-table.py +++ b/examples/modeling_features/06-ply-direction-lookup-table.py @@ -23,8 +23,8 @@ """ .. _direction_definition_example: -Direction definition example -============================ +Direction definition +==================== This example shows how to define directions from lookup tables. They can be either reference directions for oriented selection sets or draping angles for modeling plies. diff --git a/examples/modeling_features/07-imported-plies.py b/examples/modeling_features/07-imported-plies.py new file mode 100644 index 0000000000..fe9cbf9d8d --- /dev/null +++ b/examples/modeling_features/07-imported-plies.py @@ -0,0 +1,245 @@ +# 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_plies_example: + +Imported ply +============ + +The definition and use of imported plies is demonstrated in this example. +In a nutshell, the difference between :class:`.ImportedModelingPly` and +:class:`.ModelingPly` is that the surface mesh of an :class:`.ImportedModelingPly` +is defined by an external source, such as a CAD surface, where a :class:`.ModelingPly` +is always defined on the initial loaded shell mesh. +Therefore, an imported ply can only be used in combination with an +:class:`.ImportedSolidModel`. + +This examples shows hot to: + +- Load an initial mesh +- Add a :class:`.Material` and :class:`.Fabric` +- Import geometries which will be used to define the surface of the :class:`.ImportedModelingPly` +- Add two imported modeling plies +- Create an :class:`.ImportedSolidModel` +- Map the imported plies to the solid model +- Visualized the mapped plies. +""" + +# %% +# Import modules +# -------------- +# +# Import the standard library and third-party dependencies. +import os +import pathlib +import tempfile + +import pyvista + +# %% +# Import the PyACP dependencies. +from ansys.acp.core import CADGeometry, ImportedPlyOffsetType, PlyType, VirtualGeometry, launch_acp +from ansys.acp.core.extras import ExampleKeys, get_example_file +from ansys.acp.core.material_property_sets import ConstantDensity, ConstantEngineeringConstants + +# sphinx_gallery_thumbnail_number = 3 + + +CAMERA_POSITION = [(0.0436, 0.0102, 0.0193), (0.0111, 0.0035, 0.0046), (-0.1685, 0.9827, -0.0773)] + +# %% +# 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.BASIC_FLAT_PLATE_DAT, WORKING_DIR) + +# %% +# Launch the PyACP server and connect to it. +acp = launch_acp() + +# %% +# Create a model by loading a shell mesh +model = acp.import_model(path=input_file, format="ansys:dat") + +# %% +# Add a material and a fabric with 1mm thickness. +# The fabric is used for the imported modeling ply. +engineering_constants_ud = ConstantEngineeringConstants.from_orthotropic_constants( + E1=5e10, E2=1e10, E3=1e10, nu12=0.28, nu13=0.28, nu23=0.3, G12=5e9, G23=4e9, G31=4e9 +) +density_ud = ConstantDensity(rho=2700) + +ud_material = model.create_material( + name="E-Glass UD", + ply_type=PlyType.REGULAR, + engineering_constants=engineering_constants_ud, + density=density_ud, +) + +engineering_resin = ConstantEngineeringConstants.from_isotropic_constants(E=5e9, nu=0.3) +density_resin = ConstantDensity(rho=1200) + +void_material = model.create_material( + name="Void material", + ply_type=PlyType.ISOTROPIC, + engineering_constants=engineering_resin, + density=density_resin, +) +filler_material = model.create_material( + name="Filler material", + ply_type=PlyType.ISOTROPIC, + engineering_constants=engineering_resin, + density=density_resin, +) + +fabric = model.create_fabric(name="E-Glass Fabric", material=ud_material, thickness=0.001) + + +# %% +# Import CAD geometries +# --------------------- +# +# Import two cad surfaces to define the surface of the imported modeling plies. +def create_virtual_geometry_from_file( + example_key: ExampleKeys, +) -> tuple[CADGeometry, VirtualGeometry]: + """Create a CAD geometry and virtual geometry.""" + geometry_file = get_example_file(example_key, WORKING_DIR) + geometry_obj = model.create_cad_geometry() + geometry_obj.refresh(geometry_file) # upload and load the geometry file + model.update() + virtual_geometry = model.create_virtual_geometry( + name=os.path.basename(geometry_file), cad_components=geometry_obj.root_shapes.values() + ) + return geometry_obj, virtual_geometry + + +triangle_surf_cad, triangle_surf_vcad = create_virtual_geometry_from_file( + ExampleKeys.RULE_GEOMETRY_TRIANGLE +) +top_surf_cad, top_surf_vcad = create_virtual_geometry_from_file(ExampleKeys.SNAP_TO_GEOMETRY) + +# %% +# Definition of Imported Plies +# ---------------------------- +imported_ply_group = model.create_imported_modeling_group(name="Imported Ply Group") +imported_ply_triangle = imported_ply_group.create_imported_modeling_ply( + name="Triangle Ply", + offset_type=ImportedPlyOffsetType.BOTTOM_OFFSET, + ply_material=fabric, + mesh_geometry=triangle_surf_vcad, + ply_angle=0, + rosettes=[model.rosettes["12"]], +) + +imported_ply_top = imported_ply_group.create_imported_modeling_ply( + name="Triangle Ply", + offset_type=ImportedPlyOffsetType.MIDDLE_OFFSET, + ply_material=fabric, + mesh_geometry=top_surf_vcad, + ply_angle=45, + rosettes=[model.rosettes["12"]], +) +model.update() + + +# %% +# Imported plies cannot be visualized directly yet but the cad geometries are +# shown here instead. +# To visualize the imported plies, you can save the model and load it in ACP +# standalone. +def plotter_with_all_geometries(cad_geometries): + colors = ["green", "yellow", "blue", "red"] + plotter = pyvista.Plotter() + for index, cad in enumerate(cad_geometries): + geom_mesh = cad.visualization_mesh.to_pyvista() + plotter.add_mesh(geom_mesh, color=colors[index], opacity=0.1) + edges = geom_mesh.extract_feature_edges() + plotter.add_mesh(edges, color="white", line_width=4) + plotter.add_mesh(edges, color="black", line_width=2) + plotter.camera_position = CAMERA_POSITION + return plotter + + +plotter = plotter_with_all_geometries([triangle_surf_cad, top_surf_cad]) +plotter.show() + +# %% +# Map Imported Plies onto a solid mesh +# ------------------------------------ +# +# An external solid mesh is loaded now to map the imported plies +# onto the solid model. The next figure shows the imported solid mesh +# and the imported plies. +local_solid_mesh_file = get_example_file(ExampleKeys.BASIC_FLAT_PLATE_SOLID_MESH_CDB, 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=remote_solid_mesh_file, + format="ansys:cdb", +) +imported_solid_model.import_initial_mesh() +plotter = plotter_with_all_geometries([triangle_surf_cad, top_surf_cad]) +plotter.add_mesh(imported_solid_model.solid_mesh.to_pyvista(), show_edges=True, opacity=0.5) +plotter.show() + +# %% +# Add a mapping object to link the imported plies with the solid model. +# In this example, all imported plies are mapped in one go. +# The remaining elemental volume and elements which do not intersect +# with the imported plies are filled with a void and filler material, +# respectively. +imported_solid_model.create_layup_mapping_object( + name="Map imported plies", + use_imported_plies=True, # enable imported plies + select_all_plies=True, # select all plies + entire_solid_mesh=True, + scale_ply_thicknesses=False, + void_material=void_material, + delete_lost_elements=False, + filler_material=filler_material, + rosettes=[model.rosettes["12"]], +) +model.update() + +# %% +# Show the imported ply geometries and mapped plies on the solid model. +# Note that the analysis plies are not yet directly accessible via +# the API of the imported solid model. Also, elemental data such as +# thicknesses are not yet implemented for imported plies. +plotter = plotter_with_all_geometries([triangle_surf_cad, top_surf_cad]) +for imported_ply in [imported_ply_triangle, imported_ply_top]: + for pp in imported_ply.imported_production_plies.values(): + for ap in pp.imported_analysis_plies.values(): + plotter.add_mesh(ap.solid_mesh.to_pyvista(), show_edges=True, opacity=1) +plotter.add_mesh(mesh=imported_solid_model.solid_mesh.to_pyvista(), show_edges=False, opacity=0.2) +plotter.show() + +# %% +# The imported solid model can be passed to Mechanical or MAPDL to run an analysis +# as shown in the examples :ref:`pymechanical_solid_example` and +# :ref:`pymapdl_workflow_example`. diff --git a/src/ansys/acp/core/_tree_objects/imported_analysis_ply.py b/src/ansys/acp/core/_tree_objects/imported_analysis_ply.py index 5a5b42b03e..7c41744fff 100644 --- a/src/ansys/acp/core/_tree_objects/imported_analysis_ply.py +++ b/src/ansys/acp/core/_tree_objects/imported_analysis_ply.py @@ -32,6 +32,7 @@ grpc_link_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) -> imported_analysis_ply_pb2_grpc.ObjectServiceStub: active_in_post_mode: ReadOnlyProperty[bool] = grpc_data_property_read_only( "properties.active_in_post_mode" ) + + solid_mesh = solid_mesh_property 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 55f534b985..eb56434ede 100644 --- a/src/ansys/acp/core/_tree_objects/imported_solid_model.py +++ b/src/ansys/acp/core/_tree_objects/imported_solid_model.py @@ -57,6 +57,7 @@ grpc_link_property, mark_grpc_properties, ) +from ._mesh_data import solid_mesh_property from ._solid_model_export import SolidModelExportMixin from .base import ( CreatableTreeObject, @@ -380,3 +381,5 @@ def import_initial_mesh(self) -> None: self._get_stub().ImportInitialMesh( # type: ignore imported_solid_model_pb2.ImportInitialMeshRequest(resource_path=self._resource_path) ) + + solid_mesh = solid_mesh_property diff --git a/src/ansys/acp/core/extras/__init__.py b/src/ansys/acp/core/extras/__init__.py index 02b7305b3e..8d20f00f4f 100644 --- a/src/ansys/acp/core/extras/__init__.py +++ b/src/ansys/acp/core/extras/__init__.py @@ -21,9 +21,16 @@ # SOFTWARE. """Extras of the Ansys Composites PrepPost module.""" -from ansys.acp.core.extras.example_helpers import ExampleKeys, get_example_file +from ansys.acp.core.extras.example_helpers import ( + FLAT_PLATE_SHELL_CAMERA, + FLAT_PLATE_SOLID_CAMERA, + ExampleKeys, + get_example_file, +) __all__ = [ "ExampleKeys", "get_example_file", + "FLAT_PLATE_SHELL_CAMERA", + "FLAT_PLATE_SOLID_CAMERA", ] diff --git a/src/ansys/acp/core/extras/example_helpers.py b/src/ansys/acp/core/extras/example_helpers.py index cc59e277b3..141b85a723 100644 --- a/src/ansys/acp/core/extras/example_helpers.py +++ b/src/ansys/acp/core/extras/example_helpers.py @@ -46,6 +46,19 @@ # _EXAMPLE_REPO = "D:\\ANSYSDev\\pyansys-example-data\\pyacp\\" +# Order of inputs: position, rotation point, orientation +FLAT_PLATE_SHELL_CAMERA = [ + (-0.0053, 0.0168, 0.0220), + (0.0022, 0.0041, 0.0104), + (0.3510, 0.7368, -0.5779), +] +FLAT_PLATE_SOLID_CAMERA = [ + (0.0251, 0.0144, 0.0256), + (0.0086, 0.0041, 0.0089), + (-0.2895, 0.9160, -0.2776), +] + + @dataclasses.dataclass class _ExampleLocation: directory: str @@ -56,7 +69,9 @@ class ExampleKeys(Enum): """Keys for the example files.""" BASIC_FLAT_PLATE_DAT = auto() + BASIC_FLAT_PLATE_REFINED_DAT = auto() BASIC_FLAT_PLATE_ACPH5 = auto() + BASIC_FLAT_PLATE_SOLID_MESH_CDB = auto() RACE_CAR_NOSE_ACPH5 = auto() RACE_CAR_NOSE_STEP = auto() CUT_OFF_GEOMETRY = auto() @@ -77,9 +92,15 @@ class ExampleKeys(Enum): ExampleKeys.BASIC_FLAT_PLATE_DAT: _ExampleLocation( directory="basic_flat_plate_example", filename="flat_plate_input.dat" ), + ExampleKeys.BASIC_FLAT_PLATE_REFINED_DAT: _ExampleLocation( + directory="basic_flat_plate_example", filename="flat_plate_input_refined.dat" + ), ExampleKeys.BASIC_FLAT_PLATE_ACPH5: _ExampleLocation( directory="basic_flat_plate_example", filename="flat_plate.acph5" ), + ExampleKeys.BASIC_FLAT_PLATE_SOLID_MESH_CDB: _ExampleLocation( + directory="basic_flat_plate_example", filename="solid_mesh.cdb" + ), ExampleKeys.RACE_CAR_NOSE_ACPH5: _ExampleLocation( directory="race_car_nose", filename="race_car_nose.acph5" ), diff --git a/tests/unittests/test_imported_solid_model.py b/tests/unittests/test_imported_solid_model.py index 16e7229880..0080a57b9e 100644 --- a/tests/unittests/test_imported_solid_model.py +++ b/tests/unittests/test_imported_solid_model.py @@ -260,6 +260,8 @@ def test_import_initial_mesh(acp_instance, parent_object): format=pyacp.SolidModelImportFormat.ANSYS_H5, ) imported_solid_model.import_initial_mesh() + assert imported_solid_model.solid_mesh is not None + assert imported_solid_model.solid_mesh.element_labels == (3,) # refresh from external source with the same format imported_solid_model.refresh(out_path_h5) @@ -268,3 +270,5 @@ def test_import_initial_mesh(acp_instance, parent_object): # 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() + assert imported_solid_model.solid_mesh is not None + assert imported_solid_model.solid_mesh.element_labels == (3,)