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/2179.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Body sweep_with_guide
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 @@ -59,6 +59,11 @@ def create_sweeping_chain(self, **kwargs) -> dict:
"""Create a sweeping chain."""
pass

@abstractmethod
def sweep_with_guide(self, **kwargs) -> dict:
"""Sweep with a guide."""
pass

@abstractmethod
def create_extruded_body_from_face_profile(self, **kwargs) -> dict:
"""Create an extruded body from a face profile."""
Expand Down
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 @@ -171,6 +171,48 @@ def create_sweeping_chain(self, **kwargs) -> dict: # noqa: D102
"is_surface": resp.is_surface,
}

@protect_grpc
def sweep_with_guide(self, **kwargs) -> dict: # noqa: D102
from ansys.api.dbu.v0.dbumodels_pb2 import EntityIdentifier
from ansys.api.geometry.v0.bodies_pb2 import (
SweepWithGuideRequest,
SweepWithGuideRequestData,
)

# Create request object - assumes all inputs are valid and of the proper type
request = SweepWithGuideRequest(
request_data=[
SweepWithGuideRequestData(
name=data.name,
parent=EntityIdentifier(id=data.parent_id),
plane=from_plane_to_grpc_plane(data.sketch.plane),
geometries=from_sketch_shapes_to_grpc_geometries(
data.sketch.plane, data.sketch.edges, data.sketch.faces
),
path=from_trimmed_curve_to_grpc_trimmed_curve(data.path),
guide=from_trimmed_curve_to_grpc_trimmed_curve(data.guide),
tight_tolerance=data.tight_tolerance,
)
for data in kwargs["sweep_data"]
],
)

# Call the gRPC service
resp = self.stub.SweepWithGuide(request=request)

# Return the response - formatted as a dictionary
return {
"bodies": [
{
"id": body.id,
"name": body.name,
"master_id": body.master_id,
"is_surface": body.is_surface,
}
]
for body in resp.bodies
}

@protect_grpc
def create_extruded_body_from_face_profile(self, **kwargs) -> dict: # noqa: D102
from ansys.api.geometry.v0.bodies_pb2 import CreateExtrudedBodyFromFaceProfileRequest
Expand Down
40 changes: 40 additions & 0 deletions src/ansys/geometry/core/_grpc/_services/v0/conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,44 @@ def from_nurbs_curve_to_grpc_nurbs_curve(curve: "NURBSCurve") -> GRPCNurbsCurve:
)


def from_grpc_nurbs_curve_to_nurbs_curve(curve: GRPCNurbsCurve) -> "NURBSCurve":
"""Convert a NURBS curve gRPC message to a ``NURBSCurve``.

Parameters
----------
curve : GRPCNurbsCurve
Geometry service gRPC NURBS curve message.

Returns
-------
NURBSCurve
Resulting converted NURBS curve.
"""
from ansys.geometry.core.shapes.curves.nurbs import NURBSCurve

# Extract control points
control_points = [from_grpc_point_to_point3d(cp.position) for cp in curve.control_points]

# Extract weights
weights = [cp.weight for cp in curve.control_points]

# Extract degree
degree = curve.nurbs_data.degree

# Convert gRPC knots to full knot vector
knots = []
for grpc_knot in curve.nurbs_data.knots:
knots.extend([grpc_knot.parameter] * grpc_knot.multiplicity)

# Create and return the NURBS curve
return NURBSCurve.from_control_points(
control_points=control_points,
degree=degree,
knots=knots,
weights=weights,
)


def from_knots_to_grpc_knots(knots: list[float]) -> list[GRPCKnot]:
"""Convert a list of knots to a list of gRPC knot messages.

Expand Down Expand Up @@ -813,6 +851,8 @@ def from_grpc_curve_to_curve(curve: GRPCCurveGeometry) -> "Curve":
result = Circle(origin, curve.radius, reference, axis)
elif curve.major_radius != 0 and curve.minor_radius != 0:
result = Ellipse(origin, curve.major_radius, curve.minor_radius, reference, axis)
elif curve.nurbs_curve.nurbs_data.degree != 0:
result = from_grpc_nurbs_curve_to_nurbs_curve(curve.nurbs_curve)
elif curve.direction is not None:
result = Line(
origin,
Expand Down
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 @@ -64,6 +64,10 @@ def create_sweeping_profile_body(self, **kwargs) -> dict: # noqa: D102
def create_sweeping_chain(self, **kwargs) -> dict: # noqa: D102
raise NotImplementedError

@protect_grpc
def sweep_with_guide(self, **kwargs) -> dict: # noqa: D102
raise NotImplementedError

@protect_grpc
def create_extruded_body_from_face_profile(self, **kwargs) -> dict: # noqa: D102
raise NotImplementedError
Expand Down
55 changes: 55 additions & 0 deletions src/ansys/geometry/core/designer/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# SOFTWARE.
"""Provides for managing components."""

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


@dataclass
class SweepWithGuideData:
"""Data class for sweep with guide parameters.

