forked from qt-creator/qt-creator
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:
@@ -42,13 +42,22 @@ DirectionalDraggable {
|
||||
return targetNode.parent ? targetNode.parent.mapPositionFromScene(newScenePos) : newScenePos;
|
||||
}
|
||||
|
||||
onPressed: {
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.restartMultiSelection();
|
||||
}
|
||||
|
||||
onDragged: {
|
||||
targetNode.position = localPos(sceneRelativeDistance);
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.moveMultiSelection(false);
|
||||
positionMove();
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
targetNode.position = localPos(sceneRelativeDistance);
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.moveMultiSelection(true);
|
||||
positionCommit();
|
||||
}
|
||||
}
|
||||
|
@@ -49,7 +49,7 @@ Item {
|
||||
property int selectionMode: EditView3D.SelectionMode.Item
|
||||
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 lightIconGizmos: []
|
||||
@@ -62,8 +62,8 @@ Item {
|
||||
property real fps: 0
|
||||
|
||||
signal selectionChanged(var selectedNodes)
|
||||
signal commitObjectProperty(var object, var propName)
|
||||
signal changeObjectProperty(var object, var propName)
|
||||
signal commitObjectProperty(var objects, var propNames)
|
||||
signal changeObjectProperty(var objects, var propNames)
|
||||
signal notifyActiveSceneChange()
|
||||
|
||||
onUsePerspectiveChanged: _generalHelper.storeToolState(sceneId, "usePerspective", usePerspective)
|
||||
@@ -268,10 +268,14 @@ Item {
|
||||
selectionBoxes[i].targetNode = null;
|
||||
|
||||
selectedNodes = objects;
|
||||
if (objects.length === 0 || objects.length > 1)
|
||||
if (objects.length === 0) {
|
||||
selectedNode = null;
|
||||
else
|
||||
} else if (objects.length > 1) {
|
||||
selectedNode = multiSelectionNode;
|
||||
_generalHelper.setMultiSelectionTargets(multiSelectionNode, objects);
|
||||
} else {
|
||||
selectedNode = objects[0];
|
||||
}
|
||||
}
|
||||
|
||||
function handleObjectClicked(object, multi)
|
||||
@@ -513,6 +517,11 @@ Item {
|
||||
view3D: overlayView
|
||||
}
|
||||
|
||||
Node {
|
||||
id: multiSelectionNode
|
||||
objectName: "multiSelectionNode"
|
||||
}
|
||||
|
||||
MoveGizmo {
|
||||
id: moveGizmo
|
||||
scale: autoScale.getScale(Qt.vector3d(5, 5, 5))
|
||||
@@ -522,9 +531,20 @@ Item {
|
||||
visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Move
|
||||
view3D: overlayView
|
||||
dragHelper: gizmoDragHelper
|
||||
property var propertyNames: ["position"]
|
||||
|
||||
onPositionCommit: viewRoot.commitObjectProperty(viewRoot.selectedNode, "position")
|
||||
onPositionMove: viewRoot.changeObjectProperty(viewRoot.selectedNode, "position")
|
||||
onPositionCommit: {
|
||||
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 {
|
||||
@@ -535,9 +555,21 @@ Item {
|
||||
visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Scale
|
||||
view3D: overlayView
|
||||
dragHelper: gizmoDragHelper
|
||||
property var propertyNames: ["scale"]
|
||||
property var propertyNamesMulti: ["position", "scale"]
|
||||
|
||||
onScaleCommit: viewRoot.commitObjectProperty(viewRoot.selectedNode, "scale")
|
||||
onScaleChange: viewRoot.changeObjectProperty(viewRoot.selectedNode, "scale")
|
||||
onScaleCommit: {
|
||||
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 {
|
||||
@@ -549,19 +581,31 @@ Item {
|
||||
visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Rotate
|
||||
view3D: overlayView
|
||||
dragHelper: gizmoDragHelper
|
||||
property var propertyNames: ["eulerRotation"]
|
||||
property var propertyNamesMulti: ["position", "eulerRotation"]
|
||||
|
||||
onRotateCommit: viewRoot.commitObjectProperty(viewRoot.selectedNode, "eulerRotation")
|
||||
onRotateChange: viewRoot.changeObjectProperty(viewRoot.selectedNode, "eulerRotation")
|
||||
onRotateCommit: {
|
||||
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 {
|
||||
id: lightGizmo
|
||||
targetNode: viewRoot.selectedNode
|
||||
targetNode: viewRoot.selectedNode != multiSelectionNode ? viewRoot.selectedNode : null
|
||||
view3D: overlayView
|
||||
dragHelper: gizmoDragHelper
|
||||
|
||||
onPropertyValueCommit: viewRoot.commitObjectProperty(targetNode, propName)
|
||||
onPropertyValueChange: viewRoot.changeObjectProperty(targetNode, propName)
|
||||
onPropertyValueCommit: viewRoot.commitObjectProperty([targetNode], [propName])
|
||||
onPropertyValueChange: viewRoot.changeObjectProperty([targetNode], [propName])
|
||||
}
|
||||
|
||||
AutoScaleHelper {
|
||||
@@ -578,7 +622,7 @@ Item {
|
||||
|
||||
Line3D {
|
||||
id: pivotLine
|
||||
visible: viewRoot.selectedNode
|
||||
visible: viewRoot.selectedNode && viewRoot.selectedNode != multiSelectionNode
|
||||
name: "3D Edit View Pivot Line"
|
||||
color: "#ddd600"
|
||||
|
||||
|
@@ -43,13 +43,22 @@ PlanarDraggable {
|
||||
return targetNode.parent ? targetNode.parent.mapPositionFromScene(newScenePos) : newScenePos;
|
||||
}
|
||||
|
||||
onPressed: {
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.restartMultiSelection();
|
||||
}
|
||||
|
||||
onDragged: {
|
||||
targetNode.position = localPos(sceneRelativeDistance);
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.moveMultiSelection(false);
|
||||
positionMove();
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
targetNode.position = localPos(sceneRelativeDistance);
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.moveMultiSelection(true);
|
||||
positionCommit();
|
||||
}
|
||||
}
|
||||
|
@@ -41,18 +41,24 @@ PlanarDraggable {
|
||||
property vector3d _startScale
|
||||
|
||||
onPressed: {
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.restartMultiSelection();
|
||||
_startScale = targetNode.scale;
|
||||
}
|
||||
|
||||
onDragged: {
|
||||
targetNode.scale = mouseArea.getNewScale(_startScale, relativeDistance.times(scale.x),
|
||||
axisX, axisY);
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.scaleMultiSelection(false);
|
||||
scaleChange();
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
targetNode.scale = mouseArea.getNewScale(_startScale, relativeDistance.times(scale.x),
|
||||
axisX, axisY);
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.scaleMultiSelection(true);
|
||||
scaleCommit();
|
||||
}
|
||||
}
|
||||
|
@@ -208,6 +208,9 @@ Node {
|
||||
if (!rotateGizmo.targetNode)
|
||||
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
|
||||
// scenePosition, which is read-only property
|
||||
var scenePos = rotateGizmo.dragHelper.pivotScenePosition(rotateGizmo.targetNode);
|
||||
@@ -237,6 +240,9 @@ Node {
|
||||
rotateGizmo.targetNode, _startRotation, _pointerPosPressed,
|
||||
Qt.vector3d(screenPos.x, screenPos.y, 0));
|
||||
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.rotateMultiSelection(false);
|
||||
|
||||
rotateGizmo.rotateChange();
|
||||
}
|
||||
|
||||
@@ -249,6 +255,9 @@ Node {
|
||||
rotateGizmo.targetNode, _startRotation, _pointerPosPressed,
|
||||
Qt.vector3d(screenPos.x, screenPos.y, 0));
|
||||
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.rotateMultiSelection(true);
|
||||
|
||||
rotateGizmo.rotateCommit();
|
||||
dragging = false;
|
||||
}
|
||||
|
@@ -77,6 +77,9 @@ Model {
|
||||
if (!targetNode)
|
||||
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
|
||||
// scenePosition, which is read-only property
|
||||
var scenePos = mouseAreaMain.pivotScenePosition(targetNode);
|
||||
@@ -107,6 +110,10 @@ Model {
|
||||
return;
|
||||
|
||||
applyLocalRotation(screenPos);
|
||||
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.rotateMultiSelection(false);
|
||||
|
||||
currentMousePos = screenPos;
|
||||
rotateChange();
|
||||
}
|
||||
@@ -117,6 +124,10 @@ Model {
|
||||
return;
|
||||
|
||||
applyLocalRotation(screenPos);
|
||||
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.rotateMultiSelection(true);
|
||||
|
||||
rotateCommit();
|
||||
currentAngle = 0;
|
||||
currentMousePos = screenPos;
|
||||
|
@@ -212,6 +212,9 @@ Node {
|
||||
if (!scaleGizmo.targetNode)
|
||||
return;
|
||||
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.restartMultiSelection();
|
||||
|
||||
// Recreate vector so we don't follow the changes in targetNode.scale
|
||||
_startScale = Qt.vector3d(scaleGizmo.targetNode.scale.x,
|
||||
scaleGizmo.targetNode.scale.y,
|
||||
@@ -222,6 +225,8 @@ Node {
|
||||
if (!scaleGizmo.targetNode)
|
||||
return;
|
||||
scaleGizmo.targetNode.scale = localScale(screenPos);
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.scaleMultiSelection(false);
|
||||
scaleGizmo.scaleChange();
|
||||
}
|
||||
onReleased: {
|
||||
@@ -229,6 +234,8 @@ Node {
|
||||
return;
|
||||
|
||||
scaleGizmo.targetNode.scale = localScale(screenPos);
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.scaleMultiSelection(true);
|
||||
scaleGizmo.scaleCommit();
|
||||
}
|
||||
}
|
||||
|
@@ -50,18 +50,24 @@ DirectionalDraggable {
|
||||
}
|
||||
|
||||
onPressed: {
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.restartMultiSelection();
|
||||
_startScale = targetNode.scale;
|
||||
}
|
||||
|
||||
onDragged: {
|
||||
targetNode.scale = mouseArea.getNewScale(_startScale, Qt.vector2d(relativeDistance, 0),
|
||||
axis, Qt.vector3d(0, 0, 0));
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.scaleMultiSelection(false);
|
||||
scaleChange();
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
targetNode.scale = mouseArea.getNewScale(_startScale, Qt.vector2d(relativeDistance, 0),
|
||||
axis, Qt.vector3d(0, 0, 0));
|
||||
if (targetNode == multiSelectionNode)
|
||||
_generalHelper.scaleMultiSelection(true);
|
||||
scaleCommit();
|
||||
}
|
||||
}
|
||||
|
@@ -347,6 +347,171 @@ double GeneralHelper::brightnessScaler() const
|
||||
#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
|
||||
{
|
||||
#ifdef Q_OS_MACOS
|
||||
|
@@ -27,13 +27,14 @@
|
||||
|
||||
#ifdef QUICK3D_MODULE
|
||||
|
||||
#include <QtCore/qobject.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
#include <QtCore/qhash.h>
|
||||
#include <QtCore/qpointer.h>
|
||||
#include <QtCore/qvariant.h>
|
||||
#include <QtGui/qvector3d.h>
|
||||
#include <QtGui/qmatrix4x4.h>
|
||||
#include <QHash>
|
||||
#include <QMatrix4x4>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QQuaternion>
|
||||
#include <QTimer>
|
||||
#include <QVariant>
|
||||
#include <QVector3D>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QQuick3DCamera;
|
||||
@@ -91,6 +92,14 @@ public:
|
||||
|
||||
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;
|
||||
|
||||
void addRotationBlocks(const QSet<QQuick3DNode *> &nodes);
|
||||
@@ -116,6 +125,19 @@ private:
|
||||
QHash<QString, QVariantMap> m_toolStatesPending;
|
||||
QSet<QQuick3DNode *> m_gizmoTargets;
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -152,6 +152,34 @@ static bool isQuick3DMode()
|
||||
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,
|
||||
RenderViewData &viewData)
|
||||
{
|
||||
@@ -422,64 +450,108 @@ Qt5InformationNodeInstanceServer::propertyToPropertyValueTriples(
|
||||
return result;
|
||||
}
|
||||
|
||||
void Qt5InformationNodeInstanceServer::modifyVariantValue(
|
||||
const QVariant &node,
|
||||
const PropertyName &propertyName,
|
||||
ValuesModifiedCommand::TransactionOption option)
|
||||
void Qt5InformationNodeInstanceServer::modifyVariantValue(const QObjectList &objects,
|
||||
const QList<PropertyName> &propNames, 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'.
|
||||
// We prefer 'x'.
|
||||
if (propertyName != "position")
|
||||
targetPropertyName = propertyName;
|
||||
for (const auto &propName : propNames) {
|
||||
if (propName != "position")
|
||||
propNamePairs.append({propName, propName});
|
||||
else
|
||||
propNamePairs.append({propName, PropertyName{}});
|
||||
}
|
||||
|
||||
auto *obj = node.value<QObject *>();
|
||||
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)
|
||||
instance.setModifiedFlag(true);
|
||||
else if (option == ValuesModifiedCommand::TransactionOption::End)
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (obj) {
|
||||
ServerNodeInstance instance = instanceForObject(obj);
|
||||
|
||||
if (option == ValuesModifiedCommand::TransactionOption::Start)
|
||||
instance.setModifiedFlag(true);
|
||||
else if (option == ValuesModifiedCommand::TransactionOption::End)
|
||||
instance.setModifiedFlag(false);
|
||||
|
||||
// We do have to split position into position.x, position.y, position.z
|
||||
ValuesModifiedCommand command = createValuesModifiedCommand(propertyToPropertyValueTriples(
|
||||
instance,
|
||||
targetPropertyName,
|
||||
obj->property(propertyName)));
|
||||
|
||||
command.transactionOption = option;
|
||||
|
||||
nodeInstanceClient()->valuesModified(command);
|
||||
if (!valueVector.isEmpty()) {
|
||||
ValuesModifiedCommand command(valueVector);
|
||||
command.transactionOption = option;
|
||||
nodeInstanceClient()->valuesModified(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Qt5InformationNodeInstanceServer::handleObjectPropertyCommit(const QVariant &object,
|
||||
const QVariant &propName)
|
||||
void Qt5InformationNodeInstanceServer::handleObjectPropertyCommit(const QVariant &objects,
|
||||
const QVariant &propNames)
|
||||
{
|
||||
modifyVariantValue(object, propName.toByteArray(),
|
||||
modifyVariantValue(toObjectList(objects), toPropertyNameList(propNames),
|
||||
ValuesModifiedCommand::TransactionOption::End);
|
||||
m_changedNode = {};
|
||||
m_changedProperty = {};
|
||||
m_changedNodes.clear();
|
||||
m_changedProperties.clear();
|
||||
m_propertyChangeTimer.stop();
|
||||
}
|
||||
|
||||
void Qt5InformationNodeInstanceServer::handleObjectPropertyChange(const QVariant &object,
|
||||
const QVariant &propName)
|
||||
void Qt5InformationNodeInstanceServer::handleObjectPropertyChange(const QVariant &objects,
|
||||
const QVariant &propNames)
|
||||
{
|
||||
PropertyName propertyName(propName.toByteArray());
|
||||
if (m_changedProperty != propertyName || m_changedNode != object) {
|
||||
if (!m_changedNode.isNull())
|
||||
handleObjectPropertyCommit(m_changedNode, m_changedProperty);
|
||||
modifyVariantValue(object, propertyName,
|
||||
ValuesModifiedCommand::TransactionOption::Start);
|
||||
QObjectList objList = toObjectList(objects);
|
||||
QList<PropertyName> propList = toPropertyNameList(propNames);
|
||||
|
||||
bool nodeChanged = true;
|
||||
if (objList.size() == m_changedNodes.size()) {
|
||||
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()) {
|
||||
m_propertyChangeTimer.start();
|
||||
}
|
||||
m_changedNode = object;
|
||||
m_changedProperty = propertyName;
|
||||
m_changedNodes = objList;
|
||||
m_changedProperties = propList;
|
||||
}
|
||||
|
||||
void Qt5InformationNodeInstanceServer::handleActiveSceneChange()
|
||||
@@ -1125,7 +1197,7 @@ void Qt5InformationNodeInstanceServer::initializeAuxiliaryViews()
|
||||
|
||||
void Qt5InformationNodeInstanceServer::handleObjectPropertyChangeTimeout()
|
||||
{
|
||||
modifyVariantValue(m_changedNode, m_changedProperty,
|
||||
modifyVariantValue(m_changedNodes, m_changedProperties,
|
||||
ValuesModifiedCommand::TransactionOption::None);
|
||||
}
|
||||
|
||||
|
@@ -77,8 +77,8 @@ public:
|
||||
|
||||
private slots:
|
||||
void handleSelectionChanged(const QVariant &objs);
|
||||
void handleObjectPropertyCommit(const QVariant &object, const QVariant &propName);
|
||||
void handleObjectPropertyChange(const QVariant &object, const QVariant &propName);
|
||||
void handleObjectPropertyCommit(const QVariant &objects, const QVariant &propNames);
|
||||
void handleObjectPropertyChange(const QVariant &objects, const QVariant &propNames);
|
||||
void handleActiveSceneChange();
|
||||
void handleToolStateChanged(const QString &sceneId, const QString &tool,
|
||||
const QVariant &toolState);
|
||||
@@ -115,8 +115,8 @@ private:
|
||||
const ServerNodeInstance &instance,
|
||||
const PropertyName &propertyName,
|
||||
const QVariant &variant);
|
||||
void modifyVariantValue(const QVariant &node,
|
||||
const PropertyName &propertyName,
|
||||
void modifyVariantValue(const QObjectList &objects,
|
||||
const QList<PropertyName> &propNames,
|
||||
ValuesModifiedCommand::TransactionOption option);
|
||||
void updateView3DRect(QObject *view3D);
|
||||
void updateActiveSceneToEditView3D();
|
||||
@@ -159,8 +159,8 @@ private:
|
||||
QTimer m_render3DEditViewTimer;
|
||||
QTimer m_renderModelNodeImageViewTimer;
|
||||
QTimer m_inputEventTimer;
|
||||
QVariant m_changedNode;
|
||||
PropertyName m_changedProperty;
|
||||
QObjectList m_changedNodes;
|
||||
QList<PropertyName> m_changedProperties;
|
||||
ChangeSelectionCommand m_lastSelectionChangeCommand;
|
||||
QList<InputEventCommand> m_pendingInputEventCommands;
|
||||
QObject *m_3dHelper = nullptr;
|
||||
|
Reference in New Issue
Block a user