def test_add_curves3(): path = Path() c1 = Bezier3P(((0, 0), (1, 1), (2, 0))) c2 = Bezier3P(((2, 0), (1, -1), (0, 0))) tools.add_bezier3p(path, [c1, c2]) assert len(path) == 2 assert path.end == (0, 0)
def test_add_curves3_with_gap(): path = Path() c1 = Bezier3P(((0, 0), (1, 1), (2, 0))) c2 = Bezier3P(((2, -1), (3, -2), (0, -1))) tools.add_bezier3p(path, [c1, c2]) assert len(path) == 3 # added a line segment between curves assert path.end == (0, -1)
def quadratic_bezier_from_3p(p1: Vertex, p2: Vertex, p3: Vertex) -> Bezier3P: """Returns a quadratic Bèzier curve :class:`Bezier3P` from three points. The curve starts at `p1`, goes through `p2` and ends at `p3`. (source: `pomax-2`_) .. versionadded:: 0.17.2 .. _pomax-2: https://pomax.github.io/bezierinfo/#pointcurves """ def u_func(t: float) -> float: mt = 1.0 - t mt2 = mt * mt return mt2 / (t * t + mt2) def ratio(t: float) -> float: t2 = t * t mt = 1.0 - t mt2 = mt * mt return abs((t2 + mt2 - 1.0) / (t2 + mt2)) s = Vec3(p1) b = Vec3(p2) e = Vec3(p3) d1 = (s - b).magnitude d2 = (e - b).magnitude t = d1 / (d1 + d2) u = u_func(t) c = s * u + e * (1.0 - u) a = b + (b - c) / ratio(t) return Bezier3P([s, a, e])
def test_quadratic_to_cubic_bezier(): r = random.Random(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)
def test_curve3_to(self): bez3 = Bezier3P([(0, 0), (2, 1), (4, 0)]) p = path.Path() p.curve3_to(bez3.control_points[2], bez3.control_points[1]) qpath = path.to_qpainter_path([p]) # Qt converts quadratic bezier curves unto cubic bezier curves assert qpath.elementCount() == 4 bez4 = quadratic_to_cubic_bezier(bez3) q1 = qpath.elementAt(1) assert q1.isCurveTo() # start of cure assert q1.x, q1.y == bez4.control_points[1] q2 = qpath.elementAt(2) assert q2.type == 3 # curve data element assert q2.x, q2.y == bez4.control_points[2] q3 = qpath.elementAt(3) assert q3.type == 3 # curve data element assert q3.x, q3.y == bez4.control_points[2]
def test_add_curves3_reverse(): path = Path(start=(0, 0)) c1 = Bezier3P(((2, 0), (1, 1), (0, 0))) tools.add_bezier3p(path, [c1]) assert len(path) == 1 assert path.end == (2, 0, 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()
def approx_curve3(s, c, e) -> Iterable[Vec3]: return Bezier3P((s, c, e)).flattening(distance, segments)
def approx_curve3(s, c, e) -> Iterable[Vec3]: return Bezier3P((s, c, e)).approximate(segments)
def approx(self): return ApproxParamT(Bezier3P([(0, 0), (1, 2), (2, 4)]))
def curve(self): return Bezier3P([(0, 0), (1, 2), (2, 4)])