Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changelog.d/2394.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Sweepable body detection
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,8 @@ def create_cylinder_enclosure(self, **kwargs) -> dict:
def create_sphere_enclosure(self, **kwargs) -> dict:
"""Create a sphere enclosure around bodies."""
pass

@abstractmethod
def detect_sweepable_bodies(self, **kwargs) -> dict:
"""Check if body is sweepable."""
pass
21 changes: 21 additions & 0 deletions src/ansys/geometry/core/_grpc/_services/v0/prepare_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,3 +393,24 @@ def create_sphere_enclosure(self, **kwargs) -> dict: # noqa: D102
"created_bodies": [body.id for body in response.created_bodies],
"tracker_response": serialized_tracker_response,
}

@protect_grpc
def detect_sweepable_bodies(self, **kwargs): # noqa: D102
from ansys.api.geometry.v0.preparetools_pb2 import DetectSweepableBodiesRequest

# Create the request - assumes all inputs are valid and of the proper type
request = DetectSweepableBodiesRequest(
bodies=[build_grpc_id(id=id) for id in kwargs["body_ids"]],
get_source_target_faces=kwargs["get_source_target_faces"],
)

# Call the gRPC service
response = self.stub.DetectSweepableBodies(request)

# Return the response - formatted as a dictionary
return {
"results": [
{"sweepable": result.result, "face_ids": [face.id for face in result.face_ids]}
for result in response.response_data
]
}
4 changes: 4 additions & 0 deletions src/ansys/geometry/core/_grpc/_services/v1/prepare_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,7 @@ def create_cylinder_enclosure(self, **kwargs) -> dict: # noqa: D102
@protect_grpc
def create_sphere_enclosure(self, **kwargs) -> dict: # noqa: D102
raise NotImplementedError

@protect_grpc
def detect_sweepable_bodies(self, **kwargs): # noqa: D102
raise NotImplementedError
44 changes: 44 additions & 0 deletions src/ansys/geometry/core/tools/prepare_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
get_design_from_body,
get_design_from_edge,
get_design_from_face,
get_faces_from_ids,
)
from ansys.geometry.core.misc.checks import check_type_all_elements_in_iterable, min_backend_version
from ansys.geometry.core.misc.measurements import Distance
Expand Down Expand Up @@ -779,3 +780,46 @@ def create_sphere_enclosure(
else:
self._grpc_client.log.info("Failed to create enclosure...")
return []

@min_backend_version(26, 1, 0)
def detect_sweepable_bodies(
self,
bodies: list["Body"],
get_source_target_faces: bool = False,
) -> list[tuple[bool, list["Face"]]]:
"""Check if bodies are sweepable.

Parameters
----------
bodies : list[Body]
List of bodies to check.
get_source_target_faces : bool
Whether to get source and target faces. By default, ``False``.

Returns
-------
list[tuple[bool, list[Face]]]
List of tuples, each containing a boolean indicating if the body is sweepable and
a list of source and target faces if requested.
"""
from ansys.geometry.core.designer.body import Body

check_type_all_elements_in_iterable(bodies, Body)

if not bodies:
return []

response = self._grpc_client._services.prepare_tools.detect_sweepable_bodies(
body_ids=[body.id for body in bodies],
get_source_target_faces=get_source_target_faces,
)

results = []
for result_data in response.get("results"):
faces = []
parent_design = get_design_from_body(bodies[0])
if get_source_target_faces:
faces.extend(get_faces_from_ids(parent_design, result_data.get("face_ids")))
results.append((result_data.get("sweepable"), faces))

return results
3 changes: 3 additions & 0 deletions tests/_incompatible_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ backends:
- tests/integration/test_mating_conditions.py::test_align_condition
- tests/integration/test_mating_conditions.py::test_tangent_condition
- tests/integration/test_mating_conditions.py::test_orient_condition
- tests/integration/test_prepare_tools.py::test_detect_sweepable_bodies
- tests/integration/test_prepare_tools.py::test_volume_extract_from_faces
- tests/integration/test_prepare_tools.py::test_volume_extract_from_edge_loops
- tests/integration/test_prepare_tools.py::test_remove_rounds
Expand Down Expand Up @@ -145,6 +146,7 @@ backends:
- tests/integration/test_mating_conditions.py::test_align_condition
- tests/integration/test_mating_conditions.py::test_tangent_condition
- tests/integration/test_mating_conditions.py::test_orient_condition
- tests/integration/test_prepare_tools.py::test_detect_sweepable_bodies
- tests/integration/test_prepare_tools.py::test_volume_extract_from_faces
- tests/integration/test_prepare_tools.py::test_volume_extract_from_edge_loops
- tests/integration/test_prepare_tools.py::test_remove_rounds
Expand Down Expand Up @@ -254,6 +256,7 @@ backends:
# Model used is too new for this version
- tests/integration/test_design.py::test_import_component_named_selections
- tests/integration/test_design.py::test_component_make_independent
- tests/integration/test_prepare_tools.py::test_detect_sweepable_bodies
- tests/integration/test_prepare_tools.py::test_helix_detection
- tests/integration/test_spaceclaim_tutorial_examples.py::test_combine_example
- tests/integration/test_spaceclaim_tutorial_examples.py::test_pull_example
Expand Down
47 changes: 47 additions & 0 deletions tests/integration/test_prepare_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,3 +294,50 @@ def test_sphere_enclosure(modeler):
modeler.prepare_tools.create_sphere_enclosure(bodies, 0.1, enclosure_options)
assert len(design.components) == 1
assert len(design.components[0].bodies) == 1


def test_detect_sweepable_bodies(modeler: Modeler):
"""Test body sweepability detection."""
design = modeler.open_file(FILES_DIR / "DifferentShapes.scdocx")

bodies = design.bodies
assert len(bodies) == 6

# Test sweepability of the body
is_sweepable, faces = modeler.prepare_tools.detect_sweepable_bodies([bodies[0]])[0]
assert is_sweepable
assert len(faces) == 0

# Test non-sweepable body
is_sweepable, faces = modeler.prepare_tools.detect_sweepable_bodies([bodies[2]])[0]
assert not is_sweepable
assert len(faces) == 0

# Test sweepability of a body and getting faces
is_sweepable, faces = modeler.prepare_tools.detect_sweepable_bodies(
[bodies[0]], get_source_target_faces=True
)[0]
assert is_sweepable
assert len(faces) == 2

# Test multiple bodies at once
result = modeler.prepare_tools.detect_sweepable_bodies(bodies)
assert len(result) == 6
assert result[0][0] # first body is sweepable
assert result[1][0] # second body is sweepable
assert not result[2][0] # third body is not sweepable
assert not result[3][0] # fourth body is not sweepable

# Test with multiple bodys and getting faces
result = modeler.prepare_tools.detect_sweepable_bodies(bodies, get_source_target_faces=True)
assert len(result) == 6
assert result[0][0] # first body is sweepable
assert len(result[0][1]) == 2 # two faces for first body
assert result[1][0] # second body is sweepable
assert len(result[1][1]) == 2 # two faces for second body
assert not result[2][0] # third body is not sweepable
assert len(result[2][1]) == 0 # no faces for third body

# Test with empty input
result = modeler.prepare_tools.detect_sweepable_bodies([])
assert result == []
Loading