Skip to content

Commit 432c181

Browse files
authored
Re-enable the 'price' attribute on ModelElementalData (#853)
For server version 25.2 or later, re-enable the 'price' attribute on ModelElementalData. In order to know the server version, the _field_names method now takes an optional server_version argument. In the 'ModelElementalData', the 'price' field is removed if the server version is less than 25.2. Add back the previous test for the 'price' attribute, and a model which crashed when accessing the 'price' with 2025R1 and earlier. Closes #720.
1 parent 0c5ff77 commit 432c181

File tree

5 files changed

+67
-13
lines changed

5 files changed

+67
-13
lines changed

src/ansys/acp/core/_server/launch.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,15 @@
3838
RemoteFileTransferStrategy,
3939
)
4040
from .common import ControllableServerProtocol, LaunchMode, ServerKey
41+
from .connect import ConnectLaunchConfig
4142
from .direct import DirectLaunchConfig
4243
from .docker_compose import DockerComposeLaunchConfig
4344

4445
__all__ = ["launch_acp"]
4546

4647

4748
def launch_acp(
48-
config: DirectLaunchConfig | DockerComposeLaunchConfig | None = None,
49+
config: DirectLaunchConfig | DockerComposeLaunchConfig | ConnectLaunchConfig | None = None,
4950
launch_mode: LaunchMode | None = None,
5051
timeout: float | None = 30.0,
5152
auto_transfer_files: bool = True,

src/ansys/acp/core/_tree_objects/_elemental_or_nodal_data.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
if typing.TYPE_CHECKING: # pragma: no cover
3434
from pyvista.core.pointset import PolyData, UnstructuredGrid
3535

36+
from packaging.version import Version
37+
3638
from ansys.acp.core._utils.array_conversions import dataarray_to_numpy, to_numpy
3739
from ansys.api.acp.v0 import mesh_query_pb2, mesh_query_pb2_grpc
3840

@@ -113,6 +115,11 @@ def _get_pyvista_mesh_with_all_data(
113115
)
114116

115117
for name in mesh_data_base._field_names():
118+
data = getattr(mesh_data_base, name)
119+
# The data can be missing if the field depends on the server version - see
120+
# for example the ModelElementalData._field_names method.
121+
if data is None:
122+
continue
116123
values = getattr(mesh_data_base, name).values
117124
target_array = _expand_array(array=values, labels=labels)
118125
mesh_data_field[name] = target_array
@@ -307,7 +314,7 @@ class ElementalOrNodalDataBase:
307314
_PB_VALUE_FROM_FIELD_NAME: ClassVar[typing.Callable[[StrEnum], int]]
308315

309316
@classmethod
310-
def _field_names(cls) -> list[str]:
317+
def _field_names(cls, server_version: Version | None = None) -> list[str]:
311318
return [
312319
field.name
313320
for field in dataclasses.fields(cls)
@@ -459,7 +466,7 @@ def getter(self: TreeObject) -> MeshDataT:
459466
resource_path=self._resource_path,
460467
data_types=[
461468
wrapped_cls._PB_VALUE_FROM_FIELD_NAME(name) # type: ignore
462-
for name in wrapped_cls._field_names()
469+
for name in wrapped_cls._field_names(server_version=self._server_version)
463470
],
464471
),
465472
)

src/ansys/acp/core/_tree_objects/model.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
from typing import Any, cast
2929

3030
import numpy as np
31+
from packaging.version import Version
32+
from packaging.version import parse as parse_version
3133

