PyQt 实现飞控仪表盘:姿态、高度、速度,飞行数据实时显示(二)

AI1周前发布 beixibaobao
13 0 0

PyQt 实现飞控仪表盘:姿态、高度、速度,飞行数据实时显示(二)

  • 一、说明
  • 二、源码分享
    • 1、效果展示
    • 2、源码分享
      • 2.1、alt_widget.py
      • 2.2、asi_widget.py
      • 2.3、hsi_widget.py
      • 2.4、main.py
      • 2.5、库安装
      • 2.6、源码及资源下载
  • 三、实现原理
    • 1、 核心作用与特点
      • 1.1、 虚拟坐标系
      • 1.2 、图形项容器
      • 1.3、 事件传播与信号
    • 2、 在仪表示例中的关键用法
      • 2.1、 创建与设置
      • 2.2 、添加图形项
      • 2.3、 场景更新与渲染
      • 2.4 、场景变换与视图适配
      • 2.5 、坐标转换
    • 3、 常用方法总结
    • 4、性能与最佳实践
    • 5、总结

一、说明

由于篇幅限制此篇只显示3个仪表,剩余见下一篇

二、源码分享

1、效果展示

在这里插入图片描述

在这里插入图片描述


在这里插入图片描述

2、源码分享

2.1、alt_widget.py

import math
from PyQt6.QtWidgets import QGraphicsView, QWidget, QGraphicsScene
from PyQt6.QtSvgWidgets import QGraphicsSvgItem
from PyQt6.QtCore import QPointF, Qt, QRectF
from PyQt6.QtGui import QTransform
class Alt(QGraphicsView):
    def __init__(self, parent: QWidget = None):
        super().__init__(parent)
        # 固定原始场景尺寸240*240,统一坐标系
        self.m_scene = QGraphicsScene(QRectF(0, 0, 240, 240), self)
        self.setScene(self.m_scene)
        self.m_scene.clear()
        # 彻底关闭滚动条、边框、留白边距
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        self.setFrameStyle(0)
        self.setViewportMargins(0, 0, 0, 0)
        self.setTransformationAnchor(QGraphicsView.ViewportAnchor.NoAnchor)
        self.setResizeAnchor(QGraphicsView.ViewportAnchor.NoAnchor)
        # SVG图元对象
        self.m_itemFace_1 = None
        self.m_itemFace_2 = None
        self.m_itemFace_3 = None
        self.m_itemHand_1 = None
        self.m_itemHand_2 = None
        self.m_itemCase = None
        # 仪表参数
        self.m_altitude = 0.0
        self.m_pressure = 28.0
        # 缩放系数
        self.m_scaleX = 1.0
        self.m_scaleY = 1.0
        # 基础常量
        self.m_originalAltCtr = QPointF(120.0, 120.0)
        self.m_originalHeight = 240
        self.m_originalWidth = 240
        # Z层级(和C++完全一致,渲染堆叠顺序)
        self.m_face1Z = -50
        self.m_face2Z = -40
        self.m_face3Z = -30
        self.m_hand1Z = -20
        self.m_hand2Z = -10
        self.m_caseZ = 10
        self.init()
    def reinit(self):
        if self.m_scene:
            self.m_scene.clear()
            self.init()
    def update(self):
        self.updateView()
    # 对外接口:设置高度
    def setAltitude(self, altitude: float):
        self.m_altitude = altitude
    # 对外接口:设置气压基准,限幅28.0 ~ 31.5
    def setPressure(self, pressure: float):
        self.m_pressure = pressure
        if self.m_pressure < 28.0:
            self.m_pressure = 28.0
        if self.m_pressure > 31.5:
            self.m_pressure = 31.5
    def resizeEvent(self, event):
        super().resizeEvent(event)
        self.reinit()
    def init(self):
        self.reset()
        w = self.width()
        h = self.height()
        self.m_scaleX = w / self.m_originalWidth
        self.m_scaleY = h / self.m_originalHeight
        # 加载所有SVG图层,原始240画布,不手动缩放
        self.m_itemFace_1 = QGraphicsSvgItem("images/alt/alt_face_1.svg")
        self.m_itemFace_1.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
        self.m_itemFace_1.setZValue(self.m_face1Z)
        self.m_itemFace_1.setTransformOriginPoint(self.m_originalAltCtr)
        self.m_scene.addItem(self.m_itemFace_1)
        self.m_itemFace_2 = QGraphicsSvgItem("images/alt/alt_face_2.svg")
        self.m_itemFace_2.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
        self.m_itemFace_2.setZValue(self.m_face2Z)
        self.m_scene.addItem(self.m_itemFace_2)
        self.m_itemFace_3 = QGraphicsSvgItem("images/alt/alt_face_3.svg")
        self.m_itemFace_3.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
        self.m_itemFace_3.setZValue(self.m_face3Z)
        self.m_itemFace_3.setTransformOriginPoint(self.m_originalAltCtr)
        self.m_scene.addItem(self.m_itemFace_3)
        self.m_itemHand_1 = QGraphicsSvgItem("images/alt/alt_hand_1.svg")
        self.m_itemHand_1.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
        self.m_itemHand_1.setZValue(self.m_hand1Z)
        self.m_itemHand_1.setTransformOriginPoint(self.m_originalAltCtr)
        self.m_scene.addItem(self.m_itemHand_1)
        self.m_itemHand_2 = QGraphicsSvgItem("images/alt/alt_hand_2.svg")
        self.m_itemHand_2.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
        self.m_itemHand_2.setZValue(self.m_hand2Z)
        self.m_itemHand_2.setTransformOriginPoint(self.m_originalAltCtr)
        self.m_scene.addItem(self.m_itemHand_2)
        self.m_itemCase = QGraphicsSvgItem("images/alt/alt_case.svg")
        self.m_itemCase.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
        self.m_itemCase.setZValue(self.m_caseZ)
        self.m_scene.addItem(self.m_itemCase)
        # Qt原生自动居中、等比例放大,替代旧centerOn
        self.fitInView(self.m_scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio)
        self.updateView()
    def reset(self):
        # 清空图元指针
        self.m_itemFace_1 = None
        self.m_itemFace_2 = None
        self.m_itemFace_3 = None
        self.m_itemHand_1 = None
        self.m_itemHand_2 = None
        self.m_itemCase = None
        # 重置参数默认值
        self.m_altitude = 0.0
        self.m_pressure = 28.0
    def updateView(self):
        # 复刻C++ std::ceil(m_altitude + 0.5) 四舍五入取整
        altitude_int = math.ceil(self.m_altitude + 0.5)
        # 完全一致的旋转角度计算公式
        angleH1 = self.m_altitude * 0.036
        angleH2 = (altitude_int % 1000) * 0.36
        angleF1 = (self.m_pressure - 28.0) * 100.0
        angleF3 = self.m_altitude * 0.0036
        # 分配旋转角度
        self.m_itemHand_1.setRotation(angleH1)
        self.m_itemHand_2.setRotation(angleH2)
        self.m_itemFace_1.setRotation(-angleF1)
        self.m_itemFace_3.setRotation(angleF3)
        self.m_scene.update()

