QmlPuppet: Support multiselection transformations in 3D edit view

Add support for moving, rotating, and scaling a multiselection of 3D
nodes in 3D edit view.

Fixes: QDS-4313
Change-Id: Icf83911ac6f87fde1cb79d6f5059ae01ed434d7f
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Miikka Heikkinen
2021-05-07 15:41:58 +03:00
parent 14067cca86
commit f913a65405
12 changed files with 429 additions and 69 deletions

View File

@@ -42,13 +42,22 @@ DirectionalDraggable {
return targetNode.parent ? targetNode.parent.mapPositionFromScene(newScenePos) : newScenePos; return targetNode.parent ? targetNode.parent.mapPositionFromScene(newScenePos) : newScenePos;
} }
onPressed: {
if (targetNode == multiSelectionNode)
_generalHelper.restartMultiSelection();
}
onDragged: { onDragged: {
targetNode.position = localPos(sceneRelativeDistance); targetNode.position = localPos(sceneRelativeDistance);
if (targetNode == multiSelectionNode)
_generalHelper.moveMultiSelection(false);
positionMove(); positionMove();
} }
onReleased: { onReleased: {
targetNode.position = localPos(sceneRelativeDistance); targetNode.position = localPos(sceneRelativeDistance);
if (targetNode == multiSelectionNode)
_generalHelper.moveMultiSelection(true);
positionCommit(); positionCommit();
} }
} }

View File

