Currently, I'm using PySide to do image cropping under Ubuntu 14.04 LTS. I came across the answer in this post, which describes a rough idea of how to do image cropping. Now I'd like to change the width and color of the cropping rectangle. Following this, I understand I should create a class which subclasses QRubberBand and overrides the paintEvent method. So I got the python code at the bottom. As you can see, I did pen.setWidth(5) in paintEvent. However, when I do image cropping, the line width does not change (seems to remain 1 as default). See the following red rectangle.
Please help. Thanks!
import sys
from PyQt4 import QtGui, QtCore
class MyRubberBand(QtGui.QRubberBand):
def __init__(self, QRubberBand_Shape, QWidget_parent=None):
super(MyRubberBand, self).__init__(QRubberBand_Shape, QWidget_parent)
palette = QtGui.QPalette()
palette.setBrush(QtGui.QPalette.WindowText, QtGui.QBrush(QtCore.Qt.red))
self.setPalette(palette)
def paintEvent(self, QPaintEvent):
painter = QtGui.QStylePainter(self)
# painter.begin(self)
pen = QtGui.QPen()
# pen.setStyle(QtCore.Qt.SolidLine)
# pen.setColor(QtGui.QColor(QtCore.Qt.red))
pen.setWidth(5)
painter.setPen(pen)
option = QtGui.QStyleOptionFocusRect()
option.initFrom(self)
painter.drawControl(QtGui.QStyle.CE_FocusFrame, option)
class QExampleLabel (QtGui.QLabel):
def __init__(self, parentQWidget = None):
super(QExampleLabel, self).__init__(parentQWidget)
self.initUI()
def initUI (self):
self.setPixmap(QtGui.QPixmap('images/3.png'))
self.currentQRubberBand = MyRubberBand(QtGui.QRubberBand.Rectangle, self)
def mousePressEvent (self, eventQMouseEvent):
print 'mouse pressed'
self.originQPoint = eventQMouseEvent.pos()
self.currentQRubberBand.setGeometry(QtCore.QRect(self.originQPoint, QtCore.QSize()))
self.currentQRubberBand.show()
def mouseMoveEvent (self, eventQMouseEvent):
self.currentQRubberBand.setGeometry(QtCore.QRect(self.originQPoint, eventQMouseEvent.pos()).normalized())
def mouseReleaseEvent (self, eventQMouseEvent):
print 'mouse released'
# self.currentQRubberBand.hide()
currentQRect = self.currentQRubberBand.geometry()
# self.currentQRubberBand.deleteLater()
cropQPixmap = self.pixmap().copy(currentQRect)
cropQPixmap.save('output.png')
if __name__ == '__main__':
myQApplication = QtGui.QApplication(sys.argv)
myQExampleLabel = QExampleLabel()
myQExampleLabel.show()
sys.exit(myQApplication.exec_())
I know this question is old but I was recently trying to do the same thing and seen this so I want to post some code for anyone else who might come across this as well.
Your code with updated paintEvent.
PySide2:
import sys
from PySide2.QtCore import Qt, QRect, QSize
from PySide2.QtGui import QColor, QPen, QBrush, QPixmap, QPainter, QPalette
from PySide2.QtWidgets import QApplication, QLabel, QRubberBand
class MyRubberBand(QRubberBand):
def __init__(self, QRubberBand_Shape, QWidget_parent=None):
super(MyRubberBand, self).__init__(QRubberBand_Shape, QWidget_parent)
palette = QPalette()
palette.setBrush(QPalette.WindowText, QBrush(Qt.red))
self.setPalette(palette)
def paintEvent(self, QPaintEvent):
###QRubberBand with black border and transparent background###
painter = QPainter(self)
###Border Color with width set at 6###
pen_color = QColor(0, 0, 0, 255)
painter.setPen(QPen(pen_color, 6))
###Transparent background###
brush_color = QColor(Qt.transparent)
###Or to use solid background color###
# brush_color = QColor(Qt.red)
painter.setBrush(QBrush(brush_color))
painter.drawRect(QPaintEvent.rect())
class QExampleLabel (QLabel):
def __init__(self, parentQWidget = None):
super(QExampleLabel, self).__init__(parentQWidget)
self.initUI()
def initUI (self):
self.setPixmap(QPixmap('images/3.png'))
self.currentQRubberBand = MyRubberBand(QRubberBand.Rectangle, self)
def mousePressEvent (self, eventQMouseEvent):
print('mouse pressed')
self.originQPoint = eventQMouseEvent.pos()
self.currentQRubberBand.setGeometry(QRect(self.originQPoint, QSize()))
self.currentQRubberBand.show()
def mouseMoveEvent (self, eventQMouseEvent):
self.currentQRubberBand.setGeometry(QRect(self.originQPoint, eventQMouseEvent.pos()).normalized())
def mouseReleaseEvent (self, eventQMouseEvent):
print('mouse released')
# self.currentQRubberBand.hide()
currentQRect = self.currentQRubberBand.geometry()
# self.currentQRubberBand.deleteLater()
cropQPixmap = self.pixmap().copy(currentQRect)
cropQPixmap.save('output.png')
if __name__ == '__main__':
myQApplication = QApplication(sys.argv)
myQExampleLabel = QExampleLabel()
myQExampleLabel.resize(800, 600)
myQExampleLabel.show()
sys.exit(myQApplication.exec_())
PyQt5:
import sys
from PyQt5.QtCore import Qt, QRect, QSize
from PyQt5.QtGui import QColor, QPen, QBrush, QPixmap, QPainter, QPalette
from PyQt5.QtWidgets import QApplication, QLabel, QRubberBand
class MyRubberBand(QRubberBand):
def __init__(self, QRubberBand_Shape, QWidget_parent=None):
super(MyRubberBand, self).__init__(QRubberBand_Shape, QWidget_parent)
palette = QPalette()
palette.setBrush(QPalette.WindowText, QBrush(Qt.red))
self.setPalette(palette)
def paintEvent(self, QPaintEvent):
###QRubberBand with black border and transparent background###
painter = QPainter(self)
###Border Color with width set at 6###
pen_color = QColor(0, 0, 0, 255)
painter.setPen(QPen(pen_color, 6))
###Transparent background###
brush_color = QColor(Qt.transparent)
###Or to use solid background color###
# brush_color = QColor(Qt.red)
painter.setBrush(QBrush(brush_color))
painter.drawRect(QPaintEvent.rect())
class QExampleLabel (QLabel):
def __init__(self, parentQWidget = None):
super(QExampleLabel, self).__init__(parentQWidget)
self.initUI()
def initUI (self):
self.setPixmap(QPixmap('images/3.png'))
self.currentQRubberBand = MyRubberBand(QRubberBand.Rectangle, self)
def mousePressEvent (self, eventQMouseEvent):
print('mouse pressed')
self.originQPoint = eventQMouseEvent.pos()
self.currentQRubberBand.setGeometry(QRect(self.originQPoint, QSize()))
self.currentQRubberBand.show()
def mouseMoveEvent (self, eventQMouseEvent):
self.currentQRubberBand.setGeometry(QRect(self.originQPoint, eventQMouseEvent.pos()).normalized())
def mouseReleaseEvent (self, eventQMouseEvent):
print('mouse released')
# self.currentQRubberBand.hide()
currentQRect = self.currentQRubberBand.geometry()
# self.currentQRubberBand.deleteLater()
cropQPixmap = self.pixmap().copy(currentQRect)
cropQPixmap.save('output.png')
if __name__ == '__main__':
myQApplication = QApplication(sys.argv)
myQExampleLabel = QExampleLabel()
myQExampleLabel.resize(800, 600)
myQExampleLabel.show()
sys.exit(myQApplication.exec_())
Related
Unable to position the dialog # the bottom right of window in order to set the position setGeometry(...) method is used but its always # center. How to position the dialog to the bottom right?
Execution starts from keyWindow.py(main) and when numeric keys are pressed it opens dialog which is located in keyDialog.py
keyWindow.py
import sys
from PySide6.QtWidgets import QWidget, QMainWindow, QApplication, QHBoxLayout, QVBoxLayout, QDialog
from PySide6.QtGui import QKeyEvent
from PySide6.QtCore import Qt
import KeyDialog
class MainWindow(QMainWindow):
numericDic = {
Qt.Key_0: 0,
Qt.Key_1: 1,
Qt.Key_2: 2,
Qt.Key_3: 3,
Qt.Key_4: 4,
Qt.Key_5: 5,
Qt.Key_6: 6,
Qt.Key_7: 7,
Qt.Key_8: 8,
Qt.Key_9: 9
}
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.resize(600, 500)
def keyPressEvent(self, keyEvent: QKeyEvent):
if keyEvent.isAutoRepeat():
return
key = keyEvent.key()
print(f"keyPressEvent {key}")
if key in MainWindow.numericDic.keys() or key == Qt.Key_Plus or key == Qt.Key_Minus:
keyDialog = KeyDialog(self)
keyDialog.exec()
# def keyReleaseEvent(self, keyEvent: QKeyEvent):
# if keyEvent.isAutoRepeat():
# return
# key = keyEvent.key()
# print(f"keyReleaseEvent {key}")
if __name__ == "__main__":
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec())
keyDialog.py
from PySide6.QtWidgets import (QWidget, QMainWindow, QHBoxLayout, QVBoxLayout, QDialog,
QGridLayout, QDoubleSpinBox, QApplication)
from PySide6.QtCore import Qt
class KeyDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.initUI()
def initUI(self):
width, height = self.getScreenSize()
ownWidth = 200
ownHeight = 40
bottomGap = 30
rightGap = 40
self.setGeometry(width - ownWidth - rightGap, height - ownHeight - bottomGap, ownWidth, ownHeight)
self.layout = QGridLayout()
self.box = QDoubleSpinBox()
self.box.editingFinished.connect(self.boxValueChanged)
self.box.setMinimum(float("-inf"))
self.box.setMaximum(float("+inf"))
self.box.setValue(3.5)
self.box.setSingleStep(0.5)
self.layout.addWidget(self.box, 0, 0)
self.setLayout(self.layout)
def getScreenSize(self):
return (QApplication.primaryScreen().size().width(), QApplication.primaryScreen().size().height())
def boxValueChanged(self):
print(f"new Value {self.box.value()}")
Here is the code. It runs. To exhibit this bug. Right-click the ellipse, scale it by click-dragging on the ellipse. Right-click it > "Done editing". Then do the same thing with "Rotate."
I've tried over 10 different permutations of using self.setRotation, self.setTransform, painter.rotate, and so on... The only time it did rotate was when I did self.setTransform(self.transform().rotate(r)) but the result was wrong: scale & rotate in the wrong order or something.
from PyQt5.QtWidgets import QGraphicsItem, QMenu
from PyQt5.QtGui import QTransform, QPen, QPainter, QColor, QBrush
from PyQt5.QtCore import Qt, QPointF, QRectF, QEvent
from math import sqrt
def scaleRect(rect, scale_x, scale_y):
T = QTransform.fromScale(scale_x, scale_y)
return T.mapRect(rect)
def debugPrintTransformMatrix(T):
print(str(T.m11()) + ' ' + str(T.m12()) + ' ' + str(T.m13()))
print(str(T.m21()) + ' ' + str(T.m22()) + ' ' + str(T.m23()))
print(str(T.m31()) + ' ' + str(T.m32()) + ' ' + str(T.m33()))
# Assumes no shearing or stretching.
# Only Rotation, Translation, and Scaling.
def extractTransformScale(T):
# This is math matrix notation transposed (debug print self.sceneTransform() to see)
Sx = sqrt(T.m11()**2 + T.m12()**2)
Sy = sqrt(T.m21()**2 + T.m22()**2)
return Sx, Sy
def extractTransformTranslate(T):
return T.m31(), T.m32()
class Object(QGraphicsItem):
def sceneEvent(self, event):
if event.type() == QEvent.GraphicsSceneMouseMove:
# move, scale, or rotate
if self._mode in ['scale', 'rotate']:
mouse_pos = event.scenePos()
last_pos = event.lastScenePos()
if self._mode == 'scale':
s = self.mouseScalingFactors(mouse_pos, last_pos)
self.setTransform(self.transform().scale(*s))
if self._mode == 'rotate':
r = self.mouseRotationAngle(mouse_pos, last_pos)
self.setRotation(self.rotation() + r)
return True
return super().sceneEvent(event)
def __init__(self):
super().__init__()
self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsSelectable)
self._selectionPen = QPen(Qt.black, 1.0, style=Qt.DotLine, cap=Qt.FlatCap)
self._lastPos = QPointF(0, 0)
self.setPos(self._lastPos)
self._mode = 'neutral'
def setRenderHints(self, painter):
painter.setRenderHints(QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing | QPainter.TextAntialiasing)
def boundingRectExtraScale(self):
return (1.2 , 1.2)
def mouseScalingFactors(self, pos, last_pos):
delta = pos - last_pos
return (1.01 ** delta.x(), 1.01 ** delta.y())
def mouseRotationAngle(self, pos, last_pos):
return 1 #TODO
def createDefaultContextMenu(self):
menu = QMenu()
if self._mode == 'neutral':
menu.addAction('Scale').triggered.connect(lambda: self.setMode('scale'))
menu.addAction('Rotate').triggered.connect(lambda: self.setMode('rotate'))
else:
menu.addAction('Done Editing').triggered.connect(lambda: self.setMode('neutral'))
return menu
def contextMenuEvent(self, event):
menu = self.createDefaultContextMenu()
menu.exec(event.screenPos())
def setMode(self, mode):
self._mode = mode
def setSelected(self, selected):
super().setSelected(selected)
self.update()
class ShapedObject(Object):
def __init__(self):
super().__init__()
self._shape = {
'name' : 'ellipse',
'radius': 35
}
self._brush = QBrush(Qt.darkGreen)
self._pen = QPen(Qt.yellow, 3)
def shapeDef(self):
return self._shape
def boundingRect(self):
rect = self.shapeRect()
s = self.boundingRectExtraScale()
return scaleRect(rect, *s)
def shape(self): #TODO QPainterPath shape for collision detection
# Should call self.boundingRectExtraScale()
return super().shape()
def paint(self, painter, option, widget):
self.setRenderHints(painter)
#super().paint(painter, option, widget)
shape = self.shapeDef()
name = shape['name']
painter.setBrush(self._brush)
painter.setPen(self._pen)
painter.save()
# ********** HERE IS THE PROBLEM *************
debugPrintTransformMatrix(painter.transform())
painter.rotate(5)
debugPrintTransformMatrix(painter.transform())
rect = self.shapeRect()
if name == 'ellipse':
painter.drawEllipse(rect)
painter.restore()
def shapeRect(self):
shape = self.shapeDef()
name = shape['name']
if name == 'ellipse':
r = shape['radius']
rect = QRectF(-r, -r, 2*r, 2*r)
return rect
####
import sys
from PyQt5.QtWidgets import QMainWindow, QGraphicsScene, QGraphicsView, QApplication
if __name__ == '__main__':
app = QApplication(sys.argv)
window = QMainWindow()
view = QGraphicsView()
scene = QGraphicsScene()
view.setScene(scene)
window.setCentralWidget(view)
ellipse = ShapedObject()
scene.addItem(ellipse)
window.show()
sys.exit(app.exec_())
Got it to work. It was the order of the operations rotate / scale. Never use an circle to test your rotation code, lol!
from PyQt5.QtWidgets import QGraphicsItem, QMenu, QGraphicsRotation, QGraphicsScale
from PyQt5.QtGui import QTransform, QPen, QPainter, QColor, QBrush, QPainterPath
from PyQt5.QtCore import Qt, QPointF, QRectF, QEvent
from math import sqrt
def scaleRect(rect, scale_x, scale_y):
T = QTransform.fromScale(scale_x, scale_y)
return T.mapRect(rect)
def debugPrintTransformMatrix(T):
print(str(T.m11()) + ' ' + str(T.m12()) + ' ' + str(T.m13()))
print(str(T.m21()) + ' ' + str(T.m22()) + ' ' + str(T.m23()))
print(str(T.m31()) + ' ' + str(T.m32()) + ' ' + str(T.m33()))
# Assumes no shearing or stretching.
# Only Rotation, Translation, and Scaling.
def extractTransformScale(T):
# This is math matrix notation transposed (debug print self.sceneTransform() to see)
Sx = sqrt(T.m11()**2 + T.m12()**2)
Sy = sqrt(T.m21()**2 + T.m22()**2)
return Sx, Sy
def extractTransformTranslate(T):
return T.m31(), T.m32()
class Object(QGraphicsItem):
def sceneEvent(self, event):
if event.type() == QEvent.GraphicsSceneMouseMove:
# move, scale, or rotate
if self._mode in ['scale', 'rotate']:
mouse_pos = event.scenePos()
last_pos = event.lastScenePos()
if self._mode == 'scale':
s = self.mouseScalingFactors(mouse_pos, last_pos)
self.applyScaleTransform(*s)
if self._mode == 'rotate':
r = self.mouseRotationAngle(mouse_pos, last_pos)
self.applyRotateTransform(r)
return True
return super().sceneEvent(event)
def __init__(self):
super().__init__()
self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsSelectable)
self._selectionPen = QPen(Qt.black, 1.0, style=Qt.DotLine, cap=Qt.FlatCap)
self._lastPos = QPointF(0, 0)
self.setPos(self._lastPos)
self._mode = 'neutral'
self._scale = QGraphicsScale()
self._rotate = QGraphicsRotation()
self.setTransformations([self._rotate, self._scale])
def applyRotateTransform(self, angle):
self._rotate.setAngle(self._rotate.angle() + 15)
def applyScaleTransform(self, sx, sy):
self._scale.setXScale(sx * self._scale.xScale())
self._scale.setYScale(sy * self._scale.yScale())
def setRenderHints(self, painter):
painter.setRenderHints(QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing | QPainter.TextAntialiasing)
def boundingRectExtraScale(self):
return (1.2 , 1.2)
def mouseScalingFactors(self, pos, last_pos):
delta = pos - last_pos
return (1.01 ** delta.x(), 1.01 ** delta.y())
def mouseRotationAngle(self, pos, last_pos):
return 1 #TODO
def createDefaultContextMenu(self):
menu = QMenu()
if self._mode == 'neutral':
menu.addAction('Scale').triggered.connect(lambda: self.setMode('scale'))
menu.addAction('Rotate').triggered.connect(lambda: self.setMode('rotate'))
else:
menu.addAction('Done Editing').triggered.connect(lambda: self.setMode('neutral'))
return menu
def contextMenuEvent(self, event):
menu = self.createDefaultContextMenu()
menu.exec(event.screenPos())
def setMode(self, mode):
self._mode = mode
def setSelected(self, selected):
super().setSelected(selected)
self.update()
class ShapedObject(Object):
def __init__(self):
super().__init__()
self._shape = {
'name' : 'ellipse',
'radius': 35
}
self._brush = QBrush(Qt.darkGreen)
self._pen = QPen(Qt.yellow, 3)
def shapeDef(self):
return self._shape
def boundingRect(self):
rect = self.shapeRect()
s = self.boundingRectExtraScale()
return scaleRect(rect, *s)
def shape(self): #TODO QPainterPath shape for collision detection
# Should call self.boundingRectExtraScale()
return super().shape()
def paint(self, painter, option, widget):
self.setRenderHints(painter)
#super().paint(painter, option, widget)
shape = self.shapeDef()
name = shape['name']
painter.setBrush(self._brush)
painter.setPen(self._pen)
painter.save()
path = QPainterPath()
if name == 'ellipse':
r = shape['radius']
path.addEllipse(QPointF(0, 0), r, r)
painter.drawPath(path)
painter.restore()
def shapeRect(self):
shape = self.shapeDef()
name = shape['name']
if name == 'ellipse':
r = shape['radius']
rect = QRectF(-r, -r, 2*r, 2*r)
return rect
####
import sys
from PyQt5.QtWidgets import QMainWindow, QGraphicsScene, QGraphicsView, QApplication
if __name__ == '__main__':
app = QApplication(sys.argv)
window = QMainWindow()
view = QGraphicsView()
scene = QGraphicsScene()
view.setScene(scene)
window.setCentralWidget(view)
ellipse = ShapedObject()
scene.addItem(ellipse)
window.show()
sys.exit(app.exec_())
I am working on a simple Paint application using pyqt5. My goal is:
1. Draw freely according to mouse events. It's done!
2. Erase freely according to mouse events. It isn't work as expected!
I have a QGraphicsView, QGraphicsScene where I'm adding QGraphicsItems, specifically QGraphicsPathItem. My eraser has a rect shape, and what I want is to erase part of my drawing while moving my eraser on it.
On MouseMoveEvent, I check if my eraser shape intersects my drawing path, if so, I just subtract it from my drawing path. Then the awkward behavior occurs, it closes my drawing path.
Figure_1, shows my drawing (QGraphicsPathItem).
Figure_2, shows what happens when my eraser(QGraphicsRectItem) intersects my drawing.
Drawing freely lines working fine. Figure_1
Erasing drawing. Figure_2
As we can see, it draws a line linking my first and end points, closing my path. I dont want it. I just want to erase my drawing path.
from PyQt5.QtCore import QRectF, Qt
from PyQt5.QtGui import QPainterPath, QPen
from PyQt5.QtWidgets import QApplication, QGraphicsScene, \
QGraphicsView, QPushButton, QWidget, \
QVBoxLayout, QGraphicsItem, QGraphicsPathItem, QGraphicsRectItem
class Window(QWidget):
scene = None
def __init__(self):
QWidget.__init__(self)
self.view = View(self)
self.button = QPushButton('Clear View', self)
self.button.clicked.connect(self.handleClearView)
layout = QVBoxLayout(self)
layout.addWidget(self.view)
layout.addWidget(self.button)
def handleClearView(self):
self.view.scene.clear()
class View(QGraphicsView):
def __init__(self, parent):
self.scribing = False
self.erasing = False
QGraphicsView.__init__(self, parent)
self.scene = QGraphicsScene()
self.setScene(self.scene)
self.free_draw_item = None
self.eraser_item = None
def resizeEvent(self, QResizeEvent):
self.setSceneRect(QRectF(self.viewport().rect()))
def mousePressEvent(self, event):
if event.buttons() == Qt.LeftButton:
self.scribing = True
pp = QPainterPath(event.pos())
self.free_draw_item = QGraphicsPathItem(pp)
self.free_draw_item.setPen(QPen(Qt.green))
self.free_draw_item.setFlags(QGraphicsItem.ItemIsSelectable)
self.free_draw_item.setPath(pp)
self.scene.addItem(self.free_draw_item)
if event.buttons() == Qt.RightButton:
self.erasing = True
self.eraser_item = QGraphicsRectItem(event.pos().x() - 5,
event.pos().y() - 5, 10, 10)
self.eraser_item.setPen(QPen(Qt.red))
self.eraser_item.setBrush(Qt.transparent)
self.scene.addItem(self.eraser_item)
def mouseMoveEvent(self, event):
if (event.buttons() & Qt.LeftButton) and self.scribing:
if self.free_draw_item:
path = self.free_draw_item.path()
path.lineTo(event.pos())
self.free_draw_item.setPath(path)
if event.buttons() & Qt.RightButton and self.erasing:
self.eraser_item.setRect(event.pos().x() - 5, event.pos().y() - 5,
10, 10)
for item in self.scene.collidingItems(self.eraser_item):
if isinstance(item, QGraphicsPathItem):
if item.path().intersected(self.eraser_item.shape()):
new = item.path().subtracted(self.eraser_item.shape())
item.setPath(new)
#item.setBrush(Qt.red)
def mouseReleaseEvent(self, event):
self.scribing = False
self.erasing = False
if self.eraser_item != None:
self.scene.removeItem(self.eraser_item)
# if self.free_draw_item != None:
# self.free_draw_item.setSelected(True)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
This is my code.
Left mouse button draws green path on scene.
Right mouse button erase my drawings.
I've had same problem several and several times.
After some headache and a thousand unsuccessful researches about this specific problem I've come to on answer.
Forget having only one path
You cant subtract without closing the path turning it into a closed polygon
Think as having many paths and working around
Now you have all sub items and can "delete" them when colliding with your eraser
Use the subtract on your favor
Have many paths with 3 not align points each path(least points on a plan to have a polygon)
Group it as QGraphicsItemGroup() if you want to manipulate the whole path made of several (3 points path) as a single item
See my example
-
from PyQt5.QtCore import QPoint
from PyQt5.QtCore import QRectF, Qt
from PyQt5.QtGui import QBrush
from PyQt5.QtGui import QPainter
from PyQt5.QtGui import QPainterPath, QPen
from PyQt5.QtWidgets import QApplication, QGraphicsScene, \
QGraphicsView, QPushButton, QWidget, \
QVBoxLayout, QGraphicsItem, QGraphicsPathItem, QGraphicsRectItem
from PyQt5.QtWidgets import QGraphicsItemGroup
from core.IQGraphicsPathItem import InteractQGraphicsPathItem
class Window(QWidget):
scene = None
def __init__(self):
QWidget.__init__(self)
self.view = View(self)
self.button = QPushButton('Clear View', self)
self.button.clicked.connect(self.handleClearView)
layout = QVBoxLayout(self)
layout.addWidget(self.view)
layout.addWidget(self.button)
def handleClearView(self):
self.view.scene.clear()
class View(QGraphicsView):
dic = {}
num_item = 0
_from_x_ = None
_from_y_ = None
pp = None
pen = None
group = None
def __init__(self, parent):
self.scribing = False
self.erasing = False
QGraphicsView.__init__(self, parent)
self.scene = QGraphicsScene()
self.setScene(self.scene)
self.setRenderHint(QPainter.Antialiasing)
self.free_draw_item = QGraphicsPathItem()
# self.free_draw_item.setPen(QPen(Qt.green))
# self.free_draw_item.setFlags(QGraphicsItem.ItemIsSelectable)
self.eraser_item = None
self.pen = QPen(Qt.green,8)
self.pen.setBrush(QBrush(Qt.green))
self.pen.setWidth(1)
self.group = QGraphicsItemGroup()
def resizeEvent(self, QResizeEvent):
self.setSceneRect(QRectF(self.viewport().rect()))
def mousePressEvent(self, event):
if event.buttons() == Qt.LeftButton:
self._from_x_ = event.pos().x()
self._from_y_ = event.pos().y()
self.scribing = True
self.group = QGraphicsItemGroup()
self.group.setFlags(QGraphicsItem.ItemIsSelectable)
self.scene.addItem(self.group)
self.pp = QPainterPath(event.pos())
self.free_draw_item = QGraphicsPathItem(self.pp)
self.free_draw_item.setPen(self.pen)
# self.free_draw_item.setFlags(QGraphicsItem.ItemIsSelectable)
self.free_draw_item.setPath(self.pp)
self.scene.addItem(self.free_draw_item)
self.group.addToGroup(self.free_draw_item)
if event.buttons() == Qt.RightButton:
self.erasing = True
self.eraser_item = QGraphicsRectItem(event.pos().x() - 5,
event.pos().y() - 5, 10, 10)
self.eraser_item.setPen(QPen(Qt.red))
self.eraser_item.setBrush(Qt.white)
self.scene.addItem(self.eraser_item)
def mouseMoveEvent(self, event):
x_adjust = 1
y_adjust = 1
# if event.pos().x() >= self._from_x_:
# x_adjust = 1
# else:
# x_adjust = -1
# if event.pos().y() >= self._from_y_:
# y_adjust = 1
# else:
# y_adjust = -1
if (event.buttons() & Qt.LeftButton) and self.scribing:
if self.free_draw_item:
# path = self.free_draw_item.path()
# path.lineTo(event.pos())
# self.free_draw_item.setPath(path)
self.pp2 = QPainterPath(QPoint(self._from_x_, self._from_y_))
self.pp2.lineTo(event.pos().x(), event.pos().y())
self.pp2.lineTo(event.pos().x()+x_adjust, event.pos().y()+y_adjust)
self.pp2.lineTo(self._from_x_ + x_adjust, self._from_y_ + y_adjust)
self.free_draw_item.setPen(QPen(Qt.green, 8))
self.free_draw_item.path = QGraphicsPathItem()
self.free_draw_item.path.setPath(self.pp2)
self.scene.addItem(self.free_draw_item.path)
self.group.addToGroup(self.free_draw_item)
self._from_x_ = event.pos().x()
self._from_y_ = event.pos().y()
if event.buttons() & Qt.RightButton and self.erasing:
self.eraser_item.setRect(event.pos().x() - 5, event.pos().y() - 5,
10, 10)
for item in self.scene.collidingItems(self.eraser_item):
new = item.path() - self.eraser_item.shape()
item.setPath(new)
print('collided')
def mouseReleaseEvent(self, event):
self.scribing = False
self.erasing = False
self.group.setSelected(True)
print(self.scene.items())
if self.eraser_item != None:
self.scene.removeItem(self.eraser_item)
# if self.free_draw_item != None:
# self.free_draw_item.setSelected(True)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
If you want to personalize your own Pen I suggest you to overwrite your own QGraphicsPathItem().
I think it'll work pretty great.
I am using PyQT for one of the first times, and I'm having trouble figuring out where a mouse click is in 3D space. Obviously it is not a perfect 1-to-1 mapping, but let's say that I click on a location (x, y, 0) on my QtGui. How can I transform that mouse click to its 3D location using the camera?
Context: I am trying to have users draw splines in 3-D, and in order to do this, I need to know where the user is clicking for when I render the spline. I'm building an application using PythonOCC. I've attached my code below.
import random
import sys
import IPython
from OCC.Display.qtDisplay import qtViewer3d, get_qt_modules
from OCC.gp import gp_Pnt2d, gp_Pnt
from OCC.BRepBuilderAPI import (BRepBuilderAPI_MakeEdge,
BRepBuilderAPI_MakeVertex,
BRepBuilderAPI_MakeWire)
from OCC.BRepFill import BRepFill_Filling
from OCC.GeomAbs import GeomAbs_C0
from OCC.GeomAPI import GeomAPI_PointsToBSpline
from OCC.TColgp import TColgp_Array1OfPnt
QtCore, QtGui, QtOpenGL = get_qt_modules()
try:
from OpenGL.GL import (glViewport, glMatrixMode, glOrtho, glLoadIdentity,
GL_PROJECTION, GL_MODELVIEW)
except ImportError:
msg = "for this example, the OpenGL module is required" \
"why not run \"pip install PyOpenGL\"\?"
sys.exit(status=1)
class GLWidget(qtViewer3d):
def __init__(self, parent=None):
super(GLWidget, self).__init__(parent)
self._initialized = False
midnight = QtCore.QTime(0, 0, 0)
random.seed(midnight.secsTo(QtCore.QTime.currentTime()))
self.object = 0
self.xRot = 0
self.yRot = 0
self.zRot = 0
self.image = QtGui.QImage()
self.bubbles = []
self.lastPos = QtCore.QPoint()
self.lines = []
self.current_point = None
self.pts = []
self.shiftHeld = True
self.trolltechGreen = QtGui.QColor.fromCmykF(0.40, 0.0, 1.0, 0.0)
self.trolltechPurple = QtGui.QColor.fromCmykF(0.39, 0.39, 0.0, 0.0)
self.animationTimer = QtCore.QTimer()
self.animationTimer.setSingleShot(False)
self.animationTimer.timeout.connect(self.animate)
self.animationTimer.start(25)
self.setAutoFillBackground(False)
self.setMinimumSize(200, 200)
self.setWindowTitle("Overpainting a Scene")
# parameters for overpainting
self.setAttribute(QtCore.Qt.WA_NoSystemBackground, 0)
self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent)
def setXRotation(self, angle):
if angle != self.xRot:
self.xRot = angle
def setYRotation(self, angle):
if angle != self.yRot:
self.yRot = angle
def setZRotation(self, angle):
if angle != self.zRot:
self.zRot = angle
def mousePressEvent(self, event):
self.lastPos = event.pos()
super(GLWidget, self).mousePressEvent(event)
worldCoords = super(GLWidget, self).mapToGlobal( self.lastPos )
print self.lastPos
if event.buttons() & QtCore.Qt.RightButton and not (event.modifiers() & QtCore.Qt.ShiftModifier):
print 'first'
self.pts.append(gp_Pnt(self.lastPos.x(), self.lastPos.y(), 0.0))
elif event.buttons() & QtCore.Qt.RightButton and (event.modifiers() & QtCore.Qt.ShiftModifier):
print 'second'
curve = self.points_to_bspline(self.pts)
self._display.DisplayShape(curve, update=True)
self.pts = [] #clear it
def mouseMoveEvent(self, event):
dx = event.x() - self.lastPos.x()
dy = event.y() - self.lastPos.y()
"""
if (event.buttons() & QtCore.Qt.LeftButton):
self.setXRotation(self.xRot + 8 * dy)
self.setYRotation(self.yRot + 8 * dx)
elif (event.buttons() & QtCore.Qt.RightButton):
self.setXRotation(self.xRot + 8 * dy)
self.setZRotation(self.zRot + 8 * dx)
"""
self.lastPos = event.pos()
super(GLWidget, self).mouseMoveEvent(event)
def paintGL(self):
if self._inited:
self._display.Context.UpdateCurrentViewer()
def paintEvent(self, event):
if self._inited:
self._display.Context.UpdateCurrentViewer()
self.makeCurrent()
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
if self.context().isValid():
self.swapBuffers()
if self._drawbox:
painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0), 1))
rect = QtCore.QRect(*self._drawbox)
painter.drawRect(rect)
"""
for bubble in self.bubbles:
if bubble.rect().intersects(QtCore.QRectF(event.rect())):
bubble.drawBubble(painter)
"""
painter.end()
self.doneCurrent()
else:
print('invalid OpenGL context: Qt cannot overpaint viewer')
def showEvent(self, event):
pass
#self.createBubbles(20 - len(self.bubbles))
def sizeHint(self):
return QtCore.QSize(400, 400)
def animate(self):
pass
"""
for bubble in self.bubbles:
bubble.move(self.rect())
self.update()
"""
def setupViewport(self, width, height):
side = min(width, height)
glViewport((width - side) // 2, (height - side) // 2, side, side)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(-0.5, +0.5, +0.5, -0.5, 4.0, 15.0)
glMatrixMode(GL_MODELVIEW)
def points_to_bspline(self, pnts):
pts = TColgp_Array1OfPnt(0, len(pnts)-1)
for n, i in enumerate(pnts):
pts.SetValue(n, i)
crv = GeomAPI_PointsToBSpline(pts)
return crv.Curve()
if __name__ == '__main__':
def TestOverPainting():
class AppFrame(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setWindowTitle(self.tr("qtDisplay3d overpainting example"))
self.resize(640, 480)
self.canva = GLWidget(self)
mainLayout = QtGui.QHBoxLayout()
mainLayout.addWidget(self.canva)
mainLayout.setContentsMargins(0, 0, 0, 0)
self.setLayout(mainLayout)
def runTests(self):
self.canva._display.Test()
app = QtGui.QApplication(sys.argv)
frame = AppFrame()
frame.show()
frame.canva.InitDriver()
frame.runTests()
app.exec_()
TestOverPainting()
To answer my own question:
(x, y, z, vx, vy, vz) = self._display.View.ConvertWithProj(self.lastPos.x(), self.lastPos.y())
Gives the entire line of points that x and y can map to, with x, y, and z being one point, and vx, vy, and vz giving the parameterization of the line.
Since all window managers do not support this feature I have thought copying the background before displaying it.
But this poses several problems.
- The background is not always properly backed up. I have no idea why.
- With this method it is impossible to move the window with the mouse. Because with hide() and show() the focus is lost.
Do you think there is a solution to these problems?
#!/bin/env python3
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor, QPainter, QPen
from PyQt5.QtWidgets import QApplication, QMainWindow
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlags(Qt.FramelessWindowHint)
self.offset= None
self.ps= app.primaryScreen()
self.setGeometry(500, 500, 200, 200)
self.saveBackground()
def keyPressEvent(self, event):
if event.key() == Qt.Key_Escape:
self.close()
elif event.key() in [ Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down ]:
point= self.geometry().topLeft()
if event.key() == Qt.Key_Left:
point.setX(point.x() - 1)
elif event.key() == Qt.Key_Right:
point.setX(point.x() + 1)
elif event.key() == Qt.Key_Up:
point.setY(point.y() - 1)
elif event.key() == Qt.Key_Down:
point.setY(point.y() + 1)
self.move(point)
self.saveBackground()
def mousePressEvent(self, event):
self.offset= event.pos()
def mouseMoveEvent(self, event):
self.move(self.mapToParent(event.pos() - self.offset))
self.saveBackground()
def paintEvent(self, event):
qp= QPainter()
qp.begin(self)
qp.drawPixmap(0, 0, self.pix)
qp.setPen(QPen(QColor(255, 0, 0), 2, Qt.SolidLine))
qp.drawRect(1, 1, self.width() - 2, self.height() - 2)
def saveBackground(self):
geo= self.frameGeometry()
self.hide()
self.pix= self.ps.grabWindow(app.desktop().winId(), geo.x(), geo.y(), geo.width(), geo.height())
self.show()
if __name__ == "__main__":
app= QApplication([])
ui= Window()
ui.show()
exit(app.exec_())
#!/bin/env python3
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor, QPainter, QPen
from PyQt5.QtWidgets import QApplication, QMainWindow
from time import time
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlags(Qt.FramelessWindowHint)
self.offset= None
self.ps= app.primaryScreen()
self.setGeometry(500, 500, 200, 200)
self.saveBackground()
self.csec= time()
self.moving= False
self.ratio= 2
def keyPressEvent(self, event):
if event.key() == Qt.Key_Escape:
self.close()
elif event.key() in [ Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down ]:
if time() > self.csec + 1.2:
point= self.geometry().topLeft()
if event.key() == Qt.Key_Left:
point.setX(point.x() - 1)
elif event.key() == Qt.Key_Right:
point.setX(point.x() + 1)
elif event.key() == Qt.Key_Up:
point.setY(point.y() - 1)
elif event.key() == Qt.Key_Down:
point.setY(point.y() + 1)
self.move(point)
self.csec= time()
self.saveBackground()
super().keyPressEvent(event)
def mousePressEvent(self, event):
self.offset= event.pos()
self.moving= True
self.repaint()
def mouseReleaseEvent(self, event):
self.saveBackground()
self.moving= False
self.repaint()
def mouseMoveEvent(self, event):
self.move(self.mapToParent(event.pos() - self.offset))
def paintEvent(self, event):
qp= QPainter()
qp.begin(self)
if self.moving:
qp.setBrush(QColor(220, 220, 220))
qp.drawRect(0, 0, self.width() - 1, self.height() - 1)
else:
qp.drawPixmap(0, 0, self.pix.scaled(self.size() * self.ratio, Qt.KeepAspectRatio))
qp.setPen(QPen(QColor(255, 0, 0), 1, Qt.SolidLine))
qp.drawRect(0, 0, self.width() - 1, self.height() - 1)
def saveBackground(self):
self.hide()
self.pix= self.ps.grabWindow(app.desktop().winId(), self.x(), self.y(), self.width(), self.height())
self.show()
if __name__ == "__main__":
app= QApplication([])
ui= Window()
ui.show()
exit(app.exec_())