Skip to content

Commit 32797ba

Browse files
jacobrkerstetterpre-commit-ci[bot]pyansys-ci-botjonahrb
authored
feat: body sweep_with_guide (#2179)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Co-authored-by: Jonah Boling <56607167+jonahrb@users.noreply.github.com>
1 parent 33c5744 commit 32797ba

File tree

7 files changed

+198
-0
lines changed

7 files changed

+198
-0
lines changed

doc/changelog.d/2179.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Body sweep_with_guide

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ def create_sweeping_chain(self, **kwargs) -> dict:
5959
"""Create a sweeping chain."""
6060
pass
6161

62+
@abstractmethod
63+
def sweep_with_guide(self, **kwargs) -> dict:
64+
"""Sweep with a guide."""
65+
pass
66+
6267
@abstractmethod
6368
def create_extruded_body_from_face_profile(self, **kwargs) -> dict:
6469
"""Create an extruded body from a face profile."""

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,48 @@ def create_sweeping_chain(self, **kwargs) -> dict: # noqa: D102
171171
"is_surface": resp.is_surface,
172172
}
173173

174+
@protect_grpc
175+
def sweep_with_guide(self, **kwargs) -> dict: # noqa: D102
176+
from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier
177+
from ansys.api.geometry.v0.bodies_pb2 import (
178+
SweepWithGuideRequest,
179+
SweepWithGuideRequestData,
180+
)
181+
182+
# Create request object - assumes all inputs are valid and of the proper type
183+
request = SweepWithGuideRequest(
184+
request_data=[
185+
SweepWithGuideRequestData(
186+
name=data.name,
187+
parent=EntityIdentifier(id=data.parent_id),
188+
plane=from_plane_to_grpc_plane(data.sketch.plane),
189+
geometries=from_sketch_shapes_to_grpc_geometries(
190+
data.sketch.plane, data.sketch.edges, data.sketch.faces
191+
),
192+
path=from_trimmed_curve_to_grpc_trimmed_curve(data.path),
193+
guide=from_trimmed_curve_to_grpc_trimmed_curve(data.guide),
194+
tight_tolerance=data.tight_tolerance,
195+
)
196+
for data in kwargs["sweep_data"]
197+
],
198+
)
199+
200+
# Call the gRPC service
201+
resp = self.stub.SweepWithGuide(request=request)
202+
203+
# Return the response - formatted as a dictionary
204+
return {
205+
"bodies": [
206+
{
207+
"id": body.id,
208+
"name": body.name,
209+
"master_id": body.master_id,
210+
"is_surface": body.is_surface,
211+
}
212+
]
213+
for body in resp.bodies
214+
}
215+
174216
@protect_grpc
175217
def create_extruded_body_from_face_profile(self, **kwargs) -> dict: # noqa: D102
176218
from ansys.api.geometry.v0.bodies_pb2 import CreateExtrudedBodyFromFaceProfileRequest

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,44 @@ def from_nurbs_curve_to_grpc_nurbs_curve(curve: "NURBSCurve") -> GRPCNurbsCurve:
749749
)
750750

751751

