Skip to content

Commit c5cd303

Browse files
jacobrkerstetterJacob Kerstetterpyansys-ci-botpre-commit-ci[bot]
authored
feat: grpc measurement tools stub implementation (#1909)
Co-authored-by: Jacob Kerstetter <[email protected]> Co-authored-by: pyansys-ci-bot <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 1adc6a1 commit c5cd303

File tree

10 files changed

+240
-53
lines changed

10 files changed

+240
-53
lines changed

doc/changelog.d/1909.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
grpc measurement tools stub implementation

src/ansys/geometry/core/_grpc/_services/_service.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from .base.admin import GRPCAdminService
2727
from .base.bodies import GRPCBodyService
2828
from .base.dbuapplication import GRPCDbuApplicationService
29+
from .base.measurement_tools import GRPCMeasurementToolsService
2930
from .base.named_selection import GRPCNamedSelectionService
3031

3132

@@ -71,6 +72,7 @@ def __init__(self, channel: grpc.Channel, version: GeometryApiProtos | str | Non
7172
self._bodies = None
7273
self._dbu_application = None
7374
self._named_selection = None
75+
self._measurement_tools = None
7476

7577
@property
7678
def bodies(self) -> GRPCBodyService:
@@ -175,3 +177,29 @@ def named_selection(self) -> GRPCNamedSelectionService:
175177
raise ValueError(f"Unsupported version: {self.version}")
176178

177179
return self._named_selection
180+
181+
@property
182+
def measurement_tools(self) -> GRPCMeasurementToolsService:
183+
"""
184+
Get the measurement tools service for the specified version.
185+
186+
Returns
187+
-------
188+
MeasurementToolsServiceBase
189+
The measurement tools service for the specified version.
190+
"""
191+
if not self._measurement_tools:
192+
# Import the appropriate measurement tools service based on the version
193+
from .v0.measurement_tools import GRPCMeasurementToolsServiceV0
194+
from .v1.measurement_tools import GRPCMeasurementToolsServiceV1
195+
196+
if self.version == GeometryApiProtos.V0:
197+
self._measurement_tools = GRPCMeasurementToolsServiceV0(self.channel)
198+
elif self.version == GeometryApiProtos.V1: # pragma: no cover
199+
# V1 is not implemented yet
200+
self._measurement_tools = GRPCMeasurementToolsServiceV1(self.channel)
201+
else: # pragma: no cover
202+
# This should never happen as the version is set in the constructor
203+
raise ValueError(f"Unsupported version: {self.version}")
204+
205+
return self._measurement_tools

src/ansys/geometry/core/_grpc/_services/base/conversions.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
# SOFTWARE.
2222
"""Module containing server-version agnostic conversions."""
2323

24-
from ansys.geometry.core.misc.measurements import DEFAULT_UNITS, Measurement
24+
from ansys.geometry.core.misc.measurements import DEFAULT_UNITS, Distance, Measurement
2525

2626

2727
def from_measurement_to_server_length(input: Measurement) -> float:
@@ -54,3 +54,24 @@ def from_measurement_to_server_angle(input: Measurement) -> float:
5454
Angle value in server-defined units. By default, radians.
5555
"""
5656
return input.value.m_as(DEFAULT_UNITS.SERVER_ANGLE)
57+
58+
59+
def to_distance(value: float | int) -> Distance:
60+
"""Convert a server value to a Distance object.
61+
62+
Notes
63+
-----
64+
The value is converted to a Distance object using the default server length unit.
65+
The value should represent a length in the server's unit system.
66+
67+
Parameters
68+
----------
69+
value : float | int
70+
Value to convert.
71+
72+
Returns
73+
-------
74+
Distance
75+
Converted distance.
76+
"""
77+
return Distance(value, DEFAULT_UNITS.SERVER_LENGTH)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
"""Module containing the measurement tools service implementation (abstraction layer)."""
23+
24+
from abc import ABC, abstractmethod
25+
26+
import grpc
27+
28+
29+
class GRPCMeasurementToolsService(ABC):
30+
"""Measurement tools service for gRPC communication with the Geometry server.
31+
32+
Parameters
33+
----------
34+
channel : grpc.Channel
35+
The gRPC channel to the server.
36+
"""
37+
38+
def __init__(self, channel: grpc.Channel):
39+
"""Initialize the MeasurementToolsService class."""
40+
pass # pragma: no cover
41+
42+
@abstractmethod
43+
def min_distance_between_objects(self, **kwargs) -> dict:
44+
"""Calculate the minimum distance between two objects."""
45+
pass # pragma: no cover
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
"""Module containing the measurement tools service implementation for v0."""
23+
24+
import grpc
25+
26+
from ansys.geometry.core.errors import protect_grpc
27+
28+
from ..base.measurement_tools import GRPCMeasurementToolsService
29+
30+
31+
class GRPCMeasurementToolsServiceV0(GRPCMeasurementToolsService):
32+
"""Measurement tools service for gRPC communication with the Geometry server.
33+
34+
This class provides methods to interact with the Geometry server's
35+
measurement tools service. It is specifically designed for the v0 version of the
36+
Geometry API.
37+
38+
Parameters
39+
----------
40+
channel : grpc.Channel
41+
The gRPC channel to the server.
42+
"""
43+
44+
@protect_grpc
45+
def __init__(self, channel: grpc.Channel): # noqa: D102
46+
from ansys.api.geometry.v0.measuretools_pb2_grpc import MeasureToolsStub
47+
48+
self.stub = MeasureToolsStub(channel)
49+
50+
@protect_grpc
51+
def min_distance_between_objects(self, **kwargs) -> dict: # noqa: D102
52+
from ansys.api.geometry.v0.measuretools_pb2 import MinDistanceBetweenObjectsRequest
53+
54+
from ..base.conversions import to_distance
55+
56+
# Create the request - assumes all inputs are valid and of the proper type
57+
# Request is different based on backend_version (25.2 vs. earlier)
58+
if kwargs["backend_version"] < (25, 2, 0):
59+
request = MinDistanceBetweenObjectsRequest(bodies=kwargs["selection"])
60+
else:
61+
from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier
62+
63+
request = MinDistanceBetweenObjectsRequest(
64+
selection=[EntityIdentifier(id=item) for item in kwargs["selection"]]
65+
)
66+
67+
# Call the gRPC service
68+
response = self.stub.MinDistanceBetweenSelectionObjects(request)
69+
70+
# Return the response - formatted as a dictionary
71+
return {"distance": to_distance(response.gap.distance)}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
"""Module containing the measurement tools service implementation for v0."""
23+
24+
import grpc
25+
26+
from ansys.geometry.core.errors import protect_grpc
27+
28+
from ..base.measurement_tools import GRPCMeasurementToolsService
29+
30+
31+
class GRPCMeasurementToolsServiceV1(GRPCMeasurementToolsService):
32+
"""Measurement tools service for gRPC communication with the Geometry server.
33+
34+
This class provides methods to interact with the Geometry server's
35+
measurement tools service. It is specifically designed for the v0 version of the
36+
Geometry API.
37+
38+
Parameters
39+
----------
40+
channel : grpc.Channel
41+
The gRPC channel to the server.
42+
"""
43+
44+
@protect_grpc
45+
def __init__(self, channel: grpc.Channel): # noqa: D102
46+
from ansys.api.geometry.v1.measuretools_pb2_grpc import MeasureToolsStub
47+
48+
self.stub = MeasureToolsStub(channel)
49+
50+
@protect_grpc
51+
def min_distance_between_objects(self, **kwargs) -> dict: # noqa: D102
52+
raise NotImplementedError

src/ansys/geometry/core/connection/client.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,8 @@ def __init__(
189189
response = self._services.admin.get_backend()
190190

191191
# Store the backend type and version
192-
self._backend_type = response["backend"]
193-
self._backend_version = response["version"]
192+
self._backend_type = response.get("backend")
193+
self._backend_version = response.get("version")
194194

195195
# Register the close method to be called at exit - irrespectively of
196196
# the user calling it or not...
@@ -357,8 +357,7 @@ def _get_service_logs(
357357
the current logs are retrieved). The ``dump_to_file`` parameter
358358
must be set to ``True``.
359359
"""
360-
response = self._services.admin.get_logs(all_logs=all_logs)
361-
logs: dict[str, str] = response["logs"]
360+
logs: dict[str, str] = self._services.admin.get_logs(all_logs=all_logs).get("logs")
362361

363362
# Let's handle the various scenarios...
364363
if not dump_to_file:

src/ansys/geometry/core/designer/selection.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def __init__(
129129
response = self._grpc_client.services.named_selection.create_named_selection(
130130
name=name, members=ids
131131
)
132-
self._id = response["id"]
132+
self._id = response.get("id")
133133

134134
@property
135135
def id(self) -> str:
@@ -204,15 +204,15 @@ def __verify_ns(self) -> None:
204204
return
205205

206206
# Get all entities from the named selection
207-
resp = self._grpc_client.services.named_selection.get_named_selection(id=self._id)
207+
response = self._grpc_client.services.named_selection.get_named_selection(id=self._id)
208208

209209
# Check if the named selection has changed
210210
ids = {
211-
"bodies": resp["bodies"],
212-
"faces": resp["faces"],
213-
"edges": resp["edges"],
214-
"beams": resp["beams"],
215-
"design_points": resp["design_points"],
211+
"bodies": response.get("bodies"),
212+
"faces": response.get("faces"),
213+
"edges": response.get("edges"),
214+
"beams": response.get("beams"),
215+
"design_points": response.get("design_points"),
216216
}
217217

218218
for key in ids:

src/ansys/geometry/core/modeler.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -566,15 +566,15 @@ def run_discovery_script_file(
566566
api_version=api_version.value if api_version is not None else None,
567567
)
568568

569-
if not response["success"]:
570-
raise GeometryRuntimeError(response["message"])
569+
if not response.get("success"):
570+
raise GeometryRuntimeError(response.get("message"))
571571

572-
self.client.log.debug(f"Script result message: {response['message']}")
572+
self.client.log.debug(f"Script result message: {response.get('message')}")
573573

574574
if import_design:
575-
return response["values"], self.read_existing_design()
575+
return response.get("values"), self.read_existing_design()
576576
else:
577-
return response["values"], None
577+
return response.get("values"), None
578578

579579
@property
580580
def repair_tools(self) -> RepairTools:

src/ansys/geometry/core/tools/measurement_tools.py

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,9 @@
2323

2424
from typing import TYPE_CHECKING, Union
2525

26-
from ansys.api.geometry.v0.measuretools_pb2 import (
27-
MinDistanceBetweenObjectsRequest,
28-
MinDistanceBetweenObjectsResponse,
29-
)
30-
from ansys.api.geometry.v0.measuretools_pb2_grpc import MeasureToolsStub
3126
from ansys.geometry.core.connection import GrpcClient
32-
from ansys.geometry.core.errors import protect_grpc
3327
from ansys.geometry.core.misc.checks import min_backend_version
34-
from ansys.geometry.core.misc.measurements import DEFAULT_UNITS, Distance
28+
from ansys.geometry.core.misc.measurements import Distance
3529

3630
if TYPE_CHECKING: # pragma: no cover
3731
from ansys.geometry.core.designer.body import Body
@@ -57,23 +51,6 @@ def distance(self) -> Distance:
5751
"""Returns the closest distance between two bodies."""
5852
return self._distance
5953

60-
@classmethod
61-
def _from_distance_response(cls, response: MinDistanceBetweenObjectsResponse) -> "Gap":
62-
"""Construct ``Gap`` object from distance response.
63-
64-
Parameters
65-
----------
66-
response : MinDistanceBetweenObjectsResponse
67-
Response from the gRPC server.
68-
69-
Notes
70-
-----
71-
This method is used internally to construct a ``Gap`` object from a
72-
gRPC response.
73-
"""
74-
distance = Distance(response.gap.distance, unit=DEFAULT_UNITS.LENGTH)
75-
return cls(distance)
76-
7754

7855
class MeasurementTools:
7956
"""Measurement tools for PyAnsys Geometry.
@@ -84,13 +61,10 @@ class MeasurementTools:
8461
gRPC client to use for the measurement tools.
8562
"""
8663

87-
@protect_grpc
8864
def __init__(self, grpc_client: GrpcClient):
8965
"""Initialize measurement tools class."""
9066
self._grpc_client = grpc_client
91-
self._measure_stub = MeasureToolsStub(self._grpc_client.channel)
9267

93-
@protect_grpc
9468
@min_backend_version(24, 2, 0)
9569
def min_distance_between_objects(
9670
self, object1: Union["Body", "Face", "Edge"], object2: Union["Body", "Face", "Edge"]
@@ -109,12 +83,8 @@ def min_distance_between_objects(
10983
Gap
11084
Gap between two bodies.
11185
"""
112-
if self._grpc_client.backend_version < (25, 2, 0):
113-
response = self._measure_stub.MinDistanceBetweenObjects(
114-
MinDistanceBetweenObjectsRequest(bodies=[object1.id, object2.id])
115-
)
116-
else:
117-
response = self._measure_stub.MinDistanceBetweenSelectionObjects(
118-
MinDistanceBetweenObjectsRequest(selection=[object1._grpc_id, object2._grpc_id])
119-
)
120-
return Gap._from_distance_response(response)
86+
response = self._grpc_client.services.measurement_tools.min_distance_between_objects(
87+
selection=[object1.id, object2.id],
88+
backend_version=self._grpc_client.backend_version,
89+
)
90+
return Gap(response.get("distance"))

0 commit comments

Comments
 (0)