2

I'm trying to rotate the background of my QGraphicsView object an I am getting in trouble. The red pen describes the problem what I'm getting.

What I want:

  • I want to keep the arrow centered in the middle of my screen.
  • I want the background image to rotate, taking the center of my screen as reference
  • I want to define a rectangle inside my QGraphicsView. that will be defined as the region where it will delimit what will be shown. Thus, every time I rotate my screen. The region "not covered" by the image will always be outside the boundary of the rectangle.
  • I want define the reference point of rotation, in the middle of my screen (x=VIEW_WIDTH/2, y=VIEW_HEIGHT/2) enter image description here

And here is my code:

First, I'll show the arrow class:


from PyQt5 import QtCore, QtGui, QtWidgets # importation of some libraries 

# Construnction of an arrow item, it'll be used in the QGraphicsView 
class ArrowItem(QtWidgets.QGraphicsPathItem): # it inherit QgraphicsPathItem, which allows to handle it 
# in the QgraphicsView
    def __init__(self, parent=None): # The init method
        super().__init__(parent)
        self._length = -1
        self._angle = 0
        self._points = QtCore.QPointF(), QtCore.QPointF(), QtCore.QPointF() # with three points we 
# construct a triangle. 
        self.length = 40.0 # the basic triangle length 
        self.rotate(180) # the triangle was built upside down, though I've just ran the function 'rotate'

    @property
    def angle(self):
        """
        angle of the arrow
        :return:
        """
        return self._angle

    @angle.setter
    def angle(self, angle):
        self._angle = angle

    @property
    def length(self):
        return self._length

    @length.setter
    def length(self, l):
        self._length = l

        pos_top = QtCore.QPointF(0, l * 4 / 5)
        pos_left = QtCore.QPointF(-l * 3 / 5, -l / 5)
        pos_right = QtCore.QPointF(
            l * 3 / 5,
            -l / 5,
        )

        path = QtGui.QPainterPath()
        path.moveTo(pos_top)
        path.lineTo(pos_right)
        path.lineTo(pos_left)

        self.setPath(path)

        self._points = pos_top, pos_left, pos_right

    def paint(self, painter, option, widget):
        pos_top, pos_left, pos_right = self._points

        left_color = QtGui.QColor("#cc0000")
        right_color = QtGui.QColor("#ff0000")
        bottom_color = QtGui.QColor("#661900")

        path_left = QtGui.QPainterPath()
        path_left.lineTo(pos_top)
        path_left.lineTo(pos_left)

        path_right = QtGui.QPainterPath()
        path_right.lineTo(pos_top)
        path_right.lineTo(pos_right)

        path_bottom = QtGui.QPainterPath()
        path_bottom.lineTo(pos_left)
        path_bottom.lineTo(pos_right)

        painter.setPen(QtGui.QColor("black"))
        painter.setBrush(left_color)
        painter.drawPath(path_left)
        painter.setBrush(right_color)
        painter.drawPath(path_right)
        painter.setBrush(bottom_color)
        painter.drawPath(path_bottom)

    def rotate(self, value):
        """

        :param value:
        """

        self._angle += value
        transform = QtGui.QTransform()
        transform.translate(20, 100)
        transform.rotate(self._angle)

        self.setTransform(transform)

    # Just a function to translate the arrow, it'll not be used now!
    def moveTo(self, next_position, duration=100):
        self._animation = QtCore.QVariantAnimation()
        self._animation.setStartValue(self.pos())
        self._animation.setEndValue(next_position)
        self._animation.setDuration(duration)
        self._animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
        self._animation.valueChanged.connect(self.setPos)

Here follows the main window code:

