我在 Qt 5.15.x Qt Widgets 中有多个 Pyvista 3d 可视化渲染。这是使用 QtInteractor ( https://github.com/pyvista/pyvistaqt ) 完成的。现在,我正在从 Qt 小部件转向 Qt QML。
我的想法是从 PyVista 获取渲染器并在 QML QQuickFrameBufferObject 中渲染。根据来自的知识
- QQuickFrameBufferObject 导致 PySide2 崩溃
- https://github.com/nicanor-romero/QtVtk/blob/master/src/QVTKFramebufferObjectRenderer.cpp
我编写了以下 Python3 代码。文件:qml_fbo_pyvista.py
from __future__ import annotations
import os
import sys
import pyvista as pv
from PySide2.QtCore import QSize
from PySide2.QtGui import QOpenGLFramebufferObject, QOpenGLFramebufferObjectFormat, QOpenGLFunctions
from PySide2.QtQml import QQmlApplicationEngine, qmlRegisterType
from PySide2.QtQuick import QQuickWindow, QQuickFramebufferObject
from PySide2.QtWidgets import QApplication
# https://doc.qt.io/qt-5/qquickframebufferobject-renderer.html
from pyvista import PolyData, BasePlotter
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTrackballCamera
from vtkmodules.vtkRenderingCore import vtkRenderWindowInteractor, vtkRenderer
from vtkmodules.vtkRenderingExternal import vtkExternalOpenGLRenderWindow
from vtkmodules.vtkRenderingOpenGL2 import vtkOpenGLActor
from vtkmodules.vtkRenderingUI import vtkGenericRenderWindowInteractor
class PyvistaFBORenderer(QQuickFramebufferObject.Renderer, BasePlotter):
_fbo: QOpenGLFramebufferObject
_m_fboItem: QQuickFramebufferObject
def __init__(self) -> None:
super().__init__()
print("* init renderer")
# surfaceFormat: QSurfaceFormat = QSurfaceFormat()
# surfaceFormat.setRenderableType(QSurfaceFormat.OpenGL)
# # surfaceFormat.setVersion(3, 2)
# # surfaceFormat.setProfile(QSurfaceFormat.CoreProfile)
# # surfaceFormat.setSwapBehavior(QSurfaceFormat.DoubleBuffer)
# # surfaceFormat.setRedBufferSize(8)
# # surfaceFormat.setGreenBufferSize(8)
# # surfaceFormat.setBlueBufferSize(8)
# # surfaceFormat.setDepthBufferSize(24)
# # surfaceFormat.setStencilBufferSize(8)
# # surfaceFormat.setAlphaBufferSize(0)
# # surfaceFormat.setStereo(False)
# QSurfaceFormat.setDefaultFormat(surfaceFormat)
self.plotter = pv.Plotter(off_screen=True,
polygon_smoothing=True,
line_smoothing=True,
point_smoothing=True)
self.geom: PolyData = pv.Sphere()
self.m_actor: vtkOpenGLActor = self.plotter.add_mesh(self.geom)
self.m_renderer: vtkRenderer = self.plotter.renderer
self.m_interactor: vtkRenderWindowInteractor = vtkGenericRenderWindowInteractor()
self.m_renderWindow: vtkExternalOpenGLRenderWindow = vtkExternalOpenGLRenderWindow()
self.m_renderWindow.SetInteractor(self.m_interactor)
self.m_renderer.SetBackground(0,0,0)
self.m_renderWindow.AddRenderer(self.m_renderer)
interactor_style = vtkInteractorStyleTrackballCamera()
self.m_interactor.SetInteractorStyle(interactor_style)
self.m_interactor.Initialize()
self.update()
def createFramebufferObject(self, size: QSize) -> QOpenGLFramebufferObject:
print("* createFramebufferObject called")
fmt = QOpenGLFramebufferObjectFormat()
fmt.setAttachment(QOpenGLFramebufferObject.Depth)
self._fbo = QOpenGLFramebufferObject(size, fmt)
self.m_renderWindow.SetBackLeftBuffer(0x8CE0)
self.m_renderWindow.SetFrontLeftBuffer(0x8CE0)
self.m_renderWindow.SetSize(size.width(), size.height())
self.m_renderWindow.SetOffScreenRendering(True)
self.m_renderWindow.Modified()
return self._fbo
def synchronize(self, item: QQuickFramebufferObject) -> None:
print("* Sync")
self._m_fboItem = item
globj = QOpenGLFunctions()
globj.initializeOpenGLFunctions()
size = (item.size() * item.window().devicePixelRatio()).toSize()
self.m_renderWindow.SetSize(size.width(), size.height())
def render(self) -> None:
print(" * Rendering")
self.m_renderWindow.PushState()
self.m_renderWindow.OpenGLInitState()
self.m_renderWindow.MakeCurrent()
globj = QOpenGLFunctions()
globj.initializeOpenGLFunctions()
globj.glEnable(0x809D)
globj.glUseProgram(0)
self.m_renderWindow.Start()
self.m_renderWindow.Render()
self.m_renderWindow.PopState()
self._m_fboItem.window().resetOpenGLState()
# https://doc.qt.io/qt-5/qquickframebufferobject.html
class QMLPyvistaOpenGLItem(QQuickFramebufferObject):
__renderer_obj: PyvistaFBORenderer
def __init__(self) -> None:
super().__init__()
print("* created new PyvistaFBORenderer")
def createRenderer(self) -> QQuickFramebufferObject.Renderer:
self.__renderer_obj = PyvistaFBORenderer()
print("* return PyvistaFBORenderer")
return self.__renderer_obj
if __name__ == '__main__':
os.environ['QSG_VISUALIZE'] = 'overdraw'
app = QApplication(sys.argv)
qmlRegisterType(QMLPyvistaOpenGLItem, "QMLPyvistaOpenGLItem", 1, 0, "QMLPyvistaOpenGLItem")
engine = QQmlApplicationEngine()
engine.load("./qml_view/pyvista3d_view.qml")
if not engine.rootObjects():
print("ERROR loading pyvista3d_view.qml")
sys.exit(-1)
mainWin: QQuickWindow = engine.rootObjects()[0]
mainWin.show()
sys.exit(app.exec_())
文件:qml_view/pyvista3d_view.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls.Material 2.12
import QMLPyvistaOpenGLItem 1.0
ApplicationWindow {
id: mainapp
width: 800
height: 600
title: "Hello Pyvista!!"
visible: true
color: "#2e03eb"
Material.theme: Material.Dark
Material.accent: Material.Indigo
Button{
text: "Hello"
width: 100
height: 100
}
QMLPyvistaOpenGLItem{
anchors.fill: parent
visible: true
Component.onCompleted: console.log(" *********** Completed QMLPyvistaOpenGLItem!")
}
}
由于 PyVista 的渲染器已经有一个 Sphere 3d 对象,我的期望是,它会在 QML 的帧缓冲区对象中渲染,并将在 QML 窗口中可见。但是,我错了。没有崩溃,但我也没有在 QML 窗口中看到 Sphere 3d 对象。
QML QSG_VISUALIZE 调试显示创建了一个 FBO 对象,但无法让 PyVista 的渲染器与 QML 帧缓冲区一起工作。
输出: qml_fbo_pyvista.py UI 输出 qml_fbo_pyvista.py 控制台输出
环境:
OS : Windows
CPU(s) : 12
Machine : AMD64
Architecture : 64bit
Environment : Python
GPU Vendor : Intel
GPU Renderer : Intel(R) UHD Graphics
GPU Version : 4.5.0 - Build 27.20.100.8681
Python 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit
(AMD64)]
pyvista : 0.31.1
vtk : 9.0.20210612
numpy : 1.21.0
imageio : 2.9.0
appdirs : 1.4.4
scooby : 0.5.7
meshio : 4.4.6
matplotlib : 3.4.2
pyvistaqt : 0.5.0
IPython : 7.25.0
scipy : 1.7.0
Pyvista2 : 5.15.4
Qt: 5.15.4
我错过了什么?