QmlDesigner: Implement proper preview for pure 3D scenes

Refactored the existing 3D node preview generation to be done
synchronously, which enables their use also for creating state
and item library previews for 3D nodes with ease.

Fixes: QDS-5785
Change-Id: Ib493eccbc239f33bcad3301673a865494616a901
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Miikka Heikkinen
2022-02-02 17:56:16 +02:00
parent ab5fdd94f3
commit f146b846cd
29 changed files with 420 additions and 243 deletions

View File

@@ -32,6 +32,11 @@ View3D {
property Material previewMaterial
function fitToViewPort()
{
// No need to zoom this view, this is here just to avoid runtime warnings
}
SceneEnvironment {
id: sceneEnv
antialiasingMode: SceneEnvironment.MSAA

View File

@@ -41,8 +41,6 @@ Item {
property var modelViewComponent
property var nodeViewComponent
property bool ready: false
function destroyView()
{
previewObject = null;
@@ -58,8 +56,6 @@ Item {
createViewForModel(obj);
else if (obj instanceof Node)
createViewForNode(obj);
previewObject = obj;
}
function createViewForMaterial(material)
@@ -70,6 +66,8 @@ Item {
// Always recreate the view to ensure material is up to date
if (materialViewComponent.status === Component.Ready)
view = materialViewComponent.createObject(viewRect, {"previewMaterial": material});
previewObject = material;
}
function createViewForModel(model)
@@ -80,6 +78,8 @@ Item {
// Always recreate the view to ensure model is up to date
if (modelViewComponent.status === Component.Ready)
view = modelViewComponent.createObject(viewRect, {"sourceModel": model});
previewObject = model;
}
function createViewForNode(node)
@@ -90,16 +90,13 @@ Item {
// Always recreate the view to ensure node is up to date
if (nodeViewComponent.status === Component.Ready)
view = nodeViewComponent.createObject(viewRect, {"importScene": node});
previewObject = node;
}
function afterRender()
function fitToViewPort()
{
if (previewObject instanceof Node) {
view.fitToViewPort();
ready = view.ready;
} else {
ready = true;
}
}
View3D {

View File

@@ -32,24 +32,13 @@ View3D {
environment: sceneEnv
camera: theCamera
property bool ready: false
property real prevZoomFactor: -1
property Model sourceModel
function fitToViewPort()
{
cameraControl.focusObject(model, theCamera.eulerRotation, true, false);
if (cameraControl._zoomFactor < 0.1) {
model.scale = model.scale.times(10);
} else if (cameraControl._zoomFactor > 10) {
model.scale = model.scale.times(0.1);
} else {
// We need one more render after zoom factor change, so only set ready when zoom factor
// or scaling hasn't changed from the previous frame
ready = _generalHelper.fuzzyCompare(cameraControl._zoomFactor, prevZoomFactor);
prevZoomFactor = cameraControl._zoomFactor;
}
// The magic number is the distance from camera default pos to origin
_generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, importScene, root,
1040);
}
SceneEnvironment {
@@ -58,14 +47,6 @@ View3D {
antialiasingQuality: SceneEnvironment.High
}
EditCameraController {
id: cameraControl
camera: theCamera
anchors.fill: parent
view3d: root
ignoreToolState: true
}
DirectionalLight {
eulerRotation.x: -30
eulerRotation.y: -30
@@ -75,15 +56,15 @@ View3D {
id: theCamera
z: 600
y: 600
x: 600
eulerRotation.x: -45
eulerRotation.y: -45
clipFar: 10000
clipNear: 1
}
Model {
id: model
eulerRotation.y: 45
source: sourceModel.source
geometry: sourceModel.geometry

View File

@@ -32,30 +32,11 @@ View3D {
environment: sceneEnv
camera: theCamera
property bool ready: false
property bool first: true
property real prevZoomFactor: -1
function fitToViewPort()
{
if (first) {
first = false;
selectionBox.targetNode = root.importScene;
} else {
cameraControl.focusObject(selectionBox.model, theCamera.eulerRotation, true, false);
if (cameraControl._zoomFactor < 0.1) {
root.importScene.scale = root.importScene.scale.times(10);
} else if (cameraControl._zoomFactor > 10) {
root.importScene.scale = root.importScene.scale.times(0.1);
} else {
// We need one more render after zoom factor change, so only set ready when zoom factor
// or scaling hasn't changed from the previous frame
ready = _generalHelper.fuzzyCompare(cameraControl._zoomFactor, prevZoomFactor);
prevZoomFactor = cameraControl._zoomFactor;
selectionBox.visible = false;
}
}
// The magic number is the distance from camera default pos to origin
_generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, importScene, root,
1040);
}
SceneEnvironment {
@@ -64,20 +45,6 @@ View3D {
antialiasingQuality: SceneEnvironment.High
}
SelectionBox {
id: selectionBox
view3D: root
geometryName: "NodeNodeViewSB"
}
EditCameraController {
id: cameraControl
camera: theCamera
anchors.fill: parent
view3d: root
ignoreToolState: true
}
DirectionalLight {
eulerRotation.x: -30
eulerRotation.y: -30

View File

@@ -32,6 +32,11 @@ View3D {
property Material previewMaterial
function fitToViewPort()
{
// No need to zoom this view, this is here just to avoid runtime warnings
}
SceneEnvironment {
id: sceneEnv
antialiasingMode: SceneEnvironment.MSAA

View File

@@ -41,8 +41,6 @@ Item {
property var modelViewComponent
property var nodeViewComponent
property bool ready: false
function destroyView()
{
previewObject = null;
@@ -58,8 +56,6 @@ Item {
createViewForModel(obj);
else if (obj instanceof Node)
createViewForNode(obj);
previewObject = obj;
}
function createViewForMaterial(material)
@@ -70,6 +66,8 @@ Item {
// Always recreate the view to ensure material is up to date
if (materialViewComponent.status === Component.Ready)
view = materialViewComponent.createObject(viewRect, {"previewMaterial": material});
previewObject = material;
}
function createViewForModel(model)
@@ -80,6 +78,8 @@ Item {
// Always recreate the view to ensure model is up to date
if (modelViewComponent.status === Component.Ready)
view = modelViewComponent.createObject(viewRect, {"sourceModel": model});
previewObject = model;
}
function createViewForNode(node)
@@ -90,16 +90,13 @@ Item {
// Always recreate the view to ensure node is up to date
if (nodeViewComponent.status === Component.Ready)
view = nodeViewComponent.createObject(viewRect, {"importScene": node});
previewObject = node;
}
function afterRender()
function fitToViewPort()
{
if (previewObject instanceof Node) {
view.fitToViewPort();
ready = view.ready;
} else {
ready = true;
}
}
Item {

View File

@@ -32,24 +32,13 @@ View3D {
environment: sceneEnv
camera: theCamera
property bool ready: false
property real prevZoomFactor: -1
property Model sourceModel
function fitToViewPort()
{
cameraControl.focusObject(model, theCamera.eulerRotation, true, false);
if (cameraControl._zoomFactor < 0.1) {
model.scale = model.scale.times(10);
} else if (cameraControl._zoomFactor > 10) {
model.scale = model.scale.times(0.1);
} else {
// We need one more render after zoom factor change, so only set ready when zoom factor
// or scaling hasn't changed from the previous frame
ready = _generalHelper.fuzzyCompare(cameraControl._zoomFactor, prevZoomFactor);
prevZoomFactor = cameraControl._zoomFactor;
}
// The magic number is the distance from camera default pos to origin
_generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, importScene, root,
1040);
}
SceneEnvironment {
@@ -58,14 +47,6 @@ View3D {
antialiasingQuality: SceneEnvironment.High
}
EditCameraController {
id: cameraControl
camera: theCamera
anchors.fill: parent
view3d: root
ignoreToolState: true
}
DirectionalLight {
eulerRotation.x: -30
eulerRotation.y: -30
@@ -75,16 +56,15 @@ View3D {
id: theCamera
z: 600
y: 600
x: 600
eulerRotation.x: -45
eulerRotation.y: -45
clipFar: 10000
clipNear: 1
}
Model {
id: model
readonly property bool _edit3dLocked: true // Make this non-pickable
eulerRotation.y: 45
source: sourceModel.source
geometry: sourceModel.geometry

View File

@@ -32,30 +32,11 @@ View3D {
environment: sceneEnv
camera: theCamera
property bool ready: false
property bool first: true
property real prevZoomFactor: -1
function fitToViewPort()
{
if (first) {
first = false;
selectionBox.targetNode = root.importScene;
} else {
cameraControl.focusObject(selectionBox.model, theCamera.eulerRotation, true, false);
if (cameraControl._zoomFactor < 0.1) {
root.importScene.scale = root.importScene.scale.times(10);
} else if (cameraControl._zoomFactor > 10) {
root.importScene.scale = root.importScene.scale.times(0.1);
} else {
// We need one more render after zoom factor change, so only set ready when zoom factor
// or scaling hasn't changed from the previous frame
ready = _generalHelper.fuzzyCompare(cameraControl._zoomFactor, prevZoomFactor);
prevZoomFactor = cameraControl._zoomFactor;
selectionBox.visible = false;
}
}
// The magic number is the distance from camera default pos to origin
_generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, importScene, root,
1040);
}
SceneEnvironment {
@@ -64,20 +45,6 @@ View3D {
antialiasingQuality: SceneEnvironment.High
}
SelectionBox {
id: selectionBox
view3D: root
geometryName: "NodeNodeViewSB"
}
EditCameraController {
id: cameraControl
camera: theCamera
anchors.fill: parent
view3d: root
ignoreToolState: true
}
DirectionalLight {
eulerRotation.x: -30
eulerRotation.y: -30

View File

@@ -55,6 +55,11 @@ const QString _globalStateId = QStringLiteral("@GTS"); // global tool state
const QString _lastSceneIdKey = QStringLiteral("lastSceneId");
const QString _rootSizeKey = QStringLiteral("rootSize");
static const float floatMin = std::numeric_limits<float>::lowest();
static const float floatMax = std::numeric_limits<float>::max();
static const QVector3D maxVec = QVector3D(floatMax, floatMax, floatMax);
static const QVector3D minVec = QVector3D(floatMin, floatMin, floatMin);
GeneralHelper::GeneralHelper()
: QObject()
{
@@ -269,6 +274,37 @@ QVector4D GeneralHelper::focusNodesToCamera(QQuick3DCamera *camera, float defaul
return QVector4D(lookAt, cameraZoomFactor);
}
// This function can be used to synchronously focus camera on a node, which doesn't have to be
// a selection box for bound calculations to work. This is used to focus the view for
// various preview image generations, where doing things asynchronously is not good
// and recalculating bounds for every frame is not a problem.
void GeneralHelper::calculateNodeBoundsAndFocusCamera(
QQuick3DCamera *camera, QQuick3DNode *node, QQuick3DViewport *viewPort,
float defaultLookAtDistance)
{
QVector3D minBounds;
QVector3D maxBounds;
getBounds(viewPort, node, minBounds, maxBounds);
QVector3D extents = maxBounds - minBounds;
QVector3D lookAt = minBounds + (extents / 2.f);
float maxExtent = qMax(extents.x(), qMax(extents.y(), extents.z()));
// Reset camera position to default zoom
QMatrix4x4 m = camera->sceneTransform();
const float *dataPtr(m.data());
QVector3D newLookVector(dataPtr[8], dataPtr[9], dataPtr[10]);
newLookVector.normalize();
newLookVector *= defaultLookAtDistance;
camera->setPosition(lookAt + newLookVector);
float newZoomFactor = maxExtent / 725.f; // Divisor taken from focusNodesToCamera function
zoomCamera(viewPort, camera, 0, defaultLookAtDistance, lookAt, newZoomFactor, false);
}
// Aligns any cameras found in nodes list to a camera.
// Only position and rotation are copied, rest of the camera properties stay the same.
void GeneralHelper::alignCameras(QQuick3DCamera *camera, const QVariant &nodes)
@@ -727,6 +763,129 @@ QVector3D GeneralHelper::pivotScenePosition(QQuick3DNode *node) const
return mat44::getPosition(sceneTransform);
}
// Calculate bounds for given node, including all child nodes.
// Returns true if the tree contains at least one Model node.
bool GeneralHelper::getBounds(QQuick3DViewport *view3D, QQuick3DNode *node, QVector3D &minBounds,
QVector3D &maxBounds, bool recursive)
{
if (!node) {
const float halfExtent = 100.f;
minBounds = {-halfExtent, -halfExtent, -halfExtent};
maxBounds = {halfExtent, halfExtent, halfExtent};
return false;
}
QMatrix4x4 localTransform;
auto nodePriv = QQuick3DObjectPrivate::get(node);
auto renderNode = static_cast<QSSGRenderNode *>(nodePriv->spatialNode);
if (recursive && renderNode) {
if (renderNode->flags.testFlag(QSSGRenderNode::Flag::TransformDirty))
renderNode->calculateLocalTransform();
localTransform = renderNode->localTransform;
}
QVector3D localMinBounds = maxVec;
QVector3D localMaxBounds = minVec;
// Find bounds for children
QVector<QVector3D> minBoundsVec;
QVector<QVector3D> maxBoundsVec;
const auto children = node->childItems();
bool hasModel = false;
for (const auto child : children) {
if (auto childNode = qobject_cast<QQuick3DNode *>(child)) {
QVector3D newMinBounds = minBounds;
QVector3D newMaxBounds = maxBounds;
hasModel = getBounds(view3D, childNode, newMinBounds, newMaxBounds, true);
// Ignore any subtrees that do not have Model in them as we don't need those
// for visual bounds calculations
if (hasModel) {
minBoundsVec << newMinBounds;
maxBoundsVec << newMaxBounds;
}
}
}
auto combineMinBounds = [](QVector3D &target, const QVector3D &source) {
target.setX(qMin(source.x(), target.x()));
target.setY(qMin(source.y(), target.y()));
target.setZ(qMin(source.z(), target.z()));
};
auto combineMaxBounds = [](QVector3D &target, const QVector3D &source) {
target.setX(qMax(source.x(), target.x()));
target.setY(qMax(source.y(), target.y()));
target.setZ(qMax(source.z(), target.z()));
};
auto transformCorner = [&](const QMatrix4x4 &m, QVector3D &minTarget, QVector3D &maxTarget,
const QVector3D &corner) {
QVector3D mappedCorner = m.map(corner);
combineMinBounds(minTarget, mappedCorner);
combineMaxBounds(maxTarget, mappedCorner);
};
auto transformCorners = [&](const QMatrix4x4 &m, QVector3D &minTarget, QVector3D &maxTarget,
const QVector3D &minCorner, const QVector3D &maxCorner) {
transformCorner(m, minTarget, maxTarget, minCorner);
transformCorner(m, minTarget, maxTarget, maxCorner);
transformCorner(m, minTarget, maxTarget, QVector3D(minCorner.x(), minCorner.y(), maxCorner.z()));
transformCorner(m, minTarget, maxTarget, QVector3D(minCorner.x(), maxCorner.y(), minCorner.z()));
transformCorner(m, minTarget, maxTarget, QVector3D(maxCorner.x(), minCorner.y(), minCorner.z()));
transformCorner(m, minTarget, maxTarget, QVector3D(minCorner.x(), maxCorner.y(), maxCorner.z()));
transformCorner(m, minTarget, maxTarget, QVector3D(maxCorner.x(), maxCorner.y(), minCorner.z()));
transformCorner(m, minTarget, maxTarget, QVector3D(maxCorner.x(), minCorner.y(), maxCorner.z()));
};
// Combine all child bounds
for (const auto &newBounds : qAsConst(minBoundsVec))
combineMinBounds(localMinBounds, newBounds);
for (const auto &newBounds : qAsConst(maxBoundsVec))
combineMaxBounds(localMaxBounds, newBounds);
if (qobject_cast<QQuick3DModel *>(node)) {
if (auto renderModel = static_cast<QSSGRenderModel *>(renderNode)) {
QWindow *window = static_cast<QWindow *>(view3D->window());
if (window) {
QSSGRef<QSSGRenderContextInterface> context;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
context = QSSGRenderContextInterface::getRenderContextInterface(quintptr(window));
#else
context = QQuick3DObjectPrivate::get(node)->sceneManager->rci;
#endif
if (!context.isNull()) {
auto bufferManager = context->bufferManager();
#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0)
QSSGBounds3 bounds = renderModel->getModelBounds(bufferManager);
#else
QSSGBounds3 bounds = bufferManager->getModelBounds(renderModel);
#endif
QVector3D center = bounds.center();
QVector3D extents = bounds.extents();
QVector3D localMin = center - extents;
QVector3D localMax = center + extents;
combineMinBounds(localMinBounds, localMin);
combineMaxBounds(localMaxBounds, localMax);
hasModel = true;
}
}
}
} else {
combineMinBounds(localMinBounds, {});
combineMaxBounds(localMaxBounds, {});
}
if (localMaxBounds == minVec) {
localMinBounds = {};
localMaxBounds = {};
}
// Transform local space bounding box to parent space
transformCorners(localTransform, minBounds, maxBounds, localMinBounds, localMaxBounds);
return hasModel;
}
}
}

View File

@@ -72,6 +72,9 @@ public:
const QVariant &nodes, QQuick3DViewport *viewPort,
float oldZoom, bool updateZoom = true,
bool closeUp = false);
Q_INVOKABLE void calculateNodeBoundsAndFocusCamera(QQuick3DCamera *camera, QQuick3DNode *node,
QQuick3DViewport *viewPort,
float defaultLookAtDistance);
Q_INVOKABLE void alignCameras(QQuick3DCamera *camera, const QVariant &nodes);
Q_INVOKABLE QVector3D alignView(QQuick3DCamera *camera, const QVariant &nodes,
const QVector3D &lookAtPoint);
@@ -126,6 +129,8 @@ protected:
private:
void handlePendingToolStateUpdate();
QVector3D pivotScenePosition(QQuick3DNode *node) const;
bool getBounds(QQuick3DViewport *view3D, QQuick3DNode *node, QVector3D &minBounds,
QVector3D &maxBounds, bool recursive = false);
QTimer m_overlayUpdateTimer;
QTimer m_toolStateUpdateTimer;

View File

@@ -1588,6 +1588,11 @@ bool NodeInstanceServer::isInformationServer() const
return false;
}
bool NodeInstanceServer::isPreviewServer() const
{
return false;
}
static QString baseProperty(const QString &property)
{
int index = property.indexOf('.');

View File

@@ -229,8 +229,10 @@ public:
virtual QImage grabWindow() = 0;
virtual QImage grabItem(QQuickItem *item) = 0;
virtual bool renderWindow() = 0;
virtual bool isInformationServer() const;
virtual bool isPreviewServer() const;
void addAnimation(QQuickAbstractAnimation *animation);
QVector<QQuickAbstractAnimation *> animations() const;
QVariant animationDefaultValue(int index) const;

View File

@@ -180,6 +180,11 @@ bool ObjectNodeInstance::isLayoutable() const
return false;
}
bool ObjectNodeInstance::isRenderable() const
{
return false;
}
bool ObjectNodeInstance::equalGraphicsItem(QGraphicsItem * /*item*/) const
{
return false;

View File

@@ -103,6 +103,7 @@ public:
virtual bool isQuickItem() const;
virtual bool isQuickWindow() const;
virtual bool isLayoutable() const;
virtual bool isRenderable() const;
virtual bool equalGraphicsItem(QGraphicsItem *item) const;

View File

@@ -44,10 +44,10 @@ QImage renderImage(ServerNodeInstance rootNodeInstance)
QSize previewImageSize = rootNodeInstance.boundingRect().size().toSize();
if (previewImageSize.isEmpty())
previewImageSize = {300, 300};
previewImageSize = {150, 150};
if (previewImageSize.width() > 300 || previewImageSize.height() > 300)
previewImageSize.scale({300, 300}, Qt::KeepAspectRatio);
if (previewImageSize.width() > 150 || previewImageSize.height() > 150)
previewImageSize.scale({150, 150}, Qt::KeepAspectRatio);
QImage previewImage = rootNodeInstance.renderPreviewImage(previewImageSize);
@@ -68,7 +68,8 @@ void Qt5CaptureImageNodeInstanceServer::collectItemChangesAndSendChangeCommands(
inFunction = true;
auto rooNodeInstance = rootNodeInstance();
rooNodeInstance.rootQuickItem()->setClip(true);
if (QQuickItem *qitem = rooNodeInstance.rootQuickItem())
qitem->setClip(true);
DesignerSupport::polishItems(quickWindow());

View File

@@ -1007,17 +1007,17 @@ void Qt5InformationNodeInstanceServer::doRenderModelNodeImageView()
void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView()
{
#ifdef QUICK3D_MODULE
m_modelNode3DImageViewAsyncData.cleanup();
if (m_modelNode3DImageViewData.rootItem) {
QMetaObject::invokeMethod(m_modelNode3DImageViewData.rootItem, "destroyView");
if (!m_modelNode3DImageViewData.contentItem)
m_modelNode3DImageViewData.contentItem = getContentItemForRendering(m_modelNode3DImageViewData.rootItem);
QImage renderImage;
if (m_modelNodePreviewImageCache.contains(m_modelNodePreviewImageCommand.componentPath())) {
m_modelNode3DImageViewAsyncData.renderImage
= m_modelNodePreviewImageCache[m_modelNodePreviewImageCommand.componentPath()];
modelNode3DImageViewSendImageToCreator();
renderImage = m_modelNodePreviewImageCache[m_modelNodePreviewImageCommand.componentPath()];
} else {
bool createdFromComponent = false;
QObject *instanceObj = nullptr;
ServerNodeInstance instance = instanceForId(m_modelNodePreviewImageCommand.instanceId());
if (!m_modelNodePreviewImageCommand.componentPath().isEmpty()
&& instance.isSubclassOf("QQuick3DNode")) {
@@ -1026,15 +1026,14 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView()
// wouldn't want the children of the Node to appear in the preview.
QQmlComponent component(engine());
component.loadUrl(QUrl::fromLocalFile(m_modelNodePreviewImageCommand.componentPath()));
m_modelNode3DImageViewAsyncData.instanceObj = qobject_cast<QQuick3DObject *>(component.create());
if (!m_modelNode3DImageViewAsyncData.instanceObj) {
instanceObj = qobject_cast<QQuick3DObject *>(component.create());
if (!instanceObj) {
qWarning() << "Could not create preview component: " << component.errors();
m_modelNode3DImageViewAsyncData.cleanup();
return;
}
m_modelNode3DImageViewAsyncData.createdFromComponent = true;
createdFromComponent = true;
} else {
m_modelNode3DImageViewAsyncData.instanceObj = instance.internalObject();
instanceObj = instance.internalObject();
}
QSize renderSize = m_modelNodePreviewImageCommand.size();
if (Internal::QuickItemNodeInstance::unifiedRenderPathOrQt6()) {
@@ -1055,41 +1054,18 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView()
QMetaObject::invokeMethod(
m_modelNode3DImageViewData.rootItem, "createViewForObject",
Q_ARG(QVariant, objectToVariant(m_modelNode3DImageViewAsyncData.instanceObj)));
Q_ARG(QVariant, objectToVariant(instanceObj)));
// Selection box geometry updates have an asynchronous step, so we need to do rendering
// in asynchronous steps as well, since we are adjusting the selection box geometry
// while finding correct zoom level.
m_modelNode3DImageViewAsyncData.timer.start();
}
}
#endif
}
void Qt5InformationNodeInstanceServer::modelNode3DImageViewSendImageToCreator()
{
if (!m_modelNode3DImageViewAsyncData.renderImage.isNull()) {
// Key number is selected so that it is unlikely to conflict other ImageContainer use.
ImageContainer imgContainer(m_modelNodePreviewImageCommand.instanceId(), {}, 2100000001);
imgContainer.setImage(m_modelNode3DImageViewAsyncData.renderImage);
// send the rendered image to creator process
nodeInstanceClient()->handlePuppetToCreatorCommand(
{PuppetToCreatorCommand::RenderModelNodePreviewImage,
QVariant::fromValue(imgContainer)});
m_modelNode3DImageViewAsyncData.cleanup();
}
}
void Qt5InformationNodeInstanceServer::modelNode3DImageViewRenderStep()
{
++m_modelNode3DImageViewAsyncData.count;
// Need to render twice, first render updates spatial nodes
for (int i = 0; i < 2; ++i) {
if (i == 1)
QMetaObject::invokeMethod(m_modelNode3DImageViewData.rootItem, "fitToViewPort"
, Qt::DirectConnection);
updateNodesRecursive(m_modelNode3DImageViewData.contentItem);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
if (Internal::QuickItemNodeInstance::unifiedRenderPath()) {
m_modelNode3DImageViewAsyncData.renderImage = m_modelNode3DImageViewData.window->grabWindow();
renderImage = m_modelNode3DImageViewData.window->grabWindow();
} else {
// Fake render loop signaling to update things like QML items as 3D textures
m_modelNode3DImageViewData.window->beforeSynchronizing();
@@ -1097,27 +1073,34 @@ void Qt5InformationNodeInstanceServer::modelNode3DImageViewRenderStep()
QSizeF size = qobject_cast<QQuickItem *>(m_modelNode3DImageViewData.contentItem)->size();
QRectF renderRect(QPointF(0., 0.), size);
m_modelNode3DImageViewAsyncData.renderImage
= designerSupport()->renderImageForItem(m_modelNode3DImageViewData.contentItem,
renderImage = designerSupport()->renderImageForItem(m_modelNode3DImageViewData.contentItem,
renderRect, size.toSize());
m_modelNode3DImageViewData.window->afterRendering();
}
#else
m_modelNode3DImageViewAsyncData.renderImage = grabRenderControl(m_modelNode3DImageViewData);
renderImage = grabRenderControl(m_modelNode3DImageViewData);
#endif
QMetaObject::invokeMethod(m_modelNode3DImageViewData.rootItem, "afterRender");
const bool ready = QQmlProperty::read(m_modelNode3DImageViewData.rootItem, "ready").value<bool>();
if (ready || m_modelNode3DImageViewAsyncData.count >= 10) {
}
QMetaObject::invokeMethod(m_modelNode3DImageViewData.rootItem, "destroyView");
if (m_modelNode3DImageViewAsyncData.createdFromComponent) {
if (createdFromComponent) {
// If component changes, puppet will need a reset anyway, so we can cache the image
m_modelNodePreviewImageCache.insert(m_modelNodePreviewImageCommand.componentPath(),
m_modelNode3DImageViewAsyncData.renderImage);
renderImage);
delete instanceObj;
}
modelNode3DImageViewSendImageToCreator();
} else {
m_modelNode3DImageViewAsyncData.timer.start();
}
// Key number is selected so that it is unlikely to conflict other ImageContainer use.
ImageContainer imgContainer(m_modelNodePreviewImageCommand.instanceId(), {}, 2100000001);
imgContainer.setImage(renderImage);
// send the rendered image to creator process
nodeInstanceClient()->handlePuppetToCreatorCommand(
{PuppetToCreatorCommand::RenderModelNodePreviewImage,
QVariant::fromValue(imgContainer)});
}
#endif
}
static QRectF itemBoundingRect(QQuickItem *item)
@@ -1234,7 +1217,6 @@ Qt5InformationNodeInstanceServer::Qt5InformationNodeInstanceServer(NodeInstanceC
m_render3DEditViewTimer.setSingleShot(true);
m_inputEventTimer.setSingleShot(true);
m_renderModelNodeImageViewTimer.setSingleShot(true);
m_modelNode3DImageViewAsyncData.timer.setSingleShot(true);
m_dynamicAddObjectTimer.setSingleShot(true);
#ifdef FPS_COUNTER
@@ -1260,7 +1242,6 @@ Qt5InformationNodeInstanceServer::~Qt5InformationNodeInstanceServer()
m_render3DEditViewTimer.stop();
m_inputEventTimer.stop();
m_renderModelNodeImageViewTimer.stop();
m_modelNode3DImageViewAsyncData.timer.stop();
m_dynamicAddObjectTimer.stop();
if (m_editView3DData.rootItem)
@@ -1677,8 +1658,6 @@ void Qt5InformationNodeInstanceServer::setup3DEditView(const QList<ServerNodeIns
this, &Qt5InformationNodeInstanceServer::doRender3DEditView);
QObject::connect(&m_inputEventTimer, &QTimer::timeout,
this, &Qt5InformationNodeInstanceServer::handleInputEvents);
QObject::connect(&m_modelNode3DImageViewAsyncData.timer, &QTimer::timeout,
this, &Qt5InformationNodeInstanceServer::modelNode3DImageViewRenderStep);
QObject::connect(&m_dynamicAddObjectTimer, &QTimer::timeout,
this, &Qt5InformationNodeInstanceServer::handleDynamicAddObjectTimeout);
@@ -2165,7 +2144,6 @@ void Qt5InformationNodeInstanceServer::view3DAction(const View3DActionCommand &c
void Qt5InformationNodeInstanceServer::requestModelNodePreviewImage(const RequestModelNodePreviewImageCommand &command)
{
m_modelNode3DImageViewAsyncData.timer.stop();
m_modelNodePreviewImageCommand = command;
renderModelNodeImageView();
}

View File

@@ -140,8 +140,6 @@ private:
void renderModelNodeImageView();
void doRenderModelNodeImageView();
void doRenderModelNode3DImageView();
void modelNode3DImageViewSendImageToCreator();
void modelNode3DImageViewRenderStep();
void doRenderModelNode2DImageView();
void updateLockedAndHiddenStates(const QSet<ServerNodeInstance> &instances);
void handleInputEvents();
@@ -190,26 +188,6 @@ private:
QObject *m_3dHelper = nullptr;
int m_need3DEditViewRender = 0;
QSet<QObject *> m_dynamicObjectConstructors;
struct ModelNode3DImageViewAsyncData {
QTimer timer;
QImage renderImage;
int count = 0;
bool createdFromComponent = false;
QObject *instanceObj = nullptr;
void cleanup()
{
timer.stop();
count = 0;
renderImage = {};
if (createdFromComponent)
delete instanceObj;
instanceObj = nullptr;
createdFromComponent = false;
}
};
ModelNode3DImageViewAsyncData m_modelNode3DImageViewAsyncData;
};
} // namespace QmlDesigner

View File

@@ -70,6 +70,7 @@ public:
QImage grabWindow() override;
QImage grabItem(QQuickItem *item) override;
bool renderWindow() override;
static QQuickItem *parentEffectItem(QQuickItem *item);
@@ -97,7 +98,6 @@ protected:
virtual bool initRhi(RenderViewData &viewData);
virtual QImage grabRenderControl(RenderViewData &viewData);
virtual bool renderWindow();
private:
RenderViewData m_viewData;

View File

@@ -133,4 +133,9 @@ void Qt5PreviewNodeInstanceServer::changePreviewImageSize(
collectItemChangesAndSendChangeCommands();
}
bool Qt5PreviewNodeInstanceServer::isPreviewServer() const
{
return true;
}
} // namespace QmlDesigner

View File

@@ -39,6 +39,7 @@ public:
void changeState(const ChangeStateCommand &command) override;
void removeSharedMemory(const RemoveSharedMemoryCommand &command) override;
void changePreviewImageSize(const ChangePreviewImageSizeCommand &command) override;
bool isPreviewServer() const override;
QImage renderPreviewImage();

View File

@@ -26,6 +26,8 @@
#include "quick3dnodeinstance.h"
#include "qt5nodeinstanceserver.h"
#include "qt5informationnodeinstanceserver.h"
#include "quickitemnodeinstance.h"
#include "../editor3d/generalhelper.h"
#include <qmlprivategate.h>
@@ -37,6 +39,7 @@
#include <cmath>
#ifdef QUICK3D_MODULE
#include <private/qquick3dobject_p.h>
#include <private/qquick3dnode_p.h>
#include <private/qquick3dmodel_p.h>
#include <private/qquick3dnode_p_p.h>
@@ -45,8 +48,10 @@
#if defined(QUICK3D_ASSET_UTILS_MODULE) && QT_VERSION > QT_VERSION_CHECK(6, 2, 0)
#include <private/qquick3druntimeloader_p.h>
#endif
#include <private/qquickstategroup_p.h>
#endif
namespace QmlDesigner {
namespace Internal {
@@ -57,6 +62,7 @@ Quick3DNodeInstance::Quick3DNodeInstance(QObject *node)
Quick3DNodeInstance::~Quick3DNodeInstance()
{
delete m_dummyRootView;
}
void Quick3DNodeInstance::initialize(const ObjectNodeInstance::Pointer &objectNodeInstance,
@@ -87,10 +93,123 @@ void Quick3DNodeInstance::initialize(const ObjectNodeInstance::Pointer &objectNo
}
}
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
// In case this is the scene root, we need to create a dummy View3D for the scene
// in preview puppets
if (instanceId() == 0 && nodeInstanceServer()->isPreviewServer()) {
auto helper = new QmlDesigner::Internal::GeneralHelper();
engine()->rootContext()->setContextProperty("_generalHelper", helper);
QQmlComponent component(engine());
component.loadUrl(QUrl("qrc:/qtquickplugin/mockfiles/qt6/ModelNode3DImageView.qml"));
m_dummyRootView = qobject_cast<QQuickItem *>(component.create());
QMetaObject::invokeMethod(
m_dummyRootView, "createViewForNode",
Q_ARG(QVariant, QVariant::fromValue(object())));
nodeInstanceServer()->setRootItem(m_dummyRootView);
}
#endif
#endif
ObjectNodeInstance::initialize(objectNodeInstance, flags);
}
QImage Quick3DNodeInstance::renderImage() const
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (!isRootNodeInstance() || !m_dummyRootView)
return {};
QSize size(640, 480);
nodeInstanceServer()->quickWindow()->resize(size);
m_dummyRootView->setSize(size);
// Just render the window once to update spatial nodes
nodeInstanceServer()->renderWindow();
QMetaObject::invokeMethod(m_dummyRootView, "fitToViewPort", Qt::DirectConnection);
QRectF renderBoundingRect = m_dummyRootView->boundingRect();
QImage renderImage;
if (QuickItemNodeInstance::unifiedRenderPath()) {
renderImage = nodeInstanceServer()->grabWindow();
renderImage = renderImage.copy(renderBoundingRect.toRect());
} else {
renderImage = nodeInstanceServer()->grabItem(m_dummyRootView);
}
// When grabbing an offscreen window the device pixel ratio is 1
renderImage.setDevicePixelRatio(1);
return renderImage;
#endif
return {};
}
QImage Quick3DNodeInstance::renderPreviewImage(const QSize &previewImageSize) const
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (!isRootNodeInstance() || !m_dummyRootView)
return {};
nodeInstanceServer()->quickWindow()->resize(previewImageSize);
m_dummyRootView->setSize(previewImageSize);
// Just render the window once to update spatial nodes
nodeInstanceServer()->renderWindow();
QMetaObject::invokeMethod(m_dummyRootView, "fitToViewPort", Qt::DirectConnection);
QRectF previewItemBoundingRect = boundingRect();
if (previewItemBoundingRect.isValid()) {
const QSize size = previewImageSize;
if (m_dummyRootView->isVisible()) {
QImage image;
image = nodeInstanceServer()->grabWindow();
image = image.copy(previewItemBoundingRect.toRect());
image = image.scaledToWidth(size.width());
return image;
} else {
QImage transparentImage(size, QImage::Format_ARGB32_Premultiplied);
transparentImage.fill(Qt::transparent);
return transparentImage;
}
}
#endif
return {};
}
bool Quick3DNodeInstance::isRenderable() const
{
return m_dummyRootView;
}
QRectF Quick3DNodeInstance::boundingRect() const
{
if (m_dummyRootView)
return m_dummyRootView->boundingRect();
return ObjectNodeInstance::boundingRect();
}
QList<ServerNodeInstance> Quick3DNodeInstance::stateInstances() const
{
QList<ServerNodeInstance> instanceList;
#ifdef QUICK3D_MODULE
if (auto obj3D = quick3DNode()) {
const QList<QQuickState *> stateList = QQuick3DObjectPrivate::get(obj3D)->_states()->states();
for (QQuickState *state : stateList) {
if (state && nodeInstanceServer()->hasInstanceForObject(state))
instanceList.append(nodeInstanceServer()->instanceForObject(state));
}
}
#endif
return instanceList;
}
Qt5NodeInstanceServer *Quick3DNodeInstance::qt5NodeInstanceServer() const
{
return qobject_cast<Qt5NodeInstanceServer *>(nodeInstanceServer());

View File

@@ -47,12 +47,22 @@ public:
void initialize(const ObjectNodeInstance::Pointer &objectNodeInstance,
InstanceContainer::NodeFlags flags) override;
QImage renderImage() const override;
QImage renderPreviewImage(const QSize &previewImageSize) const override;
bool isRenderable() const override;
QRectF boundingRect() const override;
QList<ServerNodeInstance> stateInstances() const override;
protected:
explicit Quick3DNodeInstance(QObject *node);
private:
Qt5NodeInstanceServer *qt5NodeInstanceServer() const;
QQuick3DNode *quick3DNode() const;
QQuickItem *m_dummyRootView = nullptr;
};
} // namespace Internal

View File

@@ -571,6 +571,11 @@ bool QuickItemNodeInstance::isQuickItem() const
return true;
}
bool QuickItemNodeInstance::isRenderable() const
{
return quickItem() && (!s_unifiedRenderPath || isRootNodeInstance());
}
QList<ServerNodeInstance> QuickItemNodeInstance::stateInstances() const
{
QList<ServerNodeInstance> instanceList;

View File

@@ -94,6 +94,7 @@ public:
bool isResizable() const override;
bool isMovable() const override;
bool isQuickItem() const override;
bool isRenderable() const override;
QList<ServerNodeInstance> stateInstances() const override;

View File

@@ -154,7 +154,7 @@ void ServerNodeInstance::setNodeSource(const QString &source)
bool ServerNodeInstance::holdsGraphical() const
{
return m_nodeInstance->isQuickItem();
return m_nodeInstance->isRenderable();
}
bool ServerNodeInstance::isComponentWrap() const

View File

@@ -48,7 +48,8 @@ QString ItemLibraryItem::typeName() const
QString ItemLibraryItem::itemLibraryIconPath() const
{
if (m_itemLibraryEntry.customComponentSource().isEmpty()) {
if (m_itemLibraryEntry.customComponentSource().isEmpty()
|| !m_itemLibraryEntry.libraryEntryIconPath().isEmpty()) {
return QStringLiteral("image://qmldesigner_itemlibrary/")
+ m_itemLibraryEntry.libraryEntryIconPath();
} else {

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>651</width>
<height>318</height>
<width>517</width>
<height>166</height>
</rect>
</property>
<property name="sizePolicy">
@@ -90,8 +90,8 @@
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>300</height>
<width>150</width>
<height>150</height>
</size>
</property>
<property name="frameShape">
@@ -163,7 +163,7 @@
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
<verstretch>3</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">

View File

@@ -90,7 +90,8 @@ void ImageCacheCollector::start(Utils::SmallStringView name,
model->setRewriterView(&rewriterView);
if (rewriterView.inErrorState() || !rewriterView.rootModelNode().metaInfo().isGraphicalItem()) {
if (rewriterView.inErrorState() || (!rewriterView.rootModelNode().metaInfo().isGraphicalItem()
&& !rewriterView.rootModelNode().isSubclassOf("Quick3D.Node") )) {
if (abortCallback)
abortCallback(ImageCache::AbortReason::Failed);
return;

View File

@@ -420,6 +420,7 @@ void SubComponentManager::parseQuick3DAssetsItem(const QString &importUrl, const
itemLibraryEntry.setType(type.toUtf8(), 1, 0);
itemLibraryEntry.setName(name);
itemLibraryEntry.setCategory(::QmlDesigner::SubComponentManager::tr("My 3D Components"));
itemLibraryEntry.setCustomComponentSource(qmlIt.fileInfo().absoluteFilePath());
itemLibraryEntry.setRequiredImport(importUrl);
QString iconPath = qmlIt.fileInfo().absolutePath() + '/'
+ Constants::QUICK_3D_ASSET_ICON_DIR + '/' + name