Skip to content

Commit 6d62b4c

Browse files
jacobrkerstetterpre-commit-ci[bot]pyansys-ci-botRobPasMue
authored
feat: loft profiles with guides (#2252)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: pyansys-ci-bot <[email protected]> Co-authored-by: Roberto Pastor Muela <[email protected]>
1 parent 0bfefb6 commit 6d62b4c

File tree

6 files changed

+135
-0
lines changed

6 files changed

+135
-0
lines changed

doc/changelog.d/2252.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Loft profiles with guides

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,8 @@ def get_tesellation_with_options(self, **kwargs) -> dict:
218218
def boolean(self, **kwargs) -> dict:
219219
"""Boolean operation."""
220220
pass
221+
222+
@abstractmethod
223+
def create_body_from_loft_profiles_with_guides(self, **kwargs) -> dict:
224+
"""Create a body from loft profiles with guides."""
225+
pass

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,3 +759,45 @@ def boolean(self, **kwargs) -> dict: # noqa: D102
759759

760760
# Return the response - formatted as a dictionary
761761
return {}
762+
763+
@protect_grpc
764+
def create_body_from_loft_profiles_with_guides(self, **kwargs) -> dict: # noqa: D102
765+
from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier
766+
from ansys.api.geometry.v0.bodies_pb2 import (
767+
CreateBodyFromLoftWithGuidesRequest,
768+
CreateBodyFromLoftWithGuidesRequestData,
769+
)
770+
from ansys.api.geometry.v0.models_pb2 import TrimmedCurveList
771+
772+
# Create request object - assumes all inputs are valid and of the proper type
773+
request = CreateBodyFromLoftWithGuidesRequest(
774+
request_data=[
775+
CreateBodyFromLoftWithGuidesRequestData(
776+
name=kwargs["name"],
777+
parent=EntityIdentifier(id=kwargs["parent_id"]),
778+
profiles=[
779+
TrimmedCurveList(
780+
curves=[from_trimmed_curve_to_grpc_trimmed_curve(tc) for tc in profile]
781+
)
782+
for profile in kwargs["profiles"]
783+
],
784+
guides=TrimmedCurveList(
785+
curves=[
786+
from_trimmed_curve_to_grpc_trimmed_curve(tc) for tc in kwargs["guides"]
787+
]
788+
),
789+
)
790+
]
791+
)
792+
793+
# Call the gRPC service
794+
response = self.stub.CreateBodyFromLoftWithGuides(request)
795+
796+
# Return the response - formatted as a dictionary
797+
new_body = response.created_bodies[0]
798+
return {
799+
"id": new_body.id,
800+
"name": new_body.name,
801+
"master_id": new_body.master_id,
802+
"is_surface": new_body.is_surface,
803+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,7 @@ def get_tesellation_with_options(self, **kwargs) -> dict: # noqa: D102
191191
@protect_grpc
192192
def boolean(self, **kwargs) -> dict: # noqa: D102
193193
raise NotImplementedError
194+
195+
@protect_grpc
196+
def create_body_from_loft_profiles_with_guides(self, **kwargs) -> dict: # noqa: D102
197+
raise NotImplementedError

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -965,6 +965,41 @@ def create_body_from_loft_profile(
965965
)
966966
return self.__build_body_from_response(response)
967967

968+
@check_input_types
969+
@ensure_design_is_active
970+
@min_backend_version(26, 1, 0)
971+
def create_body_from_loft_profiles_with_guides(
972+
self,
973+
name: str,
974+
profiles: list[list[TrimmedCurve]],
975+
guides: list[TrimmedCurve],
976+
) -> Body:
977+
"""Create a lofted body from a collection of trimmed curves with guide curves.
978+
979+
Parameters
980+
----------
981+
name : str
982+
Name of the lofted body.
983+
profiles : list[list[TrimmedCurve]]
984+
Collection of lists of trimmed curves (profiles) defining the lofted body's shape.
985+
guides : list[TrimmedCurve]
986+
Collection of guide curves to influence the lofting process.
987+
988+
Returns
989+
-------
990+
Body
991+
Created lofted body object.
992+
"""
993+
self._grpc_client.log.debug(f"Creating a loft profile body with guides on {self.id}.")
994+
response = self._grpc_client._services.bodies.create_body_from_loft_profiles_with_guides(
995+
name=name,
996+
parent_id=self.id,
997+
profiles=profiles,
998+
guides=guides,
999+
)
1000+
1001+
return self.__build_body_from_response(response)
1002+
9681003
@check_input_types
9691004
@ensure_design_is_active
9701005
def create_surface(self, name: str, sketch: Sketch) -> Body:

tests/integration/test_design.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2915,6 +2915,54 @@ def test_create_body_from_loft_profile(modeler: Modeler):
29152915
assert result.volume.m == 0
29162916

29172917

2918+
def test_create_body_from_loft_profile_with_guides(modeler: Modeler):
2919+
"""Test the ``create_body_from_loft_profile_with_guides()`` method to create a vase
2920+
shape.
2921+
"""
2922+
design_sketch = modeler.create_design("LoftProfileWithGuides")
2923+
2924+
circle1 = Circle(origin=[0, 0, 0], radius=8)
2925+
circle2 = Circle(origin=[0, 0, 10], radius=10)
2926+
2927+
profile1 = circle1.trim(Interval(0, 2 * np.pi))
2928+
profile2 = circle2.trim(Interval(0, 2 * np.pi))
2929+
2930+
def circle_point(center, radius, angle_deg):
2931+
# Returns a point on the circle at the given angle
2932+
angle_rad = np.deg2rad(angle_deg)
2933+
return Point3D(
2934+
[
2935+
center[0] + radius.m * np.cos(angle_rad),
2936+
center[1] + radius.m * np.sin(angle_rad),
2937+
center[2],
2938+
]
2939+
)
2940+
2941+
angles = [0, 90, 180, 270]
2942+
guide_curves = []
2943+
2944+
for angle in angles:
2945+
pt1 = circle_point(circle1.origin, circle1.radius, angle)
2946+
pt2 = circle_point(circle2.origin, circle2.radius, angle)
2947+
2948+
# Create a guide curve (e.g., a line or spline) between pt1 and pt2
2949+
guide_curve = NURBSCurve.fit_curve_from_points([pt1, pt2], 1).trim(Interval(0, 1))
2950+
guide_curves.append(guide_curve)
2951+
2952+
# Call the method
2953+
result = design_sketch.create_body_from_loft_profiles_with_guides(
2954+
"vase", [[profile1], [profile2]], guide_curves
2955+
)
2956+
2957+
# Assert that the resulting body has only one face.
2958+
assert len(result.faces) == 1
2959+
2960+
# check volume of body
2961+
# expected is 0 since it's not a closed surface
2962+
assert result.volume.m == 0
2963+
assert result.is_surface is True
2964+
2965+
29182966
def test_revolve_sketch(modeler: Modeler):
29192967
"""Test revolving a circular profile for a quarter donut."""
29202968
# Initialize the donut sketch design

0 commit comments

Comments
 (0)