实战PyQt5: 170

您所在的位置:网站首页 数据可视化项目实战 实战PyQt5: 170

实战PyQt5: 170

2023-05-18 21:18| 来源: 网络整理| 查看: 265

本文的示例程序使用3D旋转方式来演示太阳磁场分布,在示例程序中,用一个金色的球体,表示太阳。用带箭头的小线段来表示磁场。

 

一、示例程序的基本功能

示例程序演示了一下基本功能:

使用3D旋转;使用自定义条目网格;使用范围渐变为serial着色。

二、示例程序分析

类似前面的文章,示例程序包括两部分,曲面的交互功能部分封装在类DemoWidget中,对应的py文件是roationsdemo.py, 首先调用QWidget::createWindowContainer()创建窗口容器。然后创建水平和垂直布局管理,将图形和垂直布局添加到一个水平布局中,垂直布局中用于添加交互部件。水平布局设置为DemoWidget的布局。

scatterdatamodifier.py实现类ScatterDataModifier,它实现对太阳磁场的模拟。

使用旋转

在这个例子中,我们想要使箭头项目的方向与原点相切。这需要旋转它们,这可以通过为每个条目指定旋转四元数来实现:

# 旋转-箭头始终与原点相切 yRotation = QQuaternion.fromAxisAndAngle(0.0, 1.0, 0.0, horizontalAngle * radiansToDegrees) for j in range (self.arrowsPerLine): # 计算以原点为中心并平行于x轴的椭圆上的点 verticalAngle = ((doublePi * j) / self.arrowsPerLine) + self.angleOffset xUnrotated = ellipse_a * math.cos(verticalAngle) y = ellipse_b * math.sin(verticalAngle) # 绕y轴旋转椭圆 xRotated = xUnrotated * math.cos(horizontalAngle) zRotated = xUnrotated * math.sin(horizontalAngle) # 添加偏移 x = xCenter + xRotated z = zCenter + zRotated zRotation = QQuaternion.fromAxisAndAngle(0.0, 0.0, 1.0, verticalAngle * radiansToDegrees) totalRotation = yRotation * zRotation scatterDataItem = QScatterDataItem(QVector3D(x, y, x)) scatterDataItem.setRotation(totalRotation) magneticFieldArray.append(scatterDataItem)

由于条目需要沿着两个轴旋转,我们定义了两个旋转四元数,一个用于y轴,一个用于z轴,然后将它们相乘得到总旋转,我们将其设置为数据项目。

使用自定义条目网格

用于磁场箭头条目的窄箭头网格不是标准网格,而是使用了自定义文件narrowarrow.obj。

self.magneticField.setMesh(QAbstract3DSeries.MeshUserDefined) self.magneticField.setUserDefinedMesh(':/mesh/narrowarrow.obj')

使用范围渐变

将颜色样式设置为系列中的范围渐变,根据条目在可见y坐标范围上的相对y值对其进行着色。希望图底部的箭头颜色更暗,并逐渐变得更亮,所以定义了一个范围梯度,黑色在0.0位置,白色在1.0位置:

fieldGradient = QLinearGradient(0, 0, 16, 1024) fieldGradient.setColorAt(0.0, Qt.black) fieldGradient.setColorAt(1.0, Qt.white) self.magneticField.setBaseGradient(fieldGradient) self.magneticField.setColorStyle(Q3DTheme.ColorStyleRangeGradient)

scatterdatamodifier.py完整代码:

import math from PyQt5.QtCore import Qt, QObject, QTimer from PyQt5.QtGui import QVector3D, QLinearGradient, QImage, QColor, QQuaternion from PyQt5.QtDataVisualization import (Q3DScatter, QScatter3DSeries, QScatterDataProxy, QAbstract3DSeries, QValue3DAxis, QAbstract3DGraph, Q3DCamera, QScatterDataItem, Q3DTheme, QCustom3DItem) verticalRange = 8.0 horizontalRange = verticalRange ellipse_a = horizontalRange / 3.0 ellipse_b = verticalRange doublePi = math.pi * 2.0 radiansToDegrees = 360.0 / doublePi animationFrames = 30.0 class ScatterDataModifier(QObject): def __init__(self, scatter): super(ScatterDataModifier, self).__init__() self.graph = scatter self.fieldLines = 12 self.arrowsPerLine = 16 self.magneticField = QScatter3DSeries() self.sun = QCustom3DItem() self.angleOffset = 0.0 self.angleStep = doublePi / self.arrowsPerLine / animationFrames self.graph.setShadowQuality(QAbstract3DGraph.ShadowQualityNone) self.graph.scene().activeCamera().setCameraPreset(Q3DCamera.CameraPresetFront) #磁场力线使用的箭头符号 self.magneticField.setItemSize(0.2) self.magneticField.setMesh(QAbstract3DSeries.MeshUserDefined) self.magneticField.setUserDefinedMesh(':/mesh/narrowarrow.obj') fieldGradient = QLinearGradient(0, 0, 16, 1024) fieldGradient.setColorAt(0.0, Qt.black) fieldGradient.setColorAt(1.0, Qt.white) self.magneticField.setBaseGradient(fieldGradient) self.magneticField.setColorStyle(Q3DTheme.ColorStyleRangeGradient) #使用一个定制的大球最为太阳 self.sun.setScaling(QVector3D(0.07, 0.07, 0.07)) self.sun.setMeshFile(':/mesh/largesphere.obj') sunColor = QImage(2, 2, QImage.Format_RGB32) sunColor.fill(QColor(0xff, 0xbb, 0x00)) self.sun.setTextureImage(sunColor) self.graph.addSeries(self.magneticField) self.graph.addCustomItem(self.sun) #设置坐标轴 self.graph.axisX().setRange(-horizontalRange, horizontalRange) self.graph.axisY().setRange(-verticalRange, verticalRange) self.graph.axisZ().setRange(-horizontalRange, horizontalRange) self.graph.axisX().setSegmentCount(int(horizontalRange)) self.graph.axisZ().setSegmentCount(int(horizontalRange)) self.rotationTimer = QTimer() self.rotationTimer.timeout.connect(self.triggerRotation) self.triggerRotation() self.generateData() def generateData(self): arraySize = self.fieldLines * self.arrowsPerLine magneticFieldArray = [] for i in range (self.fieldLines): horizontalAngle = (doublePi * i) / self.fieldLines xCenter = ellipse_a * math.cos(horizontalAngle) zCenter = ellipse_a * math.sin(horizontalAngle) # 旋转-箭头始终与原点相切 yRotation = QQuaternion.fromAxisAndAngle(0.0, 1.0, 0.0, horizontalAngle * radiansToDegrees) for j in range (self.arrowsPerLine): # 计算以原点为中心并平行于x轴的椭圆上的点 verticalAngle = ((doublePi * j) / self.arrowsPerLine) + self.angleOffset xUnrotated = ellipse_a * math.cos(verticalAngle) y = ellipse_b * math.sin(verticalAngle) # 绕y轴旋转椭圆 xRotated = xUnrotated * math.cos(horizontalAngle) zRotated = xUnrotated * math.sin(horizontalAngle) # 添加偏移 x = xCenter + xRotated z = zCenter + zRotated zRotation = QQuaternion.fromAxisAndAngle(0.0, 0.0, 1.0, verticalAngle * radiansToDegrees) totalRotation = yRotation * zRotation scatterDataItem = QScatterDataItem(QVector3D(x, y, x)) scatterDataItem.setRotation(totalRotation) magneticFieldArray.append(scatterDataItem) if self.graph.selectedSeries() == self.magneticField: self.graph.clearSelection() self.magneticField.dataProxy().resetArray(magneticFieldArray) def setFieldLines(self, lines): self.fieldLines = lines self.generateData() def setArrowsPerLine(self, arrows): self.angleOffset = 0.0 self.angleStep = doublePi / self.arrowsPerLine /animationFrames self.arrowsPerLine = arrows self.generateData() def triggerRotation(self): self.angleOffset += self.angleStep self.generateData() def toggleSun(self): self.sun.setVisible(not self.sun.isVisible()) def toggleRotation(self): if self.rotationTimer.isActive(): self.rotationTimer.stop() else: self.rotationTimer.start(15)

roationsdemo.py完整代码:

import sys from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtCore import Qt, QSize from PyQt5.QtGui import QFont from PyQt5.QtWidgets import (QApplication, QWidget, QMessageBox, QSizePolicy, QHBoxLayout, QVBoxLayout, QPushButton, QSlider, QLabel) from PyQt5.QtDataVisualization import ( Q3DScatter, QAbstract3DSeries, QAbstract3DGraph) from scatterdatamodifier import ScatterDataModifier import rotations_rc class DemoWidget(QWidget): def __init__(self, parent=None): super(DemoWidget, self).__init__(parent) # 设置窗口标题 self.setWindowTitle('实战 Qt for Python: 太阳磁场演示') # 设置窗口大小 self.resize(640, 480) self.initUi() def initUi(self): widgetgraph = Q3DScatter() container = QWidget.createWindowContainer(widgetgraph) if not widgetgraph.hasContext(): msgBox = QMessageBox() msgBox.setText('不能初始化OpenGL上下文') msgBox.exec() return screenSize = widgetgraph.screen().size() container.setMinimumSize( QSize(int(screenSize.width() / 2.0), int(screenSize.height() / 1.5))) container.setMaximumSize(screenSize) container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) container.setFocusPolicy(Qt.StrongFocus) hLayout = QHBoxLayout() vLayout = QVBoxLayout() hLayout.addWidget(container, 1) # 左边绘图部分 hLayout.addLayout(vLayout) # 右边控制部分 toggleRotationButton = QPushButton(self) toggleRotationButton.setText('绑定动画') toggleSunButton = QPushButton(self) toggleSunButton.setText('绑定太阳') fieldLinesSlider = QSlider(Qt.Horizontal, self) fieldLinesSlider.setTickInterval(1) fieldLinesSlider.setMinimum(1) fieldLinesSlider.setValue(12) fieldLinesSlider.setMaximum(128) arrowsSlider = QSlider(Qt.Horizontal, self) arrowsSlider.setTickInterval(1) arrowsSlider.setMinimum(8) arrowsSlider.setValue(16) arrowsSlider.setMaximum(32) vLayout.addWidget(toggleRotationButton) vLayout.addWidget(toggleSunButton) vLayout.addWidget(QLabel('磁场力线 (1-128)')) vLayout.addWidget(fieldLinesSlider) vLayout.addWidget(QLabel('每条力线的箭头数目')) vLayout.addWidget(arrowsSlider, 1, Qt.AlignTop) self.modifier = ScatterDataModifier(widgetgraph) toggleRotationButton.clicked.connect(self.modifier.toggleRotation) toggleSunButton.clicked.connect(self.modifier.toggleSun) fieldLinesSlider.valueChanged.connect(self.modifier.setFieldLines) arrowsSlider.valueChanged.connect(self.modifier.setArrowsPerLine) self.setLayout(hLayout) if __name__ == '__main__': app = QApplication(sys.argv) window = DemoWidget() window.show() sys.exit(app.exec())

运行效果如下:

 太阳磁场模拟示意图

 太阳磁场模拟交互动态演示

 

三、本文知识点

使用子定义条目网格;使用3D旋转。

前一篇: 实战PyQt5:169-数据可视化之三维表面图交互演示



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3