Advanced PyMAPDL workflow#

This example shows how to define a composite lay-up with PyACP, solve the resulting model with PyMAPDL, and run a failure analysis with PyDPF - Composites.

Begin with an MAPDL CDB file that contains the mesh, material data, and boundary conditions. Import the file to PyACP to define the lay-up, and then export the resulting model to PyMAPDL. Once the results are available, the RST file is loaded in PyDPF - Composites for postprocessing. The additional input files (material.xml and ACPCompositeDefinitions.h5) can also be stored with PyACP and passed to PyDPF - Composites.

Import modules and start ACP#

Import the standard library and third-party dependencies.

import pathlib
import tempfile

import pyvista

Import the Ansys libraries.

import ansys.acp.core as pyacp

Launch the PyACP server and connect to it.

acp = pyacp.launch_acp()

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 = pyacp.extras.example_helpers.get_example_file(
    pyacp.extras.example_helpers.ExampleKeys.CLASS40_CDB, working_dir_path
)

Load mesh and materials from CDB file#

Load the CDB file into PyACP and set the unit system.

model = acp.import_model(path=input_file, format="ansys:cdb", unit_system=pyacp.UnitSystemType.MPA)
model
<Model with name 'ACP Lay-up Model'>

Visualize the loaded mesh.

mesh = model.mesh.to_pyvista()
mesh.plot()
02 advanced pymapdl workflow

Build Composite Lay-up#

Create the model (MPA unit system).

Materials#

mat_corecell_81kg = model.materials["1"]
mat_corecell_81kg.name = "Core Cell 81kg"
mat_corecell_81kg.ply_type = "isotropic_homogeneous_core"

mat_corecell_103kg = model.materials["2"]
mat_corecell_103kg.name = "Core Cell 103kg"
mat_corecell_103kg.ply_type = "isotropic_homogeneous_core"

mat_eglass_ud = model.materials["3"]
mat_eglass_ud.name = "E-Glass (uni-directional)"
mat_eglass_ud.ply_type = "regular"

Fabrics#

corecell_81kg_5mm = model.create_fabric(
    name="Corecell 81kg", thickness=0.005, material=mat_corecell_81kg
)
corecell_103kg_10mm = model.create_fabric(
    name="Corecell 103kg", thickness=0.01, material=mat_corecell_103kg
)
eglass_ud_02mm = model.create_fabric(name="eglass UD", thickness=0.0002, material=mat_eglass_ud)

Rosettes#

ros_deck = model.create_rosette(name="ros_deck", origin=(-5.9334, -0.0481, 1.693))
ros_hull = model.create_rosette(name="ros_hull", origin=(-5.3711, -0.0506, -0.2551))
ros_bulkhead = model.create_rosette(
    name="ros_bulkhead", origin=(-5.622, 0.0022, 0.0847), dir1=(0.0, 1.0, 0.0), dir2=(0.0, 0.0, 1.0)
)
ros_keeltower = model.create_rosette(
    name="ros_keeltower", origin=(-6.0699, -0.0502, 0.623), dir1=(0.0, 0.0, 1.0)
)

Oriented Selection Sets#

Note that the element sets are imported from the initial mesh (CBD file).

oss_deck = model.create_oriented_selection_set(
    name="oss_deck",
    orientation_point=(-5.3806, -0.0016, 1.6449),
    orientation_direction=(0.0, 0.0, -1.0),
    element_sets=[model.element_sets["DECK"]],
    rosettes=[ros_deck],
)

oss_hull = model.create_oriented_selection_set(
    name="oss_hull",
    orientation_point=(-5.12, 0.1949, -0.2487),
    orientation_direction=(0.0, 0.0, 1.0),
    element_sets=[model.element_sets["HULL_ALL"]],
    rosettes=[ros_hull],
)

oss_bulkhead = model.create_oriented_selection_set(
    name="oss_bulkhead",
    orientation_point=(-5.622, -0.0465, -0.094),
    orientation_direction=(1.0, 0.0, 0.0),
    element_sets=[model.element_sets["BULKHEAD_ALL"]],
    rosettes=[ros_bulkhead],
)

esets = [
    model.element_sets["KEELTOWER_AFT"],
    model.element_sets["KEELTOWER_FRONT"],
    model.element_sets["KEELTOWER_PORT"],
    model.element_sets["KEELTOWER_STB"],
]

oss_keeltower = model.create_oriented_selection_set(
    name="oss_keeltower",
    orientation_point=(-6.1019, 0.0001, 1.162),
    orientation_direction=(-1.0, 0.0, 0.0),
    element_sets=esets,
    rosettes=[ros_keeltower],
)

