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_())
You do not have to use QPainter in QGraphicsPathItem, you have to set a QPen using setPen().
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)