Esempio n. 1
0
    def process_cubic(self, offset_path, blade_path, params, quality):
        """ Add offset correction to a cubic bezier.
        """
        r = self.config.offset
        p0 = blade_path.currentPosition()
        p1, p2, p3 = params
        self.add_continuity_correction(offset_path, blade_path, p1)

        curve = QPainterPath()
        curve.moveTo(p0)
        curve.cubicTo(*params)
        p = QPainterPath()
        p.moveTo(p0)

        if quality == 1:
            polygon = curve.toSubpathPolygons(IDENITY_MATRIX)[0]
        else:
            m = QTransform.fromScale(quality, quality)
            m_inv = QTransform.fromScale(1/quality, 1/quality)
            polygon = m_inv.map(curve.toSubpathPolygons(m)[0])

        for point in polygon:
            p.lineTo(point)
            t = curve.percentAtLength(p.length())
            angle = curve.angleAtPercent(t)
            a = radians(angle)
            dx, dy = r*cos(a), -r*sin(a)
            offset_path.lineTo(point.x()+dx, point.y()+dy)

        blade_path.cubicTo(*params)
Esempio n. 2
0
    def add_continuity_correction(self, offset_path, blade_path, point):
        """ Adds if the upcoming angle and previous angle are not the same
        we need to correct for that difference by "arcing back" about the
        current blade point with a radius equal to the offset.

        """
        # Current blade position
        cur = blade_path.currentPosition()

        # Determine direction of next move
        sp = QPainterPath()
        sp.moveTo(cur)
        sp.lineTo(point)
        next_angle = sp.angleAtPercent(1)

        # Direction of last move
        angle = blade_path.angleAtPercent(1)

        # If not continuous it needs corrected with an arc
        if isnan(angle) or isnan(next_angle):
            return
        if abs(angle - next_angle) > self.config.cutoff:
            r = self.config.offset
            a = radians(next_angle)
            dx, dy = r*cos(a), -r*sin(a)
            po = QPointF(cur.x()+dx, cur.y()+dy)

            c = offset_path.currentPosition()
            dx, dy = po.x()-cur.x()+c.x()-cur.x(), po.y()-cur.y()+c.y()-cur.y()
            c1 = QPointF(cur.x()+dx, cur.y()+dy)
            offset_path.quadTo(c1, po)
Esempio n. 3
0
    def move_path(self):
        """ Returns the path the head moves when not cutting

        """
        # Compute the negative
        path = QPainterPath()
        for i in range(self.model.elementCount()):
            e = self.model.elementAt(i)
            if e.isMoveTo():
                path.lineTo(e.x, e.y)
            else:
                path.moveTo(e.x, e.y)
        return path
Esempio n. 4
0
def split_painter_path(path):
    """ Split a QPainterPath into subpaths. """
    if not isinstance(path, QPainterPath):
        raise TypeError("path must be a QPainterPath, got: {}".format(path))

    # Element types
    MoveToElement = QPainterPath.MoveToElement
    LineToElement = QPainterPath.LineToElement
    CurveToElement = QPainterPath.CurveToElement
    CurveToDataElement = QPainterPath.CurveToDataElement

    subpaths = []
    params = []
    e = None

    def finish_curve(p, params):
        if len(params) == 2:
            p.quadTo(*params)
        elif len(params) == 3:
            p.cubicTo(*params)
        else:
            raise ValueError("Invalid curve parameters: {}".format(params))

    for i in range(path.elementCount()):
        e = path.elementAt(i)

        # Finish the previous curve (if there was one)
        if params and e.type != CurveToDataElement:
            finish_curve(p, params)
            params = []

        # Reconstruct the path
        if e.type == MoveToElement:
            p = QPainterPath()
            p.moveTo(e.x, e.y)
            subpaths.append(p)
        elif e.type == LineToElement:
            p.lineTo(e.x, e.y)
        elif e.type == CurveToElement:
            params = [QPointF(e.x, e.y)]
        elif e.type == CurveToDataElement:
            params.append(QPointF(e.x, e.y))

    # Finish the previous curve (if there was one)
    if params and e and e.type != CurveToDataElement:
        finish_curve(p, params)
    return subpaths
