def test_cubic_cubic(self):
        # q1 = Bezier(10,100, 90,30, 40,140, 220,220)
        # q2 = Bezier(5,150, 180,20, 80,250, 210,190)
        # console.log(q1.intersects(q2))
        q1 = CubicBezier(Point(10, 100), Point(90, 30), Point(40, 140),
                         Point(220, 220))
        q2 = CubicBezier(Point(5, 150), Point(180, 20), Point(80, 250),
                         Point(210, 190))
        i = q1.intersections(q2)
        # self.assertEqual(len(i),3)
        # self.assertAlmostEqual(i[0].point.x,81.7904225873)
        # self.assertAlmostEqual(i[0].point.y,109.899396337)
        # self.assertAlmostEqual(i[1].point.x,133.186831292)
        # self.assertAlmostEqual(i[1].point.y,167.148173322)
        # self.assertAlmostEqual(i[2].point.x,179.869157678)
        # self.assertAlmostEqual(i[2].point.y,199.661989162)
        import matplotlib.pyplot as plt
        fig, ax = plt.subplots()

        path = BezierPath()
        path.closed = False
        path.activeRepresentation = SegmentRepresentation(path, [q1])
        path.plot(ax)
        path.activeRepresentation = SegmentRepresentation(path, [q2])
        path.plot(ax)

        for n in i:
            circle = plt.Circle((n.point.x, n.point.y),
                                2,
                                fill=True,
                                color="red")
            ax.add_artist(circle)
示例#2
0
    def append(self, other, joinType="line"):
        """Append another path to this one. If the end point of the first
    path is not the same as the start point of the other path, a line
    will be drawn between them."""
        segs1 = self.asSegments()
        segs2 = other.asSegments()
        if len(segs1) < 1:
            self.activeRepresentation = SegmentRepresentation(self, segs2)
            return
        if len(segs2) < 1:
            self.activeRepresentation = SegmentRepresentation(self, segs1)
            return

        # Which way around should they go?
        dist1 = segs1[-1].end.distanceFrom(segs2[0].start)
        dist2 = segs1[-1].end.distanceFrom(segs2[-1].end)
        if dist2 > 2 * dist1:
            segs2 = list(reversed([x.reversed() for x in segs2]))

        # Add a line between if they don't match up
        if segs1[-1].end != segs2[0].start:
            segs1.append(Line(segs1[-1].end, segs2[0].start))

        # XXX Check for discontinuities and harmonize if needed

        segs1.extend(segs2)
        self.activeRepresentation = SegmentRepresentation(self, segs1)
        return self
    def removeOverlap(self):
        """Resolves a path's self-intersections by 'walking around the outside'."""
        if not self.closed:
            raise "Can only remove overlap on closed paths"
        splitlist = []
        splitpoints = {}

        def roundoff(point):
            return (int(point.x * 1), int(point.y * 1))

        for i in self.getSelfIntersections():
            splitlist.append((i.seg1, i.t1))
            splitlist.append((i.seg2, i.t2))
            splitpoints[roundoff(i.point)] = {"in": [], "out": []}
        self.splitAtPoints(splitlist)
        # Trace path
        segs = self.asSegments()
        for i in range(0, len(segs)):
            seg = segs[i]
            if i < len(segs) - 1:
                seg.next = segs[i + 1]
            else:
                seg.next = segs[0]
            seg.visited = False
            segWinding = self.windingNumberOfPoint(seg.pointAtTime(0.5))
            seg.windingNumber = segWinding
            if roundoff(seg.end) in splitpoints:
                splitpoints[roundoff(seg.end)]["in"].append(seg)
            if roundoff(seg.start) in splitpoints:
                splitpoints[roundoff(seg.start)]["out"].append(seg)
        newsegs = []
        copying = True
        logging.debug("Split points:", splitpoints)
        seg = segs[0]
        while not seg.visited:
            logging.debug("Starting at %s, visiting %s" % (seg.start, seg))
            newsegs.append(seg)
            seg.visited = True
            if roundoff(seg.end) in splitpoints and len(splitpoints[roundoff(
                    seg.end)]["out"]) > 0:
                logging.debug("\nI am at %s and have a decision: " % seg.end)
                inAngle = seg.tangentAtTime(1).angle
                logging.debug("My angle is %s" % inAngle)
                # logging.debug("Options are: ")
                # for s in splitpoints[roundoff(seg.end)]["out"]:
                # logging.debug(s.end, s.tangentAtTime(0).angle, self.windingNumberOfPoint(s.pointAtTime(0.5)))
                # Filter out the inside points
                splitpoints[roundoff(seg.end)]["out"] = [
                    o for o in splitpoints[roundoff(seg.end)]["out"]
                    if o.windingNumber < 2
                ]
                splitpoints[roundoff(seg.end)]["out"].sort(
                    key=lambda x: x.tangentAtTime(0).angle - inAngle)
                seg = splitpoints[roundoff(seg.end)]["out"].pop(-1)
                # seg = seg.next
                # logging.debug("I chose %s\n" % seg)
            else:
                seg = seg.next

        self.activeRepresentation = SegmentRepresentation(self, newsegs)
