Skip to content

Commit e6cea95

Browse files
roosrePipKat
andauthored
Fix bug while processing ACP's imported solid models (#469)
* Fix bug while processing analysis plies of imported solid models. * update unit test --------- Co-authored-by: Kathy Pippert <[email protected]>
1 parent 77ffadc commit e6cea95

14 files changed

+2452
-13
lines changed

examples/010_harmonic_example.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
import matplotlib.pyplot as plt
5151

5252
from ansys.dpf.composites.composite_model import CompositeModel
53-
from ansys.dpf.composites.constants import FailureOutput
53+
from ansys.dpf.composites.constants import FAILURE_LABEL, FailureOutput
5454
from ansys.dpf.composites.example_helper import get_continuous_fiber_example_files
5555
from ansys.dpf.composites.failure_criteria import (
5656
CombinedFailureCriterion,
@@ -119,7 +119,6 @@
119119
# This is a bit confusing, but DPF uses the same label for Frequency and Time
120120
FREQ_LABEL = "time"
121121
PHASE_LABEL = "phase"
122-
FAILURE_LABEL = "failure_label"
123122
all_phases_and_freqs_failure_value_fc = dpf.FieldsContainer()
124123
all_phases_and_freqs_failure_value_fc.labels = [FREQ_LABEL, PHASE_LABEL]
125124

src/ansys/dpf/composites/layup_info/_layup_info.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,14 +324,30 @@ def get_analysis_ply_index_to_name_map(
324324
----------
325325
mesh
326326
DPF Meshed region enriched with lay-up information
327+
328+
.. note::
329+
330+
Analysis plies of ACP's imported solid model that are linked only
331+
to homogeneous elements are currently skipped.
327332
"""
328333
analysis_ply_name_to_index_map = {}
329334
with mesh.property_field("layer_to_analysis_ply").as_local_field() as local_field:
335+
element_ids = local_field.scoping.ids
330336
for analysis_ply_name in get_all_analysis_ply_names(mesh):
331337
analysis_ply_property_field = _get_analysis_ply(
332338
mesh, analysis_ply_name, skip_check=True
333339
)
334340
first_element_id = analysis_ply_property_field.scoping.id(0)
341+
# analysis plies which represent a filler ply are ignored because
342+
# they are linked to homogeneous elements only. So, they are not
343+
# part of layer_to_analysis_ply. This filler plies can occur in
344+
# imported solid models.
345+
# The analysis ply indices can be retrieved from
346+
# analysis_ply_property_field as soon as the
347+
# properties of PropertyField are available in Python.
348+
if first_element_id not in element_ids:
349+
continue
350+
335351
analysis_ply_indices: list[int] = local_field.get_entity_data_by_id(first_element_id)
336352

337353
layer_index = analysis_ply_property_field.get_entity_data(0)[0]

src/ansys/dpf/composites/server_helpers/_versions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class _DpfVersionInfo:
5353
"9.0": _DpfVersionInfo(
5454
"9.0",
5555
"2025 R1 pre 0",
56-
"DPF Composites: exposure of ply type.",
56+
"DPF Composites: exposure of ply type and support of imported solid models.",
5757
),
5858
}
5959

tests/composite_model_scoping_test.py

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import pytest
2727

2828
from ansys.dpf.composites.composite_model import CompositeModel, CompositeScope
29-
from ansys.dpf.composites.constants import FailureOutput
29+
from ansys.dpf.composites.constants import FAILURE_LABEL, FailureOutput
3030
from ansys.dpf.composites.data_sources import get_composite_files_from_workbench_result_folder
3131
from ansys.dpf.composites.failure_criteria import (
3232
CombinedFailureCriterion,
@@ -35,7 +35,7 @@
3535
MaxStressCriterion,
3636
)
3737
from ansys.dpf.composites.layup_info import get_all_analysis_ply_names
38-
from ansys.dpf.composites.server_helpers._versions import version_older_than
38+
from ansys.dpf.composites.server_helpers._versions import version_equal_or_later, version_older_than
3939

4040
from .helper import get_basic_shell_files
4141

@@ -49,13 +49,13 @@ def test_composite_model_element_scope(dpf_server, data_files):
4949

5050
composite_scope = CompositeScope(elements=[1, 3])
5151
failure_container = composite_model.evaluate_failure_criteria(cfc, composite_scope)
52-
irfs = failure_container.get_field({"failure_label": FailureOutput.FAILURE_VALUE})
52+
irfs = failure_container.get_field({FAILURE_LABEL: FailureOutput.FAILURE_VALUE})
5353
min_id = irfs.scoping.ids[np.argmin(irfs.data)]
5454
max_id = irfs.scoping.ids[np.argmax(irfs.data)]
5555

5656
composite_scope = CompositeScope(elements=[min_id, max_id])
5757
max_container = composite_model.evaluate_failure_criteria(cfc, composite_scope)
58-
max_irfs = max_container.get_field({"failure_label": FailureOutput.FAILURE_VALUE})
58+
max_irfs = max_container.get_field({FAILURE_LABEL: FailureOutput.FAILURE_VALUE})
5959
assert len(max_irfs.data) == 2
6060
assert max_irfs.get_entity_data_by_id(min_id)[0] == pytest.approx(min(irfs.data), 1e-8)
6161
assert max_irfs.get_entity_data_by_id(max_id)[0] == pytest.approx(max(irfs.data), 1e-8)
@@ -78,7 +78,7 @@ def test_composite_model_named_selection_scope(dpf_server, data_files, distribut
7878

7979
scope = CompositeScope(named_selections=[ns_name])
8080
failure_container = composite_model.evaluate_failure_criteria(cfc, scope)
81-
irfs = failure_container.get_field({"failure_label": FailureOutput.FAILURE_VALUE})
81+
irfs = failure_container.get_field({FAILURE_LABEL: FailureOutput.FAILURE_VALUE})
8282
assert len(irfs.data) == 2
8383
assert irfs.get_entity_data_by_id(2) == pytest.approx(1.4792790331384016, 1e-8)
8484
assert irfs.get_entity_data_by_id(3) == pytest.approx(1.3673715033617213, 1e-8)
@@ -118,8 +118,8 @@ def test_composite_model_ply_scope(dpf_server):
118118

119119
scope = CompositeScope(plies=ply_ids)
120120
failure_container = composite_model.evaluate_failure_criteria(cfc, scope)
121-
irfs = failure_container.get_field({"failure_label": FailureOutput.FAILURE_VALUE})
122-
modes = failure_container.get_field({"failure_label": FailureOutput.FAILURE_MODE})
121+
irfs = failure_container.get_field({FAILURE_LABEL: FailureOutput.FAILURE_VALUE})
122+
modes = failure_container.get_field({FAILURE_LABEL: FailureOutput.FAILURE_MODE})
123123

124124
if version_older_than(dpf_server, "7.0"):
125125
# the old implementation did not allow to distinguish between plies of the different parts.
@@ -194,8 +194,8 @@ def test_composite_model_named_selection_and_ply_scope(dpf_server, data_files, d
194194

195195
scope = CompositeScope(named_selections=[ns_name], plies=ply_ids)
196196
failure_container = composite_model.evaluate_failure_criteria(cfc, scope)
197-
irfs = failure_container.get_field({"failure_label": FailureOutput.FAILURE_VALUE})
198-
modes = failure_container.get_field({"failure_label": FailureOutput.FAILURE_MODE})
197+
irfs = failure_container.get_field({FAILURE_LABEL: FailureOutput.FAILURE_VALUE})
198+
modes = failure_container.get_field({FAILURE_LABEL: FailureOutput.FAILURE_MODE})
199199
assert len(irfs.data) == 2
200200
assert len(modes.data) == 2
201201
expected_irfs_by_id = {2: 0.49282684, 3: 0.32568454}
@@ -230,6 +230,53 @@ def test_composite_model_time_scope(dpf_server):
230230
for time, expected_max_irf in time_id_and_expected_max_irf.items():
231231
scope = CompositeScope(time=time)
232232
failure_container = composite_model.evaluate_failure_criteria(cfc, scope)
233-
irfs = failure_container.get_field({"failure_label": FailureOutput.FAILURE_VALUE})
233+
irfs = failure_container.get_field({FAILURE_LABEL: FailureOutput.FAILURE_VALUE})
234234
assert len(irfs.data) == 4
235235
assert max(irfs.data) == pytest.approx(expected_max_irf, abs=1e-6)
236+
237+
238+
def test_ply_wise_scoping_in_assembly_with_imported_solid_model(dpf_server):
239+
"""Ensure that the ply-wise scoping works in combination with the reference surface plot."""
240+
if version_older_than(dpf_server, "8.0"):
241+
pytest.xfail("Not supported because of limitations in the handling of assemblies.")
242+
243+
result_folder = pathlib.Path(__file__).parent / "data" / "assembly_imported_solid_model"
244+
composite_files = get_composite_files_from_workbench_result_folder(result_folder)
245+
246+
# Create a composite model
247+
composite_model = CompositeModel(composite_files, dpf_server)
248+
249+
plies = [
250+
"Setup 2_shell::P1L1__ModelingPly.2",
251+
"Setup_solid::P1L1__ModelingPly.2",
252+
"Setup 3_solid::P1L1__ModelingPly.1",
253+
]
254+
255+
# Evaluate combined failure criterion
256+
combined_failure_criterion = CombinedFailureCriterion(failure_criteria=[MaxStressCriterion()])
257+
failure_result = composite_model.evaluate_failure_criteria(
258+
combined_criterion=combined_failure_criterion, composite_scope=CompositeScope(plies=plies)
259+
)
260+
261+
# check the on reference surface data
262+
for failure_output in [
263+
FailureOutput.FAILURE_VALUE_REF_SURFACE,
264+
FailureOutput.FAILURE_MODE_REF_SURFACE,
265+
FailureOutput.MAX_GLOBAL_LAYER_IN_STACK,
266+
FailureOutput.MAX_LOCAL_LAYER_IN_ELEMENT,
267+
FailureOutput.MAX_SOLID_ELEMENT_ID,
268+
]:
269+
field = failure_result.get_field({FAILURE_LABEL: failure_output})
270+
271+
if version_equal_or_later(dpf_server, "9.0"):
272+
assert field.size == 21
273+
elif version_equal_or_later(dpf_server, "8.0"):
274+
# Servers of the 2024 R2 series do not extract the reference surface
275+
# of imported solid models. So the reference surface mesh
276+
# contains only the shell elements and reference surface of the
277+
# standard solid model.
278+
assert field.size == 12
279+
else:
280+
# Servers before 8.0 are not tested because of several limitations:
281+
# handling of assemblies, reference surface not supported
282+
assert False

tests/composite_model_test.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from ansys.dpf.composites.layup_info import (
4141
LayerProperty,
4242
LayupModelContextType,
43+
get_all_analysis_ply_names,
4344
get_analysis_ply_index_to_name_map,
4445
)
4546
from ansys.dpf.composites.layup_info.material_properties import MaterialMetadata, MaterialProperty
@@ -539,3 +540,72 @@ def test_failure_criteria_evaluation_default_unit_system(dpf_server):
539540
cfc = CombinedFailureCriterion("max stress", failure_criteria=[MaxStressCriterion()])
540541

541542
failure_container = composite_model.evaluate_failure_criteria(cfc)
543+
544+
545+
def test_composite_model_with_imported_solid_model_assembly(dpf_server):
546+
"""
547+
Tests the show on reference surface option for imported solid models.
548+
549+
Imported solid models are slightly different if compared with shell parts
550+
and standard solid models. For instance, they have analysis plies which are
551+
not linked to a layered elements. These are called filler plies.
552+
553+
In addition, the `show on reference surface` option uses the skin of the
554+
imported solid mesh instead of a predefined reference surface.
555+
This general test ensures that the composite model can handle these.
556+
557+
The model has one shell part, one standard solid model and
558+
an imported solid model.
559+
"""
560+
result_folder = pathlib.Path(__file__).parent / "data" / "assembly_imported_solid_model"
561+
composite_files = get_composite_files_from_workbench_result_folder(result_folder)
562+
563+
# Create a composite model
564+
composite_model = CompositeModel(composite_files, dpf_server)
565+
566+
# ensure that all analysis plies are available, also the filler plies
567+
# The initial version of DPF Composites had limitations in the handling
568+
# of assemblies and so the test is skipped for old versions of the server.
569+
if version_equal_or_later(dpf_server, "7.0"):
570+
analysis_plies = get_all_analysis_ply_names(composite_model.get_mesh())
571+
ref_plies = [
572+
"Setup 2_shell::P1L1__ModelingPly.2",
573+
"Setup_solid::P1L1__ModelingPly.2",
574+
"Setup 3_solid::P1L1__ModelingPly.1",
575+
"Setup 3_solid::P1L1__ModelingPly.3",
576+
"Setup 3_solid::filler_Epoxy Carbon Woven (230 GPa) Prepreg",
577+
"Setup 3_solid::P1L1__ModelingPly.2",
578+
"Setup 2_shell::P1L1__ModelingPly.1",
579+
"Setup_solid::P1L1__ModelingPly.1",
580+
]
581+
assert set(analysis_plies) == set(ref_plies)
582+
583+
# Evaluate combined failure criterion
584+
combined_failure_criterion = CombinedFailureCriterion(failure_criteria=[MaxStressCriterion()])
585+
failure_result = composite_model.evaluate_failure_criteria(combined_failure_criterion)
586+
587+
irf_field = failure_result.get_field({FAILURE_LABEL: FailureOutput.FAILURE_VALUE})
588+
assert irf_field.size == 84
589+
590+
# check the on reference surface data
591+
for failure_output in [
592+
FailureOutput.FAILURE_VALUE_REF_SURFACE,
593+
FailureOutput.FAILURE_MODE_REF_SURFACE,
594+
FailureOutput.MAX_GLOBAL_LAYER_IN_STACK,
595+
FailureOutput.MAX_LOCAL_LAYER_IN_ELEMENT,
596+
FailureOutput.MAX_SOLID_ELEMENT_ID,
597+
]:
598+
field = failure_result.get_field({FAILURE_LABEL: failure_output})
599+
if version_equal_or_later(dpf_server, "9.0"):
600+
assert field.size == 60
601+
elif version_equal_or_later(dpf_server, "8.0"):
602+
# Servers of the 2024 R2 series do not extract the reference surface
603+
# of imported solid models. So the reference surface mesh
604+
# contains only the shell elements and reference surface of the
605+
# standard solid model.
606+
assert field.size == 18
607+
else:
608+
# Server of 2024 R1 and before do not support results on the reference
609+
# surface at all. It is tested that the operator update
610+
# completes without error nevertheless.
611+
pass

0 commit comments

Comments
 (0)