@@ -49,7 +49,7 @@ Item {
property int selectionMode: EditView3D.SelectionMode.Item property int selectionMode: EditView3D.SelectionMode.Item
property int transformMode: EditView3D.TransformMode.Move property int transformMode: EditView3D.TransformMode.Move
property Node selectedNode: null // This is non-null only in single selection case property Node selectedNode: null // This is multiSelectionNode in multi-selection case
property var selectedNodes: [] // All selected nodes property var selectedNodes: [] // All selected nodes
property var lightIconGizmos: [] property var lightIconGizmos: []
@@ -62,8 +62,8 @@ Item {
property real fps: 0 property real fps: 0
signal selectionChanged(var selectedNodes) signal selectionChanged(var selectedNodes)
signal commitObjectProperty(var object, var propName) signal commitObjectProperty(var objects, var propNames)
signal changeObjectProperty(var object, var propName) signal changeObjectProperty(var objects, var propNames)
signal notifyActiveSceneChange() signal notifyActiveSceneChange()
onUsePerspectiveChanged: _generalHelper.storeToolState(sceneId, "usePerspective", usePerspective) onUsePerspectiveChanged: _generalHelper.storeToolState(sceneId, "usePerspective", usePerspective)
@@ -268,11 +268,15 @@ Item {
selectionBoxes[i].targetNode = null; selectionBoxes[i].targetNode = null;
selectedNodes = objects; selectedNodes = objects;
if (objects.length === 0 || objects.length > 1) if (objects.length === 0) {
selectedNode = null; selectedNode = null;
else } else if (objects.length > 1) {
selectedNode = multiSelectionNode;
_generalHelper.setMultiSelectionTargets(multiSelectionNode, objects);
} else {
selectedNode = objects[0]; selectedNode = objects[0];
} }
}
function handleObjectClicked(object, multi) function handleObjectClicked(object, multi)
{ {
@@ -513,6 +517,11 @@ Item {
view3D: overlayView view3D: overlayView
} }
Node {
id: multiSelectionNode
objectName: "multiSelectionNode"
}
MoveGizmo { MoveGizmo {
id: moveGizmo id: moveGizmo
scale: autoScale.getScale(Qt.vector3d(5, 5, 5)) scale: autoScale.getScale(Qt.vector3d(5, 5, 5))
@@ -522,9 +531,20 @@ Item {
visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Move visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Move
view3D: overlayView view3D: overlayView
dragHelper: gizmoDragHelper dragHelper: gizmoDragHelper
property var propertyNames: ["position"]
onPositionCommit: viewRoot.commitObjectProperty(viewRoot.selectedNode, "position") onPositionCommit: {
onPositionMove: viewRoot.changeObjectProperty(viewRoot.selectedNode, "position") if (targetNode == multiSelectionNode)
viewRoot.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNames);
else
viewRoot.commitObjectProperty([viewRoot.selectedNode], propertyNames);
}
onPositionMove: {
if (targetNode == multiSelectionNode)
viewRoot.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNames);
else
viewRoot.changeObjectProperty([viewRoot.selectedNode], propertyNames);
}
} }
ScaleGizmo { ScaleGizmo {
@@ -535,9 +555,21 @@ Item {
visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Scale visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Scale
view3D: overlayView view3D: overlayView
dragHelper: gizmoDragHelper dragHelper: gizmoDragHelper
property var propertyNames: ["scale"]
property var propertyNamesMulti: ["position", "scale"]
onScaleCommit: viewRoot.commitObjectProperty(viewRoot.selectedNode, "scale") onScaleCommit: {
onScaleChange: viewRoot.changeObjectProperty(viewRoot.selectedNode, "scale") if (targetNode == multiSelectionNode)
viewRoot.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti);
else
viewRoot.commitObjectProperty([viewRoot.selectedNode], propertyNames);
}
onScaleChange: {
if (targetNode == multiSelectionNode)
viewRoot.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti);
else
viewRoot.changeObjectProperty([viewRoot.selectedNode], propertyNames);
}
} }
RotateGizmo { RotateGizmo {
@@ -549,19 +581,31 @@ Item {
visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Rotate visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Rotate
view3D: overlayView view3D: overlayView
dragHelper: gizmoDragHelper dragHelper: gizmoDragHelper
property var propertyNames: ["eulerRotation"]
property var propertyNamesMulti: ["position", "eulerRotation"]
onRotateCommit: viewRoot.commitObjectProperty(viewRoot.selectedNode, "eulerRotation") onRotateCommit: {
onRotateChange: viewRoot.changeObjectProperty(viewRoot.selectedNode, "eulerRotation") if (targetNode == multiSelectionNode)
viewRoot.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti);
else
viewRoot.commitObjectProperty([viewRoot.selectedNode], propertyNames);
}
onRotateChange: {
if (targetNode == multiSelectionNode)
viewRoot.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti);
else
viewRoot.changeObjectProperty([viewRoot.selectedNode], propertyNames);
}
} }
LightGizmo { LightGizmo {
id: lightGizmo id: lightGizmo
targetNode: viewRoot.selectedNode targetNode: viewRoot.selectedNode != multiSelectionNode ? viewRoot.selectedNode : null
view3D: overlayView view3D: overlayView
dragHelper: gizmoDragHelper dragHelper: gizmoDragHelper
onPropertyValueCommit: viewRoot.commitObjectProperty(targetNode, propName) onPropertyValueCommit: viewRoot.commitObjectProperty([targetNode], [propName])
onPropertyValueChange: viewRoot.changeObjectProperty(targetNode, propName) onPropertyValueChange: viewRoot.changeObjectProperty([targetNode], [propName])
} }
AutoScaleHelper { AutoScaleHelper {
@@ -578,7 +622,7 @@ Item {
Line3D { Line3D {
id: pivotLine id: pivotLine
visible: viewRoot.selectedNode visible: viewRoot.selectedNode && viewRoot.selectedNode != multiSelectionNode
name: "3D Edit View Pivot Line" name: "3D Edit View Pivot Line"
color: "#ddd600" color: "#ddd600"

View File

@@ -43,13 +43,22 @@ PlanarDraggable {
return targetNode.parent ? targetNode.parent.mapPositionFromScene(newScenePos) : newScenePos; return targetNode.parent ? targetNode.parent.mapPositionFromScene(newScenePos) : newScenePos;
} }
onPressed: {
if (targetNode == multiSelectionNode)
_generalHelper.restartMultiSelection();
}
onDragged: { onDragged: {
targetNode.position = localPos(sceneRelativeDistance); targetNode.position = localPos(sceneRelativeDistance);
if (targetNode == multiSelectionNode)
_generalHelper.moveMultiSelection(false);
positionMove(); positionMove();
} }
onReleased: { onReleased: {
targetNode.position = localPos(sceneRelativeDistance); targetNode.position = localPos(sceneRelativeDistance);
if (targetNode == multiSelectionNode)
_generalHelper.moveMultiSelection(true);
positionCommit(); positionCommit();
} }
} }

View File

@@ -41,18 +41,24 @@ PlanarDraggable {
property vector3d _startScale property vector3d _startScale
onPressed: { onPressed: {
if (targetNode == multiSelectionNode)
_generalHelper.restartMultiSelection();
_startScale = targetNode.scale; _startScale = targetNode.scale;
} }
onDragged: { onDragged: {
targetNode.scale = mouseArea.getNewScale(_startScale, relativeDistance.times(scale.x), targetNode.scale = mouseArea.getNewScale(_startScale, relativeDistance.times(scale.x),
axisX, axisY); axisX, axisY);
if (targetNode == multiSelectionNode)
_generalHelper.scaleMultiSelection(false);
scaleChange(); scaleChange();
} }
onReleased: { onReleased: {
targetNode.scale = mouseArea.getNewScale(_startScale, relativeDistance.times(scale.x), targetNode.scale = mouseArea.getNewScale(_startScale, relativeDistance.times(scale.x),
axisX, axisY); axisX, axisY);
if (targetNode == multiSelectionNode)
_generalHelper.scaleMultiSelection(true);
scaleCommit(); scaleCommit();
} }
} }

View File

@@ -208,6 +208,9 @@ Node {
if (!rotateGizmo.targetNode) if (!rotateGizmo.targetNode)
return; return;
if (targetNode == multiSelectionNode)
_generalHelper.restartMultiSelection();
// Need to recreate vector as we need to adjust it and we can't do that on reference of // Need to recreate vector as we need to adjust it and we can't do that on reference of
// scenePosition, which is read-only property // scenePosition, which is read-only property
var scenePos = rotateGizmo.dragHelper.pivotScenePosition(rotateGizmo.targetNode); var scenePos = rotateGizmo.dragHelper.pivotScenePosition(rotateGizmo.targetNode);
@@ -237,6 +240,9 @@ Node {
rotateGizmo.targetNode, _startRotation, _pointerPosPressed, rotateGizmo.targetNode, _startRotation, _pointerPosPressed,
Qt.vector3d(screenPos.x, screenPos.y, 0)); Qt.vector3d(screenPos.x, screenPos.y, 0));
if (targetNode == multiSelectionNode)
_generalHelper.rotateMultiSelection(false);
rotateGizmo.rotateChange(); rotateGizmo.rotateChange();
} }
@@ -249,6 +255,9 @@ Node {
rotateGizmo.targetNode, _startRotation, _pointerPosPressed, rotateGizmo.targetNode, _startRotation, _pointerPosPressed,
Qt.vector3d(screenPos.x, screenPos.y, 0)); Qt.vector3d(screenPos.x, screenPos.y, 0));
if (targetNode == multiSelectionNode)
_generalHelper.rotateMultiSelection(true);
rotateGizmo.rotateCommit(); rotateGizmo.rotateCommit();
dragging = false; dragging = false;
} }

