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;
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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"
|
||||||
|
|
||||||
|
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
Reference in New Issue
Block a user