示例#4
0
 def removeIrrelevantSegments(self, relLength=1 / 50000, absLength=0):
     """Removes small and collinear line segments. Collinear line
 segments are adjacent line segments which are heading in the same
 direction, and hence can be collapsed into a single segment.
 Small segments (those less than ``absLength`` units, or less than
 ``relLength`` as a fraction of the path's total length) are
 removed entirely."""
     segs = self.asSegments()
     newsegs = [segs[0]]
     smallLength = self.length * relLength
     for i in range(1, len(segs)):
         prev = newsegs[-1]
         this = segs[i]
         if this.length < smallLength or this.length < absLength:
             this[0] = prev[0]
             newsegs[-1] = this
             continue
         if len(prev) == 2 and len(this) == 2:
             if math.isclose(
                     prev.tangentAtTime(0).angle,
                     this.tangentAtTime(0).angle):
                 this[0] = prev[0]
                 newsegs[-1] = this
                 continue
         newsegs.append(this)
     self.activeRepresentation = SegmentRepresentation(self, newsegs)
     return self
示例#5
0
 def balance(self):
     """Performs Tunni balancing on the path."""
     segs = self.asSegments()
     for x in segs:
         if isinstance(x, CubicBezier): x.balance()
     self.activeRepresentation = SegmentRepresentation(self, segs)
     return self
示例#6
0
 def fromSegments(klass, array):
     """Construct a path from an array of Segment objects."""
     self = klass()
     for a in array:
         assert (isinstance(a, Segment))
     self.activeRepresentation = SegmentRepresentation(self, array)
     return self
示例#7
0
    def splitAtPoints(self, splitlist):
        def mapx(v, ds):
            return (v - ds) / (1 - ds)

        segs = self.asSegments()
        newsegs = []
        # Cluster splitlist by seg
        newsplitlist = {}
        for (seg, t) in splitlist:
            if not seg in newsplitlist: newsplitlist[seg] = []
            newsplitlist[seg].append(t)
        for k in newsplitlist:
            newsplitlist[k] = sorted(newsplitlist[k])
        # Now walk the path
        for seg in segs:
            if seg in newsplitlist:
                tList = newsplitlist[seg]
                while len(tList) > 0:
                    t = tList.pop(0)
                    if t < 1e-8: continue
                    seg1, seg2 = seg.splitAtTime(t)
                    newsegs.append(seg1)
                    seg = seg2
                    for i in range(0, len(tList)):
                        tList[i] = mapx(tList[i], t)
            newsegs.append(seg)
        self.activeRepresentation = SegmentRepresentation(self, newsegs)
示例#8
0
    def fromPoints(self,
                   points,
                   error=50.0,
                   cornerTolerance=20.0,
                   maxSegments=20):
        """Fit a poly-bezier curve to the points given. This operation should be familiar
    from 'pencil' tools in a vector drawing application: the application samples points
    where your mouse pointer has been dragged, and then turns the sketch into a Bezier
    path. The goodness of fit can be controlled by tuning the `error` parameter. Corner
    detection can be controlled with `cornerTolerance`.

    Here are some points fit with `error=100.0`:

..  figure:: curvefit1.png
    :scale: 75 %
    :alt: curvefit1


    And with `error=10.0`:

..  figure:: curvefit2.png
    :scale: 75 %
    :alt: curvefit1

    """
        from beziers.utils.curvefitter import CurveFit
        segs = CurveFit.fitCurve(points, error, cornerTolerance, maxSegments)
        path = BezierPath()
        path.closed = False
        path.activeRepresentation = SegmentRepresentation(path, segs)
        return path
示例#9
0
 def test_addextremes(self):
     q = CubicBezier(Point(42, 135), Point(129, 242), Point(167, 77),
                     Point(65, 59))
     ex = q.findExtremes()
     self.assertEqual(len(ex), 2)
     path = BezierPath()
     path.closed = False
     path.activeRepresentation = SegmentRepresentation(path, [q])
     path.addExtremes()
     path.balance()
     segs = path.asSegments()
     self.assertEqual(len(segs), 3)
 def test_cubic_line(self):
     q = CubicBezier(Point(100, 240), Point(30, 60), Point(210, 230),
                     Point(160, 30))
     l = Line(Point(25, 260), Point(230, 20))
     path = BezierPath()
     path.closed = False
     path.activeRepresentation = SegmentRepresentation(path, [q])
     i = q.intersections(l)
     self.assertEqual(len(i), 3)
     self.assertEqual(i[0].point, q.pointAtTime(0.117517031451))
     self.assertEqual(i[1].point, q.pointAtTime(0.518591792307))
     self.assertEqual(i[2].point, q.pointAtTime(0.867886610031))
