Пример #1
0
    def center(self):
        """
        Returns a Point representing the center of the Triangle.
        
        >>> P = point.Point
        >>> print(Triangle(P(3, 5), P(0, 1), P(-1, 3)).center())
        (0.6666666666666667, 3.0)
        >>> print(Triangle(P(-10, 0), P(0, 0.000001), P(10, 0)).center())
        (0.0, 5e-07)
        """

        if self.isDegenerate():
            minX = min(self.p1.x, self.p2.x, self.p3.x)
            minY = min(self.p1.y, self.p2.y, self.p3.y)
            maxX = max(self.p1.x, self.p2.x, self.p3.x)
            maxY = max(self.p1.y, self.p2.y, self.p3.y)

            L = line.Line(point.Point(minX, minY), point.Point(maxX, maxY))

            return L.midpoint()

        Line = line.Line
        line12 = Line(self.p1, self.p2)
        line23 = Line(self.p2, self.p3)
        testLine1 = Line(self.p3, line12.midpoint())
        testLine2 = Line(self.p1, line23.midpoint())
        return testLine1.intersection(testLine2)
Пример #2
0
 def extrema(self, excludeOffCurve=False):
     """
     Returns a Rectangle representing the extrema (based on the actual point
     coordinates, and not the curve itself).
     
     >>> print(_testingValues[0].extrema())
     Minimum X = 1, Minimum Y = 2, Maximum X = 10, Maximum Y = 7
     
     >>> print(_testingValues[2].extrema())
     Minimum X = 1, Minimum Y = 1, Maximum X = 5, Maximum Y = 5
     """
     
     if self.isLine():
         return line.Line(self.start, self.end).extrema()
     
     if excludeOffCurve:
         ps = [self.start, self.end]
     else:
         ps = [self.start, self.control0, self.control1, self.end]
     
     return rectangle.Rectangle(
       min(p.x for p in ps),
       min(p.y for p in ps),
       max(p.x for p in ps),
       max(p.y for p in ps))
Пример #3
0
 def pointFromParametricValue(self, t):
     """
     Returns a Point representing the specified parametric value.
     
     >>> obj = _testingValues[0]
     >>> print(obj.pointFromParametricValue(0))
     (1, 2)
     
     >>> print(obj.pointFromParametricValue(1))
     (10, 7)
     
     >>> print(obj.pointFromParametricValue(0.5))
     (4.0, 5.25)
     
     >>> print(obj.pointFromParametricValue(0.25))
     (2.125, 4.1875)
     
     >>> print(obj.pointFromParametricValue(1.5))
     (19.0, 13.25)
     
     >>> print(obj.pointFromParametricValue(-0.75))
     (2.125, -18.8125)
     """
     
     if self.isLine():
         L = line.Line(self.start, self.end)
         return L.pointFromParametricValue(t)
     
     if t == 0:
         return self.start
     
     if t == 1:
         return self.end
     
     a = self.start.x
     b = self.control0.x
     c = self.control1.x
     d = self.end.x
     e = self.start.y
     f = self.control0.y
     g = self.control1.y
     h = self.end.y
     
     x = (
       (-a + 3 * b - 3 * c + d) * (t ** 3) +
       (3 * a - 6 * b + 3 * c) * (t ** 2) +
       (-3 * a + 3 * b) * t +
       a)
     
     y = (
       (-e + 3 * f - 3 * g + h) * (t ** 3) +
       (3 * e - 6 * f + 3 * g) * (t ** 2) +
       (-3 * e + 3 * f) * t +
       e)
     
     return point.Point(x, y)
Пример #4
0
    def normal(self):
        """
        Returns a new BSpline of unit length, starting from the point on the
        curve corresponding to a t-value of 0.5, and moving in a direction
        orthogonal to the tangent at that point, outwards (i.e. away from the
        line from self.onCurve1 to self.onCurve2).
        
        >>> P = point.Point
        >>> print(BSpline(P(2, -3), P(4, 4), P(0, 1)).normal())
        Line from (1.5, 0.75) to (0.5384760523591766, 1.0247211278973771)
        """

        startPt = self.pointFromParametricValue(0.5)
        refLine = line.Line(self.onCurve1, self.onCurve2)
        tProj = refLine.parametricValueFromProjectedPoint(startPt)
        tProjPt = refLine.pointFromParametricValue(tProj)
        unnormEndPt = (startPt * 2) - tProjPt
        unnormLine = line.Line(startPt, unnormEndPt)
        return unnormLine.normalized()
Пример #5
0
    def normalized(self):
        """
        If self.offCurve is None, or if self.offCurve is not None and lies
        legitimately off the line from self.onCurve1 to self.onCurve2, then
        self is returned. Otherwise, a new BSpline is created with the off-
        curve point set appropriately.
        
        >>> P = point.Point
        >>> b = BSpline(P(2, -3), P(4, 4), P(0, 1))
        >>> print(b)
        Curve from (2, -3) to (4, 4) with off-curve point (0, 1)
        >>> print(b.normalized())
        Curve from (2, -3) to (4, 4) with off-curve point (0, 1)
        
        >>> b = BSpline(P(0, 0), P(10, 10), P(5, 5))
        >>> print(b)
        Curve from (0, 0) to (10, 10) with off-curve point (5, 5)
        >>> print(b.normalized())
        Line from (0, 0) to (10, 10)
        
        We also need to normalize the cases where the off-curve point is
        co-linear with the two on-curve points, but has a parametric value
        less than zero or greater than one:
        
        >>> print(BSpline(P(5, 5), P(10, 10), P(0, 0)).normalized())
        Line from (0, 0) to (10, 10)
        >>> print(BSpline(P(0,0), P(10, 10), P(5,5)).normalized())
        Line from (0, 0) to (10, 10)
        >>> print(BSpline(P(0,0), P(10, 10), P(9.0001,10.0001)).normalized())
        Curve from (0, 0) to (10, 10) with off-curve point (9.0001, 10.0001)
        """

        if self.offCurve is None or self.onCurve1.equalEnough(self.onCurve2):
            return self

        L = line.Line(self.onCurve1, self.onCurve2)
        t = L.parametricValueFromPoint(self.offCurve)

        if t is None:
            return self

        # If we get here, the supposed off-curve point is actually colinear
        # with the two on-curve points, so it's a straight line and needs to
        # reflect that fact.

        if mathutilities.closeEnoughSpan(t):
            return type(self)(self.onCurve1, self.onCurve2, None)

        if t < 0:
            return type(self)(self.offCurve, self.onCurve2, None)

        return type(self)(self.onCurve1, self.offCurve, None)
Пример #6
0
    def slopeFromParametricValue(self, t):
        """
        Returns the slope of the curve at parametric value t.
        
        >>> P = point.Point
        >>> b = BSpline(P(2, -3), P(4, 4), P(0, 1))
        >>> print(b.slopeFromParametricValue(0))
        -2.0
        >>> print(b.slopeFromParametricValue(0.5))
        3.5
        >>> print(b.slopeFromParametricValue(1))
        0.75
        """

        # The slope is dy/dx, which can be expressed parametrically by:
        #
        # dy/dx = dy/dt divided by dx/dt
        #
        # Let a=onCurve1.x, b=offCurve.x, and c=onCurve2.x. The parametric
        # equation for x in terms of t is:
        #
        # x = a(1-t)^2 + 2bt(1-t) + ct^2
        #
        # Expanding and differentiating with respect to t, we get:
        #
        # dx/dt = 2(a+c-2b)t + 2(b-a)
        #
        # Using d, e, and f for analogous equations in y, we get:
        #
        # dy/dt = 2(d+f-2e)t + 2(e-d)
        #
        # Dividing dy/dt by dx/dt gives us the result.

        if self.offCurve is None:
            return line.Line(self.onCurve1, self.onCurve2).slope()

        a = self.onCurve1.x
        b = self.offCurve.x
        c = self.onCurve2.x
        denom = (a + c - 2 * b) * t + (b - a)

        if mathutilities.zeroEnough(denom):
            return float("+inf")

        d = self.onCurve1.y
        e = self.offCurve.y
        f = self.onCurve2.y
        numer = (d + f - 2 * e) * t + (e - d)

        return numer / denom
Пример #7
0
 def parametricValueFromPoint(self, p):
     """
     Returns the parametric t-value for the specified point p, or None if p
     does not lie on the spline.
     
     >>> obj = _testingValues[0]
     >>> print(obj.parametricValueFromPoint(point.Point(1, 2)))
     0
     
     >>> print(obj.parametricValueFromPoint(point.Point(10, 7)))
     1
     
     >>> print(obj.parametricValueFromPoint(point.Point(4, 5.25)))
     0.5
     
     >>> print(obj.parametricValueFromPoint(point.Point(4, -5.25)))
     None
     """
     
     if self.isLine():
         L = line.Line(self.start, self.end)
         return L.parametricValueFromPoint(p)
     
     a = self.start.x
     b = self.control0.x
     c = self.control1.x
     d = self.end.x
     
     roots = mathutilities.cubic(
       (-a + 3 * b - 3 * c + d),
       (3 * a - 6 * b + 3 * c),
       (-3 * a + 3 * b),
       (a - p.x))
     
     # Check that the y-value for real roots is sufficiently close
     
     for root in roots:
         if not mathutilities.zeroEnough(root.imag):
             continue
         
         pTest = self.pointFromParametricValue(root)
         
         if mathutilities.closeEnough(p.y, pTest.y):
             return root
     
     return None
Пример #8
0
    def distanceToPoint(self, p):
        """
        Returns an unsigned distance from the point to the nearest position on
        the curve.
        
        >>> P = point.Point
        >>> obj = BSpline(P(2, -3), P(4, 4), P(0, 1))
        >>> print(obj.distanceToPoint(P(3, 3)))
        0.10068277482538779
        >>> print(BSpline(P(2, -3), P(4, 4), None).distanceToPoint(P(2,-3)))
        0.0
        >>> print(BSpline(P(4, 4), P(4, 4), P(2, 4)).distanceToPoint(P(2,-3)))
        7.280109889280518
        """

        if self.onCurve1.equalEnough(self.onCurve2):
            return p.distanceFrom(self.onCurve1)

        if self.offCurve is None:
            return line.Line(self.onCurve1, self.onCurve2).distanceToPoint(p)

        a = self.onCurve1.x
        b = self.onCurve2.x
        c = self.offCurve.x
        d = self.onCurve1.y
        e = self.onCurve2.y
        f = self.offCurve.y
        A = a + b - 2 * c
        B = c - a
        C = d + e - 2 * f
        D = f - d
        E = a - p.x
        F = d - p.y

        roots = mathutilities.cubic(
            4 * (A * A + C * C), 12 * (A * B + C * D),
            4 * (2 * B * B + 2 * D * D + A * E + C * F), 4 * (B * E + D * F))

        ZE = mathutilities.zeroEnough

        v = [
            p.distanceFrom(self.pointFromParametricValue(root))
            for root in roots if ZE(root.imag)
        ]

        return utilities.safeMin(v)
Пример #9
0
 def isLine(self):
     """
     Returns True if the two control points are colinear with the start and
     end points (or are both None), False otherwise.
     
     >>> _testingValues[1].isLine()
     False
     
     >>> _testingValues[2].isLine()
     True
     """
     
     if self.control0 is None and self.control1 is None:
         return True
     
     L = line.Line(self.start, self.end)
     
     if L.parametricValueFromPoint(self.control0) is None:
         return False
     
     return L.parametricValueFromPoint(self.control1) is not None
Пример #10
0
 def piece(self, t1, t2):
     """
     Create and return a new CSpline object which maps t1 to t2 in the
     original to 0 to 1 in the new.
     
     Returns two things: the new CSpline, and an anonymous function which
     maps an old t-value to a new u-value.
     
     >>> origObj = _testingValues[0]
     >>> origObj.pprint()
     Start point:
       0: 1
       1: 2
     First control point:
       0: 2
       1: 6
     Second control point:
       0: 5
       1: 5
     End point:
       0: 10
       1: 7
     >>> newObj, f = origObj.piece(0.25, 0.75)
     >>> newObj.pprint()
     Start point:
       0: 2.125
       1: 4.1875
     First control point:
       0: 3.125
       1: 5.1875
     Second control point:
       0: 4.625
       1: 5.4375
     End point:
       0: 6.625
       1: 5.9375
     >>> print(origObj.pointFromParametricValue(0.25))
     (2.125, 4.1875)
     >>> print(newObj.pointFromParametricValue(0))
     (2.125, 4.1875)
     >>> print(origObj.pointFromParametricValue(0.75))
     (6.625, 5.9375)
     >>> print(newObj.pointFromParametricValue(1))
     (6.625, 5.9375)
     """
     
     if self.isLine():
         return line.Line(self.start, self.end).piece(t1, t2)
     
     A, B, C, D, E, F, G, H = self._coefficients()
     
     if t1 > t2:
         t1, t2 = t2, t1
     
     a = t2 - t1
     b = t1
     A2 = A * (a ** 3)
     B2 = 3 * A * a * a * b + B * a * a
     C2 = 3 * A * a * b * b + 2 * B * a * b + C * a
     D2 = A * (b ** 3) + B * b * b + C * b + D
     E2 = E * (a ** 3)
     F2 = 3 * E * a * a * b + F * a * a
     G2 = 3 * E * a * b * b + 2 * F * a * b + G * a
     H2 = E * (b ** 3) + F * b * b + G * b + H
     pVec = self._parametersToPoints(A2, B2, C2, D2, E2, F2, G2, H2)
     
     newSpline = type(self)(
       point.Point(pVec[0], pVec[4]),
       point.Point(pVec[1], pVec[5]),
       point.Point(pVec[2], pVec[6]),
       point.Point(pVec[3], pVec[7]))
     
     return newSpline, lambda x: ((x - t1) / (t2 - t1))
Пример #11
0
    def intersection(self, other, delta=1.0e-5):
        """
        Returns a tuple of objects representing the intersection of the two
        CSpline objects, usually Points but possibly a CSpline; empty if there
        is no intersection.
        
        We use Newton's method here to solve the nonlinear system:
        
            self_x(t) - other_x(u) = 0
            self_y(t) - other_y(u) = 0
        
        There is an admittedly ad hoc decision here to look at 25 different
        (t, u) pairs as starting values to find local solutions. This method
        has (so far) found all solutions for intersections I've thrown at it,
        including one case with 5 intersections, but this heuristic code should
        be rigorously tested.
        
        Also, the resulting t- and u-values are rounded to six places.
        
        >>> for obj in _testingValues[0].intersection(_testingValues[3]):
        ...     obj.pprint()
        Start point:
          0: 4.0
          1: 5.25
        First control point:
          0: 5.5
          1: 5.75
        Second control point:
          0: 7.5
          1: 6.0
        End point:
          0: 10.0
          1: 7.0
          
        >>> P = point.Point
        >>> a = CSpline(P(0, 0), P(3, 3), P(4, 4), P(6, 6))
        >>> for obj in _testingValues[2].intersection(a):
        ...   print(str(obj))
        Line from (1, 1) to (5, 5)

        >>> for obj in _testingValues[2].intersection(_testingValues[2]):
        ...   print(str(obj))
        Line from (1, 1) to (5, 5)
        
        >>> c = CSpline(P(0, 0), P(-1, 0), P(0, 0), P(0, 0))
        >>> print(_testingValues[2].intersection(c))
        ()
        """
        
        # Before we start working on potential point intersections, do a check
        # to see if the entire cubics overlap, or if they're both lines.
        
        selfLine = self.isLine()
        otherLine = other.isLine()
        
        if selfLine or otherLine:
            if selfLine and otherLine:
                L1 = line.Line(self.start, self.end)
                L2 = line.Line(other.start, other.end)
                sect = L1.intersection(L2, delta)
                
                if sect is None:
                    return ()
                
                return (sect,)
            
            if selfLine and (self.control0 is None and self.control1 is None):
                L1 = line.Line(self.start, self.end)
                
                self = type(self)(
                  self.start,
                  L1.midpoint(),
                  L1.midpoint(),
                  self.end)
            
            if otherLine and (other.control0 is None and other.control1 is None):
                L1 = line.Line(other.start, other.end)
                
                other = type(other)(
                  other.start,
                  L1.midpoint(),
                  L1.midpoint(),
                  other.end)
        
        pvsSelf = [0.0, 0.25, 0.5, 0.75, 1.0]
        pts = [self.pointFromParametricValue(n) for n in pvsSelf]
        pvsOther = [other.parametricValueFromPoint(pt) for pt in pts]
        ZE = mathutilities.zeroEnough
        
        if None not in pvsOther:
            diffs = [i - j for i, j in zip(pvsSelf, pvsOther)]
            
            if ZE(max(diffs) - min(diffs)):
                d = diffs[0]
                
                if not (ZE(d) or ZE(d - 1) or (0 < d < 1)):
                    return ()
                
                elif ZE(d):
                    return (self.__deepcopy__(),)
                
                elif d < 0:
                    return (self.piece(0, 1 + d)[0],)
                
                return (self.piece(d, 1)[0],)
        
        # If we get here, there is no overlap, so proceed with the actual
        # calculations.
        
        A, B, C, D, E, F, G, H = self._coefficients()
        S, T, U, V, W, X, Y, Z = other._coefficients()
        
        def func(v):  # maps a (u, v) pair to a distance in x and y
            t, u = v
            
            x = (
              A * (t ** 3)
              + B * t * t
              + C * t
              + D
              - S * (u ** 3)
              - T * u * u
              - U * u
              - V)
            
            y = (
              E * (t ** 3)
              + F * t * t
              + G * t
              + H
              - W * (u ** 3)
              - X * u * u
              - Y * u
              - Z)
    
            return [x, y]

        def Jacobian(t, u):
            e1 = 3 * A * t * t + 2 * B * t + C
            e2 = -3 * S * u * u - 2 * T * u - U
            e3 = 3 * E * t * t + 2 * F * t + G
            e4 = -3 * W * u * u - 2 * X * u - Y
            return [[e1, e2], [e3, e4]]
        
        def matInv(m):
            det = m[0][0] * m[1][1] - m[0][1] * m[1][0]
            
            return [
              [m[1][1] / det, -m[0][1] / det],
              [-m[1][0] / det, m[0][0] / det]]
        
        def matMul(m, v):
            e1 = m[0][0] * v[0] + m[0][1] * v[1]
            e2 = m[1][0] * v[0] + m[1][1] * v[1]
            return [e1, e2]
        
        solutions = set()
        zeroDivCount = 0
        giveUpCount = 0
        
        for n1 in range(5):
            for n2 in range(5):
                n = [n1 / 4, n2 / 4]
                nPrior = [-1, -1]
                iterCount = 0
                okToProceed = True
                
                while (not ZE(n[0] - nPrior[0])) or (not ZE(n[1] - nPrior[1])):
                    try:
                        nDelta = matMul(matInv(Jacobian(*n)), func(n))
                    
                    except ZeroDivisionError:
                        zeroDivCount += 1
                        okToProceed = False
                    
                    if not okToProceed:
                        break
                    
                    nPrior = list(n)
                    n[0] -= nDelta[0]
                    n[1] -= nDelta[1]
                    iterCount += 1
                
                    if iterCount > 10:
                        giveUpCount += 1
                        okToProceed = False
                        break
                
                if okToProceed:
                    if not (ZE(n[0]) or ZE(n[0] - 1) or (0 < n[0] < 1)):
                        continue
                
                    if not (ZE(n[1]) or ZE(n[1] - 1) or (0 < n[1] < 1)):
                        continue
                
                    checkIt = func(n)
                
                    if (not ZE(checkIt[0])) or (not ZE(checkIt[1])):
                        continue
                
                    n = (round(n[0], 6), round(n[1], 6))
                    solutions.add(n)
        
        return tuple(
          self.pointFromParametricValue(obj[0])
          for obj in solutions)
Пример #12
0
 def distanceToPoint(self, p):
     """
     Returns an unsigned distance from the point to the nearest position on
     the curve. This will perforce be a point normal to the curve, but as
     there may be several such, the distance returned will be the smallest.
     
     >>> P = point.Point
     >>> print(_testingValues[0].distanceToPoint(P(5, 3)))
     2.4471964319857658
     
     >>> print(_testingValues[1].distanceToPoint(P(10, 10)))
     3.3691151093862604
     
     >>> print(_testingValues[2].distanceToPoint(P(6,6)))
     0.0
     """
     
     if self.isLine():
         L = line.Line(self.start, self.end)
         return L.distanceToPoint(p)
     
     a = self.start.x
     b = self.control0.x
     c = self.control1.x
     d = self.end.x
     e = self.start.y
     f = self.control0.y
     g = self.control1.y
     h = self.end.y
     A = 3 * b - 3 * c + d - a
     B = 3 * a + 3 * c - 6 * b
     C = 3 * b - 3 * a
     D = 3 * f - 3 * g + h - e
     E = 3 * e + 3 * g - 6 * f
     F = 3 * f - 3 * e
     G = a - p.x
     H = e - p.y
     
     def sumPrime(t):
         return (
           6 * (A * A + D * D) * (t ** 5) +
           10 * (A * B + D * E) * (t ** 4) +
           4 * (B * B + E * E + 2 * A * C + 2 * D * F) * (t ** 3) +
           6 * (A * G + B * C + D * H + E * F) * (t * t) +
           2 * (C * C + F * F + 2 * B * G + 2 * E * H) * t +
           2 * (C * G + F * H))
     
     def sumDoublePrime(t):
         return (
           30 * (A * A + D * D) * (t ** 4) +
           40 * (A * B + D * E) * (t ** 3) +
           12 * (B * B + E * E + 2 * A * C + 2 * D * F) * (t * t) +
           12 * (A * G + B * C + D * H + E * F) * t +
           2 * (C * C + F * F + 2 * B * G + 2 * E * H))
     
     tReal = mathutilities.newton(sumPrime, sumDoublePrime, 0.5)
     
     # Now we remove this root from the quintic, leaving a quartic that can
     # be solved directly.
     
     factors = (
       2 * (C * G + F * H),
       2 * (C * C + F * F + 2 * B * G + 2 * E * H),
       6 * (A * G + B * C + D * H + E * F),
       4 * (B * B + E * E + 2 * A * C + 2 * D * F),
       10 * (A * B + D * E),
       6 * (A * A + D * D))
     
     reducedFactors = mathutilities.polyreduce(factors, tReal)
     roots = (tReal,) + mathutilities.quartic(*reversed(reducedFactors))
     ZE = mathutilities.zeroEnough
     
     v = [
       p.distanceFrom(self.pointFromParametricValue(root))
       for root in roots
       if ZE(root.imag)]
     
     return utilities.safeMin(v)
Пример #13
0
 def bounds(self):
     """
     Returns a Rectangle representing the actual bounds, based on the curve
     itself.
     
     >>> print(_testingValues[0].bounds())
     Minimum X = 1, Minimum Y = 2, Maximum X = 10, Maximum Y = 7
     
     >>> print(_testingValues[1].bounds())
     Minimum X = 1, Minimum Y = 1, Maximum X = 19, Maximum Y = 6.631236180096162
     
     >>> print(_testingValues[2].bounds())
     Minimum X = 1, Minimum Y = 1, Maximum X = 5, Maximum Y = 5
     """
     
     if self.isLine():
         return line.Line(self.start, self.end).bounds()
     
     extremaRect = self.extrema(True)
     a = self.start.x
     b = self.control0.x
     c = self.control1.x
     d = self.end.x
     e = self.start.y
     f = self.control0.y
     g = self.control1.y
     h = self.end.y
     ZE = mathutilities.zeroEnough
     CES = mathutilities.closeEnoughSpan
     
     # xZeroes are where dx/dt goes to zero (i.e. vertical tangents)
     xZeroes = mathutilities.quadratic(
       3 * (-a + 3 * b - 3 * c + d),
       2 * (3 * a - 6 * b + 3 * c),
       (-3 * a + 3 * b))
     
     xZeroes = [n for n in xZeroes if ZE(n.imag) if CES(n)]
     
     # yZeroes are where dy/dt goes to zero (i.e. horizontal tangents)
     yZeroes = mathutilities.quadratic(
       3 * (-e + 3 * f - 3 * g + h),
       2 * (3 * e - 6 * f + 3 * g),
       (-3 * e + 3 * f))
     
     yZeroes = [n for n in yZeroes if ZE(n.imag) if CES(n)]
     
     pX = [
       self.pointFromParametricValue(root)
       for root in xZeroes]
     
     pY = [
       self.pointFromParametricValue(root)
       for root in yZeroes
       if root not in xZeroes]
     
     vXMin = [extremaRect.xMin] + [p.x for p in pX]
     vXMax = [extremaRect.xMax] + [p.x for p in pX]
     vYMin = [extremaRect.yMin] + [p.y for p in pY]
     vYMax = [extremaRect.yMax] + [p.y for p in pY]
     
     return rectangle.Rectangle(
       min(vXMin),
       min(vYMin),
       max(vXMax),
       max(vYMax))
Пример #14
0
def _validate(obj, **kwArgs):
    logger = kwArgs['logger']
    r = True
    
    # check for empty contour

    if len(obj) < 1:
        logger.error((
          'V0950',
          (),
          "Contour is empty (no points)."))
          
        r = False
    
    # check for coincident adjacent points
    
    sawDups = sawBadDups = False
    
    for k, g in itertools.groupby(obj, key=tuple):
        v = list(g)
        
        if len(v) > 1:
            sawDups = True
        
        if len(set(x.onCurve for x in v)) > 1:
            sawBadDups = True
    
    if sawBadDups:
        logger.error((
          'V0295',
          (),
          "Contour has duplicate adjacent points of differing "
          "on-curve states."))
        
        r = False
    
    elif sawDups:
        logger.warning((
          'W1111',
          (),
          "Contour has duplicate adjacent points."))
    
    # check for coincident non-adjacent points and zero-length contours
    
    d = {}
    sawDups = sawBadDups = False
    
    for i, p in enumerate(obj):
        d.setdefault(tuple(p), set()).add(i)
    
    if len(d) == 1:
        logger.warning((
          'W1113',
          (),
          "Contour is degenerate (only a single (x, y) location)."))
          
    if len(d) == 2:
        logger.error((
          'V1013',
          (),
          "Contour is degenerate (only two (x,y) locations)."))
    
    for k, s in d.items():
        if len(s) > 1:
            for a, b in itertools.permutations(s, 2):
                if abs(a - b) > 1:
                    sawDups = True
                    
                    if obj[a].onCurve != obj[b].onCurve:
                        sawBadDups = True
    
    if sawBadDups:
        logger.error((
          'V0297',
          (),
          "Contour has duplicate non-adjacent points of differing "
          "on-curve states."))
        
        r = False
    
    elif sawDups:
        logger.warning((
          'V0296',
          (),
          "Contour has duplicate non-adjacent points."))
    
    # check for on-curve points on the extrema
    
    extRectWith = obj.extrema(False)
    extRectWithout = obj.extrema(True)
    
    if extRectWith != extRectWithout:
        logger.warning((
          'W1112',
          (),
          "Not all extrema are marked with on-curve points."))
    
    # check for internally-intersecting contours
    
    allSplines = list(obj.splineIterator())
    allExtrema = [x.extrema() for x in allSplines]
    it = zip(allSplines, allExtrema)
    foundOverlap = False
    CE = mathutilities.closeEnough
    
    for (curve1, rect1), (curve2, rect2) in itertools.combinations(it, 2):
        # only bother with intersection check if extrema overlap
        
        if (
          rect1.isEmpty() or
          rect2.isEmpty() or
          max(rect1.overlapDegrees(rect2)) > 0):
            
            for sectObj in curve1.intersection(curve2):
                if not isinstance(sectObj, point.Point):
                    foundOverlap = True
                    break
                
                t = curve1.parametricValueFromPoint(sectObj)
                u = curve2.parametricValueFromPoint(sectObj)
                
                if (
                  (t is not None) and
                  (u is not None) and
                  not ((CE(t, 0) or CE(t, 1)) and (CE(u, 0) or CE(u, 1)))):
                    
                    foundOverlap = True
                    break
        
        if foundOverlap:
            break
    
    if foundOverlap:
        logger.warning((
          'E1111',
          (),
          "Contour intersects itself."))
    
    # check for co-linear off-curve points
    
    for curve in allSplines:
        if curve.offCurve is not None:
            L = line.Line(curve.onCurve1, curve.onCurve2)
            t = L.parametricValueFromPoint(curve.offCurve)
            
            if t is not None:
                logger.warning((
                  'V0309',
                  (curve.offCurve,),
                  "The off-curve point %s is co-linear with its two "
                  "adjacent on-curve points."))
    
    return r
Пример #15
0
    def parametricValueFromPoint(self, p):
        """
        Given a Point p, find the parametric value t which corresponds to the
        point along the curve. Returns None if the point does not lie on the
        curve.
        
        >>> P = point.Point
        >>> b = BSpline(P(2, -3), P(4, 4), P(0, 1))
        >>> print(b.parametricValueFromPoint(b.pointFromParametricValue(0)))
        0
        >>> print(b.parametricValueFromPoint(b.pointFromParametricValue(1)))
        1
        >>> print(b.parametricValueFromPoint(b.pointFromParametricValue(0.5)))
        0.5
        
        >>> m = BSpline(P(2, -3), P(4, 4), P(0, 1))
        >>> print(b.parametricValueFromPoint(b.pointFromParametricValue(0)))
        0
        
        >>> b = BSpline(P(5, 5), P(10, 10), None)
        >>> print(b.parametricValueFromPoint(P(0, 0)))
        -1.0
        """

        if self.offCurve is None:
            theLine = line.Line(self.onCurve1, self.onCurve2)
            return theLine.parametricValueFromPoint(p)

        a = self.onCurve1.x + self.onCurve2.x - 2 * self.offCurve.x
        b = 2 * (self.offCurve.x - self.onCurve1.x)
        c = self.onCurve1.x - p.x

        if a:
            tx1, tx2 = mathutilities.quadratic(a, b, c)
        elif b:
            tx1 = tx2 = -c / b
        else:
            """           
            >>> d = BSpline(P(1, 5), P(1, 10), P(1,-5))
            >>> print(d.parametricValueFromPoint(P(0, 0)))
            None
            >>> e = BSpline(P(-9, 5), P(1, 10), P(-1,-5))
            >>> print(e.parametricValueFromPoint(P(0, 0)))
            None
            """
            return (None if c else 0)

        if tx1.imag:
            return None

        a = self.onCurve1.y + self.onCurve2.y - 2 * self.offCurve.y
        b = 2 * (self.offCurve.y - self.onCurve1.y)
        c = self.onCurve1.y - p.y

        if a:
            ty1, ty2 = mathutilities.quadratic(a, b, c)
        elif b:
            ty1 = ty2 = -c / b
        else:
            """
            >>> c = BSpline(P(5, 1), P(10, 1), P(-5,1))
            >>> print(c.parametricValueFromPoint(P(0, 0)))
            None
            """
            return (None if c else 0)

        CE = mathutilities.closeEnough

        if CE(tx1, ty1) or CE(tx1, ty2):
            return tx1

        if CE(tx2, ty1) or CE(tx2, ty2):
            return tx2

        return None