2.2、asi_widget.py

from PyQt6.QtWidgets import QGraphicsView, QWidget, QGraphicsScene
from PyQt6.QtSvgWidgets import QGraphicsSvgItem
from PyQt6.QtCore import QPointF, Qt, QRectF
class Asi(QGraphicsView):
    def __init__(self, parent: QWidget = None):
        super().__init__(parent)
        # 固定原始场景尺寸240*240
        self.m_scene = QGraphicsScene(QRectF(0, 0, 240, 240), self)
        self.setScene(self.m_scene)
        self.m_scene.clear()
        # 关闭滚动条、边框、边距
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        self.setFrameStyle(0)
        self.setViewportMargins(0, 0, 0, 0)
        self.setTransformationAnchor(QGraphicsView.ViewportAnchor.NoAnchor)
        self.setResizeAnchor(QGraphicsView.ViewportAnchor.NoAnchor)
        # SVG图元
        self.m_itemFace = None
        self.m_itemHand = None
        self.m_itemCase = None
        # 空速参数
        self.m_airspeed = 0.0
        # 缩放基础参数
        self.m_scaleX = 1.0
        self.m_scaleY = 1.0
        self.m_originalAsiCtr = QPointF(120.0, 120.0)
        self.m_originalHeight = 240
        self.m_originalWidth = 240
        # Z渲染层级(和C++完全一致)
        self.m_faceZ = -20
        self.m_handZ = -10
        self.m_caseZ = 10
        self.init()
    def reinit(self):
        if self.m_scene:
            self.m_scene.clear()
            self.init()
    def update(self):
        self.updateView()
    # 对外接口:空速限幅 0 ~ 235
    def setAirspeed(self, airspeed: float):
        self.m_airspeed = airspeed
        if self.m_airspeed < 0.0:
            self.m_airspeed = 0.0
        if self.m_airspeed > 235.0:
            self.m_airspeed = 235.0
    def resizeEvent(self, event):
        super().resizeEvent(event)
        self.reinit()
    def init(self):
        self.reset()
        w = self.width()
        h = self.height()
        self.m_scaleX = w / self.m_originalWidth
        self.m_scaleY = h / self.m_originalHeight
        # 加载SVG图层,原始240画布,不手动缩放
        self.m_itemFace = QGraphicsSvgItem("images/asi/asi_face.svg")
        self.m_itemFace.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
        self.m_itemFace.setZValue(self.m_faceZ)
        self.m_scene.addItem(self.m_itemFace)
        self.m_itemHand = QGraphicsSvgItem("images/asi/asi_hand.svg")
        self.m_itemHand.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
        self.m_itemHand.setZValue(self.m_handZ)
        self.m_itemHand.setTransformOriginPoint(self.m_originalAsiCtr)
        self.m_scene.addItem(self.m_itemHand)
        self.m_itemCase = QGraphicsSvgItem("images/asi/asi_case.svg")
        self.m_itemCase.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
        self.m_itemCase.setZValue(self.m_caseZ)
        self.m_scene.addItem(self.m_itemCase)
        # Qt原生自动居中、等比例适配
        self.fitInView(self.m_scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio)
        self.updateView()
    def reset(self):
        self.m_itemFace = None
        self.m_itemHand = None
        self.m_itemCase = None
        self.m_airspeed = 0.0
    def updateView(self):
        # 复刻C++分段角度计算公式
        if self.m_airspeed < 40.0:
            angle = 0.9 * self.m_airspeed
        elif self.m_airspeed < 70.0:
            angle = 36.0 + 1.8 * (self.m_airspeed - 40.0)
        elif self.m_airspeed < 130.0:
            angle = 90.0 + 2.0 * (self.m_airspeed - 70.0)
        elif self.m_airspeed < 160.0:
            angle = 210.0 + 1.8 * (self.m_airspeed - 130.0)
        else:
            angle = 264.0 + 1.2 * (self.m_airspeed - 160.0)
        self.m_itemHand.setRotation(angle)
        self.m_scene.update()

