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/2252.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Loft profiles with guides
5 changes: 5 additions & 0 deletions src/ansys/geometry/core/_grpc/_services/base/bodies.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,8 @@ def get_tesellation_with_options(self, **kwargs) -> dict:
def boolean(self, **kwargs) -> dict:
"""Boolean operation."""
pass

@abstractmethod
def create_body_from_loft_profiles_with_guides(self, **kwargs) -> dict:
"""Create a body from loft profiles with guides."""
pass
42 changes: 42 additions & 0 deletions src/ansys/geometry/core/_grpc/_services/v0/bodies.py
Original file line number Diff line number Diff line change
Expand Up @@ -759,3 +759,45 @@ def boolean(self, **kwargs) -> dict: # noqa: D102

# Return the response - formatted as a dictionary
return {}

@protect_grpc
def create_body_from_loft_profiles_with_guides(self, **kwargs) -> dict: # noqa: D102
from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier
from ansys.api.geometry.v0.bodies_pb2 import (
CreateBodyFromLoftWithGuidesRequest,
CreateBodyFromLoftWithGuidesRequestData,
)
from ansys.api.geometry.v0.models_pb2 import TrimmedCurveList

# Create request object - assumes all inputs are valid and of the proper type
request = CreateBodyFromLoftWithGuidesRequest(
request_data=[
CreateBodyFromLoftWithGuidesRequestData(
name=kwargs["name"],
parent=EntityIdentifier(id=kwargs["parent_id"]),
profiles=[
TrimmedCurveList(
curves=[from_trimmed_curve_to_grpc_trimmed_curve(tc) for tc in profile]
)
for profile in kwargs["profiles"]
],
guides=TrimmedCurveList(
curves=[
from_trimmed_curve_to_grpc_trimmed_curve(tc) for tc in kwargs["guides"]
]
),
)
]
)

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

# Return the response - formatted as a dictionary
new_body = response.created_bodies[0]
return {
"id": new_body.id,
"name": new_body.name,
"master_id": new_body.master_id,
"is_surface": new_body.is_surface,
}
4 changes: 4 additions & 0 deletions src/ansys/geometry/core/_grpc/_services/v1/bodies.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,7 @@ def get_tesellation_with_options(self, **kwargs) -> dict: # noqa: D102
@protect_grpc
def boolean(self, **kwargs) -> dict: # noqa: D102
raise NotImplementedError

@protect_grpc
def create_body_from_loft_profiles_with_guides(self, **kwargs) -> dict: # noqa: D102
raise NotImplementedError
35 changes: 35 additions & 0 deletions src/ansys/geometry/core/designer/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,41 @@ def create_body_from_loft_profile(
)
return self.__build_body_from_response(response)

@check_input_types
@ensure_design_is_active
@min_backend_version(26, 1, 0)
def create_body_from_loft_profiles_with_guides(
self,
name: str,
profiles: list[list[TrimmedCurve]],
guides: list[TrimmedCurve],
) -> Body:
"""Create a lofted body from a collection of trimmed curves with guide curves.

Parameters
----------
name : str
Name of the lofted body.
profiles : list[list[TrimmedCurve]]
Collection of lists of trimmed curves (profiles) defining the lofted body's shape.
guides : list[TrimmedCurve]
Collection of guide curves to influence the lofting process.

Returns
-------
Body
Created lofted body object.
"""
self._grpc_client.log.debug(f"Creating a loft profile body with guides on {self.id}.")
response = self._grpc_client._services.bodies.create_body_from_loft_profiles_with_guides(
name=name,
parent_id=self.id,
profiles=profiles,
guides=guides,
)

return self.__build_body_from_response(response)

@check_input_types
@ensure_design_is_active
def create_surface(self, name: str, sketch: Sketch) -> Body:
Expand Down
48 changes: 48 additions & 0 deletions tests/integration/test_design.py
Original file line number Diff line number Diff line change
Expand Up @@ -2915,6 +2915,54 @@ def test_create_body_from_loft_profile(modeler: Modeler):
assert result.volume.m == 0


def test_create_body_from_loft_profile_with_guides(modeler: Modeler):
"""Test the ``create_body_from_loft_profile_with_guides()`` method to create a vase
shape.
"""
design_sketch = modeler.create_design("LoftProfileWithGuides")

circle1 = Circle(origin=[0, 0, 0], radius=8)
circle2 = Circle(origin=[0, 0, 10], radius=10)

profile1 = circle1.trim(Interval(0, 2 * np.pi))
profile2 = circle2.trim(Interval(0, 2 * np.pi))

def circle_point(center, radius, angle_deg):
# Returns a point on the circle at the given angle
angle_rad = np.deg2rad(angle_deg)
return Point3D(
[
center[0] + radius.m * np.cos(angle_rad),
center[1] + radius.m * np.sin(angle_rad),
center[2],
]
)

angles = [0, 90, 180, 270]
guide_curves = []

for angle in angles:
pt1 = circle_point(circle1.origin, circle1.radius, angle)
pt2 = circle_point(circle2.origin, circle2.radius, angle)

# Create a guide curve (e.g., a line or spline) between pt1 and pt2
guide_curve = NURBSCurve.fit_curve_from_points([pt1, pt2], 1).trim(Interval(0, 1))
guide_curves.append(guide_curve)

# Call the method
result = design_sketch.create_body_from_loft_profiles_with_guides(
"vase", [[profile1], [profile2]], guide_curves
)

# Assert that the resulting body has only one face.
assert len(result.faces) == 1

# check volume of body
# expected is 0 since it's not a closed surface
assert result.volume.m == 0
assert result.is_surface is True


def test_revolve_sketch(modeler: Modeler):
"""Test revolving a circular profile for a quarter donut."""
# Initialize the donut sketch design
Expand Down
Loading