简体   繁体   中英

Python PyQt5 2D graphics application: How to draw lines with two end point coordinates and arcs with two end and a center point coordinates

I want to build a PyQt5 application with Python that draws lines and arcs using known (already calculated) point coordinates, ie, lines with two end point and arcs with two end point and a center point. The point coordinates will be calculated from known geometric parameters such as length, angle, and arc radius. I would like to add horizontal sliders to control the geometric parameters and obtain an interactive 2D graphics application similar to the one in the following image. What is the fastest and most efficient way to achieve this with Pyt5 and Python? What 2D drawing libraries would be most suitable?

在此处输入图像描述

I solved this issue with the help from musicamante. I used QT's QPainter class. The drawLine() method is simple to use as it requires only the end point coordinates. The drawArc() method uses start angle and span angle which required additional method to obtain them. Following is the working code.

# Draw lines and arcs.

import sys
import math
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QPen, QColor, QBrush
from PyQt5.QtCore import Qt


def three_points_angle(p1x, p1y, p2x, p2y, c1x, c1y):
        numerator = p1y*(c1x-p2x) + c1y*(p2x-p1x) + p2y*(p1x-c1x)
        denominator = (p1x-c1x)*(c1x-p2x) + (p1y-c1y)*(c1y-p2y)
        ratio = numerator/denominator

        angleRad = math.atan(ratio)
        angleDeg = (angleRad*180)/math.pi

        if angleDeg < 0:
            angleDeg = 180 + angleDeg

        return angleDeg

class MyApp(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setFixedSize(500, 200)
        self.setWindowTitle('Draw Lines and Arcs')
        self.show()

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        self.draw_arc(qp)
        qp.end()

    

    def draw_arc(self, qp):
        qp.setPen(QPen(Qt.blue, 3))

        r1 = 3

        p0y = 0.000000 
        p0x = 56.000000
        p1y = 7.000000 
        p1x = 56.000000
        p2y = 7.000000 
        p2x = 55.500000
        p3y = 3.410242 
        p3x = 53.256870
        p4y = 2.001828 
        p4x = 50.608028
        p5y = 5.000000 
        p5x = 50.712726
        p6y = 3.349775 
        p6x = 12.007856
        
        sf = 490/p1x

        qp.drawLine(round(p0x*sf), round(p0y*sf), round(p1x*sf), round(p1y*sf))
        qp.drawLine(round(p1x*sf), round(p1y*sf), round(p2x*sf), round(p2y*sf))
        qp.drawLine(round(p2x*sf), round(p2y*sf), round(p3x*sf), round(p3y*sf))

        a1_start = three_point_angle(p5x+1, p5y, p3x, p3y, p5x, p5y)
        print("start angle: %f" %a1_start)
        a1_span = three_point_angle(p3x, p3y, p4x, p4y, p5x, p5y)
        print("span angle: %f" %a1_span)
        
        qp.drawArc(round((p5x-r1)*sf), round((p5y-r1)*sf), round(2*r1*sf), round(2*r1*sf), round(a1_start*16), round(a1_span*16))
        qp.drawLine(round(p4x*sf), round(p4y*sf), round(p6x*sf), round(p6y*sf))



if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = MyApp()
    sys.exit(app.exec_())

Output:

在此处输入图像描述

QPainterPath is generally a more appropriate choice for complex and connected drawing paths: not only it provides a unique object representing the path, but it also provides proper painting using dash patterns which wouldn't be (easily) possible with multiple segments.

Qt already provides basic functions to achieve what you want. Specifically, since the center is actually the center of the circle, you already know everything is needed to draw the arc:

  • the rectangle (square) of the ellipse (circle) is the p5 point;
  • its size is twice the length between p5 and p3 (or p4 );
  • the spans are the angles of the lines between p5 and p3 and p5 and p4 ;

Based on your answer, also note that:

  • all basic QPainter functions that accept positional arguments for coordinates ( drawLine(x1, y1, x2, y2) , drawArc(x, y, w, h, angle, span) , etc.) only accept integer values, to achieve precise painting you need to use the relative Qt objects: QPointF, QLineF, QRectF; always check the documentation to see the accepted arguments types (and don't rely too much in automatic python conversions);
  • it is usually better to use QPointF objects (which can also be multiplied by factors) as they provide a single object reference;
  • antialiasing should be used for smooth lines and curves;
  • advanced painting works better with the Graphics View Framework , which also provide easier access to transformation (notably, scaling and rotation);
    def draw_arc(self, qp):
        # ...
        path = QPainterPath(QPointF(p0x, p0y) * sf)
        path.lineTo(QPointF(p1x, p1y) * sf)
        path.lineTo(QPointF(p2x, p2y) * sf)

        start = QPointF(p3x, p3y) * sf
        end = QPointF(p4x, p4y) * sf
        center = QPointF(p5x, p5y) * sf

        # create reference lines to the center of the circle
        startLine = QLineF(center, start)
        endLine = QLineF(center, end)
        radius = startLine.length()
        arcRect = QRectF(center.x() - radius, center.y() - radius, 
            radius * 2, radius * 2)

        # no need to lineTo(start), as arcTo() already connects the previous
        # point to the start angle of the arc
        path.arcTo(arcRect, startLine.angle(), endLine.angle() - startLine.angle())

        path.lineTo(QPointF(p6x, p6y) * sf)

        qp.setRenderHints(qp.Antialiasing)
        qp.drawPath(path)

Note that for arbitrary connections you might need to check the direction of the lines to and from the arc in order to use the proper span angle (which might be negative for counter-clock directions).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM