Python PyQt5二维图形应用:如何画两个端点坐标的线和两个端点和一个圆心坐标的圆弧

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

我想使用 Python 构建一个 PyQt5 应用程序,该应用程序使用已知(已计算)点坐标绘制直线和圆弧,即具有两个端点的线和具有两个端点和一个中心点的圆弧。点坐标将从已知的几何参数(例如长度、角度和圆弧半径)计算得出。我想添加水平滑块来控制几何参数并获得类似于下图中的交互式 2D 图形应用程序。使用 Pyt5 和 Python 实现此目的最快最有效的方法是什么?哪些二维绘图库最合适?

我在 musicamante 的帮助下解决了这个问题。我用的是QT的QPainterclass。 drawLine() 方法使用简单,因为它只需要端点坐标。 drawArc() 方法使用起始角度和跨度角度,这需要额外的方法来获取它们。以下是工作代码。

# 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_())

输出:

QPainterPath 通常是更适合复杂且相互连接的绘图路径的选择:它不仅提供了表示路径的唯一对象,而且还提供了使用破折号图案的正确绘画,这不会(很容易) ) 可能有多个段。

Qt已经提供了基本的功能来实现你想要的。具体来说,由于 center 实际上是圆的中心,您已经知道绘制圆弧所需的一切:

  • 椭圆(圆)的长方形(正方形)为p5点;
  • 它的大小是p5p3(或p4)之间长度的两倍;
  • 跨度是 p5p3 以及 p5p4 之间的线的角度;

根据您的回答,还请注意:

  • 所有接受坐标位置参数(drawLine(x1, y1, x2, y2)drawArc(x, y, w, h, angle, span) 等)的基本 QPainter 函数只接受整数值,要实现精确绘画,您需要使用相关的 Qt 对象:QPointF , QLineF, QRectF;始终检查文档以查看可接受的参数类型(并且不要过分依赖自动 python 转换);
  • 通常最好使用 QPointF 对象(也可以乘以因子),因为它们提供单个对象引用;
  • 平滑的线条和曲线应该使用抗锯齿;
  • 高级绘画与 Graphics View Framework 配合使用时效果更好,它还提供更容易的转换(尤其是缩放和旋转);
    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)

请注意,对于任意连接,您可能需要检查进出圆弧的线的方向,以便使用正确的跨度角(counter-clock 方向可能为负)。