def render_as_fit_points( self, layout: "BaseLayout", degree: int = 3, method: str = "chord", dxfattribs: dict = None, ) -> None: """Render a B-spline as 2D/3D :class:`~ezdxf.entities.Polyline`, where the definition points are fit points. - 2D spline vertices uses: :meth:`~ezdxf.layouts.BaseLayout.add_polyline2d` - 3D spline vertices uses: :meth:`~ezdxf.layouts.BaseLayout.add_polyline3d` Args: layout: :class:`~ezdxf.layouts.BaseLayout` object degree: degree of B-spline (order = `degree` + 1) method: "uniform", "distance"/"chord", "centripetal"/"sqrt_chord" or "arc" calculation method for parameter t dxfattribs: DXF attributes for :class:`~ezdxf.entities.Polyline` """ spline = global_bspline_interpolation( self.points, degree=degree, method=method ) vertices = list(spline.approximate(self.segments)) if any(vertex.z != 0.0 for vertex in vertices): layout.add_polyline3d(vertices, dxfattribs=dxfattribs) else: layout.add_polyline2d(vertices, dxfattribs=dxfattribs)
def spline_interpolation( vertices: Iterable["Vertex"], degree: int = 3, method: str = "chord", subdivide: int = 4, ) -> List[Vec3]: """B-spline interpolation, vertices are fit points for the spline definition. Only method 'uniform', yields vertices at fit points. Args: vertices: fit points degree: degree of B-spline method: "uniform", "chord"/"distance", "centripetal"/"sqrt_chord" or "arc" calculation method for parameter t subdivide: count of sub vertices + 1, e.g. 4 creates 3 sub-vertices Returns: list of vertices """ vertices = list(vertices) spline = global_bspline_interpolation( vertices, degree=degree, method=method ) return list(spline.approximate(segments=(len(vertices) - 1) * subdivide))
def test_check_values(): test_points = [(0., 0.), (1., 2.), (3., 1.), (5., 3.)] spline = global_bspline_interpolation(test_points, degree=3, method='distance') result = list(spline.approximate(49)) assert len(result) == 50 for p1, p2 in zip(result, expected): assert isclose(p1[0], p2[0], abs_tol=1e-6) assert isclose(p1[1], p2[1], abs_tol=1e-6)
def from_fit_points(edge, fit_points): tangents = None if edge.start_tangent and edge.end_tangent: tangents = (wcs(edge.start_tangent), wcs(edge.end_tangent)) return global_bspline_interpolation( fit_points, degree=edge.degree, tangents=tangents, )
def export_path(path): doc = ezdxf.new() msp = doc.modelspace() bbox = BoundingBox(path) msp.add_polyline3d(path, dxfattribs={'layer': 'Path', 'color': 2}) spline = msp.add_spline(dxfattribs={'layer': 'B-spline', 'color': 1}) curve = global_bspline_interpolation(path) spline.apply_construction_tool(curve) doc.set_modelspace_vport(center=bbox.center, height=bbox.size[1]) doc.saveas(DIR / 'path1.dxf')
def test_bspline_interpolation(fit_points): spline = global_bspline_interpolation(fit_points, degree=3) assert len(spline.control_points) == len(fit_points) assert spline.t_array[0] == 0. assert spline.t_array[-1] == 1. assert len(spline.t_array) == len(fit_points) t_points = [spline.point(t) for t in spline.t_array] for p1, p2 in zip(t_points, fit_points): assert p1 == p2
def test_bspline_interpolation(fit_points): spline = global_bspline_interpolation(fit_points, degree=3, method="chord") assert len(spline.control_points) == len(fit_points) t_array = list(create_t_vector(fit_points, "chord")) assert t_array[0] == 0.0 assert t_array[-1] == 1.0 assert len(t_array) == len(fit_points) t_points = [spline.point(t) for t in t_array] assert close_vectors(t_points, fit_points)
def test_bspline_interpolation(fit_points): spline = global_bspline_interpolation(fit_points, degree=3, method='chord') assert len(spline.control_points) == len(fit_points) t_array = list(create_t_vector(fit_points, 'chord')) assert t_array[0] == 0. assert t_array[-1] == 1. assert len(t_array) == len(fit_points) t_points = [spline.point(t) for t in t_array] for p1, p2 in zip(t_points, fit_points): assert p1 == p2
def add_spline_control_frame( self, fit_points: Iterable[Tuple[float, float]], degree: int = 3, method: str = "distance", ) -> "SplineEdge": bspline = global_bspline_interpolation( fit_points=fit_points, degree=degree, method=method ) return self.add_spline( fit_points=fit_points, control_points=bspline.control_points, knot_values=bspline.knots(), )
def test_from_ezdxf_bspline_to_nurbs_python_curve_non_rational(): bspline = global_bspline_interpolation([(0, 0), (0, 10), (10, 10), (10, 0)], degree=3) # to NURBS-Python curve = bspline.to_nurbs_python_curve() assert curve.degree == 3 assert len(curve.ctrlpts) == 4 assert len(curve.knotvector) == 8 # count + order assert curve.rational is False # and back to ezdxf spline = BSpline.from_nurbs_python_curve(curve) assert spline.degree == 3 assert len(spline.control_points) == 4 assert len(spline.knots()) == 8 # count + order
def profile_bspline_interpolation(count, path): for _ in range(count): global_bspline_interpolation(path)
def test_bspline_interpolation_first_derivatives(fit_points): tangents = estimate_tangents(fit_points) spline = global_bspline_interpolation(fit_points, degree=3, tangents=tangents) assert len(spline.control_points) == 2 * len(fit_points)
for p, t in zip(data, tangents): msp.add_line(p, p + t, dxfattribs={ 'color': 5, 'layer': f'Estimated tangents ({METHOD})' }) # local interpolation: a normalized tangent vector for each data point is required, s = local_cubic_bspline_interpolation( data, tangents=[t.normalize() for t in tangents]) # or set argument 'method' for automatic tangent estimation, default method is '5-points' interpolation # s = local_cubic_bspline_interpolation(data, method=METHOD) msp.add_spline(dxfattribs={ 'color': 3, 'layer': f'Local interpolation ({METHOD})' }).apply_construction_tool(s) # global interpolation: take first and last vector from 'tangents' as start- and end tangent m1, m2 = estimate_end_tangent_magnitude(data, method='chord') s = global_bspline_interpolation(data, tangents=(tangents[0].normalize(m1), tangents[-1].normalize(m2))) msp.add_spline(dxfattribs={ 'color': 4, 'layer': f'Global interpolation ({METHOD})' }).apply_construction_tool(s) doc.set_modelspace_vport(5, center=(4, 1)) doc.saveas(DIR / f'sine-wave-{METHOD}.dxf')
msp = doc.modelspace() msp.add_lwpolyline(points, dxfattribs={'color': 5, 'layer': 'frame'}) for p in points: msp.add_circle(p, radius=0.1, dxfattribs={ 'color': 1, 'layer': 'frame' }) return doc, msp # 1. Fit points from DXF file: Interpolation without any constraints doc, msp = setup() # First spline defined by control vertices interpolated from given fit points s = global_bspline_interpolation(points, degree=3) msp.add_spline(dxfattribs={ 'color': 4, 'layer': 'Global Curve Interpolation' }).apply_construction_tool(s) # Second spline defined only by fit points as reference, does not match the # BricsCAD interpolation. spline = msp.add_spline(points, degree=3, dxfattribs={ 'layer': 'BricsCAD B-spline', 'color': 2 }) zoom.extents(msp) doc.saveas(DIR / 'concept-0-fit-points-only.dxf')
# Copyright (c) 2020, Manfred Moitzi # License: MIT License import ezdxf from pathlib import Path import matplotlib.pyplot as plt from ezdxf.addons.drawing import Frontend, RenderContext from ezdxf.addons.drawing.matplotlib_backend import MatplotlibBackend from ezdxf.math import global_bspline_interpolation wave = [(0.0, 0.0), (0.897597901, 0.78183148), (1.79519580, 0.97492791), (2.69279370, 0.433883739), (3.59039160, -0.43388373), (4.48798950, -0.97492791), (5.38558740, -0.78183148), (6.28318530, 0.0)] DIR = Path('~/Desktop/Outbox').expanduser() FILE = 'wave' doc = ezdxf.new() msp = doc.modelspace() s = global_bspline_interpolation(wave) msp.add_spline(dxfattribs={'color': 2}).apply_construction_tool(s) fig = plt.figure() ax = fig.add_axes([0, 0, 1, 1]) backend = MatplotlibBackend(ax) Frontend(RenderContext(doc), backend).draw_layout(msp) fig.savefig(DIR / f'{FILE}.png', dpi=300)
# Copyright (c) 2020, Manfred Moitzi # License: MIT License from typing import Iterable import time import ezdxf from pathlib import Path import math from ezdxf.math import global_bspline_interpolation, linspace from ezdxf.render import random_3d_path DIR = Path("~/Desktop/Outbox").expanduser() path = list(random_3d_path(100, max_step_size=10, max_heading=math.pi * 0.8)) spline = global_bspline_interpolation(path) def profile_bspline_point_new(count, spline): for _ in range(count): for t in linspace(0, 1.0, 100): spline.point(t) def profile_bspline_derivatives_new(count, spline): for _ in range(count): list(spline.derivatives(t=linspace(0, 1.0, 100))) def profile(text, func, *args): t0 = time.perf_counter() func(*args) t1 = time.perf_counter()