Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
dd66488
nurbs surface trim working
jacobrkerstetter Sep 29, 2025
8770b05
Merge branch 'main' of https://github.com/ansys/pyansys-geometry into…
jacobrkerstetter Sep 30, 2025
978d81f
chore: auto fixes from pre-commit hooks
pre-commit-ci[bot] Sep 30, 2025
860d5e3
remove test export
jacobrkerstetter Sep 30, 2025
feb3a8c
Merge branch 'feat/nurbs_surface_body_creation' of https://github.com…
jacobrkerstetter Sep 30, 2025
86764b6
chore: adding changelog file 2273.added.md [dependabot-skip]
pyansys-ci-bot Sep 30, 2025
92c7f2b
Merge branch 'main' into feat/nurbs_surface_body_creation
jacobrkerstetter Sep 30, 2025
f287eb5
bump protos
jacobrkerstetter Sep 30, 2025
a38dddf
Merge branch 'feat/nurbs_surface_body_creation' of https://github.com…
jacobrkerstetter Sep 30, 2025
89bf3b7
Merge branch 'main' into feat/nurbs_surface_body_creation
RobPasMue Oct 1, 2025
905f247
fix tests for backwards compatibility
jacobrkerstetter Oct 1, 2025
636a8b1
chore: auto fixes from pre-commit hooks
pre-commit-ci[bot] Oct 1, 2025
a6d4d54
fixed skipped test name
jacobrkerstetter Oct 1, 2025
ed2f006
Merge branch 'feat/nurbs_surface_body_creation' of https://github.com…
jacobrkerstetter Oct 1, 2025
dc238f7
fix: resolving comments
jacobrkerstetter Oct 1, 2025
ca2f3d3
chore: auto fixes from pre-commit hooks
pre-commit-ci[bot] Oct 1, 2025
af001e4
Update src/ansys/geometry/core/shapes/surfaces/nurbs.py
jacobrkerstetter Oct 1, 2025
8803f36
Update src/ansys/geometry/core/shapes/surfaces/nurbs.py
jacobrkerstetter Oct 1, 2025
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/2273.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NURBS surface body creation
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ classifiers = [
]

dependencies = [
"ansys-api-geometry==0.4.77",
"ansys-api-geometry==0.4.78",
"ansys-tools-path>=0.3,<1",
"beartype>=0.11.0,<0.22",
"geomdl>=5,<6",
Expand Down
57 changes: 57 additions & 0 deletions src/ansys/geometry/core/_grpc/_services/v0/conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
MaterialProperty as GRPCMaterialProperty,
Matrix as GRPCMatrix,
NurbsCurve as GRPCNurbsCurve,
NurbsSurface as GRPCNurbsSurface,
Plane as GRPCPlane,
Point as GRPCPoint,
Polygon as GRPCPolygon,
Expand All @@ -58,6 +59,7 @@

from ansys.geometry.core.errors import GeometryRuntimeError
from ansys.geometry.core.misc.checks import graphics_required
from ansys.geometry.core.shapes.surfaces.nurbs import NURBSSurface

if TYPE_CHECKING: # pragma: no cover
import pyvista as pv
Expand Down Expand Up @@ -770,6 +772,53 @@ def from_nurbs_curve_to_grpc_nurbs_curve(curve: "NURBSCurve") -> GRPCNurbsCurve:
)


def from_nurbs_surface_to_grpc_nurbs_surface(surface: "NURBSSurface") -> GRPCNurbsSurface:
"""Convert a ``NURBSSurface`` to a NURBS surface gRPC message.

Parameters
----------
surface : NURBSSurface
Surface to convert.

Returns
-------
GRPCNurbsSurface
Geometry service gRPC ``NURBSSurface`` message.
"""
from ansys.api.geometry.v0.models_pb2 import (
ControlPoint as GRPCControlPoint,
NurbsData as GRPCNurbsData,
)

# Convert control points
control_points = [
GRPCControlPoint(
position=from_point3d_to_grpc_point(point),
weight=weight,
)
for weight, point in zip(surface.weights, surface.control_points)
]

# Convert nurbs data
nurbs_data_u = GRPCNurbsData(
degree=surface.degree_u,
knots=from_knots_to_grpc_knots(surface.knotvector_u),
order=surface.degree_u + 1,
)

nurbs_data_v = GRPCNurbsData(
degree=surface.degree_v,
knots=from_knots_to_grpc_knots(surface.knotvector_v),
order=surface.degree_v + 1,
)

