Skip to content

Commit

Permalink
Support section data from RST (#388)
Browse files Browse the repository at this point in the history
* Import section data directly from the RST (MAPDL solution)

* Postprocessing of layered shell and solid elements of MAPDL models which are not preprocessed with ACP.

* Additional examples and documentation for the new workflow
---------

Co-authored-by: Kathy Pippert <[email protected]>
Co-authored-by: janvonrickenbach <[email protected]>
  • Loading branch information
3 people authored Nov 30, 2023
1 parent d38293f commit 6f729cd
Show file tree
Hide file tree
Showing 35 changed files with 4,481 additions and 102 deletions.
4 changes: 3 additions & 1 deletion doc/.vale.ini
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ Vocab = ANSYS
BasedOnStyles = Vale, Google

# Removing Google-specific rule - Not applicable under some circumstances
Google.Colons = NO
Google.Colons = NO

TokenIgnores = ((:py)?:(func|class|meth|attr|py):`(?:.|\n)*?`)|(<.*>)|(.. code::.*\n| .*)
1 change: 1 addition & 0 deletions doc/source/api/layup_info.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ General features to access information on the composite lay-up.
LayerProperty
LayupPropertiesProvider
LayupProperty
LayupModelContextType


Material properties
Expand Down
33 changes: 29 additions & 4 deletions doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,17 @@ to implement custom failure criteria and computation.

Provides developer installation and usage information.

The minimum set of inputs to run a postprocessing for composites is the result files of an
MAPDL solution and a material file (MATML) which was generated by Engineering Data.
Lay-up files from ACP are optional and only required for some advanced operations. Refer to
:ref:`Limitations`.

Key features
''''''''''''

Here are some key features of PyDPF Composites:

* Postprocessing of layered shell and solid elements. MAPDL models as well as models preprocessed with ACP are supported.
* Failure criteria evaluation as shown in :ref:`Composite failure analysis <sphx_glr_examples_gallery_examples_001_failure_operator_example.py>`.
* A :class:`.SamplingPoint` class for extracting and visualizing a result over the entire thickness of a laminate as shown in
:ref:`Sampling point <sphx_glr_examples_gallery_examples_002_sampling_point_example.py>`.
Expand All @@ -63,15 +69,34 @@ Here are some key features of PyDPF Composites:
* Postprocessing of homogeneous elements.


.. _limitations:

Limitations
'''''''''''
- Layered elements (section data) that have not been preprocessed with ACP are not supported.
For information on converting legacy models, see `Import of Legacy Mechanical APDL Composite Models`_
in the Ansys Help.
- Only the Mechanical APDL solver is supported.
- The following operators and features are only supported if the model was
preprocessed with ACP and if the corresponding lay-up definition file is passed to the :class:`.CompositeModel` class.

- The evaluation of the failure criteria for sandwich
(:class:`FaceSheetWrinklingCriterion <.failure_criteria.FaceSheetWrinklingCriterion>`,
:class:`ShearCrimpingCriterion <.failure_criteria.ShearCrimpingCriterion>`)
for solid elements. Layered shell elements are always supported.

- The computation of interlaminar normal stresses (s3) for layered shell elements.
Without ACP layup definitions, s3 is always zero. This also affects 3D failure criteria which use s3,
such as Puck 3D. This limitation does not affect (layered) solid elements.

- The support of variable materials and material fields. Without ACP, only the
temperature is considered for the evaluation of variable material properties.

- Global plies, and scoping by plies. Layer-wise post-processing is always supported.

- Plotting results on the reference surface of a laminate.

Note: MAPDL models that have not been preprocessed with ACP can be converted. For more
information, see `Import of Legacy Mechanical APDL Composite Models`_ in the Ansys Help.

.. _Ansys Workbench: https://download.ansys.com/Current%20Release
.. _Import of Legacy Mechanical APDL Composite Models: https://ansyshelp.ansys.com/account/secured?returnurl=/Views/Secured/corp/v231/en/acp_ug/acp_import_legacy_APDL_comp.html
.. _Import of Legacy Mechanical APDL Composite Models: https://ansyshelp.ansys.com/account/secured?returnurl=/Views/Secured/corp/v232/en/acp_ug/acp_import_legacy_APDL_comp.html
.. _Compatibility: https://dpf.docs.pyansys.com/version/stable/getting_started/compatibility.html
.. _Ansys DPF: https://dpf.docs.pyansys.com/version/stable/
2 changes: 2 additions & 0 deletions doc/source/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ By default the DPF server is started from the latest Ansys installer. To choose

* - Server version
- ansys.dpf.composites Python module version
* - 8.0 (Ansys 2024 R2 pre0)
- 0.3.0 and later
* - 7.0 (Ansys 2024 R1 pre0)
- 0.3.0 and later
* - 6.2 (Ansys 2023 R2)
Expand Down
2 changes: 2 additions & 0 deletions doc/styles/Vocab/ANSYS/accept.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ accessor
interlaminar
postprocess
Postprocessing
postprocessing
ply-wise

6 changes: 3 additions & 3 deletions examples/008_assembly_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@
irf_field.plot()

# %%
# Plot IRF
# ~~~~~~~~
# Plot IRF on the reference surface
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Plot the maximum IRF on the reference surface
if version_equal_or_later(server, "8.0"):
irf_field = output_all_elements.get_field(
Expand All @@ -72,7 +72,7 @@
# ~~~~~~~~~~~~~~~~~~~~~~~
# In the assembly, two composite definitions exist: one with a "shell" label
# and one with a "solid" label. For DPF Server versions earlier than 7.0,
# the layup properties must be queried with the correct composite definition label. The code
# the lay-up properties must be queried with the correct composite definition label. The code
# following gets element information for all layered elements.
# For DPF Server versions 7.0 and later, element information can be retrieved directly.

Expand Down
114 changes: 114 additions & 0 deletions examples/011_rst_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""
.. _rst_workflow_example:
Failure analysis of an MAPDL (RST) model
----------------------------------------
This example shows the postprocessing of an MAPDL (RST) model with layered elements that was not
preprocessed by ACP. The difference between the RST-only and ACP-based workflow is that
the section data are loaded from the RST file instead of the ACP layup file.
This happens automatically if the parameter `composite` of the
:class:`.ContinuousFiberCompositesFiles` class is not set.
The engineering data file (XML or ENGD) with the material properties is needed anyway.
Otherwise, the material properties cannot be mapped.
At the end of this example, two workflows are shown on how to create
the engineering data file based on a MAPDL model and how to set the
material UUIDs in MAPDL.
.. important::
The material UUIDs in the engineering data file must be identical
to the UUIDs in Mechanical APDL (RST file).
The postprocessing of MAPDL models is supported in 2024 R2 (DPF Server version 8.0)
and later. A few advanced features are not supported with the RST only workflow.
For more information, see :ref:`limitations`.
"""
# %%
# Set up analysis
# ~~~~~~~~~~~~~~~
# Setting up the analysis consists of loading Ansys libraries, connecting to the
# DPF server, and retrieving the example files.
#
# Load Ansys libraries.

from ansys.dpf.composites.composite_model import CompositeModel
from ansys.dpf.composites.constants import FailureOutput
from ansys.dpf.composites.example_helper import get_continuous_fiber_example_files
from ansys.dpf.composites.failure_criteria import (
CombinedFailureCriterion,
CoreFailureCriterion,
FaceSheetWrinklingCriterion,
MaxStrainCriterion,
MaxStressCriterion,
VonMisesCriterion,
)
from ansys.dpf.composites.server_helpers import connect_to_or_start_server

# %%
# Start a DPF server and copy the example files into the current working directory.
server = connect_to_or_start_server()

# %%
# Get input files (RST and material.engd but skip the ACP layup file).
composite_files_on_server = get_continuous_fiber_example_files(server, "shell", True)
print(composite_files_on_server)

# %%
# Configure combined failure criterion
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Configure the combined failure criterion.

combined_fc = CombinedFailureCriterion(
name="failure of all materials",
failure_criteria=[
MaxStrainCriterion(),
MaxStressCriterion(),
CoreFailureCriterion(),
VonMisesCriterion(vme=True, vms=False),
FaceSheetWrinklingCriterion(),
],
)

# %%
# Set up model and evaluate failures
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Set up the composite model.

composite_model = CompositeModel(composite_files_on_server, server)

# %%
# Evaluate failures for the entire model
output_all_elements = composite_model.evaluate_failure_criteria(
combined_criterion=combined_fc,
)
irf_field = output_all_elements.get_field({"failure_label": FailureOutput.FAILURE_VALUE})
irf_field.plot()

# %%
# Create and plot a sampling point
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sampling_point = composite_model.get_sampling_point(combined_criterion=combined_fc, element_id=2)
sampling_plot = sampling_point.get_result_plots(core_scale_factor=0.1)
sampling_plot.figure.show()


# %%
# Create Engineering Data file and set material UUIDs in MAPDL
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Here are two workflows for setting up the engineering data file
# and the material UUIDs in MAPDL. The material UUIDs must be set
# in MAPDL before the model is solved.
#
# With WB and Engineering Data:
# - Create an External Model system in WB and load the solver input file
# - Link the External Model with an Engineering Data system and update it
# - Save the project and copy the generated engineering data file (EngineeringData.xml)
# - For each material, look for the ``DataTransferID``, go to MAPDL and set the material
# UUIDs with the ``MP,UVID,<material index>,<value>`` command
#
# With ACP Standalone (for constant material properties only):
# - Start ACP, go to *File - Import Model* and load the solver input file (CDB)
# - Go to the Materials folder and export the engineering data file (Ansys Workbench XML)
# - For each material, look for the ``DataTransferID``, go to MAPDL and set the material
# UUID with the ``MP,UVID,<material index>,<value>`` command.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
.. _basic_example:
.. _basic_example_composite_failure_workflow:
DPF composite failure workflow
------------------------------
Expand Down
68 changes: 57 additions & 11 deletions src/ansys/dpf/composites/_composite_model_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
from .layup_info import (
ElementInfo,
LayerProperty,
LayupModelContextType,
LayupPropertiesProvider,
add_layup_info_to_mesh,
get_element_info_provider,
)
from .layup_info._layup_info import _get_layup_model_context
from .layup_info._reference_surface import (
_get_map_to_reference_surface_operator,
_get_reference_surface_and_mapping_field,
Expand Down Expand Up @@ -174,9 +176,23 @@ def __init__(
data_sources=self.data_sources,
material_operators=self.material_operators,
unit_system=self._unit_system,
rst_stream_provider=self._get_rst_streams_provider(),
)

# self._layup_provider.outputs.layup_model_context_type.get_data() does not work because
# int32 is not supported in the Python API. See bug 946754.
if version_equal_or_later(self._server, "8.0"):
self._layup_model_type = LayupModelContextType(
_get_layup_model_context(self._layup_provider)
)
else:
self._layup_model_type = (
LayupModelContextType.ACP
if len(composite_files.composite) > 0
else LayupModelContextType.NOT_AVAILABLE
)

if self._supports_reference_surface_operators():
self._reference_surface_and_mapping_field = _get_reference_surface_and_mapping_field(
data_sources=self.data_sources.composite, unit_system=self._unit_system
)
Expand All @@ -189,6 +205,7 @@ def __init__(
self._element_info_provider = get_element_info_provider(
mesh=self.get_mesh(),
stream_provider_or_data_source=self._get_rst_streams_provider(),
material_provider=self.material_operators.material_provider,
)
self._layup_properties_provider = LayupPropertiesProvider(
layup_provider=self._layup_provider, mesh=self.get_mesh()
Expand Down Expand Up @@ -246,8 +263,8 @@ def material_names(self) -> dict[str, int]:
material_ids = string_field.scoping.ids

names = {}
for mat_id in material_ids:
names[string_field.data[string_field.scoping.index(mat_id)]] = mat_id
for dpf_mat_id in material_ids:
names[string_field.data[string_field.scoping.index(dpf_mat_id)]] = dpf_mat_id

return names

Expand All @@ -274,6 +291,14 @@ def get_layup_operator(self, composite_definition_label: Optional[str] = None) -
"""
return self._layup_provider

@property
def layup_model_type(self) -> LayupModelContextType:
"""Get the context type of the lay-up model.
Type can be one of the following values: ``NOT_AVAILABLE``, ``ACP``, ``RST``, ``MIXED``.
"""
return self._layup_model_type

@_deprecated_composite_definition_label
def evaluate_failure_criteria(
self,
Expand Down Expand Up @@ -332,8 +357,6 @@ def evaluate_failure_criteria(
# is irrelevant for cases without a ply scope, we set it to False here.
write_data_for_full_element_scope = False

has_layup_provider = len(self._composite_files.composite) > 0

# configure primary scoping
scope_config = dpf.DataTree()
if time_in:
Expand All @@ -355,7 +378,9 @@ def evaluate_failure_criteria(
chunking_generator = dpf.Operator("composite::scope_generator")
chunking_generator.inputs.stream_provider(self._get_rst_streams_provider())
chunking_generator.inputs.data_tree(chunking_data_tree)
chunking_generator.inputs.data_sources(self.data_sources.composite)
if self.data_sources.composite:
chunking_generator.inputs.data_sources(self.data_sources.composite)

if element_scope_in:
element_scope = dpf.Scoping(location="elemental")
element_scope.ids = element_scope_in
Expand Down Expand Up @@ -396,7 +421,14 @@ def evaluate_failure_criteria(
self._get_rst_streams_provider()
)
evaluate_failure_criterion_per_scope_op.inputs.mesh(self.get_mesh())
evaluate_failure_criterion_per_scope_op.inputs.has_layup_provider(has_layup_provider)
if version_equal_or_later(self._server, "8.0"):
evaluate_failure_criterion_per_scope_op.inputs.layup_model_context_type(
self.layup_model_type.value
)
else:
evaluate_failure_criterion_per_scope_op.inputs.has_layup_provider(
self.layup_model_type != LayupModelContextType.NOT_AVAILABLE
)
evaluate_failure_criterion_per_scope_op.inputs.section_data_container(
self._layup_provider.outputs.section_data_container
)
Expand All @@ -421,7 +453,10 @@ def evaluate_failure_criteria(
self.material_operators.material_support_provider.outputs
)

if has_layup_provider and write_data_for_full_element_scope:
if (
self.layup_model_type != LayupModelContextType.NOT_AVAILABLE
and write_data_for_full_element_scope
):
add_default_data_op = dpf.Operator("composite::add_default_data")
add_default_data_op.inputs.requested_element_scoping(chunking_generator.outputs)
add_default_data_op.inputs.time_id(
Expand Down Expand Up @@ -454,11 +489,9 @@ def evaluate_failure_criteria(
merge_index = merge_index + 1

if merge_index == 0:
raise RuntimeError(
"No output is generated! Please check the scope (element and ply ids)."
)
raise RuntimeError("No output is generated! Check the scope (element and ply IDs).")

if version_equal_or_later(self._server, "8.0"):
if self._supports_reference_surface_operators():
self._map_to_reference_surface_operator.inputs.min_container(
min_merger.outputs.merged_fields_container()
)
Expand Down Expand Up @@ -739,3 +772,16 @@ def _first_composite_definition_label_if_only_one(self) -> str:
f"Multiple composite definition keys exist: {self.composite_definition_labels}. "
f"Specify a key explicitly."
)

# Whether the reference surface operators are available or supported by the server
def _supports_reference_surface_operators(self) -> bool:
if not version_equal_or_later(self._server, "8.0"):
return False

if (
self.layup_model_type == LayupModelContextType.ACP
or self.layup_model_type == LayupModelContextType.MIXED
):
return True

return False
Loading

0 comments on commit 6f729cd

Please sign in to comment.