View File

@@ -77,6 +77,9 @@ Model {
if (!targetNode) if (!targetNode)
return; return;
if (targetNode == multiSelectionNode)
_generalHelper.restartMultiSelection();
// Need to recreate vector as we need to adjust it and we can't do that on reference of // Need to recreate vector as we need to adjust it and we can't do that on reference of
// scenePosition, which is read-only property // scenePosition, which is read-only property
var scenePos = mouseAreaMain.pivotScenePosition(targetNode); var scenePos = mouseAreaMain.pivotScenePosition(targetNode);
@@ -107,6 +110,10 @@ Model {
return; return;
applyLocalRotation(screenPos); applyLocalRotation(screenPos);
if (targetNode == multiSelectionNode)
_generalHelper.rotateMultiSelection(false);
currentMousePos = screenPos; currentMousePos = screenPos;
rotateChange(); rotateChange();
} }
@@ -117,6 +124,10 @@ Model {
return; return;
applyLocalRotation(screenPos); applyLocalRotation(screenPos);
if (targetNode == multiSelectionNode)
_generalHelper.rotateMultiSelection(true);
rotateCommit(); rotateCommit();
currentAngle = 0; currentAngle = 0;
currentMousePos = screenPos; currentMousePos = screenPos;

View File

@@ -212,6 +212,9 @@ Node {
if (!scaleGizmo.targetNode) if (!scaleGizmo.targetNode)
return; return;
if (targetNode == multiSelectionNode)
_generalHelper.restartMultiSelection();
// Recreate vector so we don't follow the changes in targetNode.scale // Recreate vector so we don't follow the changes in targetNode.scale
_startScale = Qt.vector3d(scaleGizmo.targetNode.scale.x, _startScale = Qt.vector3d(scaleGizmo.targetNode.scale.x,
scaleGizmo.targetNode.scale.y, scaleGizmo.targetNode.scale.y,
@@ -222,6 +225,8 @@ Node {
if (!scaleGizmo.targetNode) if (!scaleGizmo.targetNode)
return; return;
scaleGizmo.targetNode.scale = localScale(screenPos); scaleGizmo.targetNode.scale = localScale(screenPos);
if (targetNode == multiSelectionNode)
_generalHelper.scaleMultiSelection(false);
scaleGizmo.scaleChange(); scaleGizmo.scaleChange();
} }
onReleased: { onReleased: {
@@ -229,6 +234,8 @@ Node {
return; return;
scaleGizmo.targetNode.scale = localScale(screenPos); scaleGizmo.targetNode.scale = localScale(screenPos);
if (targetNode == multiSelectionNode)
_generalHelper.scaleMultiSelection(true);
scaleGizmo.scaleCommit(); scaleGizmo.scaleCommit();
} }
} }

View File

@@ -50,18 +50,24 @@ DirectionalDraggable {
} }
onPressed: { onPressed: {
if (targetNode == multiSelectionNode)
_generalHelper.restartMultiSelection();
_startScale = targetNode.scale; _startScale = targetNode.scale;
} }
onDragged: { onDragged: {
targetNode.scale = mouseArea.getNewScale(_startScale, Qt.vector2d(relativeDistance, 0), targetNode.scale = mouseArea.getNewScale(_startScale, Qt.vector2d(relativeDistance, 0),
axis, Qt.vector3d(0, 0, 0)); axis, Qt.vector3d(0, 0, 0));
if (targetNode == multiSelectionNode)
_generalHelper.scaleMultiSelection(false);
scaleChange(); scaleChange();
} }
onReleased: { onReleased: {
targetNode.scale = mouseArea.getNewScale(_startScale, Qt.vector2d(relativeDistance, 0), targetNode.scale = mouseArea.getNewScale(_startScale, Qt.vector2d(relativeDistance, 0),
axis, Qt.vector3d(0, 0, 0)); axis, Qt.vector3d(0, 0, 0));
if (targetNode == multiSelectionNode)
_generalHelper.scaleMultiSelection(true);
scaleCommit(); scaleCommit();
} }
} }

