Esempio n. 1
0
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
    bspline2 = bspline.insert_knot(t)
    assert len(bspline2.control_points) == 9
 def test_complex_ellipse_with_spline_intersection(self):
     ellipse = ConstructionEllipse(center=(0, 0), major_axis=(3, 0), ratio=0.5)
     bspline = BSpline([(-4, -4), (-2, -1), (2, 1), (4, 4)])
     p1 = ellipse.flattening(distance=0.01)
     p2 = bspline.flattening(distance=0.01)
     res = intersect_polylines_2d(Vec2.list(p1), Vec2.list(p2))
     assert len(res) == 2
Esempio n. 3
0
def add_spline(path: Path, spline: BSpline, level=4, reset=True) -> None:
    """Add a B-spline as multiple cubic Bèzier-curves.

    Non-rational B-splines of 3rd degree gets a perfect conversion to
    cubic bezier curves with a minimal count of curve segments, all other
    B-spline require much more curve segments for approximation.

    Auto-detect the connection point to the given `path`, if neither the start-
    nor the end point of the B-spline is close to the path end point, a line
    from the path end point to the start point of the B-spline will be added
    automatically. (see :meth:`add_bezier4p`).

    By default the start of an **empty** path is set to the start point of
    the spline, setting argument `reset` to ``False`` prevents this
    behavior.

    Args:
        path: :class:`~ezdxf.path.Path` object
        spline: B-spline parameters as :class:`~ezdxf.math.BSpline` object
        level: subdivision level of approximation segments
        reset: set start point to start of spline if path is empty

    """
    if len(path) == 0 and reset:
        path.start = spline.point(0)
    curves: Iterable[Bezier4P]
    if spline.degree == 3 and not spline.is_rational and spline.is_clamped:
        curves = [Bezier4P(points) for points in spline.bezier_decomposition()]
    else:
        curves = spline.cubic_bezier_approximation(level=level)
    add_bezier4p(path, curves)
Esempio n. 4
0
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
Esempio n. 5
0
def test_bspline_point_calculation_against_derivative_calculation():
    # point calculation and derivative calculation are not the same functions
    # for optimization reasons. The derivatives() function returns the curve
    # point and n derivatives, check if both functions return the
    # same curve point:
    spline = BSpline(DEFPOINTS, order=4)
    curve_points = [p[0] for p in spline.derivatives(PARAMS, n=1)]
    for p, expected in zip(curve_points, spline.points(PARAMS)):
        assert p.isclose(expected)
Esempio n. 6
0
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)
Esempio n. 7
0
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)
Esempio n. 8
0
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 Vec3(expect).isclose(point)
Esempio n. 9
0
    def from_spline(
        cls,
        spline: BSpline,
        start_width: float,
        end_width: float,
        segments: int,
    ) -> "CurvedTrace":
        """
        Create curved trace from a B-spline.

        Args:
            spline: :class:`~ezdxf.math.BSpline` object
            start_width: start width
            end_width: end width
            segments: count of segments for approximation

        """
        curve_trace = cls()
        count = segments + 1
        t = linspace(0, spline.max_t, count)
        for ((point, derivative),
             width) in zip(spline.derivatives(t, n=1),
                           linspace(start_width, end_width, count)):
            normal = Vec2(derivative).orthogonal(True)
            curve_trace._append(Vec2(point), normal, width)
        return curve_trace
Esempio n. 10
0
 def from_control_points(edge: SplineEdge, control_points):
     return BSpline(
         control_points=control_points,
         order=edge.degree + 1,
         knots=edge.knot_values,
         weights=edge.weights if edge.weights else None,
     )
