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";