Reputation: 23
I have a Qt3DWindow inside a QWidget container, with a QSphere added to it (and material, lighting, camera). This works all well when I launch it and I can see the sphere. I now want to "capture" / "render" this view and save it as an image. I have spent the whole day trying to figure out how to achieve this seemingly very simple task, but everything has failed thus far. I have tried QRenderCapture(), grab(), and QOffscreenSurface() as recommended by some common AI tools, to no avail. Here is the working code of the window and viewer.
import sys
from PyQt6.QtWidgets import QApplication, QHBoxLayout, QWidget, QPushButton,
from PyQt6.QtWidgets import QMainWindow, QWidget
from PyQt6.QtGui import QIcon, QPixmap, QPainter, QImage, QMatrix4x4, QQuaternion, QVector3D, QColor, QGuiApplication, QPageLayout
from PyQt6.QtCore import QTimer
from PyQt6.Qt3DCore import QEntity, QTransform
from PyQt6 import Qt3DRender
from PyQt6.Qt3DExtras import QForwardRenderer, QPhongMaterial, Qt3DWindow, QOrbitCameraController, QDiffuseSpecularMaterial, QTextureMaterial, QNormalDiffuseMapMaterial, QDiffuseMapMaterial
from PyQt6.Qt3DExtras import QSphereMesh
from PyQt6.Qt3DRender import QRenderCapture, QRenderCaptureReply
from PyQt6.QtCore import QTimer
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.view = Qt3DWindow()
self.container = QWidget.createWindowContainer(self.view)
self.setCentralWidget(self.container)
# Create & set layouts
main_layout = QHBoxLayout()
main_layout.addWidget(self.container)
main_widget = QWidget()
main_widget.setLayout(main_layout)
self.setCentralWidget(main_widget)
# Root entity
self.rootEntity = QEntity()
# Load mesh
sphere_mesh = QSphereMesh()
sphere_mesh.setRadius(20)
mesh_entity = QEntity(self.rootEntity)
mesh_entity.addComponent(sphere_mesh)
# Load/create textures
diffuse_material = QPhongMaterial(self.rootEntity)
diffuse_material.setDiffuse(QColor.fromRgbF(0.1, 0.9, 0.7, 0.6))
mesh_entity.addComponent(diffuse_material)
# Add material components to entity
mesh_entity.addComponent(diffuse_material)
# Camera
self.camera = self.view.camera()
self.camera.lens().setPerspectiveProjection(60.0, 16.0 / 9.0, 1, 1000.0)
self.camera.setPosition(QVector3D(0.0, 0, 120.0))
self.camera.setViewCenter(QVector3D(0.0, 0.0, 0.0))
# camera controls
self.camController = QOrbitCameraController(self.rootEntity)
self.camController.setLinearSpeed(200.0)
self.camController.setLookSpeed(280.0)
self.camController.setCamera(self.camera)
# Light
light_entity = QEntity(self.rootEntity)
light = Qt3DRender.QPointLight(light_entity)
light.setConstantAttenuation(0)
light_entity.addComponent(light)
light_transform = QTransform()
light_transform.setTranslation(QVector3D(350, 100, 200))
light_entity.addComponent(light_transform)
self.view.setRootEntity(self.rootEntity)
self.capture_btn = QPushButton('Capture', self)
self.capture_btn.clicked.connect(self.capture_image)
main_layout.addWidget(self.capture_btn)
def capture_image(self):
pass
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.setGeometry(100, 100, 800, 600)
window.setWindowTitle("3D Object Viewer")
window.show()
sys.exit(app.exec())
Upvotes: 0
Views: 40
Reputation: 23
Solved. I managed to get it working. Trick was to link the FrameGraph to the QRenderCapture instance. Here is the code added to init()
self.render_capture = QRenderCapture()
self.view.activeFrameGraph().setParent(self.render_capture)
self.view.setActiveFrameGraph(self.render_capture)
The other gotcha is to wait for the rendering to complete. Here is how I implemented it.
def capture_image(self):
self.reply = self.render_capture.requestCapture()
loop = QEventLoop()
self.reply.completed.connect(loop.quit)
QTimer.singleShot(200, loop.quit) # Timeout after 1 second
loop.exec()
image = self.reply.image()
image.save("capture_output.jpg", "JPG")
I just can't believe how poorly documented this Qt3D library is...
Upvotes: 0