Esempio n. 5
0
def split_painter_path(path):
    """ Split a QPainterPath into subpaths. """
    if not isinstance(path, QPainterPath):
        raise TypeError("path must be a QPainterPath, got: {}".format(path))

    # Element types
    MoveToElement = QPainterPath.MoveToElement
    LineToElement = QPainterPath.LineToElement
    CurveToElement = QPainterPath.CurveToElement
    CurveToDataElement = QPainterPath.CurveToDataElement

    subpaths = []
    params = []
    e = None

    def finish_curve(p, params):
        if len(params) == 2:
            p.quadTo(*params)
        elif len(params) == 3:
            p.cubicTo(*params)
        else:
            raise ValueError("Invalid curve parameters: {}".format(params))

    for i in range(path.elementCount()):
        e = path.elementAt(i)

        # Finish the previous curve (if there was one)
        if params and e.type != CurveToDataElement:
            finish_curve(p, params)
            params = []

        # Reconstruct the path 
        if e.type == MoveToElement:
            p = QPainterPath()
            p.moveTo(e.x, e.y)
            subpaths.append(p)
        elif e.type == LineToElement:
            p.lineTo(e.x, e.y)
        elif e.type == CurveToElement:
            params = [QPointF(e.x, e.y)]
        elif e.type == CurveToDataElement:
            params.append(QPointF(e.x, e.y))

    # Finish the previous curve (if there was one)
    if params and e and e.type != CurveToDataElement:
        finish_curve(p, params)
    return subpaths
Esempio n. 6
0
 def apply_blade_offset(self, poly, offset):
     """ Apply blade offset to the given polygon by appending a quadratic
     bezier to each point .
     
     """
     # Use a QPainterPath to track the distance in c++
     path = QPainterPath()
     cutoff = cos(radians(self.config.cutoff)) # Forget 
     last = None
     n = len(poly)
     for i, p in enumerate(poly):
         if i == 0:
             path.moveTo(p)
             last_path = QPainterPath()
             last_path.moveTo(p)
             last = p
             continue
         
         # Move to the point
         path.lineTo(p)
         
         if i+1 == n:
             # Done
             break
         
         # Get next point
         next = poly.at(i+1)
         
         # Make our paths
         last_path.lineTo(p)
         next_path = QPainterPath()
         next_path.moveTo(p)
         next_path.lineTo(next)
         
         # Get angle between the two components
         u, v = QVector2D(last-p), QVector2D(next-p)
         cos_theta = QVector2D.dotProduct(u.normalized(), v.normalized())
         
         # If the angle is large enough to need compensation
         if (cos_theta < cutoff and
                 last_path.length() > offset and
                 next_path.length() > offset):
             # Calculate the extended point
             t = last_path.percentAtLength(offset)
             c1 = p+(last_path.pointAtPercent(t)-last)
             c2 = p
             t = next_path.percentAtLength(offset)
             ep = next_path.pointAtPercent(t)
             if offset > 2:
                 # Can smooth it for larger offsets
                 path.cubicTo(c1, c2, ep)
             else:
                 # This works for small offsets < 0.5 mm 
                 path.lineTo(c1)
                 path.lineTo(ep)
         
         # Update last
         last_path = next_path
         last = p
     return path.toSubpathPolygons(IDENITY_MATRIX)