Esempio n. 11
0
    def render_open_bspline(
        self, layout: "BaseLayout", degree: int = 3, dxfattribs: dict = None
    ) -> None:
        """Render an open uniform B-spline 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
        )
Esempio n. 12
0
    def from_arc(cls, entity: 'DXFGraphic') -> 'Spline':
        """ Create a new SPLINE entity from a 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!

        """
        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 test_from_nurbs_python_curve_to_ezdxf_bspline():
    from geomdl.fitting import interpolate_curve
    curve = interpolate_curve([(0, 0), (0, 10), (10, 10), (10, 0)], degree=3)
    bspline = BSpline.from_nurbs_python_curve(curve)
    assert bspline.degree == 3
    assert len(bspline.control_points) == 4
    assert len(bspline.knots()) == 8  # count + order
Esempio n. 14
0
def test_add_spline_segment():
    t = CurvedTrace.from_spline(BSpline.from_fit_points([(1, 0), (3, 1),
                                                         (5, -1), (6, 0)]),
                                start_width=2,
                                end_width=1,
                                segments=10)
    assert len(t) == 11
Esempio n. 15
0
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 close_vectors(
        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 close_vectors(
        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),
        ],
    )
Esempio n. 16
0
def spline_insert_knot():
    doc = ezdxf.new('R2000', setup=True)
    msp = doc.modelspace()

    def add_spline(control_points, color=3, knots=None):
        msp.add_polyline2d(control_points, dxfattribs={'color': color, 'linetype': 'DASHED'})
        msp.add_open_spline(control_points, degree=3, knots=knots, dxfattribs={'color': color})

    control_points = Vec3.list([(0, 0), (10, 20), (30, 10), (40, 10), (50, 0), (60, 20), (70, 50), (80, 70)])
    add_spline(control_points, color=3, knots=None)

    bspline = BSpline(control_points, order=4)
    bspline.insert_knot(bspline.max_t/2)
    add_spline(bspline.control_points, color=4, knots=bspline.knots())

    doc.saveas("Spline_R2000_spline_insert_knot.dxf")
Esempio n. 17
0
def test_rational_spline_from_elliptic_arc_has_expected_parameters():
    ellipse = ConstructionEllipse(
        center=(1, 1),
        major_axis=(2, 0),
        ratio=0.5,
        start_param=0,
        end_param=math.pi / 2,
    )
    spline = rational_bspline_from_ellipse(ellipse)
    assert spline.degree == 2

    cpoints = spline.control_points
    assert len(cpoints) == 3
    assert cpoints[0].isclose((3, 1, 0))
    assert cpoints[1].isclose((3, 2, 0))
    assert cpoints[2].isclose((1, 2, 0))

    weights = spline.weights()
    assert len(weights) == 3
    assert weights[0] == 1.0
    assert weights[1] == math.cos(math.pi / 4)
    assert weights[2] == 1.0

    # as BSpline constructor()
    s2 = BSpline.from_ellipse(ellipse)
    assert spline.control_points == s2.control_points
Esempio n. 18
0
def bezier_to_bspline(curves: Iterable[AnyBezier]) -> BSpline:
    """Convert multiple quadratic or cubic Bèzier curves into a single cubic
    B-spline (:class:`ezdxf.math.BSpline`).
    For good results the curves must be lined up seamlessly, i.e. the starting
    point of the following curve must be the same as the end point of the
    previous curve. G1 continuity or better at the connection points of the
    Bézier curves is required to get best results.

    .. versionadded: 0.16

    """

    # Source: https://math.stackexchange.com/questions/2960974/convert-continuous-bezier-curve-to-b-spline
    def get_points(bezier: AnyBezier):
        points = bezier.control_points
        if len(points) < 4:
            return quadratic_to_cubic_bezier(bezier).control_points
        else:
            return points

    bezier_curve_points = [get_points(c) for c in curves]
    if len(bezier_curve_points) == 0:
        raise ValueError("one or more Bézier curves required")
    # Control points of the B-spline are the same as of the Bézier curves.
    # Remove duplicate control points at start and end of the curves.
    control_points = list(bezier_curve_points[0])
    for c in bezier_curve_points[1:]:
        control_points.extend(c[1:])
    knots = [0, 0, 0, 0]  # multiplicity of the 1st and last control point is 4
    n = len(bezier_curve_points)
    for k in range(1, n):
        knots.extend(
            (k, k, k))  # multiplicity of the inner control points is 3
    knots.extend((n, n, n, n))
    return BSpline(control_points, order=4, knots=knots)
Esempio n. 19
0
 def construction_tool(self) -> BSpline:
     """ Returns the construction tool :class:`ezdxf.math.BSpline`.
     """
     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.')
Esempio n. 20
0
def test_is_a_clamped_bspline(knots):
    s = BSpline(
        control_points=DEFPOINTS,
        knots=knots,
        order=4,
    )
    assert s.is_clamped is True
Esempio n. 21
0
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
Esempio n. 22
0
    def approximate(self,
                    segments: int = 40,
                    ucs: 'UCS' = None) -> List['Vertex']:
        """ Approximate the 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 may 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)
