PyQt 实现飞控仪表盘:姿态、高度、速度,飞行数据实时显示(三) PyQt 实现飞控仪表盘:姿态、高度、速度,飞行数据实时显示二一、说明二、源码分享1、效果展示2、源码分享2.1、vsi_widget.py2.2、main.py2.3、库安装2.4、源码及资源下载三、实现原理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、vsi_widget.pyfromPyQt6.QtWidgetsimportQGraphicsView,QWidget,QGraphicsScenefromPyQt6.QtSvgWidgetsimportQGraphicsSvgItemfromPyQt6.QtCoreimportQPointF,Qt,QRectFclassVsi(QGraphicsView):def__init__(self,parent:QWidgetNone):super().__init__(parent)# 固定原始场景尺寸240×240self.m_sceneQGraphicsScene(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_itemFaceNoneself.m_itemHandNoneself.m_itemCaseNone# 升降速度参数 ft/minself.m_climbRate0.0# 基础常量self.m_scaleX1.0self.m_scaleY1.0self.m_originalVsiCtrQPointF(120.0,120.0)self.m_originalHeight240self.m_originalWidth240# Z渲染层级 和C完全一致self.m_faceZ-20self.m_handZ-10self.m_caseZ10self.init()defreinit(self):ifself.m_scene:self.m_scene.clear()self.init()defupdate(self):self.updateView()# 对外接口升降速度限幅 -2000 ~ 2000 ft/mindefsetClimbRate(self,climb_rate:float):self.m_climbRateclimb_rateifself.m_climbRate-2000.0:self.m_climbRate-2000.0ifself.m_climbRate2000.0:self.m_climbRate2000.0defresizeEvent(self,event):super().resizeEvent(event)self.reinit()definit(self):self.reset()wself.width()hself.height()self.m_scaleXw/self.m_originalWidth self.m_scaleYh/self.m_originalHeight# 加载SVG图层原始240画布不手动缩放矩阵self.m_itemFaceQGraphicsSvgItem(images/vsi/vsi_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_itemHandQGraphicsSvgItem(images/vsi/vsi_hand.svg)self.m_itemHand.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)self.m_itemHand.setZValue(self.m_handZ)self.m_itemHand.setTransformOriginPoint(self.m_originalVsiCtr)self.m_scene.addItem(self.m_itemHand)self.m_itemCaseQGraphicsSvgItem(images/vsi/vsi_case.svg)self.m_itemCase.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)self.m_itemCase.setZValue(self.m_caseZ)self.m_scene.addItem(self.m_itemCase)# Qt原生自动居中、等比例放大替代旧centerOnself.fitInView(self.m_scene.sceneRect(),Qt.AspectRatioMode.KeepAspectRatio)self.updateView()defreset(self):self.m_itemFaceNoneself.m_itemHandNoneself.m_itemCaseNoneself.m_climbRate0.0defupdateView(self):# 指针旋转系数完全复刻C 0.086self.m_itemHand.setRotation(self.m_climbRate*0.086)self.m_scene.update()2.2、main.pyimportsysimportmathfromPyQt6.QtWidgetsimportQApplication,QMainWindow,QWidget,QGridLayoutfromPyQt6.QtCoreimportQTimer,QElapsedTimerfromadi_widgetimportAdifromalt_widgetimportAltfromasi_widgetimportAsifromhsi_widgetimportHsifromtc_widgetimportTcfromnav_widgetimportNavfromvsi_widgetimportVsiclassMainWindow(QMainWindow):# 全局常量统一管理REFRESH_INTERVAL30GRID_SPACING6def__init__(self):super().__init__()self.setWindowTitle(多仪表独立布局仿真 3行3列)self.resize(1400,1000)central_widgetQWidget()self.setCentralWidget(central_widget)gridQGridLayout(central_widget)grid.setSpacing(self.GRID_SPACING)# 3列均分forcolinrange(3):grid.setColumnStretch(col,1)# 3行均分forrowinrange(3):grid.setRowStretch(row,1)# 9个独立仪表实例 w0~w8包含w6 w7 # 第一行self.w0Tc()# 0行0列self.w1Nav()# 0行1列self.w2Adi()# 0行2列# 第二行self.w3Alt()# 1行0列self.w4Asi()# 1行1列self.w5Hsi()# 1行2列# 第三行新增 w6 w7 w8self.w6Vsi()# 2行0列 垂直速度表self.w7QWidget()# 2行1列 转弯协调仪self.w8QWidget()# 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_counterQElapsedTimer()self.time_counter.start()self.real_time0.0self.timerQTimer(self)self.timer.setInterval(self.REFRESH_INTERVAL)self.timer.timeout.connect(self.timer_event)self.timer.start()deftimer_event(self):# 计算帧时间步长step_msself.time_counter.restart()self.real_timestep_ms/1000.0# TC仪表共用动态参数slip_skid1.0*math.sin(self.real_time/10.0)turn_rate7.0*math.sin(self.real_time/10.0)# w0 TCself.w0.setSlipSkid(slip_skid*15.0)self.w0.setTurnRate(turn_rate)self.w0.update()# Nav仪表(w1)动态参数消除固定000文字heading360.0*math.sin(self.real_time/40.0)adf-360.0*math.sin(self.real_time/50.0)dev_h1.0*math.sin(self.real_time/20.0)dme99.0*math.sin(self.real_time/100.0)heading_bugheading*0.7courseheading*0.5self.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 w2roll180.0*math.sin(self.real_time/10.0)pitch90.0*math.sin(self.real_time/20.0)self.w2.setPitch(pitch)self.w2.setRoll(roll)self.w2.update()# ALT w3altitude9000.0*math.sin(self.real_time/40.0)9000.0pressure2.0*math.sin(self.real_time/20.0)30.0self.w3.setAltitude(altitude)self.w3.setPressure(pressure)self.w3.update()# ASI w4airspeed125.0*math.sin(self.real_time/40.0)125.0self.w4.setAirspeed(airspeed)self.w4.update()# HSI w5self.w5.setHeading(heading)self.w5.update()# 新增w6 VSI垂直速度 climb_rate1800*math.sin(self.real_time/20.0)self.w6.setClimbRate(climb_rate)self.w6.update()if__name____main__:appQApplication(sys.argv)winMainWindow()win.show()sys.exit(app.exec())2.3、库安装2.4、源码及资源下载见文章顶部绑定资源三、实现原理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 和 240self.m_sceneQGraphicsScene(QRectF(0,0,240,240),self)self.setScene(self.m_scene)# 将场景设置给 QGraphicsView参数parent通常传入self即QGraphicsView这样场景的生命周期将由视图管理。场景矩形 (sceneRect)定义了场景的逻辑边界。图形项可以放置在此矩形内或之外但视图通常只会显示场景矩形内的部分除非手动调整。2.2 、添加图形项我们使用QGraphicsSvgItem用于 SVG 矢量图和QGraphicsTextItem用于文本作为图形项并通过addItem()将它们添加到场景中。self.m_itemBackQGraphicsSvgItem(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(rectQRectF())安排场景矩形区域默认整个场景重绘。self.m_scene.update()invalidate(rect, layers)使场景的指定区域和图层无效强制重绘。self.m_scene.invalidate()4、性能与最佳实践批量操作当需要添加/删除大量图形项时可以考虑使用QGraphicsScene的begin()/end()块或QGraphicsScene.blockSignals(True)来暂时阻止信号发射提高性能。缓存模式对于静态或较少变化的图形项如仪表背景可以设置缓存模式以提高渲染性能self.m_itemBack.setCacheMode(QGraphicsSvgItem.CacheMode.DeviceCoordinateCache)场景矩形优化将sceneRect设置为刚好包含所有图形项的最小矩形可以减少不必要的绘制和事件处理。避免频繁 clear()在动态更新的应用中如果图形项频繁变化尽量复用已有的图形项更新其属性而不是清空场景后重新添加。5、总结在本文的 PyQt6 仪表盘项目中QGraphicsScene扮演了后台舞台的角色它定义了每个仪表的虚拟画布尺寸240x240 或 300x300。它托管了所有 SVG 图元和文本项并通过 Z 值管理它们的绘制顺序。它与QGraphicsView配合通过fitInView实现了自适应缩放和居中。它提供了坐标转换、事件转发、场景更新等基础设施使得复杂的 2D 图形界面能够高效、灵活地构建和更新。理解QGraphicsScene是掌握 PyQt6 图形视图框架的关键一步。通过将图形项组织在场景中你可以轻松实现复杂的交互式图形应用如本文的航空仪表盘、绘图软件、流程图编辑器等。