3234
from ansys.api.acp.v0 import (
3335
boolean_selection_rule_pb2_grpc,
@@ -187,18 +189,29 @@ class ModelElementalData(ElementalData):
187189
thickness: ScalarData[np.float64] | None = None
188190
relative_thickness_correction: ScalarData[np.float64] | None = None
189191
area: ScalarData[np.float64] | None = None
190-
# Retrieving the 'price' can crash the server if the model contains void
191-
# analysis plies (on an imported solid model).
192-
# This is fixed in the backend for 2025R2, but for now we simply comment
193-
# out the property. In this way, the other properties can still be accessed,
194-
# and we can avoid the crash.
195-
# See https://github.com/ansys/pyacp/issues/717
196-
# price: ScalarData[np.float64] | None = None
192+
price: ScalarData[np.float64] | None = None
197193
volume: ScalarData[np.float64] | None = None
198194
mass: ScalarData[np.float64] | None = None
199195
offset: ScalarData[np.float64] | None = None
200196
cog: VectorData | None = None
201197

198+
@classmethod
199+
def _field_names(cls, server_version: Version | None = None) -> list[str]:
200+
"""Bugfix override for #717.
201+
202+
Override the base class _field_names method to remove the 'price' field
203+
if the server version is less than 25.2.
204+
205+
Before 25.2, retrieving the 'price' could crash the server if the model
206+
contains void analysis plies (on an imported solid model).
207+
In this way, the other properties can still be accessed, and we can avoid
208+
the crash.
209+
"""
210+
res = super()._field_names(server_version)
211+
if server_version is not None and server_version < parse_version("25.2"):
212+
res.remove("price")
213+
return res
214+
202215

203216
@dataclasses.dataclass
204217
class ModelNodalData(NodalData):
781 KB
Binary file not shown.

tests/unittests/test_model.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
import numpy as np
3131
import numpy.testing
32+
from packaging.version import parse as parse_version
3233
import pytest
3334

3435
from ansys.acp.core import ElementalDataType, UnitSystemType
@@ -145,20 +146,45 @@ def test_mesh_data(minimal_complete_model):
145146
numpy.testing.assert_equal(mesh.element_nodes_offsets, np.array([0]))
146147

147148

148-
def test_elemental_data(minimal_complete_model):
149+
def test_elemental_data(acp_instance, minimal_complete_model):
149150
data = minimal_complete_model.elemental_data
150151
numpy.testing.assert_allclose(data.element_labels.values, np.array([1]))
151152
numpy.testing.assert_allclose(data.normal.values, np.array([[0.0, 0.0, 1.0]]))
152153
numpy.testing.assert_allclose(data.thickness.values, np.array([1e-4]))
153154
numpy.testing.assert_allclose(data.relative_thickness_correction.values, np.array([1.0]))
154155
numpy.testing.assert_allclose(data.area.values, np.array([9e4]))
155-
# numpy.testing.assert_allclose(data.price.values, np.array([0.0])) # disabled due to issue #717.
156+
# The 'price' is disabled on servers prior to 25.2 due to issue #717.
157+
if parse_version(acp_instance.server_version) >= parse_version("25.2"):
158+
numpy.testing.assert_allclose(data.price.values, np.array([0.0]))
159+
else:
160+
data.price is None
156161
numpy.testing.assert_allclose(data.volume.values, np.array([9.0]))
157162
numpy.testing.assert_allclose(data.mass.values, np.array([7.065e-08]))
158163
numpy.testing.assert_allclose(data.offset.values, np.array([5e-5]))
159164
numpy.testing.assert_allclose(data.cog.values, np.array([[0.0, 0.0, 5e-5]]))
160165

161166

167+
def test_elemental_data_with_void_filler_analysis_plies(
168+
acp_instance, load_model_from_tempfile, skip_before_version
169+
):
170+
"""Regression test for issue #717.
171+
172+
Retrieving the price of a model with void and filler analysis plies crashes the server
173+
prior to 25.2.
174+
"""
175+
# On 2024R2, accessing the elemental data failed for this model since the
176+
# CoG is not supported for LayeredPolyhedron elements.
177+
skip_before_version("25.1")
178+
179+
is_supported = parse_version(acp_instance.server_version) >= parse_version("25.2")
180+
181+
with load_model_from_tempfile("regression_model_717.acph5") as model:
182+
if is_supported:
183+
assert model.elemental_data.price is not None
184+
else:
185+
assert model.elemental_data.price is None
186+
187+
162188
def test_nodal_data(minimal_complete_model):
163189
data = minimal_complete_model.nodal_data
164190
numpy.testing.assert_allclose(data.node_labels.values, np.array([1, 2, 3, 4]))
@@ -187,9 +213,16 @@ def test_elemental_data_to_pyvista(minimal_complete_model):
187213

188214
@pytest.mark.graphics
189215
@pytest.mark.parametrize("component", [e.value for e in ElementalDataType])
190-
def test_elemental_data_to_pyvista_with_component(minimal_complete_model, component):
216+
def test_elemental_data_to_pyvista_with_component(
217+
acp_instance,
218+
minimal_complete_model,
219+
component,
220+
):
191221
import pyvista
192222

223+
if component == "price" and parse_version(acp_instance.server_version) < parse_version("25.2"):
224+
pytest.skip("Price is not supported on this version of the server.")
225+
193226
data = minimal_complete_model.elemental_data
194227
if not hasattr(data, component):
195228
pytest.skip(f"Model elemental data does not contain component '{component}'")

0 commit comments

Comments
 (0)