def run(self): """ Run the model synchronously. Shows a modal dialog until the run completes. Returns the dialog. """ runner = self.runner gui_handler = lambda *args: deferred_call(self.handle_error, *args) runner.error_handler = gui_handler dialog = RunDialog(parent=self.parent, title='Running model...', text='Please wait while the model runs.') dialog.observe('rejected', lambda change: runner.cancel()) dialog.show() event_loop = QtCore.QEventLoop() timer = QtCore.QTimer() def start_run(): runner.run() timer.setInterval(50) timer.timeout.connect(check_loop) timer.start() def check_loop(): dialog.output = runner.output() if not runner.running: timer.stop() event_loop.quit() timed_call(50, start_run) event_loop.exec_() dialog.accept() return dialog
def __init__(self, proxy, parent, flags=QtCore.Qt.Widget): """ Initialize a QWindowDialog. Parameters ---------- parent : QWidget, optional The parent of the dialog. """ super(QWindowDialog, self).__init__(parent, flags) # PySide2 segfaults self._proxy_ref = None if QT_API in 'pyside2' else atomref(proxy) self._expl_min_size = QtCore.QSize() self._expl_max_size = QtCore.QSize() layout = QWindowLayout() layout.setSizeConstraint(QtWidgets.QLayout.SetMinAndMaxSize) self.setLayout(layout)
def delete_widget(self, widget, timeout=1.0): """Runs the real Qt event loop until the widget provided has been deleted. Raises ConditionTimeoutError on timeout. Parameters ---------- widget : QObject The widget whose deletion will stop the event loop. timeout : float Number of seconds to run the event loop in the case that the widget is not deleted. """ timer = QtCore.QTimer() timer.setSingleShot(True) timer.setInterval(timeout * 1000) timer.timeout.connect(self.qt_app.quit) widget.destroyed.connect(self.qt_app.quit) yield timer.start() self.qt_app.exec_() if not timer.isActive(): # We exited the event loop on timeout raise ConditionTimeoutError( 'Could not destroy widget before timeout: {!r}'.format(widget))
def __init__(self, parent, delimiters, entries, entries_updater): super(QDelimitedCompleter, self).__init__(parent) self.delimiters = delimiters if isinstance(parent, QtWidgets.QLineEdit): self.text_getter = parent.text self.cursor_pos = parent.cursorPosition self.insert_text = parent.insert parent.textChanged[str].connect(self.text_changed) self.completionNeeded.connect(self.complete) elif isinstance(parent, QtWidgets.QTextEdit): parent.textChanged.connect(self.text_changed) self.cursor_pos = lambda: parent.textCursor().position() self.insert_text =\ lambda text: parent.textCursor().insertText(text) self.text_getter = parent.toPlainText self.completionNeeded.connect(self._text_edit_complete) else: msg = 'Parent of QtCompleter must QLineEdit or QTextEdit, not {}' raise ValueError(msg.format(parent)) self.setCaseSensitivity(QtCore.Qt.CaseSensitive) self.setModel(QtCore.QStringListModel(entries, self)) self.activated[str].connect(self.complete_text) self.setWidget(parent) self._upddate_entries = True self._popup_active = False self.entries_updater = entries_updater
def on_paint(self, painter, style_option, widget=None): lod = style_option.levelOfDetailFromTransform(painter.worldTransform()) # painting circle painter.setBrush(self.color_background) painter.setPen(self.pen_outline) painter.drawEllipse(-self.radius, -self.radius, 2 * self.radius, 2 * self.radius) if self.show_label: painter.setFont(self.font_label) painter.setPen(self.pen_label) is_left = self.socket_position in (SocketPosition.LEFT_BOTTOM, SocketPosition.LEFT_TOP) alignment = QtCore.Qt.AlignVCenter if is_left: alignment |= QtCore.Qt.AlignLeft else: alignment |= QtCore.Qt.AlignRight node = self.parent() width = node.width / 2 - self.radius - self.outline_width height = self.radius * 2 offset = 2 * self.radius + self.outline_width x = offset if is_left else -width - offset y = -self.radius rect = QtCore.QRectF(x, y, width, height) painter.drawText(rect, alignment, self.name)
def boundingRect(self): p = self.proxy return QtCore.QRectF( -p.radius - p.outline_width, -p.radius - p.outline_width, 2 * (p.radius + p.outline_width), 2 * (p.radius + p.outline_width), )
def activate_proxy(self): super(ToolBar, self).activate_proxy() widget = self.proxy.widget widget.setWindowTitle(self.title) # XXX: Work around Qt bug on Retina displays. To prevent toolbars being # twice the correct size, set the toolbar size manually. if sys.platform == 'darwin': widget.setIconSize(QtCore.QSize(32, 32))
def calcPath(self): edge_type = self.proxy.edge_type if edge_type == EdgeType.EDGE_TYPE_DIRECT: path = QtGui.QPainterPath( QtCore.QPointF(self.proxy.pos_source.x, self.proxy.pos_source.y)) path.lineTo(self.proxy.pos_destination.x, self.proxy.pos_destination.y) return path elif edge_type == EdgeType.EDGE_TYPE_BEZIER: s = self.proxy.pos_source d = self.proxy.pos_destination dist = (d.x - s.x) * 0.5 cpx_s = +dist cpx_d = -dist cpy_s = 0 cpy_d = 0 if self.proxy.start_socket is not None: sspos = self.proxy.start_socket.socket_position if (s.x > d.x and sspos in (SocketPosition.RIGHT_TOP, SocketPosition.RIGHT_BOTTOM)) \ or (s.x < d.x and sspos in (SocketPosition.LEFT_BOTTOM, SocketPosition.LEFT_TOP)): cpx_d *= -1 cpx_s *= -1 cpy_d = ((s.y - d.y) / math.fabs( (s.y - d.y) if (s.y - d.y) != 0 else 0.00001) ) * self.proxy.edge_roundness cpy_s = ((d.y - s.y) / math.fabs( (d.y - s.y) if (d.y - s.y) != 0 else 0.00001) ) * self.proxy.edge_roundness path = QtGui.QPainterPath( QtCore.QPointF(self.proxy.pos_source.x, self.proxy.pos_source.y)) path.cubicTo(s.x + cpx_s, s.y + cpy_s, d.x + cpx_d, d.y + cpy_d, self.proxy.pos_destination.x, self.proxy.pos_destination.y) return path
def init_signal(self): """allow clean shutdown on sigint""" signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2)) # need a timer, so that QApplication doesn't block until a real # Qt event fires (can require mouse movement) # timer trick from http://stackoverflow.com/q/4938723/938949 timer = QtCore.QTimer() # Let the interpreter run each 200 ms: timer.timeout.connect(lambda: None) timer.start(200) # hold onto ref, so the timer doesn't get cleaned up self._sigint_timer = timer
def __init__(self, proxy, parent=None): if QtOGLWidget._ShareWidget is None: QtOGLWidget._ShareWidget = QtOpenGL.QGLWidget() super(QtOGLWidget, self).__init__(parent, QtOGLWidget._ShareWidget) self.proxy = proxy self.size = QtCore.QSize(400, 300) # eventually handle repeating keys with timer as in pyqtgraph GLViewWidget self.setFocusPolicy(QtCore.Qt.ClickFocus) self.makeCurrent()
def event_loop_until_condition(self, condition, timeout=10.0): """Runs the real Qt event loop until the provided condition evaluates to True. Raises ConditionTimeoutError if the timeout occurs before the condition is satisfied. Parameters ---------- condition : callable A callable to determine if the stop criteria have been met. This should accept no arguments. timeout : float Number of seconds to run the event loop in the case that the trait change does not occur. """ def handler(): if condition(): self.qt_app.quit() condition_timer = QtCore.QTimer() condition_timer.setInterval(50) condition_timer.timeout.connect(handler) timeout_timer = QtCore.QTimer() timeout_timer.setSingleShot(True) timeout_timer.setInterval(timeout * 1000) timeout_timer.timeout.connect(self.qt_app.quit) timeout_timer.start() condition_timer.start() try: self.qt_app.exec_() if not timeout_timer.isActive(): # We exited the event loop on timeout raise ConditionTimeoutError('Timed out waiting for condition') finally: timeout_timer.stop() condition_timer.stop()
def paintEvent(self, event): if self._inited: self._display.Context.UpdateCurrentViewer() # important to allow overpainting of the OCC OpenGL context in Qt self.swapBuffers() if self._drawbox: self.makeCurrent() painter = QtGui.QPainter(self) painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0), 1)) rect = QtCore.QRect(*self._drawbox) painter.drawRect(rect) painter.end() self.doneCurrent()
def on_draw_background(self, painter, rect): if not self.show_background: return # here we create our grid left = int(math.floor(rect.left())) right = int(math.ceil(rect.right())) top = int(math.floor(rect.top())) bottom = int(math.ceil(rect.bottom())) first_left = left - (left % self.background_grid_size) first_top = top - (top % self.background_grid_size) # compute all lines to be drawn lines_light, lines_dark = [], [] for x in range(first_left, right, self.background_grid_size): if x % (self.background_grid_size * self.background_grid_squares) != 0: lines_light.append(QtCore.QLine(x, top, x, bottom)) else: lines_dark.append(QtCore.QLine(x, top, x, bottom)) for y in range(first_top, bottom, self.background_grid_size): if y % (self.background_grid_size * self.background_grid_squares) != 0: lines_light.append(QtCore.QLine(left, y, right, y)) else: lines_dark.append(QtCore.QLine(left, y, right, y)) # draw the lines if lines_light: painter.setPen(self.pen_light) painter.drawLines(*lines_light) if lines_dark: painter.setPen(self.pen_dark) painter.drawLines(*lines_dark)
def splitAtPercent(self, t): paths = [] path = QtGui.QPainterPath() i = 0 while i < self.elementCount(): e = self.elementAt(i) if e.type == ElementType.MoveToElement: if not path.isEmpty(): paths.append(path) path = QtGui.QPainterPath(QtCore.QPointF(e.x, e.y)) elif e.type == ElementType.LineToElement: path.lineTo(QtCore.QPointF(e.x, e.y)) elif e.type == ElementType.CurveToElement: e1, e2 = self.elementAt(i + 1), self.elementAt(i + 2) path.cubicTo(QtCore.QPointF(e.x, e.y), QtCore.QPointF(e1.x, e1.y), QtCore.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
class PushSinkAdapter(QtCore.QObject): dataReady = QtCore.Signal(['PyQt_PyObject'], name='dataReady') def __init__(self, sink): super(PushSinkAdapter, self).__init__() self.sink = sink self._time = None self._value = None def cb_handler(self, m): try: self._time = m.time() self._value = m self.dataReady.emit(self._time) except Exception, e: log.exception(e)
def set_language(self, language): try: language = getattr(QtCore.QLocale, language, 'system') if isinstance(language, int): locale = QtCore.QLocale(language).name() elif callable(language): locale = language().name() else: locale = QtCore.QLocale.system().name() root_dir = os.path.dirname(os.path.dirname(__file__)) path = os.path.join(root_dir, "res", "translations", locale) if self.translator.load(path): log.warning("Setting locale: %s" % locale) self.application._qapp.installTranslator(self.translator) else: log.warning("Translations not found at %s" % path) except Exception as e: log.exception(e)
def __init__(self, key_event_handler=None, mouse_event_handler=None, cam_width=1024, cam_height=768, cam_near=0.01, cam_far=10.0, camera_intrinsics=None, parent=None): if QtVirtualCameraWidget.ShareWidget is None: ## create a dummy widget to allow sharing objects (textures, shaders, etc) between views QtVirtualCameraWidget.ShareWidget = QtOpenGL.QGLWidget() QtOpenGL.QGLWidget.__init__(self, parent, QtVirtualCameraWidget.ShareWidget) self.setFocusPolicy(QtCore.Qt.ClickFocus) self.bgtexture = visualization.BackgroundImage() self.camera_intrinsics = camera_intrinsics self.camera_pose = None self.camera_width = float(cam_width) self.camera_height = float(cam_height) self.camera_near = float(cam_near) self.camera_far = float(cam_far) self.screen_width = cam_width self.screen_height = cam_height self.items = [] self.key_event_handler = key_event_handler self.mouse_event_handler = mouse_event_handler self.noRepeatKeys = [ QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown ] self.keysPressed = {} self.keyTimer = QtCore.QTimer() self.keyTimer.timeout.connect(self.evalKeyState) self.makeCurrent()
def startDrag(self, actions): """ Reimplemented to start the drag of a tree widget item. """ nid = self.currentItem() if nid is None: return self._dragging = nid _, node, obj = self._controller._get_node_data(nid) # Convert the item being dragged to MIME data. drag_object = node.get_drag_object(obj) md = PyMimeData.coerce(drag_object) # Render the item being dragged as a pixmap. nid_rect = self.visualItemRect(nid) rect = nid_rect.intersected(self.viewport().rect()) pm = QtGui.QPixmap(rect.size()) pm.fill(self.palette().base().color()) painter = QtGui.QPainter(pm) option = self.viewOptions() option.state |= QtWidgets.QStyle.State_Selected option.rect = QtCore.QRect(nid_rect.topLeft() - rect.topLeft(), nid_rect.size()) self.itemDelegate().paint(painter, option, self.indexFromItem(nid)) painter.end() # Calculate the hotspot so that the pixmap appears on top of the # original item. hspos = self.viewport().mapFromGlobal(QtGui.QCursor.pos()) - \ nid_rect.topLeft() # Start the drag. drag = QtGui.QDrag(self) drag.setMimeData(md) drag.setPixmap(pm) drag.setHotSpot(hspos) drag.exec_(actions)
class OutPort(Port): dataReady = QtCore.Signal(['PyQt_PyObject'], name='dataReady') def __init__(self, parent, name): super(Port, self).__init__(parent, name) def send(self, value): self.dataReady.emit(value) def subscribe(self, inport): info = inport.get_info() if info.queued: return self.dataReady.connect(inport.handle_receive, QtCore.Qt.QueuedConnection) else: return self.dataReady.connect(inport.handle_receive) def unsubscribe(self, inport): raise NotImplemented
def __init__(self, e, ids=None): """ Creates a QtPainterPath from an SVG document applying all transforms. Does NOT include any styling. Parameters ---------- e: Element or string An lxml etree.Element or an argument to pass to etree.parse() ids: List List of node ids to include. If not given all will be used. """ self.isParentSvg = not isinstance(e, EtreeElement) self.ids = ids or [] if self.isParentSvg: self._doc = etree.parse(e) self._svg = self._doc.getroot() self.viewBox = QtCore.QRectF(0, 0, -1, -1) super(QtSvgDoc, self).__init__(self._svg)
def text_changed(self, text=None): """Callback handling the text being edited on the parent. """ if not text: text = self.text_getter() if self._upddate_entries and self.entries_updater: entries = self.entries_updater() self.setModel(QtCore.QStringListModel(entries, self)) self._upddate_entries = False all_text = uni(text) text = all_text[:self.cursor_pos()] split = text.split(self.delimiters[0]) prefix = split[-1].strip() if len(split) > 1: self.setCompletionPrefix(prefix) self.completionNeeded.emit() elif self.popup().isVisible(): self.popup().hide()
def run(self): """ Run the UI workbench application. This method will load the core and ui plugins and start the main application event loop. This is a blocking call which will return when the application event loop exits. """ InkcutWorkbench._instance = self with enaml.imports(): from enaml.workbench.core.core_manifest import CoreManifest from enaml.workbench.ui.ui_manifest import UIManifest from inkcut.core.manifest import InkcutManifest #from inkcut.settings.manifest import SettingsManifest self.register(CoreManifest()) self.register(UIManifest()) self.register(InkcutManifest()) #self.register(SettingsManifest()) #: Init the ui ui = self.get_plugin('enaml.workbench.ui') ui.show_window() # Make sure ^C keeps working signal.signal(signal.SIGINT, signal.SIG_DFL) #: Start the core plugin plugin = self.get_plugin('inkcut.core') locale = QtCore.QLocale.system().name() qtTranslator = QtCore.QTranslator() if qtTranslator.load("inkcut/res/translations/" + locale): self.application._qapp.installTranslator(qtTranslator) ui.start_application()
def __init__(self, e, ids=None): """ Creates a QtPainterPath from an SVG document applying all transforms. Does NOT include any styling. Parameters ---------- e: Element or string An lxml etree.Element or an argument to pass to etree.parse() ids: List List of node ids to include. If not given all will be used. """ self.isParentSvg = not isinstance(e, EtreeElement) if self.isParentSvg: self._doc = etree.parse(e) self._svg = self._doc.getroot() if ids: nodes = set() xpath = self._svg.xpath for node_id in ids: nodes.update(set(xpath('//*[@id="%s"]' % node_id))) # Find all nodes and their parents valid_nodes = set() for node in nodes: valid_nodes.add(node) parent = node.getparent() while parent: valid_nodes.add(parent) parent = parent.getparent() self._nodes = valid_nodes self.viewBox = QtCore.QRectF(0, 0, -1, -1) super(QtSvgDoc, self).__init__(self._svg, self._nodes)
def _job_changed(self, change): """ Recreate an instance of of the plot using the current settings """ if self._blocked: return if change['name'] == 'copies': self._desired_copies = self.copies #try: model = QtGui.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 * QtGui.QTransform.fromTranslate(x, -y)) c += 1 # Create weedline if self.plot_weedline: self._add_weedline(model, self.plot_weedline_padding) # Move to 0,0 bbox = model.boundingRect() p = bbox.bottomLeft() tx, ty = -p.x(), -p.y() # Center or set to padding tx += ((self.material.width() - bbox.width()) / 2.0 if self.align_center[0] else self.material.padding_left) ty += (-(self.material.height() - bbox.height()) / 2.0 if self.align_center[1] else -self.material.padding_bottom) t = QtGui.QTransform.fromTranslate(tx, ty) model = model * t end_point = (QtCore.QPointF( 0, -self.feed_after + model.boundingRect().top()) if self.feed_to_end else QtCore.QPointF(0, 0)) model.moveTo(end_point) # Set new model self.model = model #.simplified()
class CompleterLineEdit(QtGui.QLineEdit): """ """ completionNeeded = QtCore.Signal(str) def __init__(self, parent, delimiters, entries, entries_updater): self.delimiters = delimiters super(CompleterLineEdit, self).__init__(parent) self.textChanged[str].connect(self.text_changed) self.completer = QtGui.QCompleter(self) self.completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) self.completer.setModel(QtGui.QStringListModel(entries, self.completer)) self.completionNeeded.connect(self.completer.complete) self.completer.activated[str].connect(self.complete_text) self.completer.setWidget(self) self._upddate_entries = True self.editingFinished.connect(self.on_editing_finished) self.entries_updater = entries_updater def text_changed(self, text): """ """ if self._upddate_entries and self.entries_updater: entries = self.entries_updater() self.completer.setModel( QtGui.QStringListModel(entries, self.completer)) self._upddate_entries = False all_text = unicode(text) text = all_text[:self.cursorPosition()] split = text.split(self.delimiters[0]) prefix = split[-1].strip() if len(split) > 1: self.completer.setCompletionPrefix(prefix) self.completionNeeded.emit(prefix) self.string = text def complete_text(self, text): """ """ cursor_pos = self.cursorPosition() before_text = unicode(self.text())[:cursor_pos] after_text = unicode(self.text())[cursor_pos:] prefix_len = len(before_text.split(self.delimiters[0])[-1].strip()) if after_text.startswith(self.delimiters[1]): self.setText(before_text[:cursor_pos - prefix_len] + text + after_text) else: self.setText(before_text[:cursor_pos - prefix_len] + text + self.delimiters[1] + after_text) self.string = before_text[:cursor_pos - prefix_len] + text +\ self.delimiters[1] + after_text self.setCursorPosition(cursor_pos - prefix_len + len(text) + 2) self.textEdited.emit(self.string) def on_editing_finished(self): self._upddate_entries = True def _update_entries(self, entries): self.completer.setModel(QtGui.QStringListModel(entries, self.completer))
def qInitResources(): QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
def qCleanupResources(): QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
def parse(self, e): c = QtCore.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 _default_area(self): return QtCore.QRectF(0, 0, self.size[0], self.size[1])
def available_area(self): x, y = self.padding_left, self.padding_bottom w, h = (self.size[0] - (self.padding_right + self.padding_left), self.size[1] - (self.padding_bottom + self.padding_top)) return QtCore.QRectF(x, y, w, h)
def process(self, model): """ Process the path model of a job and return each command within the job. Parameters ---------- model: QPainterPath The path to process Returns ------- generator: A list or generator object that yields each command to invoke on the device and the distance moved. In the format (distance, cmd, args, kwargs) """ config = self.config #: Previous point _p = QtCore.QPointF(self.origin[0], self.origin[1]) #: Do a final translation since Qt's y axis is reversed t = QtGui.QTransform.fromScale(1, -1) model = model * t #: Determine if interpolation should be used skip_interpolation = (self.connection.always_spools or config.spooled or not config.interpolate) # speed = distance/seconds # So distance/speed = seconds to wait step_size = config.step_size if not skip_interpolation and step_size <= 0: raise ValueError("Cannot have a step size <= 0!") try: # Apply device filters for f in self.filters: log.debug(" filter | Running {} on model".format(f)) model = f.apply_to_model(model) # Some versions of Qt seem to require a value in toSubpathPolygons m = QtGui.QTransform.fromScale(1, 1) polypath = model.toSubpathPolygons(m) # Apply device filters to polypath for f in self.filters: log.debug(" filter | Running {} on polypath".format(f)) polypath = f.apply_to_polypath(polypath) for path in polypath: #: And then each point within the path #: this is a polygon for i, p in enumerate(path): #: Head state # 0 move, 1 cut z = 0 if i == 0 else 1 #: Make a subpath subpath = QtGui.QPainterPath() subpath.moveTo(_p) subpath.lineTo(p) #: Update the last point _p = p #: Total length l = subpath.length() #: If the device does not support streaming #: the path interpolation is skipped entirely if skip_interpolation: x, y = p.x(), p.y() yield (l, self.move, ([x, y, z], ), {}) continue #: Where we are within the subpath d = 0 #: Interpolate path in steps of dl and ensure we get #: _p and p (t=0 and t=1) #: This allows us to cancel mid point while d <= l: #: Now set d to the next point by step_size #: if the end of the path is less than the step size #: use the minimum of the two dl = min(l - d, step_size) #: Now find the point at the given step size #: the first point d=0 so t=0, the last point d=l so t=1 t = subpath.percentAtLength(d) sp = subpath.pointAtPercent(t) #if d == l: # break #: Um don't we want to send the last point?? #: -y because Qt's axis is from top to bottom not bottom #: to top x, y = sp.x(), sp.y() yield (dl, self.move, ([x, y, z], ), {}) #: When we reached the end but instead of breaking above #: with a d < l we do it here to ensure we get the last #: point if d == l: #: We reached the end break #: Add step size d += dl #: Make sure we get the endpoint ep = model.currentPosition() yield (0, self.move, ([ep.x(), ep.y(), 0], ), {}) except Exception as e: log.error("device | processing error: {}".format( traceback.format_exc())) raise e
def process(self, model): """ Process the path model of a job and return each command within the job. Parameters ---------- model: QPainterPath The path to process Returns ------- generator: A list or generator object that yields each command to invoke on the device and the distance moved. In the format (distance, cmd, args, kwargs) """ config = self.config # Previous point _p = QtCore.QPointF(self.origin[0], self.origin[1]) # Do a final translation since Qt's y axis is reversed from svg's # It should now be a bbox of (x=0, y=0, width, height) # this creates a copy model = model * QtGui.QTransform.fromScale(1, -1) # Determine if interpolation should be used skip_interpolation = (self.connection.always_spools or config.spooled or not config.interpolate) # speed = distance/seconds # So distance/speed = seconds to wait step_size = config.step_size if not skip_interpolation and step_size <= 0: raise ValueError("Cannot have a step size <= 0!") try: # Apply device filters for f in self.filters: log.debug(" filter | Running {} on model".format(f)) model = f.apply_to_model(model, job=self) # Since Qt's toSubpathPolygons converts curves without accepting # a parameter to set the minimum distance between points on the # curve, we need to prescale by a "quality factor" before # converting then undo the scaling to effectively adjust the # number of points on a curve. m = QtGui.QTransform.fromScale(config.quality_factor, config.quality_factor) # Some versions of Qt seem to require a value in toSubpathPolygons polypath = model.toSubpathPolygons(m) if config.quality_factor != 1: # Undo the prescaling, if the quality_factor > 1 the curve # quality will be improved. m_inv = QtGui.QTransform.fromScale(1 / config.quality_factor, 1 / config.quality_factor) polypath = list(map(m_inv.map, polypath)) # Apply device filters to polypath for f in self.filters: log.debug(" filter | Running {} on polypath".format(f)) polypath = f.apply_to_polypath(polypath) for path in polypath: #: And then each point within the path #: this is a polygon for i, p in enumerate(path): #: Head state # 0 move, 1 cut z = 0 if i == 0 else 1 #: Make a subpath subpath = QtGui.QPainterPath() subpath.moveTo(_p) subpath.lineTo(p) #: Update the last point _p = p #: Total length l = subpath.length() #: If the device does not support streaming #: the path interpolation is skipped entirely if skip_interpolation: x, y = p.x(), p.y() yield (l, self.move, ([x, y, z], ), {}) continue #: Where we are within the subpath d = 0 #: Interpolate path in steps of dl and ensure we get #: _p and p (t=0 and t=1) #: This allows us to cancel mid point while d <= l: #: Now set d to the next point by step_size #: if the end of the path is less than the step size #: use the minimum of the two dl = min(l - d, step_size) #: Now find the point at the given step size #: the first point d=0 so t=0, the last point d=l so t=1 t = subpath.percentAtLength(d) sp = subpath.pointAtPercent(t) #if d == l: # break #: Um don't we want to send the last point?? x, y = sp.x(), sp.y() yield (dl, self.move, ([x, y, z], ), {}) #: When we reached the end but instead of breaking above #: with a d < l we do it here to ensure we get the last #: point if d == l: #: We reached the end break #: Add step size d += dl #: Make sure we get the endpoint ep = model.currentPosition() x, y = ep.x(), ep.y() yield (0, self.move, ([x, y, 0], ), {}) except Exception as e: log.error("device | processing error: {}".format( traceback.format_exc())) raise e