Exemplo n.º 1
0
def test_add_curves4_with_gap():
    path = Path()
    c1 = Bezier4P(((0, 0, 0), (0, 1, 0), (2, 1, 0), (2, 0, 0)))
    c2 = Bezier4P(((2, -1, 0), (2, -2, 0), (0, -2, 0), (0, -1, 0)))
    tools.add_bezier4p(path, [c1, c2])
    assert len(path) == 3  # added a line segment between curves
    assert path.end == (0, -1, 0)
Exemplo n.º 2
0
def test_add_curves():
    path = Path()
    c1 = Bezier4P(((0, 0, 0), (0, 1, 0), (2, 1, 0), (2, 0, 0)))
    c2 = Bezier4P(((2, 0, 0), (2, -1, 0), (0, -1, 0), (0, 0, 0)))
    path.add_curves([c1, c2])
    assert len(path) == 2
    assert path.end == (0, 0, 0)
Exemplo n.º 3
0
def test_add_curves4():
    path = Path()
    c1 = Bezier4P(((0, 0), (0, 1), (2, 1), (2, 0)))
    c2 = Bezier4P(((2, 0), (2, -1), (0, -1), (0, 0)))
    tools.add_bezier4p(path, [c1, c2])
    assert len(path) == 2
    assert path.end == (0, 0)
Exemplo n.º 4
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)
    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)
Exemplo n.º 5
0
 def approximate(self) -> Iterable[Vec3]:
     control_points = [
         self.start,
         self.start + self.start_tangent,
         self.end + self.end_tangent,
         self.end,
     ]
     bezier = Bezier4P(control_points)
     return bezier.approximate(self.segments)
Exemplo n.º 6
0
def quadratic_to_cubic_bezier(curve: Bezier3P) -> Bezier4P:
    """Convert quadratic Bèzier curves (:class:`ezdxf.math.Bezier3P`) into
    cubic Bèzier curves (:class:`ezdxf.math.Bezier4P`).

    .. versionadded: 0.16

    """
    start, control, end = curve.control_points
    control_1 = start + 2 * (control - start) / 3
    control_2 = end + 2 * (control - end) / 3
    return Bezier4P((start, control_1, control_2, end))
Exemplo n.º 7
0
def cubic_bezier_interpolation(
        points: Iterable['Vertex']) -> Iterable[Bezier4P]:
    """ Returns an interpolation curve for given data `points` as multiple cubic
    Bézier-curves. Returns n-1 cubic Bézier-curves for n given data points,
    curve i goes from point[i] to point[i+1].

    Args:
        points: data points

    .. versionadded:: 0.13

    """
    from ezdxf.math import tridiagonal_matrix_solver
    # Source: https://towardsdatascience.com/b%C3%A9zier-interpolation-8033e9a262c2
    points = Vec3.tuple(points)
    if len(points) < 3:
        raise ValueError('At least 3 points required.')

    num = len(points) - 1

    # setup tri-diagonal matrix (a, b, c)
    b = [4.0] * num
    a = [1.0] * num
    c = [1.0] * num
    b[0] = 2.0
    b[num - 1] = 7.0
    a[num - 1] = 2.0

    # setup right-hand side quantities
    points_vector = [points[0] + 2.0 * points[1]]
    points_vector.extend(2.0 * (2.0 * points[i] + points[i + 1])
                         for i in range(1, num - 1))
    points_vector.append(8.0 * points[num - 1] + points[num])

    # solve tri-diagonal linear equation system
    solution = tridiagonal_matrix_solver((a, b, c), points_vector)
    control_points_1 = Vec3.list(solution.rows())
    control_points_2 = [
        p * 2.0 - cp for p, cp in zip(points[1:], control_points_1[1:])
    ]
    control_points_2.append((control_points_1[num - 1] + points[num]) / 2.0)

    for defpoints in zip(points, control_points_1, control_points_2,
                         points[1:]):
        yield Bezier4P(defpoints)
Exemplo n.º 8
0
    def approximate(self, segments: int = 20) -> Iterable[Vector]:
        """ Approximate path by vertices, `segments` is the count of approximation segments
        for each cubic bezier curve.
        """
        if not self._commands:
            return

        start = self._start
        yield start

        for cmd in self._commands:
            type_ = cmd[0]
            end_location = cmd[1]
            if type_ == Command.LINE_TO:
                yield end_location
            elif type_ == Command.CURVE_TO:
                pts = iter(Bezier4P((start, cmd[2], cmd[3], end_location)).approximate(segments))
                next(pts)  # skip first vertex
                yield from pts
            else:
                raise ValueError(f'Invalid command: {type_}')
            start = end_location
Exemplo n.º 9
0
def test_add_curves4_reverse():
    path = Path(start=(0, 0, 0))
    c1 = Bezier4P(((2, 0, 0), (2, 1, 0), (0, 1, 0), (0, 0, 0)))
    tools.add_bezier4p(path, [c1])
    assert len(path) == 1
    assert path.end == (2, 0, 0)