# Just setting up the main Window 
class WorkScreen(QtWidgets.QMainWindow):
   # Some constant variable to keep things easy to change in the future
    VIEW_HEIGHT = 600
    VIEW_WIDTH = 900
    FLAT_BUTTON = True
    """
    Attempt to paint the work screen.
    """

    def __init__(self, **kwargs):
        # initializing the parent class, using a 2.7 pythonic style
        super(WorkScreen, self).__init__(**kwargs)
        # customizing the main window.
        self.setStyleSheet("""
                    QMainWindow{
                    background: rgb(180, 180, 180)}
                    
                    
                    QPushButton{
                     background-color: rgb(0, 200, 0);
                      
                    }

                """)
        # the attribute 'rotation' must be zero.
        self.rotation = 0
        # defining some attributes
        self.pixmap = QtGui.QPixmap()
        self.scene = QtWidgets.QGraphicsScene()

        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
        self.verticalLayout_3 = QtWidgets.QVBoxLayout()
        self.pushButton_status = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_settings = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_analyse = QtWidgets.QPushButton(self.centralwidget)
        self.verticalLayout_2 = QtWidgets.QVBoxLayout()
        self.view = QtWidgets.QGraphicsView(self.centralwidget)
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.pushButton_field = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_guide = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_mapping = QtWidgets.QPushButton(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.arrowItem = ArrowItem()
        self.set_up()
        self.view.setScene(self.scene)
        self.view.scale(-1, 1)
        self.image = QtGui.QImage("ola.png")
        self.image = self.image.scaled(self.VIEW_WIDTH+300, self.VIEW_HEIGHT+300)
        self.pixmap.convertFromImage(self.image)
        self.scene.addPixmap(self.pixmap)
        self.add_arrow()

    def set_up(self):
        """
        build the main window.
        """
        self.pushButton_guide.setFlat(self.FLAT_BUTTON)
        self.pushButton_mapping.setFlat(self.FLAT_BUTTON)
        self.pushButton_field.setFlat(self.FLAT_BUTTON)
        self.pushButton_status.setFlat(self.FLAT_BUTTON)
        self.pushButton_settings.setFlat(self.FLAT_BUTTON)
        self.pushButton_analyse.setFlat(self.FLAT_BUTTON)
        self.setObjectName("MainWindow")
        self.setMaximumWidth(1024)
        self.setMaximumHeight(600)
        self.resize(1024, 600)
        self.centralwidget.setObjectName("centralwidget")
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.verticalLayout_3.setContentsMargins(-1, -1, 0, -1)
        self.verticalLayout_3.setObjectName("verticalLayout_3")
        self.pushButton_status.setObjectName("pushButton_status")
        self.verticalLayout_3.addWidget(self.pushButton_status)
        self.pushButton_settings.setObjectName("pushButton_settings")
        self.verticalLayout_3.addWidget(self.pushButton_settings)
        self.pushButton_analyse.setObjectName("pushButton_analyse")
        self.verticalLayout_3.addWidget(self.pushButton_analyse)
        self.horizontalLayout.addLayout(self.verticalLayout_3)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.view.setRenderHints(QtGui.QPainter.HighQualityAntialiasing | QtGui.QPainter.TextAntialiasing)
        self.view.setCacheMode(QtWidgets.QGraphicsView.CacheBackground)
        self.view.scale(-1, 1)
        self.view.setFixedHeight(self.VIEW_HEIGHT)
        self.view.setFixedWidth(self.VIEW_WIDTH)
        self.view.setObjectName("view")
        # self.view.setFixedWidth(self.VIEW_WIDTH)
        # self.view.setFixedHeight(self.VIEW_HEIGHT)
        self.verticalLayout_2.addWidget(self.view)
        self.horizontalLayout.addLayout(self.verticalLayout_2)
        self.verticalLayout.setContentsMargins(-1, -1, 0, -1)
        self.verticalLayout.setObjectName("verticalLayout")
        self.pushButton_field.setObjectName("pushButton_field")
        self.verticalLayout.addWidget(self.pushButton_field)
        self.pushButton_guide.setObjectName("pushButton_guide")
        self.verticalLayout.addWidget(self.pushButton_guide)
        self.pushButton_mapping.setObjectName("pushButton_mapping")
        self.verticalLayout.addWidget(self.pushButton_mapping)
        self.horizontalLayout.addLayout(self.verticalLayout)
        self.setCentralWidget(self.centralwidget)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 26))
        self.menubar.setObjectName("menubar")
        self.setMenuBar(self.menubar)
        self.statusbar.setObjectName("statusbar")
        self.setStatusBar(self.statusbar)

        self.retranslateUi()
        QtCore.QMetaObject.connectSlotsByName(self)
        # Just to test
        self.pushButton_status.clicked.connect(self.rotate_arrow)
        self.pushButton_guide.clicked.connect(self.rotate_pixmap)

    # Created just to emit a signal
    def rotate_arrow(self):
        """
        It'll not be used after.
        """
        self.arrowItem.rotate(value=30)

    def rotate_pixmap(self):
        pixmap = self.pixmap

        self.rotation += 15
        transform = QtGui.QTransform().rotate(self.rotation, axis=QtCore.Qt.ZAxis)
        pixmap = pixmap.transformed(transform, QtCore.Qt.SmoothTransformation)
        self.scene.addPixmap(pixmap)
        self.arrowItem.setPos(self.arrowItem.pos())
        self.arrowItem.rotate(self.rotation)
        self.arrowItem.setZValue(self.arrowItem.zValue()+1)





    def add_arrow(self):
        """

        :cvar

        """
        self.scene.addItem(self.arrowItem)
        self.arrowItem.setPos(QtCore.QPointF(self.VIEW_WIDTH / 2 - 50, self.VIEW_HEIGHT / 2 - 100))

    def retranslateUi(self):
        _translate = QtCore.QCoreApplication.translate
        self.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton_status.setText(_translate("MainWindow", "Situação"))
        self.pushButton_settings.setText(_translate("MainWindow", "Configurações"))
        self.pushButton_analyse.setText(_translate("MainWindow", "Analisar"))
        self.pushButton_field.setText(_translate("MainWindow", "Campo"))
        self.pushButton_guide.setText(_translate("MainWindow", "Guia"))
        self.pushButton_mapping.setText(_translate("MainWindow", "Mapeamento"))