Parameters
----------
name : str
Name of the body to be generated by the sweep operation.
parent_id : str
ID of the parent component.
sketch : Sketch
Sketch to use for the sweep operation.
path : TrimmedCurve
Path to sweep along.
guide : TrimmedCurve
Guide curve for the sweep operation.
tight_tolerance : bool
Whether to use tight tolerance for the sweep operation.
"""

name: str
parent_id: str
sketch: Sketch
path: TrimmedCurve
guide: TrimmedCurve
tight_tolerance: bool = False


class Component:
"""Provides for creating and managing a component.

Expand Down Expand Up @@ -707,6 +736,32 @@ def sweep_chain(
)
return self.__build_body_from_response(response)

@min_backend_version(26, 1, 0)
@check_input_types
@ensure_design_is_active
def sweep_with_guide(self, sweep_data: list[SweepWithGuideData]) -> list[Body]:
"""Create a body by sweeping a sketch along a path with a guide curve.

The newly created body is placed under this component within the design assembly.

Parameters
----------
sweep_data: list[SweepWithGuideData]
Data for the sweep operation, including the sketch, path, and guide curve.

Returns
-------
list[Body]
Created bodies from the given sweep data.

Warnings
--------
This method is only available starting on Ansys release 26R1.
"""
self._grpc_client.log.debug(f"Sweeping the profile {self.id}. Creating body...")
response = self._grpc_client.services.bodies.sweep_with_guide(sweep_data=sweep_data)
return [self.__build_body_from_response(body_data) for body_data in response.get("bodies")]

@min_backend_version(24, 2, 0)
@check_input_types
def revolve_sketch(
Expand Down
51 changes: 51 additions & 0 deletions tests/integration/test_design.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
SurfaceType,
)
from ansys.geometry.core.designer.body import CollisionType, FillStyle, MasterBody
from ansys.geometry.core.designer.component import SweepWithGuideData
from ansys.geometry.core.designer.face import FaceLoopType
from ansys.geometry.core.designer.part import MasterComponent, Part
from ansys.geometry.core.errors import GeometryExitedError, GeometryRuntimeError
Expand Down Expand Up @@ -71,6 +72,7 @@
Torus,
)
from ansys.geometry.core.shapes.box_uv import BoxUV
from ansys.geometry.core.shapes.curves.nurbs import NURBSCurve
from ansys.geometry.core.shapes.parameterization import (
Interval,
)
Expand Down Expand Up @@ -2809,6 +2811,55 @@ def test_sweep_chain(modeler: Modeler):
assert body.volume.m == 0


def test_sweep_with_guide(modeler: Modeler):
"""Test creating a body by sweeping a profile with a guide curve."""
design = modeler.create_design("SweepWithGuide")

# Create path points for the sweep path
path_points = [
Point3D([0.0, 0.0, 0.15]),
Point3D([0.05, 0.0, 0.1]),
Point3D([0.1, 0.0, 0.05]),
Point3D([0.15, 0.0, 0.1]),
Point3D([0.2, 0.0, 0.15]),
]
nurbs_path = NURBSCurve.fit_curve_from_points(path_points, degree=3)
n_l_points = len(path_points)
path_interval = Interval(1.0 / (n_l_points - 1), (n_l_points - 2.0) / (n_l_points - 1))
trimmed_path = nurbs_path.trim(path_interval)

# Create a simple circular profile sketch
profile_plane = Plane(origin=path_points[1])
profile_sketch = Sketch(profile_plane)
profile_sketch.circle(Point2D([0, 0]), 0.01) # 0.01 radius

# Create guide curve points (offset from path)
guide_points = [Point3D([p.x.m, p.y.m + 0.01, p.z.m]) for p in path_points]
guide_curve = NURBSCurve.fit_curve_from_points(guide_points, degree=3)
guide_interval = Interval(1.0 / (n_l_points - 1), (n_l_points - 2.0) / (n_l_points - 1))
trimmed_guide = guide_curve.trim(guide_interval)

# Sweep the profile along the path with the guide curve
sweep_data = [
SweepWithGuideData(
name="SweptBody",
parent_id=design.id,
sketch=profile_sketch,
path=trimmed_path,
guide=trimmed_guide,
tight_tolerance=True,
)
]
sweep_body = design.sweep_with_guide(sweep_data=sweep_data)[0]

assert sweep_body is not None
assert sweep_body.name == "SweptBody"
assert sweep_body.is_surface
assert len(sweep_body.faces) == 1
assert len(sweep_body.edges) == 2
assert len(sweep_body.vertices) == 0


def test_create_body_from_loft_profile(modeler: Modeler):
"""Test the ``create_body_from_loft_profile()`` method to create a vase
shape.
Expand Down