Show the orientations on the hull oriented selection set (OSS).

Note that the model must be updated before the orientations are available.

model.update()

plotter = pyvista.Plotter()
plotter.add_mesh(model.mesh.to_pyvista(), color="white")
orientation = oss_hull.elemental_data.orientation
assert orientation is not None
plotter.add_mesh(
    orientation.get_pyvista_glyphs(mesh=model.mesh, factor=0.2, culling_factor=5),
    color="blue",
)
plotter.show()
02 advanced pymapdl workflow

Modeling Plies#

def add_ply(mg, name, ply_material, angle, oss):
    return mg.create_modeling_ply(
        name=name,
        ply_material=ply_material,
        oriented_selection_sets=oss,
        ply_angle=angle,
        number_of_layers=1,
        global_ply_nr=0,  # add at the end
    )

Define plies for the hull, deck, and bulkhead.

angles = [-90.0, -60.0, -45.0 - 30.0, 0.0, 0.0, 30.0, 45.0, 60.0, 90.0]
for mg_name in ["hull", "deck", "bulkhead"]:
    mg = model.create_modeling_group(name=mg_name)
    oss_list = [model.oriented_selection_sets["oss_" + mg_name]]
    for angle in angles:
        add_ply(mg, "eglass_ud_02mm_" + str(angle), eglass_ud_02mm, angle, oss_list)
    add_ply(mg, "corecell_103kg_10mm", corecell_103kg_10mm, 0.0, oss_list)
    for angle in angles:
        add_ply(mg, "eglass_ud_02mm_" + str(angle), eglass_ud_02mm, angle, oss_list)

Add plies to the keeltower.

mg = model.create_modeling_group(name="keeltower")
oss_list = [model.oriented_selection_sets["oss_keeltower"]]
for angle in angles:
    add_ply(mg, "eglass_ud_02mm_" + str(angle), eglass_ud_02mm, angle, oss_list)

add_ply(mg, "corecell_81kg_5mm", corecell_81kg_5mm, 0.0, oss_list)

for angle in angles:
    add_ply(mg, "eglass_ud_02mm_" + str(angle), eglass_ud_02mm, angle, oss_list)

Inspect the number of modeling groups and plies.

print(len(model.modeling_groups))
print(len(model.modeling_groups["hull"].modeling_plies))
print(len(model.modeling_groups["deck"].modeling_plies))
print(len(model.modeling_groups["bulkhead"].modeling_plies))
print(len(model.modeling_groups["keeltower"].modeling_plies))
4
19
19
19
19

Show the thickness of one of the plies.

model.update()
modeling_ply = model.modeling_groups["deck"].modeling_plies["eglass_ud_02mm_0.5"]
thickness = modeling_ply.elemental_data.thickness
assert thickness is not None
thickness.get_pyvista_mesh(mesh=model.mesh).plot()
02 advanced pymapdl workflow

Show the ply offsets that are scaled by a factor of 200.

plotter = pyvista.Plotter()
plotter.add_mesh(model.mesh.to_pyvista(), color="white")
ply_offset = modeling_ply.nodal_data.ply_offset
assert ply_offset is not None
plotter.add_mesh(
    ply_offset.get_pyvista_glyphs(mesh=model.mesh, factor=200),
)
plotter.show()
02 advanced pymapdl workflow

Show the thickness of the entire lay-up.

thickness = model.elemental_data.thickness
assert thickness is not None
thickness.get_pyvista_mesh(mesh=model.mesh).plot()
02 advanced pymapdl workflow

Write out ACP Model#

acph5_filename = "class40.acph5"
cdb_filename_out = "class40_analysis_model.cdb"
composite_definition_h5_filename = "ACPCompositeDefinitions.h5"
matml_filename = "materials.xml"

Update and save the ACP model.

model.update()
model.save(working_dir_path / acph5_filename, save_cache=True)

Save the model as a CDB file for solving with PyMAPDL.

model.export_analysis_model(working_dir_path / cdb_filename_out)
# Export the shell lay-up and material file for PyDPF - Composites.
model.export_shell_composite_definitions(working_dir_path / composite_definition_h5_filename)
model.export_materials(working_dir_path / matml_filename)

Solve with PyMAPDL#

Import PyMAPDL and connect to its server.

from ansys.mapdl.core import launch_mapdl

mapdl = launch_mapdl()
mapdl.clear()

Load the CDB file into PyMAPDL.