# calling the application 
if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = WorkScreen()
    ui.show()
    sys.exit(app.exec_())

Here is the image that I am using to display on the background:

enter image description here

  • No, it's not. Is there a way to define a square region in the center of the screen that will be shown? That way, the discovered regions will always be outside that square. I was trying to do this by defining a quad region for Qgraphicscene, but to no avail. – Iaggo Capitanio Sep 13 '20 at 20:39
  • The reference point that I would like to use to rotate, must be the center of my Qgraphicsview. Do I define this in Qtransform? – Iaggo Capitanio Sep 13 '20 at 20:40
  • Sorry, I will improve it next time. – Iaggo Capitanio Sep 13 '20 at 20:53

1 Answers1

2

If you want to rotate a QGraphicsItem it is not necessary to use QTransform, just use the setRotation method. On the other hand, the image must be scaled so that the rotated area intersected by the visible area is the same as the visible area and that minimum area is calculated as: sqrt(2) x max(width, height). Considering the above, the solution is:

from PyQt5 import QtCore, QtGui, QtWidgets


class ArrowItem(QtWidgets.QGraphicsPathItem):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._length = -1
        self._points = (
            QtCore.QPointF(),
            QtCore.QPointF(),
            QtCore.QPointF(),
        )
        self.length = 40.0

    @property
    def length(self):
        return self._length

    @length.setter
    def length(self, l):
        self._length = l

        pos_top = QtCore.QPointF(0, l * 4 / 5)
        pos_left = QtCore.QPointF(-l * 3 / 5, -l / 5)
        pos_right = QtCore.QPointF(
            l * 3 / 5,
            -l / 5,
        )

        path = QtGui.QPainterPath()
        path.moveTo(pos_top)
        path.lineTo(pos_right)
        path.lineTo(pos_left)

        self.setPath(path)

        self._points = pos_top, pos_left, pos_right

    def paint(self, painter, option, widget):
        pos_top, pos_left, pos_right = self._points

        left_color = QtGui.QColor("#cc0000")
        right_color = QtGui.QColor("#ff0000")
        bottom_color = QtGui.QColor("#661900")

        path_left = QtGui.QPainterPath()
        path_left.lineTo(pos_top)
        path_left.lineTo(pos_left)

        path_right = QtGui.QPainterPath()
        path_right.lineTo(pos_top)
        path_right.lineTo(pos_right)

        path_bottom = QtGui.QPainterPath()
        path_bottom.lineTo(pos_left)
        path_bottom.lineTo(pos_right)

        painter.setPen(QtGui.QColor("black"))
        painter.setBrush(left_color)
        painter.drawPath(path_left)
        painter.setBrush(right_color)
        painter.drawPath(path_right)
        painter.setBrush(bottom_color)
        painter.drawPath(path_bottom)

    def moveTo(self, next_position, duration=100):
        self._animation = QtCore.QVariantAnimation()
        self._animation.setStartValue(self.pos())
        self._animation.setEndValue(next_position)
        self._animation.setDuration(duration)
        self._animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
        self._animation.valueChanged.connect(self.setPos)


