From 3890bf4efbe9f956c0b8f451296a3bba6ba33472 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 28 Jan 2020 10:40:44 +0200 Subject: [PATCH] QmlDesigner: Add support for multiple views to 3D edit view Any subtree of Nodes can be shown in 3D edit view. Selecting any Node or their children, or View3D changes the view in 3D edit view to the relevant scene. Fixes: QDS-1494 Fixes: QDS-1565 Change-Id: I2d5a6f88bab2a20b74c347351235f79fb530519b Reviewed-by: Mahmoud Badri Reviewed-by: Thomas Hartmann --- .../qml/qmlpuppet/mockfiles/Arrow.qml | 2 +- .../qml/qmlpuppet/mockfiles/AxisHelper.qml | 6 +- .../mockfiles/EditCameraController.qml | 28 +- .../qml/qmlpuppet/mockfiles/EditView3D.qml | 137 +++++---- .../qml/qmlpuppet/mockfiles/IconGizmo.qml | 4 +- .../qmlpuppet/mockfiles/PlanarMoveHandle.qml | 2 +- .../qml/qmlpuppet/mockfiles/SceneView3D.qml | 80 ++++++ .../editor3d/selectionboxgeometry.cpp | 7 +- .../qt5informationnodeinstanceserver.cpp | 260 +++++++++++++----- .../qt5informationnodeinstanceserver.h | 14 +- share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc | 1 + 11 files changed, 375 insertions(+), 166 deletions(-) create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/SceneView3D.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/Arrow.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/Arrow.qml index 67ae4c7b295..488198911b3 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/Arrow.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/Arrow.qml @@ -40,7 +40,7 @@ DirectionalDraggable { _targetStartPos.x + sceneRelativeDistance.x, _targetStartPos.y + sceneRelativeDistance.y, _targetStartPos.z + sceneRelativeDistance.z); - return targetNode.parent.mapPositionFromScene(newScenePos); + return targetNode.parent ? targetNode.parent.mapPositionFromScene(newScenePos) : newScenePos; } onDragged: { diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/AxisHelper.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/AxisHelper.qml index 25fe455f49d..34808aecde7 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/AxisHelper.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/AxisHelper.qml @@ -37,9 +37,9 @@ View3D { Node { OrthographicCamera { id: axisHelperCamera - rotation: editCameraCtrl.camera.rotation - position: editCameraCtrl.camera.position.minus(editCameraCtrl._lookAtPoint) - .normalized().times(600) + rotation: editCameraCtrl.camera ? editCameraCtrl.camera.rotation : Qt.vector3d(0, 0, 0) + position: editCameraCtrl.camera ? editCameraCtrl.camera.position.minus(editCameraCtrl._lookAtPoint) + .normalized().times(600) : Qt.vector3d(0, 0, 0) } AutoScaleHelper { diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/EditCameraController.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/EditCameraController.qml index 9dd78568456..77252881184 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/EditCameraController.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/EditCameraController.qml @@ -46,6 +46,9 @@ Item { property Camera _prevCamera: null function restoreCameraState(cameraState) { + if (!camera) + return; + _lookAtPoint = cameraState[0]; _zoomFactor = cameraState[1]; camera.position = cameraState[2]; @@ -55,6 +58,9 @@ Item { } function storeCameraState(delay) { + if (!camera) + return; + var cameraState = []; cameraState[0] = _lookAtPoint; cameraState[1] = _zoomFactor; @@ -66,6 +72,9 @@ Item { function focusObject(targetObject, rotation, updateZoom) { + if (!camera) + return; + camera.rotation = rotation; var newLookAtAndZoom = _generalHelper.focusObjectToCamera( camera, _defaultCameraLookAtDistance, targetObject, view3d, _zoomFactor, updateZoom); @@ -76,16 +85,19 @@ Item { function zoomRelative(distance) { + if (!camera) + return; + _zoomFactor = _generalHelper.zoomCamera(camera, distance, _defaultCameraLookAtDistance, _lookAtPoint, _zoomFactor, true); } Component.onCompleted: { - cameraCtrl._defaultCameraLookAtDistance = cameraCtrl.camera.position.length(); + cameraCtrl._defaultCameraLookAtDistance = Qt.vector3d(0, 600, -600).length(); } onCameraChanged: { - if (_prevCamera) { + if (camera && _prevCamera) { // Reset zoom on previous camera to ensure it's properties are good to copy to new cam _generalHelper.zoomCamera(_prevCamera, 0, _defaultCameraLookAtDistance, _lookAtPoint, 1, false); @@ -106,7 +118,7 @@ Item { hoverEnabled: false anchors.fill: parent onPositionChanged: { - if (mouse.modifiers === Qt.AltModifier && cameraCtrl._dragging) { + if (cameraCtrl.camera && mouse.modifiers === Qt.AltModifier && cameraCtrl._dragging) { var currentPoint = Qt.vector3d(mouse.x, mouse.y, 0); if (cameraCtrl._button == Qt.LeftButton) { _generalHelper.orbitCamera(cameraCtrl.camera, cameraCtrl._startRotation, @@ -124,7 +136,7 @@ Item { } } onPressed: { - if (mouse.modifiers === Qt.AltModifier) { + if (cameraCtrl.camera && mouse.modifiers === Qt.AltModifier) { cameraCtrl._dragging = true; cameraCtrl._startRotation = cameraCtrl.camera.rotation; cameraCtrl._startPosition = cameraCtrl.camera.position; @@ -147,9 +159,11 @@ Item { onCanceled: handleRelease() onWheel: { - // Emprically determined divisor for nice zoom - cameraCtrl.zoomRelative(wheel.angleDelta.y / -40); - cameraCtrl.storeCameraState(500); + if (cameraCtrl.camera) { + // Emprically determined divisor for nice zoom + cameraCtrl.zoomRelative(wheel.angleDelta.y / -40); + cameraCtrl.storeCameraState(500); + } } } } diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml index 36b10b9fc91..ad569f0f611 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml @@ -41,7 +41,8 @@ Window { // need all those flags otherwise the title bar disappears after setting WindowStaysOnTopHint flag later flags: Qt.Window | Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.WindowMinMaxButtonsHint | Qt.WindowCloseButtonHint - property alias scene: editView.importScene + property Node activeScene: null + property View3D editView: null property alias showEditLight: btnEditViewLight.toggled property alias usePerspective: btnPerspective.toggled @@ -63,6 +64,31 @@ Window { onShowEditLightChanged: _generalHelper.storeToolState("showEditLight", showEditLight) onGlobalOrientationChanged: _generalHelper.storeToolState("globalOrientation", globalOrientation) + onActiveSceneChanged: { + // importScene cannot be updated after initial set, so we need to reconstruct entire View3D + var component = Qt.createComponent("SceneView3D.qml"); + if (component.status === Component.Ready) { + var oldView = editView; + + if (editView) + editView.visible = false; + + editView = component.createObject(viewRect, + {"usePerspective": usePerspective, + "showSceneLight": showEditLight, + "importScene": activeScene, + "z": 1}); + editView.usePerspective = Qt.binding(function() {return usePerspective;}); + editView.showSceneLight = Qt.binding(function() {return showEditLight;}); + + selectionBoxes.length = 0; + ensureSelectionBoxes(1); + + if (oldView) + oldView.destroy(); + } + } + function updateToolStates(toolStates) { // Init the stored state so we don't unnecessarily reflect changes back to creator _generalHelper.initToolStates(toolStates); @@ -103,9 +129,13 @@ Window { if (component.status === Component.Ready) { for (var i = 0; i < needMore; ++i) { var geometryName = _generalHelper.generateUniqueName("SelectionBoxGeometry"); - var box = component.createObject(mainSceneHelpers, {"view3D": editView, + var boxParent = null; + if (editView) + boxParent = editView.sceneHelpers; + var box = component.createObject(boxParent, {"view3D": editView, "geometryName": geometryName}); selectionBoxes[selectionBoxes.length] = box; + box.view3D = Qt.binding(function() {return editView;}); } } } @@ -133,7 +163,7 @@ Window { function handleObjectClicked(object, multi) { var theObject = object; if (btnSelectGroup.selected) { - while (theObject && theObject.parent !== scene) + while (theObject && theObject.parent !== activeScene) theObject = theObject.parent; } // Object selection logic: @@ -162,11 +192,12 @@ Window { selectionChanged(newSelection); } - function addLightGizmo(obj) + function addLightGizmo(scene, obj) { // Insert into first available gizmo for (var i = 0; i < lightGizmos.length; ++i) { if (!lightGizmos[i].targetNode) { + lightGizmos[i].scene = scene; lightGizmos[i].targetNode = obj; return; } @@ -177,18 +208,21 @@ Window { if (component.status === Component.Ready) { var gizmo = component.createObject(overlayScene, {"view3D": overlayView, "targetNode": obj, - "selectedNodes": selectedNodes}); + "selectedNodes": selectedNodes, "scene": scene, + "activeScene": activeScene}); lightGizmos[lightGizmos.length] = gizmo; gizmo.clicked.connect(handleObjectClicked); gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;}); + gizmo.activeScene = Qt.binding(function() {return activeScene;}); } } - function addCameraGizmo(obj) + function addCameraGizmo(scene, obj) { // Insert into first available gizmo for (var i = 0; i < cameraGizmos.length; ++i) { if (!cameraGizmos[i].targetNode) { + cameraGizmos[i].scene = scene; cameraGizmos[i].targetNode = obj; return; } @@ -200,11 +234,13 @@ Window { var gizmo = component.createObject( overlayScene, {"view3D": overlayView, "targetNode": obj, "geometryName": geometryName, - "viewPortRect": viewPortRect, "selectedNodes": selectedNodes}); + "viewPortRect": viewPortRect, "selectedNodes": selectedNodes, + "scene": scene, "activeScene": activeScene}); cameraGizmos[cameraGizmos.length] = gizmo; gizmo.clicked.connect(handleObjectClicked); gizmo.viewPortRect = Qt.binding(function() {return viewPortRect;}); gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;}); + gizmo.activeScene = Qt.binding(function() {return activeScene;}); } } @@ -233,19 +269,19 @@ Window { PerspectiveCamera { id: overlayPerspectiveCamera - clipFar: editPerspectiveCamera.clipFar - clipNear: editPerspectiveCamera.clipNear - position: editPerspectiveCamera.position - rotation: editPerspectiveCamera.rotation + clipFar: viewWindow.editView ? viewWindow.editView.perpectiveCamera.clipFar : 1000 + clipNear: viewWindow.editView ? viewWindow.editView.perpectiveCamera.clipNear : 1 + position: viewWindow.editView ? viewWindow.editView.perpectiveCamera.position : Qt.vector3d(0, 0, 0) + rotation: viewWindow.editView ? viewWindow.editView.perpectiveCamera.rotation : Qt.vector3d(0, 0, 0) } OrthographicCamera { id: overlayOrthoCamera - clipFar: editOrthoCamera.clipFar - clipNear: editOrthoCamera.clipNear - position: editOrthoCamera.position - rotation: editOrthoCamera.rotation - scale: editOrthoCamera.scale + clipFar: viewWindow.editView ? viewWindow.editView.orthoCamera.clipFar : 1000 + clipNear: viewWindow.editView ? viewWindow.editView.orthoCamera.clipNear : 1 + position: viewWindow.editView ? viewWindow.editView.orthoCamera.position : Qt.vector3d(0, 0, 0) + rotation: viewWindow.editView ? viewWindow.editView.orthoCamera.rotation : Qt.vector3d(0, 0, 0) + scale: viewWindow.editView ? viewWindow.editView.orthoCamera.scale : Qt.vector3d(0, 0, 0) } MouseArea3D { @@ -349,6 +385,7 @@ Window { } Rectangle { + id: viewRect anchors.fill: parent focus: true @@ -361,11 +398,13 @@ Window { anchors.fill: parent acceptedButtons: Qt.LeftButton onClicked: { - var pickResult = editView.pick(mouse.x, mouse.y); - handleObjectClicked(_generalHelper.resolvePick(pickResult.objectHit), - mouse.modifiers & Qt.ControlModifier); - if (!pickResult.objectHit) - mouse.accepted = false; + if (viewWindow.editView) { + var pickResult = viewWindow.editView.pick(mouse.x, mouse.y); + handleObjectClicked(_generalHelper.resolvePick(pickResult.objectHit), + mouse.modifiers & Qt.ControlModifier); + if (!pickResult.objectHit) + mouse.accepted = false; + } } } @@ -373,57 +412,12 @@ Window { anchors.fill: parent } - View3D { - id: editView - anchors.fill: parent - camera: usePerspective ? editPerspectiveCamera : editOrthoCamera - - Node { - id: mainSceneHelpers - - HelperGrid { - id: helperGrid - lines: 50 - step: 50 - } - - PointLight { - id: editLight - visible: showEditLight - position: usePerspective ? editPerspectiveCamera.position - : editOrthoCamera.position - quadraticFade: 0 - linearFade: 0 - } - - // Initial camera position and rotation should be such that they look at origin. - // Otherwise EditCameraController._lookAtPoint needs to be initialized to correct - // point. - PerspectiveCamera { - id: editPerspectiveCamera - z: -600 - y: 600 - rotation.x: 45 - clipFar: 100000 - clipNear: 1 - } - - OrthographicCamera { - id: editOrthoCamera - z: -600 - y: 600 - rotation.x: 45 - clipFar: 100000 - clipNear: -10000 - } - } - } - View3D { id: overlayView anchors.fill: parent camera: usePerspective ? overlayPerspectiveCamera : overlayOrthoCamera importScene: overlayScene + z: 2 } Overlay2D { @@ -431,6 +425,7 @@ Window { targetNode: moveGizmo.visible ? moveGizmo : scaleGizmo targetView: overlayView visible: targetNode.dragging + z: 3 Rectangle { color: "white" @@ -463,9 +458,9 @@ Window { EditCameraController { id: cameraControl - camera: editView.camera + camera: viewWindow.editView ? viewWindow.editView.camera : null anchors.fill: parent - view3d: editView + view3d: viewWindow.editView } } @@ -557,10 +552,10 @@ Window { togglable: false onSelectedChanged: { - if (selected) { + if (viewWindow.editView && selected) { var targetNode = viewWindow.selectedNodes.length > 0 ? selectionBoxes[0].model : null; - cameraControl.focusObject(targetNode, editView.camera.rotation, true); + cameraControl.focusObject(targetNode, viewWindow.editView.camera.rotation, true); } } } diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/IconGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/IconGizmo.qml index 2f78458bbdd..a198cae04bc 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/IconGizmo.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/IconGizmo.qml @@ -30,6 +30,8 @@ import QtGraphicalEffects 1.12 Node { id: iconGizmo + property Node activeScene: null + property Node scene: null property View3D view3D property bool highlightOnHover: true property Node targetNode: null @@ -50,7 +52,7 @@ Node { position: targetNode ? targetNode.scenePosition : Qt.vector3d(0, 0, 0) rotation: targetNode ? targetNode.sceneRotation : Qt.vector3d(0, 0, 0) - visible: targetNode ? targetNode.visible : false + visible: activeScene === scene && (targetNode ? targetNode.visible : false) Overlay2D { id: iconOverlay diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/PlanarMoveHandle.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/PlanarMoveHandle.qml index f79ae6e248e..591f1196dea 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/PlanarMoveHandle.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/PlanarMoveHandle.qml @@ -40,7 +40,7 @@ PlanarDraggable { _targetStartPos.x + sceneRelativeDistance.x, _targetStartPos.y + sceneRelativeDistance.y, _targetStartPos.z + sceneRelativeDistance.z); - return targetNode.parent.mapPositionFromScene(newScenePos); + return targetNode.parent ? targetNode.parent.mapPositionFromScene(newScenePos) : newScenePos; } onDragged: { diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/SceneView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/SceneView3D.qml new file mode 100644 index 00000000000..09139716619 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/SceneView3D.qml @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick3D 1.14 + +View3D { + id: sceneView + anchors.fill: parent + + property bool usePerspective: false + property bool showSceneLight: false + property alias sceneHelpers: sceneHelpers + property alias perpectiveCamera: scenePerspectiveCamera + property alias orthoCamera: sceneOrthoCamera + + camera: usePerspective ? scenePerspectiveCamera : sceneOrthoCamera + + Node { + id: sceneHelpers + + HelperGrid { + id: helperGrid + lines: 50 + step: 50 + } + + PointLight { + id: sceneLight + visible: showSceneLight + position: usePerspective ? scenePerspectiveCamera.position + : sceneOrthoCamera.position + quadraticFade: 0 + linearFade: 0 + } + + // Initial camera position and rotation should be such that they look at origin. + // Otherwise EditCameraController._lookAtPoint needs to be initialized to correct + // point. + PerspectiveCamera { + id: scenePerspectiveCamera + z: -600 + y: 600 + rotation.x: 45 + clipFar: 100000 + clipNear: 1 + } + + OrthographicCamera { + id: sceneOrthoCamera + z: -600 + y: 600 + rotation.x: 45 + clipFar: 100000 + clipNear: -10000 + } + } +} diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.cpp index 910501a9e4f..9d8de9dc178 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.cpp @@ -154,8 +154,11 @@ QSSGRenderGraphObject *SelectionBoxGeometry::updateSpatialNode(QSSGRenderGraphOb // Explicitly set local transform of root node to target node parent's global transform // to avoid having to reparent the selection box. This has to be done directly on render // nodes. - targetRN->parent->calculateGlobalVariables(); - QMatrix4x4 m = targetRN->parent->globalTransform; + QMatrix4x4 m; + if (targetRN->parent) { + targetRN->parent->calculateGlobalVariables(); + m = targetRN->parent->globalTransform; + } rootRN->localTransform = m; rootRN->markDirty(QSSGRenderNode::TransformDirtyFlag::TransformNotDirty); rootRN->calculateGlobalVariables(); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index 501145166c9..0413a3bef9c 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -81,6 +81,12 @@ #include #include +#ifdef QUICK3D_MODULE +#include +#include +#include +#endif + namespace QmlDesigner { static QVariant objectToVariant(QObject *object) @@ -255,12 +261,12 @@ void Qt5InformationNodeInstanceServer::modifyVariantValue( const PropertyName &propertyName, ValuesModifiedCommand::TransactionOption option) { - PropertyName targetPopertyName; + PropertyName targetPropertyName; // Position is a special case, because the position can be 'position.x 'or simply 'x'. // We prefer 'x'. if (propertyName != "position") - targetPopertyName = propertyName; + targetPropertyName = propertyName; auto *obj = node.value(); @@ -275,7 +281,7 @@ void Qt5InformationNodeInstanceServer::modifyVariantValue( // We do have to split position into position.x, position.y, position.z ValuesModifiedCommand command = createValuesModifiedCommand(vectorToPropertyValue( instance, - targetPopertyName, + targetPropertyName, obj->property(propertyName))); command.transactionOption = option; @@ -318,14 +324,32 @@ void Qt5InformationNodeInstanceServer::handleToolStateChanged(const QString &too QVariant::fromValue(data)}); } -void Qt5InformationNodeInstanceServer::updateViewPortRect() +void Qt5InformationNodeInstanceServer::handleView3DSizeChange() { - QRectF viewPortrect(0, 0, m_viewPortInstance.internalObject()->property("width").toDouble(), - m_viewPortInstance.internalObject()->property("height").toDouble()); + QObject *view3D = sender(); + if (view3D == m_active3DView.internalObject()) + updateView3DRect(view3D); +} + +void Qt5InformationNodeInstanceServer::updateView3DRect(QObject *view3D) +{ + QRectF viewPortrect(0., 0., 1000., 1000.); + if (view3D) { + viewPortrect = QRectF(0., 0., view3D->property("width").toDouble(), + view3D->property("height").toDouble()); + } QQmlProperty viewPortProperty(m_editView3D, "viewPortRect", context()); viewPortProperty.write(viewPortrect); } +void Qt5InformationNodeInstanceServer::updateActiveSceneToEditView3D() +{ + QQmlProperty sceneProperty(m_editView3D, "activeScene", context()); + sceneProperty.write(objectToVariant(m_active3DScene)); + if (m_active3DView.isValid()) + updateView3DRect(m_active3DView.internalObject()); +} + Qt5InformationNodeInstanceServer::Qt5InformationNodeInstanceServer(NodeInstanceClientInterface *nodeInstanceClient) : Qt5NodeInstanceServer(nodeInstanceClient) { @@ -425,65 +449,147 @@ void Qt5InformationNodeInstanceServer::handleSelectionChangeTimeout() changeSelection(m_pendingSelectionChangeCommand); } -QObject *Qt5InformationNodeInstanceServer::findRootNodeOf3DViewport( - const QList &instanceList) const -{ - for (const ServerNodeInstance &instance : instanceList) { - if (instance.isSubclassOf("QQuick3DViewport")) { - QObject *rootObj = nullptr; - int viewChildCount = 0; - for (const ServerNodeInstance &child : instanceList) { /* Look for scene node */ - /* The QQuick3DViewport always creates a root node. - * This root node contains the complete scene. */ - if (child.isSubclassOf("QQuick3DNode") && child.parent() == instance) { - // Implicit root node is not visible in editor, so there is often another node - // added below it that serves as the actual scene root node. - // If the found root is the only node child of the view, assume that is the case. - ++viewChildCount; - if (!rootObj) - rootObj = child.internalObject(); - } - } - if (viewChildCount == 1) - return rootObj; - else if (rootObj) - return rootObj->property("parent").value(); - } - } - return nullptr; -} - void Qt5InformationNodeInstanceServer::createCameraAndLightGizmos( const QList &instanceList) const { - QObjectList cameras; - QObjectList lights; + QHash cameras; + QHash lights; for (const ServerNodeInstance &instance : instanceList) { if (instance.isSubclassOf("QQuick3DCamera")) - cameras << instance.internalObject(); + cameras[find3DSceneRoot(instance)] << instance.internalObject(); else if (instance.isSubclassOf("QQuick3DAbstractLight")) - lights << instance.internalObject(); + lights[find3DSceneRoot(instance)] << instance.internalObject(); } - for (auto &obj : qAsConst(cameras)) { - QMetaObject::invokeMethod(m_editView3D, "addCameraGizmo", - Q_ARG(QVariant, objectToVariant(obj))); + auto cameraIt = cameras.constBegin(); + while (cameraIt != cameras.constEnd()) { + const auto cameraObjs = cameraIt.value(); + for (auto &obj : cameraObjs) { + QMetaObject::invokeMethod(m_editView3D, "addCameraGizmo", + Q_ARG(QVariant, objectToVariant(cameraIt.key())), + Q_ARG(QVariant, objectToVariant(obj))); + } + ++cameraIt; } - for (auto &obj : qAsConst(lights)) { - QMetaObject::invokeMethod(m_editView3D, "addLightGizmo", - Q_ARG(QVariant, objectToVariant(obj))); + auto lightIt = lights.constBegin(); + while (lightIt != lights.constEnd()) { + const auto lightObjs = lightIt.value(); + for (auto &obj : lightObjs) { + QMetaObject::invokeMethod(m_editView3D, "addLightGizmo", + Q_ARG(QVariant, objectToVariant(lightIt.key())), + Q_ARG(QVariant, objectToVariant(obj))); + } + ++lightIt; } } -ServerNodeInstance Qt5InformationNodeInstanceServer::findViewPort( - const QList &instanceList) +void Qt5InformationNodeInstanceServer::addViewPorts(const QList &instanceList) { for (const ServerNodeInstance &instance : instanceList) { - if (instance.isSubclassOf("QQuick3DViewport")) - return instance; + if (instance.isSubclassOf("QQuick3DViewport")) { + m_view3Ds << instance; + QObject *obj = instance.internalObject(); + QObject::connect(obj, SIGNAL(widthChanged()), this, SLOT(handleView3DSizeChange())); + QObject::connect(obj, SIGNAL(heightChanged()), this, SLOT(handleView3DSizeChange())); + } } - return ServerNodeInstance(); +} + +ServerNodeInstance Qt5InformationNodeInstanceServer::findView3DForInstance(const ServerNodeInstance &instance) const +{ +#ifdef QUICK3D_MODULE + if (!instance.isValid()) + return {}; + + // View3D of an instance is one of the following, in order of priority: + // - Any direct ancestor View3D of the instance + // - Any View3D that specifies the instance's scene as importScene + ServerNodeInstance checkInstance = instance; + while (checkInstance.isValid()) { + if (checkInstance.isSubclassOf("QQuick3DViewport")) + return checkInstance; + else + checkInstance = checkInstance.parent(); + } + + // If no ancestor View3D was found, check if the scene root is specified as importScene in + // some View3D. + QObject *sceneRoot = find3DSceneRoot(instance); + for (const auto &view3D : qAsConst(m_view3Ds)) { + auto view = qobject_cast(view3D.internalObject()); + if (sceneRoot == view->importScene()) + return view3D; + } +#endif + return {}; +} + +QObject *Qt5InformationNodeInstanceServer::find3DSceneRoot(const ServerNodeInstance &instance) const +{ +#ifdef QUICK3D_MODULE + // The root of a 3D scene is any QQuick3DNode that doesn't have QQuick3DNode as parent. + // One exception is QQuick3DSceneRootNode that has only a single child QQuick3DNode (not + // a subclass of one, but exactly QQuick3DNode). In that case we consider the single child node + // to be the scene root (as QQuick3DSceneRootNode is not visible in the navigator scene graph). + + if (!instance.isValid()) + return nullptr; + + QQuick3DNode *childNode = nullptr; + auto countChildNodes = [&childNode](QQuick3DViewport *view) -> int { + QQuick3DNode *sceneNode = view->scene(); + QList children = sceneNode->childItems(); + int nodeCount = 0; + for (const auto &child : children) { + auto nodeChild = qobject_cast(child); + if (nodeChild) { + ++nodeCount; + childNode = nodeChild; + } + } + return nodeCount; + }; + + // In case of View3D is selected, the root scene is whatever is contained in View3D, or + // importScene, in case there is no content in View3D + QObject *obj = instance.internalObject(); + auto view = qobject_cast(obj); + if (view) { + int nodeCount = countChildNodes(view); + if (nodeCount == 0) + return view->importScene(); + else if (nodeCount == 1) + return childNode; + else + return view->scene(); + } + + ServerNodeInstance checkInstance = instance; + bool foundNode = checkInstance.isSubclassOf("QQuick3DNode"); + while (checkInstance.isValid()) { + ServerNodeInstance parentInstance = checkInstance.parent(); + if (parentInstance.isSubclassOf("QQuick3DViewport")) { + view = qobject_cast(parentInstance.internalObject()); + int nodeCount = countChildNodes(view); + if (nodeCount == 1) + return checkInstance.internalObject(); + else + return view->scene(); + } else if (parentInstance.isSubclassOf("QQuick3DNode")) { + foundNode = true; + checkInstance = parentInstance; + } else { + if (!foundNode) { + // We haven't found any node yet, continue the search + checkInstance = parentInstance; + } else { + return checkInstance.internalObject(); + } + } + } +#endif + return nullptr; } void Qt5InformationNodeInstanceServer::setup3DEditView(const QList &instanceList, @@ -491,38 +597,28 @@ void Qt5InformationNodeInstanceServer::setup3DEditView(const QListsetParent(m_editView3D); - sceneProperty.write(objectToVariant(m_rootNode)); - QQmlProperty parentProperty(m_rootNode, "parent", context()); - parentProperty.write(objectToVariant(m_editView3D)); - QQmlProperty showLightProperty(m_editView3D, "showLight", context()); - showLightProperty.write(showCustomLight); - - m_viewPortInstance = findViewPort(instanceList); - if (m_viewPortInstance.internalObject()) { - QObject::connect(m_viewPortInstance.internalObject(), SIGNAL(widthChanged()), - this, SLOT(updateViewPortRect())); - QObject::connect(m_viewPortInstance.internalObject(), SIGNAL(heightChanged()), - this, SLOT(updateViewPortRect())); - updateViewPortRect(); } + updateActiveSceneToEditView3D(); + createCameraAndLightGizmos(instanceList); QMetaObject::invokeMethod(m_editView3D, "updateToolStates", Q_ARG(QVariant, toolStates)); @@ -711,19 +807,33 @@ void Qt5InformationNodeInstanceServer::changeSelection(const ChangeSelectionComm return; } + // Find a scene root of the selection to update active scene shown const QVector instanceIds = command.instanceIds(); QVariantList selectedObjs; + QObject *firstSceneRoot = nullptr; + ServerNodeInstance firstInstance; for (qint32 id : instanceIds) { if (hasInstanceForId(id)) { ServerNodeInstance instance = instanceForId(id); + QObject *sceneRoot = find3DSceneRoot(instance); + if (!firstSceneRoot && sceneRoot) { + firstSceneRoot = sceneRoot; + firstInstance = instance; + } QObject *object = nullptr; - if (instance.isSubclassOf("QQuick3DNode")) + if (firstSceneRoot && sceneRoot == firstSceneRoot && instance.isSubclassOf("QQuick3DNode")) object = instance.internalObject(); - if (object && object != m_rootNode) + if (object && (firstSceneRoot != object || instance.isSubclassOf("QQuick3DModel"))) selectedObjs << objectToVariant(object); } } + if (firstSceneRoot && m_active3DScene != firstSceneRoot) { + m_active3DScene = firstSceneRoot; + m_active3DView = findView3DForInstance(firstInstance); + updateActiveSceneToEditView3D(); + } + // Ensure the UI has enough selection box items. If it doesn't yet have them, which can be the // case when the first selection processed is a multiselection, we wait a bit as // using the new boxes immediately leads to visual glitches. diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h index adc18674880..e8a782a7df9 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h @@ -62,7 +62,7 @@ private slots: void handleObjectPropertyCommit(const QVariant &object, const QVariant &propName); void handleObjectPropertyChange(const QVariant &object, const QVariant &propName); void handleToolStateChanged(const QString &tool, const QVariant &toolState); - void updateViewPortRect(); + void handleView3DSizeChange(); protected: void collectItemChangesAndSendChangeCommands() override; @@ -81,9 +81,10 @@ private: QObject *createEditView3D(QQmlEngine *engine); void setup3DEditView(const QList &instanceList, const QVariantMap &toolStates); - QObject *findRootNodeOf3DViewport(const QList &instanceList) const; void createCameraAndLightGizmos(const QList &instanceList) const; - ServerNodeInstance findViewPort(const QList &instanceList); + void addViewPorts(const QList &instanceList); + ServerNodeInstance findView3DForInstance(const ServerNodeInstance &instance) const; + QObject *find3DSceneRoot(const ServerNodeInstance &instance) const; QVector vectorToPropertyValue(const ServerNodeInstance &instance, const PropertyName &propertyName, const QVariant &variant); @@ -91,8 +92,13 @@ private: const PropertyName &propertyName, ValuesModifiedCommand::TransactionOption option); bool dropAcceptable(QDragMoveEvent *event) const; + void updateView3DRect(QObject *view3D); + void updateActiveSceneToEditView3D(); QObject *m_editView3D = nullptr; + QVector m_view3Ds; + ServerNodeInstance m_active3DView; + QObject *m_active3DScene = nullptr; QSet m_parentChangedSet; QList m_completedComponentList; QList m_tokenList; @@ -100,8 +106,6 @@ private: QTimer m_selectionChangeTimer; QVariant m_changedNode; PropertyName m_changedProperty; - ServerNodeInstance m_viewPortInstance; - QObject *m_rootNode = nullptr; ChangeSelectionCommand m_pendingSelectionChangeCommand; }; diff --git a/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc b/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc index 4174d4b5b72..79ffb4934f2 100644 --- a/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc +++ b/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc @@ -27,6 +27,7 @@ mockfiles/ToggleButton.qml mockfiles/RotateGizmo.qml mockfiles/RotateRing.qml + mockfiles/SceneView3D.qml mockfiles/SelectionBox.qml mockfiles/AxisHelper.qml mockfiles/AxisHelperArm.qml