View File

@@ -347,6 +347,171 @@ double GeneralHelper::brightnessScaler() const
#endif #endif
} }
void GeneralHelper::setMultiSelectionTargets(QQuick3DNode *multiSelectRootNode,
const QVariantList &selectedList)
{
// Filter selection to contain only topmost parent nodes in the selection
m_multiSelDataMap.clear();
m_multiSelNodes.clear();
for (auto &connection : qAsConst(m_multiSelectConnections))
disconnect(connection);
m_multiSelectConnections.clear();
m_multiSelectRootNode = multiSelectRootNode;
QSet<QQuick3DNode *> selNodes;
for (const auto &var : selectedList) {
QQuick3DNode *node = nullptr;
node = var.value<QQuick3DNode *>();
if (node)
selNodes.insert(node);
}
for (const auto selNode : qAsConst(selNodes)) {
bool found = false;
QQuick3DNode *parent = selNode->parentNode();
while (parent) {
if (selNodes.contains(parent)) {
found = true;
break;
}
parent = parent->parentNode();
}
if (!found) {
m_multiSelDataMap.insert(selNode, {});
m_multiSelNodes.append(QVariant::fromValue(selNode));
m_multiSelectConnections.append(connect(selNode, &QObject::destroyed, [this]() {
// If any multiselected node is destroyed, assume the entire selection is invalid.
// The new selection should be notified by creator immediately after anyway.
m_multiSelDataMap.clear();
m_multiSelNodes.clear();
for (auto &connection : qAsConst(m_multiSelectConnections))
disconnect(connection);
m_multiSelectConnections.clear();
}));
m_multiSelectConnections.append(connect(selNode, &QQuick3DNode::sceneTransformChanged,
[this]() {
// Reposition the multiselection root node if scene transform of any multiselected
// node changes outside of drag (i.e. changes originating from creator side)
if (!m_blockMultiSelectionNodePositioning) {
QVector3D newPos;
for (auto it = m_multiSelDataMap.begin(); it != m_multiSelDataMap.end(); ++it)
newPos += it.key()->scenePosition();
newPos /= m_multiSelDataMap.size();
m_multiSelectRootNode->setPosition(newPos);
}
}));
}
}
restartMultiSelection();
m_blockMultiSelectionNodePositioning = false;
}
void GeneralHelper::restartMultiSelection()
{
for (auto it = m_multiSelDataMap.begin(); it != m_multiSelDataMap.end(); ++it) {
it.value() = {it.key()->scenePosition(),
it.key()->scale(),
it.key()->rotation()};
}
m_multiSelNodeData = {};
if (!m_multiSelDataMap.isEmpty()) {
for (const auto &data : qAsConst(m_multiSelDataMap))
m_multiSelNodeData.startScenePos += data.startScenePos;
m_multiSelNodeData.startScenePos /= m_multiSelDataMap.size();
}
m_multiSelectRootNode->setPosition(m_multiSelNodeData.startScenePos);
m_multiSelectRootNode->setRotation({});
m_multiSelectRootNode->setScale({1.f, 1.f, 1.f});
m_blockMultiSelectionNodePositioning = true;
}
QVariantList GeneralHelper::multiSelectionTargets() const
{
return m_multiSelNodes;
}
void GeneralHelper::moveMultiSelection(bool commit)
{
// Move the multiselected nodes in global space by offset from multiselection start to scenePos
QVector3D globalOffset = m_multiSelectRootNode->scenePosition() - m_multiSelNodeData.startScenePos;
for (auto it = m_multiSelDataMap.constBegin(); it != m_multiSelDataMap.constEnd(); ++it) {
QVector3D newGlobalPos = it.value().startScenePos + globalOffset;
QMatrix4x4 m;
if (it.key()->parentNode())
m = it.key()->parentNode()->sceneTransform();
it.key()->setPosition(m.inverted() * newGlobalPos);
}
m_blockMultiSelectionNodePositioning = !commit;
}
void GeneralHelper::scaleMultiSelection(bool commit)
{
// Offset the multiselected nodes in global space according to scale factor and scale them by
// the same factor.
// TODO: The math below breaks down with negative scaling, so don't allow it for now
const QVector3D sceneScale = m_multiSelectRootNode->sceneScale();
QVector3D fixedSceneScale = sceneScale;
for (int i = 0; i < 3; ++i) {
if (sceneScale[i] < 0.f)
fixedSceneScale[i] = -sceneScale[i];
}
const QVector3D unitVector {1.f, 1.f, 1.f};
const QVector3D diffScale = (fixedSceneScale - unitVector);
for (auto it = m_multiSelDataMap.constBegin(); it != m_multiSelDataMap.constEnd(); ++it) {
const QVector3D newGlobalPos = m_multiSelNodeData.startScenePos
+ (it.value().startScenePos - m_multiSelNodeData.startScenePos) * sceneScale;
QMatrix4x4 parentMat;
if (it.key()->parentNode())
parentMat = it.key()->parentNode()->sceneTransform().inverted();
it.key()->setPosition(parentMat * newGlobalPos);
QMatrix4x4 mat;
mat.rotate(it.key()->sceneRotation());
auto scaleDim = [&](int dim) -> QVector3D {
QVector3D dimScale;
float diffScaleDim = diffScale[dim];
dimScale[dim] = diffScaleDim;
dimScale = (mat.inverted() * dimScale).normalized() * diffScaleDim;
for (int i = 0; i < 3; ++i)
dimScale[i] = qAbs(dimScale[i]);
if (fixedSceneScale[dim] < 1.0f)
dimScale = -dimScale;
return dimScale;
};
QVector3D finalScale = scaleDim(0) + scaleDim(1) + scaleDim(2) + unitVector;
it.key()->setScale(finalScale * it.value().startScale);
}
m_blockMultiSelectionNodePositioning = !commit;
}
void GeneralHelper::rotateMultiSelection(bool commit)
{
// Rotate entire selection around the multiselection node
const QQuaternion sceneRotation = m_multiSelectRootNode->sceneRotation();
QVector3D rotAxis;
float rotAngle = 0;
sceneRotation.getAxisAndAngle(&rotAxis, &rotAngle);
for (auto it = m_multiSelDataMap.constBegin(); it != m_multiSelDataMap.constEnd(); ++it) {
QVector3D globalOffset = it.value().startScenePos - m_multiSelNodeData.startScenePos;
QVector3D newGlobalPos = m_multiSelNodeData.startScenePos + sceneRotation * globalOffset;
QMatrix4x4 parentMat;
if (it.key()->parentNode())
parentMat = it.key()->parentNode()->sceneTransform().inverted();
it.key()->setPosition(parentMat * newGlobalPos);
it.key()->setRotation(it.value().startRot);
it.key()->rotate(rotAngle, rotAxis, QQuick3DNode::SceneSpace);
}
m_blockMultiSelectionNodePositioning = !commit;
}
bool GeneralHelper::isMacOS() const bool GeneralHelper::isMacOS() const
{ {
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS

View File

@@ -27,13 +27,14 @@
#ifdef QUICK3D_MODULE #ifdef QUICK3D_MODULE
#include <QtCore/qobject.h> #include <QHash>
#include <QtCore/qtimer.h> #include <QMatrix4x4>
#include <QtCore/qhash.h> #include <QObject>
#include <QtCore/qpointer.h> #include <QPointer>
#include <QtCore/qvariant.h> #include <QQuaternion>
#include <QtGui/qvector3d.h> #include <QTimer>
#include <QtGui/qmatrix4x4.h> #include <QVariant>
#include <QVector3D>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QQuick3DCamera; class QQuick3DCamera;
@@ -91,6 +92,14 @@ public:
Q_INVOKABLE double brightnessScaler() const; Q_INVOKABLE double brightnessScaler() const;
Q_INVOKABLE void setMultiSelectionTargets(QQuick3DNode *multiSelectRootNode,
const QVariantList &selectedList);
Q_INVOKABLE void restartMultiSelection();
Q_INVOKABLE QVariantList multiSelectionTargets() const;
Q_INVOKABLE void moveMultiSelection(bool commit);
Q_INVOKABLE void scaleMultiSelection(bool commit);
Q_INVOKABLE void rotateMultiSelection(bool commit);
bool isMacOS() const; bool isMacOS() const;
void addRotationBlocks(const QSet<QQuick3DNode *> &nodes); void addRotationBlocks(const QSet<QQuick3DNode *> &nodes);
@@ -116,6 +125,19 @@ private:
QHash<QString, QVariantMap> m_toolStatesPending; QHash<QString, QVariantMap> m_toolStatesPending;
QSet<QQuick3DNode *> m_gizmoTargets; QSet<QQuick3DNode *> m_gizmoTargets;
QSet<QQuick3DNode *> m_rotationBlockedNodes; QSet<QQuick3DNode *> m_rotationBlockedNodes;
struct MultiSelData {
QVector3D startScenePos;
QVector3D startScale;
QQuaternion startRot;
};
QHash<QQuick3DNode *, MultiSelData> m_multiSelDataMap;
QVariantList m_multiSelNodes;
MultiSelData m_multiSelNodeData;
QQuick3DNode *m_multiSelectRootNode = nullptr;
QList<QMetaObject::Connection> m_multiSelectConnections;
bool m_blockMultiSelectionNodePositioning = false;
}; };
} }

