From 98673190adb6e67f133741254753750dae82ee4b Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Mon, 21 Feb 2022 11:26:53 +0200 Subject: [PATCH] QmlDesigner: Add visualization gizmo for ParticleEmitter3D Particle emitters are now visualized in 3D edit view either by a small sphere for point emitters or by a proper model for model based emitters. The visualization model is rendered transparent. The visualization models can be displayed either always or only when the parent particle system is active in editor. Trail emitters are not visualized, as any visualization would be misleading, since these emitters follow generated particles. Fixes: QDS-6189 Change-Id: Idb6f12cadd9cea8110e5290cc18443aeb62c38d6 Reviewed-by: Mahmoud Badri --- .../qmlpuppet/commands/view3dactioncommand.h | 1 + .../qtcreator/qml/qmlpuppet/editor3d_qt6.qrc | 3 +- .../qmlpuppet/mockfiles/qt6/EditView3D.qml | 126 ++++++++++++++--- .../qml/qmlpuppet/mockfiles/qt6/IconGizmo.qml | 3 +- .../mockfiles/qt6/ParticleEmitterGizmo.qml | 127 ++++++++++++++++++ .../mockfiles/qt6/ParticleSystemGizmo.qml | 4 + .../qmlpuppet/mockfiles/qt6/RotateRing.qml | 1 + .../qml2puppet/editor3d/generalhelper.cpp | 31 +++++ .../qml2puppet/editor3d/generalhelper.h | 2 + .../qt5informationnodeinstanceserver.cpp | 78 +++++++++-- .../qt5informationnodeinstanceserver.h | 2 +- .../components/edit3d/edit3dview.cpp | 35 +++-- .../components/edit3d/edit3dview.h | 1 + .../instances/nodeinstanceview.cpp | 7 + .../qmldesigner/qmldesignerconstants.h | 1 + 15 files changed, 383 insertions(+), 39 deletions(-) create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ParticleEmitterGizmo.qml diff --git a/share/qtcreator/qml/qmlpuppet/commands/view3dactioncommand.h b/share/qtcreator/qml/qmlpuppet/commands/view3dactioncommand.h index cb23b2c9ad5..cb34c253f9e 100644 --- a/share/qtcreator/qml/qmlpuppet/commands/view3dactioncommand.h +++ b/share/qtcreator/qml/qmlpuppet/commands/view3dactioncommand.h @@ -50,6 +50,7 @@ public: ShowSelectionBox, ShowIconGizmo, ShowCameraFrustum, + ShowParticleEmitter, Edit3DParticleModeToggle, ParticlesPlay, ParticlesRestart, diff --git a/share/qtcreator/qml/qmlpuppet/editor3d_qt6.qrc b/share/qtcreator/qml/qmlpuppet/editor3d_qt6.qrc index 065f9e32cf3..9831bec4be2 100644 --- a/share/qtcreator/qml/qmlpuppet/editor3d_qt6.qrc +++ b/share/qtcreator/qml/qmlpuppet/editor3d_qt6.qrc @@ -35,7 +35,6 @@ mockfiles/qt6/LightGizmo.qml mockfiles/qt6/LightIconGizmo.qml mockfiles/qt6/LightModel.qml - mockfiles/qt6/ParticleSystemGizmo.qml mockfiles/qt6/Line3D.qml mockfiles/qt6/MaterialNodeView.qml mockfiles/qt6/ModelNode2DImageView.qml @@ -44,6 +43,8 @@ mockfiles/qt6/MoveGizmo.qml mockfiles/qt6/NodeNodeView.qml mockfiles/qt6/Overlay2D.qml + mockfiles/qt6/ParticleSystemGizmo.qml + mockfiles/qt6/ParticleEmitterGizmo.qml mockfiles/qt6/PlanarDraggable.qml mockfiles/qt6/PlanarMoveHandle.qml mockfiles/qt6/PlanarScaleHandle.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml index eb7d50d2e4f..a8425fd6cb7 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml @@ -42,6 +42,7 @@ Item { property bool showSelectionBox: true property bool showIconGizmo: true property bool showCameraFrustum: false + property bool showParticleEmitter: false property bool usePerspective: true property bool globalOrientation: false property alias contentItem: contentItem @@ -58,9 +59,10 @@ Item { property var lightIconGizmos: [] property var cameraGizmos: [] property var particleSystemIconGizmos: [] + property var particleEmitterGizmos: [] property var selectionBoxes: [] property rect viewPortRect: Qt.rect(0, 0, 1000, 1000) - + property Node activeParticleSystem: null property bool shuttingDown: false property real fps: 0 @@ -70,15 +72,16 @@ Item { signal changeObjectProperty(var objects, var propNames) signal notifyActiveSceneChange() - onUsePerspectiveChanged: _generalHelper.storeToolState(sceneId, "usePerspective", usePerspective) - onShowEditLightChanged: _generalHelper.storeToolState(sceneId, "showEditLight", showEditLight) - onGlobalOrientationChanged: _generalHelper.storeToolState(sceneId, "globalOrientation", globalOrientation) - onShowGridChanged: _generalHelper.storeToolState(sceneId, "showGrid", showGrid); - onShowSelectionBoxChanged: _generalHelper.storeToolState(sceneId, "showSelectionBox", showSelectionBox); - onShowIconGizmoChanged: _generalHelper.storeToolState(sceneId, "showIconGizmo", showIconGizmo); - onShowCameraFrustumChanged: _generalHelper.storeToolState(sceneId, "showCameraFrustum", showCameraFrustum); - onSelectionModeChanged: _generalHelper.storeToolState(sceneId, "selectionMode", selectionMode); - onTransformModeChanged: _generalHelper.storeToolState(sceneId, "transformMode", transformMode); + onUsePerspectiveChanged: _generalHelper.storeToolState(sceneId, "usePerspective", usePerspective) + onShowEditLightChanged: _generalHelper.storeToolState(sceneId, "showEditLight", showEditLight) + onGlobalOrientationChanged: _generalHelper.storeToolState(sceneId, "globalOrientation", globalOrientation) + onShowGridChanged: _generalHelper.storeToolState(sceneId, "showGrid", showGrid); + onShowSelectionBoxChanged: _generalHelper.storeToolState(sceneId, "showSelectionBox", showSelectionBox); + onShowIconGizmoChanged: _generalHelper.storeToolState(sceneId, "showIconGizmo", showIconGizmo); + onShowCameraFrustumChanged: _generalHelper.storeToolState(sceneId, "showCameraFrustum", showCameraFrustum); + onShowParticleEmitterChanged: _generalHelper.storeToolState(sceneId, "showParticleEmitter", showParticleEmitter); + onSelectionModeChanged: _generalHelper.storeToolState(sceneId, "selectionMode", selectionMode); + onTransformModeChanged: _generalHelper.storeToolState(sceneId, "transformMode", transformMode); onActiveSceneChanged: updateActiveScene() @@ -232,6 +235,11 @@ Item { else if (resetToDefault) showCameraFrustum = false; + if ("showParticleEmitter" in toolStates) + showParticleEmitter = toolStates.showParticleEmitter; + else if (resetToDefault) + showParticleEmitter = false; + if ("usePerspective" in toolStates) usePerspective = toolStates.usePerspective; else if (resetToDefault) @@ -265,6 +273,7 @@ Item { _generalHelper.storeToolState(sceneId, "showSelectionBox", showSelectionBox) _generalHelper.storeToolState(sceneId, "showIconGizmo", showIconGizmo) _generalHelper.storeToolState(sceneId, "showCameraFrustum", showCameraFrustum) + _generalHelper.storeToolState(sceneId, "showParticleEmitter", showParticleEmitter) _generalHelper.storeToolState(sceneId, "usePerspective", usePerspective) _generalHelper.storeToolState(sceneId, "globalOrientation", globalOrientation) _generalHelper.storeToolState(sceneId, "selectionMode", selectionMode); @@ -480,12 +489,56 @@ Item { "activeScene": activeScene, "locked": _generalHelper.isLocked(obj), "hidden": _generalHelper.isHidden(obj), - "globalShow": showIconGizmo}); + "globalShow": showIconGizmo, + "activeParticleSystem": activeParticleSystem}); particleSystemIconGizmos[particleSystemIconGizmos.length] = gizmo; gizmo.clicked.connect(handleObjectClicked); gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;}); gizmo.activeScene = Qt.binding(function() {return activeScene;}); gizmo.globalShow = Qt.binding(function() {return showIconGizmo;}); + gizmo.activeParticleSystem = Qt.binding(function() {return activeParticleSystem;}); + } + } + + function addParticleEmitterGizmo(scene, obj) + { + // Insert into first available gizmo if we don't already have gizmo for this object + var slotFound = -1; + for (var i = 0; i < particleEmitterGizmos.length; ++i) { + if (!particleEmitterGizmos[i].targetNode) { + slotFound = i; + } else if (particleEmitterGizmos[i].targetNode === obj) { + particleEmitterGizmos[i].scene = scene; + return; + } + } + + if (slotFound !== -1) { + particleEmitterGizmos[slotFound].scene = scene; + particleEmitterGizmos[slotFound].targetNode = obj; + particleEmitterGizmos[slotFound].hidden = _generalHelper.isHidden(obj); + particleEmitterGizmos[slotFound].systemHidden = _generalHelper.isHidden(obj.system); + _generalHelper.registerGizmoTarget(obj); + return; + } + + // No free gizmos available, create a new one + var gizmoComponent = Qt.createComponent("ParticleEmitterGizmo.qml"); + if (gizmoComponent.status === Component.Ready) { + _generalHelper.registerGizmoTarget(obj); + var gizmo = gizmoComponent.createObject( + overlayScene, + {"targetNode": obj, "selectedNodes": selectedNodes, + "activeParticleSystem": activeParticleSystem, "scene": scene, + "activeScene": activeScene, "hidden": _generalHelper.isHidden(obj), + "systemHidden": _generalHelper.isHidden(obj.system), + "globalShow": showParticleEmitter}); + + particleEmitterGizmos[particleEmitterGizmos.length] = gizmo; + gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;}); + gizmo.activeParticleSystem = Qt.binding(function() {return activeParticleSystem;}); + gizmo.globalShow = Qt.binding(function() {return showParticleEmitter;}); + gizmo.activeScene = Qt.binding(function() {return activeScene;}); } } @@ -525,6 +578,18 @@ Item { } } + function releaseParticleEmitterGizmo(obj) + { + for (var i = 0; i < particleEmitterGizmos.length; ++i) { + if (particleEmitterGizmos[i].targetNode === obj) { + particleEmitterGizmos[i].scene = null; + particleEmitterGizmos[i].targetNode = null; + _generalHelper.unregisterGizmoTarget(obj); + return; + } + } + } + function updateLightGizmoScene(scene, obj) { for (var i = 0; i < lightIconGizmos.length; ++i) { @@ -555,6 +620,16 @@ Item { } } + function updateParticleEmitterGizmoScene(scene, obj) + { + for (var i = 0; i < particleEmitterGizmos.length; ++i) { + if (particleEmitterGizmos[i].targetNode === obj) { + particleEmitterGizmos[i].scene = scene; + return; + } + } + } + Component.onCompleted: { createEditView(); selectObjects([]); @@ -588,7 +663,12 @@ Item { return; } } - + for (var i = 0; i < particleEmitterGizmos.length; ++i) { + if (particleEmitterGizmos[i].targetNode === node) { + particleEmitterGizmos[i].locked = _generalHelper.isLocked(node); + return; + } + } } function onHiddenStateChanged(node) { @@ -610,6 +690,15 @@ Item { return; } } + for (var i = 0; i < particleEmitterGizmos.length; ++i) { + if (particleEmitterGizmos[i].targetNode === node) { + particleEmitterGizmos[i].hidden = _generalHelper.isHidden(node); + return; + } else if (particleEmitterGizmos[i].targetNode && particleEmitterGizmos[i].targetNode.system === node) { + particleEmitterGizmos[i].systemHidden = _generalHelper.isHidden(node); + return; + } + } } } @@ -810,9 +899,16 @@ Item { onPressed: (mouse)=> { if (viewRoot.editView) { - var pickResult = _generalHelper.pickViewAt(viewRoot.editView, mouse.x, mouse.y); - handleObjectClicked(_generalHelper.resolvePick(pickResult.objectHit), - mouse.modifiers & Qt.ControlModifier); + // First pick overlay to check for hits there + var pickResult = _generalHelper.pickViewAt(overlayView, mouse.x, mouse.y); + var resolvedResult = _generalHelper.resolvePick(pickResult.objectHit); + if (!resolvedResult) { + // No hits from overlay view, pick the main scene + pickResult = _generalHelper.pickViewAt(viewRoot.editView, mouse.x, mouse.y); + resolvedResult = _generalHelper.resolvePick(pickResult.objectHit); + } + + handleObjectClicked(resolvedResult, mouse.modifiers & Qt.ControlModifier); if (pickResult.objectHit) { if (transformMode === EditView3D.TransformMode.Move) diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/IconGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/IconGizmo.qml index 7752693d559..2b78dd04aac 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/IconGizmo.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/IconGizmo.qml @@ -47,6 +47,7 @@ Item { property bool locked: false property bool globalShow: true property bool canBeVisible: activeScene === scene && !hidden && (targetNode ? targetNode.visible : false) + property real iconOpacity: selected ? 0.2 : 1 property alias iconSource: iconImage.source @@ -76,7 +77,7 @@ Item { border.color: "#7777ff" border.width: !iconGizmo.locked && iconGizmo.highlightOnHover && iconGizmo.hasMouse ? 2 : 0 radius: 5 - opacity: iconGizmo.selected ? 0.2 : 1 + opacity: iconGizmo.iconOpacity Image { id: iconImage fillMode: Image.Pad diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ParticleEmitterGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ParticleEmitterGizmo.qml new file mode 100644 index 00000000000..a06a38a2024 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ParticleEmitterGizmo.qml @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2022 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 +import QtQuick3D +import QtQuick3D.Particles3D + +Node { + id: root + + property Node targetNode: null + property var selectedNodes: [] + property Node activeParticleSystem: null + property Node scene: null + property Node activeScene: null + property bool hidden: false + property bool systemHidden: false + property Node shapeModel: null + property bool globalShow: false + property bool canBeVisible: activeScene === scene && targetNode && !hidden && !systemHidden + property bool partOfActiveSystem: root.targetNode && root.targetNode.system === activeParticleSystem + + opacity: 0.15 + + readonly property bool selected: selectedNodes.includes(targetNode) + + visible: canBeVisible && (globalShow || selected) + + position: targetNode ? targetNode.scenePosition : Qt.vector3d(0, 0, 0) + rotation: targetNode ? targetNode.sceneRotation : Qt.quaternion(1, 0, 0, 0) + scale: targetNode ? targetNode.sceneScale : Qt.vector3d(1, 1, 1) + + function basicShape() + { + if (targetNode && targetNode.shape && targetNode.shape instanceof ParticleShape3D) { + if (targetNode.shape.type === ParticleShape3D.Cube) + return "#Cube"; + else if (targetNode.shape.type === ParticleShape3D.Cylinder) + return "#Cylinder"; + } + return "#Sphere"; + } + + function updateShape() + { + if (shapeModel) + shapeModel.destroy(); + + if (!targetNode) + return; + + if (targetNode.shape instanceof ParticleModelShape3D) { + shapeModel = _generalHelper.createParticleEmitterGizmoModel(targetNode, defaultMaterial); + shapeModel.parent = root; + } + } + + Component.onCompleted: { + updateShape(); + } + + Connections { + target: targetNode + function onSystemChanged() { systemHidden = _generalHelper.isHidden(system); } + } + + Connections { + target: targetNode + function onShapeChanged() { updateShape(); } + } + + Connections { + target: targetNode.shape instanceof ParticleModelShape3D ? targetNode.shape + :null + function onDelegateChanged() { updateShape(); } + } + + Connections { + target: targetNode.shape instanceof ParticleModelShape3D ? targetNode.shape.delegate + : null + function onSourceChanged() { updateShape(); } + } + + Model { + readonly property Node _pickTarget: root.targetNode + materials: [defaultMaterial] + source: basicShape() + scale: root.targetNode && root.targetNode.shape && targetNode.shape instanceof ParticleShape3D + ? root.targetNode.shape.extents.times(0.02) // default extent is 50 + : autoScale.getScale(Qt.vector3d(0.1, 0.1, 0.1)) + visible: !shapeModel + } + + AutoScaleHelper { + id: autoScale + view3D: overlayView + } + + DefaultMaterial { + id: defaultMaterial + diffuseColor: root.selected ? "#FF0000" : partOfActiveSystem ? "#FFFF00" : "#AAAAAA" + lighting: DefaultMaterial.NoLighting + cullMode: Material.NoCulling + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ParticleSystemGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ParticleSystemGizmo.qml index de591fcd217..3d498ce335d 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ParticleSystemGizmo.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ParticleSystemGizmo.qml @@ -28,5 +28,9 @@ import QtQuick3D 6.0 IconGizmo { id: particleSystemGizmo + + property Node activeParticleSystem: null + iconSource: "qrc:///qtquickplugin/mockfiles/images/editor_particlesystem.png" + iconOpacity: selected || activeParticleSystem == targetNode ? 0.2 : 1 } diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/RotateRing.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/RotateRing.qml index 8003839a894..79d31f45943 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/RotateRing.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/RotateRing.qml @@ -54,6 +54,7 @@ Model { Model { id: pickModel + readonly property bool _edit3dLocked: true // Make this non-pickable in main picking handling objectName: "PickModel for " + rotateRing.objectName source: "../meshes/ringselect.mesh" pickable: true diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp index 9d7d4b05ce9..3c976ea0cbe 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp @@ -46,6 +46,12 @@ #include #include +#ifdef QUICK3D_PARTICLES_MODULE +#include +#include +#include +#endif + #include namespace QmlDesigner { @@ -460,6 +466,31 @@ bool GeneralHelper::isPickable(QQuick3DNode *node) const return true; } +// Emitter gizmo model creation is done in C++ as creating dynamic properties and +// assigning materials to dynamically created models is lot simpler in C++ +QQuick3DNode *GeneralHelper::createParticleEmitterGizmoModel(QQuick3DNode *emitter, + QQuick3DMaterial *material) const +{ +#ifdef QUICK3D_PARTICLES_MODULE + auto e = qobject_cast(emitter); + if (!e || qobject_cast(e) || !material) + return nullptr; + + auto shape = qobject_cast(e->shape()); + if (shape && shape->delegate()) { + if (auto model = qobject_cast( + shape->delegate()->create(shape->delegate()->creationContext()))) { + QQmlEngine::setObjectOwnership(model, QQmlEngine::JavaScriptOwnership); + model->setProperty("_pickTarget", QVariant::fromValue(emitter)); + QQmlListReference matRef(model, "materials"); + matRef.append(material); + return model; + } + } +#endif + return nullptr; +} + void GeneralHelper::storeToolState(const QString &sceneId, const QString &tool, const QVariant &state, int delay) { diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h index 46e6019dd58..97ffa35adb4 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h @@ -89,6 +89,8 @@ public: Q_INVOKABLE bool isLocked(QQuick3DNode *node) const; Q_INVOKABLE bool isHidden(QQuick3DNode *node) const; Q_INVOKABLE bool isPickable(QQuick3DNode *node) const; + Q_INVOKABLE QQuick3DNode *createParticleEmitterGizmoModel(QQuick3DNode *emitter, + QQuick3DMaterial *material) const; Q_INVOKABLE void storeToolState(const QString &sceneId, const QString &tool, const QVariant &state, int delayEmit = 0); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index e2bd56562d5..c1df9cc56ee 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -113,6 +113,7 @@ #include #include #include +#include #endif #ifdef IMPORT_QUICK3D_ASSETS @@ -427,15 +428,23 @@ void Qt5InformationNodeInstanceServer::resetParticleSystem() void Qt5InformationNodeInstanceServer::handleParticleSystemSelected(QQuick3DParticleSystem* targetParticleSystem) { - if (!m_particleAnimationDriver || targetParticleSystem == m_targetParticleSystem) + if (targetParticleSystem == m_targetParticleSystem) + return; + + m_targetParticleSystem = targetParticleSystem; + + if (m_editView3DData.rootItem) { + QQmlProperty systemProperty(m_editView3DData.rootItem, "activeParticleSystem", context()); + systemProperty.write(objectToVariant(m_targetParticleSystem)); + } + + if (!m_particleAnimationDriver) return; m_particleAnimationDriver->reset(); // stop the previously selected from animating resetParticleSystem(); - m_targetParticleSystem = targetParticleSystem; - resetParticleSystem(); #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 2) QObject::disconnect(m_particleAnimationConnection); @@ -507,9 +516,17 @@ static QQuick3DParticleSystem *parentParticleSystem(QObject *selectedObject) return nullptr; } -void Qt5InformationNodeInstanceServer::handleParticleSystemDeselected(QObject *selectedObject) +void Qt5InformationNodeInstanceServer::handleParticleSystemDeselected() { + resetParticleSystem(); + m_targetParticleSystem = nullptr; + + if (m_editView3DData.rootItem) { + QQmlProperty systemProperty(m_editView3DData.rootItem, "activeParticleSystem", context()); + systemProperty.write(objectToVariant(nullptr)); + } + const auto anim = animations(); int i = 0; for (auto a : anim) { @@ -526,7 +543,7 @@ void Qt5InformationNodeInstanceServer::handleParticleSystemDeselected(QObject *s void Qt5InformationNodeInstanceServer::handleSelectionChanged(const QVariant &objs) { #ifdef QUICK3D_PARTICLES_MODULE - resetParticleSystem(); + bool skipSystemDeselect = m_targetParticleSystem == nullptr; #endif QList instanceList; const QVariantList varObjs = objs.value(); @@ -535,8 +552,20 @@ void Qt5InformationNodeInstanceServer::handleSelectionChanged(const QVariant &ob if (obj) { ServerNodeInstance instance = instanceForObject(obj); instanceList << instance; +#ifdef QUICK3D_PARTICLES_MODULE + if (!skipSystemDeselect) { + auto particleSystem = parentParticleSystem(instance.internalObject()); + skipSystemDeselect = particleSystem == m_targetParticleSystem; + } +#endif } } + +#ifdef QUICK3D_PARTICLES_MODULE + if (m_targetParticleSystem && !skipSystemDeselect) + handleParticleSystemDeselected(); +#endif + selectInstances(instanceList); // Hold selection changes reflected back from designer for a bit m_selectionChangeTimer.start(500); @@ -745,6 +774,10 @@ void Qt5InformationNodeInstanceServer::handleNode3DDestroyed(QObject *obj) } else if (qobject_cast(obj)) { QMetaObject::invokeMethod(m_editView3DData.rootItem, "releaseParticleSystemGizmo", Q_ARG(QVariant, objectToVariant(obj))); + } else if (qobject_cast(obj) + && !qobject_cast(obj)) { + QMetaObject::invokeMethod(m_editView3DData.rootItem, "releaseParticleEmitterGizmo", + Q_ARG(QVariant, objectToVariant(obj))); #endif } removeNode3D(obj); @@ -853,6 +886,11 @@ void Qt5InformationNodeInstanceServer::resolveSceneRoots() QMetaObject::invokeMethod(m_editView3DData.rootItem, "updateParticleSystemGizmoScene", Q_ARG(QVariant, objectToVariant(newRoot)), Q_ARG(QVariant, objectToVariant(node))); + } else if (qobject_cast(node) + && !qobject_cast(node)) { + QMetaObject::invokeMethod(m_editView3DData.rootItem, "updateParticleEmitterGizmoScene", + Q_ARG(QVariant, objectToVariant(newRoot)), + Q_ARG(QVariant, objectToVariant(node))); #endif } } @@ -1415,15 +1453,19 @@ void Qt5InformationNodeInstanceServer::createCameraAndLightGizmos( QHash cameras; QHash lights; QHash particleSystems; + QHash particleEmitters; for (const ServerNodeInstance &instance : instanceList) { - if (instance.isSubclassOf("QQuick3DCamera")) + if (instance.isSubclassOf("QQuick3DCamera")) { cameras[find3DSceneRoot(instance)] << instance.internalObject(); - else if (instance.isSubclassOf("QQuick3DAbstractLight")) + } else if (instance.isSubclassOf("QQuick3DAbstractLight")) { lights[find3DSceneRoot(instance)] << instance.internalObject(); - else if (instance.isSubclassOf("QQuick3DParticleSystem")) + } else if (instance.isSubclassOf("QQuick3DParticleSystem")) { particleSystems[find3DSceneRoot(instance)] << instance.internalObject(); - + } else if (instance.isSubclassOf("QQuick3DParticleEmitter") + && !instance.isSubclassOf("QQuick3DParticleTrailEmitter")) { + particleEmitters[find3DSceneRoot(instance)] << instance.internalObject(); + } } auto cameraIt = cameras.constBegin(); @@ -1456,6 +1498,17 @@ void Qt5InformationNodeInstanceServer::createCameraAndLightGizmos( } ++particleIt; } + + auto emitterIt = particleEmitters.constBegin(); + while (emitterIt != particleEmitters.constEnd()) { + const auto emitterObjs = emitterIt.value(); + for (auto &obj : emitterObjs) { + QMetaObject::invokeMethod(m_editView3DData.rootItem, "addParticleEmitterGizmo", + Q_ARG(QVariant, objectToVariant(emitterIt.key())), + Q_ARG(QVariant, objectToVariant(obj))); + } + ++emitterIt; + } } void Qt5InformationNodeInstanceServer::add3DViewPorts(const QList &instanceList) @@ -1946,7 +1999,7 @@ void Qt5InformationNodeInstanceServer::changeSelection(const ChangeSelectionComm if (particlesystem != m_targetParticleSystem) handleParticleSystemSelected(particlesystem); } else { - handleParticleSystemDeselected(instance.internalObject()); + handleParticleSystemDeselected(); } } #endif @@ -1957,6 +2010,8 @@ void Qt5InformationNodeInstanceServer::changeSelection(const ChangeSelectionComm || qobject_cast(object) #ifdef QUICK3D_PARTICLES_MODULE || qobject_cast(object) + || qobject_cast(object) + #endif ) { return true; @@ -2105,6 +2160,9 @@ void Qt5InformationNodeInstanceServer::view3DAction(const View3DActionCommand &c updatedState.insert("showCameraFrustum", command.isEnabled()); break; #ifdef QUICK3D_PARTICLES_MODULE + case View3DActionCommand::ShowParticleEmitter: + updatedState.insert("showParticleEmitter", command.isEnabled()); + break; case View3DActionCommand::ParticlesPlay: m_particleAnimationPlaying = command.isEnabled(); updatedState.insert("particlePlay", command.isEnabled()); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h index 2f6331e8dd4..8f08dec3892 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.h @@ -151,7 +151,7 @@ private: #ifdef QUICK3D_PARTICLES_MODULE void handleParticleSystemSelected(QQuick3DParticleSystem* targetParticleSystem); void resetParticleSystem(); - void handleParticleSystemDeselected(QObject *selectedObject); + void handleParticleSystemDeselected(); #endif RenderViewData m_editView3DData; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index 082c7d3eb55..dc4af0255fd 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -118,17 +118,18 @@ void Edit3DView::renderImage3DChanged(const QImage &img) void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState) { - const QString sceneKey = QStringLiteral("sceneInstanceId"); - const QString selectKey = QStringLiteral("selectionMode"); - const QString transformKey = QStringLiteral("transformMode"); - const QString perspectiveKey = QStringLiteral("usePerspective"); - const QString orientationKey = QStringLiteral("globalOrientation"); - const QString editLightKey = QStringLiteral("showEditLight"); - const QString gridKey = QStringLiteral("showGrid"); - const QString selectionBoxKey = QStringLiteral("showSelectionBox"); - const QString iconGizmoKey = QStringLiteral("showIconGizmo"); - const QString cameraFrustumKey = QStringLiteral("showCameraFrustum"); - const QString particlesPlayKey = QStringLiteral("particlePlay"); + const QString sceneKey = QStringLiteral("sceneInstanceId"); + const QString selectKey = QStringLiteral("selectionMode"); + const QString transformKey = QStringLiteral("transformMode"); + const QString perspectiveKey = QStringLiteral("usePerspective"); + const QString orientationKey = QStringLiteral("globalOrientation"); + const QString editLightKey = QStringLiteral("showEditLight"); + const QString gridKey = QStringLiteral("showGrid"); + const QString selectionBoxKey = QStringLiteral("showSelectionBox"); + const QString iconGizmoKey = QStringLiteral("showIconGizmo"); + const QString cameraFrustumKey = QStringLiteral("showCameraFrustum"); + const QString particleEmitterKey = QStringLiteral("showParticleEmitter"); + const QString particlesPlayKey = QStringLiteral("particlePlay"); if (sceneState.contains(sceneKey)) { qint32 newActiveScene = sceneState[sceneKey].value(); @@ -188,6 +189,11 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState) else m_showCameraFrustumAction->action()->setChecked(false); + if (sceneState.contains(particleEmitterKey)) + m_showParticleEmitterAction->action()->setChecked(sceneState[particleEmitterKey].toBool()); + else + m_showParticleEmitterAction->action()->setChecked(false); + if (sceneState.contains(particlesPlayKey)) m_particlesPlayAction->action()->setChecked(sceneState[particlesPlayKey].toBool()); else @@ -352,6 +358,12 @@ void Edit3DView::createEdit3DActions() QKeySequence(Qt::Key_C), true, false, {}, {}, nullptr, QCoreApplication::translate("ShowCameraFrustumAction", "Toggle between always showing the camera frustum visualization and only showing it when the camera is selected.")); + m_showParticleEmitterAction = new Edit3DAction( + QmlDesigner::Constants::EDIT3D_EDIT_SHOW_PARTICLE_EMITTER, View3DActionCommand::ShowParticleEmitter, + QCoreApplication::translate("ShowParticleEmitterAction", "Always Show Particle Emitters"), + QKeySequence(Qt::Key_M), true, false, {}, {}, nullptr, + QCoreApplication::translate("ShowParticleEmitterAction", "Toggle between always showing the particle emitter visualization and only showing it when the emitter is selected.")); + SelectionContextOperation resetTrigger = [this](const SelectionContext &) { m_particlesPlayAction->action()->setEnabled(particlemode); m_particlesRestartAction->action()->setEnabled(particlemode); @@ -458,6 +470,7 @@ void Edit3DView::createEdit3DActions() m_visibilityToggleActions << m_showSelectionBoxAction; m_visibilityToggleActions << m_showIconGizmoAction; m_visibilityToggleActions << m_showCameraFrustumAction; + m_visibilityToggleActions << m_showParticleEmitterAction; } QVector Edit3DView::leftActions() const diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index daddb84d6c8..40e21708734 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -102,6 +102,7 @@ private: Edit3DAction *m_showSelectionBoxAction = nullptr; Edit3DAction *m_showIconGizmoAction = nullptr; Edit3DAction *m_showCameraFrustumAction = nullptr; + Edit3DAction *m_showParticleEmitterAction = nullptr; Edit3DAction *m_resetAction = nullptr; Edit3DAction *m_particleViewModeAction = nullptr; Edit3DAction *m_particlesPlayAction = nullptr; diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 48391fe4b9c..547d7ad8a60 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -555,6 +555,13 @@ void NodeInstanceView::nodeReparented(const ModelNode &node, const NodeAbstractP updateChildren(newPropertyParent); m_nodeInstanceServer->reparentInstances( createReparentInstancesCommand(node, newPropertyParent, oldPropertyParent)); + + // Reset puppet when particle emitter is reparented to work around issue in + // autodetecting the particle system it belongs to. QTBUG-101157 + if (node.isSubclassOf("QtQuick.Particles3D.ParticleEmitter3D") + && node.property("system").toBindingProperty().expression().isEmpty()) { + resetPuppet(); + } } } diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index 03c2f42cca3..fc394ae089c 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -68,6 +68,7 @@ const char EDIT3D_EDIT_SHOW_GRID[] = "QmlDesigner.Editor3D.ToggleGrid"; const char EDIT3D_EDIT_SHOW_SELECTION_BOX[] = "QmlDesigner.Editor3D.ToggleSelectionBox"; const char EDIT3D_EDIT_SHOW_ICON_GIZMO[] = "QmlDesigner.Editor3D.ToggleIconGizmo"; const char EDIT3D_EDIT_SHOW_CAMERA_FRUSTUM[] = "QmlDesigner.Editor3D.ToggleCameraFrustum"; +const char EDIT3D_EDIT_SHOW_PARTICLE_EMITTER[] = "QmlDesigner.Editor3D.ToggleParticleEmitter"; const char EDIT3D_RESET_VIEW[] = "QmlDesigner.Editor3D.ResetView"; const char EDIT3D_PARTICLE_MODE[] = "QmlDesigner.Editor3D.ParticleViewModeToggle"; const char EDIT3D_PARTICLES_PLAY[] = "QmlDesigner.Editor3D.ParticlesPlay";