How can I draw a custom shape by using (combining) other basic shapes (Circle and line)?
My problem is that if I want to rotate that custom shape I need to do some math and rotate all other basic shapes and re-positioning. How to avoid that and in one parameter I can rotate the whole of that custom shape.
i want something like this (For example):
orientation_angale = 40 # i.e
custom_shape.rotate(orientation_angale)
Here is the code that draws the above image:
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtCore import QPoint, QPointF
class CustomShape(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.pixmap = QtGui.QPixmap()
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.drawPixmap(self.rect(), self.pixmap)
pen = QtGui.QPen()
pen.setWidth(1)
painter.setPen(pen)
# =========== Draw big circle ===========
x, y = 100, 100
r = 150
painter.drawEllipse(x, y, r, r)
# =========== Draw top line ===========
s_point_x = x + r / 2 # centre of the big circle
s_point_y = y
s_point = QPointF(s_point_x, s_point_y)
e_point_x = x + r / 2 # centre of the big circle
e_point_y = y - 20
e_point = QPointF(e_point_x, e_point_y)
painter.drawLine(s_point, e_point)
# =========== Draw small circle ===========
s_r = 10
s_x, s_y = x + (r / 2) - (s_r / 2), e_point_y - s_r
painter.drawEllipse(s_x, s_y, s_r, s_r)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = CustomShape()
w.resize(400, 400)
w.show()
sys.exit(app.exec_())
here is the default orientation:
I want to rotate that custom shape, so it should look like this (the rotation will applied in 360degrees)
You can use the painter.rotate()
function, but remember that rotation is always around the origin point (0, 0
), so you first have to translate to the center of the rotation, then rotate, and translate back.
Consider that you can use QPainterPath to "store" advanced shapes in a single element (an alternative is to use QPicture which allows to "cache" QPainter actions).
class CustomShape(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.pixmap = QtGui.QPixmap()
self.setMinimumSize(210, 210)
self.path = QtGui.QPainterPath()
self.path.addEllipse(100, 100, 150, 150)
self.path.moveTo(175, 100)
self.path.lineTo(175, 80)
self.path.addEllipse(170, 70, 10, 10)
self.center = QtCore.QPoint(175, 175)
self.angle = 0
def setAngle(self, angle):
self.angle = angle % 360
self.update()
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.drawPixmap(self.rect(), self.pixmap)
painter.setRenderHint(painter.Antialiasing)
painter.translate(self.center)
painter.rotate(self.angle)
painter.translate(-self.center)
painter.drawPath(self.path)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = CustomShape()
w.resize(400, 400)
timer = QtCore.QTimer(interval=100)
timer.timeout.connect(lambda: w.setAngle(w.angle + 1))
timer.start()
w.show()
sys.exit(app.exec_())
Be aware that Qt provides helper functions that can be used to do computations for simple geometries and positions; while not always optimal for frequent updates, since it relies on C++ functions it's usually faster than computing everything on the python side, or, at least, it provides a better readable form:
class CustomShape(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.pixmap = QtGui.QPixmap()
self.setMinimumSize(210, 210)
self.center = QtCore.QPoint(175, 175)
self.bigRadius = 75
self.smallRadius = 5
self.distance = 20
self.angle = 0
def setAngle(self, angle):
self.angle = angle % 360
self.update()
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.drawPixmap(self.rect(), self.pixmap)
painter.setRenderHint(painter.Antialiasing)
painter.drawEllipse(self.center, self.bigRadius, self.bigRadius)
fullExtent = self.bigRadius + self.distance
# create a line that extents to the edge of the small circle
line = QtCore.QLineF.fromPolar(fullExtent, 90 - self.angle)
# translate it to the center
line.translate(self.center)
# move the origin point based on the ratio of the radius
line.setP1(line.pointAt(self.bigRadius / fullExtent))
painter.drawLine(line)
# create a line similar to the above to get the center of the
# small circle
otherLine = QtCore.QLineF.fromPolar(
fullExtent + self.smallRadius, 90 - self.angle)
otherLine.translate(self.center)
painter.drawEllipse(otherLine.p2(), self.smallRadius, self.smallRadius)