2.3、hsi_widget.py

from PyQt6.QtWidgets import QGraphicsView, QWidget, QGraphicsScene
from PyQt6.QtSvgWidgets import QGraphicsSvgItem
from PyQt6.QtCore import QPointF, Qt, QRectF
class Hsi(QGraphicsView):
    def __init__(self, parent: QWidget = None):
        super().__init__(parent)
        # 固定原始画布尺寸240×240
        self.m_scene = QGraphicsScene(QRectF(0, 0, 240, 240), self)
        self.setScene(self.m_scene)
        self.m_scene.clear()
        # 彻底关闭滚动条、控件边框、留白边距
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        self.setFrameStyle(0)
        self.setViewportMargins(0, 0, 0, 0)
        self.setTransformationAnchor(QGraphicsView.ViewportAnchor.NoAnchor)
        self.setResizeAnchor(QGraphicsView.ViewportAnchor.NoAnchor)
        # SVG图元对象
        self.m_itemFace = None
        self.m_itemCase = None
        # 航向参数
        self.m_heading = 0.0
        # 基础常量
        self.m_scaleX = 1.0
        self.m_scaleY = 1.0
        self.m_originalHsiCtr = QPointF(120.0, 120.0)
        self.m_originalHeight = 240
        self.m_originalWidth = 240
        # Z渲染层级(和C++完全一致)
        self.m_faceZ = -20
        self.m_caseZ = 10
        self.init()
    def reinit(self):
        if self.m_scene:
            self.m_scene.clear()
            self.init()
    def update(self):
        self.updateView()
    # 对外接口:设置航向角
    def setHeading(self, heading: float):
        self.m_heading = heading
    def resizeEvent(self, event):
        super().resizeEvent(event)
        self.reinit()
    def init(self):
        self.reset()
        w = self.width()
        h = self.height()
        self.m_scaleX = w / self.m_originalWidth
        self.m_scaleY = h / self.m_originalHeight
        # 加载SVG,原始240画布,不手动缩放矩阵
        self.m_itemFace = QGraphicsSvgItem("images/hsi/hsi_face.svg")
        self.m_itemFace.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
        self.m_itemFace.setZValue(self.m_faceZ)
        self.m_itemFace.setTransformOriginPoint(self.m_originalHsiCtr)
        self.m_scene.addItem(self.m_itemFace)
        self.m_itemCase = QGraphicsSvgItem("images/hsi/hsi_case.svg")
        self.m_itemCase.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
        self.m_itemCase.setZValue(self.m_caseZ)
        self.m_scene.addItem(self.m_itemCase)
        # Qt原生自动居中、等比例放大,替代旧centerOn
        self.fitInView(self.m_scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio)
        self.updateView()
    def reset(self):
        self.m_itemFace = None
        self.m_itemCase = None
        self.m_heading = 0.0
    def updateView(self):
        # 航向刻度盘反向旋转
        self.m_itemFace.setRotation(-self.m_heading)
        self.m_scene.update()