Esempio n. 7
0
    def create(self, swap_xy=False, scale=None):
        """ Create a path model that is rotated and scaled

        """
        model = QPainterPath()

        if not self.path:
            return

        path = self._create_copy()

        # Update size
        bbox = path.boundingRect()
        self.size = [bbox.width(), bbox.height()]

        # Create copies
        c = 0
        points = self._copy_positions_iter(path)

        if self.auto_copies:
            self.stack_size = self._compute_stack_sizes(path)
            if self.stack_size[0]:
                copies_left = self.copies % self.stack_size[0]
                if copies_left:  # not a full stack
                    with self.events_suppressed():
                        self.copies = self._desired_copies
                        self.add_stack()

        while c < self.copies:
            x, y = next(points)
            model.addPath(path * QTransform.fromTranslate(x, -y))
            c += 1

        # Create weedline
        if self.plot_weedline:
            self._add_weedline(model, self.plot_weedline_padding)

        # Determine padding
        bbox = model.boundingRect()
        if self.align_center[0]:
            px = (self.material.width() - bbox.width()) / 2.0
        else:
            px = self.material.padding_left

        if self.align_center[1]:
            py = -(self.material.height() - bbox.height()) / 2.0
        else:
            py = -self.material.padding_bottom

        # Scale and rotate
        if scale:
            model *= QTransform.fromScale(*scale)
            px, py = px * abs(scale[0]), py * abs(scale[1])

        if swap_xy:
            t = QTransform()
            t.rotate(90)
            model *= t

        # Move to 0,0
        bbox = model.boundingRect()
        p = bbox.bottomLeft()
        tx, ty = -p.x(), -p.y()

        # If swapped, make sure padding is still correct
        if swap_xy:
            px, py = -py, -px
        tx += px
        ty += py

        model = model * QTransform.fromTranslate(tx, ty)

        end_point = (QPointF(0, -self.feed_after + model.boundingRect().top())
                     if self.feed_to_end else QPointF(0, 0))
        model.moveTo(end_point)

        return model
Esempio n. 8
0
    def arc(self, x1, y1, rx, ry, phi, large_arc_flag, sweep_flag, x2o, y2o):

        # handle rotated arcs as normal arcs that are transformed as a rotation
        if phi != 0:
            x2 = x1 + (x2o - x1) * cos(radians(phi)) + (y2o - y1) * sin(
                radians(phi))
            y2 = y1 - (x2o - x1) * sin(radians(phi)) + (y2o - y1) * cos(
                radians(phi))
        else:
            x2, y2 = x2o, y2o

        # https://www.w3.org/TR/SVG/implnote.html F.6.6
        rx = abs(rx)
        ry = abs(ry)

        # https://www.w3.org/TR/SVG/implnote.html F.6.5
        x1prime = (x1 - x2) / 2
        y1prime = (y1 - y2) / 2

        # https://www.w3.org/TR/SVG/implnote.html F.6.6
        lamb = (x1prime * x1prime) / (rx * rx) + (y1prime * y1prime) / (ry *
                                                                        ry)
        if lamb >= 1:
            ry = sqrt(lamb) * ry
            rx = sqrt(lamb) * rx

        # Back to https://www.w3.org/TR/SVG/implnote.html F.6.5
        radicand = (rx * rx * ry * ry - rx * rx * y1prime * y1prime -
                    ry * ry * x1prime * x1prime)
        radicand /= (rx * rx * y1prime * y1prime + ry * ry * x1prime * x1prime)

        if radicand < 0:
            radicand = 0

        factor = (-1 if large_arc_flag == sweep_flag else 1) * sqrt(radicand)

        cxprime = factor * rx * y1prime / ry
        cyprime = -factor * ry * x1prime / rx

        cx = cxprime + (x1 + x2) / 2
        cy = cyprime + (y1 + y2) / 2

        start_theta = -atan2((y1 - cy) * rx, (x1 - cx) * ry)

        start_phi = -atan2((y1 - cy) / ry, (x1 - cx) / rx)
        end_phi = -atan2((y2 - cy) / ry, (x2 - cx) / rx)

        sweep_length = end_phi - start_phi

        if sweep_length < 0 and not sweep_flag:
            sweep_length += 2 * pi
        elif sweep_length > 0 and sweep_flag:
            sweep_length -= 2 * pi

        if phi != 0:
            rotarc = QPainterPath()
            rotarc.moveTo(x1, y1)
            rotarc.arcTo(cx - rx, cy - ry, rx * 2, ry * 2,
                         start_theta * 360 / 2 / pi,
                         sweep_length * 360 / 2 / pi)

            t = QTransform()
            t.translate(x1, y1)
            t.rotate(phi)
            t.translate(-x1, -y1)
            tmp = rotarc * t
            rotarc -= rotarc
            rotarc += tmp
            self.addPath(rotarc)

        else:
            self.arcTo(cx - rx, cy - ry, rx * 2, ry * 2,
                       start_theta * 360 / 2 / pi, sweep_length * 360 / 2 / pi)