Search code examples
pythonpyqt5qpainterqpixmapqtimer

How to update a QPainter pixmap based on QTimer


I would like to continuously rotate a QPainter pixmap every tick based from a QTimer - in this example a clock arm. I can rotate the clock arm, however I dont have the skills to make the rotation dynamic. Here is the clock I would like to make and below is my sample code. Let me know if you can help me on the way, thanks!

enter image description here

import sys
import random 

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.label = QtWidgets.QLabel()
        self.setCentralWidget(self.label)

        self.Clock_pixmap = QtGui.QPixmap("clock.png")
        self.Arm_pixmap = QtGui.QPixmap("clockarm.png")
        self.painter = QtGui.QPainter(self.Clock_pixmap)
        self.painter.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform)
        self.painter.drawPixmap(QtCore.QPoint(), self.Arm_pixmap)
        self.painter.end()
        self.label.setPixmap(self.Clock_pixmap.scaled(self.label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
        self.label.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        self.label.setMinimumSize(150, 150) 

        self.w1 = self.Arm_pixmap.width()/2
        self.h1 = self.Arm_pixmap.height()/2

        self.rotationData = random.sample(range(100), 100)

        timer = QtCore.QTimer(self, timeout=self.rotateArm, interval=100)
        timer.start()

        self.n=0

    def rotateArm(self):
        self.n+=1
        self.painter.translate(self.w1,self.h1)
        self.painter.rotate(self.rotationData[self.n])
        self.painter.translate(-self.w1,-self.h1)
        self.update()

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

Solution

  • You are painting on a QPainter where you indicated that it was finished painting since you used the end() method. So it is not necessary to make a class attribute to QPainter but only a local variable. Considering the above, the solution is:

    import sys
    import random
    
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
            self._angle = 0
    
            self.label = QtWidgets.QLabel()
            self.setCentralWidget(self.label)
    
            self.clock_pixmap = QtGui.QPixmap("clock.png")
            self.arm_pixmap = QtGui.QPixmap("clockarm.png")
    
            rotation_data = random.sample(range(100), 100)
            self.data_iter = iter(rotation_data)
    
            timer = QtCore.QTimer(self, timeout=self.rotate_arm, interval=100)
            timer.start()
    
        def rotate_arm(self):
            try:
                angle = next(self.data_iter)
            except StopIteration:
                pass
            else:
                self.draw(angle)
    
        def draw(self, angle):
            pixmap = self.clock_pixmap.copy()
            painter = QtGui.QPainter(pixmap)
            painter.setRenderHints(
                QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform
            )
            painter.translate(pixmap.rect().center())
            painter.rotate(angle)
            painter.translate(-pixmap.rect().center())
            painter.drawPixmap(QtCore.QPoint(), self.arm_pixmap)
            painter.end()
            self.label.setPixmap(pixmap)
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        w = MainWindow()
        w.show()
        sys.exit(app.exec_())