Пример #16
0
    def intersection(self, other):
        """
        Returns a tuple of objects representing the intersection of the two
        BSplines.
        
        >>> P = point.Point
        >>> b1 = BSpline(P(2, -3), P(4, 4), None)
        >>> b2 = BSpline(P(1, 0), P(4, -1), None)
        >>> for obj in b1.intersection(b2): print(obj)
        (2.6956521739130435, -0.5652173913043479)
        >>> for obj in b1.intersection(b2.moved(10)): print(obj)
        
        >>> b3 = BSpline(P(2, -3), P(4, 4), P(0, 1))
        >>> for obj in b3.intersection(b2): print(obj)
        (1.345578690319357, -0.11519289677311899)
        
        >>> for obj in b2.intersection(b3): print(obj)
        (1.345578690319357, -0.11519289677311899)
        
        >>> for obj in b3.intersection(b2.moved(10)): print(obj)
        
        >>> for obj in b3.intersection(b1.moved(-1)): print(obj)
        (2.6484772346100725, 2.7696703211352536)
        (1.4424318562990182, -1.4514885029534366)
        
        >>> b4 = BSpline(P(-2, 0), P(2, 0), P(0, 2))
        >>> for obj in b4.intersection(b3): print(obj)
        (1.4334792522721553, 0.4862843083263152)
        
        >>> b5 = BSpline(P(0, 3), P(0, -3), P(3, 0))
        >>> for obj in b5.intersection(b3): print(obj)
        (1.448865608003318, 0.553901030852897)
        (1.3583316809062103, -0.92195982264009)
        
        >>> b6 = BSpline(P(2, -3), P(2, -3), None)
        >>> b7 = BSpline(P(1, 0), P(4, -1), None)
        >>> b8 = BSpline(P(0, 0), P(10, 10), P(5,5))
        >>> b9 = BSpline(P(2.1, 1), P(20, 20), P(5,5))
        >>> b6.intersection(b7)
        ()
        >>> b7.intersection(b6)
        ()
        >>> b8.intersection(b9)
        ()
        """

        if self.onCurve1.equalEnough(self.onCurve2):
            t = other.parametricValueFromPoint(self.onCurve1)
            return (() if t is None else (self.onCurve1, ))

        if other.onCurve1.equalEnough(other.onCurve2):
            t = self.parametricValueFromPoint(other.onCurve1)
            return (() if t is None else (other.onCurve1, ))

        self = self.normalized()
        other = other.normalized()
        CEXY = mathutilities.closeEnoughXY

        if self.offCurve is None and other.offCurve is None:
            # actually a line intersection case
            L1 = line.Line(self.onCurve1, self.onCurve2)
            L2 = line.Line(other.onCurve1, other.onCurve2)
            isect = L1.intersection(L2)
            return (() if isect is None else (isect, ))

        if self.offCurve is None or other.offCurve is None:

            # one is a line and one is a curve. check endpoints first.

            if (CEXY(self.onCurve1, other.onCurve1)
                    or CEXY(self.onCurve1, other.onCurve2)):

                return (self.onCurve1.__copy__(), )

            if (CEXY(self.onCurve2, other.onCurve1)
                    or CEXY(self.onCurve2, other.onCurve2)):

                return (self.onCurve2.__copy__(), )

            if self.offCurve is None:
                # self is line and other is curve; from 0-2 intersection points
                return other._intersection_line(
                    line.Line(self.onCurve1, self.onCurve2))

            # other is line and self is curve; from 0-2 intersection points
            return self._intersection_line(
                line.Line(other.onCurve1, other.onCurve2))

        if CEXY(self.offCurve, other.offCurve):
            # If the two off-curve points coincide, then we check to see if
            # other's start and end points lie on self. If they do, the curves
            # overlap.

            tStart = self.parametricValueFromPoint(other.onCurve1)

            if tStart is not None:
                tEnd = self.parametricValueFromPoint(other.onCurve2)

                if tEnd is not None:
                    return (self.piece(max(0, tStart), min(1, tEnd))[0],
                            )  # don't need the function

        if CEXY(self.onCurve1, other.onCurve1) or CEXY(self.onCurve1,
                                                       other.onCurve2):
            return (self.onCurve1.__copy__(), )

        if CEXY(self.onCurve2, other.onCurve1) or CEXY(self.onCurve2,
                                                       other.onCurve2):
            return (self.onCurve2.__copy__(), )

        # At this point we know it's a full curve/curve intersection, so the
        # possible results are 0-2 points

        ZE = mathutilities.zeroEnough
        A = self.onCurve1.x + self.onCurve2.x - (2 * self.offCurve.x)
        B = 2 * (self.offCurve.x - self.onCurve1.x)
        G = self.onCurve1.y + self.onCurve2.y - (2 * self.offCurve.y)
        H = 2 * (self.offCurve.y - self.onCurve1.y)

        assert not (ZE(A) and ZE(G))  # linear case already handled

        if ZE(A):
            return self._intersection_curve_xspecial(other)

        if ZE(G):
            return self._intersection_curve_yspecial(other)

        if ZE(B * G - A * H):
            return other.intersection(self)

        return self._intersection_curve(other)