Skip to content

Commit

Permalink
add BSpline.degree_elevation() method
Browse files Browse the repository at this point in the history
  • Loading branch information
mozman committed Jan 11, 2025
1 parent 7cabef3 commit 3516661
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 18 deletions.
2 changes: 2 additions & 0 deletions docs/source/math/core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1141,6 +1141,8 @@ BSpline

.. automethod:: cubic_bezier_approximation

.. automethod:: degree_elevation


Bezier
------
Expand Down
2 changes: 1 addition & 1 deletion notes/pages/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- NEW: `Mesh.audit()` removes `MESH` entities without vertices or faces
- NEW: `Polyline.points_in_wcs()` method
- NEW: `EdgePath.close_gaps()` method
- NEW: `ezdxf.math.bspline.degree_elevation()` function
- NEW: `BSpline.degree_elevation()` method
- BUGFIX: Exported `MESH` entities without vertices or faces create invalid DXF files
- {{issue 1219}}
- ## Version 1.3.5 - 2024-12-15
Expand Down
14 changes: 14 additions & 0 deletions src/ezdxf/math/bspline.py
Original file line number Diff line number Diff line change
Expand Up @@ -1323,6 +1323,18 @@ def approximation_params(self, level: int = 3) -> Sequence[float]:
params = list(subdivide_params(params))
return params

def degree_elevation(self, t: int) -> BSpline:
"""Returns a new :class:`BSpline` with a t-times elevated degree.
Degree elevation increases the degree of a curve without changing the shape of the
curve. This method implements the algorithm A5.9 of the "The NURBS Book" by
Piegl & Tiller.
.. versionadded:: 1.4
"""
return degree_elevation(self, t)


def subdivide_params(p: list[float]) -> Iterable[float]:
for i in range(len(p) - 1):
Expand Down Expand Up @@ -1576,6 +1588,8 @@ def degree_elevation(spline: BSpline, t: int) -> BSpline:
curve. This function implements the algorithm A5.9 of the "The NURBS Book" by
Piegl & Tiller.
.. versionadded:: 1.4
"""
# Naming and structure have been retained to facilitate comparison with the original
# algorithm during debugging.
Expand Down
34 changes: 17 additions & 17 deletions tests/test_06_math/test_628_bspline_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pytest

import math
from ezdxf.math import bspline
from ezdxf.math import BSpline


CONTROL_POINTS = [(0, 0), (1, -1), (2, 0), (3, 2), (4, 0), (5, -2)]
Expand Down Expand Up @@ -56,48 +56,48 @@
class TestNonRationalBSpline:
@pytest.fixture(scope="class")
def result(self):
spline = bspline.BSpline(CONTROL_POINTS)
return bspline.degree_elevation(spline, 1)
spline = BSpline(CONTROL_POINTS)
return spline.degree_elevation(1)

def test_degree_is_elevated(self, result: bspline.BSpline):
def test_degree_is_elevated(self, result: BSpline):
assert result.degree == 4

def test_clamped_start_and_end_points_are_preserved(self, result: bspline.BSpline):
def test_clamped_start_and_end_points_are_preserved(self, result: BSpline):
assert result.control_points[0].isclose(CONTROL_POINTS[0])
assert result.control_points[-1].isclose(CONTROL_POINTS[-1])

def test_expected_control_points(self, result: bspline.BSpline):
def test_expected_control_points(self, result: BSpline):
assert (
all(cp.isclose(e) for cp, e in zip(result.control_points, EXPECTED_POINTS))
is True
)

def test_expected_knot_values(self, result: bspline.BSpline):
def test_expected_knot_values(self, result: BSpline):
assert (
all(math.isclose(k, e) for k, e in zip(result.knots(), EXPECTED_KNOTS))
is True
)

def test_elevation_0_times(self):
spline = bspline.BSpline(CONTROL_POINTS)
assert bspline.degree_elevation(spline, 0) is spline
assert bspline.degree_elevation(spline, -1) is spline
spline = BSpline(CONTROL_POINTS)
assert spline.degree_elevation(0) is spline
assert spline.degree_elevation(-1) is spline


class TestRationalBSpline:
@pytest.fixture(scope="class")
def result(self):
spline = bspline.BSpline(CONTROL_POINTS, weights=WEIGHTS)
return bspline.degree_elevation(spline, 1)
spline = BSpline(CONTROL_POINTS, weights=WEIGHTS)
return spline.degree_elevation(1)

def test_degree_is_elevated(self, result: bspline.BSpline):
def test_degree_is_elevated(self, result: BSpline):
assert result.degree == 4

def test_clamped_start_and_end_points_are_preserved(self, result: bspline.BSpline):
def test_clamped_start_and_end_points_are_preserved(self, result: BSpline):
assert result.control_points[0].isclose(CONTROL_POINTS[0])
assert result.control_points[-1].isclose(CONTROL_POINTS[-1])

def test_expected_control_points(self, result: bspline.BSpline):
def test_expected_control_points(self, result: BSpline):
assert (
all(
cp.isclose(e)
Expand All @@ -106,13 +106,13 @@ def test_expected_control_points(self, result: bspline.BSpline):
is True
)

def test_expected_knot_values(self, result: bspline.BSpline):
def test_expected_knot_values(self, result: BSpline):
assert (
all(math.isclose(k, e) for k, e in zip(result.knots(), EXPECTED_KNOTS))
is True
)

def test_expected_weights(self, result: bspline.BSpline):
def test_expected_weights(self, result: BSpline):
assert (
all(math.isclose(w, e) for w, e in zip(result.weights(), EXPECTED_WEIGHTS))
is True
Expand Down

0 comments on commit 3516661

Please sign in to comment.