示例#11
0
    def offset(self, vector, rotateVector=True):
        """Returns a new BezierPath which approximates offsetting the
    current Bezier path by the given vector. Note that the vector
    will be rotated around the normal of the curve so that the
    offsetting always happens on the same 'side' of the curve:

..  figure:: offset1.png
    :scale: 75 %
    :alt: offset1

    If you don't want that and you want 'straight' offsetting instead
    (which may intersect with the original curve), pass
    `rotateVector=False`:

..  figure:: offset2.png
    :scale: 75 %
    :alt: offset1

    """
        # Method 1 - curve fit
        newsegs = []
        points = []

        def finishPoints(newsegs, points):
            if len(points) > 0:
                bp = BezierPath.fromPoints(points,
                                           error=0.1,
                                           cornerTolerance=1)
                newsegs.extend(bp.asSegments())
            while len(points) > 0:
                points.pop()

        for seg in self.asSegments():
            if isinstance(seg, Line):
                finishPoints(newsegs, points)
                newsegs.append(seg.translated(vector))
            else:
                t = 0.0
                while t < 1.0:
                    if rotateVector:
                        points.append(
                            seg.pointAtTime(t) +
                            vector.rotated(Point(0, 0),
                                           seg.normalAtTime(t).angle))
                    else:
                        points.append(seg.pointAtTime(t) + vector)
                    step = max(abs(seg.curvatureAtTime(t)), 0.1)
                    t = t + min(seg.length / step, 0.1)
        finishPoints(newsegs, points)
        newpath = BezierPath()
        newpath.activeRepresentation = SegmentRepresentation(newpath, newsegs)
        return newpath
示例#12
0
    def smooth(self, maxCollectionSize=30, lengthLimit=20, cornerTolerance=10):
        """Smooths a curve, by collating lists of small (at most ``lengthLimit``
    units long) segments at most ``maxCollectionSize`` segments at a time,
    and running them through a curve fitting algorithm. The list collation
    also stops when one segment turns more than ``cornerTolerance`` degrees
    away from the previous one, so that corners are not smoothed."""
        smallLineLength = lengthLimit
        segs = self.asSegments()
        i = 0
        collection = []
        while i < len(segs):
            s = segs[i]
            if s.length < smallLineLength and len(
                    collection) <= maxCollectionSize:
                collection.append(s)
            else:
                corner = False
                if len(collection) > 1:
                    last = collection[-1]
                    if abs(
                            last.tangentAtTime(1).angle -
                            s.tangentAtTime(0).angle) > math.radians(
                                cornerTolerance):
                        corner = True
                if len(collection
                       ) > maxCollectionSize or corner or i == len(segs) - 2:
                    points = [x.start for x in collection]
                    bp = BezierPath.fromPoints(points)
                    if len(bp.asSegments()) > 0:
                        segs[i - len(collection):i] = bp.asSegments()
                        i -= len(collection)
                    collection = []
            i += 1
        if len(collection) > 0:
            points = [x.start for x in collection]
            bp = BezierPath.fromPoints(points)
            if len(bp.asSegments()) > 0:
                segs[i - (1 + len(collection)):i - 1] = bp.asSegments()

        self.activeRepresentation = SegmentRepresentation(self, segs)
        return self
示例#13
0
 def scale(self, by):
     """Scales the path by a given magnitude."""
     seg2 = [x.scaled(by) for x in self.asSegments()]
     self.activeRepresentation = SegmentRepresentation(self, seg2)
     return self
示例#14
0
 def rotate(self, about, angle):
     """Rotate the path by a given vector."""
     seg2 = [x.rotated(about, angle) for x in self.asSegments()]
     self.activeRepresentation = SegmentRepresentation(self, seg2)
     return self
示例#15
0
 def translate(self, vector):
     """Translates the path by a given vector."""
     seg2 = [x.translated(vector) for x in self.asSegments()]
     self.activeRepresentation = SegmentRepresentation(self, seg2)
     return self
示例#16
0
 def reverse(self):
     """Reverse this path (mutates path)."""
     seg2 = [x.reversed() for x in self.asSegments()]
     self.activeRepresentation = SegmentRepresentation(
         self, list(reversed(seg2)))
     return self
示例#17
0
 def round(self):
     """Rounds the points of this path to integer coordinates."""
     segs = self.asSegments()
     for s in segs:
         s.round()
     self.activeRepresentation = SegmentRepresentation(self, segs)