2.4、main.py

import sys
import math
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QGridLayout
from PyQt6.QtCore import QTimer, QElapsedTimer
from adi_widget import Adi
from alt_widget import Alt
from asi_widget import Asi
from hsi_widget import Hsi
from tc_widget import Tc
from nav_widget import Nav
from vsi_widget import Vsi
class MainWindow(QMainWindow):
    # 全局常量统一管理
    REFRESH_INTERVAL = 30
    GRID_SPACING = 6
    def __init__(self):
        super().__init__()
        self.setWindowTitle("多仪表独立布局仿真 3行3列")
        self.resize(1400, 1000)
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        grid = QGridLayout(central_widget)
        grid.setSpacing(self.GRID_SPACING)
        # 3列均分
        for col in range(3):
            grid.setColumnStretch(col, 1)
        # 3行均分
        for row in range(3):
            grid.setRowStretch(row, 1)
        # ====================== 9个独立仪表实例 w0~w8(包含w6 w7) ======================
        # 第一行
        self.w0 = Tc()    # 0行0列
        self.w1 = Nav()   # 0行1列
        self.w2 = Adi()   # 0行2列
        # 第二行
        self.w3 = Alt()   # 1行0列
        self.w4 = Asi()   # 1行1列
        self.w5 = Hsi()   # 1行2列
        # 第三行(新增 w6 w7 w8)
        self.w6 = Vsi()   # 2行0列 垂直速度表
        self.w7 = QWidget()    # 2行1列 转弯协调仪
        self.w8 = QWidget()   # 2行2列 HSI航向仪
        # 网格摆放
        grid.addWidget(self.w0, 0, 0)
        grid.addWidget(self.w1, 0, 1)
        grid.addWidget(self.w2, 0, 2)
        grid.addWidget(self.w3, 1, 0)
        grid.addWidget(self.w4, 1, 1)
        grid.addWidget(self.w5, 1, 2)
        grid.addWidget(self.w6, 2, 0)
        grid.addWidget(self.w7, 2, 1)
        grid.addWidget(self.w8, 2, 2)
        # 计时与定时器
        self.time_counter = QElapsedTimer()
        self.time_counter.start()
        self.real_time = 0.0
        self.timer = QTimer(self)
        self.timer.setInterval(self.REFRESH_INTERVAL)
        self.timer.timeout.connect(self.timer_event)
        self.timer.start()
    def timer_event(self):
        # 计算帧时间步长
        step_ms = self.time_counter.restart()
        self.real_time += step_ms / 1000.0
        # TC仪表共用动态参数
        slip_skid = 1.0 * math.sin(self.real_time / 10.0)
        turn_rate = 7.0 * math.sin(self.real_time / 10.0)
        # w0 TC
        self.w0.setSlipSkid(slip_skid * 15.0)
        self.w0.setTurnRate(turn_rate)
        self.w0.update()
        # Nav仪表(w1)动态参数,消除固定000文字
        heading = 360.0 * math.sin(self.real_time / 40.0)
        adf = -360.0 * math.sin(self.real_time / 50.0)
        dev_h = 1.0 * math.sin(self.real_time / 20.0)
        dme = 99.0 * math.sin(self.real_time / 100.0)
        heading_bug = heading * 0.7
        course = heading * 0.5
        self.w1.setHeading(heading)
        self.w1.setHeadingBug(heading_bug)
        self.w1.setCourse(course)
        self.w1.setBearing(adf, True)
        self.w1.setDeviation(dev_h, True)
        self.w1.setDistance(dme, True)
        self.w1.update()
        # ADI w2
        roll = 180.0 * math.sin(self.real_time / 10.0)
        pitch = 90.0 * math.sin(self.real_time / 20.0)
        self.w2.setPitch(pitch)
        self.w2.setRoll(roll)
        self.w2.update()
        # ALT w3
        altitude = 9000.0 * math.sin(self.real_time / 40.0) + 9000.0
        pressure = 2.0 * math.sin(self.real_time / 20.0) + 30.0
        self.w3.setAltitude(altitude)
        self.w3.setPressure(pressure)
        self.w3.update()
        # ASI w4
        airspeed = 125.0 * math.sin(self.real_time / 40.0) + 125.0
        self.w4.setAirspeed(airspeed)
        self.w4.update()
        # HSI w5
        self.w5.setHeading(heading)
        self.w5.update()
        # ===================== 新增w6 VSI垂直速度 =====================
        climb_rate = 1800 * math.sin(self.real_time / 20.0)
        self.w6.setClimbRate(climb_rate)
        self.w6.update()
if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = MainWindow()
    win.show()
    sys.exit(app.exec())

2.5、库安装

在这里插入图片描述

2.6、源码及资源下载

见文章顶部绑定资源

三、实现原理

QGraphicsScene 是 PyQt6 图形视图框架 (QGraphicsView, QGraphicsItem) 的核心组件之一,它充当一个虚拟的画布,用于管理和渲染大量的 2D 图形项(QGraphicsItem)。在本文的仪表盘示例中,我们正是通过 QGraphicsScene 来组织和管理所有 SVG 仪表元件(背景、指针、刻度、文字等)的。

1、 核心作用与特点

1.1、 虚拟坐标系

QGraphicsScene 使用一个虚拟的、与设备无关的坐标系(默认为浮点精度)。你可以为场景设置任意大小的矩形区域作为其边界(通过构造函数或 setSceneRect)。在我们的代码中,每个仪表都创建了一个固定尺寸的场景:

  • Tc (转弯协调仪)QRectF(0, 0, 240, 240)
  • Nav (导航仪)QRectF(0, 0, 300, 300)
  • Adi (姿态指示器)QRectF(0, 0, 240, 240)

这个虚拟坐标系是图形项定位、变换和碰撞检测的基础。

1.2 、图形项容器

QGraphicsScene 是一个容器,可以添加、删除、查找和管理成千上万的 QGraphicsItem 对象。它负责:

  • 维护图形项列表(通过 addItem()removeItem()items() 等方法)。
  • 处理图形项的 Z 值(深度)排序,决定绘制顺序(setZValue())。
  • 提供场景级别的查询,如 itemAt()items()(按点、矩形、多边形等查找)。

1.3、 事件传播与信号

场景接收来自视图 (QGraphicsView) 的事件(如鼠标点击、移动、键盘事件),并将其转发给位于相应位置的图形项。它还提供了一系列信号,用于通知场景内容的变化,例如:

  • changed():场景内容(如图形项添加、删除、移动、变换)发生变化时发射。
  • selectionChanged():场景中图形项的选择状态改变时发射。

2、 在仪表示例中的关键用法

2.1、 创建与设置

# 创建场景,并指定其矩形区域(原点在左上角,宽高分别为 240 和 240)
self.m_scene = QGraphicsScene(QRectF(0, 0, 240, 240), self)
self.setScene(self.m_scene)  # 将场景设置给 QGraphicsView
  • 参数 parent:通常传入 self(即 QGraphicsView),这样场景的生命周期将由视图管理。
  • 场景矩形 (sceneRect):定义了场景的逻辑边界。图形项可以放置在此矩形内或之外,但视图通常只会显示场景矩形内的部分(除非手动调整)。

2.2 、添加图形项

我们使用 QGraphicsSvgItem(用于 SVG 矢量图)和 QGraphicsTextItem(用于文本)作为图形项,并通过 addItem() 将它们添加到场景中。

self.m_itemBack = QGraphicsSvgItem("images/tc/tc_back.svg")
self.m_itemBack.setZValue(self.m_backZ)  # 设置 Z 值,控制绘制顺序
self.m_scene.addItem(self.m_itemBack)   # 将项添加到场景

Z 值管理:通过 setZValue() 控制项的堆叠顺序。值越小,越先绘制(在底层);值越大,越后绘制(在顶层)。在仪表中,我们精心安排了 Z 值:

  • 背景 (m_backZ = -70):最底层。
  • 指针/刻度 (m_markZ = -30):中间层。
  • 外壳/表盘 (m_caseZ = 10):最顶层,覆盖其他元素边缘。

