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)
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
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
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)
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'