View File

@@ -152,6 +152,34 @@ static bool isQuick3DMode()
return mode3D; return mode3D;
} }
static QObjectList toObjectList(const QVariant &variantList)
{
QObjectList objList;
if (!variantList.isNull()) {
const auto varList = variantList.value<QVariantList>();
for (const auto &var : varList) {
QObject *obj = var.value<QObject *>();
if (obj)
objList.append(obj);
}
}
return objList;
}
static QList<PropertyName> toPropertyNameList(const QVariant &variantList)
{
QList<PropertyName> propList;
if (!variantList.isNull()) {
const auto varList = variantList.value<QVariantList>();
for (const auto &var : varList) {
PropertyName prop = var.toByteArray();
if (!prop.isEmpty())
propList.append(prop);
}
}
return propList;
}
void Qt5InformationNodeInstanceServer::createAuxiliaryQuickView(const QUrl &url, void Qt5InformationNodeInstanceServer::createAuxiliaryQuickView(const QUrl &url,
RenderViewData &viewData) RenderViewData &viewData)
{ {
@@ -422,64 +450,108 @@ Qt5InformationNodeInstanceServer::propertyToPropertyValueTriples(
return result; return result;
} }
void Qt5InformationNodeInstanceServer::modifyVariantValue( void Qt5InformationNodeInstanceServer::modifyVariantValue(const QObjectList &objects,
const QVariant &node, const QList<PropertyName> &propNames, ValuesModifiedCommand::TransactionOption option)
const PropertyName &propertyName,
ValuesModifiedCommand::TransactionOption option)
{ {
PropertyName targetPropertyName; struct PropNamePair {
PropertyName origPropName;
PropertyName targetPropName;
};
QList<PropNamePair> propNamePairs;
// Position is a special case, because the position can be 'position.x 'or simply 'x'. // Position is a special case, because the position can be 'position.x 'or simply 'x'.
// We prefer 'x'. // We prefer 'x'.
if (propertyName != "position") for (const auto &propName : propNames) {
targetPropertyName = propertyName; if (propName != "position")
propNamePairs.append({propName, propName});
auto *obj = node.value<QObject *>(); else
propNamePairs.append({propName, PropertyName{}});
if (obj) { }
ServerNodeInstance instance = instanceForObject(obj);
if (!objects.isEmpty()) {
QVector<PropertyValueContainer> valueVector;
for (const auto listObj : objects) {
ServerNodeInstance instance = instanceForObject(listObj);
if (instance.isValid()) {
const qint32 instId = instance.instanceId();
if (option == ValuesModifiedCommand::TransactionOption::Start) if (option == ValuesModifiedCommand::TransactionOption::Start)
instance.setModifiedFlag(true); instance.setModifiedFlag(true);
else if (option == ValuesModifiedCommand::TransactionOption::End) else if (option == ValuesModifiedCommand::TransactionOption::End)
instance.setModifiedFlag(false); instance.setModifiedFlag(false);
for (const auto &propNamePair : propNamePairs) {
// We do have to split vector3d props into foobar.x, foobar.y, foobar.z
const QVector<InstancePropertyValueTriple> triple
= propertyToPropertyValueTriples(instance, propNamePair.targetPropName,
listObj->property(propNamePair.origPropName));
for (const auto &property : triple) {
const PropertyName propName = property.propertyName;
const QVariant propValue = property.propertyValue;
valueVector.append(PropertyValueContainer(instId, propName,
propValue, PropertyName()));
}
}
}
}
// We do have to split position into position.x, position.y, position.z if (!valueVector.isEmpty()) {
ValuesModifiedCommand command = createValuesModifiedCommand(propertyToPropertyValueTriples( ValuesModifiedCommand command(valueVector);
instance,
targetPropertyName,
obj->property(propertyName)));
command.transactionOption = option; command.transactionOption = option;
nodeInstanceClient()->valuesModified(command); nodeInstanceClient()->valuesModified(command);
} }
}
} }
void Qt5InformationNodeInstanceServer::handleObjectPropertyCommit(const QVariant &object, void Qt5InformationNodeInstanceServer::handleObjectPropertyCommit(const QVariant &objects,
const QVariant &propName) const QVariant &propNames)
{ {
modifyVariantValue(object, propName.toByteArray(), modifyVariantValue(toObjectList(objects), toPropertyNameList(propNames),
ValuesModifiedCommand::TransactionOption::End); ValuesModifiedCommand::TransactionOption::End);
m_changedNode = {}; m_changedNodes.clear();
m_changedProperty = {}; m_changedProperties.clear();
m_propertyChangeTimer.stop(); m_propertyChangeTimer.stop();
} }
void Qt5InformationNodeInstanceServer::handleObjectPropertyChange(const QVariant &object, void Qt5InformationNodeInstanceServer::handleObjectPropertyChange(const QVariant &objects,
const QVariant &propName) const QVariant &propNames)
{ {
PropertyName propertyName(propName.toByteArray()); QObjectList objList = toObjectList(objects);
if (m_changedProperty != propertyName || m_changedNode != object) { QList<PropertyName> propList = toPropertyNameList(propNames);
if (!m_changedNode.isNull())
handleObjectPropertyCommit(m_changedNode, m_changedProperty); bool nodeChanged = true;
modifyVariantValue(object, propertyName, if (objList.size() == m_changedNodes.size()) {
ValuesModifiedCommand::TransactionOption::Start); nodeChanged = false;
for (int i = 0; i < objList.size(); ++i) {
if (objList[i] != m_changedNodes[i]) {
nodeChanged = true;
break;
}
}
}
if (!nodeChanged && propList.size() == m_changedProperties.size()) {
for (int i = 0; i < propList.size(); ++i) {
if (propList[i] != m_changedProperties[i]) {
nodeChanged = true;
break;
}
}
}
if (nodeChanged) {
if (!m_changedNodes.isEmpty()) {
// Nodes/properties changed, commit currently pending changes
modifyVariantValue(m_changedNodes, m_changedProperties,
ValuesModifiedCommand::TransactionOption::End);
m_changedNodes.clear();
m_changedProperties.clear();
m_propertyChangeTimer.stop();
}
modifyVariantValue(objList, propList, ValuesModifiedCommand::TransactionOption::Start);
} else if (!m_propertyChangeTimer.isActive()) { } else if (!m_propertyChangeTimer.isActive()) {
m_propertyChangeTimer.start(); m_propertyChangeTimer.start();
} }
m_changedNode = object; m_changedNodes = objList;
m_changedProperty = propertyName; m_changedProperties = propList;
} }
void Qt5InformationNodeInstanceServer::handleActiveSceneChange() void Qt5InformationNodeInstanceServer::handleActiveSceneChange()
@@ -1125,7 +1197,7 @@ void Qt5InformationNodeInstanceServer::initializeAuxiliaryViews()
void Qt5InformationNodeInstanceServer::handleObjectPropertyChangeTimeout() void Qt5InformationNodeInstanceServer::handleObjectPropertyChangeTimeout()
{ {
modifyVariantValue(m_changedNode, m_changedProperty, modifyVariantValue(m_changedNodes, m_changedProperties,
ValuesModifiedCommand::TransactionOption::None); ValuesModifiedCommand::TransactionOption::None);
} }