2.3、 场景更新与渲染

当图形项的状态(如位置、旋转、可见性)发生变化时,需要通知场景进行重绘。

  • 局部更新:调用图形项自身的 update() 方法,标记该项的矩形区域为脏区,触发局部重绘。
  • 全局更新:调用 self.m_scene.update()(如 Nav.updateView 末尾),强制重绘整个场景或所有脏区。

在我们的代码中,updateView() 方法会更新所有图形项的变换(旋转、平移),然后调用 self.m_scene.update() 以确保界面刷新。

2.4 、场景变换与视图适配

QGraphicsScene 本身不负责显示,显示由 QGraphicsView 完成。视图通过 setScene() 与场景关联。为了使场景内容自适应视图大小并保持比例,我们使用了:

# 在 init() 中,加载所有项后调用
self.fitInView(self.m_scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio)

fitInView()QGraphicsView 的方法,它会自动计算变换矩阵,将整个场景矩形适配到视图的视口内,并保持宽高比(KeepAspectRatio)。这是实现仪表等比例缩放、居中显示的关键。

2.5 、坐标转换

由于场景使用虚拟坐标,而视图显示在屏幕像素坐标中,经常需要进行坐标转换:

  • 场景坐标 → 视图坐标QGraphicsView.mapFromScene()
  • 视图坐标 → 场景坐标QGraphicsView.mapToScene()

Nav.repositionText() 中,我们使用了 self.viewportTransform() 获取视图的变换矩阵,从而计算当前缩放比例,动态调整文本位置和字体大小,确保文本始终位于仪表上的正确位置。

3、 常用方法总结

方法 说明 示例
addItem(item) 向场景添加一个图形项。 self.m_scene.addItem(self.m_itemBack)
removeItem(item) 从场景移除一个图形项。 self.m_scene.removeItem(self.m_itemBack)
clear() 移除场景中所有图形项。 self.m_scene.clear()(在 reinit() 中使用)
items() 返回场景中所有图形项的列表(可选排序)。 items = self.m_scene.items()
itemAt(pos, deviceTransform) 返回位于指定场景位置的顶层图形项。 item = self.m_scene.itemAt(QPointF(100,100))
setSceneRect(rect) 设置场景的矩形区域。 self.m_scene.setSceneRect(QRectF(0,0,500,500))
sceneRect() 返回当前场景矩形。 rect = self.m_scene.sceneRect()
update(rect=QRectF()) 安排场景矩形区域(默认整个场景)重绘。 self.m_scene.update()
invalidate(rect, layers) 使场景的指定区域和图层无效,强制重绘。 self.m_scene.invalidate()

4、性能与最佳实践

  1. 批量操作:当需要添加/删除大量图形项时,可以考虑使用 QGraphicsScenebegin() / end() 块(或 QGraphicsScene.blockSignals(True))来暂时阻止信号发射,提高性能。
  2. 缓存模式:对于静态或较少变化的图形项(如仪表背景),可以设置缓存模式以提高渲染性能:

    self.m_itemBack.setCacheMode(QGraphicsSvgItem.CacheMode.DeviceCoordinateCache)
    
  3. 场景矩形优化:将 sceneRect 设置为刚好包含所有图形项的最小矩形,可以减少不必要的绘制和事件处理。
  4. 避免频繁 clear():在动态更新的应用中,如果图形项频繁变化,尽量复用已有的图形项(更新其属性),而不是清空场景后重新添加。

5、总结

在本文的 PyQt6 仪表盘项目中,QGraphicsScene 扮演了后台舞台的角色:

  • 它定义了每个仪表的虚拟画布尺寸(240×240 或 300×300)。
  • 托管了所有 SVG 图元和文本项,并通过 Z 值管理它们的绘制顺序
  • 它与 QGraphicsView 配合,通过 fitInView 实现了自适应缩放和居中
  • 它提供了坐标转换、事件转发、场景更新等基础设施,使得复杂的 2D 图形界面能够高效、灵活地构建和更新。

理解 QGraphicsScene 是掌握 PyQt6 图形视图框架的关键一步。通过将图形项组织在场景中,你可以轻松实现复杂的交互式图形应用,如本文的航空仪表盘、绘图软件、流程图编辑器等。

在这里插入图片描述

© 版权声明

相关文章