752+
def from_grpc_nurbs_curve_to_nurbs_curve(curve: GRPCNurbsCurve) -> "NURBSCurve":
753+
"""Convert a NURBS curve gRPC message to a ``NURBSCurve``.
754+
755+
Parameters
756+
----------
757+
curve : GRPCNurbsCurve
758+
Geometry service gRPC NURBS curve message.
759+
760+
Returns
761+
-------
762+
NURBSCurve
763+
Resulting converted NURBS curve.
764+
"""
765+
from ansys.geometry.core.shapes.curves.nurbs import NURBSCurve
766+
767+
# Extract control points
768+
control_points = [from_grpc_point_to_point3d(cp.position) for cp in curve.control_points]
769+
770+
# Extract weights
771+
weights = [cp.weight for cp in curve.control_points]
772+
773+
# Extract degree
774+
degree = curve.nurbs_data.degree
775+
776+
# Convert gRPC knots to full knot vector
777+
knots = []
778+
for grpc_knot in curve.nurbs_data.knots:
779+
knots.extend([grpc_knot.parameter] * grpc_knot.multiplicity)
780+
781+
# Create and return the NURBS curve
782+
return NURBSCurve.from_control_points(
783+
control_points=control_points,
784+
degree=degree,
785+
knots=knots,
786+
weights=weights,
787+
)
788+
789+
752790
def from_knots_to_grpc_knots(knots: list[float]) -> list[GRPCKnot]:
753791
"""Convert a list of knots to a list of gRPC knot messages.
754792
@@ -813,6 +851,8 @@ def from_grpc_curve_to_curve(curve: GRPCCurveGeometry) -> "Curve":
813851
result = Circle(origin, curve.radius, reference, axis)
814852
elif curve.major_radius != 0 and curve.minor_radius != 0:
815853
result = Ellipse(origin, curve.major_radius, curve.minor_radius, reference, axis)
854+
elif curve.nurbs_curve.nurbs_data.degree != 0:
855+
result = from_grpc_nurbs_curve_to_nurbs_curve(curve.nurbs_curve)
816856
elif curve.direction is not None:
817857
result = Line(
818858
origin,

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ def create_sweeping_profile_body(self, **kwargs) -> dict: # noqa: D102
6464
def create_sweeping_chain(self, **kwargs) -> dict: # noqa: D102
6565
raise NotImplementedError
6666

67+
@protect_grpc
68+
def sweep_with_guide(self, **kwargs) -> dict: # noqa: D102
69+
raise NotImplementedError
70+
6771
@protect_grpc
6872
def create_extruded_body_from_face_profile(self, **kwargs) -> dict: # noqa: D102
6973
raise NotImplementedError

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
# SOFTWARE.
2222
"""Provides for managing components."""
2323

24+
from dataclasses import dataclass
2425
from enum import Enum, unique
2526
from functools import cached_property
2627
from typing import TYPE_CHECKING, Any, Optional, Union
@@ -135,6 +136,34 @@ def get_multiplier(self) -> int:
135136
return 1 if self is ExtrusionDirection.POSITIVE else -1
136137

137138

139+
@dataclass
140+
class SweepWithGuideData:
141+
"""Data class for sweep with guide parameters.
142+
143+
Parameters
144+
----------
145+
name : str
146+
Name of the body to be generated by the sweep operation.
147+
parent_id : str
148+
ID of the parent component.
149+
sketch : Sketch
150+
Sketch to use for the sweep operation.
151+
path : TrimmedCurve
152+
Path to sweep along.
153+
guide : TrimmedCurve
154+
Guide curve for the sweep operation.
155+
tight_tolerance : bool
156+
Whether to use tight tolerance for the sweep operation.
157+
"""
158+
159+
name: str
160+
parent_id: str
161+
sketch: Sketch
162+
path: TrimmedCurve
163+
guide: TrimmedCurve
164+
tight_tolerance: bool = False
165+
166+
138167
class Component:
139168
"""Provides for creating and managing a component.
140169
@@ -707,6 +736,32 @@ def sweep_chain(
707736
)
708737
return self.__build_body_from_response(response)
709738