mapdl.input(str(working_dir_path / cdb_filename_out))
'\n /INPUT FILE=    LINE=       0\n ANSYS RELEASE 11.0    UP20070125       16:39:41    03/10/2009\n   *****MAPDL VERIFICATION RUN ONLY*****\n     DO NOT USE RESULTS FOR PRODUCTION\n\n          ***** MAPDL ANALYSIS DEFINITION (PREP7) *****\n\n\n ***** ROUTINE COMPLETED *****  CP =         0.000\n\n\n'

Solve the model.

mapdl.allsel()
mapdl.slashsolu()
mapdl.solve()
*****  MAPDL SOLVE    COMMAND  *****

 *** NOTE ***                            CP =       0.000   TIME= 00:00:00
 There is no title defined for this analysis.

 *** SELECTION OF ELEMENT TECHNOLOGIES FOR APPLICABLE ELEMENTS ***
                ---GIVE SUGGESTIONS ONLY---

 ELEMENT TYPE         1 IS SHELL181. IT IS ASSOCIATED WITH ELASTOPLASTIC
 MATERIALS ONLY. KEYOPT(8) IS ALREADY SET AS SUGGESTED. KEYOPT(3)=2
 IS SUGGESTED FOR HIGHER ACCURACY OF MEMBRANE STRESSES; OTHERWISE,
 KEYOPT(3)=0 IS SUGGESTED.

 ELEMENT TYPE         2 IS BEAM188 . KEYOPT(1)=1 IS SUGGESTED FOR NON-CIRCULAR CROSS
 SECTIONS AND KEYOPT(3)=2 IS ALWAYS SUGGESTED.

 ELEMENT TYPE         2 IS BEAM188 . KEYOPT(15) IS ALREADY SET AS SUGGESTED.

 ELEMENT TYPE         3 IS SHELL181. IT IS ASSOCIATED WITH ELASTOPLASTIC
 MATERIALS ONLY. KEYOPT(8) IS ALREADY SET AS SUGGESTED. KEYOPT(3)=2
 IS SUGGESTED FOR HIGHER ACCURACY OF MEMBRANE STRESSES; OTHERWISE,
 KEYOPT(3)=0 IS SUGGESTED.


   *****MAPDL VERIFICATION RUN ONLY*****
     DO NOT USE RESULTS FOR PRODUCTION

                       S O L U T I O N   O P T I O N S

   PROBLEM DIMENSIONALITY. . . . . . . . . . . . .3-D
   DEGREES OF FREEDOM. . . . . . UX   UY   UZ   ROTX ROTY ROTZ
   ANALYSIS TYPE . . . . . . . . . . . . . . . . .STATIC (STEADY-STATE)
   GLOBALLY ASSEMBLED MATRIX . . . . . . . . . . .SYMMETRIC

 *** NOTE ***                            CP =       0.000   TIME= 00:00:00
 Poisson's ratio PR input has been converted to NU input.

 *** NOTE ***                            CP =       0.000   TIME= 00:00:00
 Present time 0 is less than or equal to the previous time.  Time will
 default to 1.

 *** NOTE ***                            CP =       0.000   TIME= 00:00:00
 The conditions for direct assembly have been met.  No .emat or .erot
 files will be produced.

                      L O A D   S T E P   O P T I O N S

   LOAD STEP NUMBER. . . . . . . . . . . . . . . .     1
   TIME AT END OF THE LOAD STEP. . . . . . . . . .  1.0000
   NUMBER OF SUBSTEPS. . . . . . . . . . . . . . .     1
   STEP CHANGE BOUNDARY CONDITIONS . . . . . . . .    NO
   COPY INTEGRATION POINT VALUES TO NODE . . . . .   YES
   PRINT OUTPUT CONTROLS . . . . . . . . . . . . .NO PRINTOUT
   DATABASE OUTPUT CONTROLS. . . . . . . . . . . .ALL DATA WRITTEN
                                                  FOR THE LAST SUBSTEP


 *** NOTE ***                            CP =       0.000   TIME= 00:00:00
 Predictor is ON by default for structural elements with rotational
 degrees of freedom.  Use the PRED,OFF command to turn the predictor
 OFF if it adversely affects the convergence.



                         ***********  PRECISE MASS SUMMARY  ***********

   TOTAL RIGID BODY MASS MATRIX ABOUT ORIGIN
               Translational mass               |   Coupled translational/rotational mass
         324.96       0.46544E-18   0.14304E-17 |   -0.11582E-16    211.44      -0.36088E-02
        0.46544E-18    324.96      -0.49693E-17 |    -211.44      -0.96175E-17   -2015.3
        0.14304E-17  -0.49693E-17    324.96     |    0.36088E-02    2015.3       0.20783E-16
     ------------------------------------------ | ------------------------------------------
                                                |         Rotational mass (inertia)
                                                |     759.66       0.20436E-01    1316.9
                                                |    0.20436E-01    13054.      -0.32469E-02
                                                |     1316.9      -0.32469E-02    13218.

   TOTAL MASS =  324.96
     The mass principal axes coincide with the global Cartesian axes

   CENTER OF MASS (X,Y,Z)=   -6.2017       0.11105E-04   0.65067

   TOTAL INERTIA ABOUT CENTER OF MASS
         622.08      -0.19444E-02    5.6093
       -0.19444E-02    418.20      -0.89877E-03
         5.6093      -0.89877E-03    719.59

   PRINCIPAL INERTIAS =    621.76        418.20        719.92
   ORIENTATION VECTORS OF THE INERTIA PRINCIPAL AXES IN GLOBAL CARTESIAN
     ( 0.998,-0.000,-0.057) ( 0.000, 1.000, 0.000) ( 0.057,-0.000, 0.998)


  *** MASS SUMMARY BY ELEMENT TYPE ***

  TYPE      MASS
     1   322.570
     2   1.65744
     3  0.729815

 Range of element maximum matrix coefficients in global coordinates
 Maximum = 104467551 at element 0.
 Minimum = 31671440.9 at element 0.

   *** ELEMENT MATRIX FORMULATION TIMES
     TYPE    NUMBER   ENAME      TOTAL CP  AVE CP

        1      3973  SHELL181      0.000   0.000000
        2        88  BEAM188       0.000   0.000000
        3        22  SHELL181      0.000   0.000000
 Time at end of element matrix formulation CP = 0.

 SPARSE MATRIX DIRECT SOLVER.
  Number of equations =       24096,    Maximum wavefront =      0
  Memory available (MB) =    0.0    ,  Memory required (MB) =    0.0

 Sparse solver maximum pivot= 0 at node 0 .
 Sparse solver minimum pivot= 0 at node 0 .
 Sparse solver minimum pivot in absolute value= 0 at node 0 .

   *** ELEMENT RESULT CALCULATION TIMES
     TYPE    NUMBER   ENAME      TOTAL CP  AVE CP

        1      3973  SHELL181      0.000   0.000000
        2        88  BEAM188       0.000   0.000000
        3        22  SHELL181      0.000   0.000000

   *** NODAL LOAD CALCULATION TIMES
     TYPE    NUMBER   ENAME      TOTAL CP  AVE CP

        1      3973  SHELL181      0.000   0.000000
        2        88  BEAM188       0.000   0.000000
        3        22  SHELL181      0.000   0.000000
 *** LOAD STEP     1   SUBSTEP     1  COMPLETED.    CUM ITER =      1
 *** TIME =   1.00000         TIME INC =   1.00000      NEW TRIANG MATRIX

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"
mapdl.download(rstfile_name, working_dir_path)
02 advanced pymapdl workflow
['file.rst']

