From 35166619347fbb350f477b15259b364ad9b522f3 Mon Sep 17 00:00:00 2001 From: mozman Date: Sat, 11 Jan 2025 14:33:22 +0100 Subject: [PATCH] add BSpline.degree_elevation() method --- docs/source/math/core.rst | 2 ++ notes/pages/CHANGELOG.md | 2 +- src/ezdxf/math/bspline.py | 14 ++++++++ tests/test_06_math/test_628_bspline_tools.py | 34 ++++++++++---------- 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/docs/source/math/core.rst b/docs/source/math/core.rst index 3e4c304c6..e32051e11 100644 --- a/docs/source/math/core.rst +++ b/docs/source/math/core.rst @@ -1141,6 +1141,8 @@ BSpline .. automethod:: cubic_bezier_approximation + .. automethod:: degree_elevation + Bezier ------ diff --git a/notes/pages/CHANGELOG.md b/notes/pages/CHANGELOG.md index 92f03dd50..bc5142e10 100644 --- a/notes/pages/CHANGELOG.md +++ b/notes/pages/CHANGELOG.md @@ -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 diff --git a/src/ezdxf/math/bspline.py b/src/ezdxf/math/bspline.py index 0998c49d1..36258e659 100644 --- a/src/ezdxf/math/bspline.py +++ b/src/ezdxf/math/bspline.py @@ -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): @@ -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. diff --git a/tests/test_06_math/test_628_bspline_tools.py b/tests/test_06_math/test_628_bspline_tools.py index 10bb3822d..8c4551518 100644 --- a/tests/test_06_math/test_628_bspline_tools.py +++ b/tests/test_06_math/test_628_bspline_tools.py @@ -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)] @@ -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) @@ -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