Skip to content

Commit 47d5cf6

Browse files
jacobrkerstetterpre-commit-ci[bot]pyansys-ci-bot
authored
feat: sweepable body detection (#2394)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pyansys-ci-bot <[email protected]>
1 parent 3ab596f commit 47d5cf6

File tree

7 files changed

+125
-0
lines changed

7 files changed

+125
-0
lines changed

doc/changelog.d/2394.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Sweepable body detection

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,8 @@ def create_cylinder_enclosure(self, **kwargs) -> dict:
9898
def create_sphere_enclosure(self, **kwargs) -> dict:
9999
"""Create a sphere enclosure around bodies."""
100100
pass
101+
102+
@abstractmethod
103+
def detect_sweepable_bodies(self, **kwargs) -> dict:
104+
"""Check if body is sweepable."""
105+
pass

src/ansys/geometry/core/_grpc/_services/v0/prepare_tools.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,3 +393,24 @@ def create_sphere_enclosure(self, **kwargs) -> dict: # noqa: D102
393393
"created_bodies": [body.id for body in response.created_bodies],
394394
"tracker_response": serialized_tracker_response,
395395
}
396+
397+
@protect_grpc
398+
def detect_sweepable_bodies(self, **kwargs): # noqa: D102
399+
from ansys.api.geometry.v0.preparetools_pb2 import DetectSweepableBodiesRequest
400+
401+
# Create the request - assumes all inputs are valid and of the proper type
402+
request = DetectSweepableBodiesRequest(
403+
bodies=[build_grpc_id(id=id) for id in kwargs["body_ids"]],
404+
get_source_target_faces=kwargs["get_source_target_faces"],
405+
)
406+
407+
# Call the gRPC service
408+
response = self.stub.DetectSweepableBodies(request)
409+
410+
# Return the response - formatted as a dictionary
411+
return {
412+
"results": [
413+
{"sweepable": result.result, "face_ids": [face.id for face in result.face_ids]}
414+
for result in response.response_data
415+
]
416+
}

src/ansys/geometry/core/_grpc/_services/v1/prepare_tools.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,7 @@ def create_cylinder_enclosure(self, **kwargs) -> dict: # noqa: D102
9494
@protect_grpc
9595
def create_sphere_enclosure(self, **kwargs) -> dict: # noqa: D102
9696
raise NotImplementedError
97+
98+
@protect_grpc
99+
def detect_sweepable_bodies(self, **kwargs): # noqa: D102
100+
raise NotImplementedError

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
get_design_from_body,
3939
get_design_from_edge,
4040
get_design_from_face,
41+
get_faces_from_ids,
4142
)
4243
from ansys.geometry.core.misc.checks import check_type_all_elements_in_iterable, min_backend_version
4344
from ansys.geometry.core.misc.measurements import Distance
@@ -779,3 +780,46 @@ def create_sphere_enclosure(
779780
else:
780781
self._grpc_client.log.info("Failed to create enclosure...")
781782
return []
783+
784+
@min_backend_version(26, 1, 0)
785+
def detect_sweepable_bodies(
786+
self,
787+
bodies: list["Body"],
788+
get_source_target_faces: bool = False,
789+
) -> list[tuple[bool, list["Face"]]]:
790+
"""Check if bodies are sweepable.
791+
792+
Parameters
793+
----------
794+
bodies : list[Body]
795+
List of bodies to check.
796+
get_source_target_faces : bool
797+
Whether to get source and target faces. By default, ``False``.
798+
799+
Returns
800+
-------
801+
list[tuple[bool, list[Face]]]
802+
List of tuples, each containing a boolean indicating if the body is sweepable and
803+
a list of source and target faces if requested.
804+
"""
805+
from ansys.geometry.core.designer.body import Body
806+
807+
check_type_all_elements_in_iterable(bodies, Body)
808+
809+
if not bodies:
810+
return []
811+
812+
response = self._grpc_client._services.prepare_tools.detect_sweepable_bodies(
813+
body_ids=[body.id for body in bodies],
814+
get_source_target_faces=get_source_target_faces,
815+
)
816+
817+
results = []
818+
for result_data in response.get("results"):
819+
faces = []
820+
parent_design = get_design_from_body(bodies[0])
821+
if get_source_target_faces:
822+
faces.extend(get_faces_from_ids(parent_design, result_data.get("face_ids")))
823+
results.append((result_data.get("sweepable"), faces))
824+
825+
return results

tests/_incompatible_tests.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ backends:
2020
- tests/integration/test_mating_conditions.py::test_align_condition
2121
- tests/integration/test_mating_conditions.py::test_tangent_condition
2222
- tests/integration/test_mating_conditions.py::test_orient_condition
23+
- tests/integration/test_prepare_tools.py::test_detect_sweepable_bodies
2324
- tests/integration/test_prepare_tools.py::test_volume_extract_from_faces
2425
- tests/integration/test_prepare_tools.py::test_volume_extract_from_edge_loops
2526
- tests/integration/test_prepare_tools.py::test_remove_rounds
@@ -145,6 +146,7 @@ backends:
145146
- tests/integration/test_mating_conditions.py::test_align_condition
146147
- tests/integration/test_mating_conditions.py::test_tangent_condition
147148
- tests/integration/test_mating_conditions.py::test_orient_condition
149+
- tests/integration/test_prepare_tools.py::test_detect_sweepable_bodies
148150
- tests/integration/test_prepare_tools.py::test_volume_extract_from_faces
149151
- tests/integration/test_prepare_tools.py::test_volume_extract_from_edge_loops
150152
- tests/integration/test_prepare_tools.py::test_remove_rounds
@@ -254,6 +256,7 @@ backends:
254256
# Model used is too new for this version
255257
- tests/integration/test_design.py::test_import_component_named_selections
256258
- tests/integration/test_design.py::test_component_make_independent
259+
- tests/integration/test_prepare_tools.py::test_detect_sweepable_bodies
257260
- tests/integration/test_prepare_tools.py::test_helix_detection
258261
- tests/integration/test_spaceclaim_tutorial_examples.py::test_combine_example
259262
- tests/integration/test_spaceclaim_tutorial_examples.py::test_pull_example

tests/integration/test_prepare_tools.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,3 +294,50 @@ def test_sphere_enclosure(modeler):
294294
modeler.prepare_tools.create_sphere_enclosure(bodies, 0.1, enclosure_options)
295295
assert len(design.components) == 1
296296
assert len(design.components[0].bodies) == 1
297+
298+
299+
def test_detect_sweepable_bodies(modeler: Modeler):
300+
"""Test body sweepability detection."""
301+
design = modeler.open_file(FILES_DIR / "DifferentShapes.scdocx")
302+
303+
bodies = design.bodies
304+
assert len(bodies) == 6
305+
306+
# Test sweepability of the body
307+
is_sweepable, faces = modeler.prepare_tools.detect_sweepable_bodies([bodies[0]])[0]
308+
assert is_sweepable
309+
assert len(faces) == 0
310+
311+
# Test non-sweepable body
312+
is_sweepable, faces = modeler.prepare_tools.detect_sweepable_bodies([bodies[2]])[0]
313+
assert not is_sweepable
314+
assert len(faces) == 0
315+
316+
# Test sweepability of a body and getting faces
317+
is_sweepable, faces = modeler.prepare_tools.detect_sweepable_bodies(
318+
[bodies[0]], get_source_target_faces=True
319+
)[0]
320+
assert is_sweepable
321+
assert len(faces) == 2
322+
323+
# Test multiple bodies at once
324+
result = modeler.prepare_tools.detect_sweepable_bodies(bodies)
325+
assert len(result) == 6
326+
assert result[0][0] # first body is sweepable
327+
assert result[1][0] # second body is sweepable
328+
assert not result[2][0] # third body is not sweepable
329+
assert not result[3][0] # fourth body is not sweepable
330+
331+
# Test with multiple bodys and getting faces
332+
result = modeler.prepare_tools.detect_sweepable_bodies(bodies, get_source_target_faces=True)
333+
assert len(result) == 6
334+
assert result[0][0] # first body is sweepable
335+
assert len(result[0][1]) == 2 # two faces for first body
336+
assert result[1][0] # second body is sweepable
337+
assert len(result[1][1]) == 2 # two faces for second body
338+
assert not result[2][0] # third body is not sweepable
339+
assert len(result[2][1]) == 0 # no faces for third body
340+
341+
# Test with empty input
342+
result = modeler.prepare_tools.detect_sweepable_bodies([])
343+
assert result == []

0 commit comments

Comments
 (0)