Skip to content

Commit 799a4b1

Browse files
nurbs surfaces working and tested
1 parent 19be4e6 commit 799a4b1

File tree

2 files changed

+183
-18
lines changed

2 files changed

+183
-18
lines changed

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

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -237,32 +237,45 @@ def __eq__(self, other: "NURBSSurface") -> bool:
237237
and self._nurbs_surface.weights == other._nurbs_surface.weights
238238
)
239239

240-
def parameterization(self) -> Parameterization:
240+
def parameterization(self) -> tuple[Parameterization, Parameterization]:
241241
"""Get the parametrization of the NURBS surface.
242242
243243
The parameter is defined in the interval [0, 1] by default. Information
244244
is provided about the parameter type and form.
245245
246246
Returns
247247
-------
248-
Parameterization
249-
Information about how the NURBS surface is parameterized.
248+
tuple[Parameterization, Parameterization]
249+
Parameterization in the U and V directions respectively.
250250
"""
251-
return Parameterization(
252-
ParamForm.OTHER,
253-
ParamType.OTHER,
254-
Interval(start=self._nurbs_surface.domain[0], end=self._nurbs_surface.domain[1]),
251+
return (
252+
Parameterization(
253+
ParamForm.OTHER,
254+
ParamType.OTHER,
255+
Interval(
256+
start=self._nurbs_surface.domain[0][0],
257+
end=self._nurbs_surface.domain[0][1]
258+
),
259+
),
260+
Parameterization(
261+
ParamForm.OTHER,
262+
ParamType.OTHER,
263+
Interval(
264+
start=self._nurbs_surface.domain[1][0],
265+
end=self._nurbs_surface.domain[1][1],
266+
),
267+
),
255268
)
256269

257270
def transformed_copy(self, matrix: Matrix44) -> "NURBSSurface": # noqa: D102
258271
raise NotImplementedError("transformed_copy() is not implemented.")
259272

260-
def evaluate(self, parameter: Real) -> SurfaceEvaluation:
273+
def evaluate(self, parameter: ParamUV) -> SurfaceEvaluation:
261274
"""Evaluate the surface at the given parameter.
262275
263276
Parameters
264277
----------
265-
parameter : Real
278+
parameter : ParamUV
266279
Parameter to evaluate the surface at.
267280
268281
Returns
@@ -272,7 +285,7 @@ def evaluate(self, parameter: Real) -> SurfaceEvaluation:
272285
"""
273286
return NURBSSurfaceEvaluation(self, parameter)
274287

275-
def contains_param(self, param: Real) -> bool: # noqa: D102
288+
def contains_param(self, param: ParamUV) -> bool: # noqa: D102
276289
raise NotImplementedError("contains_param() is not implemented.")
277290

278291
def contains_point(self, point: Point3D) -> bool: # noqa: D102
@@ -297,15 +310,26 @@ def __init__(self, nurbs_surface: NURBSSurface, parameter: ParamUV) -> None:
297310
"""Initialize the ``NURBSsurfaceEvaluation`` class."""
298311
self._surface = nurbs_surface
299312
self._parameter = parameter
300-
self._derivatives = nurbs_surface.geomdl_nurbs_surface.derivatives(parameter.u, parameter.v, 2)
313+
314+
u, v = parameter.u, parameter.v
315+
domain = nurbs_surface._nurbs_surface.domain
316+
u_start, u_end = domain[0][0], domain[0][1]
317+
v_start, v_end = domain[1][0], domain[1][1]
318+
319+
if not (u_start <= u <= u_end and v_start <= v <= v_end):
320+
raise ValueError(
321+
f"Parameter [u={u}, v={v}] is outside the surface domain: "
322+
f"U[{u_start}, {u_end}], V[{v_start}, {v_end}]"
323+
)
324+
self._derivatives = nurbs_surface.geomdl_nurbs_surface.derivatives(u, v, 2)
301325

302326
@property
303327
def surface(self) -> "NURBSSurface":
304328
"""Surface being evaluated."""
305329
return self._surface
306330

307331
@property
308-
def parameter_u(self) -> ParamUV:
332+
def parameter(self) -> ParamUV:
309333
"""Parameter the evaluation is based upon."""
310334
return self._parameter
311335

@@ -330,9 +354,10 @@ def normal(self) -> UnitVector3D:
330354
Normal to the surface at this evaluation.
331355
"""
332356
from geomdl.operations import normal
333-
return UnitVector3D(
334-
normal(self._surface.geomdl_nurbs_surface, [self._parameter.u, self._parameter.v])
335-
)
357+
uv = [float(self._parameter.u), float(self._parameter.v)]
358+
result = normal(self._surface.geomdl_nurbs_surface, uv)
359+
360+
return UnitVector3D(result[1])
336361

337362
@cached_property
338363
def u_derivative(self) -> Vector3D:
@@ -407,4 +432,5 @@ def max_curvature(self) -> Real:
407432
@cached_property
408433
def max_curvature_direction(self) -> UnitVector3D:
409434
"""Maximum curvature direction."""
410-
raise NotImplementedError("max_curvature_direction() is not implemented.")
435+
raise NotImplementedError("max_curvature_direction() is not implemented.")
436+

tests/test_primitives.py

Lines changed: 141 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222

2323
from beartype.roar import BeartypeCallHintParamViolation
2424
import geomdl
25-
from geomdl.visualization import VisMPL
2625
import numpy as np
2726
from pint import Quantity
2827
import pytest
@@ -53,7 +52,7 @@
5352
ParamForm,
5453
ParamType,
5554
)
56-
from ansys.geometry.core.shapes.surfaces.nurbs import NURBSSurface
55+
from ansys.geometry.core.shapes.surfaces.nurbs import NURBSSurface, NURBSSurfaceEvaluation
5756
from ansys.geometry.core.shapes.surfaces.sphere import SphereEvaluation
5857

5958

@@ -1664,3 +1663,143 @@ def test_nurbs_surface_fitting():
16641663
assert len(surface.knotvector_u) == 6
16651664
assert len(surface.knotvector_v) == 6
16661665
assert len(surface.control_points) == 9
1666+
1667+
1668+
def test_nurbs_surface_equality():
1669+
"""Test if two NURBSSurface instances are equal."""
1670+
points = [
1671+
Point3D([0, 0, 0]),
1672+
Point3D([0, 1, 1]),
1673+
Point3D([0, 2, 0]),
1674+
Point3D([1, 0, 1]),
1675+
Point3D([1, 1, 2]),
1676+
Point3D([1, 2, 1]),
1677+
Point3D([2, 0, 0]),
1678+
Point3D([2, 1, 1]),
1679+
Point3D([2, 2, 0]),
1680+
]
1681+
degree_u = 2
1682+
degree_v = 2
1683+
1684+
surface = NURBSSurface.fit_surface_from_points(
1685+
points=points, size_u = 3, size_v = 3, degree_u=degree_u, degree_v=degree_v
1686+
)
1687+
other_surface = NURBSSurface.fit_surface_from_points(
1688+
points=points, size_u = 3, size_v = 3, degree_u=degree_u, degree_v=degree_v
1689+
)
1690+
1691+
assert surface == other_surface
1692+
1693+
# Test with a different, non-equivalent, surface
1694+
different_points = [
1695+
Point3D([0, 0, 0]),
1696+
Point3D([0, 1, 1]),
1697+
Point3D([0, 2, 0]),
1698+
Point3D([2, 0, 1]),
1699+
Point3D([2, 1, 2]),
1700+
Point3D([2, 2, 1]),
1701+
Point3D([4, 0, 0]),
1702+
Point3D([4, 1, 1]),
1703+
Point3D([4, 2, 0]),
1704+
]
1705+
different_surface = NURBSSurface.fit_surface_from_points(
1706+
points=different_points, size_u=3, size_v=3, degree_u=degree_u, degree_v=degree_v
1707+
)
1708+
1709+
assert surface != different_surface
1710+
1711+
# Test comparison with a non-NURBSSurface object
1712+
non_nurbs_object = "Not a NURBSSurface"
1713+
assert surface != non_nurbs_object
1714+
1715+
1716+
def test_nurbs_surface_parameterization():
1717+
"""Test the parameterization method of the NURBSSurface class."""
1718+
# Define valid inputs for the NURBS surface
1719+
degree_u = 2
1720+
degree_v = 2
1721+
knots_u = [0, 0, 0, 1, 1, 1]
1722+
knots_v = [0, 0, 0, 1, 1, 1]
1723+
control_points = [
1724+
Point3D([0, 0, 0]),
1725+
Point3D([0, 1, 1]),
1726+
Point3D([0, 2, 0]),
1727+
Point3D([1, 0, 1]),
1728+
Point3D([1, 1, 2]),
1729+
Point3D([1, 2, 1]),
1730+
Point3D([2, 0, 0]),
1731+
Point3D([2, 1, 1]),
1732+
Point3D([2, 2, 0]),
1733+
]
1734+
1735+
# Create a NURBS surface instance
1736+
nurbs_surface = NURBSSurface.from_control_points(
1737+
degree_u=degree_u,
1738+
degree_v=degree_v,
1739+
knots_u=knots_u,
1740+
knots_v=knots_v,
1741+
control_points=control_points,
1742+
)
1743+
1744+
# Call the parameterization method
1745+
u_param, v_param = nurbs_surface.parameterization()
1746+
1747+
# Validate the u parameterization
1748+
assert isinstance(u_param, Parameterization)
1749+
assert u_param.form == ParamForm.OTHER
1750+
assert u_param.type == ParamType.OTHER
1751+
assert u_param.interval.start == nurbs_surface._nurbs_surface.domain[0][0]
1752+
assert u_param.interval.end == nurbs_surface._nurbs_surface.domain[0][1]
1753+
1754+
# Validate the v parameterization
1755+
assert isinstance(v_param, Parameterization)
1756+
assert v_param.form == ParamForm.OTHER
1757+
assert v_param.type == ParamType.OTHER
1758+
assert v_param.interval.start == nurbs_surface._nurbs_surface.domain[1][0]
1759+
assert v_param.interval.end == nurbs_surface._nurbs_surface.domain[1][1]
1760+
1761+
1762+
def test_nurbs_surface_simple_evaluation():
1763+
"""Test NURBSSurface evaluation."""
1764+
degree_u = 2
1765+
degree_v = 2
1766+
knots_u = [0, 0, 0, 1, 1, 1]
1767+
knots_v = [0, 0, 0, 1, 1, 1]
1768+
control_points = [
1769+
Point3D([0, 0, 0]),
1770+
Point3D([0, 1, 1]),
1771+
Point3D([0, 2, 0]),
1772+
Point3D([1, 0, 1]),
1773+
Point3D([1, 1, 2]),
1774+
Point3D([1, 2, 1]),
1775+
Point3D([2, 0, 0]),
1776+
Point3D([2, 1, 1]),
1777+
Point3D([2, 2, 0]),
1778+
]
1779+
1780+
nurbs_surface = NURBSSurface.from_control_points(
1781+
degree_u=degree_u,
1782+
degree_v=degree_v,
1783+
knots_u=knots_u,
1784+
knots_v=knots_v,
1785+
control_points=control_points,
1786+
)
1787+
1788+
# Test invalid evaluation at (-1,0)
1789+
with pytest.raises(ValueError):
1790+
_ = nurbs_surface.evaluate(ParamUV(-1, 0))
1791+
1792+
# Test evaluation at (0,0)
1793+
eval = nurbs_surface.evaluate(ParamUV(0.5, 0.5))
1794+
1795+
assert isinstance(eval, NURBSSurfaceEvaluation)
1796+
assert eval.parameter.u == 0.5
1797+
assert eval.parameter.v == 0.5
1798+
assert eval.position == Point3D([1, 1, 1])
1799+
assert isinstance(eval.u_derivative, Vector3D)
1800+
assert isinstance(eval.v_derivative, Vector3D)
1801+
assert isinstance(eval.uu_derivative, Vector3D)
1802+
assert isinstance(eval.uv_derivative, Vector3D)
1803+
assert isinstance(eval.vv_derivative, Vector3D)
1804+
assert isinstance(eval.normal, UnitVector3D)
1805+
assert isinstance(eval.surface, NURBSSurface)

0 commit comments

Comments
 (0)