Search code examples
pythonpython-3.xpyqtpyqt5qpainter

PyQt5 QGraphicsPathItem to be drawn by QPainter


I have a path that makes up a symbol and I was wondering if that path can then be drawn by the QPainter. When reading the documentation I saw there was a drawPath function associated with QPainter, but every time I try and implement it I keep getting an error that says QPainter::drawPath: Painter not active.

Because I have not has any successful attempts I have added a minimized working version of my code. It will give you some basic zoom and panning functions along with selecting the symbol, and holding shift to deselect the symbol.

I would like the path the change colors when it is selected or hovered over and currently it gets filled in when it is selected or hovered over. Eventually, I would like the user to be able to change the path to a dashed line or the color from the start.

Thank you for any advice and help.

Code

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtSvg import *
import sys
from math import sqrt,cos,acos,asin,degrees,pi


class LogObject(QObject):
    hovered = pyqtSignal()
    notHovered = pyqtSignal()

def figAngle(radius,opposite):
    dist = sqrt((radius) ** 2 + (opposite) ** 2)
    angle = degrees(asin(opposite/dist))
    if angle < 0:
        angle = angle + 360
    return angle

def create_path():
    scale = 250
    path = QPainterPath()
    path.addEllipse(QPointF(0,0), 0.0268, 0.0268) # Using QPointF will center it
    startAngle = figAngle(0.0357/scale,0.0045/scale) # Use the scale so that it will adjust as it is scaled
    endAngle = figAngle(0.0357/scale,-0.0092/scale)
    path.moveTo((0.0357+0.0821),0.0045 )
    path.arcTo(QRectF(-0.0357,-0.0357,(0.0357*2),(0.0357*2)),-startAngle,-endAngle)
    path.moveTo(0.0357, -0.0045)
    path.lineTo(0.0357 + 0.0821, -0.0045)
    path.lineTo(0.0357+0.0821,-0.0045-0.0680)
    path.lineTo(0.0357+0.0821+0.1450,-0.0045-0.0680)
    path.lineTo(0.0357+0.0821+0.1450,-0.0045-0.0680+0.1450)
    path.lineTo(0.0357+0.0821,-0.0045-0.0680+0.1450)
    path.lineTo(0.0357+0.0821,-0.0045-0.0680+0.1450-0.0680)
    path.moveTo(0.0357+0.0821+0.0090,-0.0045-0.0680+0.1450-0.0090)

    path.addRect(0.0357+0.0821+0.0090, -0.0045-0.0680+0.1450-0.0090-0.1270, 0.1270, 0.1270)
    tr = QTransform()
    tr.scale(scale, scale)
    path = tr.map(path)
    return path

class Point(QGraphicsPathItem):
    def __init__(self, x, y, r, name):
        super(Point, self).__init__()
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setPath(create_path())
        self.setScale(1)
        self.setRotation(180+r)
        self.setAcceptHoverEvents(True)
        self.log = LogObject()
        self.setPos(x, y)
        self.isSelected = False

    def itemChange(self, change, value):
        if change == self.ItemSelectedChange:
            self.setBrush(QBrush(Qt.green) if value else QBrush(Qt.black))
        return QGraphicsItem.itemChange(self, change, value)

    def hoverEnterEvent(self, event):
        self.setBrush(QColor("red"))
        self.log.hovered.emit()
        QGraphicsItem.hoverMoveEvent(self, event)

    def hoverLeaveEvent(self, event):
        if self.isSelected == True:
            self.setBrush(QBrush(Qt.green))
        else:
            self.setBrush(QColor("black"))
        self.log.notHovered.emit()
        QGraphicsItem.hoverMoveEvent(self, event)


