Skip to content

Commit cd1197f

Browse files
jacobrkerstetterpre-commit-ci[bot]pyansys-ci-botRobPasMue
authored
feat: NURBS surface body creation (#2273)
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 6546de8 commit cd1197f

File tree

7 files changed

+148
-5
lines changed

7 files changed

+148
-5
lines changed

doc/changelog.d/2273.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
NURBS surface body creation

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ classifiers = [
2424
]
2525

2626
dependencies = [
27-
"ansys-api-geometry==0.4.77",
27+
"ansys-api-geometry==0.4.78",
2828
"ansys-tools-path>=0.3,<1",
2929
"beartype>=0.11.0,<0.22",
3030
"geomdl>=5,<6",

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
MaterialProperty as GRPCMaterialProperty,
4545
Matrix as GRPCMatrix,
4646
NurbsCurve as GRPCNurbsCurve,
47+
NurbsSurface as GRPCNurbsSurface,
4748
Plane as GRPCPlane,
4849
Point as GRPCPoint,
4950
Polygon as GRPCPolygon,
@@ -58,6 +59,7 @@
5859

5960
from ansys.geometry.core.errors import GeometryRuntimeError
6061
from ansys.geometry.core.misc.checks import graphics_required
62+
from ansys.geometry.core.shapes.surfaces.nurbs import NURBSSurface
6163

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

772774

775+
def from_nurbs_surface_to_grpc_nurbs_surface(surface: "NURBSSurface") -> GRPCNurbsSurface:
776+
"""Convert a ``NURBSSurface`` to a NURBS surface gRPC message.
777+
778+
Parameters
779+
----------
780+
surface : NURBSSurface
781+
Surface to convert.
782+
783+
Returns
784+
-------
785+
GRPCNurbsSurface
786+
Geometry service gRPC ``NURBSSurface`` message.
787+
"""
788+
from ansys.api.geometry.v0.models_pb2 import (
789+
ControlPoint as GRPCControlPoint,
790+
NurbsData as GRPCNurbsData,
791+
)
792+
793+
# Convert control points
794+
control_points = [
795+
GRPCControlPoint(
796+
position=from_point3d_to_grpc_point(point),
797+
weight=weight,
798+
)
799+
for weight, point in zip(surface.weights, surface.control_points)
800+
]
801+
802+
# Convert nurbs data
803+
nurbs_data_u = GRPCNurbsData(
804+
degree=surface.degree_u,
805+
knots=from_knots_to_grpc_knots(surface.knotvector_u),
806+
order=surface.degree_u + 1,
807+
)
808+
809+
nurbs_data_v = GRPCNurbsData(
810+
degree=surface.degree_v,
811+
knots=from_knots_to_grpc_knots(surface.knotvector_v),
812+
order=surface.degree_v + 1,
813+
)
814+
815+
return GRPCNurbsSurface(
816+
control_points=control_points,
817+
nurbs_data_u=nurbs_data_u,
818+
nurbs_data_v=nurbs_data_v,
819+
)
820+
821+
773822
def from_grpc_nurbs_curve_to_nurbs_curve(curve: GRPCNurbsCurve) -> "NURBSCurve":
774823
"""Convert a NURBS curve gRPC message to a ``NURBSCurve``.
775824
@@ -976,6 +1025,14 @@ def from_surface_to_grpc_surface(surface: "Surface") -> tuple[GRPCSurface, GRPCS
9761025
minor_radius=surface.minor_radius.m,
9771026
)
9781027
surface_type = GRPCSurfaceType.SURFACETYPE_TORUS
1028+
elif isinstance(surface, NURBSSurface):
1029+
grpc_surface = GRPCSurface(
1030+
origin=origin,
1031+
reference=reference,
1032+
axis=axis,
1033+
nurbs_surface=from_nurbs_surface_to_grpc_nurbs_surface(surface),
1034+
)
1035+
surface_type = GRPCSurfaceType.SURFACETYPE_NURBS
9791036

9801037
return grpc_surface, surface_type
9811038

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
from ansys.geometry.core.shapes.curves.trimmed_curve import TrimmedCurve
6262
from ansys.geometry.core.shapes.parameterization import Interval
6363
from ansys.geometry.core.shapes.surfaces import TrimmedSurface
64+
from ansys.geometry.core.shapes.surfaces.nurbs import NURBSSurface
6465
from ansys.geometry.core.sketch.sketch import Sketch
6566
from ansys.geometry.core.typing import Real
6667

@@ -1041,7 +1042,15 @@ def create_body_from_surface(self, name: str, trimmed_surface: TrimmedSurface) -
10411042
Warnings
10421043
--------
10431044
This method is only available starting on Ansys release 25R1.
1045+
NURBS surface bodies are only supported starting on Ansys release 26R1.
10441046
"""
1047+
if (self._grpc_client.backend_version < (26, 1, 0)) and (
1048+
isinstance(trimmed_surface.geometry, NURBSSurface)
1049+
):
1050+
raise ValueError(
1051+
"NURBS surface bodies are only supported starting on Ansys release 26R1."
1052+
)
1053+
10451054
self._grpc_client.log.debug(
10461055
f"Creating surface body from trimmed surface provided on {self.id}. Creating body..."
10471056
)

src/ansys/geometry/core/shapes/surfaces/nurbs.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626

2727
from beartype import beartype as check_input_types
2828

29-
from ansys.geometry.core.math import Point3D
29+
from ansys.geometry.core.math import ZERO_POINT3D, Point3D
30+
from ansys.geometry.core.math.constants import UNITVECTOR3D_X, UNITVECTOR3D_Z
3031
from ansys.geometry.core.math.matrix import Matrix44
3132
from ansys.geometry.core.math.vector import UnitVector3D, Vector3D
3233
from ansys.geometry.core.shapes.parameterization import (
@@ -57,7 +58,13 @@ class NURBSSurface(Surface):
5758
5859
"""
5960

60-
def __init__(self, geomdl_object: "geomdl_nurbs.Surface" = None):
61+
def __init__(
62+
self,
63+
origin: Point3D = ZERO_POINT3D,
64+
reference: UnitVector3D = UNITVECTOR3D_X,
65+
axis: UnitVector3D = UNITVECTOR3D_Z,
66+
geomdl_object: "geomdl_nurbs.Surface" = None,
67+
):
6168
"""Initialize ``NURBSSurface`` class."""
6269
try:
6370
import geomdl.NURBS as geomdl_nurbs # noqa: N811
@@ -68,6 +75,9 @@ def __init__(self, geomdl_object: "geomdl_nurbs.Surface" = None):
6875
) from e
6976

7077
self._nurbs_surface = geomdl_object if geomdl_object else geomdl_nurbs.Surface()
78+
self._origin = origin
79+
self._reference = reference
80+
self._axis = axis
7181

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

123+
@property
124+
def origin(self) -> Point3D:
125+
"""Get the origin of the surface."""
126+
return self._origin
127+
128+
@property
129+
def dir_x(self) -> UnitVector3D:
130+
"""Get the reference direction of the surface."""
131+
return self._reference
132+
133+
@property
134+
def dir_z(self) -> UnitVector3D:
135+
"""Get the axis direction of the surface."""
136+
return self._axis
137+
113138
@classmethod
114139
@check_input_types
115140
def from_control_points(
@@ -120,6 +145,9 @@ def from_control_points(
120145
knots_v: list[Real],
121146
control_points: list[Point3D],
122147
weights: list[Real] = None,
148+
origin: Point3D = ZERO_POINT3D,
149+
reference: UnitVector3D = UNITVECTOR3D_X,
150+
axis: UnitVector3D = UNITVECTOR3D_Z,
123151
) -> "NURBSSurface":
124152
"""Create a NURBS surface from control points and knot vectors.
125153
@@ -139,13 +167,19 @@ def from_control_points(
139167
Weights for the control points. If not provided, all weights are set to 1.
140168
delta : float, optional
141169
Evaluation delta for the surface. Default is 0.01.
170+
origin : Point3D, optional
171+
Origin of the surface. Default is (0, 0, 0).
172+
reference : UnitVector3D, optional
173+
Reference direction of the surface. Default is (1, 0, 0).
174+
axis : UnitVector3D, optional
175+
Axis direction of the surface. Default is (0, 0, 1).
142176
143177
Returns
144178
-------
145179
NURBSSurface
146180
Created NURBS surface.
147181
"""
148-
nurbs_surface = cls()
182+
nurbs_surface = cls(origin, reference, axis)
149183
nurbs_surface._nurbs_surface.degree_u = degree_u
150184
nurbs_surface._nurbs_surface.degree_v = degree_v
151185

@@ -182,6 +216,9 @@ def fit_surface_from_points(
182216
size_v: int,
183217
degree_u: int,
184218
degree_v: int,
219+
origin: Point3D = ZERO_POINT3D,
220+
reference: UnitVector3D = UNITVECTOR3D_X,
221+
axis: UnitVector3D = UNITVECTOR3D_Z,
185222
) -> "NURBSSurface":
186223
"""Fit a NURBS surface to a set of points.
187224
@@ -197,6 +234,12 @@ def fit_surface_from_points(
197234
Degree of the surface in the U direction.
198235
degree_v : int
199236
Degree of the surface in the V direction.
237+
origin : Point3D, optional
238+
Origin of the surface. Default is (0, 0, 0).
239+
reference : UnitVector3D, optional
240+
Reference direction of the surface. Default is (1, 0, 0).
241+
axis : UnitVector3D, optional
242+
Axis direction of the surface. Default is (0, 0, 1).
200243
201244
Returns
202245
-------
@@ -215,7 +258,7 @@ def fit_surface_from_points(
215258
degree_v=degree_v,
216259
)
217260

218-
nurbs_surface = cls()
261+
nurbs_surface = cls(origin, reference, axis)
219262
nurbs_surface._nurbs_surface.degree_u = degree_u
220263
nurbs_surface._nurbs_surface.degree_v = degree_v
221264

tests/_incompatible_tests.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,8 @@ backends:
296296
# Export body facets add in 26.1
297297
- tests/integration/test_design.py::test_write_body_facets_on_save[scdocx-None]
298298
- tests/integration/test_design.py::test_write_body_facets_on_save[dsco-DISCO]
299+
# NURBS surface creation only available from 26.1 onwards
300+
- tests/integration/test_design.py::test_nurbs_surface_body_creation
299301

300302
- version: "25.2"
301303
incompatible_tests:
@@ -330,3 +332,5 @@ backends:
330332
# Export body facets add in 26.1
331333
- tests/integration/test_design.py::test_write_body_facets_on_save[scdocx-None]
332334
- tests/integration/test_design.py::test_write_body_facets_on_save[dsco-DISCO]
335+
# NURBS surface creation only available from 26.1 onwards
336+
- tests/integration/test_design.py::test_nurbs_surface_body_creation

tests/integration/test_design.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
from ansys.geometry.core.shapes.parameterization import (
7777
Interval,
7878
)
79+
from ansys.geometry.core.shapes.surfaces.nurbs import NURBSSurface
7980
from ansys.geometry.core.sketch import Sketch
8081

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

32663267

3268+
def test_nurbs_surface_body_creation(modeler: Modeler):
3269+
"""Test surface body creation from NURBS surfaces."""
3270+
design = modeler.create_design("Design1")
3271+
3272+
points = [
3273+
Point3D([0, 0, 0]),
3274+
Point3D([0, 1, 1]),
3275+
Point3D([0, 2, 0]),
3276+
Point3D([1, 0, 1]),
3277+
Point3D([1, 1, 2]),
3278+
Point3D([1, 2, 1]),
3279+
Point3D([2, 0, 0]),
3280+
Point3D([2, 1, 1]),
3281+
Point3D([2, 2, 0]),
3282+
]
3283+
degree_u = 2
3284+
degree_v = 2
3285+
surface = NURBSSurface.fit_surface_from_points(
3286+
points=points, size_u=3, size_v=3, degree_u=degree_u, degree_v=degree_v
3287+
)
3288+
3289+
trimmed_surface = surface.trim(BoxUV(Interval(0, 1), Interval(0, 1)))
3290+
body = design.create_body_from_surface("nurbs_surface", trimmed_surface)
3291+
assert len(design.bodies) == 1
3292+
assert body.is_surface
3293+
assert body.faces[0].area.m == pytest.approx(7.44626609)
3294+
3295+
32673296
def test_create_surface_from_nurbs_sketch(modeler: Modeler):
32683297
"""Test creating a surface from a NURBS sketch."""
32693298
design = modeler.create_design("NURBS_Sketch_Surface")

0 commit comments

Comments
 (0)