From cbe936bf01319f8161cdc97f887ee098d3556f6c Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Thu, 21 Nov 2024 10:28:37 +0100 Subject: [PATCH 1/3] Add CDB to PyMechanical example Add an example which loads the CDB file and ACPCompositeDefinitions.h5 into PyMechanical (after running PyACP), and then runs the analysis inside PyMechanical. PyDPF Composites is used for post-processing. --- ..._06-cdb-to-pymechanical-workflow_thumb.png | Bin 0 -> 28417 bytes .../06-cdb-to-pymechanical-workflow.py | 305 ++++++++++++++++++ .../core/mechanical_integration_helpers.py | 4 +- 3 files changed, 307 insertions(+), 2 deletions(-) create mode 100644 doc/source/_static/gallery_thumbnails/sphx_glr_06-cdb-to-pymechanical-workflow_thumb.png create mode 100644 examples/workflows/06-cdb-to-pymechanical-workflow.py diff --git a/doc/source/_static/gallery_thumbnails/sphx_glr_06-cdb-to-pymechanical-workflow_thumb.png b/doc/source/_static/gallery_thumbnails/sphx_glr_06-cdb-to-pymechanical-workflow_thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..ce181ec59d9c6b50ff7269132b17d6f49ceb45af GIT binary patch literal 28417 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 MsmWGKfBO3W06jWuMF0Q* literal 0 HcmV?d00001 diff --git a/examples/workflows/06-cdb-to-pymechanical-workflow.py b/examples/workflows/06-cdb-to-pymechanical-workflow.py new file mode 100644 index 0000000000..8d8250bc2f --- /dev/null +++ b/examples/workflows/06-cdb-to-pymechanical-workflow.py @@ -0,0 +1,305 @@ +# 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. + + +""" + +.. _cdb_to_pymechanical_example: + +CDB to PyMechanical shell workflow +================================== + +This example shows how to ... + +""" + + +# %% +# Import modules and start the Ansys products +# ------------------------------------------- + + +# %% +# Import the standard library and third-party dependencies. + +from concurrent.futures import ThreadPoolExecutor +import pathlib +import tempfile +import textwrap + +# %% +# Import PyACP, PyMechanical, and PyDPF Composites. + +# isort: off +import ansys.acp.core as pyacp +from ansys.acp.core.extras import example_helpers +import ansys.dpf.composites as pydpf_composites +import ansys.mechanical.core as pymechanical + +# sphinx_gallery_thumbnail_path = '_static/gallery_thumbnails/sphx_glr_06-cdb-to-pymechanical-workflow_thumb.png' + +# %% +# Start the ACP, Mechanical, and DPF servers. We use a ``ThreadPoolExecutor`` +# to start them in parallel. +with ThreadPoolExecutor() as executor: + futures = [ + executor.submit(pyacp.launch_acp), + executor.submit(pymechanical.launch_mechanical, batch=True), + executor.submit(pydpf_composites.server_helpers.connect_to_or_start_server), + ] + acp, mechanical, dpf = (fut.result() for fut in futures) + +# %% +# Get example input files +# ----------------------- +# +# Create a temporary working directory, and download the example input files +# to this directory. + +working_dir = tempfile.TemporaryDirectory() +working_dir_path = pathlib.Path(working_dir.name) +input_file = example_helpers.get_example_file( + example_helpers.ExampleKeys.BASIC_FLAT_PLATE_DAT, working_dir_path +) + +# %% +# Set up the ACP model +# -------------------- +# +# Setup basic ACP lay-up based on the CDB file. + + +model = acp.import_model(path=input_file, format="ansys:cdb") + +# %% +# Visualize the loaded mesh. +mesh = model.mesh.to_pyvista() +mesh.plot(show_edges=True) + + +# %% +# Define the composite lay-up +# --------------------------- +# +# Create an orthotropic material and fabric including strain limits, which are later +# used to postprocess the simulation. +engineering_constants = ( + pyacp.material_property_sets.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 + ) +) + +strain_limit = 0.01 +strain_limits = pyacp.material_property_sets.ConstantStrainLimits.from_orthotropic_constants( + eXc=-strain_limit, + eYc=-strain_limit, + eZc=-strain_limit, + eXt=strain_limit, + eYt=strain_limit, + eZt=strain_limit, + eSxy=strain_limit, + eSyz=strain_limit, + eSxz=strain_limit, +) + +ud_material = model.create_material( + name="UD", + ply_type=pyacp.PlyType.REGULAR, + engineering_constants=engineering_constants, + strain_limits=strain_limits, +) + +fabric = model.create_fabric(name="UD", material=ud_material, thickness=0.1) + + +# %% +# Define a rosette and oriented selection set. Plot the orientation. +rosette = model.create_rosette(origin=(0.0, 0.0, 0.0), dir1=(1.0, 0.0, 0.0), dir2=(0.0, 0.0, 1.0)) + +oss = model.create_oriented_selection_set( + name="oss", + orientation_point=(0.0, 0.0, 0.0), + orientation_direction=(0.0, 1.0, 0), + element_sets=[model.element_sets["All_Elements"]], + rosettes=[rosette], +) + +model.update() + +plotter = pyacp.get_directions_plotter(model=model, components=[oss.elemental_data.orientation]) +plotter.show() + + +# %% +# Create various plies with different angles and add them to a modeling group. +modeling_group = model.create_modeling_group(name="modeling_group") +angles = [0, 45, -45, 45, -45, 0] +for idx, angle in enumerate(angles): + modeling_group.create_modeling_ply( + name=f"ply_{idx}_{angle}_{fabric.name}", + ply_angle=angle, + ply_material=fabric, + oriented_selection_sets=[oss], + ) + +model.update() + + +# %% +# Show the fiber directions of a specific ply. +modeling_ply = model.modeling_groups["modeling_group"].modeling_plies["ply_4_-45_UD"] + + +fiber_direction = modeling_ply.elemental_data.fiber_direction +assert fiber_direction is not None +plotter = pyacp.get_directions_plotter( + model=model, + components=[fiber_direction], +) + +plotter.show() + + +# %% +# For a quick overview, print the model tree. Note that +# the model can also be opened in the ACP GUI. For more +# information, see :ref:`view_the_model_in_the_acp_gui`. +pyacp.print_model(model) + +# %% +# Save the ACP model +# ------------------ + +composite_definitions_h5_filename = "ACPCompositeDefinitions.h5" +matml_filename = "materials.xml" + +model.export_shell_composite_definitions(working_dir_path / composite_definitions_h5_filename) +model.export_materials(working_dir_path / matml_filename) + + +# %% +# Import mesh, materials and plies into Mechanical +# ------------------------------------------------ +# +# Import geometry, mesh, and named selections into Mechanical + + +# TODO: rename 'import_acp_solid_mesh'? Here it is used with a shell mesh. +pyacp.mechanical_integration_helpers.import_acp_solid_mesh( + mechanical=mechanical, cdb_path=working_dir_path / input_file +) + + +# %% +# Import materials into Mechanical + +mechanical.run_python_script(f"Model.Materials.Import({str(working_dir_path / matml_filename)!r})") + +# %% +# Import plies into Mechanical + +pyacp.mechanical_integration_helpers.import_acp_composite_definitions( + mechanical=mechanical, path=working_dir_path / composite_definitions_h5_filename +) + +# %% +# Set boundary condition and solve +# --------------------------------- +# + +mechanical.run_python_script( + textwrap.dedent( + """\ + front_edge = Model.AddNamedSelection() + front_edge.Name = "Front Edge" + front_edge.ScopingMethod = GeometryDefineByType.Worksheet + + front_edge.GenerationCriteria.Add(None) + front_edge.GenerationCriteria[0].EntityType = SelectionType.GeoEdge + front_edge.GenerationCriteria[0].Criterion = SelectionCriterionType.LocationX + front_edge.GenerationCriteria[0].Operator = SelectionOperatorType.Largest + front_edge.Generate() + + back_edge = Model.AddNamedSelection() + back_edge.Name = "Back Edge" + back_edge.ScopingMethod = GeometryDefineByType.Worksheet + + back_edge.GenerationCriteria.Add(None) + back_edge.GenerationCriteria[0].EntityType = SelectionType.GeoEdge + back_edge.GenerationCriteria[0].Criterion = SelectionCriterionType.LocationX + back_edge.GenerationCriteria[0].Operator = SelectionOperatorType.Smallest + back_edge.Generate() + + analysis = Model.AddStaticStructuralAnalysis() + + fixed_support = analysis.AddFixedSupport() + fixed_support.Location = back_edge + + force = analysis.AddForce() + force.DefineBy = LoadDefineBy.Components + force.XComponent.Output.SetDiscreteValue(0, Quantity(1e6, "N")) + force.Location = front_edge + + analysis.Solution.Solve(True) + """ + ) +) + + +rst_file = [filename for filename in mechanical.list_files() if filename.endswith(".rst")][0] +matml_out = [filename for filename in mechanical.list_files() if filename.endswith("MatML.xml")][0] + +# %% +# Postprocess results +# ------------------- +# +# Evaluate the failure criteria using the PyDPF Composites. + + +max_strain = pydpf_composites.failure_criteria.MaxStrainCriterion() +cfc = pydpf_composites.failure_criteria.CombinedFailureCriterion( + name="Combined Failure Criterion", + failure_criteria=[max_strain], +) + +composite_model = pydpf_composites.composite_model.CompositeModel( + composite_files=pydpf_composites.data_sources.ContinuousFiberCompositesFiles( + rst=rst_file, + composite={ + "shell": pydpf_composites.data_sources.CompositeDefinitionFiles( + definition=working_dir_path / composite_definitions_h5_filename + ), + }, + engineering_data=working_dir_path / matml_out, + ), + server=dpf, +) + +# Evaluate the failure criteria +output_all_elements = composite_model.evaluate_failure_criteria(cfc) + +# Query and plot the results +irf_field = output_all_elements.get_field( + {"failure_label": pydpf_composites.constants.FailureOutput.FAILURE_VALUE} +) + +irf_field.plot() diff --git a/src/ansys/acp/core/mechanical_integration_helpers.py b/src/ansys/acp/core/mechanical_integration_helpers.py index f3de0b6bd4..1054f9e190 100644 --- a/src/ansys/acp/core/mechanical_integration_helpers.py +++ b/src/ansys/acp/core/mechanical_integration_helpers.py @@ -87,8 +87,8 @@ def import_acp_solid_mesh(*, mechanical: "pymechanical.Mechanical", cdb_path: PA """ cdb_path = pathlib.Path(cdb_path) - if cdb_path.suffix != ".cdb": - raise ValueError(f"The CDB file extension must be '.cdb', not '{cdb_path.suffix}'.") + # if cdb_path.suffix != ".cdb": + # raise ValueError(f"The CDB file extension must be '.cdb', not '{cdb_path.suffix}'.") cdb_path_str = str(cdb_path) mechanical.run_python_script( From 57f9d2cd8b9052a1e7161ec8a53514b0e38da464 Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 26 Nov 2024 16:12:06 +0100 Subject: [PATCH 2/3] Add description --- examples/workflows/06-cdb-to-pymechanical-workflow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/workflows/06-cdb-to-pymechanical-workflow.py b/examples/workflows/06-cdb-to-pymechanical-workflow.py index 8d8250bc2f..f991653d42 100644 --- a/examples/workflows/06-cdb-to-pymechanical-workflow.py +++ b/examples/workflows/06-cdb-to-pymechanical-workflow.py @@ -28,7 +28,9 @@ CDB to PyMechanical shell workflow ================================== -This example shows how to ... +This example shows how to define a composite lay-up in PyACP based on a mesh +from a CDB file, import the model into PyMechanical for defining the load and +boundary conditions, and run a failure analysis with PyDPF Composites. """ From d2e564e13063de91490e8de6d938679cc16fc79e Mon Sep 17 00:00:00 2001 From: Dominik Gresch Date: Tue, 26 Nov 2024 16:47:30 +0100 Subject: [PATCH 3/3] Rename mesh import helper function --- .../api/mechanical_integration_helpers.rst | 2 +- .../workflows/04-pymechanical-solid-workflow.py | 2 +- .../workflows/06-cdb-to-pymechanical-workflow.py | 7 ++++--- .../acp/core/mechanical_integration_helpers.py | 16 ++++++++-------- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/doc/source/api/mechanical_integration_helpers.rst b/doc/source/api/mechanical_integration_helpers.rst index 4732b11628..784725952e 100644 --- a/doc/source/api/mechanical_integration_helpers.rst +++ b/doc/source/api/mechanical_integration_helpers.rst @@ -22,4 +22,4 @@ Mechanical integration helpers export_mesh_for_acp import_acp_composite_definitions - import_acp_solid_mesh + import_acp_mesh_from_cdb diff --git a/examples/workflows/04-pymechanical-solid-workflow.py b/examples/workflows/04-pymechanical-solid-workflow.py index 78fe3785c5..749ac96d0c 100644 --- a/examples/workflows/04-pymechanical-solid-workflow.py +++ b/examples/workflows/04-pymechanical-solid-workflow.py @@ -262,7 +262,7 @@ # # Import geometry, mesh, and named selections into Mechanical -pyacp.mechanical_integration_helpers.import_acp_solid_mesh( +pyacp.mechanical_integration_helpers.import_acp_mesh_from_cdb( mechanical=mechanical_solid_model, cdb_path=working_dir_path / solid_model_cdb_file ) diff --git a/examples/workflows/06-cdb-to-pymechanical-workflow.py b/examples/workflows/06-cdb-to-pymechanical-workflow.py index f991653d42..32257d5de7 100644 --- a/examples/workflows/06-cdb-to-pymechanical-workflow.py +++ b/examples/workflows/06-cdb-to-pymechanical-workflow.py @@ -191,9 +191,11 @@ # Save the ACP model # ------------------ +cdb_filename = "model.cdb" composite_definitions_h5_filename = "ACPCompositeDefinitions.h5" matml_filename = "materials.xml" +model.export_analysis_model(working_dir_path / cdb_filename) model.export_shell_composite_definitions(working_dir_path / composite_definitions_h5_filename) model.export_materials(working_dir_path / matml_filename) @@ -205,9 +207,8 @@ # Import geometry, mesh, and named selections into Mechanical -# TODO: rename 'import_acp_solid_mesh'? Here it is used with a shell mesh. -pyacp.mechanical_integration_helpers.import_acp_solid_mesh( - mechanical=mechanical, cdb_path=working_dir_path / input_file +pyacp.mechanical_integration_helpers.import_acp_mesh_from_cdb( + mechanical=mechanical, cdb_path=working_dir_path / cdb_filename ) diff --git a/src/ansys/acp/core/mechanical_integration_helpers.py b/src/ansys/acp/core/mechanical_integration_helpers.py index 1054f9e190..319d1b657d 100644 --- a/src/ansys/acp/core/mechanical_integration_helpers.py +++ b/src/ansys/acp/core/mechanical_integration_helpers.py @@ -35,7 +35,7 @@ __all__ = [ "export_mesh_for_acp", "import_acp_composite_definitions", - "import_acp_solid_mesh", + "import_acp_mesh_from_cdb", ] @@ -67,11 +67,11 @@ def export_mesh_for_acp(*, mechanical: "pymechanical.Mechanical", path: PATH) -> ) -def import_acp_solid_mesh(*, mechanical: "pymechanical.Mechanical", cdb_path: PATH) -> None: - """Import an ACP solid model into Mechanical. +def import_acp_mesh_from_cdb(*, mechanical: "pymechanical.Mechanical", cdb_path: PATH) -> None: + """Import an ACP CDB mesh into Mechanical. - Import a solid mesh in CDB format into Mechanical. This function does not - import the ACP layup definition, use :func:`import_acp_composite_definitions` + Import a mesh exported from ACP in CDB format into Mechanical. This function + does not import the ACP layup definition, use :func:`import_acp_composite_definitions` for this purpose. .. warning:: @@ -87,8 +87,8 @@ def import_acp_solid_mesh(*, mechanical: "pymechanical.Mechanical", cdb_path: PA """ cdb_path = pathlib.Path(cdb_path) - # if cdb_path.suffix != ".cdb": - # raise ValueError(f"The CDB file extension must be '.cdb', not '{cdb_path.suffix}'.") + if cdb_path.suffix != ".cdb": + raise ValueError(f"The CDB file extension must be '.cdb', not '{cdb_path.suffix}'.") cdb_path_str = str(cdb_path) mechanical.run_python_script( @@ -126,7 +126,7 @@ def import_acp_composite_definitions(*, mechanical: "pymechanical.Mechanical", p Imports the composite layup defined in ACP into Mechanical, as Imported Plies. - This function does not import the solid mesh, use :func:`import_acp_solid_mesh` + This function does not import the solid mesh, use :func:`import_acp_mesh_from_cdb` for this purpose. Parameters