View File

@@ -77,8 +77,8 @@ public:
private slots: private slots:
void handleSelectionChanged(const QVariant &objs); void handleSelectionChanged(const QVariant &objs);
void handleObjectPropertyCommit(const QVariant &object, const QVariant &propName); void handleObjectPropertyCommit(const QVariant &objects, const QVariant &propNames);
void handleObjectPropertyChange(const QVariant &object, const QVariant &propName); void handleObjectPropertyChange(const QVariant &objects, const QVariant &propNames);
void handleActiveSceneChange(); void handleActiveSceneChange();
void handleToolStateChanged(const QString &sceneId, const QString &tool, void handleToolStateChanged(const QString &sceneId, const QString &tool,
const QVariant &toolState); const QVariant &toolState);
@@ -115,8 +115,8 @@ private:
const ServerNodeInstance &instance, const ServerNodeInstance &instance,
const PropertyName &propertyName, const PropertyName &propertyName,
const QVariant &variant); const QVariant &variant);
void modifyVariantValue(const QVariant &node, void modifyVariantValue(const QObjectList &objects,
const PropertyName &propertyName, const QList<PropertyName> &propNames,
ValuesModifiedCommand::TransactionOption option); ValuesModifiedCommand::TransactionOption option);
void updateView3DRect(QObject *view3D); void updateView3DRect(QObject *view3D);
void updateActiveSceneToEditView3D(); void updateActiveSceneToEditView3D();
@@ -159,8 +159,8 @@ private:
QTimer m_render3DEditViewTimer; QTimer m_render3DEditViewTimer;
QTimer m_renderModelNodeImageViewTimer; QTimer m_renderModelNodeImageViewTimer;
QTimer m_inputEventTimer; QTimer m_inputEventTimer;
QVariant m_changedNode; QObjectList m_changedNodes;
PropertyName m_changedProperty; QList<PropertyName> m_changedProperties;
ChangeSelectionCommand m_lastSelectionChangeCommand; ChangeSelectionCommand m_lastSelectionChangeCommand;
QList<InputEventCommand> m_pendingInputEventCommands; QList<InputEventCommand> m_pendingInputEventCommands;
QObject *m_3dHelper = nullptr; QObject *m_3dHelper = nullptr;