Note
Go to the end to download the full example code.
CDB to PyMechanical shell workflow#
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.
Warning
The PyACP / PyMechanical integration is still experimental. Refer to the limitations section for more information.
Note
Outputs and plots for this example are not shown in the rendered online documentation. To see the outputs and plots, run the example script or Jupyter notebook locally.
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 set_plot_theme
from ansys.acp.core.extras import example_helpers
import ansys.dpf.composites as pydpf_composites
import ansys.mechanical.core as pymechanical
Set the plot theme for the example. This is optional, and ensures that you get the same plot style (theme, color map, etc.) as in the online documentation.
set_plot_theme()
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")
model.unit_system
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=1e-4)
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 View model in ACP GUI.
pyacp.print_model(model)
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)
Import mesh, materials and plies into Mechanical#
Import geometry, mesh, and named selections into Mechanical
pyacp.mechanical_integration_helpers.import_acp_mesh_from_cdb(
mechanical=mechanical, cdb_path=working_dir_path / cdb_filename
)
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(100, "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()