Esempio n. 23
0
 def construction_tool(self) -> BSpline:
     """Returns BSpline() for the OCS representation."""
     return BSpline(
         control_points=self.control_points,
         knots=self.knot_values,
         order=self.degree + 1,
         weights=self.weights,
     )
 def spline(self):
     points = [(0, 0), (1, 2), (2, 4), (3, 1), (4, 2)]
     # by default knot values are normalized in the range [0, 1],
     # this creates a not normalized knot vector in the range [0, 2]:
     knots = open_uniform_knot_vector(count=len(points), order=4)
     return BSpline(
         [(0, 0), (1, 2), (2, 4), (3, 1), (4, 2)],
         knots=knots,
     )
Esempio n. 25
0
def test_is_not_a_clamped_bspline(knots):
    """To be a clamped B-spline 4 repetitive knot values at the start and at
    the end of the knot vector are required.
    """
    s = BSpline(
        control_points=DEFPOINTS,
        knots=knots,
        order=4,
    )
    assert s.is_clamped is False
Esempio n. 26
0
    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)
Esempio n. 27
0
 def to_line_edges(spline_edge: SplineEdge):
     weights = spline_edge.weights
     if len(spline_edge.control_points):
         bspline = BSpline(
             control_points=spline_edge.control_points,
             order=spline_edge.degree + 1,
             knots=spline_edge.knot_values,
             weights=weights if len(weights) else None,
         )
     elif len(spline_edge.fit_points):
         bspline = BSpline.from_fit_points(
             spline_edge.fit_points, spline_edge.degree
         )
     else:
         raise const.DXFStructureError(
             "SplineEdge() without control points or fit points."
         )
     segment_count = (max(len(bspline.control_points), 3) - 1) * factor
     vertices = list(bspline.approximate(segment_count))
     for v1, v2 in zip(vertices[:-1], vertices[1:]):
         edge = LineEdge()
         edge.start = v1.vec2
         edge.end = v2.vec2
         yield edge
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
Esempio n. 29
0
    def apply_construction_tool(self, s) -> "Spline":
        """Apply SPLINE data from a :class:`~ezdxf.math.BSpline` construction
        tool or from a :class:`geomdl.BSpline.Curve` object.

        """
        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 test_from_ezdxf_bspline_to_nurbs_python_curve_rational():
    bspline = rational_spline_from_arc(center=Vector(0, 0), radius=2, start_angle=0, end_angle=90)

    # to NURBS-Python
    curve = bspline.to_nurbs_python_curve()
    assert curve.degree == 2
    assert len(curve.ctrlpts) == 3
    assert len(curve.knotvector) == 6  # count + order
    assert curve.rational is True
    assert curve.weights == [1.0, 0.7071067811865476, 1.0]

    # and back to ezdxf
    spline = BSpline.from_nurbs_python_curve(curve)
    assert spline.degree == 2
    assert len(spline.control_points) == 3
    assert len(spline.knots()) == 6  # count + order
    assert spline.weights() == [1.0, 0.7071067811865476, 1.0]