739+
@min_backend_version(26, 1, 0)
740+
@check_input_types
741+
@ensure_design_is_active
742+
def sweep_with_guide(self, sweep_data: list[SweepWithGuideData]) -> list[Body]:
743+
"""Create a body by sweeping a sketch along a path with a guide curve.
744+
745+
The newly created body is placed under this component within the design assembly.
746+
747+
Parameters
748+
----------
749+
sweep_data: list[SweepWithGuideData]
750+
Data for the sweep operation, including the sketch, path, and guide curve.
751+
752+
Returns
753+
-------
754+
list[Body]
755+
Created bodies from the given sweep data.
756+
757+
Warnings
758+
--------
759+
This method is only available starting on Ansys release 26R1.
760+
"""
761+
self._grpc_client.log.debug(f"Sweeping the profile {self.id}. Creating body...")
762+
response = self._grpc_client.services.bodies.sweep_with_guide(sweep_data=sweep_data)
763+
return [self.__build_body_from_response(body_data) for body_data in response.get("bodies")]
764+
710765
@min_backend_version(24, 2, 0)
711766
@check_input_types
712767
def revolve_sketch(

tests/integration/test_design.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
SurfaceType,
4242
)
4343
from ansys.geometry.core.designer.body import CollisionType, FillStyle, MasterBody
44+
from ansys.geometry.core.designer.component import SweepWithGuideData
4445
from ansys.geometry.core.designer.face import FaceLoopType
4546
from ansys.geometry.core.designer.part import MasterComponent, Part
4647
from ansys.geometry.core.errors import GeometryExitedError, GeometryRuntimeError
@@ -71,6 +72,7 @@
7172
Torus,
7273
)
7374
from ansys.geometry.core.shapes.box_uv import BoxUV
75+
from ansys.geometry.core.shapes.curves.nurbs import NURBSCurve
7476
from ansys.geometry.core.shapes.parameterization import (
7577
Interval,
7678
)
@@ -2809,6 +2811,55 @@ def test_sweep_chain(modeler: Modeler):
28092811
assert body.volume.m == 0
28102812

28112813

2814+
def test_sweep_with_guide(modeler: Modeler):
2815+
"""Test creating a body by sweeping a profile with a guide curve."""
2816+
design = modeler.create_design("SweepWithGuide")
2817+
2818+
# Create path points for the sweep path
2819+
path_points = [
2820+
Point3D([0.0, 0.0, 0.15]),
2821+
Point3D([0.05, 0.0, 0.1]),
2822+
Point3D([0.1, 0.0, 0.05]),
2823+
Point3D([0.15, 0.0, 0.1]),
2824+
Point3D([0.2, 0.0, 0.15]),
2825+
]
2826+
nurbs_path = NURBSCurve.fit_curve_from_points(path_points, degree=3)
2827+
n_l_points = len(path_points)
2828+
path_interval = Interval(1.0 / (n_l_points - 1), (n_l_points - 2.0) / (n_l_points - 1))
2829+
trimmed_path = nurbs_path.trim(path_interval)
2830+
2831+
# Create a simple circular profile sketch
2832+
profile_plane = Plane(origin=path_points[1])
2833+
profile_sketch = Sketch(profile_plane)
2834+
profile_sketch.circle(Point2D([0, 0]), 0.01) # 0.01 radius
2835+
2836+
# Create guide curve points (offset from path)
2837+
guide_points = [Point3D([p.x.m, p.y.m + 0.01, p.z.m]) for p in path_points]
2838+
guide_curve = NURBSCurve.fit_curve_from_points(guide_points, degree=3)
2839+
guide_interval = Interval(1.0 / (n_l_points - 1), (n_l_points - 2.0) / (n_l_points - 1))
2840+
trimmed_guide = guide_curve.trim(guide_interval)
2841+
2842+
# Sweep the profile along the path with the guide curve
2843+
sweep_data = [
2844+
SweepWithGuideData(
2845+
name="SweptBody",
2846+
parent_id=design.id,
2847+
sketch=profile_sketch,
2848+
path=trimmed_path,
2849+
guide=trimmed_guide,
2850+
tight_tolerance=True,
2851+
)
2852+
]
2853+
sweep_body = design.sweep_with_guide(sweep_data=sweep_data)[0]
2854+
2855+
assert sweep_body is not None
2856+
assert sweep_body.name == "SweptBody"
2857+
assert sweep_body.is_surface
2858+
assert len(sweep_body.faces) == 1
2859+
assert len(sweep_body.edges) == 2
2860+
assert len(sweep_body.vertices) == 0
2861+
2862+
28122863
def test_create_body_from_loft_profile(modeler: Modeler):
28132864
"""Test the ``create_body_from_loft_profile()`` method to create a vase
28142865
shape.

0 commit comments

Comments
 (0)