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 <mahmoud.badri@qt.io>
This commit is contained in:
Miikka Heikkinen
2022-02-21 11:26:53 +02:00
parent 1b22558857
commit 98673190ad
15 changed files with 383 additions and 39 deletions

View File

@@ -50,6 +50,7 @@ public:
ShowSelectionBox,
ShowIconGizmo,
ShowCameraFrustum,
ShowParticleEmitter,
Edit3DParticleModeToggle,
ParticlesPlay,
ParticlesRestart,

View File

@@ -35,7 +35,6 @@
<file>mockfiles/qt6/LightGizmo.qml</file>
<file>mockfiles/qt6/LightIconGizmo.qml</file>
<file>mockfiles/qt6/LightModel.qml</file>
<file>mockfiles/qt6/ParticleSystemGizmo.qml</file>
<file>mockfiles/qt6/Line3D.qml</file>
<file>mockfiles/qt6/MaterialNodeView.qml</file>
<file>mockfiles/qt6/ModelNode2DImageView.qml</file>
@@ -44,6 +43,8 @@
<file>mockfiles/qt6/MoveGizmo.qml</file>
<file>mockfiles/qt6/NodeNodeView.qml</file>
<file>mockfiles/qt6/Overlay2D.qml</file>
<file>mockfiles/qt6/ParticleSystemGizmo.qml</file>
<file>mockfiles/qt6/ParticleEmitterGizmo.qml</file>
<file>mockfiles/qt6/PlanarDraggable.qml</file>
<file>mockfiles/qt6/PlanarMoveHandle.qml</file>
<file>mockfiles/qt6/PlanarScaleHandle.qml</file>

View File

@@ -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
@@ -77,6 +79,7 @@ Item {
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);
@@ -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)

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -46,6 +46,12 @@
#include <QtQuick/qquickitem.h>
#include <QtCore/qmath.h>
#ifdef QUICK3D_PARTICLES_MODULE
#include <QtQuick3DParticles/private/qquick3dparticlemodelshape_p.h>
#include <QtQuick3DParticles/private/qquick3dparticleemitter_p.h>
#include <QtQuick3DParticles/private/qquick3dparticletrailemitter_p.h>
#endif
#include <limits>
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<QQuick3DParticleEmitter *>(emitter);
if (!e || qobject_cast<QQuick3DParticleTrailEmitter *>(e) || !material)
return nullptr;
auto shape = qobject_cast<QQuick3DParticleModelShape *>(e->shape());
if (shape && shape->delegate()) {
if (auto model = qobject_cast<QQuick3DModel *>(
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)
{

View File

@@ -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);

View File

@@ -113,6 +113,7 @@
#include <QtQuick3DParticles/private/qquick3dparticle_p.h>
#include <QtQuick3DParticles/private/qquick3dparticleaffector_p.h>
#include <QtQuick3DParticles/private/qquick3dparticleemitter_p.h>
#include <QtQuick3DParticles/private/qquick3dparticletrailemitter_p.h>
#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<ServerNodeInstance> instanceList;
const QVariantList varObjs = objs.value<QVariantList>();
@@ -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<QQuick3DParticleSystem *>(obj)) {
QMetaObject::invokeMethod(m_editView3DData.rootItem, "releaseParticleSystemGizmo",
Q_ARG(QVariant, objectToVariant(obj)));
} else if (qobject_cast<QQuick3DParticleEmitter *>(obj)
&& !qobject_cast<QQuick3DParticleTrailEmitter *>(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<QQuick3DParticleEmitter *>(node)
&& !qobject_cast<QQuick3DParticleTrailEmitter *>(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<QObject *, QObjectList> cameras;
QHash<QObject *, QObjectList> lights;
QHash<QObject *, QObjectList> particleSystems;
QHash<QObject *, QObjectList> 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<ServerNodeInstance> &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<QQuick3DAbstractLight *>(object)
#ifdef QUICK3D_PARTICLES_MODULE
|| qobject_cast<QQuick3DParticleSystem *>(object)
|| qobject_cast<QQuick3DParticleEmitter *>(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());

View File

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

View File

@@ -128,6 +128,7 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState)
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)) {
@@ -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<Edit3DAction *> Edit3DView::leftActions() const

View File

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

View File

@@ -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();
}
}
}

View File

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