class Viewer(QGraphicsView):
    photoClicked = pyqtSignal(QPoint)
    rectChanged = pyqtSignal(QRect)

    def __init__(self, parent):
        super(Viewer, self).__init__(parent)
        self.setRenderHints(QPainter.Antialiasing)

        self._zoom = 0
        self._empty = True
        self.setScene(QGraphicsScene(self))

        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setFrameShape(QFrame.NoFrame)
        self.area = float()
        self.setPoints()
        self.viewport().setCursor(Qt.ArrowCursor)
        QTimer.singleShot(0, self.reset_fit)
        self.selectedItems = []

    def setItems(self):
        self.data = {
            "x": [
                -2414943.8686,
                -2417160.6592,
                -2417160.6592,
                -2416009.9966,
                -2416012.5232,
                -2416012.5232,
            ],
            "y": [
                10454269.7008,
                10454147.2672,
                10454147.2672,
                10453240.2808,
                10455255.8752,
                10455255.8752,

            ],
            "rotation":[
            313.9962,
            43.9962,
            223.9962,
            313.9962,
            43.9962,
            223.9962,
            ]
        }

        for i, (x, y,r) in enumerate(zip(self.data["x"], self.data["y"],self.data["rotation"])):
            p = Point(x, y,r, "Point__" + str(i))
            p.log.hovered.connect(self.hoverChange)
            p.log.notHovered.connect(self.notHoverChange)
            self.scene().addItem(p)

    def setPoints(self):
        self.setItems()
        self.setDragMode(self.ScrollHandDrag)

    def wheelEvent(self, event):
        if event.angleDelta().y() > 0:
            factor = 1.25
            self._zoom += 1
        else:
            factor = 0.8
            self._zoom -= 1
        if self._zoom > 0:
            self.scale(factor, factor)
        elif self._zoom == 0:
            self.reset_fit()
        else:
            self._zoom = 0

    def hoverChange(self):
        self.viewport().setCursor(Qt.PointingHandCursor)

    def notHoverChange(self):
        self.viewport().setCursor(Qt.ArrowCursor)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            singleItem = self.itemAt(event.pos().x(), event.pos().y())
            if singleItem != None:
                if QApplication.keyboardModifiers() == Qt.ShiftModifier: # This will determine if the shift key is depressed
                    if singleItem.isSelected == True:
                        singleItem.setSelected(False)
                        singleItem.isSelected = False
                        self.selectedItems.remove(singleItem)
                elif singleItem.isSelected == False:
                    singleItem.setSelected(True)
                    singleItem.isSelected = True
                    self.selectedItems.append(singleItem)
            return

        elif event.button() == Qt.MidButton:
            self.viewport().setCursor(Qt.ClosedHandCursor)
            self.original_event = event
            handmade_event = QMouseEvent(
                QEvent.MouseButtonPress,
                QPointF(event.pos()),
                Qt.LeftButton,
                event.buttons(),
                Qt.KeyboardModifiers(),
            )
            QGraphicsView.mousePressEvent(self, handmade_event)

        super(Viewer, self).mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.MidButton:
            self.viewport().setCursor(Qt.ArrowCursor)
            handmade_event = QMouseEvent(
                QEvent.MouseButtonRelease,
                QPointF(event.pos()),
                Qt.LeftButton,
                event.buttons(),
                Qt.KeyboardModifiers(),
            )
            QGraphicsView.mouseReleaseEvent(self, handmade_event)

    def reset_fit(self):
        r = self.scene().itemsBoundingRect()
        self.resetTransform()
        self.setSceneRect(r)
        self.fitInView(r, Qt.KeepAspectRatio)
        self._zoom = 0
        self.scale(1, -1)


class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.viewer = Viewer(self)
        VBlayout = QVBoxLayout(self)
        VBlayout.addWidget(self.viewer)


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 300, 800, 600)
    window.show()
    sys.exit(app.exec_())

Solution

  • You do not have to use QPainter in QGraphicsPathItem, you have to set a QPen using setPen().


    TL; DR;

    QGraphicsPathItem already has a QPainter that should be used only in the paint() method, but in this case it is not necessary, the existing QGraphicsItems like QGraphicsRectItem, QGraphicsEllipseItem, etc are not necessary to use them since these methods inherit from QAbstractGraphicsShapeItem and that support QPen and QBrush using the setPen() and setBrush() method, respectively.

    Another problem that I see in your code is that you are using as an isSelected attribute but you should not do it, there is already a method with that name so you must use isSelected().

    Considering previous the solution is:

    class Point(QGraphicsPathItem):
        def __init__(self, x, y, r, name):
            super(Point, self).__init__()
            self.setFlag(QGraphicsItem.ItemIsSelectable, True)
            self.setPath(create_path())
            self.setScale(10)
            self.setRotation(180 + r)
            self.setAcceptHoverEvents(True)
            self.log = LogObject()
            self.setPos(x, y)
            pen = QPen(Qt.red)
            pen.setStyle(Qt.DashLine)
            self.setPen(pen)
    
        def itemChange(self, change, value):
            if change == self.ItemSelectedChange:
                color = QColor(Qt.green) if value else QColor("black")
                # self.setBrush(color)
                pen = self.pen()
                pen.setColor(color)
                self.setPen(pen)
            return QGraphicsItem.itemChange(self, change, value)
    
        def hoverEnterEvent(self, event):
            color = QColor("red")
            # self.setBrush(color)
            pen = self.pen()
            pen.setColor(color)
            self.setPen(pen)
            self.log.hovered.emit()
            QGraphicsItem.hoverMoveEvent(self, event)
    
        def hoverLeaveEvent(self, event):
            color = QColor(Qt.green) if self.isSelected() else QColor("black")
            # self.setBrush(color)
            pen = self.pen()
            pen.setColor(color)
            self.setPen(pen)
            self.log.notHovered.emit()
            QGraphicsItem.hoverMoveEvent(self, event)