Postprocessing with PyDPF - Composites#

To postprocess the results, you must configure the imports, connect to the PyDPF - Composites server, and load its plugin.

from ansys.dpf.composites.composite_model import CompositeModel
from ansys.dpf.composites.constants import FailureOutput
from ansys.dpf.composites.data_sources import (
    CompositeDefinitionFiles,
    ContinuousFiberCompositesFiles,
)
from ansys.dpf.composites.failure_criteria import (
    CombinedFailureCriterion,
    CoreFailureCriterion,
    MaxStrainCriterion,
    MaxStressCriterion,
)
from ansys.dpf.composites.server_helpers import connect_to_or_start_server
from ansys.dpf.core.unit_system import unit_systems

Connect to the server. The connect_to_or_start_server function automatically loads the composites plugin.

dpf_server = connect_to_or_start_server()

Specify the combined failure criterion.

max_strain = MaxStrainCriterion()
max_stress = MaxStressCriterion()
core_failure = CoreFailureCriterion()

cfc = CombinedFailureCriterion(
    name="Combined Failure Criterion",
    failure_criteria=[max_strain, max_stress, core_failure],
)

Create the composite model and configure its input.

composite_model = CompositeModel(
    composite_files=ContinuousFiberCompositesFiles(
        rst=working_dir_path / rstfile_name,
        composite={
            "shell": CompositeDefinitionFiles(
                definition=working_dir_path / composite_definition_h5_filename
            ),
        },
        engineering_data=working_dir_path / matml_filename,
    ),
    default_unit_system=unit_systems.solver_nmm,
    server=dpf_server,
)

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": FailureOutput.FAILURE_VALUE})
irf_field.plot()

# Close MAPDL instance
mapdl.exit()
02 advanced pymapdl workflow

Total running time of the script: (0 minutes 19.949 seconds)

Gallery generated by Sphinx-Gallery