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)
def apply_blade_offset(self, path, job): """ Apply blade offset to the given path. """ params = [] e = None cmd = None qf = getattr(job.config, 'quality_factor', 1) # Holds the blade path blade_path = QPainterPath() offset_path = QPainterPath() for i in range(path.elementCount()): e = path.elementAt(i) # Finish the previous curve (if there was one) if cmd == CurveToElement and e.type != CurveToDataElement: n = len(params) if n == 2: self.process_quad(offset_path, blade_path, params, qf) elif n == 3: self.process_cubic(offset_path, blade_path, params, qf) else: raise ValueError("Unexpected curve data length %s" % n) params = [] # Reconstruct the path if e.type == MoveToElement: cmd = MoveToElement self.process_move(offset_path, blade_path, [QPointF(e.x, e.y)]) elif e.type == LineToElement: cmd = LineToElement self.process_line(offset_path, blade_path, [QPointF(e.x, e.y)]) elif e.type == CurveToElement: cmd = 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.type != CurveToDataElement: n = len(params) if n == 2: self.process_quad(offset_path, blade_path, params, qf) elif n == 3: self.process_cubic(offset_path, blade_path, params, qf) return offset_path
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
def splitAtPercent(self, t): paths = [] path = QPainterPath() i = 0 while i < self.elementCount(): e = self.elementAt(i) if e.type == ElementType.MoveToElement: if not path.isEmpty(): paths.append(path) path = QPainterPath(QPointF(e.x, e.y)) elif e.type == ElementType.LineToElement: path.lineTo(QPointF(e.x, e.y)) elif e.type == ElementType.CurveToElement: e1, e2 = self.elementAt(i + 1), self.elementAt(i + 2) path.cubicTo(QPointF(e.x, e.y), QPointF(e1.x, e1.y), QPointF(e2.x, e2.y)) i += 2 else: raise ValueError("Invalid element type %s" % (e.type, )) i += 1 if not path.isEmpty(): paths.append(path) return paths
def process_line(self, offset_path, blade_path, params): """ Correct continuity and adjust the end point of the line to end at the correct spot. """ r = self.config.offset p0 = params[0] self.add_continuity_correction(offset_path, blade_path, p0) blade_path.lineTo(p0) # Must be done after continuity correction! angle = blade_path.angleAtPercent(1) if isnan(angle): dx, dy = 0, r else: a = radians(angle) dx, dy = r*cos(a), -r*sin(a) po = QPointF(p0.x()+dx, p0.y()+dy) offset_path.lineTo(po)
def process_move(self, offset_path, blade_path, params): """ Adjust the start point of a move by the blade offset. When just starting we don't know the blade angle so no correction is done. """ r = self.config.offset p0 = params[0] # Get direction of last move blade_path.moveTo(p0) angle = blade_path.angleAtPercent(1) if isnan(angle): dx, dy = 0, r else: a = radians(angle) dx, dy = r*cos(a), -r*sin(a) po = QPointF(p0.x()+dx, p0.y()+dy) offset_path.moveTo(po)
def get_items_in(self, top_left, bottom_right): qrect = QRectF(QPointF(top_left.x, top_left.y), QPointF(bottom_right.x, bottom_right.y)) items = self.scene.items(qrect) return [item.ref().declaration for item in items if item.ref()]
def set_points(self, points): polygon = QPolygonF([QPointF(*p[:2]) for p in points]) self.widget.setPolygon(polygon)
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
def parse(self, e): c = QPointF(*map(self.parseUnit, (e.attrib.get('cx', 0), e.attrib.get('cy', 0)))) r = self.parseUnit(e.attrib.get('r', 0)) self.addEllipse(c, r, r)
def parse(self, e): c = QPointF(*map(self.parseUnit, (e.attrib.get('cx', 0), e.attrib.get('cy', 0)))) rx, ry = map(self.parseUnit, (e.attrib.get('rx', 0), e.attrib.get('ry', 0))) self.addEllipse(c, rx, ry)
def request_relayout(self): # y = 0.0 # for child in self.children(): # if not isinstance(child, QtContainer): # continue # scene_proxy = self._proxies[child] # width, height = child._layout_manager.best_size() # scene_proxy.setPos(0.0, y) # y += height + 25.0 for p in self._edge_paths: self.scene.removeItem(p) self._edge_paths = [] g = pygraphviz.AGraph(directed=True) g.graph_attr['nodesep'] = 100 g.graph_attr['ranksep'] = 50 g.node_attr['shape'] = 'rect' children_names = {child.declaration.name for child in self.children() if isinstance(child, QtContainer)} if any(from_ not in children_names or to not in children_names for (from_, to) in self.declaration.edges): # hasn't finished being set up yet return for child in self.children(): if not isinstance(child, QtContainer): continue scene_proxy = self._proxy(child) width, height = child._layout_manager.best_size() scene_proxy.setGeometry(QRectF(0.0, 0.0, width, height)) g.add_node(child.declaration.name, width=width, height=height) for from_, to in self.declaration.edges: g.add_edge(from_, to) before = time.time() g.layout(prog='dot') after = time.time() for child in self.children(): if not isinstance(child, QtContainer): continue scene_proxy = self._proxies[child] node = g.get_node(child.declaration.name) center_x, center_y = (-float(v)/72.0 for v in node.attr['pos'].split(',')) width, height = child._layout_manager.best_size() x = center_x - (width / 2.0) y = center_y - (height / 2.0) scene_proxy.setPos(x, y) for from_, to in self.declaration.edges: if from_ not in children_names or to not in children_names: continue edge = g.get_edge(from_, to) # TODO: look at below code all_points = [tuple(-float(v)/72.0 for v in t.strip('e,').split(',')) for t in edge.attr['pos'].split(' ')] arrow = all_points[0] start_point = all_points[1] painter = QPainterPath(QPointF(*start_point)) for c1, c2, end in grouper(all_points[2:], 3): painter.cubicTo(QPointF(*c1), QPointF(*c2), QPointF(*end)) self._edge_paths.append(self.scene.addPath(painter)) self.show_selected()