Skip to content

Commit 7890c7d

Browse files
jacobrkerstetterpre-commit-ci[bot]pyansys-ci-bot
authored
feat: helix detection (#2232)
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 de8df27 commit 7890c7d

File tree

8 files changed

+188
-0
lines changed

8 files changed

+188
-0
lines changed

doc/changelog.d/2232.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Helix 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
@@ -78,3 +78,8 @@ def find_and_remove_logos(self, **kwargs) -> dict:
7878
def remove_logo(self, **kwargs) -> dict:
7979
"""Remove logos in geometry."""
8080
pass
81+
82+
@abstractmethod
83+
def detect_helixes(self, **kwargs) -> dict:
84+
"""Detect helixes in geometry."""
85+
pass

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,64 @@ def remove_logo(self, **kwargs): # noqa: D102
214214

215215
# Return the response - formatted as a dictionary
216216
return {"success": response.success}
217+
218+
@protect_grpc
219+
def detect_helixes(self, **kwargs) -> dict: # noqa: D102
220+
from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier
221+
from ansys.api.geometry.v0.models_pb2 import DetectHelixesOptions
222+
from ansys.api.geometry.v0.preparetools_pb2 import DetectHelixesRequest
223+
224+
from ansys.geometry.core.shapes.parameterization import Interval
225+
226+
from ..base.conversions import (
227+
from_measurement_to_server_length,
228+
to_distance,
229+
)
230+
from .conversions import (
231+
from_grpc_curve_to_curve,
232+
from_grpc_point_to_point3d,
233+
)
234+
235+
# Create the request - assumes all inputs are valid and of the proper type
236+
request = DetectHelixesRequest(
237+
body_ids=[EntityIdentifier(id=body.id) for body in kwargs["bodies"]],
238+
options=DetectHelixesOptions(
239+
min_radius=from_measurement_to_server_length(kwargs["min_radius"]),
240+
max_radius=from_measurement_to_server_length(kwargs["max_radius"]),
241+
fit_radius_error=from_measurement_to_server_length(kwargs["fit_radius_error"]),
242+
),
243+
)
244+
245+
# Call the gRPC service
246+
response = self.stub.DetectHelixes(request)
247+
248+
# If no helixes, return empty dictionary
249+
if len(response.helixes) == 0:
250+
return {"helixes": []}
251+
252+
# Return the response - formatted as a dictionary
253+
return {
254+
"helixes": [
255+
{
256+
"trimmed_curve": {
257+
"geometry": from_grpc_curve_to_curve(helix.trimmed_curve.curve),
258+
"start": from_grpc_point_to_point3d(helix.trimmed_curve.start),
259+
"end": from_grpc_point_to_point3d(helix.trimmed_curve.end),
260+
"interval": Interval(
261+
helix.trimmed_curve.interval_start, helix.trimmed_curve.interval_end
262+
),
263+
"length": to_distance(helix.trimmed_curve.length).value,
264+
},
265+
"edges": [
266+
{
267+
"id": edge.id,
268+
"parent_id": edge.parent.id,
269+
"curve_type": edge.curve_type,
270+
"is_reversed": edge.is_reversed,
271+
}
272+
for edge in helix.edges
273+
],
274+
}
275+
for helix in response.helixes
276+
]
277+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,7 @@ def find_and_remove_logos(self, **kwargs) -> dict: # noqa: D102
7878
@protect_grpc
7979
def remove_logo(self, **kwargs) -> dict: # noqa: D102
8080
raise NotImplementedError
81+
82+
@protect_grpc
83+
def detect_helixes(self, **kwargs) -> dict: # noqa: D102
84+
raise NotImplementedError

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

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,21 @@
2424
from typing import TYPE_CHECKING
2525

2626
from beartype import beartype as check_input_types
27+
from pint import Quantity
2728

2829
from ansys.geometry.core.connection import GrpcClient
2930
from ansys.geometry.core.connection.backend import BackendType
3031
from ansys.geometry.core.errors import GeometryRuntimeError
3132
from ansys.geometry.core.logger import LOG
3233
from ansys.geometry.core.misc.auxiliary import (
3334
get_bodies_from_ids,
35+
get_design_from_body,
3436
get_design_from_edge,
3537
get_design_from_face,
3638
)
3739
from ansys.geometry.core.misc.checks import check_type_all_elements_in_iterable, min_backend_version
40+
from ansys.geometry.core.misc.measurements import Distance
41+
from ansys.geometry.core.shapes.curves.trimmed_curve import TrimmedCurve
3842
from ansys.geometry.core.tools.problem_areas import LogoProblemArea
3943
from ansys.geometry.core.tools.repair_tool_message import RepairToolMessage
4044
from ansys.geometry.core.typing import Real
@@ -412,3 +416,86 @@ def find_and_remove_logos(
412416
)
413417

414418
return response.get("success")
419+
420+
@min_backend_version(26, 1, 0)
421+
def detect_helixes(
422+
self,
423+
bodies: list["Body"],
424+
min_radius: Distance | Quantity | Real = 0.0,
425+
max_radius: Distance | Quantity | Real = 100.0,
426+
fit_radius_error: Distance | Quantity | Real = 0.01,
427+
) -> dict["TrimmedCurve", list["Edge"]]:
428+
"""Detect helixes in the given bodies.
429+
430+
Parameters
431+
----------
432+
bodies : list[Body]
433+
List of bodies to detect helixes in.
434+
min_radius : Distance, Quantity, or Real, default: 0.0
435+
Minimum radius of the helix to be detected.
436+
max_radius : Distance, Quantity, or Real, default: 1e6
437+
Maximum radius of the helix to be detected.
438+
fit_radius_error : Distance, Quantity, or Real, default: 0.01
439+
Maximum fit radius error of the helix to be detected.
440+
441+
Returns
442+
-------
443+
dict
444+
Dictionary with key "helixes" containing a list of detected helixes.
445+
Each helix is represented as a dictionary with keys "trimmed_curve" and "edges".
446+
447+
Warnings
448+
--------
449+
This method is only available starting on Ansys release 26R1.
450+
"""
451+
from ansys.geometry.core.designer.body import Body
452+
from ansys.geometry.core.designer.edge import CurveType, Edge
453+
454+
if not bodies:
455+
self._grpc_client.log.info("No bodies provided...")
456+
return {"helixes": []}
457+
458+
# Verify inputs
459+
check_type_all_elements_in_iterable(bodies, Body)
460+
min_radius = min_radius if isinstance(min_radius, Distance) else Distance(min_radius)
461+
max_radius = max_radius if isinstance(max_radius, Distance) else Distance(max_radius)
462+
fit_radius_error = (
463+
fit_radius_error
464+
if isinstance(fit_radius_error, Distance)
465+
else Distance(fit_radius_error)
466+
)
467+
468+
response = self._grpc_client._services.prepare_tools.detect_helixes(
469+
bodies=bodies,
470+
min_radius=min_radius,
471+
max_radius=max_radius,
472+
fit_radius_error=fit_radius_error,
473+
)
474+
475+
parent_design = get_design_from_body(bodies[0])
476+
477+
return {
478+
"helixes": [
479+
{
480+
"trimmed_curve": TrimmedCurve(
481+
helix.get("trimmed_curve").get("geometry"),
482+
helix.get("trimmed_curve").get("start"),
483+
helix.get("trimmed_curve").get("end"),
484+
helix.get("trimmed_curve").get("interval"),
485+
helix.get("trimmed_curve").get("length"),
486+
grpc_client=self._grpc_client,
487+
),
488+
"edges": [
489+
Edge(
490+
edge.get("id"),
491+
CurveType(edge.get("curve_type")),
492+
get_bodies_from_ids(parent_design, [edge.get("parent_id")])[0],
493+
self._grpc_client,
494+
edge.get("is_reversed"),
495+
)
496+
for edge in helix.get("edges")
497+
],
498+
}
499+
for helix in response.get("helixes")
500+
]
501+
}

tests/_incompatible_tests.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ backends:
2828
- tests/integration/test_prepare_tools.py::test_volume_extract_bad_faces
2929
- tests/integration/test_prepare_tools.py::test_volume_extract_bad_edges
3030
- tests/integration/test_prepare_tools.py::test_volume_extract_bad_edges
31+
- tests/integration/test_prepare_tools.py::test_helix_detection
3132
- tests/integration/test_repair_tools.py::test_fix_small_face
3233
- tests/integration/test_repair_tools.py::test_find_interference
3334
- tests/integration/test_repair_tools.py::test_fix_interference
@@ -137,6 +138,7 @@ backends:
137138
- tests/integration/test_prepare_tools.py::test_volume_extract_bad_faces
138139
- tests/integration/test_prepare_tools.py::test_volume_extract_bad_edges
139140
- tests/integration/test_prepare_tools.py::test_volume_extract_bad_edges
141+
- tests/integration/test_prepare_tools.py::test_helix_detection
140142
- tests/integration/test_repair_tools.py::test_fix_small_face
141143
- tests/integration/test_repair_tools.py::test_find_interference
142144
- tests/integration/test_repair_tools.py::test_fix_interference
@@ -222,6 +224,7 @@ backends:
222224
# Model used is too new for this version
223225
- tests/integration/test_design.py::test_import_component_named_selections
224226
- tests/integration/test_design.py::test_component_make_independent
227+
- tests/integration/test_prepare_tools.py::test_helix_detection
225228
- tests/integration/test_spaceclaim_tutorial_examples.py::test_combine_example
226229
- tests/integration/test_spaceclaim_tutorial_examples.py::test_pull_example
227230
- tests/integration/test_spaceclaim_tutorial_examples.py::test_intersect_example
2.6 MB
Binary file not shown.

tests/integration/test_prepare_tools.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,30 @@ def test_volume_extract_bad_edges(modeler: Modeler):
207207
sealing_edges,
208208
)
209209
assert len(created_bodies) == 0
210+
211+
212+
def test_helix_detection(modeler: Modeler):
213+
"""Test helix detection."""
214+
design = modeler.open_file(FILES_DIR / "bolt.scdocx")
215+
216+
bodies = design.bodies
217+
assert len(bodies) == 2
218+
219+
search_bodies = [bodies[0]]
220+
assert len(search_bodies) == 1
221+
222+
# Test default parameters
223+
result = modeler.prepare_tools.detect_helixes(search_bodies)
224+
assert len(result["helixes"]) == 1
225+
226+
# Test with non-default parameters
227+
result = modeler.prepare_tools.detect_helixes(search_bodies, 0, 10, 100)
228+
assert len(result["helixes"]) == 1
229+
230+
# Test parameters that should yield no results
231+
result = modeler.prepare_tools.detect_helixes(search_bodies, 5.0, 10.0, 0.01)
232+
assert len(result["helixes"]) == 0
233+
234+
# Test with multiple bodies
235+
result = modeler.prepare_tools.detect_helixes(bodies)
236+
assert len(result["helixes"]) == 2

0 commit comments

Comments
 (0)