예제 #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)
예제 #2
0
    def parseTransform(self, e):
        t = QTransform()
        # transforms don't apply to the root svg element, but we do need to
        # take into account the viewBox there
        if self.isParentSvg:
            viewBox = e.attrib.get('viewBox', None)
            if viewBox is not None:
                (x, y, innerWidth,
                 innerHeight) = map(self.parseUnit, re.split("[ ,]+", viewBox))

                if x != 0 or y != 0:
                    raise ValueError(
                        "viewBox '%s' needs to be translated "
                        "because is not at the origin. "
                        "See https://github.com/codelv/inkcut/issues/69" %
                        viewBox)

                outerWidth, outerHeight = map(self.parseUnit, (e.attrib.get(
                    'width', None), e.attrib.get('height', None)))
                if outerWidth is not None and outerHeight is not None:
                    t.scale(outerWidth / innerWidth, outerHeight / innerHeight)
        else:
            x, y = map(self.parseUnit,
                       (e.attrib.get('x', 0), e.attrib.get('y', 0)))
            t.translate(x, y)

        return t
예제 #3
0
파일: models.py 프로젝트: yangbiaocn/inkcut
    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
예제 #4
0
파일: models.py 프로젝트: yangbiaocn/inkcut
    def _create_copy(self):
        """ Creates a copy of the original graphic applying the given
        transforms

        """
        optimized_path = self.optimized_path
        bbox = optimized_path.boundingRect()

        # Create the base copy
        t = QTransform()

        t.scale(
            self.scale[0] * (self.mirror[0] and -1 or 1),
            self.scale[1] * (self.mirror[1] and -1 or 1),
        )

        # Rotate about center
        if self.rotation != 0:
            c = bbox.center()
            t.translate(-c.x(), -c.y())
            t.rotate(self.rotation)
            t.translate(c.x(), c.y())

        # Apply transform
        path = optimized_path * t

        # Add weedline to copy
        if self.copy_weedline:
            self._add_weedline(path, self.copy_weedline_padding)

        # If it's too big we have to scale it
        w, h = path.boundingRect().width(), path.boundingRect().height()
        available_area = self.material.available_area

        #: This screws stuff up!
        if w > available_area.width() or h > available_area.height():

            # If it's too big an auto scale is enabled, resize it to fit
            if self.auto_scale:
                sx, sy = 1, 1
                if w > available_area.width():
                    sx = available_area.width() / w
                if h > available_area.height():
                    sy = available_area.height() / h
                s = min(sx, sy)  # Fit to the smaller of the two
                path = optimized_path * QTransform.fromScale(s, s)

        # Move to bottom left
        p = path.boundingRect().bottomRight()

        path = path * QTransform.fromTranslate(-p.x(), -p.y())

        return path
예제 #5
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)
예제 #6
0
    def parseTransform(self, e):
        """ Based on simpletrasnform.py by from
        Jean-Francois Barraud, [email protected]

        """
        t = QTransform()

        if isinstance(e, EtreeElement):
            trans = e.attrib.get('transform', '').strip()
        else:
            trans = e  # e is a string of the previous transform

        if not trans:
            return t

        m = re.match(
            "(translate|scale|rotate|skewX|skewY|matrix)\s*\(([^)]*)\)\s*,?",
            trans)
        if m is None:
            return t

        name, args = m.group(1), m.group(2).replace(',', ' ').split()

        if name == "translate":
            # The translate(<x> [<y>]) transform function moves the object
            # by x and y. If y is not provided, it is assumed to be 0.
            dx = float(args[0])
            dy = float(args[1]) if len(args) == 2 else 0
            t.translate(dx, dy)

        elif name == "scale":
            # The scale(<x> [<y>]) transform function specifies a scale
            # operation by x and y. If y is not provided, it is assumed to
            # be equal to x.
            sx = float(args[0])
            sy = float(args[1]) if len(args) == 2 else sx
            t.scale(sx, sy)

        elif name == "rotate":
            # The rotate(<a> [<x> <y>]) transform function specifies a
            # rotation by a degrees about a given point. If optional
            # parameters x and y are not supplied, the rotation is about the
            # origin of the current user coordinate system. If optional
            # parameters x and y are supplied, the rotation is about the
            # point (x, y).
            if len(args) == 1:
                cx, cy = (0, 0)
            else:
                cx, cy = map(float, args[1:])

            t.translate(cx, cy)
            t.rotate(float(args[0]))
            t.translate(-cx, -cy)

        elif name == "skewX":
            # The skewX(<a>) transform function specifies a skew transformation
            # along the x axis by a degrees.
            t.shear(math.tan(float(args[0]) * math.pi / 180.0), 0)

        elif name == "skewY":
            # The skewY(<a>) transform function specifies a skew transformation
            # along the y axis by a degrees.
            t.shear(0, math.tan(float(args[0]) * math.pi / 180.0))

        elif name == "matrix":
            t = t * QTransform(*map(float, args))

        if m.end() < len(trans):
            t = self.parseTransform(trans[m.end():]) * t

        return t
예제 #7
0
from atom.api import Float, Enum, Instance
from enaml.qt.QtCore import QPointF
from enaml.qt.QtGui import QPainterPath, QTransform, QVector2D

from inkcut.device.plugin import DeviceFilter, Model
from inkcut.core.utils import unit_conversions, log


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


IDENITY_MATRIX = QTransform.fromScale(1, 1)


def fp(point):
    return "({}, {})".format(round(point.x(), 3), round(point.y(), 3))


class BladeOffsetConfig(Model):
    #: BladeOffset in user units
    offset = Float(strict=False).tag(config=True)

    #: Units for display
    offset_units = Enum(*unit_conversions.keys()).tag(config=True)

    #: If the angle is less than this, consider it continuous
    cutoff = Float(5.0, strict=False).tag(config=True)
예제 #8
0
Distributed under the terms of the GPL v3 License.

The full license is in the file LICENSE, distributed with this software.

Created on Dec 14, 2018

@author: jrm
"""
from math import sqrt, cos, radians, isinf
from atom.api import Float, Enum, Instance
from inkcut.device.plugin import DeviceFilter, Model
from inkcut.core.utils import unit_conversions
from enaml.qt.QtGui import QPainterPath, QTransform, QVector2D


IDENITY_MATRIX = QTransform.fromScale(1, 1)


class BladeOffsetConfig(Model):
    #: BladeOffset in user units
    offset = Float(strict=False).tag(config=True)
    
    #: Units for display 
    offset_units = Enum(*unit_conversions.keys()).tag(config=True)
    
    #: Cutoff angle
    cutoff = Float(20.0, strict=False).tag(config=True)
    
    def _default_offset_units(self):
        return 'mm'