Exemplo n.º 10
0
def test_add_curves_reverse():
    path = Path(start=(0, 0, 0))
    c1 = Bezier4P(((2, 0, 0), (2, 1, 0), (0, 1, 0), (0, 0, 0)))
    path.add_curves([c1])
    assert len(path) == 1
    assert path.end == (2, 0, 0)
    def random_vec() -> Vec3:
        return Vec3(r.uniform(-10, 10), r.uniform(-10, 10), r.uniform(-10, 10))

    for i in range(1000):
        quadratic = Bezier3P((random_vec(), random_vec(), random_vec()))
        quadratic_approx = list(quadratic.approximate(10))
        cubic = quadratic_to_cubic_bezier(quadratic)
        cubic_approx = list(cubic.approximate(10))

        assert len(quadratic_approx) == len(cubic_approx)
        for p1, p2 in zip(quadratic_approx, cubic_approx):
            assert p1.isclose(p2)


# G1 continuity: normalized end-tangent == normalized start-tangent of next curve
B1 = Bezier4P([(0, 0), (1, 1), (2, 1), (3, 0)])

# B1/B2 has G1 continuity:
B2 = Bezier4P([(3, 0), (4, -1), (5, -1), (6, 0)])

# B1/B3 has no G1 continuity:
B3 = Bezier4P([(3, 0), (4, 1), (5, 1), (6, 0)])

# B1/B4 G1 continuity off tolerance:
B4 = Bezier4P([(3, 0), (4, -1.03), (5, -1.0), (6, 0)])

# B1/B5 has a gap between B1 end and B5 start:
B5 = Bezier4P([(4, 0), (5, -1), (6, -1), (7, 0)])


def test_g1_continuity_for_bezier_curves():
Exemplo n.º 12
0
def to_bsplines_and_vertices(path: Path,
                             g1_tol: float = G1_TOL) -> Iterable[PathParts]:
    """ Convert a :class:`Path` object into multiple cubic B-splines and
    polylines as lists of vertices. Breaks adjacent Bèzier without G1
    continuity into separated B-splines.

    Args:
        path: :class:`Path` objects
        g1_tol: tolerance for G1 continuity check

    Returns:
        :class:`~ezdxf.math.BSpline` and lists of :class:`~ezdxf.math.Vec3`

    .. versionadded:: 0.16

    """
    from ezdxf.math import bezier_to_bspline

    def to_vertices():
        points = [polyline[0][0]]
        for line in polyline:
            points.append(line[1])
        return points

    def to_bspline():
        b1 = bezier[0]
        _g1_continuity_curves = [b1]
        for b2 in bezier[1:]:
            if have_bezier_curves_g1_continuity(b1, b2, g1_tol):
                _g1_continuity_curves.append(b2)
            else:
                yield bezier_to_bspline(_g1_continuity_curves)
                _g1_continuity_curves = [b2]
            b1 = b2

        if _g1_continuity_curves:
            yield bezier_to_bspline(_g1_continuity_curves)

    prev = path.start
    curves = []
    for cmd in path:
        if cmd.type == Command.CURVE3_TO:
            curve = Bezier3P([prev, cmd.ctrl, cmd.end])
        elif cmd.type == Command.CURVE4_TO:
            curve = Bezier4P([prev, cmd.ctrl1, cmd.ctrl2, cmd.end])
        elif cmd.type == Command.LINE_TO:
            curve = (prev, cmd.end)
        else:
            raise ValueError
        curves.append(curve)
        prev = cmd.end

    bezier = []
    polyline = []
    for curve in curves:
        if isinstance(curve, tuple):
            if bezier:
                yield from to_bspline()
                bezier.clear()
            polyline.append(curve)
        else:
            if polyline:
                yield to_vertices()
                polyline.clear()
            bezier.append(curve)

    if bezier:
        yield from to_bspline()
    if polyline:
        yield to_vertices()
Exemplo n.º 13
0
 def approx_curve4(s, c1, c2, e) -> Iterable[Vec3]:
     return Bezier4P((s, c1, c2, e)).flattening(distance, segments)
Exemplo n.º 14
0
 def approx_curve4(s, c1, c2, e) -> Iterable[Vec3]:
     return Bezier4P((s, c1, c2, e)).approximate(segments)
Exemplo n.º 15
0
def quadratic_to_cubic_bezier(curve: Bezier3P) -> Bezier4P:
    start, control, end = curve.control_points
    control_1 = start + 2 * (control - start) / 3
    control_2 = end + 2 * (control - end) / 3
    return Bezier4P((start, control_1, control_2, end))
Exemplo n.º 16
0
 def approx(self):
     return ApproxParamT(Bezier4P([(0, 0), (1, 2), (2, 4), (3, 1)]))