Can not get Pyvista render to work with Qt QML QQuickFramebufferObject
-
I have multiple Pyvista 3d visualizations render in the Qt 5.15.x Qt Widgets. This was done using QtInteractor (https://github.com/pyvista/pyvistaqt). Now, I am moving away from Qt widgets to Qt QML.
My idea is to get the renderer from PyVista and get it render in QML QQuickFrameBufferObject. Based on knowledge from
- https://stackoverflow.com/questions/61950058/qquickframebufferobject-causes-crash-in-pyside2
- https://github.com/nicanor-romero/QtVtk/blob/master/src/QVTKFramebufferObjectRenderer.cpp
I wrote a following Python3 code.
File: qml_fbo_pyvista.pyfrom __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_())
File: 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!") } }
Since PyVista's renderer already have a Sphere 3d object, My expectation is that, It gets render in the QML's framebuffer object and will be visible in the QML window. But, I am wrong. There is no crash but I also do not see Sphere 3d object in the QML window.
QML QSG_VISUALIZE debugging shows there is a FBO object is created but could not get PyVista's renderer to work with QML framebuffer.
Output:
qml_fbo_pyvista.py UI output
qml_fbo_pyvista.py consoleoutputEnvironment:
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
What am I missing?