class MainWindow(QtWidgets.QMainWindow):
    VIEW_HEIGHT = 600
    VIEW_WIDTH = 900

    def __init__(self, parent=None):
        super().__init__(parent)

        self.scene = QtWidgets.QGraphicsScene(self)
        self.view = QtWidgets.QGraphicsView(self.scene)
        self.status_btn = QtWidgets.QPushButton("Status")
        self.settings_btn = QtWidgets.QPushButton("Settings")
        self.analyze_btn = QtWidgets.QPushButton("Analyze")
        self.field_btn = QtWidgets.QPushButton("Field")
        self.guide_btn = QtWidgets.QPushButton("Guide")
        self.mapping_btn = QtWidgets.QPushButton("Mapping")

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)

        vlayl = QtWidgets.QVBoxLayout()
        vlayl.addWidget(self.status_btn)
        vlayl.addWidget(self.settings_btn)
        vlayl.addWidget(self.analyze_btn)

        vlayr = QtWidgets.QVBoxLayout()
        vlayr.addWidget(self.field_btn)
        vlayr.addWidget(self.guide_btn)
        vlayr.addWidget(self.mapping_btn)

        hlay = QtWidgets.QHBoxLayout(central_widget)
        hlay.addLayout(vlayl)
        hlay.addWidget(self.view)
        hlay.addLayout(vlayr)

        self.status_btn.clicked.connect(self.rotate_arrow)
        self.guide_btn.clicked.connect(self.rotate_pixmap)

        self.view.setFixedSize(self.VIEW_WIDTH, self.VIEW_HEIGHT)
        r = self.view.mapToScene(self.view.viewport().rect()).boundingRect()
        self.view.setSceneRect(r)

        factor = 1.5 * max(self.VIEW_WIDTH, self.VIEW_HEIGHT)
        pixmap = QtGui.QPixmap("ola.png").scaled(factor, factor)
        self.pixmap_item = self.scene.addPixmap(pixmap)
        center = self.pixmap_item.boundingRect().center()
        self.pixmap_item.setPos(-center)
        self.pixmap_item.setTransformOriginPoint(center)

        self.arrow_item = ArrowItem()
        self.scene.addItem(self.arrow_item)

    def rotate_arrow(self):
        delta = 30.0
        self.arrow_item.setRotation(self.arrow_item.rotation() + delta)

    def rotate_pixmap(self):
        delta = 15.0

        self.pixmap_item.setRotation(self.pixmap_item.rotation() + delta)
        self.arrow_item.setRotation(self.arrow_item.rotation() + delta)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241