def createNetPath(self, brushColor: str, painterPath: QPainterPath, isOccupyPathItem: bool): """ Create a QGraphicsPathItem for a network path Args: brushColor (str) : The color for filling the rectangles painterPath (QPainterPath) : The path to be inserted to the item isOccupyPathItem (bool) : Whether the path is occupied or unoccupied path """ # Generate the path item if not created if isOccupyPathItem: if self.occupiedPathItem is None: self.occupiedPathItem = QGraphicsPathItem(self) pathItem = self.occupiedPathItem else: if self.unoccupiedPathItem is None: self.unoccupiedPathItem = QGraphicsPathItem(self) pathItem = self.unoccupiedPathItem if pathItem is None: pathItem = QGraphicsPathItem(self) # Set the item parameters pathItem.setPath(painterPath) pathItem.setPen( QPen(QColor(self.netColor), self.netThickness, style=Qt.SolidLine)) pathItem.setBrush(QBrush(QColor(brushColor))) pathItem.setZValue(0)
class PathWorkplaneOutline(QGraphicsRectItem): def __init__(self, parent=None): super(PathWorkplaneOutline, self).__init__(parent) self.setPen(getNoPen()) self._path = QGraphicsPathItem(self) self._path.setBrush(getNoBrush()) self._path.setPen(newPenObj(styles.BLUE_STROKE, 0)) # end def def updateAppearance(self): tl = self.rect().topLeft() tl1 = tl + QPointF(0, -BASE_WIDTH/2) tl2 = tl + QPointF(BASE_WIDTH/2, -BASE_WIDTH/2) bl = self.rect().bottomLeft() bl1 = bl + QPointF(0, BASE_WIDTH/2) bl2 = bl + QPointF(BASE_WIDTH/2, BASE_WIDTH/2) tr = self.rect().topRight() tr1 = tr + QPointF(0, -BASE_WIDTH/2) tr2 = tr + QPointF(-BASE_WIDTH/2, -BASE_WIDTH/2) br = self.rect().bottomRight() br1 = br + QPointF(0, BASE_WIDTH/2) br2 = br + QPointF(-BASE_WIDTH/2, BASE_WIDTH/2) pp = QPainterPath() pp.moveTo(tl2) pp.lineTo(tl1) pp.lineTo(bl1) pp.lineTo(bl2) pp.moveTo(tr2) pp.lineTo(tr1) pp.lineTo(br1) pp.lineTo(br2) self._path.setPath(pp)
def drawGlyph(self, scene, glyph, offsetX=0, offsetY=0, color=(255, 255, 255)): path = QPainterPath() path.setFillRule(Qt.WindingFill) for c in self.decomposedPaths(glyph): segs = c.segments path.moveTo(segs[-1].points[-1].x, segs[-1].points[-1].y) for seg in segs: tuples = [(a.x, a.y) for a in seg.points] flattuples = list(sum(tuples, ())) if len(tuples) == 2: path.quadTo(*flattuples) elif len(tuples) == 3: path.cubicTo(*flattuples) else: path.lineTo(*flattuples) line = QGraphicsPathItem() line.setBrush(QColor(*color)) p = QPen() p.setStyle(Qt.NoPen) line.setPen(p) line.setPath(path) reflect = QTransform(1, 0, 0, -1, 0, 0) reflect.translate(offsetX, offsetY) # print(f"Drawing {glyph} at offset {offsetX} {offsetY}") line.setTransform(reflect) scene.addItem(line)
def __init__(self): super(Canvas, self).__init__() global scene scene = QGraphicsScene() self.setScene(scene) self.path1 = QPainterPath() self.path2 = QPainterPath() global viewport viewport = self.viewport() global new_path new_path = QPainterPath() global pathitem1, pathitem2, new_pathitem pathitem1 = QGraphicsPathItem() pathitem2 = QGraphicsPathItem() new_pathitem = QGraphicsPathItem() global pen pen = QPen(Qt.black, 10) pathitem1.setPen(pen) eraser = QPen(Qt.white, 10) pathitem2.setPen(eraser) global item item = pathitem1 scene.addItem(item)
def visualize(self): """ called when user clicks draw lsystem. This visualizes the lsystem to allow setup before the actual lsystification takes place """ self.getdata() self.run_lsystem() old_group = self.layersModel.itemFromIndex( self.parent.layersList.currentIndex()).get_graphics_items_group() if old_group: self.parent.scene.removeItem(old_group) group = QGraphicsItemGroup() path = QPainterPath() for idx, point in enumerate( self.lsysteminterpreter.globalstate["pts"]): x = point[0][0][0] y = point[0][1][0] direction = point[1] x = x * self.xscale + self.xoffs y = y * self.yscale + self.yoffs if idx == 0: path.moveTo(x, y) else: path.lineTo(x, y) item = QGraphicsPathItem(path) pen = QPen() pen.setWidth(self.strokeWidth) item.setPen(pen) group.addToGroup(item) self.addNewGraphicsItems(group)
class createDiscretionWindow(view.descritiondefinition_ui.Ui_discretionWindow, QDialog): def __init__(self, isCrack, he, parent=None): super(createDiscretionWindow, self).__init__(parent) self.highlight = QGraphicsPathItem() self.highlight.setPath(he) pen = QPen(Qt.red, 2) self.highlight.setPen(pen) self.highlight.setZValue(1) self.parent().scene.addItem(self.highlight) self.setupUi(self) self.move(1450, -1) self.isCrack = isCrack self.lineEdit_number_of_elements.setFocus(True) self.lineEdit_number_of_elements.textChanged.connect(self.change_number_of_discontinous_elements) self.radioButton_2.toggled.connect(self.setDiscontinousBoxState) if self.isCrack == True: self.checkBox_isCrack.setChecked(True) self.radioButton_2.setChecked(True) self.checkBox_isCrack.setEnabled(False) self.groupBox_type_of_discretization.setEnabled(False) def setDiscontinousBoxState(self): newState = not self.groupBox_discontinous_discretion.isEnabled() self.groupBox_discontinous_discretion.setEnabled(newState) def change_number_of_discontinous_elements(self): if self.radioButton_2.isChecked(): if self.lineEdit_number_of_elements.text() != "": if int(self.lineEdit_number_of_elements.text()) > self.verticalLayout_2.count(): for i in range(int(self.lineEdit_number_of_elements.text()) - self.verticalLayout_2.count()): lineEdit = QLineEdit() validator = QDoubleValidator() validator.setNotation(0) validator.setRange(0.1, 0.9, decimals=3) lineEdit.setValidator(validator) self.verticalLayout_2.addWidget(lineEdit) else: for i in range(self.verticalLayout_2.count()-1, int(self.lineEdit_number_of_elements.text())-1, -1): itemToDelete = self.verticalLayout_2.itemAt(i).widget() self.verticalLayout_2.removeWidget(itemToDelete) itemToDelete.deleteLater() else: pass @classmethod def getDiscretion(cls, he, isCrack=False, parent=None): dialog = cls(isCrack, he, parent) dialog.exec_() if dialog.radioButton.isChecked(): dialog.parent().scene.removeItem(dialog.highlight) if dialog.lineEdit_number_of_elements.text() != "": return int(dialog.lineEdit_number_of_elements.text()), False else: return 1, False else: spaces = [0] for i in range(dialog.verticalLayout_2.count()): spaces.append(float(dialog.verticalLayout_2.itemAt(i).widget().text()) + spaces[-1]) dialog.parent().scene.removeItem(dialog.highlight) return spaces, True
def createNetPath(self, brushColor: str, painterPath: QPainterPath, isOccupyPathItem: bool): """ Create a QGraphicsPathItem for a network path Args: brushColor (str) : The color for filling the rectangles painterPath (QPainterPath) : The path to be inserted to the item isOccupyPathItem (bool) : Whether the path is occupied or unoccupied path """ # Generate the path item if not created if isOccupyPathItem: if self.occupiedPathItem is None: self.occupiedPathItem = QGraphicsPathItem(self) pathItem = self.occupiedPathItem else: if self.unoccupiedPathItem is None: self.unoccupiedPathItem = QGraphicsPathItem(self) pathItem = self.unoccupiedPathItem if pathItem is None: pathItem = QGraphicsPathItem(self) # Set the item parameters pathItem.setPath(painterPath) pathItem.setPen(QPen(QColor(self.netColor), self.netThickness, style=Qt.SolidLine)) pathItem.setBrush(QBrush(QColor(brushColor))) pathItem.setZValue(0)
def colour(self): global item, new_pathitem, new_path pen.setColor(QColorDialog.getColor()) new_pathitem = QGraphicsPathItem() new_pathitem.setPen(pen) new_path = QPainterPath() item = new_pathitem scene.addItem(item)
def small_size(self): global item, new_pathitem, new_path pen.setWidth(5) new_pathitem = QGraphicsPathItem() new_pathitem.setPen(pen) new_path = QPainterPath() item = new_pathitem scene.addItem(item)
def addPainterPath(self, tag): color = getColorStr() path = sidePath.pathLoader(tag) ## return painter path pathPt = path.pointAtPercent(0.0) ## so its consistent ## use painter path pathItem = QGraphicsPathItem(path) pathItem.setPen(QPen(QColor(color), 3, Qt.DashDotLine)) pathItem.setFlag(QGraphicsPathItem.ItemIsMovable, False) self.pathGroup.addToGroup(pathItem) self.addTag(tag, color, pathPt)
def loadfrom(self): global filename, item, new_path, new_pathitem filename = QFileDialog.getOpenFileName(None, 'Choose file')[0] pixmap = QPixmap(filename) scene.addPixmap(pixmap) new_pathitem = QGraphicsPathItem() new_pathitem.setPen(pen) new_path = QPainterPath() item = new_pathitem scene.addItem(item)
def finish(self): """ return the wrapped up result of the calculations :return: qgraphicsitemgroup """ item = QGraphicsPathItem(self.path) pen = QPen() pen.setWidth(self.strokeWidth) item.setPen(pen) self.group.addToGroup(item) self.prevPos = None return self.group
def drawCross(self, scene, x, y, color): path = QPainterPath() path.moveTo(x - 50, y) path.lineTo(x + 50, y) path.moveTo(x, y - 50) path.lineTo(x, y + 50) line = QGraphicsPathItem() p = QPen(QColor(*color)) p.setWidth(5) line.setPen(p) line.setPath(path) reflect = QTransform(1, 0, 0, -1, 0, 0) line.setTransform(reflect) scene.addItem(line)
def show_shape(item): """Highlight the shape of item.""" sh = QGraphicsPathItem() path = item.shape() # The shape path was returned in the item's coordinates # FIXME: translate is not enough when itemIgnoresTranformation path.translate(item.scenePos()) sh.setPath(path) pen = QPen(Qt.magenta, 1.5, Qt.DashLine) pen.setCosmetic(True) # thickness does not scale sh.setPen(pen) item.scene().addItem(sh) return sh
class PathWorkplaneOutline(QGraphicsRectItem): """ """ def __init__(self, parent: QGraphicsItem = None): """ Args: parent: default is ``None`` """ super(PathWorkplaneOutline, self).__init__(parent) self.setPen(getNoPen()) self._path = QGraphicsPathItem(self) self._path.setBrush(getNoBrush()) self._path.setPen(newPenObj(styles.BLUE_STROKE, 0)) # end def def updateAppearance(self): tl = self.rect().topLeft() tl1 = tl + QPointF(0, -BASE_WIDTH/2) tl2 = tl + QPointF(BASE_WIDTH/2, -BASE_WIDTH/2) bl = self.rect().bottomLeft() bl1 = bl + QPointF(0, BASE_WIDTH/2) bl2 = bl + QPointF(BASE_WIDTH/2, BASE_WIDTH/2) tr = self.rect().topRight() tr1 = tr + QPointF(0, -BASE_WIDTH/2) tr2 = tr + QPointF(-BASE_WIDTH/2, -BASE_WIDTH/2) br = self.rect().bottomRight() br1 = br + QPointF(0, BASE_WIDTH/2) br2 = br + QPointF(-BASE_WIDTH/2, BASE_WIDTH/2) pp = QPainterPath() pp.moveTo(tl2) pp.lineTo(tl1) pp.lineTo(bl1) pp.lineTo(bl2) pp.moveTo(tr2) pp.lineTo(tr1) pp.lineTo(br1) pp.lineTo(br2) self._path.setPath(pp)
class DrawItem(): ''' Piirtää kuviot ''' def __init__(self, piirtoalusta): self.scene = piirtoalusta.scene self.undoStack = piirtoalusta.undoStack self.reset() self.start = QPoint() self.end = QPoint() self.pos = QPoint() self.color = Qt.black self.font = QFont() def setRect(self): ''' Määrittää nelikulmion paikkatietojen perusteella ''' rect = QRectF() rect.setTopLeft(self.start) rect.setBottomRight(self.end) return rect def drawRect(self): ''' Piirtää esineen ja lisää sen sceneen. Palauttaa esineen Load ja Undo luokkia varten. ''' path = QPainterPath() rect = self.setRect() path.addRect(rect) path.simplified() #self.rectitem = QGraphicsPathItem(path) self.rectitem = PathItem(path, self.undoStack) self.rectitem.setPen(QPen(self.color, 2)) self.rectitem.setFlag(QGraphicsItem.ItemIsSelectable) self.rectitem.setFlag(QGraphicsItem.ItemIsMovable) self.scene.addItem(self.rectitem) return self.rectitem ''' rect = self.setRect() self.rectitem = QGraphicsRectItem(rect) self.rectitem.setPen(QPen(self.color, 2)) self.scene.addItem(self.rectitem) ''' def drawEllipse(self): rect = self.setRect() #self.ellipseitem = QGraphicsEllipseItem(rect) self.ellipseitem = EllipseItem(rect, self.undoStack) self.ellipseitem.setPen(QPen(self.color, 2)) self.ellipseitem.setFlag(QGraphicsItem.ItemIsSelectable) self.ellipseitem.setFlag(QGraphicsItem.ItemIsMovable) self.scene.addItem(self.ellipseitem) return self.ellipseitem def drawCircle(self): erotus = abs(self.end.x() - self.start.x()) if self.start.y() > self.end.y(): erotus = -erotus self.end.setY(self.start.y() + erotus) self.drawEllipse() def drawLine(self): path = QPainterPath() shape = QRectF(0, 0, 1, 1) shape.moveCenter(self.pos) path.addEllipse(shape) self.paths.connectPath(path) self.paths.simplified() #self.lineitem = QGraphicsPathItem(self.paths) self.lineitem = PathItem(self.paths, self.undoStack) self.lineitem.setPen(QPen(self.color, 2)) self.lineitem.setFlag(QGraphicsItem.ItemIsSelectable) self.lineitem.setFlag(QGraphicsItem.ItemIsMovable) self.scene.addItem(self.lineitem) return self.lineitem def drawText(self): #textitem = QGraphicsTextItem('Text') textitem = TextItem('Text', self.undoStack) textitem.setPos(self.end) textitem.setAcceptHoverEvents(False) textitem.setTextInteractionFlags(Qt.TextSelectableByKeyboard | Qt.TextEditable) textitem.setFont(self.font) textitem.setDefaultTextColor(self.color) textitem.setFlag(QGraphicsItem.ItemIsMovable) textitem.setFlag(QGraphicsItem.ItemIsSelectable) self.scene.addItem(textitem) return textitem def reset(self): self.ellipseitem = QGraphicsEllipseItem() self.rectitem = QGraphicsRectItem() self.lineitem = QGraphicsPathItem() self.paths = QPainterPath() def change_selected_color(self, items): ''' Muttaa listan 'items' esineiden värin arvoon 'self.color'. ''' for item in items: if item.type() == 10: # group self.change_selected_color( item.childItems()) # kutsuu itseään joukon jäsenille elif item.type() == 8: # tekstiä item.setDefaultTextColor(self.color) else: item.setPen(QPen(self.color, 2))
class ReacItem(KineticsDisplayItem): defaultWidth = 30 defaultHeight = 30 defaultPenWidth = 2 name = constants.ITEM def __init__(self, *args, **kwargs): KineticsDisplayItem.__init__(self, *args, **kwargs) points = [ QtCore.QPointF(ReacItem.defaultWidth / 4, 0), QtCore.QPointF(0, ReacItem.defaultHeight / 4), QtCore.QPointF(ReacItem.defaultWidth, ReacItem.defaultHeight / 4), QtCore.QPointF(3 * ReacItem.defaultWidth / 4, ReacItem.defaultHeight / 2) ] path = QtGui.QPainterPath() path.moveTo(points[0]) for p in points[1:]: path.lineTo(p) path.moveTo(p) self.gobj = QGraphicsPathItem(path, self) self.gobj.setPen( QtGui.QPen(QtCore.Qt.black, 2, Qt.Qt.SolidLine, Qt.Qt.RoundCap, Qt.Qt.RoundJoin)) self.gobj.mobj = self.mobj self._Kf = self.gobj.mobj.Kf self._Kb = self.gobj.mobj.Kb doc = "Kf\t: " + str(self._Kf) + "\nKb\t: " + str(self._Kb) self.gobj.setToolTip(doc) def updateValue(self, gobj): self._gobj = gobj if (isinstance(self._gobj, moose.ReacBase)): self._Kf = self._gobj.Kf self._Kb = self._gobj.Kb doc = "Kf\t: " + str(self._Kf) + "\nKb\t: " + str(self._Kb) self.gobj.setToolTip(doc) def refresh(self, scale): defaultWidth = ReacItem.defaultWidth * scale defaultHeight = ReacItem.defaultHeight * scale points = [ QtCore.QPointF(defaultWidth / 4, 0), QtCore.QPointF(0, defaultHeight / 4), QtCore.QPointF(defaultWidth, defaultHeight / 4), QtCore.QPointF(3 * defaultWidth / 4, defaultHeight / 2) ] path = QtGui.QPainterPath() path.moveTo(points[0]) for p in points[1:]: path.lineTo(p) path.moveTo(p) self.gobj.setPath(path) ReacPen = self.gobj.pen() defaultpenwidth = ReacItem.defaultPenWidth reacWidth = defaultpenwidth * scale ReacPen.setWidth(reacWidth) self.gobj.setPen(ReacPen) def setDisplayProperties(self, x, y, textcolor, bgcolor): """Set the display properties of this item.""" self.setGeometry(x, y, self.gobj.boundingRect().width(), self.gobj.boundingRect().height())
class TextAnnotation(Annotation): """Text annotation item for the canvas scheme. """ editingFinished = Signal() """Emitted when the editing is finished (i.e. the item loses focus).""" textEdited = Signal() """Emitted when the edited text changes.""" def __init__(self, parent=None, **kwargs): Annotation.__init__(self, parent, **kwargs) self.setFlag(QGraphicsItem.ItemIsMovable) self.setFlag(QGraphicsItem.ItemIsSelectable) self.setFocusPolicy(Qt.ClickFocus) self.__textMargins = (2, 2, 2, 2) rect = self.geometry().translated(-self.pos()) ################################ # PyQt 5.10 crashes because of this call (3) #self.__framePathItem = QGraphicsPathItem(self) self.__framePathItem = QGraphicsPathItem(None) self.__framePathItem.setPen(QPen(Qt.NoPen)) self.__textItem = GraphicsTextEdit(self) self.__textItem.setPlaceholderText(self.tr("Enter text here")) self.__textItem.setPos(2, 2) self.__textItem.setTextWidth(rect.width() - 4) self.__textItem.setTabChangesFocus(True) self.__textItem.setTextInteractionFlags(Qt.NoTextInteraction) self.__textItem.setFont(self.font()) self.__textInteractionFlags = Qt.NoTextInteraction layout = self.__textItem.document().documentLayout() layout.documentSizeChanged.connect(self.__onDocumentSizeChanged) self.__updateFrame() def adjustSize(self): """Resize to a reasonable size. """ self.__textItem.setTextWidth(-1) self.__textItem.adjustSize() size = self.__textItem.boundingRect().size() left, top, right, bottom = self.textMargins() geom = QRectF(self.pos(), size + QSizeF(left + right, top + bottom)) self.setGeometry(geom) def setFramePen(self, pen): """Set the frame pen. By default Qt.NoPen is used (i.e. the frame is not shown). """ self.__framePathItem.setPen(pen) def framePen(self): """Return the frame pen. """ return self.__framePathItem.pen() def setFrameBrush(self, brush): """Set the frame brush. """ self.__framePathItem.setBrush(brush) def frameBrush(self): """Return the frame brush. """ return self.__framePathItem.brush() def setPlainText(self, text): """Set the annotation plain text. """ self.__textItem.setPlainText(text) def toPlainText(self): return self.__textItem.toPlainText() def setHtml(self, text): """Set the annotation rich text. """ self.__textItem.setHtml(text) def toHtml(self): return self.__textItem.toHtml() def setDefaultTextColor(self, color): """Set the default text color. """ self.__textItem.setDefaultTextColor(color) def defaultTextColor(self): return self.__textItem.defaultTextColor() def setTextMargins(self, left, top, right, bottom): """Set the text margins. """ margins = (left, top, right, bottom) if self.__textMargins != margins: self.__textMargins = margins self.__textItem.setPos(left, top) self.__textItem.setTextWidth( max(self.geometry().width() - left - right, 0)) def textMargins(self): """Return the text margins. """ return self.__textMargins def document(self): """Return the QTextDocument instance used internally. """ return self.__textItem.document() def setTextCursor(self, cursor): self.__textItem.setTextCursor(cursor) def textCursor(self): return self.__textItem.textCursor() def setTextInteractionFlags(self, flags): self.__textInteractionFlags = flags def textInteractionFlags(self): return self.__textInteractionFlags def setDefaultStyleSheet(self, stylesheet): self.document().setDefaultStyleSheet(stylesheet) def mouseDoubleClickEvent(self, event): Annotation.mouseDoubleClickEvent(self, event) if event.buttons() == Qt.LeftButton and \ self.__textInteractionFlags & Qt.TextEditable: self.startEdit() def startEdit(self): """Start the annotation text edit process. """ self.__textItem.setTextInteractionFlags(self.__textInteractionFlags) self.__textItem.setFocus(Qt.MouseFocusReason) # Install event filter to find out when the text item loses focus. self.__textItem.installSceneEventFilter(self) self.__textItem.document().contentsChanged.connect(self.textEdited) def endEdit(self): """End the annotation edit. """ self.__textItem.setTextInteractionFlags(Qt.NoTextInteraction) self.__textItem.removeSceneEventFilter(self) self.__textItem.document().contentsChanged.disconnect(self.textEdited) self.editingFinished.emit() def __onDocumentSizeChanged(self, size): # The size of the text document has changed. Expand the text # control rect's height if the text no longer fits inside. try: rect = self.geometry() _, top, _, bottom = self.textMargins() if rect.height() < (size.height() + bottom + top): rect.setHeight(size.height() + bottom + top) self.setGeometry(rect) except Exception: log.error("error in __onDocumentSizeChanged", exc_info=True) def __updateFrame(self): rect = self.geometry() rect.moveTo(0, 0) path = QPainterPath() path.addRect(rect) self.__framePathItem.setPath(path) def resizeEvent(self, event): width = event.newSize().width() left, _, right, _ = self.textMargins() self.__textItem.setTextWidth(max(width - left - right, 0)) self.__updateFrame() QGraphicsWidget.resizeEvent(self, event) def sceneEventFilter(self, obj, event): if obj is self.__textItem and event.type() == QEvent.FocusOut: self.__textItem.focusOutEvent(event) self.endEdit() return True return Annotation.sceneEventFilter(self, obj, event) def itemChange(self, change, value): if change == QGraphicsItem.ItemSelectedHasChanged: if self.isSelected(): self.setFramePen(QPen(Qt.DashDotLine)) else: self.setFramePen(QPen(Qt.NoPen)) return Annotation.itemChange(self, change, value) def changeEvent(self, event): if event.type() == QEvent.FontChange: self.__textItem.setFont(self.font()) Annotation.changeEvent(self, event)
class ImageViewer(QGraphicsView, QObject): def __init__(self, parent=None): super(ImageViewer, self).__init__(parent) self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) #self.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setDragMode(QGraphicsView.ScrollHandDrag) #self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) #self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) self._scene = ImageViewerScene(self) self.setScene(self._scene) self._create_grid() self._create_grid_lines() self._pixmap = None self._selection_mode = SELECTION_MODE.NONE # polygon selection _polygon_guide_line_pen = QPen(QtGui.QColor(235, 72, 40)) _polygon_guide_line_pen.setWidth(2) _polygon_guide_line_pen.setStyle(QtCore.Qt.DotLine) self._polygon_guide_line = QGraphicsLineItem() self._polygon_guide_line.setVisible(False) self._polygon_guide_line.setPen(_polygon_guide_line_pen) self._scene.addItem(self._polygon_guide_line) self._current_polygon = None # rectangle selection self._box_origin = QPoint() self._box_picker = QRubberBand(QRubberBand.Rectangle, self) # free selection self._current_free_path = None self._is_drawing = False self._last_point_drawn = QPoint() self._current_label = None @property def current_label(self): return self._current_label @current_label.setter def current_label(self, value): self._current_label = value @property def pixmap(self) -> ImagePixmap: return self._pixmap @pixmap.setter def pixmap(self, value: QPixmap): self.selection_mode = SELECTION_MODE.NONE self.resetTransform() if self.pixmap: self._scene.removeItem(self._pixmap) self.remove_annotations() self._pixmap = ImagePixmap() self._pixmap.setPixmap(value) self._pixmap.setOffset(-value.width() / 2, -value.height() / 2) self._pixmap.setTransformationMode(QtCore.Qt.SmoothTransformation) self._pixmap.signals.hoverEnterEventSgn.connect( self.pixmap_hoverEnterEvent_slot) self._pixmap.signals.hoverLeaveEventSgn.connect( self.pixmap_hoverLeaveEvent_slot) self._pixmap.signals.hoverMoveEventSgn.connect( self.pixmap_hoverMoveEvent_slot) self._scene.addItem(self._pixmap) # rect=self._scene.addRect(QtCore.QRectF(0,0,100,100), QtGui.QPen(QtGui.QColor("red"))) # rect.setZValue(1.0) self.fit_to_window() @property def selection_mode(self): return self._selection_mode @selection_mode.setter def selection_mode(self, value): self._polygon_guide_line.hide() self._current_polygon = None self._current_free_path = None self._is_drawing = value == SELECTION_MODE.FREE if value == SELECTION_MODE.NONE: self.enable_items(True) else: self.enable_items(False) self._selection_mode = value def remove_annotations(self): for item in self._scene.items(): if isinstance(item, EditableBox): self._scene.removeItem(item) elif isinstance(item, EditablePolygon): item.delete_polygon() def remove_annotations_by_label(self, label_name): for item in self._scene.items(): if isinstance(item, EditableBox): if item.label and item.label.name == label_name: self._scene.removeItem(item) elif isinstance(item, EditablePolygon): if item.label and item.label.name == label_name: item.delete_polygon() def enable_items(self, value): for item in self._scene.items(): if isinstance(item, EditablePolygon) or isinstance( item, EditableBox): item.setEnabled(value) def _create_grid(self): gridSize = 15 backgroundPixmap = QtGui.QPixmap(gridSize * 2, gridSize * 2) #backgroundPixmap.fill(QtGui.QColor("white")) backgroundPixmap.fill(QtGui.QColor(20, 20, 20)) #backgroundPixmap.fill(QtGui.QColor("powderblue")) painter = QtGui.QPainter(backgroundPixmap) #backgroundColor=QtGui.QColor("palegoldenrod") #backgroundColor=QtGui.QColor(237,237,237) backgroundColor = QtGui.QColor(0, 0, 0) painter.fillRect(0, 0, gridSize, gridSize, backgroundColor) painter.fillRect(gridSize, gridSize, gridSize, gridSize, backgroundColor) painter.end() self._scene.setBackgroundBrush(QtGui.QBrush(backgroundPixmap)) def _create_grid_lines(self): pen_color = QColor(255, 255, 255, 255) pen = QPen(pen_color) pen.setWidth(2) pen.setStyle(QtCore.Qt.DotLine) self.vline = QGraphicsLineItem() self.vline.setVisible(False) self.vline.setPen(pen) self.hline = QGraphicsLineItem() self.hline.setVisible(False) self.hline.setPen(pen) self._scene.addItem(self.vline) self._scene.addItem(self.hline) def wheelEvent(self, event: QWheelEvent): adj = (event.angleDelta().y() / 120) * 0.1 self.scale(1 + adj, 1 + adj) def fit_to_window(self): """Fit image within view.""" if not self.pixmap or not self._pixmap.pixmap(): return #self._pixmap.setTransformationMode(QtCore.Qt.SmoothTransformation) self.fitInView(self._pixmap, QtCore.Qt.KeepAspectRatio) def show_guide_lines(self): if self.hline and self.vline: self.hline.show() self.vline.show() def hide_guide_lines(self): if self.hline and self.vline: self.hline.hide() self.vline.hide() def pixmap_hoverEnterEvent_slot(self): self.show_guide_lines() def pixmap_hoverLeaveEvent_slot(self): self.hide_guide_lines() def pixmap_hoverMoveEvent_slot(self, evt: QGraphicsSceneHoverEvent, x, y): bbox: QRect = self._pixmap.boundingRect() offset = QPointF(bbox.width() / 2, bbox.height() / 2) self.vline.setLine(x, -offset.y(), x, bbox.height() - offset.y()) self.vline.setZValue(1) self.hline.setLine(-offset.x(), y, bbox.width() - offset.x(), y) self.hline.setZValue(1) def mouseMoveEvent(self, evt: QtGui.QMouseEvent) -> None: if self.selection_mode == SELECTION_MODE.BOX: if not self._box_origin.isNull(): self._box_picker.setGeometry( QRect(self._box_origin, evt.pos()).normalized()) elif self.selection_mode == SELECTION_MODE.POLYGON: if self._current_polygon: if self._current_polygon.count > 0: last_point: QPointF = self._current_polygon.last_point self._polygon_guide_line.setZValue(1) self._polygon_guide_line.show() mouse_pos = self.mapToScene(evt.pos()) self._polygon_guide_line.setLine(last_point.x(), last_point.y(), mouse_pos.x(), mouse_pos.y()) else: self._polygon_guide_line.hide() elif self.selection_mode == SELECTION_MODE.FREE and evt.buttons( ) and QtCore.Qt.LeftButton: if self._current_free_path: painter: QPainterPath = self._current_free_path.path() self._last_point_drawn = self.mapToScene(evt.pos()) painter.lineTo(self._last_point_drawn) self._current_free_path.setPath(painter) super(ImageViewer, self).mouseMoveEvent(evt) def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None: if evt.buttons() == QtCore.Qt.LeftButton: if self.selection_mode == SELECTION_MODE.BOX: self.setDragMode(QGraphicsView.NoDrag) self._box_origin = evt.pos() self._box_picker.setGeometry(QRect(self._box_origin, QSize())) self._box_picker.show() elif self._selection_mode == SELECTION_MODE.POLYGON: pixmap_rect: QRectF = self._pixmap.boundingRect() new_point = self.mapToScene(evt.pos()) # consider only the points intothe image if pixmap_rect.contains(new_point): if self._current_polygon is None: self._current_polygon = EditablePolygon() self._current_polygon.signals.deleted.connect( self.delete_polygon_slot) self._scene.addItem(self._current_polygon) self._current_polygon.addPoint(new_point) else: self._current_polygon.addPoint(new_point) elif self._selection_mode == SELECTION_MODE.FREE: # start drawing new_point = self.mapToScene(evt.pos()) pixmap_rect: QRectF = self._pixmap.boundingRect() # consider only the points intothe image if pixmap_rect.contains(new_point): self.setDragMode(QGraphicsView.NoDrag) pen = QPen(QtGui.QColor(235, 72, 40)) pen.setWidth(10) self._last_point_drawn = new_point self._current_free_path = QGraphicsPathItem() self._current_free_path.setOpacity(0.6) self._current_free_path.setPen(pen) painter = QPainterPath() painter.moveTo(self._last_point_drawn) self._current_free_path.setPath(painter) self._scene.addItem(self._current_free_path) else: self.setDragMode(QGraphicsView.ScrollHandDrag) super(ImageViewer, self).mousePressEvent(evt) def mouseReleaseEvent(self, evt: QtGui.QMouseEvent) -> None: if evt.button() == QtCore.Qt.LeftButton: if self.selection_mode == SELECTION_MODE.BOX: roi: QRect = self._box_picker.geometry() roi: QRectF = self.mapToScene(roi).boundingRect() pixmap_rect = self._pixmap.boundingRect() self._box_picker.hide() if pixmap_rect == roi.united(pixmap_rect): rect = EditableBox(roi) rect.label = self.current_label self._scene.addItem(rect) self.selection_mode = SELECTION_MODE.NONE self.setDragMode(QGraphicsView.ScrollHandDrag) elif self.selection_mode == SELECTION_MODE.FREE and self._current_free_path: # create polygon self._current_free_path: QGraphicsPathItem path_rect = self._current_free_path.boundingRect() pixmap_rect = self._pixmap.boundingRect() if pixmap_rect == path_rect.united(pixmap_rect): path = self._current_free_path.path() path_polygon = EditablePolygon() path_polygon.label = self.current_label self._scene.addItem(path_polygon) for i in range(0, path.elementCount(), 10): x, y = path.elementAt(i).x, path.elementAt(i).y path_polygon.addPoint(QPointF(x, y)) self._scene.removeItem(self._current_free_path) self.selection_mode = SELECTION_MODE.NONE self.setDragMode(QGraphicsView.ScrollHandDrag) super(ImageViewer, self).mouseReleaseEvent(evt) def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: if self._current_polygon and event.key() == QtCore.Qt.Key_Space: points = self._current_polygon.points self._current_polygon.label = self.current_label self._current_polygon = None self.selection_mode = SELECTION_MODE.NONE self._polygon_guide_line.hide() self.setDragMode(QGraphicsView.ScrollHandDrag) super(ImageViewer, self).keyPressEvent(event) def delete_polygon_slot(self, polygon: EditablePolygon): self._current_polygon = None self.selection_mode = SELECTION_MODE.NONE self._polygon_guide_line.hide()
class TableItem(KineticsDisplayItem): defaultWidth = 30 defaultHeight = 30 defaultPenWidth = 2 name = constants.ITEM def __init__(self, *args, **kwargs): KineticsDisplayItem.__init__(self, *args, **kwargs) points = [ QtCore.QPointF(0, TableItem.defaultWidth / 2), QtCore.QPointF(TableItem.defaultHeight / 2 - 2, 0), QtCore.QPointF(TableItem.defaultWidth / 2 + 2, 0), QtCore.QPointF(TableItem.defaultWidth, TableItem.defaultHeight / 2), ] path = QtGui.QPainterPath() path.moveTo(points[0]) for p in points[1:]: path.lineTo(p) path.moveTo(p) path.moveTo(0, 0) path.lineTo(TableItem.defaultWidth, 0) path.moveTo(-(TableItem.defaultWidth / 3), TableItem.defaultHeight / 4) path.lineTo((TableItem.defaultWidth + 10), TableItem.defaultHeight / 4) self.gobj = QGraphicsPathItem(path, self) #self.gobj.setToolTip("Need to see what to show unlike conc/nint for pool") tabledoc = (moose.element(self.mobj.path)).outputValue self.gobj.setToolTip(str(tabledoc)) self.gobj.setPen( QtGui.QPen(QtCore.Qt.black, 2, Qt.Qt.SolidLine, Qt.Qt.RoundCap, Qt.Qt.RoundJoin)) self.gobj.mobj = self.mobj def refresh(self, scale): defaultWidth = TableItem.defaultWidth * scale defaultHeight = TableItem.defaultHeight * scale points = [ QtCore.QPointF(0, defaultWidth / 2), QtCore.QPointF(defaultHeight / 2 - 2, 0), QtCore.QPointF(defaultWidth / 2 + 2, 0), QtCore.QPointF(defaultWidth, defaultHeight / 2) ] path = QtGui.QPainterPath() path.moveTo(points[0]) for p in points[1:]: path.lineTo(p) path.moveTo(p) path.moveTo(0, 0) path.lineTo(defaultWidth, 0) path.moveTo(-(defaultWidth / 3), defaultHeight / 4) path.lineTo((defaultWidth + 10), defaultHeight / 4) self.gobj.setPath(path) TablePen = self.gobj.pen() tableWidth = TableItem.defaultPenWidth * scale TablePen.setWidth(tableWidth) self.gobj.setPen(TablePen) def setDisplayProperties(self, x, y, textcolor, bgcolor): """Set the display properties of this item.""" # TODO: check the table bounding reactangle b'cos selection looks ugly self.setGeometry(x, y, self.gobj.boundingRect().width(), self.gobj.boundingRect().height())
class CharItem(QGraphicsRectItem): """ This item represents character item The purpose of the class is to draw a character, create a matrix of rectangles and resolve in which rectangles the character passes The class allow the following operations -# Drawing a character using the mouse events: -# Start by the mouse press event -# Continues by the mouse move event -# The character is stored in QGraphicsPathItem -# Transform the character to occupy the whole item's space -# Set operation : resolving the Occupied matrix which tell on which rectangle the character passes -# Reset operation : reverse the character transform so it is possible to continue drawing the character -# Save operation : To a QDataStream -# Load operation : From a QDataStream The graphical view of the class is composed from: -# This class which inherits from QGraphicsRectItem and holds : -# A QGraphicsPathItem : representing the character -# A QGraphicsPathItem : representing the occupied rectangles -# A QGraphicsPathItem : representing the unoccupied rectangles """ def __init__(self, rect: QRectF, pos: QPointF, viewIndex: int = -1): """ CharItem constructor Args: rect (QRectF) : The rectangle that the character should fill pos (QPointF) : The position of the item within the parent viewIndex (int) : The index of the item in case it is presented in multi character presentation """ super(CharItem, self).__init__(rect) self.setAcceptedMouseButtons(Qt.LeftButton) self.setPresentationPrms() self.occupied = [[False for idx in range(self.netCols)] for idx in range(self.netRows)] self.charPath = None self.wasSetted = False self.occupiedPathItem = None self.unoccupiedPathItem = None self.dirty = False self.viewIndex = viewIndex self.filename = "" self.boundaries = rect self.dx = 1 self.dy = 1 self.posInParent = pos self.setPos(self.posInParent) def setPresentationPrms(self): """ Setting the presentation prms The reason the there is a duplicate set of presentation parameters is that it allows changing the presentation parameters for one character (like in the select option """ self.netColor = netColor self.netThickness = netThickness self.occupyColor = occupyColor self.unOccupyColor = unOccupyColor self.shapeColor = shapeColor self.shapeLineThickness = shapeLineThickness self.selectedOccupiedColor = selectedOccupiedColor self.selectedShapeColor = selectedShapeColor self.netRows = netRows self.netCols = netCols def setNetBoxDimensions(self, rect: QRectF): """ Set net box dimensions The net box is the rectangle that compose the network drawn to show the occupy matrix """ self.netRectHeight = rect.height() / self.netRows self.netRectWidth = rect.width() / self.netCols self.left = rect.left() self.top = rect.top() def netRect(self, row_idx: int, col_idx: int) -> QRectF: """ Set net rect The net box is the rectangle that compose the network drawn to show the occupy matrix Args: row_idx (int) : The row of the network rectangle col_idx (int) : The col of the network rectangle Returns: QRectF : The rectangle """ return QRectF(self.left + col_idx * self.netRectWidth, self.top + row_idx * self.netRectHeight, self.netRectWidth, self.netRectHeight) def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent): """ Mouse move event : continue draw a line This event is activated when the mouse is pressed and moves The methods draws the line in 2 conditions: -# The item is not part of multi character presentation -# A character path was initiated (using the mouse press event) Args: event (QGraphicsSceneMouseEvent) : the event description """ if self.viewIndex == -1: if self.charPath is not None: point = event.scenePos() path = self.charPath.path() path.lineTo(point) self.charPath.setPath(path) self.update() def mousePressEvent(self, event: QGraphicsSceneMouseEvent): """ Mouse Press Event : Start a new line / Select the character If the character is part of multi character presentation activate the character selection If the character is in single character presentation Start a new line in the character Args: event (QGraphicsSceneMouseEvent) : the event description """ if self.viewIndex == -1: self.startLine(event) else: self.setSelected() def startLine(self, event: QGraphicsSceneMouseEvent): """ Start drawing a line When the mouse button is pressed and we are in single character dialog this method is activated to start drowning a line in the character Args: event (QGraphicsSceneMouseEvent) : the event description """ # There are 2 modes for the presentation: # Original mode where the character is it's original size # After setting mode when the set was done and the character fullfill all # the item's space # Drawing can be done only in original mode if self.wasSetted: QMessageBox.critical( None, "Char identifier window", "The shape was already setted use revert setting") return # If this is the first start of a line - generate the QPainterPath and QGraphicsPathItem if self.charPath is None: self.initCharPath() # Move to the mouse position point = event.scenePos() path = self.charPath.path() path.moveTo(point) self.charPath.setPath(path) self.dirty = True def initCharPath(self): """ Init the item that holds the character There is one path item that holds the character This method is activated by start line if the char item was not created to create the new and only one """ self.dirty = True self.charPath = QGraphicsPathItem(self) self.charPath.setPen( QPen(QColor(self.shapeColor), self.shapeLineThickness)) self.charPath.setZValue(1) self.charPath.originalPos = self.charPath.pos() self.charPath.setPath(QPainterPath()) def setSelected(self): """ Set the item a selected item This method is activated when the mouse button is presses and the item is part of multi character presentation """ # Set the colors of the item self.occupiedPathItem.setBrush( QBrush(QColor(self.selectedOccupiedColor))) self.charPath.setPen( QPen(QColor(self.selectedShapeColor), self.shapeLineThickness)) self.update() # Report to the parent item about the selection self.parentItem().setSelected(self.viewIndex) def resetSelected(self): """ Set the colors of the item to not selected """ self.occupiedPathItem.setBrush(QBrush(QColor(self.occupyColor))) self.charPath.setPen( QPen(QColor(self.shapeColor), self.shapeLineThickness)) self.update() def set(self): """ Calculate the occupied matrix and present the results This method does the following: -# Fill the occupied matrix -# Generate the occupied and unoccupied pathes items -# Transform the char path to fit to the item's boundaries """ # If there is no shape drawn - return if self.charPath is None: QMessageBox.critical(None, "Char identifier window", "There is no shape drawn") return # If the item is in setted mode - return if self.wasSetted: QMessageBox.critical( None, "Char identifier window", "The shape was already setted use revert setting") return # fill the occupied matrix with the data before the scaling self.fillOccupied() self.setNetBoxDimensions(self.boundingRect()) self.createNetPaths() # update the transform - change the dimensions and location # only on the first time self.transformCharPath() self.wasSetted = True # update the presentation self.update() def revertTransform(self): """ Change from Setted mode to drawing mode The drawing mode is the mode where the character can be drawn -# Restore the original size of the character (Reset the transform of the char item) -# Restor the char path item's position to the original one (saved when created) -# Empty the occupiedPath and the unoccupiedPath """ # If there is no character drawn - return if self.charPath is None: QMessageBox.critical(None, "Char identifier window", "There is no shape drawn") return # If the item is already in drawing mode - return if not self.wasSetted: QMessageBox.critical(None, "Char identifier window", "The shape was not setted use set button") return # The char path item transform = self.charPath.transform() transform.reset() # The self.dx and self.dy are the scale parameters created when the item # begins and they are the scale parameters that transform it to the boundaries # given by the parent item transform.scale(self.dx, self.dy) self.charPath.setTransform(transform) self.charPath.setPos(self.charPath.originalPos) # Empty the network pathes self.occupiedPathItem.setPath(QPainterPath()) self.unoccupiedPathItem.setPath(QPainterPath()) self.wasSetted = False def transformCharPath(self): """ Transform char path when the item is setted This method does the following -# scale the char path to the size of the item -# calculate the new position of the char path so that it will be placed at the top left corner of the item """ dx = self.boundingRect().width() / self.charPath.boundingRect().width() dy = self.boundingRect().height() / self.charPath.boundingRect( ).height() transform = self.charPath.transform() transform.reset() transform.scale(dx, dy) self.charPath.setTransform(transform) # Move the shape to the origin moveX = -(self.charPath.boundingRect().left() - self.boundingRect().left()) * dx moveY = -(self.charPath.boundingRect().top() - self.boundingRect().top()) * dy self.charPath.setX(self.charPath.x() + moveX) self.charPath.setY(self.charPath.y() + moveY) def fillOccupied(self): """ Fill the occupied matrix The algorithm of filling the occupied matrix is -# Scanning the char path -# For each point decide on where row and column of the net -# Set the occupies matrix for this column and row to True """ for idx in range(100): point = self.charPath.path().pointAtPercent(idx / 100.) row_idx, col_idx = self.calcRowCol(point) self.occupied[row_idx][col_idx] = True def calcRowCol(self, point: QPointF): """ Calculate the network row and column that a point is int calc the row and column indexes of a point The following is the algorithm: 1. Find the distance between the point and the left (or top) 2. Divide the distance with the width of path to find the relative position 3. Multipile this relative position with the number of rows/cols 4. Convert the result to int to find the indexes 5. If the index is the number of row/col reduce the index (This is for the case the the point is on the boundary and in this case the relative position is 1 which will cause the indexes to be the number of rows/cols - out of the matrix indexes) Args: point (QPointF) : The point to resolve Returns: int : The network row that the point is in int : The network column that the point is in """ partialX = (point.x() - self.charPath.boundingRect().left() ) / self.charPath.boundingRect().width() partialY = (point.y() - self.charPath.boundingRect().top() ) / self.charPath.boundingRect().height() col_idx = int(partialX * self.netCols) row_idx = int(partialY * self.netRows) if row_idx == self.netRows: row_idx -= 1 if col_idx == self.netCols: col_idx -= 1 return row_idx, col_idx def createNetPaths(self): """ Create the network pathes This method creates 2 network pathes items one for holding the occupied rectangles and one to hold the unoccupied rectangles """ # Generate 2 QPainterPath occupiedPath = QPainterPath() unoccupiedPath = QPainterPath() # For each entry in occupied matrix : # Add a rectangle to the appropriate path according the entry value for row_idx in range(self.netRows): for col_idx in range(self.netCols): if self.occupied[row_idx][col_idx]: occupiedPath.addRect(self.netRect(row_idx, col_idx)) else: unoccupiedPath.addRect(self.netRect(row_idx, col_idx)) # Create the QGraphicsPathItems that will hold the path self.createNetPath(self.occupyColor, occupiedPath, True) self.createNetPath(self.unOccupyColor, unoccupiedPath, False) def createNetPath(self, brushColor: str, painterPath: QPainterPath, isOccupyPathItem: bool): """ Create a QGraphicsPathItem for a network path Args: brushColor (str) : The color for filling the rectangles painterPath (QPainterPath) : The path to be inserted to the item isOccupyPathItem (bool) : Whether the path is occupied or unoccupied path """ # Generate the path item if not created if isOccupyPathItem: if self.occupiedPathItem is None: self.occupiedPathItem = QGraphicsPathItem(self) pathItem = self.occupiedPathItem else: if self.unoccupiedPathItem is None: self.unoccupiedPathItem = QGraphicsPathItem(self) pathItem = self.unoccupiedPathItem if pathItem is None: pathItem = QGraphicsPathItem(self) # Set the item parameters pathItem.setPath(painterPath) pathItem.setPen( QPen(QColor(self.netColor), self.netThickness, style=Qt.SolidLine)) pathItem.setBrush(QBrush(QColor(brushColor))) pathItem.setZValue(0) def save(self, stream: QDataStream, filename: str): """ Save the item to QDataStream Args: stream (QDataStream) : The data stream to write the item to filename (str) : The filename (for documenting purposes) """ # The item position stream << self.pos() # The dimensions stream << self.rect() # The presentation parameters stream.writeQString(self.netColor) stream.writeQString(self.occupyColor) stream.writeQString(self.unOccupyColor) stream.writeQString(self.shapeColor) stream.writeInt16(self.shapeLineThickness) stream.writeInt16(self.netRows) stream.writeInt16(self.netRows) # The items paths stream << self.charPath.path() self.dirty = False self.filename = filename def load(self, stream, filename): """ Loads the item from QDataStream Args: stream (QDataStream) : The data stream to read the item from filename (str) : The filename (for documenting purposes) """ # read the pos pos = QPointF() stream >> pos self.setPos(pos) # read the dimensions rect = QRectF() stream >> rect self.setRect(rect) # The presentation parameters self.netColor = stream.readQString() self.occupyColor = stream.readQString() self.unOccupyColor = stream.readQString() self.shapeColor = stream.readQString() self.shapeLineThickness = stream.readInt16() self.netRows = stream.readInt16() self.netRows = stream.readInt16() # read the paths self.initCharPath() path = self.charPath.path() stream >> path self.charPath.setPath(path) # Fit the item to the boundaries and position given by the item's parent self.fitToBoundaries() # The presentation of the item is in setted mode so we activate the set method self.wasSetted = False self.set() self.dirty = False self.filename = filename def fitToBoundaries(self): """ Fit the item to the boundaries and position given by it's parent This method was made to support the change of the character boundaries and that the char can be presented in different boundaries and position """ self.setPos(self.posInParent) self.dx = self.boundaries.width() / self.rect().width() self.dy = self.boundaries.height() / self.rect().height() transform = self.transform() transform.scale(self.dx, self.dy) self.setTransform(transform)
class EdgeItem(GraphItem): _qt_pen_styles = { 'dashed': Qt.DashLine, 'dotted': Qt.DotLine, 'solid': Qt.SolidLine, } def __init__(self, highlight_level, spline, label_center, label, from_node, to_node, parent=None, penwidth=1, edge_color=None, style='solid'): super(EdgeItem, self).__init__(highlight_level, parent) self.from_node = from_node self.from_node.add_outgoing_edge(self) self.to_node = to_node self.to_node.add_incoming_edge(self) self._default_edge_color = self._COLOR_BLACK if edge_color is not None: self._default_edge_color = edge_color self._default_text_color = self._COLOR_BLACK self._default_color = self._COLOR_BLACK self._text_brush = QBrush(self._default_color) self._shape_brush = QBrush(self._default_color) if style in ['dashed', 'dotted']: self._shape_brush = QBrush(Qt.transparent) self._label_pen = QPen() self._label_pen.setColor(self._default_text_color) self._label_pen.setJoinStyle(Qt.RoundJoin) self._edge_pen = QPen(self._label_pen) self._edge_pen.setWidth(penwidth) self._edge_pen.setColor(self._default_edge_color) self._edge_pen.setStyle(self._qt_pen_styles.get(style, Qt.SolidLine)) self._sibling_edges = set() self._label = None if label is not None: self._label = QGraphicsSimpleTextItem(label) self._label.setFont(GraphItem._LABEL_FONT) label_rect = self._label.boundingRect() label_rect.moveCenter(label_center) self._label.setPos(label_rect.x(), label_rect.y()) self._label.hoverEnterEvent = self._handle_hoverEnterEvent self._label.hoverLeaveEvent = self._handle_hoverLeaveEvent self._label.setAcceptHoverEvents(True) # spline specification according to # http://www.graphviz.org/doc/info/attrs.html#k:splineType coordinates = spline.split(' ') # extract optional end_point end_point = None if (coordinates[0].startswith('e,')): parts = coordinates.pop(0)[2:].split(',') end_point = QPointF(float(parts[0]), -float(parts[1])) # extract optional start_point if (coordinates[0].startswith('s,')): parts = coordinates.pop(0).split(',') # first point parts = coordinates.pop(0).split(',') point = QPointF(float(parts[0]), -float(parts[1])) path = QPainterPath(point) while len(coordinates) > 2: # extract triple of points for a cubic spline parts = coordinates.pop(0).split(',') point1 = QPointF(float(parts[0]), -float(parts[1])) parts = coordinates.pop(0).split(',') point2 = QPointF(float(parts[0]), -float(parts[1])) parts = coordinates.pop(0).split(',') point3 = QPointF(float(parts[0]), -float(parts[1])) path.cubicTo(point1, point2, point3) self._arrow = None if end_point is not None: # draw arrow self._arrow = QGraphicsPolygonItem() polygon = QPolygonF() polygon.append(point3) offset = QPointF(end_point - point3) corner1 = QPointF(-offset.y(), offset.x()) * 0.35 corner2 = QPointF(offset.y(), -offset.x()) * 0.35 polygon.append(point3 + corner1) polygon.append(end_point) polygon.append(point3 + corner2) self._arrow.setPolygon(polygon) self._arrow.hoverEnterEvent = self._handle_hoverEnterEvent self._arrow.hoverLeaveEvent = self._handle_hoverLeaveEvent self._arrow.setAcceptHoverEvents(True) self._path = QGraphicsPathItem(parent) self._path.setPath(path) self.addToGroup(self._path) self.set_node_color() self.set_label_color() def add_to_scene(self, scene): scene.addItem(self) if self._label is not None: scene.addItem(self._label) if self._arrow is not None: scene.addItem(self._arrow) def setToolTip(self, tool_tip): super(EdgeItem, self).setToolTip(tool_tip) if self._label is not None: self._label.setToolTip(tool_tip) if self._arrow is not None: self._arrow.setToolTip(tool_tip) def add_sibling_edge(self, edge): self._sibling_edges.add(edge) def set_node_color(self, color=None): if color is None: self._label_pen.setColor(self._default_text_color) self._text_brush.setColor(self._default_color) if self._shape_brush.isOpaque(): self._shape_brush.setColor(self._default_edge_color) self._edge_pen.setColor(self._default_edge_color) else: self._label_pen.setColor(color) self._text_brush.setColor(color) if self._shape_brush.isOpaque(): self._shape_brush.setColor(color) self._edge_pen.setColor(color) self._path.setPen(self._edge_pen) if self._arrow is not None: self._arrow.setBrush(self._shape_brush) self._arrow.setPen(self._edge_pen) def set_label_color(self, color=None): if color is None: self._label_pen.setColor(self._default_text_color) else: self._label_pen.setColor(color) if self._label is not None: self._label.setBrush(self._text_brush) self._label.setPen(self._label_pen) def _handle_hoverEnterEvent(self, event): # hovered edge item in red self.set_node_color(self._COLOR_RED) self.set_label_color(self._COLOR_RED) if self._highlight_level > 1: if self.from_node != self.to_node: # from-node in blue self.from_node.set_node_color(self._COLOR_BLUE) # to-node in green self.to_node.set_node_color(self._COLOR_GREEN) else: # from-node/in-node in teal self.from_node.set_node_color(self._COLOR_TEAL) self.to_node.set_node_color(self._COLOR_TEAL) if self._highlight_level > 2: # sibling edges in orange for sibling_edge in self._sibling_edges: sibling_edge.set_node_color(self._COLOR_ORANGE) def _handle_hoverLeaveEvent(self, event): self.set_node_color() self.set_label_color() if self._highlight_level > 1: self.from_node.set_node_color() self.to_node.set_node_color() if self._highlight_level > 2: for sibling_edge in self._sibling_edges: sibling_edge.set_node_color()
class Edge(QGraphicsPathItem): """Muchia dintre 2 noduri Muchia dintre 2 noduri este un path de la primul nod al muchiei la cel de-al doilea nod. In principiu path-ul este o line dreapta, dar daca un alt nod se intersecteaza cu aceast path, ea se va cruba pentru claritatea grafului. Atribute -------- node1 : Node primul nod al muchiei node2 : Node cel de-al doilea nod al muchiei engine : GraphEngine enginu-ul aplicatiei cost : int, optional costul muchiei arrow_length : int, 15 reprezinta lungimea sagetii muchiei in cazul unui graf orientat direct_path : QPainterPath path-ul direct de la primul la al doilea nod Metode ------ create_path(start_point, end_point, directed) creaza path-ul muchiei dintre cele 2 noduri handle_value_changed(value) schimba culoarea muchiei """ def __init__(self, node1, node2, engine, cost=None): super().__init__() self.node1 = node1 self.node2 = node2 self.engine = engine self.cost = cost self.arrow_length = 15 self.setPen(self.node1.pen) # Crearea unui path invizibil si adaugarea lui in scene self.direct_path = QGraphicsPathItem() self.direct_path.setPen(QPen(QColor(0, 0, 0, 0))) self.engine.view.scene.addItem(self.direct_path) def create_path(self, start_point, end_point, directed): """Creeaza path-ul muchiei Path-ul muchiei este o curba Bezier. In cazul in care nici-un nod nu se intersecteaza cu path-ul direct dintre noduri, punctele de control ale curbei vor fi la centrul de greutate al dreptei date de cele 2 noduri, astfel creeandu-se o linie dreapta. In caz contrar, daca un nod se intersecteaza cu path-ul direct, pucntele de control ale curbei se vor situa pe dreapta perpendiculara pe path-ul direct, ce trece centrul de greutate al acestuia (dat de punctul de control initial) la o distanta egala dublul razei nodului. Aceste pucnte se pot situa in 2 pozitii, una la 'stanga' path-ului, iar cealalta la 'dreaptea' acestuia. Pozitia finala a punctului de control se determina 'trasand' 2 linii de la nodul care se intersecteaza la cele 2 posibile puncte de control. Verificand lungimea celor 2 linii se alege locatia punctului de control. panta dreptei : m = (y2 - y1) / (x2 - x1) ecuatia dreptei : y - y1 = m(x - x1) panta drepntei perpendiculare pe o dreapta : m' = -1 / m lungimea unei drepte : AB ^ 2 = (x2 - x1) ^ 2 + (y2 - y1) ^ 2 => primul pas pentru a afla pucntele de control in cazul unei intersectii este: de a calula panta dreptei perpendiculara pe path-ul direct => m' = -1 / (node2.y - node1.y) / (node2.x - node1.x) => m' = -1 * (node2.x - node1.x) / (node2.y - node1.y) => cel de-al doilea pas este calcularea ecuatiei dreptei de panta m' ce trece prin pucntul de control (not G) => y - G.y = m'(x - G.x) => y = m'(x - G.x) + G.y => cel de-al treilea pas este inlocuirea lui y in lungimea dreptei ( lungimea dreptei dorita este dublul razei nodului) pentru a afla cele 2 coordonate x posibile (la 'stanga' si la 'dreapta' path-ului direct) => (x2 - G.x) ^ 2 + (m'(x2 - G.x) + G.y - G.y) ^ 2 = (2raza) ^ 2 => x2 ^ 2 - 2 x2 G.x + G.x ^ 2 + (m' x2) ^ 2 - 2 (m' ^ 2) x2 G.x + (m' G.x) ^ 2 - (2raza) ^ 2 = 0 => (x2 ^ 2)(1 + m' ^ 2) + x2(2 G.x (1 + m' ^ 2)) + (G.x ^ 2)(1 + m' ^ 2) - (2raza) ^ 2 = 0 => cele 2 coordonate pe Ox ale punctului de control, prentu a afla cele 2 coordonate pe Oy se inlocuiesc valorie obtinute in ecuatia dreptei. Parametrii ---------- start_point : QPointF punctul de start al path-ului end_point : QPointF punctul de final al path-ului directed : bool orientarea grafului Returneaza ---------- path : QPainterPath path-ul final al muchiei """ # Centrul de greutate al dreptei formata de cele 2 noduri control_point = QPointF((start_point.x() + end_point.x()) / 2, (start_point.y() + end_point.y()) / 2) path = QPainterPath(start_point) node_radius = self.engine.node_radius point1 = point2 = None # Creearea path-ului direct _path = QPainterPath(start_point) _path.lineTo(end_point) self.direct_path.setPath(_path) # Verificarea pentru intersectii cu path-ul direct intersecting_items = self.engine.view.scene.collidingItems( self.direct_path) intersecting_items.remove(self.node1) intersecting_items.remove(self.node2) # Calcularea coordonatelor pe Ox a punctelor de control in cazul unei intersectii try: m = -1 * (self.node2.x() - self.node1.x()) / (self.node2.y() - self.node1.y()) agent = 1 + (m**2) factors = [ agent, -2 * control_point.x() * agent, (control_point.x()**2) * agent - (node_radius * 2)**2 ] roots = np.roots(factors) # In cazul in care nodurile au acceleasi coordonate pe Ox sau Oy panta # dreptei nu exista. Atunci se va trata cazul de ZeroDivisionError except ZeroDivisionError: point1 = control_point + QPointF(0, node_radius * 2) point2 = control_point - QPointF(0, node_radius * 2) for item in intersecting_items: if isinstance(item, Node): # Daca exista o intersectie si exista si panta dreptei atunci se calculeaza # si coordonatele pe Oy ale posibilelor puncte de control if (point1 and point2) is None: point1 = QPointF( roots[0], m * (roots[0] - control_point.x()) + control_point.y()) point2 = QPointF( roots[1], m * (roots[1] - control_point.x()) + control_point.y()) # Cele 2 linii de la nod la posibilele puncte de control line1 = QLineF(item.pos(), point1) line2 = QLineF(item.pos(), point2) # Daca lungimea primei linii este mai mica decat lungimea celei de-a doua linie # inseamna ca nodul este mai aproape de prima linie deci path-ul va trebui sa se # curbeze in partea opusa => se alege cel de-al doilea punct control_point = point2 if line1.length() <= line2.length( ) else point1 break # Creearea curbei Bezier path.cubicTo(control_point, control_point, end_point) # Daca graful este orientat se adauga la capatul muchiei o sageata pentru # a reprezenta orientarea acestuia if directed: pos = path.currentPosition() dx, dy, angle = self.engine.get_angle(control_point, end_point) path.lineTo( QPointF(pos.x() + self.arrow_length * math.cos(angle + 60), pos.y() + self.arrow_length * math.sin(angle + 60))) path.moveTo(end_point) path.lineTo( QPointF(pos.x() + self.arrow_length * math.cos(angle - 60), pos.y() + self.arrow_length * math.sin(angle - 60))) # In cazul in care muchia are un cost acesta va fi afisat la mijlocul muchiei font_metrics = QFontMetrics(TEXT_FONT) font_offset = QPointF(font_metrics.height(), font_metrics.horizontalAdvance(self.cost)) path.addText(control_point - font_offset / 2, TEXT_FONT, self.cost) return path def handle_value_changed(self, value): """Schimba culoarea muchiei Aceasta metoda este folosita pentru shimbarea culorii in timpul unei animatii Parametrii ---------- value : QColor noua culoare a muchiei """ self.setPen(QPen(value, 1.5, Qt.SolidLine))
class MapCanvas(QGraphicsView): def __init__(self): # UI Init super(QGraphicsView, self).__init__() self.setAutoFillBackground(True) self.setAttribute(Qt.WA_StyledBackground) self.setStyleSheet( 'QGraphicsView { background-color: rgba(0, 0, 0, 255); }' ) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setContentsMargins(0, 0, 0, 0) self.setTransformationAnchor(self.AnchorViewCenter) self.setFocusPolicy(Qt.StrongFocus) self.setRenderHint(QPainter.Antialiasing) # Class Init self._scene = QGraphicsScene(self) self.setScene(self._scene) self.scale_ratio = 1 self.map_data = None self.map_line_path_items = {} self.map_points_items = [] self.map_points_text_items = [] self.map_grid_path_item = QGraphicsPathItem() self.map_points_player_items = {} self.players = {} # Application Settings self.settings = settings.Settings('parse99') def load_map(self, map_name): self._scene.clear() self.map_data = MapData(map_name) self.create_grid_lines() self.create_map_lines() self.create_map_points() self.update_players() self.set_scene_padding(self.map_data.map_grid_geometry.width, self.map_data.map_grid_geometry.height) self.draw() self.centerOn(0, 0) def create_grid_lines(self): grid_line_width = self.settings.get_value('maps', 'grid_line_width') self.map_grid_path_item = QGraphicsPathItem() line_path = QPainterPath() for map_line in self.map_data.grid_lines: line_path.moveTo(map_line.x1, map_line.y1) line_path.lineTo(map_line.x2, map_line.y2) self.map_grid_path_item = QGraphicsPathItem(line_path) color = QColor().fromRgb(255, 255, 255, 25) self.map_grid_path_item.setPen( QPen( color, grid_line_width / self.scale_ratio ) ) def create_map_lines(self): map_line_width = self.settings.get_value('maps', 'map_line_width') # use color as string for dictionary keys to preserve line colours self.map_line_path_items = {} line_path = {} colors = {} for map_line in self.map_data.map_lines: key = str(map_line.r) + ',' \ + str(map_line.g) + ',' \ + str(map_line.b) if key not in line_path.keys(): line_path[key] = QPainterPath() colors[key] = QColor().fromRgb( map_line.r, map_line.g, map_line.b ) line_path[key].moveTo(QPointF(map_line.x1, map_line.y1)) line_path[key].lineTo(QPointF(map_line.x2, map_line.y2)) for key in line_path.keys(): self.map_line_path_items[key] = QGraphicsPathItem(line_path[key]) self.map_line_path_items[key].setPen( QPen( colors[key], map_line_width / self.scale_ratio ) ) def create_map_points(self): self.map_points_text_items = [] self.map_points_items = [] for map_point in self.map_data.map_points: color = QColor().fromRgb(map_point.r, map_point.g, map_point.b) rect = QGraphicsRectItem( QRectF( QPointF(map_point.x, map_point.y), QSizeF(5 / self.scale_ratio, 5 / self.scale_ratio) ) ) rect.setPen(QPen(Qt.black, 1 / self.scale_ratio)) rect.setBrush(color) self.map_points_items.append(rect) text = QGraphicsTextItem(map_point.text) text.setDefaultTextColor(color) text.setPos(map_point.x, map_point.y) text.setFont(QFont('Times New Roman', 8 / self.scale_ratio, 2)) self.map_points_text_items.append(text) def draw(self): # Draw map grid self._scene.addItem(self.map_grid_path_item) # Draw map lines for key in self.map_line_path_items.keys(): self._scene.addItem(self.map_line_path_items[key]) # Draw map points for item in self.map_points_items: self._scene.addItem(item) # Draw map point's text for item in self.map_points_text_items: self._scene.addItem(item) def update_players(self): # Convert lists to sets player_list_set = set(self.players.keys()) # Player points and text should be the same so only use one player_items_set = set(self.map_points_player_items.keys()) # calculate size of player circles circle_size = max(10, 10 / self.scale_ratio) # Draw and/or update all players in players for player in player_list_set: player_data = self.players[player] if player in player_items_set and \ self.map_points_player_items[player] in self._scene.items(): # Update self.map_points_player_items[player_data.name].setRect( player_data.x - circle_size / 2, player_data.y - circle_size / 2, circle_size, circle_size ) else: # Create New Point color = QColor().fromRgb( player_data.r, player_data.g, player_data.b ) circle = QGraphicsEllipseItem( player_data.x - circle_size / 2, player_data.y - circle_size / 2, circle_size, circle_size ) circle.setBrush(color) self.map_points_player_items[player_data.name] = circle self._scene.addItem( self.map_points_player_items[player_data.name] ) # Find/remove players who aren't in players list from the map for key in [player for player in player_items_set if player not in player_list_set]: self._scene.removeItem(self.map_points_player_items[key]) # Center map self.center() def set_scale(self, ratio): # Scale scene self.setTransform(QTransform()) self.scale_ratio = ratio self.scale(self.scale_ratio, self.scale_ratio) # Scale map lines map_line_width = self.settings.get_value('maps', 'map_line_width') for key in self.map_line_path_items.keys(): pen = self.map_line_path_items[key].pen() pen.setWidth( max( map_line_width, map_line_width / self.scale_ratio ) ) self.map_line_path_items[key].setPen(pen) # Scale map grid grid_line_width = self.settings.get_value('maps', 'grid_line_width') pen = self.map_grid_path_item.pen() pen.setWidth( max( grid_line_width, grid_line_width / self.scale_ratio ) ) self.map_grid_path_item.setPen(pen) # Scale map points for i, rect in enumerate(self.map_points_items): rect.setRect( self.map_data.map_points[i].x, self.map_data.map_points[i].y, max(5, 5 / self.scale_ratio), max(5, 5 / self.scale_ratio) ) # Scale map point's text for i, text in enumerate(self.map_points_text_items): text.setFont( QFont( 'Times New Roman', max(8, 8 / self.scale_ratio) ) ) text.setX( self.map_data.map_points[i].x + max(5, 5 / self.scale_ratio) ) # Scale player point circle_size = max(10, 10 / self.scale_ratio) for player in self.map_points_player_items.keys(): self.map_points_player_items[player].setRect( self.players[player].x - circle_size / 2, self.players[player].y - circle_size / 2, circle_size, circle_size ) def set_scene_padding(self, padding_x, padding_y): # Make it so that if you are zoomed out, you can still # drag the map around (not that smooth) rect = self._scene.sceneRect() rect.adjust( -padding_x * 2, -padding_y * 2, padding_x * 2, padding_y * 2 ) self.setSceneRect(rect) def draw_loading_screen(self): pass def fit_to_window(self): pass def center(self): if self.settings.get_value('maps', 'center_on') == 'player': # Center on Player for now by default # Added try/except because initialization causes resize event try: if '__you__' in self.players.keys(): self.centerOn( self.players['__you__'].x, self.players['__you__'].y ) except AttributeError as e: print("MapCanvas().center():", e) def add_player(self, name, time_stamp, location): y, x, z = [float(value) for value in location.strip().split(',')] y = -y x = -x if name not in self.players.keys(): r, g, b = (0, 255, 0) flag = '__other__' user_level = None if name == '__you__': r, g, b = (0, 255, 0) flag = '__you__' user_level = None self.players[name] = Player( name=name, x=x, y=y, z=z, r=r, g=g, b=b, flag=flag, user_level=user_level, time_stamp=time_stamp ) else: self.players[name].x = x self.players[name].y = y self.players[name].z = z self.players[name].time_stamp = time_stamp def wheelEvent(self, event): # Scale based on scroll wheel direction movement = event.angleDelta().y() if movement > 0: self.set_scale(self.scale_ratio + self.scale_ratio * 0.1) else: self.set_scale(self.scale_ratio - self.scale_ratio * 0.1) def keyPressEvent(self, event): # Enable drag mode while control button is being held down if event.modifiers() == Qt.ControlModifier: self.setDragMode(self.ScrollHandDrag) return QGraphicsView.keyPressEvent(self, event) def keyReleaseEvent(self, event): # Disable drag mode when control button released if event.key() == Qt.Key_Control: self.setDragMode(self.NoDrag) return QGraphicsView.keyPressEvent(self, event) def resizeEvent(self, event): self.center() return QGraphicsView.resizeEvent(self, event)
class CharItem(QGraphicsRectItem): """ This item represents character item The purpose of the class is to draw a character, create a matrix of rectangles and resolve in which rectangles the character passes The class allow the following operations -# Drawing a character using the mouse events: -# Start by the mouse press event -# Continues by the mouse move event -# The character is stored in QGraphicsPathItem -# Transform the character to occupy the whole item's space -# Set operation : resolving the Occupied matrix which tell on which rectangle the character passes -# Reset operation : reverse the character transform so it is possible to continue drawing the character -# Save operation : To a QDataStream -# Load operation : From a QDataStream The graphical view of the class is composed from: -# This class which inherits from QGraphicsRectItem and holds : -# A QGraphicsPathItem : representing the character -# A QGraphicsPathItem : representing the occupied rectangles -# A QGraphicsPathItem : representing the unoccupied rectangles """ def __init__(self, rect: QRectF, pos: QPointF, viewIndex: int=-1): """ CharItem constructor Args: rect (QRectF) : The rectangle that the character should fill pos (QPointF) : The position of the item within the parent viewIndex (int) : The index of the item in case it is presented in multi character presentation """ super(CharItem, self).__init__(rect) self.setAcceptedMouseButtons(Qt.LeftButton) self.setPresentationPrms() self.occupied = [[False for idx in range(self.netCols)] for idx in range(self.netRows)] self.charPath = None self.wasSetted = False self.occupiedPathItem = None self.unoccupiedPathItem = None self.dirty = False self.viewIndex = viewIndex self.filename = "" self.boundaries = rect self.dx = 1 self.dy = 1 self.posInParent = pos self.setPos(self.posInParent) def setPresentationPrms(self): """ Setting the presentation prms The reason the there is a duplicate set of presentation parameters is that it allows changing the presentation parameters for one character (like in the select option """ self.netColor = netColor self.netThickness = netThickness self.occupyColor = occupyColor self.unOccupyColor = unOccupyColor self.shapeColor = shapeColor self.shapeLineThickness = shapeLineThickness self.selectedOccupiedColor = selectedOccupiedColor self.selectedShapeColor = selectedShapeColor self.netRows = netRows self.netCols = netCols def setNetBoxDimensions(self, rect: QRectF): """ Set net box dimensions The net box is the rectangle that compose the network drawn to show the occupy matrix """ self.netRectHeight = rect.height() / self.netRows self.netRectWidth = rect.width() / self.netCols self.left = rect.left() self.top = rect.top() def netRect(self, row_idx: int, col_idx: int) -> QRectF: """ Set net rect The net box is the rectangle that compose the network drawn to show the occupy matrix Args: row_idx (int) : The row of the network rectangle col_idx (int) : The col of the network rectangle Returns: QRectF : The rectangle """ return QRectF(self.left + col_idx * self.netRectWidth, self.top + row_idx * self.netRectHeight, self.netRectWidth, self.netRectHeight) def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent): """ Mouse move event : continue draw a line This event is activated when the mouse is pressed and moves The methods draws the line in 2 conditions: -# The item is not part of multi character presentation -# A character path was initiated (using the mouse press event) Args: event (QGraphicsSceneMouseEvent) : the event description """ if self.viewIndex == -1: if self.charPath is not None: point = event.scenePos() path = self.charPath.path() path.lineTo(point) self.charPath.setPath(path) self.update() def mousePressEvent(self, event: QGraphicsSceneMouseEvent): """ Mouse Press Event : Start a new line / Select the character If the character is part of multi character presentation activate the character selection If the character is in single character presentation Start a new line in the character Args: event (QGraphicsSceneMouseEvent) : the event description """ if self.viewIndex == -1: self.startLine(event) else: self.setSelected() def startLine(self, event: QGraphicsSceneMouseEvent): """ Start drawing a line When the mouse button is pressed and we are in single character dialog this method is activated to start drowning a line in the character Args: event (QGraphicsSceneMouseEvent) : the event description """ # There are 2 modes for the presentation: # Original mode where the character is it's original size # After setting mode when the set was done and the character fullfill all # the item's space # Drawing can be done only in original mode if self.wasSetted: QMessageBox.critical(None, "Char identifier window", "The shape was already setted use revert setting") return # If this is the first start of a line - generate the QPainterPath and QGraphicsPathItem if self.charPath is None: self.initCharPath() # Move to the mouse position point = event.scenePos() path = self.charPath.path() path.moveTo(point) self.charPath.setPath(path) self.dirty = True def initCharPath(self): """ Init the item that holds the character There is one path item that holds the character This method is activated by start line if the char item was not created to create the new and only one """ self.dirty = True self.charPath = QGraphicsPathItem(self) self.charPath.setPen(QPen(QColor(self.shapeColor), self.shapeLineThickness)) self.charPath.setZValue(1) self.charPath.originalPos = self.charPath.pos() self.charPath.setPath(QPainterPath()) def setSelected(self): """ Set the item a selected item This method is activated when the mouse button is presses and the item is part of multi character presentation """ # Set the colors of the item self.occupiedPathItem.setBrush(QBrush(QColor(self.selectedOccupiedColor))) self.charPath.setPen(QPen(QColor(self.selectedShapeColor), self.shapeLineThickness)) self.update() # Report to the parent item about the selection self.parentItem().setSelected(self.viewIndex) def resetSelected(self): """ Set the colors of the item to not selected """ self.occupiedPathItem.setBrush(QBrush(QColor(self.occupyColor))) self.charPath.setPen(QPen(QColor(self.shapeColor), self.shapeLineThickness)) self.update() def set(self): """ Calculate the occupied matrix and present the results This method does the following: -# Fill the occupied matrix -# Generate the occupied and unoccupied pathes items -# Transform the char path to fit to the item's boundaries """ # If there is no shape drawn - return if self.charPath is None: QMessageBox.critical(None, "Char identifier window", "There is no shape drawn") return # If the item is in setted mode - return if self.wasSetted: QMessageBox.critical(None, "Char identifier window", "The shape was already setted use revert setting") return # fill the occupied matrix with the data before the scaling self.fillOccupied() self.setNetBoxDimensions(self.boundingRect()) self.createNetPaths() # update the transform - change the dimensions and location # only on the first time self.transformCharPath() self.wasSetted = True # update the presentation self.update() def revertTransform(self): """ Change from Setted mode to drawing mode The drawing mode is the mode where the character can be drawn -# Restore the original size of the character (Reset the transform of the char item) -# Restor the char path item's position to the original one (saved when created) -# Empty the occupiedPath and the unoccupiedPath """ # If there is no character drawn - return if self.charPath is None: QMessageBox.critical(None, "Char identifier window", "There is no shape drawn") return # If the item is already in drawing mode - return if not self.wasSetted: QMessageBox.critical(None, "Char identifier window", "The shape was not setted use set button") return # The char path item transform = self.charPath.transform() transform.reset() # The self.dx and self.dy are the scale parameters created when the item # begins and they are the scale parameters that transform it to the boundaries # given by the parent item transform.scale(self.dx, self.dy) self.charPath.setTransform(transform) self.charPath.setPos(self.charPath.originalPos) # Empty the network pathes self.occupiedPathItem.setPath(QPainterPath()) self.unoccupiedPathItem.setPath(QPainterPath()) self.wasSetted = False def transformCharPath(self): """ Transform char path when the item is setted This method does the following -# scale the char path to the size of the item -# calculate the new position of the char path so that it will be placed at the top left corner of the item """ dx = self.boundingRect().width() / self.charPath.boundingRect().width() dy = self.boundingRect().height() / self.charPath.boundingRect().height() transform = self.charPath.transform() transform.reset() transform.scale(dx, dy) self.charPath.setTransform(transform) # Move the shape to the origin moveX = -(self.charPath.boundingRect().left() - self.boundingRect().left()) * dx moveY = -(self.charPath.boundingRect().top() - self.boundingRect().top()) * dy self.charPath.setX(self.charPath.x() + moveX) self.charPath.setY(self.charPath.y() + moveY) def fillOccupied(self): """ Fill the occupied matrix The algorithm of filling the occupied matrix is -# Scanning the char path -# For each point decide on where row and column of the net -# Set the occupies matrix for this column and row to True """ for idx in range(100): point = self.charPath.path().pointAtPercent(idx / 100.) row_idx, col_idx = self.calcRowCol(point) self.occupied[row_idx][col_idx] = True def calcRowCol(self, point: QPointF): """ Calculate the network row and column that a point is int calc the row and column indexes of a point The following is the algorithm: 1. Find the distance between the point and the left (or top) 2. Divide the distance with the width of path to find the relative position 3. Multipile this relative position with the number of rows/cols 4. Convert the result to int to find the indexes 5. If the index is the number of row/col reduce the index (This is for the case the the point is on the boundary and in this case the relative position is 1 which will cause the indexes to be the number of rows/cols - out of the matrix indexes) Args: point (QPointF) : The point to resolve Returns: int : The network row that the point is in int : The network column that the point is in """ partialX = (point.x() - self.charPath.boundingRect().left()) / self.charPath.boundingRect().width() partialY = (point.y() - self.charPath.boundingRect().top()) / self.charPath.boundingRect().height() col_idx = int(partialX * self.netCols) row_idx = int(partialY * self.netRows) if row_idx == self.netRows: row_idx -= 1 if col_idx == self.netCols: col_idx -= 1 return row_idx, col_idx def createNetPaths(self): """ Create the network pathes This method creates 2 network pathes items one for holding the occupied rectangles and one to hold the unoccupied rectangles """ # Generate 2 QPainterPath occupiedPath = QPainterPath() unoccupiedPath = QPainterPath() # For each entry in occupied matrix : # Add a rectangle to the appropriate path according the entry value for row_idx in range(self.netRows): for col_idx in range(self.netCols): if self.occupied[row_idx][col_idx]: occupiedPath.addRect(self.netRect(row_idx, col_idx)) else: unoccupiedPath.addRect(self.netRect(row_idx, col_idx)) # Create the QGraphicsPathItems that will hold the path self.createNetPath(self.occupyColor, occupiedPath, True) self.createNetPath(self.unOccupyColor, unoccupiedPath, False) def createNetPath(self, brushColor: str, painterPath: QPainterPath, isOccupyPathItem: bool): """ Create a QGraphicsPathItem for a network path Args: brushColor (str) : The color for filling the rectangles painterPath (QPainterPath) : The path to be inserted to the item isOccupyPathItem (bool) : Whether the path is occupied or unoccupied path """ # Generate the path item if not created if isOccupyPathItem: if self.occupiedPathItem is None: self.occupiedPathItem = QGraphicsPathItem(self) pathItem = self.occupiedPathItem else: if self.unoccupiedPathItem is None: self.unoccupiedPathItem = QGraphicsPathItem(self) pathItem = self.unoccupiedPathItem if pathItem is None: pathItem = QGraphicsPathItem(self) # Set the item parameters pathItem.setPath(painterPath) pathItem.setPen(QPen(QColor(self.netColor), self.netThickness, style=Qt.SolidLine)) pathItem.setBrush(QBrush(QColor(brushColor))) pathItem.setZValue(0) def save(self, stream: QDataStream, filename: str): """ Save the item to QDataStream Args: stream (QDataStream) : The data stream to write the item to filename (str) : The filename (for documenting purposes) """ # The item position stream << self.pos() # The dimensions stream << self.rect() # The presentation parameters stream.writeQString(self.netColor) stream.writeQString(self.occupyColor) stream.writeQString(self.unOccupyColor) stream.writeQString(self.shapeColor) stream.writeInt16(self.shapeLineThickness) stream.writeInt16(self.netRows) stream.writeInt16(self.netRows) # The items paths stream << self.charPath.path() self.dirty = False self.filename = filename def load(self, stream, filename): """ Loads the item from QDataStream Args: stream (QDataStream) : The data stream to read the item from filename (str) : The filename (for documenting purposes) """ # read the pos pos = QPointF() stream >> pos self.setPos(pos) # read the dimensions rect = QRectF() stream >> rect self.setRect(rect) # The presentation parameters self.netColor = stream.readQString() self.occupyColor = stream.readQString() self.unOccupyColor = stream.readQString() self.shapeColor = stream.readQString() self.shapeLineThickness = stream.readInt16() self.netRows = stream.readInt16() self.netRows = stream.readInt16() # read the paths self.initCharPath() path = self.charPath.path() stream >> path self.charPath.setPath(path) # Fit the item to the boundaries and position given by the item's parent self.fitToBoundaries() # The presentation of the item is in setted mode so we activate the set method self.wasSetted = False self.set() self.dirty = False self.filename = filename def fitToBoundaries(self): """ Fit the item to the boundaries and position given by it's parent This method was made to support the change of the character boundaries and that the char can be presented in different boundaries and position """ self.setPos(self.posInParent) self.dx = self.boundaries.width() / self.rect().width() self.dy = self.boundaries.height() / self.rect().height() transform = self.transform() transform.scale(self.dx, self.dy) self.setTransform(transform)
path2 = QPainterPath() for i in range(close2.size - 1): path2.moveTo(i, close2[i]) path2.lineTo(i + 1, close2[i + 1]) app = QApplication([]) view = MyView() scene = QGraphicsScene() path_item = QGraphicsPathItem(path) pen = QPen(Qt.darkGray) pen.setCosmetic(True) path_item.setPen(pen) scene.addItem(path_item) path_item = QGraphicsPathItem(path2) pen = QPen(Qt.darkGray) pen.setCosmetic(True) path_item.setPen(pen) scene.addItem(path_item) timer = QTimer() timer.timeout.connect(view.update_last_plot) timer.start(1000) view.setScene(scene)
class RCircleSegment(QObject): def __init__(self, radius: float, center_x: float, center_y: float, start_angle: float, end_angle: float, clockwise, line_width: float, line_color=Qt.black, fill_color=Qt.transparent): super().__init__() # The supporting rectangle if end_angle < start_angle: end_angle += 2 * math.pi start_angle = -start_angle end_angle = -end_angle shift = end_angle - start_angle if clockwise: shift = -shift - 2 * math.pi x, y = center_x - radius, center_y - radius self.rect = QRectF(x, y, 2 * radius, 2 * radius) # The underlying QGraphicsPathItem self.painter_path = QPainterPath( QPointF(center_x + math.cos(start_angle) * radius, center_y - math.sin(start_angle) * radius)) self.painter_path.arcTo(self.rect, math.degrees(start_angle), math.degrees(shift)) self.path = QGraphicsPathItem(self.painter_path) self.path.setBrush(QtGui.QBrush(fill_color)) pen = QPen() pen.setWidthF(line_width) pen.setColor(line_color) self.path.setPen(pen) self._visible = 1 # def x(self): # return self._pos.x() # # def y(self): # return self._pos.y() # # # The following functions are for animation support # # @pyqtProperty(QPointF) # def pos(self): # return self._pos # # @pos.setter # def pos(self, value): # self.rect = QRectF(value.x() - self._radius, value.y() - self._radius, 2 * self._radius, 2 * self._radius) # self.path.setRect(self.rect) # self._pos = value # @pyqtProperty(int) def visible(self): return self._visible @visible.setter def visible(self, value): if (value > 0): self.path.show() else: self.path.hide() self._visible = value
class PreXoverItem(QGraphicsRectItem): """A PreXoverItem exists between a single 'from' VirtualHelixItem index and zero or more 'to' VirtualHelixItem Indices Attributes: adapter (:obj:`PropertyWrapperObject`): Description idx (int): the base index within the virtual helix is_fwd (bool): is this a forward strand? prexoveritem_manager (:obj:`PreXoverManager`): Manager of the PreXoverItems to_vh_id_num (int): Virtual Helix number this Xover point might connect to """ FILTER_NAME = "xover" def __init__(self, from_virtual_helix_item: PathVirtualHelixItemT, is_fwd: bool, from_index: int, nearby_idxs: List[int], to_vh_id_num: int, prexoveritem_manager: PreXoverManagerT): """Summary Args: from_virtual_helix_item: Description is_fwd: is this a forward strand? from_index: index of the Virtual Helix this xover is coming from nearby_idxs: to_vh_id_num: Virtual Helix number this Xover point might connect to prexoveritem_manager: Manager of the PreXoverItems """ super(QGraphicsRectItem, self).__init__(BASE_RECT, from_virtual_helix_item) self.adapter = PropertyWrapperObject(self) self._tick_marks = QGraphicsPathItem(self) self._tick_marks.setAcceptHoverEvents(True) self._bond_item = QGraphicsPathItem(self) self._bond_item.hide() self._label = PreXoverLabel(is_fwd, self) self._path = QGraphicsPathItem() self.setZValue(styles.ZPREXOVERITEM) self.setPen(getNoPen()) self.resetItem(from_virtual_helix_item, is_fwd, from_index, nearby_idxs, to_vh_id_num, prexoveritem_manager) self._getActiveTool = from_virtual_helix_item.viewroot( ).manager.activeToolGetter # end def def shutdown(self): """Summary """ self.setBrush(getNoBrush()) self.to_vh_id_num = None self.adapter.resetAnimations() self.setAcceptHoverEvents(False) self.hide() # end def def resetItem(self, from_virtual_helix_item: PathVirtualHelixItemT, is_fwd: bool, from_index: int, nearby_idxs: List[int], to_vh_id_num: int, prexoveritem_manager: PreXoverManagerT): """Update this pooled PreXoverItem with current info. Called by PreXoverManager. Args: from_virtual_helix_item: the associated vh_item is_fwd: True if associated with fwd strand, False if rev strand from_index: idx of associated vh nearby_idxs: to_vh_id_num: id_num of the other vh prexoveritem_manager: the manager """ # to_vh_item = from_virtual_helix_item.partItem().idToVirtualHelixItem(to_vh_id_num) self.setParentItem(from_virtual_helix_item) # self.setParentItem(to_vh_item) self.resetTransform() self._id_num = from_virtual_helix_item.idNum() self._model_part = from_virtual_helix_item.part() self.idx = from_index self.is_low = False self.is_high = False self.nearby_idxs = nearby_idxs self.is_fwd = is_fwd self.color = None self.is3p = None self.enter_pos = None self.exit_pos = None self.to_vh_id_num = to_vh_id_num self._label_txt = None self.prexoveritem_manager = prexoveritem_manager # todo: check here if xover present and disable result = self.setPathAppearance(from_virtual_helix_item) if result: self.setBrush(getNoBrush()) if is_fwd: self.setPos(from_index * BASE_WIDTH, -BASE_WIDTH - 0.1 * BASE_WIDTH) else: self.setPos(from_index * BASE_WIDTH, 2 * BASE_WIDTH) self.show() # label self._label_txt = lbt = None if to_vh_id_num is None else str( to_vh_id_num) self._label.resetItem(is_fwd, self.color) self.setLabel(text=lbt) # bond line bonditem = self._bond_item bonditem.setPen( getPenObj(self.color, styles.PREXOVER_STROKE_WIDTH, penstyle=Qt.DotLine)) bonditem.hide() # end def def setPathAppearance( self, from_virtual_helix_item: PathVirtualHelixItemT) -> bool: """Sets the PainterPath according to the index (low = Left, high = Right) and strand position (top = Up, bottom = Down). Args: from_virtual_helix_item: """ part = self._model_part idx = self.idx is_fwd = self.is_fwd id_num = self._id_num strand_type = StrandEnum.FWD if is_fwd else StrandEnum.REV # relative position info bpr = from_virtual_helix_item.getProperty('bases_per_repeat') self.is_low = is_low = idx + 1 in self.nearby_idxs or ( idx + 1) % bpr in self.nearby_idxs self.is_high = is_high = idx - 1 in self.nearby_idxs or ( idx - 1) % bpr in self.nearby_idxs # check strand for xover and color if part.hasStrandAtIdx(id_num, idx)[strand_type]: strand = part.getStrand(self.is_fwd, id_num, idx) if strand.hasXoverAt(idx): return False self.color = strand.getColor() if strand is not None else EMPTY_COL else: self.color = EMPTY_COL if is_low and is_high: path = (_FWD_DUAL_PATH, _REV_DUAL_PATH)[strand_type] raise NotImplementedError("Dual xovers not yet supported") elif is_low: path = (_FWD_LO_PATH, _REV_LO_PATH)[strand_type] self.is3p = True if is_fwd else False self.enter_pos = _FWD_LO_PTS[0][-1] if is_fwd else _REV_LO_PTS[0][ -1] self.exit_pos = _FWD_LO_PTS[0][-1] if is_fwd else _REV_LO_PTS[0][-1] elif is_high: path = (_FWD_HI_PATH, _REV_HI_PATH)[strand_type] self.is3p = False if is_fwd else True self.enter_pos = _FWD_HI_PTS[0][-1] if is_fwd else _REV_HI_PTS[0][ -1] self.exit_pos = _FWD_HI_PTS[0][-1] if is_fwd else _REV_HI_PTS[0][-1] else: # print("unpaired PreXoverItem at {}[{}]".format(self._id_num, self.idx), self.nearby_idxs) return False self._tick_marks.setPen( getPenObj(self.color, styles.PREXOVER_STROKE_WIDTH, capstyle=Qt.FlatCap, joinstyle=Qt.RoundJoin)) self._tick_marks.setPath(path) self._tick_marks.show() return True # end def ### ACCESSORS ### def color(self) -> str: """The PreXoverItem's color, derived from the associated strand's oligo. Returns: str: color in hex code """ return self.color def getInfo(self) -> ABInfoT: """ Returns: Tuple: (from_id_num, is_fwd, from_index, to_vh_id_num) """ return (self._id_num, self.is_fwd, self.idx, self.to_vh_id_num) def destroyItem(self): """Removes animation adapter, label, bond_item, and this item from scene. """ scene = self.scene() self.adapter.destroyItem() if scene: scene.removeItem(self._label) self._label = None scene.removeItem(self._bond_item) self._bond_item = None self.adapter.resetAnimations() self.adapter = None scene.removeItem(self) # end defS ### EVENT HANDLERS ### def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent): """Only ``if enableActive(True)`` is called hover and key events disabled by default Args: event: the hover event """ if self._getActiveTool().methodPrefix() != "selectTool": return self.setFocus(Qt.MouseFocusReason) self.prexoveritem_manager.updateModelActiveBaseInfo(self.getInfo()) self.setActiveHovered(True) status_string = "%d[%d]" % (self._id_num, self.idx) self.parentItem().window().statusBar().showMessage(status_string) return QGraphicsItem.hoverEnterEvent(self, event) # end def def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent): """Summary Args: event (QGraphicsSceneHoverEvent): the hover event """ self.prexoveritem_manager.updateModelActiveBaseInfo(None) self.setActiveHovered(False) self.clearFocus() self.parentItem().window().statusBar().showMessage("") return QGraphicsItem.hoverLeaveEvent(self, event) # end def def mousePressEvent(self, event: QGraphicsSceneMouseEvent): """TODO: NEED TO ADD FILTER FOR A CLICK ON THE 3' MOST END OF THE XOVER TO DISALLOW OR HANDLE DIFFERENTLY """ viewroot = self.parentItem().viewroot() current_filter_set = viewroot.selectionFilterSet() if (self._getActiveTool().methodPrefix() != "selectTool" or (self.FILTER_NAME not in current_filter_set)): return part = self._model_part is_fwd = self.is_fwd if self.is3p: strand5p = part.getStrand(is_fwd, self._id_num, self.idx) strand3p = part.getStrand(not is_fwd, self.to_vh_id_num, self.idx) else: strand5p = part.getStrand(not is_fwd, self.to_vh_id_num, self.idx) strand3p = part.getStrand(is_fwd, self._id_num, self.idx) if strand5p is None or strand3p is None: return # print(strand3p, strand5p) part.createXover(strand5p, self.idx, strand3p, self.idx) nkey = (self.to_vh_id_num, not is_fwd, self.idx) npxi = self.prexoveritem_manager.neighbor_prexover_items.get( nkey, None) if npxi: npxi.shutdown() self.shutdown() part.setActiveVirtualHelix(self._id_num, is_fwd, self.idx) # self.prexoveritem_manager.handlePreXoverClick(self) def keyPressEvent(self, event: QKeyEvent): """ Args: event: Description """ self.prexoveritem_manager.handlePreXoverKeyPress(event.key()) # end def ### PUBLIC SUPPORT METHODS ### def setLabel(self, text: str = None, outline: bool = False): """Summary Args: text: Default is ``None`` outline: Default is ``False`` """ if text: self._label.setTextAndStyle(text=text, outline=outline) self._label.show() else: self._label.hide() # end def def animate(self, item: QGraphicsItem, property_name: str, duration: int, start_value, end_value): """ Args: item: Description property_name: Description duration: Description start_value (QVariant): Description end_value (QVariant): Description """ b_name = property_name.encode('ascii') anim = item.adapter.getRef(property_name) if anim is None: anim = QPropertyAnimation(item.adapter, b_name) item.adapter.saveRef(property_name, anim) anim.setDuration(duration) anim.setStartValue(start_value) anim.setEndValue(end_value) anim.start() # end def def setActiveHovered(self, is_active: bool): """Rotate phosphate Triangle if `self.to_vh_id_num` is not `None` Args: is_active: whether or not the PreXoverItem is parented to the active VirtualHelixItem """ pass # end def def enableActive(self, is_active: bool, to_vh_id_num: int = None): """Call on PreXoverItems created on the active VirtualHelixItem Args: is_active: Description to_vh_id_num: Default is ``None`` """ if is_active: self.to_vh_id_num = to_vh_id_num self.setAcceptHoverEvents(True) if to_vh_id_num is None: self.setLabel(text=None) else: self.setLabel(text=str(to_vh_id_num)) else: self.setBrush(getNoBrush()) self.setAcceptHoverEvents(False) def activateNeighbor(self, active_prexoveritem: 'PreXoverItem', shortcut: str = None): """Draws a quad line starting from the item5p to the item3p. To be called with whatever the active_prexoveritem is for the parts `active_base`. Args: active_prexoveritem: Description shortcut: Default is None """ if self._getActiveTool().methodPrefix() != "selectTool": return if self.is3p and not active_prexoveritem.is3p: item5p = active_prexoveritem item3p = self elif not self.is3p and active_prexoveritem.is3p: item5p = self item3p = active_prexoveritem else: return same_parity = self.is_fwd == active_prexoveritem.is_fwd p1 = item5p._tick_marks.scenePos() + item5p.exit_pos p2 = item3p._tick_marks.scenePos() + item3p.exit_pos c1 = QPointF() # case 1: same parity if same_parity: dy = abs(p2.y() - p1.y()) c1.setX(p1.x() + _X_SCALE * dy) c1.setY(0.5 * (p1.y() + p2.y())) # case 2: different parity else: if item3p.is_fwd: c1.setX(p1.x() - _X_SCALE * abs(p2.y() - p1.y())) else: c1.setX(p1.x() + _X_SCALE * abs(p2.y() - p1.y())) c1.setY(0.5 * (p1.y() + p2.y())) pp = QPainterPath() pp.moveTo(self._tick_marks.mapFromScene(p1)) pp.quadTo(self._tick_marks.mapFromScene(c1), self._tick_marks.mapFromScene(p2)) # pp.cubicTo(c1, c2, self._tick_marks.mapFromScene(p2)) self._bond_item.setPath(pp) self._bond_item.show() # end def def deactivateNeighbor(self): """Summary """ if self.isVisible(): self._bond_item.hide() self.setLabel(text=self._label_txt)
def _load(self): # Get list of all map files for current zone map_file_name = MapData.get_zone_dict()[self.zone.strip().lower()] extensions = ['.txt', '_1.txt', '_2.txt', '_3.txt', '_4.txt', '_5.txt'] maps = [ os.path.join(MAP_FILES_LOCATION, m) for m in [(map_file_name + e) for e in extensions] if os.path.exists(os.path.join(MAP_FILES_LOCATION, m)) ] all_x, all_y, all_z = [], [], [] # TODO: Remove the references to raw # Create Lines and Points for map_file in maps: with open(map_file, 'r') as f: for line in f.readlines(): line_type = line.lower()[0:1] data = [value.strip() for value in line[1:].split(',')] if line_type == 'l': # line x1, y1, z1, x2, y2, z2 = list(map(float, data[0:6])) self.raw['lines'].append( MapLine(x1=x1, y1=y1, z1=z1, x2=x2, y2=y2, z2=z2, color=self.color_transform( QColor(int(data[6]), int(data[7]), int(data[8]))))) all_x.extend((x1, x2)) all_y.extend((y1, y2)) all_z.append(min(z1, z2)) # if abs(z1 - z2) < 2: # if z1 == z2: # all_z.extend((z1, z2)) elif line_type == 'p': # point x, y, z = map(float, data[0:3]) self.raw['poi'].append( MapPoint(x=x, y=y, z=z, size=int(data[6]), text=str(data[7]), color=self.color_transform( QColor(int(data[3]), int(data[4]), int(data[5]))))) # Create Grid Lines lowest_x, highest_x, lowest_y, highest_y, lowest_z, highest_z = min( all_x), max(all_x), min(all_y), max(all_y), min(all_z), max(all_z) left, right = int(math.floor(lowest_x / 1000) * 1000), int( math.ceil(highest_x / 1000) * 1000) top, bottom = int(math.floor(lowest_y / 1000) * 1000), int( math.ceil(highest_y / 1000) * 1000) for number in range(left, right + 1000, 1000): self.raw['grid'].append( MapLine(x1=number, x2=number, y1=top, y2=bottom, z1=0, z2=0, color=QColor(255, 255, 255, 25))) for number in range(top, bottom + 1000, 1000): self.raw['grid'].append( MapLine(y1=number, y2=number, x1=left, x2=right, z1=0, z2=0, color=QColor(255, 255, 255, 25))) self.grid = QGraphicsPathItem() line_path = QPainterPath() for line in self.raw['grid']: line_path.moveTo(line.x1, line.y1) line_path.lineTo(line.x2, line.y2) self.grid.setPath(line_path) self.grid.setPen( QPen(line.color, config.data['maps']['grid_line_width'])) self.grid.setZValue(0) # Get z levels counter = Counter(all_z) # bunch together zgroups based on peaks with floor being low point before rise z_groups = [] last_value = None first_run = True for z in sorted(counter.items(), key=lambda x: x[0]): if last_value is None: last_value = z continue if (abs(last_value[0] - z[0]) < 20) or z[1] < 8: last_value = (last_value[0], last_value[1] + z[1]) else: if first_run: first_run = False if last_value[1] < 40 or abs(last_value[0] - z[0]) < 18: last_value = z continue z_groups.append(last_value[0]) last_value = z # get last iteration if last_value[1] > 50: z_groups.append(last_value[0]) self._z_groups = z_groups # Create QGraphicsPathItem for lines seperately to retain colors temp_dict = {} for l in self.raw['lines']: lz = min(l.z1, l.z2) lz = self.get_closest_z_group(lz) if not temp_dict.get(lz, None): temp_dict[lz] = {'paths': {}} lc = l.color.getRgb() if not temp_dict[lz]['paths'].get(lc, None): path_item = QGraphicsPathItem() path_item.setPen( QPen(l.color, config.data['maps']['line_width'])) temp_dict[lz]['paths'][lc] = path_item path = temp_dict[lz]['paths'][lc].path() path.moveTo(l.x1, l.y1) path.lineTo(l.x2, l.y2) temp_dict[lz]['paths'][lc].setPath(path) # Group QGraphicsPathItems into QGraphicsItemGroups and update self for z in temp_dict.keys(): item_group = QGraphicsItemGroup() for (_, path) in temp_dict[z]['paths'].items(): item_group.addToGroup(path) self[z] = {'paths': None, 'poi': []} self[z]['paths'] = item_group # Create Points of Interest for p in self.raw['poi']: z = self.get_closest_z_group(p.z) self[z]['poi'].append(PointOfInterest(location=p)) self.geometry = MapGeometry( lowest_x=lowest_x, highest_x=highest_x, lowest_y=lowest_y, highest_y=highest_y, lowest_z=lowest_z, highest_z=highest_z, center_x=int(highest_x - (highest_x - lowest_x) / 2), center_y=int(highest_y - (highest_y - lowest_y) / 2), width=int(highest_x - lowest_x), height=int(highest_y - lowest_y), z_groups=z_groups)
class ImageViewer(QGraphicsView, QObject): points_selection_sgn = pyqtSignal(list) key_press_sgn = pyqtSignal(QtGui.QKeyEvent) def __init__(self, parent=None): super(ImageViewer, self).__init__(parent) self.setDragMode(QGraphicsView.ScrollHandDrag) self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setResizeAnchor(QGraphicsView.AnchorUnderMouse) self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) self._scene = ImageViewerScene(self) self.setScene(self._scene) self._image = None self._image_original = None self._pixmap = None self._img_contrast = 1.0 self._img_brightness = 50.0 self._img_gamma = 1.0 self._create_grid() self._channels = [] self._current_tool = SELECTION_TOOL.POINTER self._dataset = None # create grid lines pen_color = QColor(255, 255, 255, 255) pen = QPen(pen_color) pen.setWidth(2) pen.setStyle(QtCore.Qt.DotLine) self.vline = QGraphicsLineItem() self.vline.setVisible(False) self.vline.setPen(pen) self.hline = QGraphicsLineItem() self.hline.setVisible(False) self.hline.setPen(pen) self._scene.addItem(self.vline) self._scene.addItem(self.hline) self._current_label = None # rectangle selection tool self._rectangle_tool_origin = QPoint() self._rectangle_tool_picker = QRubberBand(QRubberBand.Rectangle, self) # polygon selection tool app = QApplication.instance() color = app.palette().color(QPalette.Highlight) self._polygon_guide_line_pen = QPen(color) self._polygon_guide_line_pen.setWidth(3) self._polygon_guide_line_pen.setStyle(QtCore.Qt.DotLine) self._polygon_guide_line = QGraphicsLineItem() self._polygon_guide_line.setVisible(False) self._polygon_guide_line.setPen(self._polygon_guide_line_pen) self._scene.addItem(self._polygon_guide_line) self._current_polygon = None # circle self._current_ellipse = None # free selection tool self._current_free_path = None self._is_drawing = False self._last_point_drawn = QPoint() self._last_click_point = None self._free_Path_pen = QPen(color) self._free_Path_pen.setWidth(10) self._extreme_points = Queue(maxsize=4) @property def current_label(self): return self._current_label @current_label.setter def current_label(self, value): self._current_label = value if self._current_label: color = QColor(self._current_label.color) self._free_Path_pen.setColor(color) self._polygon_guide_line_pen.setColor(color) self._polygon_guide_line.setPen(self._polygon_guide_line_pen) @property def dataset(self): return self._dataset @dataset.setter def dataset(self, value): self._dataset = value @property def img_contrast(self): return self._img_contrast @img_contrast.setter def img_contrast(self, value): self._img_contrast = value @property def img_gamma(self): return self._img_gamma @img_gamma.setter def img_gamma(self, value): self._img_gamma = value @property def img_brightness(self): return self._img_brightness @img_brightness.setter def img_brightness(self, value): self._img_brightness = value @property def image(self): return self._image @image.setter def image(self, value): self._image = value self._image_original = value.copy() self.update_viewer() @property def pixmap(self) -> ImagePixmap: return self._pixmap @gui_exception def update_viewer(self, fit_image=True): rgb = cv2.cvtColor(self._image, cv2.COLOR_BGR2RGB) rgb = ImageUtilities.adjust_image(rgb, self._img_contrast, self._img_brightness) rgb = ImageUtilities.adjust_gamma(rgb, self._img_gamma) pil_image = Image.fromarray(rgb) qppixmap_image = pil_image.toqpixmap() x, y = -qppixmap_image.width() / 2, -qppixmap_image.height() / 2 if self._pixmap: self._pixmap.resetTransform() self._pixmap.setPixmap(qppixmap_image) self._pixmap.setOffset(x, y) else: self._pixmap = ImagePixmap() self._pixmap.setPixmap(qppixmap_image) self._pixmap.setOffset(x, y) self._scene.addItem(self._pixmap) self._pixmap.signals.hoverEnterEventSgn.connect( self.pixmap_hoverEnterEvent_slot) self._pixmap.signals.hoverLeaveEventSgn.connect( self.pixmap_hoverLeaveEvent_slot) self._pixmap.signals.hoverMoveEventSgn.connect( self.pixmap_hoverMoveEvent_slot) self._hide_guide_lines() if fit_image: self.fit_to_window() @gui_exception def reset_viewer(self): self._img_contrast = 1.0 self._img_brightness = 50.0 self._img_gamma = 1.0 self._image = self._image_original.copy() @gui_exception def equalize_histogram(self): self._image = ImageUtilities.histogram_equalization(self._image) @gui_exception def correct_lightness(self): self._image = ImageUtilities.correct_lightness(self._image) def clusterize(self, k): self._image = ImageUtilities.kmeans(self._image.copy(), k) @property def current_tool(self): return self._current_tool @current_tool.setter def current_tool(self, value): self._polygon_guide_line.hide() self._current_polygon = None self._current_free_path = None self._current_ellipse = None self._is_drawing = value == SELECTION_TOOL.FREE self._current_tool = value self.clear_extreme_points() if value == SELECTION_TOOL.POINTER: self.enable_items(True) else: self.enable_items(False) def fit_to_window(self): if not self._pixmap or not self._pixmap.pixmap(): return self.resetTransform() self.setTransform(QtGui.QTransform()) self.fitInView(self._pixmap, QtCore.Qt.KeepAspectRatio) def _create_grid(self, gridSize=15): app: QApplication = QApplication.instance() curr_theme = "dark" if app: curr_theme = app.property("theme") if curr_theme == "light": color1 = QtGui.QColor("white") color2 = QtGui.QColor(237, 237, 237) else: color1 = QtGui.QColor(20, 20, 20) color2 = QtGui.QColor(0, 0, 0) backgroundPixmap = QtGui.QPixmap(gridSize * 2, gridSize * 2) backgroundPixmap.fill(color1) painter = QtGui.QPainter(backgroundPixmap) painter.fillRect(0, 0, gridSize, gridSize, color2) painter.fillRect(gridSize, gridSize, gridSize, gridSize, color2) painter.end() self._scene.setBackgroundBrush(QtGui.QBrush(backgroundPixmap)) def wheelEvent(self, event: QWheelEvent): adj = (event.angleDelta().y() / 120) * 0.1 self.scale(1 + adj, 1 + adj) @gui_exception def keyPressEvent(self, event: QKeyEvent): if event.key() == QtCore.Qt.Key_Space: image_rect: QRectF = self._pixmap.sceneBoundingRect() if self.current_tool == SELECTION_TOOL.POLYGON and self._current_polygon: points = self._current_polygon.points self._polygon_guide_line.hide() self.setDragMode(QGraphicsView.ScrollHandDrag) if len(points) <= 2: self._current_polygon.delete_item() self.current_tool = SELECTION_TOOL.POINTER elif self.current_tool == SELECTION_TOOL.EXTREME_POINTS and \ self._extreme_points.full(): points = [] image_offset = QPointF(image_rect.width() / 2, image_rect.height() / 2) for pt in self._extreme_points.queue: pt: EditablePolygonPoint center = pt.sceneBoundingRect().center() x = math.floor(center.x() + image_offset.x()) y = math.floor(center.y() + image_offset.y()) points.append([x, y]) self.points_selection_sgn.emit(points) self.current_tool = SELECTION_TOOL.POINTER else: event.ignore() # guide lines events def _show_guide_lines(self): if self.hline and self.vline: self.hline.show() self.vline.show() def _hide_guide_lines(self): if self.hline and self.vline: self.hline.hide() self.vline.hide() def _update_guide_lines(self, x, y): bbox: QRect = self._pixmap.boundingRect() offset = QPointF(bbox.width() / 2, bbox.height() / 2) self.vline.setLine(x, -offset.y(), x, bbox.height() - offset.y()) self.vline.setZValue(1) self.hline.setLine(-offset.x(), y, bbox.width() - offset.x(), y) self.hline.setZValue(1) def pixmap_hoverMoveEvent_slot(self, evt: QGraphicsSceneHoverEvent, x, y): self._update_guide_lines(x, y) def pixmap_hoverEnterEvent_slot(self): self._show_guide_lines() def pixmap_hoverLeaveEvent_slot(self): self._hide_guide_lines() def delete_polygon_slot(self, polygon: EditablePolygon): self._current_polygon = None self.current_tool = SELECTION_TOOL.POINTER self._polygon_guide_line.hide() @gui_exception def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None: image_rect: QRectF = self._pixmap.boundingRect() mouse_pos = self.mapToScene(evt.pos()) if evt.buttons() == QtCore.Qt.LeftButton: if self.current_tool == SELECTION_TOOL.BOX: # create rectangle self.setDragMode(QGraphicsView.NoDrag) self._rectangle_tool_origin = evt.pos() geometry = QRect(self._rectangle_tool_origin, QSize()) self._rectangle_tool_picker.setGeometry(geometry) self._rectangle_tool_picker.show() elif self.current_tool == SELECTION_TOOL.POLYGON: if image_rect.contains(mouse_pos): if self._current_polygon is None: self._current_polygon = EditablePolygon() self._current_polygon.label = self._current_label self._current_polygon.tag = self._dataset self._current_polygon.signals.deleted.connect( self.delete_polygon_slot) self._scene.addItem(self._current_polygon) self._current_polygon.addPoint(mouse_pos) else: self._current_polygon.addPoint(mouse_pos) elif self.current_tool == SELECTION_TOOL.ELLIPSE: if image_rect.contains(mouse_pos): self.setDragMode(QGraphicsView.NoDrag) ellipse_rec = QtCore.QRectF(mouse_pos.x(), mouse_pos.y(), 0, 0) self._current_ellipse = EditableEllipse() self._current_ellipse.tag = self.dataset self._current_ellipse.label = self._current_label self._current_ellipse.setRect(ellipse_rec) self._scene.addItem(self._current_ellipse) elif self.current_tool == SELECTION_TOOL.FREE: # consider only the points into the image if image_rect.contains(mouse_pos): self.setDragMode(QGraphicsView.NoDrag) self._last_point_drawn = mouse_pos self._current_free_path = QGraphicsPathItem() self._current_free_path.setOpacity(0.6) self._current_free_path.setPen(self._free_Path_pen) painter = QPainterPath() painter.moveTo(self._last_point_drawn) self._current_free_path.setPath(painter) self._scene.addItem(self._current_free_path) elif self.current_tool == SELECTION_TOOL.EXTREME_POINTS: if image_rect.contains(mouse_pos): if not self._extreme_points.full(): def delete_point(idx): del self._extreme_points.queue[idx] idx = self._extreme_points.qsize() editable_pt = EditablePolygonPoint(idx) editable_pt.signals.deleted.connect(delete_point) editable_pt.setPos(mouse_pos) self._scene.addItem(editable_pt) self._extreme_points.put(editable_pt) else: self.setDragMode(QGraphicsView.ScrollHandDrag) super(ImageViewer, self).mousePressEvent(evt) @gui_exception def mouseMoveEvent(self, evt: QtGui.QMouseEvent) -> None: mouse_pos = self.mapToScene(evt.pos()) image_rect: QRectF = self._pixmap.boundingRect() if self.current_tool == SELECTION_TOOL.BOX: if not self._rectangle_tool_origin.isNull(): geometry = QRect(self._rectangle_tool_origin, evt.pos()).normalized() self._rectangle_tool_picker.setGeometry(geometry) elif self.current_tool == SELECTION_TOOL.POLYGON: if self._current_polygon and image_rect.contains(mouse_pos): if self._current_polygon.count > 0: last_point: QPointF = self._current_polygon.last_point self._polygon_guide_line.setZValue(1) self._polygon_guide_line.show() mouse_pos = self.mapToScene(evt.pos()) self._polygon_guide_line.setLine(last_point.x(), last_point.y(), mouse_pos.x(), mouse_pos.y()) else: self._polygon_guide_line.hide() elif self.current_tool == SELECTION_TOOL.ELLIPSE: if self._current_ellipse and image_rect.contains(mouse_pos): ellipse_rect = self._current_ellipse.rect() ellipse_pos = QPointF(ellipse_rect.x(), ellipse_rect.y()) distance = math.hypot(mouse_pos.x() - ellipse_pos.x(), mouse_pos.y() - ellipse_pos.y()) ellipse_rect.setWidth(distance) ellipse_rect.setHeight(distance) self._current_ellipse.setRect(ellipse_rect) elif self.current_tool == SELECTION_TOOL.FREE and evt.buttons( ) and QtCore.Qt.LeftButton: if self._current_free_path and image_rect.contains(mouse_pos): painter: QPainterPath = self._current_free_path.path() self._last_point_drawn = self.mapToScene(evt.pos()) painter.lineTo(self._last_point_drawn) self._current_free_path.setPath(painter) super(ImageViewer, self).mouseMoveEvent(evt) @gui_exception def mouseReleaseEvent(self, evt: QtGui.QMouseEvent) -> None: image_rect: QRectF = self._pixmap.boundingRect() if self.current_tool == SELECTION_TOOL.BOX: roi: QRect = self._rectangle_tool_picker.geometry() roi: QRectF = self.mapToScene(roi).boundingRect() self._rectangle_tool_picker.hide() if image_rect == roi.united(image_rect): rect = EditableBox(roi) rect.label = self.current_label rect.tag = self._dataset self._scene.addItem(rect) self.current_tool = SELECTION_TOOL.POINTER self.setDragMode(QGraphicsView.ScrollHandDrag) elif self.current_tool == SELECTION_TOOL.ELLIPSE and self._current_ellipse: roi: QRect = self._current_ellipse.boundingRect() if image_rect == roi.united(image_rect): self.current_tool = SELECTION_TOOL.POINTER self.setDragMode(QGraphicsView.ScrollHandDrag) else: self._current_ellipse.delete_item() elif self.current_tool == SELECTION_TOOL.FREE and self._current_free_path: # create polygon self._current_free_path: QGraphicsPathItem path_rect = self._current_free_path.boundingRect() if image_rect == path_rect.united(image_rect): path = self._current_free_path.path() path_polygon = EditablePolygon() path_polygon.tag = self.dataset path_polygon.label = self.current_label self._scene.addItem(path_polygon) for i in range(0, path.elementCount(), 10): x, y = path.elementAt(i).x, path.elementAt(i).y path_polygon.addPoint(QPointF(x, y)) self._scene.removeItem(self._current_free_path) self.current_tool = SELECTION_TOOL.POINTER self.setDragMode(QGraphicsView.ScrollHandDrag) super(ImageViewer, self).mouseReleaseEvent(evt) def remove_annotations(self): for item in self._scene.items(): if isinstance(item, EditableItem): item.delete_item() def remove_annotations_by_label(self, label_name): for item in self._scene.items(): if isinstance(item, EditableItem): if item.label and item.label.name == label_name: item.delete_item() def enable_items(self, value): for item in self._scene.items(): if isinstance(item, EditableItem): item.setEnabled(value) def clear_extreme_points(self): if self._extreme_points.qsize() > 0: for pt in self._extreme_points.queue: self._scene.removeItem(pt) self._extreme_points.queue.clear()
def createGraphics(self): """ Create the graphical representation of the FMU's inputs and outputs """ def variableColor(variable): if variable.type == 'Real': return QColor.fromRgb(0, 0, 127) elif variable.type in ['Integer', 'Enumeration']: return QColor.fromRgb(255, 127, 0) elif variable.type == 'Boolean': return QColor.fromRgb(255, 0, 255) elif variable.type == 'String': return QColor.fromRgb(0, 128, 0) else: return QColor.fromRgb(0, 0, 0) inputVariables = [] outputVariables = [] maxInputLabelWidth = 0 maxOutputLabelWidth = 0 textItem = QGraphicsTextItem() fontMetrics = QFontMetricsF(textItem.font()) for variable in self.modelDescription.modelVariables: if variable.causality == 'input': inputVariables.append(variable) elif variable.causality == 'output': outputVariables.append(variable) for variable in inputVariables: maxInputLabelWidth = max(maxInputLabelWidth, fontMetrics.width(variable.name)) for variable in outputVariables: maxOutputLabelWidth = max(maxOutputLabelWidth, fontMetrics.width(variable.name)) from math import floor scene = QGraphicsScene() self.ui.graphicsView.setScene(scene) group = QGraphicsItemGroup() scene.addItem(group) group.setPos(200.5, -50.5) lh = 15 # line height w = max(150., maxInputLabelWidth + maxOutputLabelWidth + 20) h = max(50., 10 + lh * max(len(inputVariables), len(outputVariables))) block = QGraphicsRectItem(0, 0, w, h, group) block.setPen(QColor.fromRgb(0, 0, 255)) pen = QPen() pen.setWidthF(1) font = QFont() font.setPixelSize(10) # inputs y = floor((h - len(inputVariables) * lh) / 2 - 2) for variable in inputVariables: text = QGraphicsTextItem(variable.name, group) text.setDefaultTextColor(QColor.fromRgb(0, 0, 255)) text.setFont(font) text.setX(3) text.setY(y) polygon = QPolygonF([ QPointF(-13.5, y + 4), QPointF(1, y + 11), QPointF(-13.5, y + 18) ]) path = QPainterPath() path.addPolygon(polygon) path.closeSubpath() contour = QGraphicsPathItem(path, group) contour.setPen(QPen(Qt.NoPen)) contour.setBrush(variableColor(variable)) y += lh # outputs y = floor((h - len(outputVariables) * lh) / 2 - 2) for variable in outputVariables: text = QGraphicsTextItem(variable.name, group) text.setDefaultTextColor(QColor.fromRgb(0, 0, 255)) text.setFont(font) text.setX(w - 3 - text.boundingRect().width()) text.setY(y) polygon = QPolygonF([ QPointF(w, y + 0 + 7.5), QPointF(w + 7, y + 3.5 + 7.5), QPointF(w, y + 7 + 7.5) ]) path = QPainterPath() path.addPolygon(polygon) path.closeSubpath() contour = QGraphicsPathItem(path, group) pen = QPen() pen.setColor(variableColor(variable)) pen.setJoinStyle(Qt.MiterJoin) contour.setPen(pen) y += lh
class MapData(dict): def __init__(self, zone=None): super().__init__() self.zone = zone self.raw = {"lines": [], "poi": [], "grid": []} self.geometry = None # MapGeometry self.players = {} self.spawns = [] self.way_point = None self.grid = None if self.zone is not None: self._load() def _load(self): # Get list of all map files for current zone map_file_name = MapData.get_zone_dict()[self.zone.strip().lower()] extensions = [".txt", "_1.txt", "_2.txt", "_3.txt", "_4.txt", "_5.txt"] maps = [ os.path.join(MAP_FILES_LOCATION, m) for m in [(map_file_name + e) for e in extensions] if os.path.exists(os.path.join(MAP_FILES_LOCATION, m)) ] all_x, all_y, all_z = [], [], [] # TODO: Remove the references to raw # Create Lines and Points for map_file in maps: with open(map_file, "r") as f: for line in f.readlines(): line_type = line.lower()[0:1] data = [value.strip() for value in line[1:].split(",")] if line_type == "l": # line x1, y1, z1, x2, y2, z2 = list(map(float, data[0:6])) self.raw["lines"].append( MapLine( x1=x1, y1=y1, z1=z1, x2=x2, y2=y2, z2=z2, color=self.color_transform( QColor(int(data[6]), int(data[7]), int(data[8]))), )) all_x.extend((x1, x2)) all_y.extend((y1, y2)) all_z.append(min(z1, z2)) # if abs(z1 - z2) < 2: # if z1 == z2: # all_z.extend((z1, z2)) elif line_type == "p": # point x, y, z = map(float, data[0:3]) self.raw["poi"].append( MapPoint( x=x, y=y, z=z, size=int(data[6]), text=str(data[7]), color=self.color_transform( QColor(int(data[3]), int(data[4]), int(data[5]))), )) # Create Grid Lines lowest_x, highest_x, lowest_y, highest_y, lowest_z, highest_z = ( min(all_x), max(all_x), min(all_y), max(all_y), min(all_z), max(all_z), ) left, right = ( int(math.floor(lowest_x / 1000) * 1000), int(math.ceil(highest_x / 1000) * 1000), ) top, bottom = ( int(math.floor(lowest_y / 1000) * 1000), int(math.ceil(highest_y / 1000) * 1000), ) for number in range(left, right + 1000, 1000): self.raw["grid"].append( MapLine( x1=number, x2=number, y1=top, y2=bottom, z1=0, z2=0, color=QColor(255, 255, 255, 25), )) for number in range(top, bottom + 1000, 1000): self.raw["grid"].append( MapLine( y1=number, y2=number, x1=left, x2=right, z1=0, z2=0, color=QColor(255, 255, 255, 25), )) self.grid = QGraphicsPathItem() line_path = QPainterPath() for line in self.raw["grid"]: line_path.moveTo(line.x1, line.y1) line_path.lineTo(line.x2, line.y2) self.grid.setPath(line_path) self.grid.setPen(QPen(line.color, profile.maps.grid_line_width)) self.grid.setZValue(0) # Get z levels counter = Counter(all_z) # bunch together zgroups based on peaks with floor being low point before rise z_groups = [] last_value = None first_run = True for z in sorted(counter.items(), key=lambda x: x[0]): if last_value is None: last_value = z continue if (abs(last_value[0] - z[0]) < 20) or z[1] < 8: last_value = (last_value[0], last_value[1] + z[1]) else: if first_run: first_run = False if last_value[1] < 40 or abs(last_value[0] - z[0]) < 18: last_value = z continue z_groups.append(last_value[0]) last_value = z # get last iteration if last_value[1] > 50: z_groups.append(last_value[0]) self._z_groups = z_groups # Create QGraphicsPathItem for lines seperately to retain colors temp_dict = {} for line in self.raw["lines"]: lz = min(line.z1, line.z2) lz = self.get_closest_z_group(lz) if not temp_dict.get(lz, None): temp_dict[lz] = {"paths": {}} lc = line.color.getRgb() if not temp_dict[lz]["paths"].get(lc, None): path_item = QGraphicsPathItem() path_item.setPen(QPen(line.color, profile.maps.line_width)) temp_dict[lz]["paths"][lc] = path_item path = temp_dict[lz]["paths"][lc].path() path.moveTo(line.x1, line.y1) path.lineTo(line.x2, line.y2) temp_dict[lz]["paths"][lc].setPath(path) # Group QGraphicsPathItems into QGraphicsItemGroups and update self for z in temp_dict.keys(): item_group = QGraphicsItemGroup() for (_, path) in temp_dict[z]["paths"].items(): item_group.addToGroup(path) self[z] = {"paths": None, "poi": []} self[z]["paths"] = item_group # Create Points of Interest for p in self.raw["poi"]: z = self.get_closest_z_group(p.z) self[z]["poi"].append(PointOfInterest(location=p)) self.geometry = MapGeometry( lowest_x=lowest_x, highest_x=highest_x, lowest_y=lowest_y, highest_y=highest_y, lowest_z=lowest_z, highest_z=highest_z, center_x=int(highest_x - (highest_x - lowest_x) / 2), center_y=int(highest_y - (highest_y - lowest_y) / 2), width=int(highest_x - lowest_x), height=int(highest_y - lowest_y), z_groups=z_groups, ) def get_closest_z_group(self, z): closest = min(self._z_groups, key=lambda x: abs(x - z)) if z < closest: lower_index = self._z_groups.index(closest) - 1 if lower_index > -1: closest = self._z_groups[lower_index] return closest @staticmethod def get_zone_dict(): # Load Map Pairs from map_keys.ini zone_dict = {} with open(MAP_KEY_FILE, "r") as file: for line in file.readlines(): values = line.split("=") zone_dict[values[0].strip()] = values[1].strip() return zone_dict @staticmethod def color_transform(color): lightness = color.lightness() if lightness == 0: return QColor(255, 255, 255) elif color.red == color.green == color.blue: return QColor(255, 255, 255) elif lightness < 150: return color.lighter(150) return color
class PreXoverItem(QGraphicsRectItem): """A PreXoverItem exists between a single 'from' VirtualHelixItem index and zero or more 'to' VirtualHelixItem Indices Attributes: adapter (:obj:`PropertyWrapperObject`): Description idx (int): the base index within the virtual helix is_fwd (bool): is this a forward strand? prexoveritem_manager (:obj:`PreXoverManager`): Manager of the PreXoverItems to_vh_id_num (int): Virtual Helix number this Xover point might connect to """ FILTER_NAME = "xover" def __init__(self, from_virtual_helix_item: PathVirtualHelixItemT, is_fwd: bool, from_index: int, nearby_idxs: List[int], to_vh_id_num: int, prexoveritem_manager: PreXoverManagerT): """Summary Args: from_virtual_helix_item: Description is_fwd: is this a forward strand? from_index: index of the Virtual Helix this xover is coming from nearby_idxs: to_vh_id_num: Virtual Helix number this Xover point might connect to prexoveritem_manager: Manager of the PreXoverItems """ super(QGraphicsRectItem, self).__init__(BASE_RECT, from_virtual_helix_item) self.adapter = PropertyWrapperObject(self) self._tick_marks = QGraphicsPathItem(self) self._tick_marks.setAcceptHoverEvents(True) self._bond_item = QGraphicsPathItem(self) self._bond_item.hide() self._label = PreXoverLabel(is_fwd, self) self._path = QGraphicsPathItem() self.setZValue(styles.ZPREXOVERITEM) self.setPen(getNoPen()) self.resetItem(from_virtual_helix_item, is_fwd, from_index, nearby_idxs, to_vh_id_num, prexoveritem_manager) self._getActiveTool = from_virtual_helix_item.viewroot().manager.activeToolGetter # end def def shutdown(self): """Summary """ self.setBrush(getNoBrush()) self.to_vh_id_num = None self.adapter.resetAnimations() self.setAcceptHoverEvents(False) self.hide() # end def def resetItem(self, from_virtual_helix_item: PathVirtualHelixItemT, is_fwd: bool, from_index: int, nearby_idxs: List[int], to_vh_id_num: int, prexoveritem_manager: PreXoverManagerT): """Update this pooled PreXoverItem with current info. Called by PreXoverManager. Args: from_virtual_helix_item: the associated vh_item is_fwd: True if associated with fwd strand, False if rev strand from_index: idx of associated vh nearby_idxs: to_vh_id_num: id_num of the other vh prexoveritem_manager: the manager """ # to_vh_item = from_virtual_helix_item.partItem().idToVirtualHelixItem(to_vh_id_num) self.setParentItem(from_virtual_helix_item) # self.setParentItem(to_vh_item) self.resetTransform() self._id_num = from_virtual_helix_item.idNum() self._model_part = from_virtual_helix_item.part() self.idx = from_index self.is_low = False self.is_high = False self.nearby_idxs = nearby_idxs self.is_fwd = is_fwd self.color = None self.is3p = None self.enter_pos = None self.exit_pos = None self.to_vh_id_num = to_vh_id_num self._label_txt = None self.prexoveritem_manager = prexoveritem_manager # todo: check here if xover present and disable result = self.setPathAppearance(from_virtual_helix_item) if result: self.setBrush(getNoBrush()) if is_fwd: self.setPos(from_index*BASE_WIDTH, -BASE_WIDTH - 0.1*BASE_WIDTH) else: self.setPos(from_index*BASE_WIDTH, 2*BASE_WIDTH) self.show() # label self._label_txt = lbt = None if to_vh_id_num is None else str(to_vh_id_num) self._label.resetItem(is_fwd, self.color) self.setLabel(text=lbt) # bond line bonditem = self._bond_item bonditem.setPen(getPenObj( self.color, styles.PREXOVER_STROKE_WIDTH, penstyle=Qt.DotLine)) bonditem.hide() # end def def setPathAppearance(self, from_virtual_helix_item: PathVirtualHelixItemT) -> bool: """Sets the PainterPath according to the index (low = Left, high = Right) and strand position (top = Up, bottom = Down). Args: from_virtual_helix_item: """ part = self._model_part idx = self.idx is_fwd = self.is_fwd id_num = self._id_num strand_type = StrandEnum.FWD if is_fwd else StrandEnum.REV # relative position info bpr = from_virtual_helix_item.getProperty('bases_per_repeat') self.is_low = is_low = idx+1 in self.nearby_idxs or (idx+1) % bpr in self.nearby_idxs self.is_high = is_high = idx-1 in self.nearby_idxs or (idx-1) % bpr in self.nearby_idxs # check strand for xover and color if part.hasStrandAtIdx(id_num, idx)[strand_type]: strand = part.getStrand(self.is_fwd, id_num, idx) if strand.hasXoverAt(idx): return False self.color = strand.getColor() if strand is not None else EMPTY_COL else: self.color = EMPTY_COL if is_low and is_high: path = (_FWD_DUAL_PATH, _REV_DUAL_PATH)[strand_type] raise NotImplementedError("Dual xovers not yet supported") elif is_low: path = (_FWD_LO_PATH, _REV_LO_PATH)[strand_type] self.is3p = True if is_fwd else False self.enter_pos = _FWD_LO_PTS[0][-1] if is_fwd else _REV_LO_PTS[0][-1] self.exit_pos = _FWD_LO_PTS[0][-1] if is_fwd else _REV_LO_PTS[0][-1] elif is_high: path = (_FWD_HI_PATH, _REV_HI_PATH)[strand_type] self.is3p = False if is_fwd else True self.enter_pos = _FWD_HI_PTS[0][-1] if is_fwd else _REV_HI_PTS[0][-1] self.exit_pos = _FWD_HI_PTS[0][-1] if is_fwd else _REV_HI_PTS[0][-1] else: # print("unpaired PreXoverItem at {}[{}]".format(self._id_num, self.idx), self.nearby_idxs) return False self._tick_marks.setPen(getPenObj( self.color, styles.PREXOVER_STROKE_WIDTH, capstyle=Qt.FlatCap, joinstyle=Qt.RoundJoin)) self._tick_marks.setPath(path) self._tick_marks.show() return True # end def ### ACCESSORS ### def color(self) -> str: """The PreXoverItem's color, derived from the associated strand's oligo. Returns: str: color in hex code """ return self.color def getInfo(self) -> ABInfoT: """ Returns: Tuple: (from_id_num, is_fwd, from_index, to_vh_id_num) """ return (self._id_num, self.is_fwd, self.idx, self.to_vh_id_num) def destroyItem(self): """Removes animation adapter, label, bond_item, and this item from scene. """ scene = self.scene() self.adapter.destroyItem() if scene: scene.removeItem(self._label) self._label = None scene.removeItem(self._bond_item) self._bond_item = None self.adapter.resetAnimations() self.adapter = None scene.removeItem(self) # end defS ### EVENT HANDLERS ### def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent): """Only ``if enableActive(True)`` is called hover and key events disabled by default Args: event: the hover event """ if self._getActiveTool().methodPrefix() != "selectTool": return self.setFocus(Qt.MouseFocusReason) self.prexoveritem_manager.updateModelActiveBaseInfo(self.getInfo()) self.setActiveHovered(True) status_string = "%d[%d]" % (self._id_num, self.idx) self.parentItem().window().statusBar().showMessage(status_string) return QGraphicsItem.hoverEnterEvent(self, event) # end def def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent): """Summary Args: event (QGraphicsSceneHoverEvent): the hover event """ self.prexoveritem_manager.updateModelActiveBaseInfo(None) self.setActiveHovered(False) self.clearFocus() self.parentItem().window().statusBar().showMessage("") return QGraphicsItem.hoverLeaveEvent(self, event) # end def def mousePressEvent(self, event: QGraphicsSceneMouseEvent): """TODO: NEED TO ADD FILTER FOR A CLICK ON THE 3' MOST END OF THE XOVER TO DISALLOW OR HANDLE DIFFERENTLY """ viewroot = self.parentItem().viewroot() current_filter_set = viewroot.selectionFilterSet() if (self._getActiveTool().methodPrefix() != "selectTool" or (self.FILTER_NAME not in current_filter_set) ): return part = self._model_part is_fwd = self.is_fwd if self.is3p: strand5p = part.getStrand(is_fwd, self._id_num, self.idx) strand3p = part.getStrand(not is_fwd, self.to_vh_id_num, self.idx) else: strand5p = part.getStrand(not is_fwd, self.to_vh_id_num, self.idx) strand3p = part.getStrand(is_fwd, self._id_num, self.idx) if strand5p is None or strand3p is None: return # print(strand3p, strand5p) part.createXover(strand5p, self.idx, strand3p, self.idx) nkey = (self.to_vh_id_num, not is_fwd, self.idx) npxi = self.prexoveritem_manager.neighbor_prexover_items.get(nkey, None) if npxi: npxi.shutdown() self.shutdown() part.setActiveVirtualHelix(self._id_num, is_fwd, self.idx) # self.prexoveritem_manager.handlePreXoverClick(self) def keyPressEvent(self, event: QKeyEvent): """ Args: event: Description """ self.prexoveritem_manager.handlePreXoverKeyPress(event.key()) # end def ### PUBLIC SUPPORT METHODS ### def setLabel(self, text: str = None, outline: bool = False): """Summary Args: text: Default is ``None`` outline: Default is ``False`` """ if text: self._label.setTextAndStyle(text=text, outline=outline) self._label.show() else: self._label.hide() # end def def animate(self, item: QGraphicsItem, property_name: str, duration: int, start_value, end_value): """ Args: item: Description property_name: Description duration: Description start_value (QVariant): Description end_value (QVariant): Description """ b_name = property_name.encode('ascii') anim = item.adapter.getRef(property_name) if anim is None: anim = QPropertyAnimation(item.adapter, b_name) item.adapter.saveRef(property_name, anim) anim.setDuration(duration) anim.setStartValue(start_value) anim.setEndValue(end_value) anim.start() # end def def setActiveHovered(self, is_active: bool): """Rotate phosphate Triangle if `self.to_vh_id_num` is not `None` Args: is_active: whether or not the PreXoverItem is parented to the active VirtualHelixItem """ pass # end def def enableActive(self, is_active: bool, to_vh_id_num: int = None): """Call on PreXoverItems created on the active VirtualHelixItem Args: is_active: Description to_vh_id_num: Default is ``None`` """ if is_active: self.to_vh_id_num = to_vh_id_num self.setAcceptHoverEvents(True) if to_vh_id_num is None: self.setLabel(text=None) else: self.setLabel(text=str(to_vh_id_num)) else: self.setBrush(getNoBrush()) self.setAcceptHoverEvents(False) def activateNeighbor(self, active_prexoveritem: 'PreXoverItem', shortcut: str = None): """Draws a quad line starting from the item5p to the item3p. To be called with whatever the active_prexoveritem is for the parts `active_base`. Args: active_prexoveritem: Description shortcut: Default is None """ if self._getActiveTool().methodPrefix() != "selectTool": return if self.is3p and not active_prexoveritem.is3p: item5p = active_prexoveritem item3p = self elif not self.is3p and active_prexoveritem.is3p: item5p = self item3p = active_prexoveritem else: return same_parity = self.is_fwd == active_prexoveritem.is_fwd p1 = item5p._tick_marks.scenePos() + item5p.exit_pos p2 = item3p._tick_marks.scenePos() + item3p.exit_pos c1 = QPointF() # case 1: same parity if same_parity: dy = abs(p2.y() - p1.y()) c1.setX(p1.x() + _X_SCALE * dy) c1.setY(0.5 * (p1.y() + p2.y())) # case 2: different parity else: if item3p.is_fwd: c1.setX(p1.x() - _X_SCALE * abs(p2.y() - p1.y())) else: c1.setX(p1.x() + _X_SCALE * abs(p2.y() - p1.y())) c1.setY(0.5 * (p1.y() + p2.y())) pp = QPainterPath() pp.moveTo(self._tick_marks.mapFromScene(p1)) pp.quadTo(self._tick_marks.mapFromScene(c1), self._tick_marks.mapFromScene(p2)) # pp.cubicTo(c1, c2, self._tick_marks.mapFromScene(p2)) self._bond_item.setPath(pp) self._bond_item.show() # end def def deactivateNeighbor(self): """Summary """ if self.isVisible(): self._bond_item.hide() self.setLabel(text=self._label_txt)
class PathMaker(QWidget): def __init__(self, parent): super().__init__() self.canvas = parent self.scene = parent.scene self.view = parent.view self.dots = parent.dots self.chooser = None ## placeholder for popup_widget self.initThis() self.sliders = self.dots.sliderpanel ## for toggling key menu self.sideWays = SideWays(self) ## extends pathMaker self.direct = { 'F': self.sideWays.openFiles, 'C': self.sideWays.centerPath, 'P': self.pathChooser, '{': self.sideWays.flipPath, '}': self.sideWays.flopPath, } self.setMouseTracking(True) self.view.viewport().installEventFilter(self) ### -------------------------------------------------------- def initThis(self): self.pts = [] self.npts = 0 ## counter used by addNewPathPts self.key = "" self.color = "DODGERBLUE" self.openPathFile = '' self.pointTag = "" self.tag = '' self.pathSet = False self.newPathSet = False self.pathChooserSet = False self.ball = None self.path = None self.newPath = None self.pathBall = None self.tagGroup = None self.pathTestSet = False self.wayPtsSet = False ## appear as tags ### ---------------------- key handler --------------------- @pyqtSlot(str) def pathKeys(self, key): self.key = key if self.key == 'D': ## always self.delete() elif self.key == '/': self.sideWays.changePathColor() elif self.newPathSet and self.key == 'cmd': ## note self.closeNewPath() elif key in NotPathSetKeys: if self.key == 'R': self.sideWays.reversePath() elif self.key == 'S': self.sideWays.savePath() elif self.key == 'T': self.sideWays.pathTest() elif self.key == 'W': self.sideWays.addWayPtTags() elif self.key == 'N': if not self.pathSet and not self.wayPtsSet: if not self.newPathSet: self.addNewPath() else: self.newPathOff() ## changed your mind self.delete() ## not waypts and not new path elif not self.wayPtsSet and not self.newPathSet: if key in self.direct: self.direct[key]() ## OK.. elif key in MoveKeys: self.sideWays.movePath(key) elif key in ScaleRotateKeys: self.sideWays.scaleRotate(key) ## waypts only elif self.wayPtsSet and key in WayPtsKeys: if self.key == '!': self.sideWays.halfPath() elif self.key == 'V': self.togglePointItems() elif self.key in ('<', '>'): self.sideWays.shiftWayPts(key) ### ----------------- event filter..not used --------------- ''' PathMaker mouse events for drawing a path ''' def eventFilter(self, source, e): if self.canvas.pathMakerOn and self.newPathSet: if e.type() == QEvent.MouseButtonPress: self.npts = 0 self.addNewPathPts(QPoint(e.pos())) elif e.type() == QEvent.MouseMove: self.addNewPathPts(QPoint(e.pos())) elif e.type() == QEvent.MouseButtonRelease: self.addNewPathPts(QPoint(e.pos())) self.updateNewPath() return False return QWidget.eventFilter(self, source, e) ### -------------------------------------------------------- def initPathMaker(self): ## from docks button if self.scene.items() and not self.canvas.pathMakerOn: MsgBox("Clear Scene First to run PathMaker") return elif self.canvas.pathMakerOn: self.pathMakerOff() else: self.canvas.pathMakerOn = True self.scene.clear() self.initThis() if not self.sliders.pathMenuSet: self.sliders.toggleMenu() self.turnGreen() # QTimer.singleShot(200, self.pathChooser) ## optional def turnGreen(self): self.dots.btnPathMaker.setStyleSheet("background-color: LIGHTGREEN") def delete(self): self.stopPathTest() self.removePointItems() self.removePath() self.removeWayPtTags() self.removeNewPath() self.pathChooserOff() self.scene.clear() self.initThis() def pathMakerOff(self): self.delete() self.canvas.pathMakerOn = False if self.sliders.pathMenuSet: self.sliders.toggleMenu() self.dots.btnPathMaker.setStyleSheet("background-color: white") def pathChooser(self): if not self.pathChooserSet and not self.newPathSet: self.chooser = DoodleMaker(self) self.chooser.move(600, 200) self.chooser.show() self.pathChooserSet = True else: self.pathChooserOff() def pathChooserOff(self): self.chooser = None self.pathChooserSet = False ### -------------------- new path ------------------------- def addNewPathPts(self, pt): ## called by dropCanvas eventfilter if self.npts == 0: self.pts.append(pt) self.npts += 1 if self.npts % 3 == 0: self.pts.append(pt) self.updateNewPath() def addNewPath(self): self.dots.btnPathMaker.setStyleSheet( "background-color: rgb(215,165,255)") self.initNewPath(True) def initNewPath(self, bool): self.newPath = None self.newPathSet = bool self.pts = [] self.npts = 0 def closeNewPath(self): ## applies only to newPath self.removeNewPath() self.addPath() ## add the completed path self.turnGreen() def newPathOff(self): if self.newPathSet: if self.newPath: self.scene.removeItem(self.newPath) self.initNewPath(False) self.turnGreen() def updateNewPath(self): if self.pts: ## list of points self.removeNewPath() ## clean up just in case self.newPath = QGraphicsPathItem(self.sideWays.setPaintPath()) self.newPath.setPen(QPen(QColor(self.color), 3, Qt.DashDotLine)) self.newPath.setZValue(common['pathZ']) self.scene.addItem(self.newPath) ## only one - no group needed self.newPathSet = True def removeNewPath(self): ## keep self.pts if self.newPath: self.scene.removeItem(self.newPath) self.newPathSet = False self.newPath = None ### -------------------- path stuff ------------------------ def addPath(self): self.removePath() self.path = QGraphicsPathItem(self.sideWays.setPaintPath(True)) self.path.setPen(QPen(QColor(self.color), 3, Qt.DashDotLine)) self.path.setZValue(common['pathZ']) self.scene.addItem(self.path) self.pathSet = True def removePath(self): if self.path: self.scene.removeItem(self.path) self.pathSet = False self.path = None ### --------------- pointItems and tags -------------------- def togglePointItems(self): if self.pointItemsSet(): self.removePointItems() else: self.addPointItems() QTimer.singleShot(200, self.redrawPathsAndTags) def redrawPathsAndTags(self): self.removeWayPtTags() self.removePath() self.addPath() self.sideWays.addWayPtTags() def findTop(self): for itm in self.scene.items(): return itm.zValue() return 0 def printZ(self): ## alternate print(self.scene.items()[0].zValue()) def addPointItems(self): idx = 0 add = self.findTop() + 10 for pt in self.pts: self.scene.addItem(PointItem(self, pt, idx, add)) idx += 1 def removePointItems(self): for pt in self.scene.items(): if pt.type == 'pt': self.scene.removeItem(pt) def pointItemsSet(self): for itm in self.scene.items(): if itm.type == 'pt': return True return False def insertPointItem(self, pointItem): idx, pt = pointItem.idx + 1, pointItem.pt if idx == len(self.pts): idx = 0 pt1 = self.pts[idx] pt1 = QPointF(pt1.x() - pt.x(), pt1.y() - pt.y()) pt1 = pt + QPointF(pt1) * .5 self.pts.insert(idx, pt1) self.redrawPoints() def delPointItem(self, pointItem): self.pts.pop(pointItem.idx) self.redrawPoints() def redrawPoints(self, bool=True): ## pointItems points self.removePointItems() self.removeWayPtTags() self.removePath() self.addPath() self.sideWays.addWayPtTags() if bool: self.addPointItems() def addPointTag(self, pnt): ## single tag pct = (pnt.idx / len(self.pts)) * 100 tag = self.sideWays.makePtsTag(pnt.pt, pnt.idx, pct) self.pointTag = TagIt('points', tag, QColor("YELLOW")) p = QPointF(0, -20) self.pointTag.setPos(pnt.pt + p) self.pointTag.setZValue(self.findTop() + 5) self.scene.addItem(self.pointTag) def addWayPtTag(self, tag, pt): self.tag = TagIt('pathMaker', tag, QColor("TOMATO")) self.tag.setPos(pt) self.tag.setZValue(common["tagZ"] + 5) self.tagGroup.addToGroup(self.tag) def removePointTag(self): ## single tag if self.pointTag: self.scene.removeItem(self.pointTag) self.pointTag = '' def addWayPtTagsGroup(self): self.tagGroup = QGraphicsItemGroup() self.scene.addItem(self.tagGroup) self.tagGroup.setZValue(common["tagZ"] + 5) def removeWayPtTags(self): if self.tagGroup or self.wayPtsSet: self.scene.removeItem(self.tagGroup) self.tagGroup = None self.wayPtsSet = False def startPathTest(self): self.scene.addItem(self.ball) self.pathBall.start() self.pathTestSet = True def stopPathTest(self): if self.pathTestSet: self.pathBall.stop() self.scene.removeItem(self.ball) self.pathBall = None self.pathTestSet = False
def randomWalk(self, image): """ actual calculations :param image: bitmap to squigglify :return: """ self.removeOldGraphicsItems() group = QGraphicsItemGroup() no_of_walks = self.parent.noOfWalksWalkify.value() coordinates = {} self.applyThreshold(image) for w in range(no_of_walks): x, y = self.find_darkest(image) x, y, color = self.find_darkest_neighbor(image, x, y) coordinates[w] = np.array([[x, y]]) no_of_line_segments = self.parent.noOfLineSegmentsWalkify.value() adjustbrightness = self.parent.localBrightnessAdjustmentWalkify.value( ) stroke_width = self.parent.lineWidthWalkify.value() for s in range(0, no_of_line_segments): dx, dy, dc = self.find_darkest_neighbor(image, x, y) self.lighten_area_around(image, adjustbrightness, dx, dy) x, y = dx, dy coordinates[w] = np.append(coordinates[w], [[x, y]], axis=0) for w in range(no_of_walks): coordinates[w] = simplify_coords( coordinates[w], self.parent.polylineSimplificationToleranceWalkify.value()) for w in range(no_of_walks): path = QPainterPath() in_the_middle_of_a_quad = False for idx, c in enumerate(coordinates[w]): quad = self.parent.useSmootherShapesWalkify.checkState( ) == Qt.Checked if not quad: if idx == 0: path.moveTo(coordinates[w][idx][0], coordinates[w][idx][1]) else: path.lineTo(coordinates[w][idx][0], coordinates[w][idx][1]) else: if idx == 0: path.moveTo(coordinates[w][idx][0], coordinates[w][idx][1]) elif idx % 2 == 1: middlex, middley = coordinates[w][idx][0], coordinates[ w][idx][1] in_the_middle_of_a_quad = True else: path.quadTo(middlex, middley, coordinates[w][idx][0], coordinates[w][idx][1]) in_the_middle_of_a_quad = False if in_the_middle_of_a_quad: path.lineTo(middlex, middley) item = QGraphicsPathItem(path) pen = QPen() pen.setWidth(stroke_width) item.setPen(pen) group.addToGroup(item) self.addNewGraphicsItems(group)