def random_point_comparision_to_nurbs_python(spline: BSpline, count: int = 10): curve = spline.to_nurbs_python_curve() for _ in range(count): t = random.random() p1 = spline.point(t) p2 = curve.evaluate_single(t) assert p1.isclose(p2)
def test_bspline_insert_knot(): bspline = BSpline([(0, 0), (10, 20), (30, 10), (40, 10), (50, 0), (60, 20), (70, 50), (80, 70)]) t = bspline.max_t / 2 assert len(bspline.control_points) == 8 bspline.insert_knot(t) assert len(bspline.control_points) == 9
def test_normalize_knots_if_needed(): s = BSpline( control_points=DEFPOINTS, knots=[2, 2, 2, 2, 3, 6, 6, 6, 6], order=4, ) k = s.knots() assert k[0] == 0.0
def __init__(self, spline): control_points = [Vector(point) for point in spline.control_points] knots = [knot for knot in spline.knots] weights = [weight for weight in spline.weights] if spline.weights else None order = spline.dxf.degree + 1 BSpline.__init__(self, control_points, order, knots, weights) points = [point for point in self.approximate()] Element.__init__(self, spline, points)
def test_bspine_points(): curve = BSpline(DEFPOINTS, order=3) points = list(curve.approximate(40)) for rpoint, epoint in zip(points, iter_points(DBSPLINE, 0)): epx, epy, epz = epoint rpx, rpy, rpz = rpoint assert isclose(epx, rpx) assert isclose(epy, rpy) assert isclose(epz, rpz)
def random_derivatives_comparision_to_nurbs_python(spline: BSpline, count: int = 10): curve = spline.to_nurbs_python_curve() for _ in range(count): t = random.random() p1, d1_1, d2_1 = spline.derivative(t, n=2) p2, d1_2, d2_2 = curve.derivatives(t, order=2) assert p1.isclose(p2) assert d1_1.isclose(d1_2) assert d2_1.isclose(d2_2)
def test_rbspline(): curve = BSpline(DEFPOINTS, order=3, weights=DEFWEIGHTS) expected = RBSPLINE points = list(curve.approximate(40)) for rpoint, epoint in zip(points, expected): epx, epy, epz = epoint rpx, rpy, rpz = rpoint assert isclose(epx, rpx) assert isclose(epy, rpy) assert isclose(epz, rpz)
def render_open_bspline(self, layout: 'BaseLayout', degree: int = 3, dxfattribs: dict = None) -> None: """ Render an open uniform BSpline as 3D :class:`~ezdxf.entities.Polyline`. Definition points are control points. Args: layout: :class:`~ezdxf.layouts.BaseLayout` object degree: degree of B-spline (order = `degree` + 1) dxfattribs: DXF attributes for :class:`~ezdxf.entities.Polyline` """ spline = BSpline(self.points, order=degree + 1) layout.add_polyline3d(list(spline.approximate(self.segments)), dxfattribs=dxfattribs)
def render_open_bspline(self, layout: 'GenericLayoutType', degree: int = 3, dxfattribs: dict = None) -> None: """ Render an open uniform BSpline as 3d polyline. Definition points are control points. Args: layout: ezdxf layout degree: B-spline degree (order = degree + 1) dxfattribs: DXF attributes for POLYLINE """ spline = BSpline(self.points, order=degree + 1) layout.add_polyline3d(list(spline.approximate(self.segments)), dxfattribs=dxfattribs)
def test_if_nurbs_python_is_reliable(): # Testing for some known values, just for the case # that NURBS-Python is incorrect. expected = [(0.0, 0.0, 0.0), (11.840000000000003, 13.760000000000002, 16.64), (22.72, 14.079999999999998, 22.719999999999995), (31.759999999999994, 11.2, 24.399999999999995), (39.92, 7.999999999999999, 26.0), (50.0, 0.0, 30.0)] params = [0, .2, .4, .6, .8, 1.0] curve = BSpline(DEFPOINTS).to_nurbs_python_curve() points = curve.evaluate_list(params) for expect, point in zip(expected, points): assert Vector(expect).isclose(point)
def apply_construction_tool(self, s: BSpline) -> None: """ Set SPLINE data from construction tool :class:`ezdxf.math.BSpline`. .. versionadded:: 0.13 """ self.dxf.degree = s.degree self.control_points = s.control_points self.fit_points = [] # remove fit points self.knots = s.knots() self.weights = s.weights() self.set_flag_state(Spline.RATIONAL, state=bool(len(self.weights)))
def render_open_rbspline(self, layout: 'GenericLayoutType', weights: Iterable[float], degree: int = 3, dxfattribs: dict = None) -> None: """ Render a rational open uniform BSpline as 3d polyline. Args: layout: ezdxf layout weights: list of weights, requires a weight value for each defpoint. degree: B-spline degree (order = degree + 1) dxfattribs: DXF attributes for POLYLINE """ spline = BSpline(self.points, order=degree + 1, weights=weights) layout.add_polyline3d(list(spline.approximate(self.segments)), dxfattribs=dxfattribs)
def render_open_rbspline(self, layout: 'BaseLayout', weights: Iterable[float], degree: int = 3, dxfattribs: dict = None) -> None: """ Render a rational open uniform BSpline as 3D :class:`~ezdxf.entities.Polyline`. Definition points are control points. Args: layout: :class:`~ezdxf.layouts.BaseLayout` object weights: list of weights, requires a weight value (float) for each definition point. degree: degree of B-spline (order = `degree` + 1) dxfattribs: DXF attributes for :class:`~ezdxf.entities.Polyline` """ spline = BSpline(self.points, order=degree + 1, weights=weights) layout.add_polyline3d(list(spline.approximate(self.segments)), dxfattribs=dxfattribs)
def test_cubic_bezier_approximation(): bspline = BSpline.from_fit_points([(0, 0), (10, 20), (30, 10), (40, 10), (50, 0), (60, 20), (70, 50), (80, 70)]) bezier_segments = list(bspline.cubic_bezier_approximation(level=3)) assert len(bezier_segments) == 28 bezier_segments = list(bspline.cubic_bezier_approximation(segments=40)) assert len(bezier_segments) == 40
def bspline(self, length: float, segments: int = 10, degree: int = 3, method: str = 'uniform') -> BSpline: """ Approximate euler spiral as B-spline. Args: length: length of euler spiral segments: count of fit points for B-spline calculation degree: degree of BSpline method: calculation method for parameter vector t Returns: :class:`BSpline` """ fit_points = list(self.approximate(length, segments=segments)) spline = global_bspline_interpolation(fit_points, degree, method=method) return BSpline( spline.control_points, spline.order, # Scale knot values to length: [v * length for v in spline.knots()], )
def approximate(self, segments: int = 40, ucs: 'UCS' = None) -> List['Vertex']: """ Approximate B-spline by a polyline with `segments` line segments. If `ucs` is not ``None``, ucs defines an :class:`~ezdxf.math.UCS`, to transformed the curve into :ref:`OCS`. The control points are placed xy-plane of the UCS, don't use z-axis coordinates, if so make sure all control points are in a plane parallel to the OCS base plane (UCS xy-plane), else the result is unpredictable and depends on the CAD application used to open the DXF file, it maybe crash. Args: segments: count of line segments for approximation, vertex count is `segments` + 1 ucs: :class:`~ezdxf.math.UCS` definition, control points in ucs coordinates. Returns: list of vertices in :class:`~ezdxf.math.OCS` as :class:`~ezdxf.math.Vec3` objects """ if self.closed: spline = BSplineClosed(self.control_points, order=self.degree + 1) else: spline = BSpline(self.control_points, order=self.degree + 1) vertices = spline.approximate(segments) if ucs is not None: vertices = (ucs.to_ocs(vertex) for vertex in vertices) return list(vertices)
def construction_tool(self) -> BSpline: """ Returns construction tool :class:`ezdxf.math.BSpline`. .. versionadded:: 0.13 """ if self.control_point_count(): weights = self.weights if len(self.weights) else None knots = self.knots if len(self.knots) else None return BSpline(control_points=self.control_points, order=self.dxf.degree + 1, knots=knots, weights=weights) elif self.fit_point_count(): return BSpline.from_fit_points(self.fit_points, degree=self.dxf.degree) else: raise ValueError( 'Construction tool requires control- or fit points.')
def approximate(self, segments: int = 40, ucs: 'UCS' = None) -> List['Vertex']: if self.closed: spline = BSplineClosed(self.control_points, order=self.degree + 1) else: spline = BSpline(self.control_points, order=self.degree + 1) vertices = spline.approximate(segments) if ucs is not None: vertices = (ucs.to_ocs(vertex) for vertex in vertices) return list(vertices)
def test_bezier_decomposition(): bspline = BSpline.from_fit_points([(0, 0), (10, 20), (30, 10), (40, 10), (50, 0), (60, 20), (70, 50), (80, 70)]) bezier_segments = list(bspline.bezier_decomposition()) assert len(bezier_segments) == 5 # results visually checked to be correct assert bezier_segments[0] == [(0.0, 0.0, 0.0), (2.02070813064438, 39.58989657555839, 0.0), (14.645958536022286, 10.410103424441612, 0.0), (30.0, 10.0, 0.0)] assert bezier_segments[-1] == [(60.0, 20.0, 0.0), (66.33216513897267, 43.20202388489432, 0.0), (69.54617236126121, 50.37880459351478, 0.0), (80.0, 70.0, 0.0)]
def apply_construction_tool(self, s) -> 'Spline': """ Set SPLINE data from construction tool :class:`ezdxf.math.BSpline` or from a :class:`geomdl.BSpline.Curve` object. .. versionadded:: 0.13 """ try: self.control_points = s.control_points except AttributeError: # maybe a geomdl.BSpline.Curve class s = BSpline.from_nurbs_python_curve(s) self.control_points = s.control_points self.dxf.degree = s.degree self.fit_points = [] # remove fit points self.knots = s.knots() self.weights = s.weights() self.set_flag_state(Spline.RATIONAL, state=bool(len(self.weights))) return self # floating interface
def from_arc(cls, entity: 'DXFGraphic') -> 'Spline': """ Create a new SPLINE entity from CIRCLE, ARC or ELLIPSE entity. The new SPLINE entity has no owner, no handle, is not stored in the entity database nor assigned to any layout! .. versionadded:: 0.13 """ dxftype = entity.dxftype() if dxftype == 'ELLIPSE': ellipse = cast('Ellipse', entity).construction_tool() elif dxftype == 'CIRCLE': ellipse = ConstructionEllipse.from_arc( center=entity.dxf.get('center', NULLVEC), radius=abs(entity.dxf.get('radius', 1.0)), extrusion=entity.dxf.get('extrusion', Z_AXIS), ) elif dxftype == 'ARC': ellipse = ConstructionEllipse.from_arc( center=entity.dxf.get('center', NULLVEC), radius=abs(entity.dxf.get('radius', 1.0)), extrusion=entity.dxf.get('extrusion', Z_AXIS), start_angle=entity.dxf.get('start_angle', 0), end_angle=entity.dxf.get('end_angle', 360) ) else: raise TypeError('CIRCLE, ARC or ELLIPSE entity required.') spline = Spline.new(dxfattribs=entity.graphic_properties(), doc=entity.doc) s = BSpline.from_ellipse(ellipse) spline.dxf.degree = s.degree spline.dxf.flags = Spline.RATIONAL spline.control_points = s.control_points spline.knots = s.knots() spline.weights = s.weights() return spline
def bspline( self, length: float, segments: int = 10, degree: int = 3, method: str = "uniform", ) -> BSpline: """Approximate euler spiral as B-spline. Args: length: length of euler spiral segments: count of fit points for B-spline calculation degree: degree of BSpline method: calculation method for parameter vector t Returns: :class:`BSpline` """ length = float(length) fit_points = list(self.approximate(length, segments=segments)) derivatives = [ # Scaling derivatives by chord length (< real length) is suggested # by Piegl & Tiller. self.tangent(t).normalize(length) for t in _params(length, segments) ] spline = global_bspline_interpolation( fit_points, degree, method=method, tangents=derivatives ) return BSpline( spline.control_points, spline.order, # Scale knot values to length: [v * length for v in spline.knots()], )
def test_flattening(): fitpoints = [(0, 0), (1, 3), (2, 0), (3, 3)] bspline = BSpline.from_fit_points(fitpoints) assert list(bspline.flattening(0.01, segments=4)) == EXPECTED_FLATTENING
def test_transform_interface(): from ezdxf.math import Matrix44 spline = BSpline(control_points=[(1, 0, 0), (3, 3, 0), (6, 0, 1)], order=3) spline.transform(Matrix44.translate(1, 2, 3)) assert spline.control_points[0] == (2, 2, 3)
def test_bspine_derivatives_random(): spline = BSpline(DEFPOINTS, order=3) random_derivatives_comparision_to_nurbs_python(spline)
def test_is_clamped(weired_spline1): spline = BSpline(DEFPOINTS, order=3) assert spline.is_clamped is True assert weired_spline1.is_clamped is False
def test_bspine_points_random(): spline = BSpline(DEFPOINTS, order=3) random_point_comparision_to_nurbs_python(spline)
def weired_spline1(): # test spline from: 'CADKitSamples\Tamiya TT-01.dxf' control_points = [ (-52.08772752271847, 158.6939842216689, 0.0), (-52.08681215879965, 158.5299954819766, 0.0), (-52.10118023714384, 158.453369560292, 0.0), (-52.15481567142786, 158.3191250853181, 0.0), (-52.19398877522381, 158.2621809388646, 0.0), (-52.28596439525645, 158.1780834350967, 0.0), (-52.33953844794299, 158.1503467960972, 0.0), (-52.44810872122953, 158.1300340044323, 0.0), (-52.50421992306838, 158.1373171840982, 0.0), (-52.6075289246734, 158.1865954546344, 0.0), (-52.65514787710273, 158.2285032895921, 0.0), (-52.73668761545541, 158.3403743627349, 0.0), (-52.77007322118961, 158.4091709021843, 0.0), (-52.82282063670695, 158.5633574927312, 0.0), (-52.84192253131899, 158.6479284406054, 0.0), (-52.86740213628708, 158.8193660227095, 0.0), (-52.87386770841857, 158.9069288997418, 0.0), (-52.87483030423064, 159.0684635170357, 0.0), (-52.86932199691667, 159.1438624785262, 0.0), (-52.84560704446005, 159.2697570380293, 0.0), (-52.82725914916205, 159.3212520891559, 0.0), (-52.75022655463125, 159.4318434990425, 0.0), (-52.6670694478151, 159.4452110783386, 0.0), (-52.51141458339235, 159.3709884860868, 0.0), (-52.45531159130934, 159.3310594465107, 0.0), (-52.34571913237574, 159.2278392570542, 0.0), (-52.29163139562603, 159.1638425241462, 0.0), (-52.19834244727945, 159.0217561474263, 0.0), (-52.15835994602539, 158.9423430023927, 0.0), (-52.10315233959036, 158.778742732499, 0.0), (-52.08772752271847, 158.6939842216689, 0.0), (-52.08681215879965, 158.5299954819766, 0.0), ] knots = [ -0.0624999999999976, -0.0624999999999976, 0.0, 0.0, 0.0624999999999998, 0.0624999999999998, 0.1249999999999997, 0.1249999999999997, 0.1874999999999996, 0.1874999999999996, 0.2499999999999994, 0.2499999999999994, 0.3124999999999992, 0.3124999999999992, 0.3749999999999991, 0.3749999999999991, 0.4374999999999989, 0.4374999999999989, 0.4999999999999988, 0.4999999999999988, 0.5624999999999987, 0.5624999999999987, 0.6249999999999984, 0.6249999999999984, 0.7500000000000099, 0.7500000000000099, 0.8125000000000074, 0.8125000000000074, 0.875000000000005, 0.875000000000005, 0.9375000000000024, 0.9375000000000024, 1.0, 1.0, 1.0625, 1.0625, ] return BSpline(control_points, order=4, knots=knots)