diff --git a/src/ansys/dpf/composites/_composite_model_impl.py b/src/ansys/dpf/composites/_composite_model_impl.py index 2cf82fc6b..b9484a2d6 100644 --- a/src/ansys/dpf/composites/_composite_model_impl.py +++ b/src/ansys/dpf/composites/_composite_model_impl.py @@ -32,11 +32,7 @@ import numpy as np from numpy.typing import NDArray -from ._composite_model_impl_helpers import ( - _create_material_container_helper_op, - _deprecated_composite_definition_label, - _merge_containers, -) +from ._composite_model_impl_helpers import _deprecated_composite_definition_label, _merge_containers from .composite_scope import CompositeScope from .constants import REF_SURFACE_NAME from .data_sources import ( @@ -59,7 +55,11 @@ _get_reference_surface_and_mapping_field, ) from .layup_info.material_operators import MaterialOperators, get_material_operators -from .layup_info.material_properties import MaterialProperty, get_constant_property_dict +from .layup_info.material_properties import ( + MaterialMetadata, + MaterialProperty, + get_constant_property_dict, +) from .result_definition import FailureMeasureEnum from .sampling_point import SamplingPointNew from .server_helpers import ( @@ -198,7 +198,13 @@ def material_names(self) -> dict[str, int]: This property can be used to filter analysis plies or element layers by material name. """ - helper_op = _create_material_container_helper_op(self._material_operators.material_provider) + helper_op = self._material_operators.material_container_helper_op + if helper_op is None: + raise RuntimeError( + "The used DPF server does not support the requested data. " + "Use version 2024 R1-pre0 or later." + ) + string_field = helper_op.outputs.material_names() material_ids = string_field.scoping.ids @@ -209,32 +215,46 @@ def material_names(self) -> dict[str, int]: return names @property - def ply_types(self) -> dict[str, int]: + def material_metadata(self) -> dict[int, MaterialMetadata]: """ - Ply type to DPF material ID map. + DPF material ID to metadata map of the materials. - This property can be used to filter analysis plies - or element layers by ply type. Ply type is one of - the following values ``regular``, - ``woven``, ``honeycomb_core``, - ``isotropic_homogeneous_core``, ``orthotropic_homogeneous_core``, - ``isotropic``, ``adhesive``, ``undefined``. Regular stands for uni-directional. + This data can be used to filter analysis plies + or element layers by ply type, material name etc. + + Note: ply type is only available in DPF server version 9.0 (2025 R1 pre0) and later. """ - helper_op = _create_material_container_helper_op(self._material_operators.material_provider) + helper_op = self._material_operators.material_container_helper_op + if helper_op is None: + raise RuntimeError( + "The used DPF server does not support the requested data." + "Use version 2024 R1-pre0 or later." + ) + material_name_field = helper_op.outputs.material_names() + solver_id_field = helper_op.outputs.solver_material_ids() + material_ids = material_name_field.scoping.ids if hasattr(helper_op.outputs, "ply_types"): - string_field = helper_op.outputs.ply_types() - material_ids = string_field.scoping.ids - ply_types = {} - for dpf_mat_id in material_ids: - ply_types[string_field.data[string_field.scoping.index(dpf_mat_id)]] = dpf_mat_id - - return ply_types + ply_type_field = helper_op.outputs.ply_types() else: - raise RuntimeError( - "Ply types are not available in the current server version. " - "Use at least 9.0 (2025 R1 pre0)." + ply_type_field = None + + metadata = {} + for dpf_mat_id in material_ids: + metadata[dpf_mat_id] = MaterialMetadata( + dpf_material_id=dpf_mat_id, + material_name=material_name_field.data[ + material_name_field.scoping.index(dpf_mat_id) + ], + ply_type=( + ply_type_field.data[ply_type_field.scoping.index(dpf_mat_id)] + if ply_type_field + else "unknown" + ), + solver_material_id=solver_id_field.data[solver_id_field.scoping.index(dpf_mat_id)], ) + return metadata + @_deprecated_composite_definition_label def get_mesh(self, composite_definition_label: Optional[str] = None) -> MeshedRegion: """Get the underlying DPF meshed region. diff --git a/src/ansys/dpf/composites/_composite_model_impl_2023r2.py b/src/ansys/dpf/composites/_composite_model_impl_2023r2.py index c8c0bf788..0053a8cb7 100644 --- a/src/ansys/dpf/composites/_composite_model_impl_2023r2.py +++ b/src/ansys/dpf/composites/_composite_model_impl_2023r2.py @@ -47,7 +47,11 @@ get_element_info_provider, ) from .layup_info.material_operators import MaterialOperators, get_material_operators -from .layup_info.material_properties import MaterialProperty, get_constant_property_dict +from .layup_info.material_properties import ( + MaterialMetadata, + MaterialProperty, + get_constant_property_dict, +) from .result_definition import FailureMeasureEnum, ResultDefinition, ResultDefinitionScope from .sampling_point_2023r2 import SamplingPoint2023R2 from .sampling_point_types import SamplingPoint @@ -227,10 +231,10 @@ def material_names(self) -> dict[str, int]: ) @property - def ply_types(self) -> dict[str, int]: - """Get ply types to DPF material ID map.""" + def material_metadata(self) -> dict[int, MaterialMetadata]: + """DPF Material ID to metadata map. Metadata are for example name and ply type.""" raise NotImplementedError( - "ply_types is not implemented" + "material_metadata is not implemented" " for this version of DPF. DPF server 9.0 (2025 R1 pre0)" " or later should be used instead." ) diff --git a/src/ansys/dpf/composites/_composite_model_impl_helpers.py b/src/ansys/dpf/composites/_composite_model_impl_helpers.py index 49cb3c09a..76d536543 100644 --- a/src/ansys/dpf/composites/_composite_model_impl_helpers.py +++ b/src/ansys/dpf/composites/_composite_model_impl_helpers.py @@ -27,25 +27,11 @@ from warnings import warn import ansys.dpf.core as dpf -from ansys.dpf.core import FieldsContainer, Operator +from ansys.dpf.core import FieldsContainer from .constants import FAILURE_LABEL, REF_SURFACE_NAME, TIME_LABEL, FailureOutput -def _create_material_container_helper_op(material_provider: Operator) -> Operator: - try: - helper_op = dpf.Operator("composite::materials_container_helper") - except Exception as exc: - raise RuntimeError( - f"Operator composite::materials_container_helper doesn't exist. " - f"This could be because the server version is 2024 R1-pre0. The " - f"latest preview or the unified installer can be used instead. " - f"Error: {exc}" - ) from exc - helper_op.inputs.materials_container(material_provider.outputs) - return helper_op - - def _deprecated_composite_definition_label(func: Callable[..., Any]) -> Any: """Emit a warning when the deprecated ``composite_definition_label`` is used.""" function_arg = "composite_definition_label" diff --git a/src/ansys/dpf/composites/composite_model.py b/src/ansys/dpf/composites/composite_model.py index 2708635ee..27e3398cf 100644 --- a/src/ansys/dpf/composites/composite_model.py +++ b/src/ansys/dpf/composites/composite_model.py @@ -36,7 +36,7 @@ from .failure_criteria import CombinedFailureCriterion from .layup_info import ElementInfo, LayerProperty, LayupModelContextType from .layup_info.material_operators import MaterialOperators -from .layup_info.material_properties import MaterialProperty +from .layup_info.material_properties import MaterialMetadata, MaterialProperty from .result_definition import FailureMeasureEnum from .sampling_point_types import SamplingPoint @@ -135,9 +135,9 @@ def material_names(self) -> dict[str, int]: return self._implementation.material_names @property - def ply_types(self) -> dict[str, int]: - """Get ply type to DPF material ID map.""" - return self._implementation.ply_types + def material_metadata(self) -> dict[int, MaterialMetadata]: + """PF material ID to metadata map.""" + return self._implementation.material_metadata def get_mesh(self, composite_definition_label: Optional[str] = None) -> MeshedRegion: """Get the underlying DPF meshed region. diff --git a/src/ansys/dpf/composites/layup_info/material_operators.py b/src/ansys/dpf/composites/layup_info/material_operators.py index 1e0e4ce63..d1dbf45eb 100644 --- a/src/ansys/dpf/composites/layup_info/material_operators.py +++ b/src/ansys/dpf/composites/layup_info/material_operators.py @@ -26,6 +26,8 @@ from ansys.dpf.core import DataSources, Operator +from ansys.dpf.composites.server_helpers import version_equal_or_later + __all__ = ("MaterialOperators", "get_material_operators") from ansys.dpf.composites.unit_system import UnitSystemProvider @@ -61,6 +63,14 @@ def __init__( self._material_support_provider = material_support_provider self._result_info_provider = result_info_provider + if version_equal_or_later(self._material_provider._server, "7.1"): + self._material_container_helper_op = Operator("composite::materials_container_helper") + self._material_container_helper_op.inputs.materials_container( + self._material_provider.outputs + ) + else: + self._material_container_helper_op = None + @property def result_info_provider(self) -> Operator: """Get result_info_provider.""" @@ -83,6 +93,17 @@ def material_provider(self) -> Operator: """Get material_provider.""" return self._material_provider + @property + def material_container_helper_op(self) -> Operator: + """ + Get material container helper operator. + + This operator can be used to access metadata of the materials. + Return value is None if the server version does not support this operator. + The minimum version is 2024 R1-pre0 (7.1). + """ + return self._material_container_helper_op + def get_material_operators( rst_data_source: DataSources, diff --git a/src/ansys/dpf/composites/layup_info/material_properties.py b/src/ansys/dpf/composites/layup_info/material_properties.py index d05b5d772..30e031fc6 100644 --- a/src/ansys/dpf/composites/layup_info/material_properties.py +++ b/src/ansys/dpf/composites/layup_info/material_properties.py @@ -22,6 +22,7 @@ """Helpers to get material properties.""" from collections.abc import Collection +from dataclasses import dataclass from enum import Enum from typing import Union, cast @@ -29,6 +30,7 @@ import numpy as np __all__ = ( + "MaterialMetadata", "MaterialProperty", "get_constant_property", "get_all_dpf_material_ids", @@ -209,3 +211,29 @@ def get_constant_property_dict( ) properties[dpf_material_id][material_property] = constant_property return properties + + +@dataclass(frozen=True) +class MaterialMetadata: + """ + Material metadata such as name and ply type. + + Parameters + ---------- + dpf_material_id: + Material index in the DPF materials container. + material_name: + Name of the material. Is empty if the name is not available. + ply_type: + Ply type. One of regular, woven, honeycomb_core, + isotropic_homogeneous_core, orthotropic_homogeneous_core, + isotropic, adhesive, undefined. Regular stands for uni-directional. + `unknown` is used of the ply type is not available. + solver_material_id: + Material index of the solver. + """ + + dpf_material_id: int = 0 + material_name: str = "" + ply_type: str = "unknown" + solver_material_id: int = 0 diff --git a/tests/composite_model_test.py b/tests/composite_model_test.py index f12040c62..c32423dd6 100644 --- a/tests/composite_model_test.py +++ b/tests/composite_model_test.py @@ -42,7 +42,7 @@ LayupModelContextType, get_analysis_ply_index_to_name_map, ) -from ansys.dpf.composites.layup_info.material_properties import MaterialProperty +from ansys.dpf.composites.layup_info.material_properties import MaterialMetadata, MaterialProperty from ansys.dpf.composites.result_definition import FailureMeasureEnum from ansys.dpf.composites.server_helpers import version_equal_or_later, version_older_than @@ -127,17 +127,66 @@ def test_basic_functionality_of_composite_model(dpf_server, data_files, distribu for mat_name in ref_material_names: assert mat_name in mat_names.keys() - if version_equal_or_later(dpf_server, "9.0"): - ref_ply_types = [ - "regular", - "woven", - "honeycomb_core", - "undefined", - ] - ply_types = composite_model.ply_types - assert len(ply_types) == len(ref_ply_types) - for p_type in ref_ply_types: - assert p_type in ply_types.keys() + metadata = composite_model.material_metadata + + if version_equal_or_later(dpf_server, "9.0"): + ref_metadata = { + 1: MaterialMetadata( + dpf_material_id=1, + material_name="Honeycomb", + ply_type="honeycomb_core", + solver_material_id=5, + ), + 2: MaterialMetadata( + dpf_material_id=2, + material_name="Epoxy Carbon UD (230 GPa) Prepreg", + ply_type="regular", + solver_material_id=4, + ), + 3: MaterialMetadata( + dpf_material_id=3, + material_name="Epoxy Carbon Woven (230 GPa) Wet", + ply_type="woven", + solver_material_id=3, + ), + 4: MaterialMetadata( + dpf_material_id=4, + material_name="Structural Steel", + ply_type="undefined", + solver_material_id=2, + ), + } + else: + ref_metadata = { + 1: MaterialMetadata( + dpf_material_id=1, + material_name="Honeycomb", + ply_type="unknown", + solver_material_id=5, + ), + 2: MaterialMetadata( + dpf_material_id=2, + material_name="Epoxy Carbon UD (230 GPa) Prepreg", + ply_type="unknown", + solver_material_id=4, + ), + 3: MaterialMetadata( + dpf_material_id=3, + material_name="Epoxy Carbon Woven (230 GPa) Wet", + ply_type="unknown", + solver_material_id=3, + ), + 4: MaterialMetadata( + dpf_material_id=4, + material_name="Structural Steel", + ply_type="unknown", + solver_material_id=2, + ), + } + + assert len(metadata) == len(ref_metadata) + for dpf_material_id, ref_data in ref_metadata.items(): + assert metadata[dpf_material_id] == ref_data timer.add("After getting properties")