Note
Go to the end to download the full example code.
PyMechanical to CDB shell workflow#
This example shows how to set up a workflow that uses PyMechanical to mesh the geometry and define the load case, PyACP to define a layup, PyMAPDL to solve the model, and PyDPF Composites to post-process the results.
This workflow does not suffer from the limitations of the PyACP to PyMechanical integration.
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
import ansys.dpf.core as pydpf_core
import ansys.dpf.composites as pydpf_composites
import ansys.mapdl.core as pymapdl
import ansys.mechanical.core as pymechanical
Start the ACP, Mechanical, and DPF servers. We use a ThreadPoolExecutor
to start them in parallel.
with ThreadPoolExecutor() as executor:
futures = [
executor.submit(pymechanical.launch_mechanical, batch=True),
executor.submit(pyacp.launch_acp),
executor.submit(pymapdl.launch_mapdl),
executor.submit(pydpf_composites.server_helpers.connect_to_or_start_server),
]
mechanical, acp, mapdl, dpf = (fut.result() for fut in futures)
mapdl.clear()
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_geometry = pyacp.extras.example_helpers.get_example_file(
pyacp.extras.example_helpers.ExampleKeys.CLASS40_AGDB, working_dir_path
)
Generate the mesh in PyMechanical#
Load the geometry into Mechanical, generate the mesh, and define the load case.
cdb_path_initial = working_dir_path / "model_from_mechanical.cdb"
mechanical.run_python_script(
# This script runs in the Mechanical Python environment, which uses IronPython 2.7.
textwrap.dedent(
f"""\
# Import the geometry
geometry_import = Model.GeometryImportGroup.AddGeometryImport()
import_format = Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic
import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences()
import_preferences.ProcessNamedSelections = True
import_preferences.ProcessCoordinateSystems = True
geometry_file = {str(input_geometry)!r}
geometry_import.Import(
geometry_file,
import_format,
import_preferences
)
# The thickness will be overridden by the ACP model, but is required
# for the model to be valid.
for body in Model.Geometry.GetChildren(
Ansys.Mechanical.DataModel.Enums.DataModelObjectCategory.Body, True
):
body.Thickness = Quantity(1e-6, "m")
# Define named selections at the front and back edges
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.GreaterThan
front_edge.GenerationCriteria[0].Value = Quantity('-4.6 [m]')
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.LessThan
back_edge.GenerationCriteria[0].Value = Quantity('-7.8 [m]')
back_edge.Generate()
# Create a static structural analysis, and define the boundary
# conditions (fixed support at the back edge, force at the front edge).
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
# Export the model to a CDB file
analysis.WriteInputFile({str(cdb_path_initial)!r})
"""
)
)
Set up the ACP model#
Setup basic ACP lay-up based on the CDB file.
model = acp.import_model(path=acp.upload_file(cdb_path_initial), format="ansys:cdb")
mat = model.create_material(name="mat")
mat.ply_type = "regular"
mat.engineering_constants.E1 = 1e12
mat.engineering_constants.E2 = 1e11
mat.engineering_constants.E3 = 1e11
mat.engineering_constants.G12 = 1e10
mat.engineering_constants.G23 = 1e10
mat.engineering_constants.G31 = 1e10
mat.engineering_constants.nu12 = 0.3
mat.engineering_constants.nu13 = 0.3
mat.engineering_constants.nu23 = 0.3
mat.strain_limits = pyacp.material_property_sets.ConstantStrainLimits.from_orthotropic_constants(
eXc=-0.01,
eYc=-0.01,
eZc=-0.01,
eXt=0.01,
eYt=0.01,
eZt=0.01,
eSxy=0.01,
eSyz=0.01,
eSxz=0.01,
)
corecell_81kg_5mm = model.create_fabric(name="Corecell 81kg", thickness=0.005, material=mat)
ros = model.create_rosette(name="ros", origin=(0, 0, 0))
oss = model.create_oriented_selection_set(
name="oss",
orientation_point=(-0, 0, 0),
orientation_direction=(0.0, 1, 0.0),
element_sets=[model.element_sets["All_Elements"]],
rosettes=[ros],
)
mg = model.create_modeling_group(name="group")
mg.create_modeling_ply(
name="ply",
ply_material=corecell_81kg_5mm,
oriented_selection_sets=[oss],
ply_angle=45,
number_of_layers=1,
global_ply_nr=0, # add at the end
)
mg.create_modeling_ply(
name="ply2",
ply_material=corecell_81kg_5mm,
oriented_selection_sets=[oss],
ply_angle=0,
number_of_layers=2,
global_ply_nr=0, # add at the end
)
Update and Save the ACP model#
model.update()
if acp.is_remote:
export_path = pathlib.PurePosixPath(".")
else:
export_path = working_dir_path # type: ignore
cdb_filename_out = "model_from_acp.cdb"
composite_definitions_h5_filename = "ACPCompositeDefinitions.h5"
matml_filename = "materials.xml"
model.export_analysis_model(export_path / cdb_filename_out)
model.export_shell_composite_definitions(export_path / composite_definitions_h5_filename)
model.export_materials(export_path / matml_filename)
for filename in [cdb_filename_out, composite_definitions_h5_filename, matml_filename]:
acp.download_file(export_path / filename, working_dir_path / filename)
Solve with PyMAPDL#
mapdl.clear()
Load the CDB file into PyMAPDL.
mapdl.input(str(working_dir_path / cdb_filename_out))
Solve the model.
mapdl.allsel()
mapdl.slashsolu()
mapdl.solve()
Show the displacements in postprocessing.
mapdl.post1()
mapdl.set("last")
mapdl.post_processing.plot_nodal_displacement(component="NORM")
Download the RST file for further postprocessing.
rstfile_name = f"{mapdl.jobname}.rst"
rst_file_local_path = working_dir_path / rstfile_name
mapdl.download(rstfile_name, working_dir_path)
Postprocessing with PyDPF Composites#
Specify the combined failure criterion.
max_strain = pydpf_composites.failure_criteria.MaxStrainCriterion()
cfc = pydpf_composites.failure_criteria.CombinedFailureCriterion(
name="Combined Failure Criterion",
failure_criteria=[max_strain],
)
Create the composite model and configure its input.
composite_model = pydpf_composites.composite_model.CompositeModel(
composite_files=pydpf_composites.data_sources.ContinuousFiberCompositesFiles(
rst=rst_file_local_path,
composite={
"shell": pydpf_composites.data_sources.CompositeDefinitionFiles(
definition=working_dir_path / composite_definitions_h5_filename
),
},
engineering_data=working_dir_path / matml_filename,
),
default_unit_system=pydpf_core.unit_system.unit_systems.solver_nmm,
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()