return GRPCNurbsSurface(
control_points=control_points,
nurbs_data_u=nurbs_data_u,
nurbs_data_v=nurbs_data_v,
)


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

Expand Down Expand Up @@ -976,6 +1025,14 @@ def from_surface_to_grpc_surface(surface: "Surface") -> tuple[GRPCSurface, GRPCS
minor_radius=surface.minor_radius.m,
)
surface_type = GRPCSurfaceType.SURFACETYPE_TORUS
elif isinstance(surface, NURBSSurface):
grpc_surface = GRPCSurface(
origin=origin,
reference=reference,
axis=axis,
nurbs_surface=from_nurbs_surface_to_grpc_nurbs_surface(surface),
)
surface_type = GRPCSurfaceType.SURFACETYPE_NURBS

return grpc_surface, surface_type

Expand Down
9 changes: 9 additions & 0 deletions src/ansys/geometry/core/designer/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
from ansys.geometry.core.shapes.curves.trimmed_curve import TrimmedCurve
from ansys.geometry.core.shapes.parameterization import Interval
from ansys.geometry.core.shapes.surfaces import TrimmedSurface
from ansys.geometry.core.shapes.surfaces.nurbs import NURBSSurface
from ansys.geometry.core.sketch.sketch import Sketch
from ansys.geometry.core.typing import Real

Expand Down Expand Up @@ -1041,7 +1042,15 @@ def create_body_from_surface(self, name: str, trimmed_surface: TrimmedSurface) -
Warnings
--------
This method is only available starting on Ansys release 25R1.
NURBS surface bodies are only supported starting on Ansys release 26R1.
"""
if (self._grpc_client.backend_version < (26, 1, 0)) and (
isinstance(trimmed_surface.geometry, NURBSSurface)
):
raise ValueError(
"NURBS surface bodies are only supported starting on Ansys release 26R1."
)

self._grpc_client.log.debug(
f"Creating surface body from trimmed surface provided on {self.id}. Creating body..."
)
Expand Down
51 changes: 47 additions & 4 deletions src/ansys/geometry/core/shapes/surfaces/nurbs.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@

from beartype import beartype as check_input_types

from ansys.geometry.core.math import Point3D
from ansys.geometry.core.math import ZERO_POINT3D, Point3D
from ansys.geometry.core.math.constants import UNITVECTOR3D_X, UNITVECTOR3D_Z
from ansys.geometry.core.math.matrix import Matrix44
from ansys.geometry.core.math.vector import UnitVector3D, Vector3D
from ansys.geometry.core.shapes.parameterization import (
Expand Down Expand Up @@ -57,7 +58,13 @@ class NURBSSurface(Surface):

"""

def __init__(self, geomdl_object: "geomdl_nurbs.Surface" = None):
def __init__(
self,
origin: Point3D = ZERO_POINT3D,
reference: UnitVector3D = UNITVECTOR3D_X,
axis: UnitVector3D = UNITVECTOR3D_Z,
geomdl_object: "geomdl_nurbs.Surface" = None,
):
"""Initialize ``NURBSSurface`` class."""
try:
import geomdl.NURBS as geomdl_nurbs # noqa: N811
Expand All @@ -68,6 +75,9 @@ def __init__(self, geomdl_object: "geomdl_nurbs.Surface" = None):
) from e

self._nurbs_surface = geomdl_object if geomdl_object else geomdl_nurbs.Surface()
self._origin = origin
self._reference = reference
self._axis = axis

@property
def geomdl_nurbs_surface(self) -> "geomdl_nurbs.Surface":
Expand Down Expand Up @@ -110,6 +120,21 @@ def weights(self) -> list[Real]:
"""Get the weights of the control points."""
return self._nurbs_surface.weights

@property
def origin(self) -> Point3D:
"""Get the origin of the surface."""
return self._origin

@property
def dir_x(self) -> UnitVector3D:
"""Get the reference direction of the surface."""
return self._reference

@property
def dir_z(self) -> UnitVector3D:
"""Get the axis direction of the surface."""
return self._axis

@classmethod
@check_input_types
def from_control_points(
Expand All @@ -120,6 +145,9 @@ def from_control_points(
knots_v: list[Real],
control_points: list[Point3D],
weights: list[Real] = None,
origin: Point3D = ZERO_POINT3D,
reference: UnitVector3D = UNITVECTOR3D_X,
axis: UnitVector3D = UNITVECTOR3D_Z,
) -> "NURBSSurface":
"""Create a NURBS surface from control points and knot vectors.

Expand All @@ -139,13 +167,19 @@ def from_control_points(
Weights for the control points. If not provided, all weights are set to 1.
delta : float, optional
Evaluation delta for the surface. Default is 0.01.
origin : Point3D, optional
Origin of the surface. Default is (0, 0, 0).
reference : UnitVector3D, optional
Reference direction of the surface. Default is (1, 0, 0).
axis : UnitVector3D, optional
Axis direction of the surface. Default is (0, 0, 1).

Returns
-------
NURBSSurface
Created NURBS surface.
"""
nurbs_surface = cls()
nurbs_surface = cls(origin, reference, axis)
nurbs_surface._nurbs_surface.degree_u = degree_u
nurbs_surface._nurbs_surface.degree_v = degree_v

Expand Down Expand Up @@ -182,6 +216,9 @@ def fit_surface_from_points(
size_v: int,
degree_u: int,
degree_v: int,
origin: Point3D = ZERO_POINT3D,
reference: UnitVector3D = UNITVECTOR3D_X,
axis: UnitVector3D = UNITVECTOR3D_Z,
) -> "NURBSSurface":
"""Fit a NURBS surface to a set of points.

Expand All @@ -197,6 +234,12 @@ def fit_surface_from_points(
Degree of the surface in the U direction.
degree_v : int
Degree of the surface in the V direction.
origin : Point3D, optional
Origin of the surface. Default is (0, 0, 0).
reference : UnitVector3D, optional
Reference direction of the surface. Default is (1, 0, 0).
axis : UnitVector3D, optional
Axis direction of the surface. Default is (0, 0, 1).

Returns
-------
Expand All @@ -215,7 +258,7 @@ def fit_surface_from_points(
degree_v=degree_v,
)

nurbs_surface = cls()
nurbs_surface = cls(origin, reference, axis)
nurbs_surface._nurbs_surface.degree_u = degree_u
nurbs_surface._nurbs_surface.degree_v = degree_v

Expand Down
4 changes: 4 additions & 0 deletions tests/_incompatible_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ backends:
# Export body facets add in 26.1
- tests/integration/test_design.py::test_write_body_facets_on_save[scdocx-None]
- tests/integration/test_design.py::test_write_body_facets_on_save[dsco-DISCO]
# NURBS surface creation only available from 26.1 onwards
- tests/integration/test_design.py::test_nurbs_surface_body_creation

- version: "25.2"
incompatible_tests:
Expand Down Expand Up @@ -330,3 +332,5 @@ backends:
# Export body facets add in 26.1
- tests/integration/test_design.py::test_write_body_facets_on_save[scdocx-None]
- tests/integration/test_design.py::test_write_body_facets_on_save[dsco-DISCO]
# NURBS surface creation only available from 26.1 onwards
- tests/integration/test_design.py::test_nurbs_surface_body_creation
29 changes: 29 additions & 0 deletions tests/integration/test_design.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
from ansys.geometry.core.shapes.parameterization import (
Interval,
)
from ansys.geometry.core.shapes.surfaces.nurbs import NURBSSurface
from ansys.geometry.core.sketch import Sketch

from ..conftest import are_graphics_available
Expand Down Expand Up @@ -3264,6 +3265,34 @@ def test_surface_body_creation(modeler: Modeler):
assert body.faces[0].area.m == pytest.approx(39.4784176044 * 2)


def test_nurbs_surface_body_creation(modeler: Modeler):
"""Test surface body creation from NURBS surfaces."""
design = modeler.create_design("Design1")

points = [
Point3D([0, 0, 0]),
Point3D([0, 1, 1]),
Point3D([0, 2, 0]),
Point3D([1, 0, 1]),
Point3D([1, 1, 2]),
Point3D([1, 2, 1]),
Point3D([2, 0, 0]),
Point3D([2, 1, 1]),
Point3D([2, 2, 0]),
]
degree_u = 2
degree_v = 2
surface = NURBSSurface.fit_surface_from_points(
points=points, size_u=3, size_v=3, degree_u=degree_u, degree_v=degree_v
)

trimmed_surface = surface.trim(BoxUV(Interval(0, 1), Interval(0, 1)))
body = design.create_body_from_surface("nurbs_surface", trimmed_surface)
assert len(design.bodies) == 1
assert body.is_surface
assert body.faces[0].area.m == pytest.approx(7.44626609)


def test_create_surface_from_nurbs_sketch(modeler: Modeler):
"""Test creating a surface from a NURBS sketch."""
design = modeler.create_design("NURBS_Sketch_Surface")
Expand Down