From 98936b97d53135b20b9f2d10b614dbdd07a757bf Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Fri, 3 Jul 2020 15:47:15 +0200 Subject: [PATCH 01/51] [ui] Viewer3D: add a Transform Gizmo Component - Visual entity is done - Need to make it pickable and to apply changes --- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 223 ++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 meshroom/ui/qml/Viewer3D/TransformGizmo.qml diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml new file mode 100644 index 0000000000..7a1f5da96e --- /dev/null +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -0,0 +1,223 @@ +import Qt3D.Core 2.0 +import Qt3D.Render 2.9 +import Qt3D.Input 2.0 +import Qt3D.Extras 2.10 +import QtQuick 2.9 +import Qt3D.Logic 2.0 + +Entity { + id: root + property real gizmoScale: 0.15 + property Camera camera + + components: [gizmoTransform] + + enum Axis { + X, + Y, + Z + } + + Transform { + id: gizmoTransform + scale: { + return root.gizmoScale * (camera.position.minus(gizmoTransform.translation)).length() + } + } + + Entity { + id: centerSphereEntity + components: [centerSphereMesh, centerSphereMaterial] + + SphereMesh { + id: centerSphereMesh + radius: 0.04 + rings: 8 + slices: 8 + } + PhongMaterial { + id: centerSphereMaterial + property color base: "white" + ambient: base + shininess: 0.2 + } + } + + // AXIS GIZMO INSTANTIATOR => X, Y and Z + NodeInstantiator { + model: 3 + + Entity { + id: axisContainer + property int axis : { + switch(index) { + case 0: return TransformGizmo.Axis.X + case 1: return TransformGizmo.Axis.Y + case 2: return TransformGizmo.Axis.Z + } + } + property color baseColor: { + switch(axis) { + case TransformGizmo.Axis.X: return "#e63b55" + case TransformGizmo.Axis.Y: return "#83c414" + case TransformGizmo.Axis.Z: return "#3387e2" + } + } + property real lineRadius: 0.015 + + PhongMaterial { + id: material + ambient: baseColor + shininess: 0.2 + } + + // SCALE ENTITY + Entity { + id: scaleEntity + components: [material] + + Entity { + id: axisCylinder + components: [cylinderMesh, cylinderTransform, material] + + CylinderMesh { + id: cylinderMesh + length: 0.5 + radius: axisContainer.lineRadius + rings: 2 + slices: 16 + } + Transform { + id: cylinderTransform + matrix: { + const offset = cylinderMesh.length/2 + centerSphereMesh.radius + const m = Qt.matrix4x4() + switch(axis) { + case TransformGizmo.Axis.X: { + m.translate(Qt.vector3d(offset, 0, 0)) + m.rotate(90, Qt.vector3d(0,0,1)) + break + } + case TransformGizmo.Axis.Y: { + m.translate(Qt.vector3d(0, offset, 0)) + break + } + case TransformGizmo.Axis.Z: { + m.translate(Qt.vector3d(0, 0, offset)) + m.rotate(90, Qt.vector3d(1,0,0)) + break + } + } + return m + } + } + } + + Entity { + id: axisScaleBox + components: [cubeScaleMesh, cubeScaleTransform, material] + + CuboidMesh { + id: cubeScaleMesh + property real edge: 0.07 + xExtent: edge + yExtent: edge + zExtent: edge + } + Transform { + id: cubeScaleTransform + matrix: { + const offset = cylinderMesh.length + centerSphereMesh.radius + const m = Qt.matrix4x4() + switch(axis) { + case TransformGizmo.Axis.X: { + m.translate(Qt.vector3d(offset, 0, 0)) + m.rotate(90, Qt.vector3d(0,0,1)) + break + } + case TransformGizmo.Axis.Y: { + m.translate(Qt.vector3d(0, offset, 0)) + break + } + case TransformGizmo.Axis.Z: { + m.translate(Qt.vector3d(0, 0, offset)) + m.rotate(90, Qt.vector3d(1,0,0)) + break + } + } + return m + } + } + } + } + + // POSITION ENTITY + Entity { + id: positionEntity + components: [coneMesh, coneTransform, material] + + ConeMesh { + id: coneMesh + bottomRadius : 0.04 + topRadius : 0.001 + hasBottomEndcap : true + hasTopEndcap : true + length : 0.15 + rings : 2 + slices : 8 + } + Transform { + id: coneTransform + matrix: { + const offset = cylinderMesh.length + centerSphereMesh.radius + 0.4 + const m = Qt.matrix4x4() + switch(axis) { + case TransformGizmo.Axis.X: { + m.translate(Qt.vector3d(offset, 0, 0)) + m.rotate(-90, Qt.vector3d(0,0,1)) + break + } + case TransformGizmo.Axis.Y: { + m.translate(Qt.vector3d(0, offset, 0)) + break + } + case TransformGizmo.Axis.Z: { + m.translate(Qt.vector3d(0, 0, offset)) + m.rotate(90, Qt.vector3d(1,0,0)) + break + } + } + return m + } + } + } + + // ROTATION ENTITY + Entity { + id: rotationEntity + components: [torusMesh, torusTransform, material] + + TorusMesh { + id: torusMesh + radius: cylinderMesh.length + 0.25 + minorRadius: axisContainer.lineRadius + slices: 8 + rings: 32 + } + Transform { + id: torusTransform + matrix: { + const scaleDiff = 2*torusMesh.minorRadius + 0.01 // Just to make sure there is no face overlapping + const m = Qt.matrix4x4() + switch(axis) { + case TransformGizmo.Axis.X: m.rotate(90, Qt.vector3d(0,1,0)); break + case TransformGizmo.Axis.Y: m.rotate(90, Qt.vector3d(1,0,0)); m.scale(Qt.vector3d(1-scaleDiff, 1-scaleDiff, 1-scaleDiff)); break + case TransformGizmo.Axis.Z: m.scale(Qt.vector3d(1-2*scaleDiff, 1-2*scaleDiff, 1-2*scaleDiff)); break + } + return m + } + } + } + } + } +} From 30004fe69083e9f3a2536a22671a892a08e246d9 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 6 Jul 2020 17:04:20 +0200 Subject: [PATCH 02/51] [ui] Viewer3D: TransformGizmo - add needed mathematics functions - Add Quaternions functions - Add Matrices computation functions --- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 108 ++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index 7a1f5da96e..6b8800e83d 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -18,6 +18,114 @@ Entity { Z } + /***** QUATERNIONS *****/ + + function multiplyQuaternion(q1, q2) { + return Qt.quaternion( + q1.scalar * q2.scalar - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z, + q1.scalar * q2.x + q1.x * q2.scalar + q1.y * q2.z - q1.z * q2.y, + q1.scalar * q2.y + q1.y * q2.scalar + q1.z * q2.x - q1.x * q2.z, + q1.scalar * q2.z + q1.z * q2.scalar + q1.x * q2.y - q1.y * q2.x + ) + } + + function dotQuaternion(q) { + return (((q.x * q.x) + (q.y * q.y)) + (q.z * q.z)) + (q.scalar * q.scalar) + } + + function normalizeQuaternion(q) { + const dot = dotQuaternion(q) + const inv = 1.0 / (Math.sqrt(dot)) + return Qt.quaternion(q.scalar * inv, q.x * inv, q.y * inv, q.z * inv) + } + + function quaternionFromAxisAngle(vec3, degree) { + const rad = degree * Math.PI/180 + const factor = Math.sin(rad/2) // Used for the quaternion computation + + // Compute the quaternion + const x = vec3.x * factor + const y = vec3.y * factor + const z = vec3.z * factor + const w = Math.cos(rad/2) + + return normalizeQuaternion(Qt.quaternion(w, x, y, z)) + } + + function quaternionToRotationMatrix(q) { + const w = q.scalar + const x = q.x + const y = q.y + const z = q.z + + return Qt.matrix4x4( + w*w + x*x - y*y - z*z, 2*(x*y - w*z), 2*(w*y + x*z), 0, + 2*(x*y + w*z), w*w - x*x + y*y - z*z, 2*(y*z - w*x), 0, + 2*(x*z - w*y), 2*(w*x + y*z), w*w - x*x - y*y + z*z, 0, + 0, 0, 0, 1 + ) + } + + /***** GENERIC MATRIX TRANSFORMATIONS *****/ + + function decomposeModelMatrixFromTransform(transform) { + const posMat = Qt.matrix4x4() + posMat.translate(transform.translation) + const rotMat = quaternionToRotationMatrix(transform.rotation) + const scaleMat = Qt.matrix4x4() + scaleMat.scale(transform.scale3D) + + return { position: posMat, rotation: rotMat, scale: scaleMat } + } + + function localTranslate(transform, translateVec) { + const modelMat = decomposeModelMatrixFromTransform(transform) + + // Compute the translation transformation matrix + const translationMat = Qt.matrix4x4() + translationMat.translate(translateVec) + + // Compute the new model matrix (POSITION * ROTATION * TRANSLATE * SCALE) and set it to the Transform + const mat = modelMat.position.times(modelMat.rotation.times(translationMat.times(modelMat.scale))) + transform.setMatrix(mat) + } + + function localRotate(transform, axis, degree) { + const modelMat = decomposeModelMatrixFromTransform(transform) + + // Compute the transformation quaternion from axis and angle in degrees + let vec3 + switch(axis) { + case TransformGizmo.Axis.X: vec3 = Qt.vector3d(1,0,0); break + case TransformGizmo.Axis.Y: vec3 = Qt.vector3d(0,1,0); break + case TransformGizmo.Axis.Z: vec3 = Qt.vector3d(0,0,1); break + } + const transformQuat = quaternionFromAxisAngle(vec3, degree) + + // Get rotation quaternion of the current model matrix + const initRotQuat = transform.rotation + // Compute the new rotation quaternion and then calculate the matrix + const newRotQuat = multiplyQuaternion(initRotQuat, transformQuat) // Order is important + const newRotationMat = quaternionToRotationMatrix(newRotQuat) + + // Compute the new model matrix (POSITION * NEW_COMPUTED_ROTATION * SCALE) and set it to the Transform + const mat = modelMat.position.times(newRotationMat.times(modelMat.scale)) + transform.setMatrix(mat) + } + + function localScale(transform, scaleVec) { + const modelMat = decomposeModelMatrixFromTransform(transform) + + // Update the scale matrix + modelMat.scale.m11 += scaleVec.x + modelMat.scale.m22 += scaleVec.y + modelMat.scale.m33 += scaleVec.z + + // Compute the new model matrix (POSITION * ROTATION * SCALE) and set it to the Transform + const mat = modelMat.position.times(modelMat.rotation.times(modelMat.scale)) + transform.setMatrix(mat) + } + Transform { id: gizmoTransform scale: { From 45fae981ba4c50cc7e549b9cb6cb8d39e4c3fa67 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 6 Jul 2020 17:10:54 +0200 Subject: [PATCH 03/51] [ui] Viewer3D: TransformGizmo - add picking and transformations - For now, transformations do not take well in count the orientation of the mouse but gizmo is functional. --- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 121 +++++++++++++++++- .../ui/qml/Viewer3D/TransformGizmoPicker.qml | 30 +++++ 2 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index 6b8800e83d..31d1f26eb0 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -9,8 +9,14 @@ Entity { id: root property real gizmoScale: 0.15 property Camera camera + readonly property Transform objectTransform : Transform {} + + signal pickedChanged(bool picked) + + components: [gizmoDisplayTransform, mouseHandler] + - components: [gizmoTransform] + /***** ENUM *****/ enum Axis { X, @@ -126,10 +132,43 @@ Entity { transform.setMatrix(mat) } + /***** SPECIFIC MATRIX TRANSFORMATIONS (using local vars) *****/ + + function doTranslation(translateVec) { + localTranslate(gizmoDisplayTransform, translateVec) // Update gizmo matrix + localTranslate(objectTransform, translateVec) // Update object matrix + } + + function doRotation(axis, degree) { + localRotate(gizmoDisplayTransform, axis, degree) // Update gizmo matrix + localRotate(objectTransform, axis, degree) // Update object matrix + } + + function doScale(scaleVec) { + localScale(objectTransform, scaleVec) // Update object matrix + } + + /***** DEVICES *****/ + + MouseDevice { id: mouseSourceDevice } + + MouseHandler { + id: mouseHandler + sourceDevice: mouseSourceDevice + property point lastPosition + property point currentPosition + onPositionChanged: { + currentPosition.x = mouse.x + currentPosition.y = mouse.y + } + } + + /***** GIZMO'S BASIC COMPONENTS *****/ + Transform { - id: gizmoTransform + id: gizmoDisplayTransform scale: { - return root.gizmoScale * (camera.position.minus(gizmoTransform.translation)).length() + return root.gizmoScale * (camera.position.minus(gizmoDisplayTransform.translation)).length() } } @@ -223,7 +262,7 @@ Entity { Entity { id: axisScaleBox - components: [cubeScaleMesh, cubeScaleTransform, material] + components: [cubeScaleMesh, cubeScaleTransform, material, scalePicker] CuboidMesh { id: cubeScaleMesh @@ -256,13 +295,20 @@ Entity { return m } } - } + } + + TransformGizmoPicker { + id: scalePicker + onPickedChanged: { + root.pickedChanged(picked) + } + } } // POSITION ENTITY Entity { id: positionEntity - components: [coneMesh, coneTransform, material] + components: [coneMesh, coneTransform, material, positionPicker] ConeMesh { id: coneMesh @@ -298,12 +344,19 @@ Entity { return m } } + + TransformGizmoPicker { + id: positionPicker + onPickedChanged: { + root.pickedChanged(picked) + } + } } // ROTATION ENTITY Entity { id: rotationEntity - components: [torusMesh, torusTransform, material] + components: [torusMesh, torusTransform, material, rotationPicker] TorusMesh { id: torusMesh @@ -325,6 +378,60 @@ Entity { return m } } + TransformGizmoPicker { + id: rotationPicker + onPickedChanged: { + root.pickedChanged(picked) + } + } + } + + FrameAction { + onTriggered: { + // POSITION PICKER + if (positionPicker.isPressed) { + const offsetX = mouseHandler.currentPosition.x - mouseHandler.lastPosition.x + const offsetY = mouseHandler.currentPosition.y - mouseHandler.lastPosition.y + + let pickedAxis + switch(axis) { + case TransformGizmo.Axis.X: pickedAxis = Qt.vector3d(1,0,0); break + case TransformGizmo.Axis.Y: pickedAxis = Qt.vector3d(0,1,0); break + case TransformGizmo.Axis.Z: pickedAxis = Qt.vector3d(0,0,1); break + } + + doTranslation(pickedAxis.times(0.01*offsetX - 0.01*offsetY)) + mouseHandler.lastPosition = mouseHandler.currentPosition + return + } + if (rotationPicker.isPressed) { + const offsetX = mouseHandler.currentPosition.x - mouseHandler.lastPosition.x + const offsetY = mouseHandler.currentPosition.y - mouseHandler.lastPosition.y + // const offset = 0.1*offsetX - 0.1*offsetY + const offset = 1 + + doRotation(axis, offset) + mouseHandler.lastPosition = mouseHandler.currentPosition + return + } + if (scalePicker.isPressed) { + const offsetX = mouseHandler.currentPosition.x - mouseHandler.lastPosition.x + const offsetY = mouseHandler.currentPosition.y - mouseHandler.lastPosition.y + // const offset = 0.1*offsetX - 0.1*offsetY + const offset = 0.05 + + let pickedAxis + switch(axis) { + case TransformGizmo.Axis.X: pickedAxis = Qt.vector3d(1,0,0); break + case TransformGizmo.Axis.Y: pickedAxis = Qt.vector3d(0,1,0); break + case TransformGizmo.Axis.Z: pickedAxis = Qt.vector3d(0,0,1); break + } + + doScale(pickedAxis.times(offset)) + mouseHandler.lastPosition = mouseHandler.currentPosition + return + } + } } } } diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml new file mode 100644 index 0000000000..0ddc026b89 --- /dev/null +++ b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml @@ -0,0 +1,30 @@ +import Qt3D.Core 2.0 +import Qt3D.Render 2.9 +import Qt3D.Input 2.0 +import Qt3D.Extras 2.10 +import QtQuick 2.9 +import Qt3D.Logic 2.0 + +ObjectPicker { + id: root + hoverEnabled: true + property bool isPressed : false + signal pickedChanged(bool picked) + + onPressed: { + root.isPressed = true + pickedChanged(true) + mouseHandler.currentPosition = mouseHandler.lastPosition = pick.position + } + onEntered: { + material.ambient = "white" + } + onExited: { + if(!isPressed) material.ambient = baseColor + } + onReleased: { + material.ambient = baseColor + root.isPressed = false + pickedChanged(false) + } +} \ No newline at end of file From 6a9f229838b160b0bd94c28b605ad7c367b7a20b Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 6 Jul 2020 18:34:24 +0200 Subject: [PATCH 04/51] [ui] Viewer3D: TransformGizmo - individual color picking --- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 47 +++++++++++++------ .../ui/qml/Viewer3D/TransformGizmoPicker.qml | 12 +++-- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index 31d1f26eb0..5550bb5c74 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -212,20 +212,13 @@ Entity { } property real lineRadius: 0.015 - PhongMaterial { - id: material - ambient: baseColor - shininess: 0.2 - } - // SCALE ENTITY Entity { id: scaleEntity - components: [material] Entity { id: axisCylinder - components: [cylinderMesh, cylinderTransform, material] + components: [cylinderMesh, cylinderTransform, scaleMaterial] CylinderMesh { id: cylinderMesh @@ -262,7 +255,7 @@ Entity { Entity { id: axisScaleBox - components: [cubeScaleMesh, cubeScaleTransform, material, scalePicker] + components: [cubeScaleMesh, cubeScaleTransform, scaleMaterial, scalePicker] CuboidMesh { id: cubeScaleMesh @@ -297,8 +290,17 @@ Entity { } } + PhongMaterial { + id: scaleMaterial + ambient: baseColor + } + TransformGizmoPicker { - id: scalePicker + id: scalePicker + mouseController : mouseHandler + objectMaterial : scaleMaterial + objectBaseColor : baseColor + onPickedChanged: { root.pickedChanged(picked) } @@ -308,7 +310,7 @@ Entity { // POSITION ENTITY Entity { id: positionEntity - components: [coneMesh, coneTransform, material, positionPicker] + components: [coneMesh, coneTransform, positionMaterial, positionPicker] ConeMesh { id: coneMesh @@ -344,9 +346,17 @@ Entity { return m } } + PhongMaterial { + id: positionMaterial + ambient: baseColor + } TransformGizmoPicker { - id: positionPicker + id: positionPicker + mouseController : mouseHandler + objectMaterial : positionMaterial + objectBaseColor : baseColor + onPickedChanged: { root.pickedChanged(picked) } @@ -356,7 +366,7 @@ Entity { // ROTATION ENTITY Entity { id: rotationEntity - components: [torusMesh, torusTransform, material, rotationPicker] + components: [torusMesh, torusTransform, rotationMaterial, rotationPicker] TorusMesh { id: torusMesh @@ -378,8 +388,17 @@ Entity { return m } } + PhongMaterial { + id: rotationMaterial + ambient: baseColor + } + TransformGizmoPicker { - id: rotationPicker + id: rotationPicker + mouseController: mouseHandler + objectMaterial: rotationMaterial + objectBaseColor: baseColor + onPickedChanged: { root.pickedChanged(picked) } diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml index 0ddc026b89..4740363880 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml @@ -9,21 +9,25 @@ ObjectPicker { id: root hoverEnabled: true property bool isPressed : false + property MouseHandler mouseController + property var objectMaterial + property color objectBaseColor + signal pickedChanged(bool picked) onPressed: { root.isPressed = true pickedChanged(true) - mouseHandler.currentPosition = mouseHandler.lastPosition = pick.position + mouseController.currentPosition = mouseController.lastPosition = pick.position } onEntered: { - material.ambient = "white" + objectMaterial.ambient = "white" } onExited: { - if(!isPressed) material.ambient = baseColor + if(!isPressed) objectMaterial.ambient = objectBaseColor } onReleased: { - material.ambient = baseColor + objectMaterial.ambient = objectBaseColor root.isPressed = false pickedChanged(false) } From 29c4be6210065f6d97ea1f506095bde1f9b678eb Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Tue, 7 Jul 2020 12:44:23 +0200 Subject: [PATCH 05/51] [ui] Viewer3D: TransformGizmo - use one global FrameAction - Use only one global FrameAction instead of having one per axis --- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 73 ++++++++++++------- .../ui/qml/Viewer3D/TransformGizmoPicker.qml | 21 +++--- 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index 5550bb5c74..84e937157f 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -7,16 +7,16 @@ import Qt3D.Logic 2.0 Entity { id: root - property real gizmoScale: 0.15 + property real gizmoScale: 0.20 property Camera camera readonly property Transform objectTransform : Transform {} - signal pickedChanged(bool picked) + signal pickedChanged(bool pressed) components: [gizmoDisplayTransform, mouseHandler] - /***** ENUM *****/ + /***** ENUMS *****/ enum Axis { X, @@ -24,6 +24,12 @@ Entity { Z } + enum Type { + POSITION, + ROTATION, + SCALE + } + /***** QUATERNIONS *****/ function multiplyQuaternion(q1, q2) { @@ -297,12 +303,15 @@ Entity { TransformGizmoPicker { id: scalePicker - mouseController : mouseHandler - objectMaterial : scaleMaterial - objectBaseColor : baseColor - + mouseController: mouseHandler + gizmoMaterial: scaleMaterial + gizmoBaseColor: baseColor + gizmoAxis: axis + gizmoType: TransformGizmo.Type.SCALE + onPickedChanged: { - root.pickedChanged(picked) + root.pickedChanged(picker.isPressed) // Used to prevent camera transformations + transformHandler.objectPicker = picker.isPressed ? picker : null // Pass the picker to the global FrameAction } } } @@ -353,12 +362,15 @@ Entity { TransformGizmoPicker { id: positionPicker - mouseController : mouseHandler - objectMaterial : positionMaterial - objectBaseColor : baseColor + mouseController: mouseHandler + gizmoMaterial: positionMaterial + gizmoBaseColor: baseColor + gizmoAxis: axis + gizmoType: TransformGizmo.Type.POSITION onPickedChanged: { - root.pickedChanged(picked) + root.pickedChanged(picker.isPressed) // Used to prevent camera transformations + transformHandler.objectPicker = picker.isPressed ? picker : null // Pass the picker to the global FrameAction } } } @@ -396,24 +408,33 @@ Entity { TransformGizmoPicker { id: rotationPicker mouseController: mouseHandler - objectMaterial: rotationMaterial - objectBaseColor: baseColor + gizmoMaterial: rotationMaterial + gizmoBaseColor: baseColor + gizmoAxis: axis + gizmoType: TransformGizmo.Type.ROTATION onPickedChanged: { - root.pickedChanged(picked) + root.pickedChanged(picker.isPressed) // Used to prevent camera transformations + transformHandler.objectPicker = picker.isPressed ? picker : null // Pass the picker to the global FrameAction } } } + } + } + + FrameAction { + id: transformHandler + property var objectPicker: null - FrameAction { - onTriggered: { - // POSITION PICKER - if (positionPicker.isPressed) { + onTriggered: { + if (objectPicker) { + switch(objectPicker.gizmoType) { + case TransformGizmo.Type.POSITION: { const offsetX = mouseHandler.currentPosition.x - mouseHandler.lastPosition.x const offsetY = mouseHandler.currentPosition.y - mouseHandler.lastPosition.y let pickedAxis - switch(axis) { + switch(objectPicker.gizmoAxis) { case TransformGizmo.Axis.X: pickedAxis = Qt.vector3d(1,0,0); break case TransformGizmo.Axis.Y: pickedAxis = Qt.vector3d(0,1,0); break case TransformGizmo.Axis.Z: pickedAxis = Qt.vector3d(0,0,1); break @@ -423,24 +444,26 @@ Entity { mouseHandler.lastPosition = mouseHandler.currentPosition return } - if (rotationPicker.isPressed) { + + case TransformGizmo.Type.ROTATION: { const offsetX = mouseHandler.currentPosition.x - mouseHandler.lastPosition.x const offsetY = mouseHandler.currentPosition.y - mouseHandler.lastPosition.y // const offset = 0.1*offsetX - 0.1*offsetY const offset = 1 - doRotation(axis, offset) + doRotation(objectPicker.gizmoAxis, offset) mouseHandler.lastPosition = mouseHandler.currentPosition return } - if (scalePicker.isPressed) { + + case TransformGizmo.Type.SCALE: { const offsetX = mouseHandler.currentPosition.x - mouseHandler.lastPosition.x const offsetY = mouseHandler.currentPosition.y - mouseHandler.lastPosition.y // const offset = 0.1*offsetX - 0.1*offsetY const offset = 0.05 let pickedAxis - switch(axis) { + switch(objectPicker.gizmoAxis) { case TransformGizmo.Axis.X: pickedAxis = Qt.vector3d(1,0,0); break case TransformGizmo.Axis.Y: pickedAxis = Qt.vector3d(0,1,0); break case TransformGizmo.Axis.Z: pickedAxis = Qt.vector3d(0,0,1); break @@ -448,7 +471,7 @@ Entity { doScale(pickedAxis.times(offset)) mouseHandler.lastPosition = mouseHandler.currentPosition - return + return } } } diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml index 4740363880..17474c73fa 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml @@ -7,28 +7,31 @@ import Qt3D.Logic 2.0 ObjectPicker { id: root - hoverEnabled: true property bool isPressed : false property MouseHandler mouseController - property var objectMaterial - property color objectBaseColor + property var gizmoMaterial + property color gizmoBaseColor + property int gizmoAxis + property int gizmoType + + signal pickedChanged(var picker) - signal pickedChanged(bool picked) + hoverEnabled: true onPressed: { root.isPressed = true - pickedChanged(true) + pickedChanged(this) mouseController.currentPosition = mouseController.lastPosition = pick.position } onEntered: { - objectMaterial.ambient = "white" + gizmoMaterial.ambient = "white" } onExited: { - if(!isPressed) objectMaterial.ambient = objectBaseColor + if(!isPressed) gizmoMaterial.ambient = gizmoBaseColor } onReleased: { - objectMaterial.ambient = objectBaseColor + gizmoMaterial.ambient = gizmoBaseColor root.isPressed = false - pickedChanged(false) + pickedChanged(this) } } \ No newline at end of file From fb3b541a7f3d624b8469ebc273ff40e26c70961c Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Wed, 8 Jul 2020 12:17:11 +0200 Subject: [PATCH 06/51] [ui] Viewer3D: TransformGizmo - new mouse handling translation - Translations are computed using the dot product between the mouse vector (picked position to current position) and the projected picked axis vector. Like this, the behaviour is a lot more natural and user-friendly. --- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 52 +++++++++++++------ .../ui/qml/Viewer3D/TransformGizmoPicker.qml | 3 ++ 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index 84e937157f..b1b719118e 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -80,26 +80,28 @@ Entity { /***** GENERIC MATRIX TRANSFORMATIONS *****/ - function decomposeModelMatrixFromTransform(transform) { + function decomposeModelMatrixFromTransformations(translation, rotation, scale3D) { const posMat = Qt.matrix4x4() - posMat.translate(transform.translation) - const rotMat = quaternionToRotationMatrix(transform.rotation) + posMat.translate(translation) + const rotMat = quaternionToRotationMatrix(rotation) const scaleMat = Qt.matrix4x4() - scaleMat.scale(transform.scale3D) + scaleMat.scale(scale3D) return { position: posMat, rotation: rotMat, scale: scaleMat } } - function localTranslate(transform, translateVec) { - const modelMat = decomposeModelMatrixFromTransform(transform) + function decomposeModelMatrixFromTransform(transform) { + return decomposeModelMatrixFromTransformations(transform.translation, transform.rotation, transform.scale3D) + } + function localTranslateFrom(transform, initialDecomposedModelMat, translateVec) { // Compute the translation transformation matrix const translationMat = Qt.matrix4x4() translationMat.translate(translateVec) // Compute the new model matrix (POSITION * ROTATION * TRANSLATE * SCALE) and set it to the Transform - const mat = modelMat.position.times(modelMat.rotation.times(translationMat.times(modelMat.scale))) - transform.setMatrix(mat) + const mat = initialDecomposedModelMat.position.times(initialDecomposedModelMat.rotation.times(translationMat.times(initialDecomposedModelMat.scale))) + transform.setMatrix(mat) } function localRotate(transform, axis, degree) { @@ -140,9 +142,9 @@ Entity { /***** SPECIFIC MATRIX TRANSFORMATIONS (using local vars) *****/ - function doTranslation(translateVec) { - localTranslate(gizmoDisplayTransform, translateVec) // Update gizmo matrix - localTranslate(objectTransform, translateVec) // Update object matrix + function doTranslation(initialDecomposedModelMat, translateVec) { + localTranslateFrom(gizmoDisplayTransform, initialDecomposedModelMat, translateVec) // Update gizmo matrix + localTranslateFrom(objectTransform, initialDecomposedModelMat, translateVec) // Update object matrix } function doRotation(axis, degree) { @@ -369,6 +371,7 @@ Entity { gizmoType: TransformGizmo.Type.POSITION onPickedChanged: { + this.decomposedObjectModelMat = decomposeModelMatrixFromTransformations(objectTransform.translation, objectTransform.rotation, objectTransform.scale3D) // Save the current transformations root.pickedChanged(picker.isPressed) // Used to prevent camera transformations transformHandler.objectPicker = picker.isPressed ? picker : null // Pass the picker to the global FrameAction } @@ -430,9 +433,7 @@ Entity { if (objectPicker) { switch(objectPicker.gizmoType) { case TransformGizmo.Type.POSITION: { - const offsetX = mouseHandler.currentPosition.x - mouseHandler.lastPosition.x - const offsetY = mouseHandler.currentPosition.y - mouseHandler.lastPosition.y - + // Get the corresponding axis translation let pickedAxis switch(objectPicker.gizmoAxis) { case TransformGizmo.Axis.X: pickedAxis = Qt.vector3d(1,0,0); break @@ -440,8 +441,27 @@ Entity { case TransformGizmo.Axis.Z: pickedAxis = Qt.vector3d(0,0,1); break } - doTranslation(pickedAxis.times(0.01*offsetX - 0.01*offsetY)) - mouseHandler.lastPosition = mouseHandler.currentPosition + const sensibility = 0.02 + + // Compute the current vector PickedPoint -> CurrentMousePoint + const pickedPosition = objectPicker.screenPoint + const mouseVector = Qt.vector2d(mouseHandler.currentPosition.x - pickedPosition.x, -(mouseHandler.currentPosition.y - pickedPosition.y)) + + // Transform the positive picked axis vector from world coord to viewport coord + const viewMatrix = camera.transform.matrix.inverted() + const gizmoLocalPointOnAxis = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 1)) + const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) + const projectedPointOnAxis = camera.projectionMatrix.times(viewMatrix.times(gizmoLocalPointOnAxis)) + const projectedCenter = camera.projectionMatrix.times(viewMatrix.times(gizmoCenterPoint)) + const projectedAxisVector = Qt.vector2d(projectedPointOnAxis.x/projectedPointOnAxis.w - projectedCenter.x/projectedCenter.w, projectedPointOnAxis.y/projectedPointOnAxis.w - projectedCenter.y/projectedCenter.w) + + // Get the cosinus of the angle from the projectedAxisVector to the mouseVector + const cosAngle = projectedAxisVector.dotProduct(mouseVector) / (projectedAxisVector.length() * mouseVector.length()) + const offset = cosAngle * mouseVector.length() * sensibility + + // If the mouse is not at the same spot as the pickedPoint, we do translation + if (offset) doTranslation(objectPicker.decomposedObjectModelMat, pickedAxis.times(offset)) // Do a translation from the initial Object Model Matrix when we picked the gizmo + return } diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml index 17474c73fa..785ca52f06 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml @@ -13,6 +13,8 @@ ObjectPicker { property color gizmoBaseColor property int gizmoAxis property int gizmoType + property point screenPoint + property var decomposedObjectModelMat signal pickedChanged(var picker) @@ -21,6 +23,7 @@ ObjectPicker { onPressed: { root.isPressed = true pickedChanged(this) + screenPoint = pick.position mouseController.currentPosition = mouseController.lastPosition = pick.position } onEntered: { From cd630418e468274a81fe6932796de9211b991de9 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Wed, 8 Jul 2020 15:27:00 +0200 Subject: [PATCH 07/51] [ui] Viewer3D: TransformGizmo - new mouse handling scale - Scale is computed using the distance between the gizmo's center and the mouse. The scale unit (where nothing changes) is defined by the distance between the gizmo's center and the picked position. --- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 65 +++++++++++++++------ 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index b1b719118e..d6f9da71da 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -9,6 +9,7 @@ Entity { id: root property real gizmoScale: 0.20 property Camera camera + property var windowSize readonly property Transform objectTransform : Transform {} signal pickedChanged(bool pressed) @@ -80,6 +81,21 @@ Entity { /***** GENERIC MATRIX TRANSFORMATIONS *****/ + function pointFromWorldToScreen(point, camera, windowSize) { + // Transform the point from World Coord to Normalized Device Coord + const viewMatrix = camera.transform.matrix.inverted() + const projectedPoint = camera.projectionMatrix.times(viewMatrix.times(point)) + const projectedPoint2D = Qt.vector2d(projectedPoint.x/projectedPoint.w, projectedPoint.y/projectedPoint.w) + + // Transform the point from Normalized Device Coord to Screen Coord + const screenPoint2D = Qt.vector2d( + parseInt((projectedPoint2D.x + 1) * windowSize.width / 2), + parseInt((projectedPoint2D.y - 1) * windowSize.height / -2) + ) + + return screenPoint2D + } + function decomposeModelMatrixFromTransformations(translation, rotation, scale3D) { const posMat = Qt.matrix4x4() posMat.translate(translation) @@ -127,16 +143,19 @@ Entity { transform.setMatrix(mat) } - function localScale(transform, scaleVec) { - const modelMat = decomposeModelMatrixFromTransform(transform) + function localScale(transform, initialDecomposedModelMat, scaleVec) { + // Make a copy of the scale matrix (otherwise, it is a reference and it does not work as expected) + // Unfortunately, we have to proceed like this because, in QML, Qt.matrix4x4(Qt.matrix4x4) does not work + const scaleMat = Qt.matrix4x4() + scaleMat.scale(Qt.vector3d(initialDecomposedModelMat.scale.m11, initialDecomposedModelMat.scale.m22, initialDecomposedModelMat.scale.m33)) - // Update the scale matrix - modelMat.scale.m11 += scaleVec.x - modelMat.scale.m22 += scaleVec.y - modelMat.scale.m33 += scaleVec.z + // Update the scale matrix copy + scaleMat.m11 += scaleVec.x + scaleMat.m22 += scaleVec.y + scaleMat.m33 += scaleVec.z // Compute the new model matrix (POSITION * ROTATION * SCALE) and set it to the Transform - const mat = modelMat.position.times(modelMat.rotation.times(modelMat.scale)) + const mat = initialDecomposedModelMat.position.times(initialDecomposedModelMat.rotation.times(scaleMat)) transform.setMatrix(mat) } @@ -152,8 +171,8 @@ Entity { localRotate(objectTransform, axis, degree) // Update object matrix } - function doScale(scaleVec) { - localScale(objectTransform, scaleVec) // Update object matrix + function doScale(initialDecomposedModelMat, scaleVec) { + localScale(objectTransform, initialDecomposedModelMat, scaleVec) // Update object matrix } /***** DEVICES *****/ @@ -312,6 +331,7 @@ Entity { gizmoType: TransformGizmo.Type.SCALE onPickedChanged: { + this.decomposedObjectModelMat = decomposeModelMatrixFromTransformations(objectTransform.translation, objectTransform.rotation, objectTransform.scale3D) // Save the current transformations root.pickedChanged(picker.isPressed) // Used to prevent camera transformations transformHandler.objectPicker = picker.isPressed ? picker : null // Pass the picker to the global FrameAction } @@ -440,14 +460,13 @@ Entity { case TransformGizmo.Axis.Y: pickedAxis = Qt.vector3d(0,1,0); break case TransformGizmo.Axis.Z: pickedAxis = Qt.vector3d(0,0,1); break } - const sensibility = 0.02 // Compute the current vector PickedPoint -> CurrentMousePoint const pickedPosition = objectPicker.screenPoint const mouseVector = Qt.vector2d(mouseHandler.currentPosition.x - pickedPosition.x, -(mouseHandler.currentPosition.y - pickedPosition.y)) - // Transform the positive picked axis vector from world coord to viewport coord + // Transform the positive picked axis vector from World Coord to Normalized Device Coord const viewMatrix = camera.transform.matrix.inverted() const gizmoLocalPointOnAxis = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 1)) const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) @@ -477,21 +496,29 @@ Entity { } case TransformGizmo.Type.SCALE: { - const offsetX = mouseHandler.currentPosition.x - mouseHandler.lastPosition.x - const offsetY = mouseHandler.currentPosition.y - mouseHandler.lastPosition.y - // const offset = 0.1*offsetX - 0.1*offsetY - const offset = 0.05 - + // Get the corresponding axis scale let pickedAxis switch(objectPicker.gizmoAxis) { case TransformGizmo.Axis.X: pickedAxis = Qt.vector3d(1,0,0); break case TransformGizmo.Axis.Y: pickedAxis = Qt.vector3d(0,1,0); break case TransformGizmo.Axis.Z: pickedAxis = Qt.vector3d(0,0,1); break } + const sensibility = 0.05 - doScale(pickedAxis.times(offset)) - mouseHandler.lastPosition = mouseHandler.currentPosition - return + // Get Screen Coordinates of the gizmo center + const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) + const screenCenter2D = pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize) + + // Compute the scale unit + const scaleUnit = screenCenter2D.minus(Qt.vector2d(objectPicker.screenPoint.x, objectPicker.screenPoint.y)).length() + + // Compute the current vector screenCenter2D -> CurrentMousePoint + const mouseVector = Qt.vector2d(mouseHandler.currentPosition.x - screenCenter2D.x, -(mouseHandler.currentPosition.y - screenCenter2D.y)) + let offset = (mouseVector.length() - scaleUnit) * sensibility + offset = (offset < 0) ? offset * 3 : offset // Used to make it more sensible when we want to reduce the scale (because the action field is shorter) + + if (offset) doScale(objectPicker.decomposedObjectModelMat, pickedAxis.times(offset)) + return } } } From ad2f287b66932c3a80cb71810a2dd6eb1db1437d Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Wed, 8 Jul 2020 15:42:41 +0200 Subject: [PATCH 08/51] [ui] Viewer3D: TransformGizmo - clean mouse handling translation --- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index d6f9da71da..6e7a4abf1e 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -466,16 +466,15 @@ Entity { const pickedPosition = objectPicker.screenPoint const mouseVector = Qt.vector2d(mouseHandler.currentPosition.x - pickedPosition.x, -(mouseHandler.currentPosition.y - pickedPosition.y)) - // Transform the positive picked axis vector from World Coord to Normalized Device Coord - const viewMatrix = camera.transform.matrix.inverted() + // Transform the positive picked axis vector from World Coord to Screen Coord const gizmoLocalPointOnAxis = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 1)) const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) - const projectedPointOnAxis = camera.projectionMatrix.times(viewMatrix.times(gizmoLocalPointOnAxis)) - const projectedCenter = camera.projectionMatrix.times(viewMatrix.times(gizmoCenterPoint)) - const projectedAxisVector = Qt.vector2d(projectedPointOnAxis.x/projectedPointOnAxis.w - projectedCenter.x/projectedCenter.w, projectedPointOnAxis.y/projectedPointOnAxis.w - projectedCenter.y/projectedCenter.w) + const screenPoint2D = pointFromWorldToScreen(gizmoLocalPointOnAxis, camera, windowSize) + const screenCenter2D = pointFromWorldToScreen(gizmoCenterPoint, camera, windowSize) + const screenAxisVector = Qt.vector2d(screenPoint2D.x - screenCenter2D.x, -(screenPoint2D.y - screenCenter2D.y)) - // Get the cosinus of the angle from the projectedAxisVector to the mouseVector - const cosAngle = projectedAxisVector.dotProduct(mouseVector) / (projectedAxisVector.length() * mouseVector.length()) + // Get the cosinus of the angle from the screenAxisVector to the mouseVector + const cosAngle = screenAxisVector.dotProduct(mouseVector) / (screenAxisVector.length() * mouseVector.length()) const offset = cosAngle * mouseVector.length() * sensibility // If the mouse is not at the same spot as the pickedPoint, we do translation From 414a865dff5bd221396f8ee53b87d302cef993ff Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Wed, 8 Jul 2020 15:51:49 +0200 Subject: [PATCH 09/51] [ui] Viewer3D: TransformGizmo - clean parameter names --- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index 6e7a4abf1e..c6db00d12b 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -106,22 +106,22 @@ Entity { return { position: posMat, rotation: rotMat, scale: scaleMat } } - function decomposeModelMatrixFromTransform(transform) { - return decomposeModelMatrixFromTransformations(transform.translation, transform.rotation, transform.scale3D) + function decomposeModelMatrixFromTransform(transformQtInstance) { + return decomposeModelMatrixFromTransformations(transformQtInstance.translation, transformQtInstance.rotation, transformQtInstance.scale3D) } - function localTranslateFrom(transform, initialDecomposedModelMat, translateVec) { + function localTranslateFrom(transformQtInstance, initialDecomposedModelMat, translateVec) { // Compute the translation transformation matrix const translationMat = Qt.matrix4x4() translationMat.translate(translateVec) // Compute the new model matrix (POSITION * ROTATION * TRANSLATE * SCALE) and set it to the Transform const mat = initialDecomposedModelMat.position.times(initialDecomposedModelMat.rotation.times(translationMat.times(initialDecomposedModelMat.scale))) - transform.setMatrix(mat) + transformQtInstance.setMatrix(mat) } - function localRotate(transform, axis, degree) { - const modelMat = decomposeModelMatrixFromTransform(transform) + function localRotate(transformQtInstance, axis, degree) { + const modelMat = decomposeModelMatrixFromTransform(transformQtInstance) // Compute the transformation quaternion from axis and angle in degrees let vec3 @@ -133,17 +133,17 @@ Entity { const transformQuat = quaternionFromAxisAngle(vec3, degree) // Get rotation quaternion of the current model matrix - const initRotQuat = transform.rotation + const initRotQuat = transformQtInstance.rotation // Compute the new rotation quaternion and then calculate the matrix const newRotQuat = multiplyQuaternion(initRotQuat, transformQuat) // Order is important const newRotationMat = quaternionToRotationMatrix(newRotQuat) // Compute the new model matrix (POSITION * NEW_COMPUTED_ROTATION * SCALE) and set it to the Transform const mat = modelMat.position.times(newRotationMat.times(modelMat.scale)) - transform.setMatrix(mat) + transformQtInstance.setMatrix(mat) } - function localScale(transform, initialDecomposedModelMat, scaleVec) { + function localScale(transformQtInstance, initialDecomposedModelMat, scaleVec) { // Make a copy of the scale matrix (otherwise, it is a reference and it does not work as expected) // Unfortunately, we have to proceed like this because, in QML, Qt.matrix4x4(Qt.matrix4x4) does not work const scaleMat = Qt.matrix4x4() @@ -156,7 +156,7 @@ Entity { // Compute the new model matrix (POSITION * ROTATION * SCALE) and set it to the Transform const mat = initialDecomposedModelMat.position.times(initialDecomposedModelMat.rotation.times(scaleMat)) - transform.setMatrix(mat) + transformQtInstance.setMatrix(mat) } /***** SPECIFIC MATRIX TRANSFORMATIONS (using local vars) *****/ @@ -437,6 +437,7 @@ Entity { gizmoType: TransformGizmo.Type.ROTATION onPickedChanged: { + this.decomposedObjectModelMat = decomposeModelMatrixFromTransformations(objectTransform.translation, objectTransform.rotation, objectTransform.scale3D) // Save the current transformations root.pickedChanged(picker.isPressed) // Used to prevent camera transformations transformHandler.objectPicker = picker.isPressed ? picker : null // Pass the picker to the global FrameAction } From d04eaaccfc7d6a701766597888fe48514e268a6b Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Wed, 8 Jul 2020 17:57:02 +0200 Subject: [PATCH 10/51] [ui] Viewer3D: TransformGizmo - new mouse handling rotation - Rotation is the result angle of the mouse rotation around the gizmo's center. It is quite user-friendly. --- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 72 ++++++++++----------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index c6db00d12b..fd39847959 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -103,7 +103,9 @@ Entity { const scaleMat = Qt.matrix4x4() scaleMat.scale(scale3D) - return { position: posMat, rotation: rotMat, scale: scaleMat } + const rotQuat = Qt.quaternion(rotation.scalar, rotation.x, rotation.y, rotation.z) + + return { position: posMat, rotation: rotMat, scale: scaleMat, quaternion: rotQuat } } function decomposeModelMatrixFromTransform(transformQtInstance) { @@ -120,26 +122,18 @@ Entity { transformQtInstance.setMatrix(mat) } - function localRotate(transformQtInstance, axis, degree) { - const modelMat = decomposeModelMatrixFromTransform(transformQtInstance) - + function localRotate(transformQtInstance, initialDecomposedModelMat, axis, degree) { // Compute the transformation quaternion from axis and angle in degrees - let vec3 - switch(axis) { - case TransformGizmo.Axis.X: vec3 = Qt.vector3d(1,0,0); break - case TransformGizmo.Axis.Y: vec3 = Qt.vector3d(0,1,0); break - case TransformGizmo.Axis.Z: vec3 = Qt.vector3d(0,0,1); break - } - const transformQuat = quaternionFromAxisAngle(vec3, degree) + const transformQuat = quaternionFromAxisAngle(axis, degree) // Get rotation quaternion of the current model matrix - const initRotQuat = transformQtInstance.rotation + const initRotQuat = initialDecomposedModelMat.quaternion // Compute the new rotation quaternion and then calculate the matrix const newRotQuat = multiplyQuaternion(initRotQuat, transformQuat) // Order is important const newRotationMat = quaternionToRotationMatrix(newRotQuat) // Compute the new model matrix (POSITION * NEW_COMPUTED_ROTATION * SCALE) and set it to the Transform - const mat = modelMat.position.times(newRotationMat.times(modelMat.scale)) + const mat = initialDecomposedModelMat.position.times(newRotationMat.times(initialDecomposedModelMat.scale)) transformQtInstance.setMatrix(mat) } @@ -166,9 +160,9 @@ Entity { localTranslateFrom(objectTransform, initialDecomposedModelMat, translateVec) // Update object matrix } - function doRotation(axis, degree) { - localRotate(gizmoDisplayTransform, axis, degree) // Update gizmo matrix - localRotate(objectTransform, axis, degree) // Update object matrix + function doRotation(initialDecomposedModelMat, axis, degree) { + localRotate(gizmoDisplayTransform, initialDecomposedModelMat, axis, degree) // Update gizmo matrix + localRotate(objectTransform, initialDecomposedModelMat, axis, degree) // Update object matrix } function doScale(initialDecomposedModelMat, scaleVec) { @@ -452,15 +446,16 @@ Entity { onTriggered: { if (objectPicker) { + + let pickedAxis + switch(objectPicker.gizmoAxis) { + case TransformGizmo.Axis.X: pickedAxis = Qt.vector3d(1,0,0); break + case TransformGizmo.Axis.Y: pickedAxis = Qt.vector3d(0,1,0); break + case TransformGizmo.Axis.Z: pickedAxis = Qt.vector3d(0,0,1); break + } + switch(objectPicker.gizmoType) { case TransformGizmo.Type.POSITION: { - // Get the corresponding axis translation - let pickedAxis - switch(objectPicker.gizmoAxis) { - case TransformGizmo.Axis.X: pickedAxis = Qt.vector3d(1,0,0); break - case TransformGizmo.Axis.Y: pickedAxis = Qt.vector3d(0,1,0); break - case TransformGizmo.Axis.Z: pickedAxis = Qt.vector3d(0,0,1); break - } const sensibility = 0.02 // Compute the current vector PickedPoint -> CurrentMousePoint @@ -485,24 +480,29 @@ Entity { } case TransformGizmo.Type.ROTATION: { - const offsetX = mouseHandler.currentPosition.x - mouseHandler.lastPosition.x - const offsetY = mouseHandler.currentPosition.y - mouseHandler.lastPosition.y - // const offset = 0.1*offsetX - 0.1*offsetY - const offset = 1 + // Get Screen Coordinates of the gizmo center + const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) + const screenCenter2D = pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize) + + // Get the vector screenCenter2D -> PickedPoint + const originalVector = Qt.vector2d(objectPicker.screenPoint.x - screenCenter2D.x, -(objectPicker.screenPoint.y - screenCenter2D.y)) + + // Compute the current vector screenCenter2D -> CurrentMousePoint + const mouseVector = Qt.vector2d(mouseHandler.currentPosition.x - screenCenter2D.x, -(mouseHandler.currentPosition.y - screenCenter2D.y)) + + // Get the angle from the originalVector to the mouseVector + const angle = Math.atan2(-originalVector.y*mouseVector.x + originalVector.x*mouseVector.y, originalVector.x*mouseVector.x + originalVector.y*mouseVector.y) * 180 / Math.PI - doRotation(objectPicker.gizmoAxis, offset) - mouseHandler.lastPosition = mouseHandler.currentPosition + // Get the orientation of the gizmo in function of the camera + const gizmoLocalAxisVector = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 0)) + const gizmoToCameraVector = camera.position.toVector4d().minus(gizmoCenterPoint) + const orientation = gizmoLocalAxisVector.dotProduct(gizmoToCameraVector) > 0 ? 1 : -1 + + if (angle !== 0) doRotation(objectPicker.decomposedObjectModelMat, pickedAxis, angle*orientation) return } case TransformGizmo.Type.SCALE: { - // Get the corresponding axis scale - let pickedAxis - switch(objectPicker.gizmoAxis) { - case TransformGizmo.Axis.X: pickedAxis = Qt.vector3d(1,0,0); break - case TransformGizmo.Axis.Y: pickedAxis = Qt.vector3d(0,1,0); break - case TransformGizmo.Axis.Z: pickedAxis = Qt.vector3d(0,0,1); break - } const sensibility = 0.05 // Get Screen Coordinates of the gizmo center From bdf9d74ec8735573e2242ce121e0faf68136479d Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Wed, 8 Jul 2020 18:06:56 +0200 Subject: [PATCH 11/51] [ui] Viewer3D: TransformGizmo - cleaning and renaming --- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 23 +++++++++---------- .../ui/qml/Viewer3D/TransformGizmoPicker.qml | 2 +- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index fd39847959..750c0ed5d6 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -105,20 +105,20 @@ Entity { const rotQuat = Qt.quaternion(rotation.scalar, rotation.x, rotation.y, rotation.z) - return { position: posMat, rotation: rotMat, scale: scaleMat, quaternion: rotQuat } + return { positionMat: posMat, rotationMat: rotMat, scaleMat: scaleMat, quaternion: rotQuat } } function decomposeModelMatrixFromTransform(transformQtInstance) { return decomposeModelMatrixFromTransformations(transformQtInstance.translation, transformQtInstance.rotation, transformQtInstance.scale3D) } - function localTranslateFrom(transformQtInstance, initialDecomposedModelMat, translateVec) { + function localTranslate(transformQtInstance, initialDecomposedModelMat, translateVec) { // Compute the translation transformation matrix const translationMat = Qt.matrix4x4() translationMat.translate(translateVec) // Compute the new model matrix (POSITION * ROTATION * TRANSLATE * SCALE) and set it to the Transform - const mat = initialDecomposedModelMat.position.times(initialDecomposedModelMat.rotation.times(translationMat.times(initialDecomposedModelMat.scale))) + const mat = initialDecomposedModelMat.positionMat.times(initialDecomposedModelMat.rotationMat.times(translationMat.times(initialDecomposedModelMat.scaleMat))) transformQtInstance.setMatrix(mat) } @@ -133,7 +133,7 @@ Entity { const newRotationMat = quaternionToRotationMatrix(newRotQuat) // Compute the new model matrix (POSITION * NEW_COMPUTED_ROTATION * SCALE) and set it to the Transform - const mat = initialDecomposedModelMat.position.times(newRotationMat.times(initialDecomposedModelMat.scale)) + const mat = initialDecomposedModelMat.positionMat.times(newRotationMat.times(initialDecomposedModelMat.scaleMat)) transformQtInstance.setMatrix(mat) } @@ -141,7 +141,7 @@ Entity { // Make a copy of the scale matrix (otherwise, it is a reference and it does not work as expected) // Unfortunately, we have to proceed like this because, in QML, Qt.matrix4x4(Qt.matrix4x4) does not work const scaleMat = Qt.matrix4x4() - scaleMat.scale(Qt.vector3d(initialDecomposedModelMat.scale.m11, initialDecomposedModelMat.scale.m22, initialDecomposedModelMat.scale.m33)) + scaleMat.scale(Qt.vector3d(initialDecomposedModelMat.scaleMat.m11, initialDecomposedModelMat.scaleMat.m22, initialDecomposedModelMat.scaleMat.m33)) // Update the scale matrix copy scaleMat.m11 += scaleVec.x @@ -149,15 +149,15 @@ Entity { scaleMat.m33 += scaleVec.z // Compute the new model matrix (POSITION * ROTATION * SCALE) and set it to the Transform - const mat = initialDecomposedModelMat.position.times(initialDecomposedModelMat.rotation.times(scaleMat)) + const mat = initialDecomposedModelMat.positionMat.times(initialDecomposedModelMat.rotationMat.times(scaleMat)) transformQtInstance.setMatrix(mat) } /***** SPECIFIC MATRIX TRANSFORMATIONS (using local vars) *****/ function doTranslation(initialDecomposedModelMat, translateVec) { - localTranslateFrom(gizmoDisplayTransform, initialDecomposedModelMat, translateVec) // Update gizmo matrix - localTranslateFrom(objectTransform, initialDecomposedModelMat, translateVec) // Update object matrix + localTranslate(gizmoDisplayTransform, initialDecomposedModelMat, translateVec) // Update gizmo matrix + localTranslate(objectTransform, initialDecomposedModelMat, translateVec) // Update object matrix } function doRotation(initialDecomposedModelMat, axis, degree) { @@ -176,7 +176,6 @@ Entity { MouseHandler { id: mouseHandler sourceDevice: mouseSourceDevice - property point lastPosition property point currentPosition onPositionChanged: { currentPosition.x = mouse.x @@ -226,9 +225,9 @@ Entity { } property color baseColor: { switch(axis) { - case TransformGizmo.Axis.X: return "#e63b55" - case TransformGizmo.Axis.Y: return "#83c414" - case TransformGizmo.Axis.Z: return "#3387e2" + case TransformGizmo.Axis.X: return "#e63b55" // Red + case TransformGizmo.Axis.Y: return "#83c414" // Green + case TransformGizmo.Axis.Z: return "#3387e2" // Blue } } property real lineRadius: 0.015 diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml index 785ca52f06..f99cfb3a4f 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml @@ -24,7 +24,7 @@ ObjectPicker { root.isPressed = true pickedChanged(this) screenPoint = pick.position - mouseController.currentPosition = mouseController.lastPosition = pick.position + mouseController.currentPosition = pick.position } onEntered: { gizmoMaterial.ambient = "white" From 85ebbba14fa030ac46d9ed25f1774652ccb11e58 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Thu, 9 Jul 2020 10:56:25 +0200 Subject: [PATCH 12/51] [ui] Viewer3D: TransformGizmo - drawing gizmo on top of the object - Special entity acting as an object container: we can add the entities we want to control with a gizmo inside it. - Drawing the gizmo always on top of the object to make sure to see it every time. --- .../qml/Viewer3D/DefaultCameraController.qml | 4 +++ meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml | 32 +++++++++++++++++++ meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 15 +++++---- meshroom/ui/qml/Viewer3D/Viewer3D.qml | 11 +++++++ 4 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml diff --git a/meshroom/ui/qml/Viewer3D/DefaultCameraController.qml b/meshroom/ui/qml/Viewer3D/DefaultCameraController.qml index 1992ca281f..0dc4f8488e 100644 --- a/meshroom/ui/qml/Viewer3D/DefaultCameraController.qml +++ b/meshroom/ui/qml/Viewer3D/DefaultCameraController.qml @@ -23,6 +23,8 @@ Entity { property alias windowSize: trackball.windowSize property alias trackballSize: trackball.trackballSize + property bool loseMouseFocus: false // Must be changed by other entities when they want to take mouse focus + readonly property alias pressed: mouseHandler._pressed signal mousePressed(var mouse) signal mouseReleased(var mouse, var moved) @@ -166,6 +168,8 @@ Entity { components: [ FrameAction { onTriggered: { + if(loseMouseFocus) return + if(panning) { // translate var d = (root.camera.viewCenter.minus(root.camera.position)).length() * 0.03; var tx = axisMX.value * root.translateSpeed * d; diff --git a/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml b/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml new file mode 100644 index 0000000000..3a6eaa5d44 --- /dev/null +++ b/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml @@ -0,0 +1,32 @@ +import Qt3D.Core 2.0 +import Qt3D.Render 2.9 +import Qt3D.Input 2.0 +import Qt3D.Extras 2.10 +import QtQuick 2.9 +import Qt3D.Logic 2.0 + +Entity { + id: root + property DefaultCameraController cameraController + property Layer frontLayerComponent + + readonly property Camera camera : cameraController.camera + readonly property var windowSize: cameraController.windowSize + readonly property alias objectTransform : transformGizmo.objectTransform // The Transform the object should use + + signal pickedChanged(bool pressed) + + onPickedChanged: { + cameraController.loseMouseFocus = pressed // Notify the camera if the transform takes/releases the focus + } + + TransformGizmo { + id: transformGizmo + camera: root.camera + windowSize: root.windowSize + frontLayerComponent: root.frontLayerComponent + onPickedChanged: { + root.pickedChanged(pressed) + } + } +} \ No newline at end of file diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index 750c0ed5d6..27a341d040 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -7,14 +7,15 @@ import Qt3D.Logic 2.0 Entity { id: root - property real gizmoScale: 0.20 + property real gizmoScale: 0.15 property Camera camera property var windowSize + property var frontLayerComponent readonly property Transform objectTransform : Transform {} signal pickedChanged(bool pressed) - components: [gizmoDisplayTransform, mouseHandler] + components: [gizmoDisplayTransform, mouseHandler, frontLayerComponent] /***** ENUMS *****/ @@ -194,7 +195,7 @@ Entity { Entity { id: centerSphereEntity - components: [centerSphereMesh, centerSphereMaterial] + components: [centerSphereMesh, centerSphereMaterial, frontLayerComponent] SphereMesh { id: centerSphereMesh @@ -238,7 +239,7 @@ Entity { Entity { id: axisCylinder - components: [cylinderMesh, cylinderTransform, scaleMaterial] + components: [cylinderMesh, cylinderTransform, scaleMaterial, frontLayerComponent] CylinderMesh { id: cylinderMesh @@ -275,7 +276,7 @@ Entity { Entity { id: axisScaleBox - components: [cubeScaleMesh, cubeScaleTransform, scaleMaterial, scalePicker] + components: [cubeScaleMesh, cubeScaleTransform, scaleMaterial, scalePicker, frontLayerComponent] CuboidMesh { id: cubeScaleMesh @@ -334,7 +335,7 @@ Entity { // POSITION ENTITY Entity { id: positionEntity - components: [coneMesh, coneTransform, positionMaterial, positionPicker] + components: [coneMesh, coneTransform, positionMaterial, positionPicker, frontLayerComponent] ConeMesh { id: coneMesh @@ -394,7 +395,7 @@ Entity { // ROTATION ENTITY Entity { id: rotationEntity - components: [torusMesh, torusTransform, rotationMaterial, rotationPicker] + components: [torusMesh, torusTransform, rotationMaterial, rotationPicker, frontLayerComponent] TorusMesh { id: torusMesh diff --git a/meshroom/ui/qml/Viewer3D/Viewer3D.qml b/meshroom/ui/qml/Viewer3D/Viewer3D.qml index 8451fda7bc..7bbcf1cfc1 100644 --- a/meshroom/ui/qml/Viewer3D/Viewer3D.qml +++ b/meshroom/ui/qml/Viewer3D/Viewer3D.qml @@ -207,6 +207,17 @@ FocusScope { ] } } + LayerFilter { + filterMode: LayerFilter.DiscardAnyMatchingLayers + layers: Layer {id: drawOnFront} + } + LayerFilter { + filterMode: LayerFilter.AcceptAnyMatchingLayers + layers: [drawOnFront] + RenderStateSet { + renderStates: DepthTest { depthFunction: DepthTest.Equal } + } + } } } } From f9d57cadff0f43a5478bc3590d7d87369644f2fc Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Thu, 9 Jul 2020 15:21:28 +0200 Subject: [PATCH 13/51] [ui] Viewer3D: TransformGizmo - add reset transform option - Right clicking on the gizmo allows to reset the specific transformation. --- .../qml/Viewer3D/DefaultCameraController.qml | 4 +- meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml | 2 + meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 55 +++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/DefaultCameraController.qml b/meshroom/ui/qml/Viewer3D/DefaultCameraController.qml index 0dc4f8488e..5f402a0c3c 100644 --- a/meshroom/ui/qml/Viewer3D/DefaultCameraController.qml +++ b/meshroom/ui/qml/Viewer3D/DefaultCameraController.qml @@ -46,7 +46,7 @@ Entity { property point lastPosition property point currentPosition property bool hasMoved - sourceDevice: mouseSourceDevice + sourceDevice: loseMouseFocus ? null : mouseSourceDevice onPressed: { _pressed = true; currentPosition.x = lastPosition.x = mouse.x; @@ -168,8 +168,6 @@ Entity { components: [ FrameAction { onTriggered: { - if(loseMouseFocus) return - if(panning) { // translate var d = (root.camera.viewCenter.minus(root.camera.position)).length() * 0.03; var tx = axisMX.value * root.translateSpeed * d; diff --git a/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml b/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml index 3a6eaa5d44..b11ac3c070 100644 --- a/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml @@ -9,6 +9,7 @@ Entity { id: root property DefaultCameraController cameraController property Layer frontLayerComponent + property var window readonly property Camera camera : cameraController.camera readonly property var windowSize: cameraController.windowSize @@ -25,6 +26,7 @@ Entity { camera: root.camera windowSize: root.windowSize frontLayerComponent: root.frontLayerComponent + window: root.window onPickedChanged: { root.pickedChanged(pressed) } diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index 27a341d040..22bbe0e648 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -4,6 +4,7 @@ import Qt3D.Input 2.0 import Qt3D.Extras 2.10 import QtQuick 2.9 import Qt3D.Logic 2.0 +import QtQuick.Controls 2.3 Entity { id: root @@ -11,6 +12,7 @@ Entity { property Camera camera property var windowSize property var frontLayerComponent + property var window readonly property Transform objectTransform : Transform {} signal pickedChanged(bool pressed) @@ -170,6 +172,28 @@ Entity { localScale(objectTransform, initialDecomposedModelMat, scaleVec) // Update object matrix } + function resetTranslation() { + gizmoDisplayTransform.translation = Qt.vector3d(0,0,0) + objectTransform.translation = Qt.vector3d(0,0,0) + } + + function resetRotation() { + gizmoDisplayTransform.rotation = Qt.quaternion(1,0,0,0) + objectTransform.rotation = Qt.quaternion(1,0,0,0) + } + + function resetScale() { + objectTransform.scale3D = Qt.vector3d(1,1,1) + } + + function resetATransformType(transformType) { + switch(transformType) { + case TransformGizmo.Type.POSITION: resetTranslation(); break + case TransformGizmo.Type.ROTATION: resetRotation(); break + case TransformGizmo.Type.SCALE: resetScale(); break + } + } + /***** DEVICES *****/ MouseDevice { id: mouseSourceDevice } @@ -178,10 +202,38 @@ Entity { id: mouseHandler sourceDevice: mouseSourceDevice property point currentPosition + property var objectPicker: null + onPositionChanged: { currentPosition.x = mouse.x currentPosition.y = mouse.y } + onReleased: { + if(objectPicker && mouse.button == Qt.RightButton) { + resetMenu.updateTypeBeforePopup(objectPicker.gizmoType) + resetMenu.popup(window) + } + } + } + + Menu { + id: resetMenu + property int transformType + property string transformTypeToDisplay + + function updateTypeBeforePopup(type) { + resetMenu.transformType = type + switch(type) { + case TransformGizmo.Type.POSITION: resetMenu.transformTypeToDisplay = "position"; break + case TransformGizmo.Type.ROTATION: resetMenu.transformTypeToDisplay = "rotation"; break + case TransformGizmo.Type.SCALE: resetMenu.transformTypeToDisplay = "scale"; break + } + } + + MenuItem { + text: `Reset ${resetMenu.transformTypeToDisplay}?` + onTriggered: resetATransformType(resetMenu.transformType) + } } /***** GIZMO'S BASIC COMPONENTS *****/ @@ -327,6 +379,7 @@ Entity { onPickedChanged: { this.decomposedObjectModelMat = decomposeModelMatrixFromTransformations(objectTransform.translation, objectTransform.rotation, objectTransform.scale3D) // Save the current transformations root.pickedChanged(picker.isPressed) // Used to prevent camera transformations + mouseHandler.objectPicker = picker.isPressed ? picker : null // Set the objectPicker of the mouseHandler transformHandler.objectPicker = picker.isPressed ? picker : null // Pass the picker to the global FrameAction } } @@ -387,6 +440,7 @@ Entity { onPickedChanged: { this.decomposedObjectModelMat = decomposeModelMatrixFromTransformations(objectTransform.translation, objectTransform.rotation, objectTransform.scale3D) // Save the current transformations root.pickedChanged(picker.isPressed) // Used to prevent camera transformations + mouseHandler.objectPicker = picker.isPressed ? picker : null // Set the objectPicker of the mouseHandler transformHandler.objectPicker = picker.isPressed ? picker : null // Pass the picker to the global FrameAction } } @@ -433,6 +487,7 @@ Entity { onPickedChanged: { this.decomposedObjectModelMat = decomposeModelMatrixFromTransformations(objectTransform.translation, objectTransform.rotation, objectTransform.scale3D) // Save the current transformations root.pickedChanged(picker.isPressed) // Used to prevent camera transformations + mouseHandler.objectPicker = picker.isPressed ? picker : null // Set the objectPicker of the mouseHandler transformHandler.objectPicker = picker.isPressed ? picker : null // Pass the picker to the global FrameAction } } From b234a766a9bc4abb1d66dca2b7179d6cde84c30c Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Thu, 9 Jul 2020 18:27:26 +0200 Subject: [PATCH 14/51] [ui] Viewer3D: TransformGizmo - removing FrameAction - Moving the mouse's dependent events inside the MouseHandler and removing the FrameAction. - MouseHandler is only enabled when the gizmo is selected. There is no infinite loop waiting for an event. --- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 180 ++++++++---------- .../ui/qml/Viewer3D/TransformGizmoPicker.qml | 7 +- 2 files changed, 86 insertions(+), 101 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index 22bbe0e648..feb3347afd 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -200,16 +200,91 @@ Entity { MouseHandler { id: mouseHandler - sourceDevice: mouseSourceDevice - property point currentPosition + sourceDevice: enabled ? mouseSourceDevice : null property var objectPicker: null + property bool enabled: false onPositionChanged: { - currentPosition.x = mouse.x - currentPosition.y = mouse.y + if (objectPicker) { + // Get the selected axis + let pickedAxis + switch(objectPicker.gizmoAxis) { + case TransformGizmo.Axis.X: pickedAxis = Qt.vector3d(1,0,0); break + case TransformGizmo.Axis.Y: pickedAxis = Qt.vector3d(0,1,0); break + case TransformGizmo.Axis.Z: pickedAxis = Qt.vector3d(0,0,1); break + } + + switch(objectPicker.gizmoType) { + case TransformGizmo.Type.POSITION: { + const sensibility = 0.02 + + // Compute the current vector PickedPoint -> CurrentMousePoint + const pickedPosition = objectPicker.screenPoint + const mouseVector = Qt.vector2d(mouse.x - pickedPosition.x, -(mouse.y - pickedPosition.y)) + + // Transform the positive picked axis vector from World Coord to Screen Coord + const gizmoLocalPointOnAxis = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 1)) + const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) + const screenPoint2D = pointFromWorldToScreen(gizmoLocalPointOnAxis, camera, windowSize) + const screenCenter2D = pointFromWorldToScreen(gizmoCenterPoint, camera, windowSize) + const screenAxisVector = Qt.vector2d(screenPoint2D.x - screenCenter2D.x, -(screenPoint2D.y - screenCenter2D.y)) + + // Get the cosinus of the angle from the screenAxisVector to the mouseVector + const cosAngle = screenAxisVector.dotProduct(mouseVector) / (screenAxisVector.length() * mouseVector.length()) + const offset = cosAngle * mouseVector.length() * sensibility + + // If the mouse is not at the same spot as the pickedPoint, we do translation + if (offset) doTranslation(objectPicker.decomposedObjectModelMat, pickedAxis.times(offset)) // Do a translation from the initial Object Model Matrix when we picked the gizmo + + return + } + + case TransformGizmo.Type.ROTATION: { + // Get Screen Coordinates of the gizmo center + const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) + const screenCenter2D = pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize) + + // Get the vector screenCenter2D -> PickedPoint + const originalVector = Qt.vector2d(objectPicker.screenPoint.x - screenCenter2D.x, -(objectPicker.screenPoint.y - screenCenter2D.y)) + + // Compute the current vector screenCenter2D -> CurrentMousePoint + const mouseVector = Qt.vector2d(mouse.x - screenCenter2D.x, -(mouse.y - screenCenter2D.y)) + + // Get the angle from the originalVector to the mouseVector + const angle = Math.atan2(-originalVector.y*mouseVector.x + originalVector.x*mouseVector.y, originalVector.x*mouseVector.x + originalVector.y*mouseVector.y) * 180 / Math.PI + + // Get the orientation of the gizmo in function of the camera + const gizmoLocalAxisVector = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 0)) + const gizmoToCameraVector = camera.position.toVector4d().minus(gizmoCenterPoint) + const orientation = gizmoLocalAxisVector.dotProduct(gizmoToCameraVector) > 0 ? 1 : -1 + + if (angle !== 0) doRotation(objectPicker.decomposedObjectModelMat, pickedAxis, angle*orientation) + return + } + + case TransformGizmo.Type.SCALE: { + const sensibility = 0.05 + + // Get Screen Coordinates of the gizmo center + const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) + const screenCenter2D = pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize) + + // Compute the scale unit + const scaleUnit = screenCenter2D.minus(Qt.vector2d(objectPicker.screenPoint.x, objectPicker.screenPoint.y)).length() + + // Compute the current vector screenCenter2D -> CurrentMousePoint + const mouseVector = Qt.vector2d(mouse.x - screenCenter2D.x, -(mouse.y - screenCenter2D.y)) + let offset = (mouseVector.length() - scaleUnit) * sensibility + offset = (offset < 0) ? offset * 3 : offset // Used to make it more sensible when we want to reduce the scale (because the action field is shorter) + + if (offset) doScale(objectPicker.decomposedObjectModelMat, pickedAxis.times(offset)) + return + } + } + } } onReleased: { - if(objectPicker && mouse.button == Qt.RightButton) { + if(objectPicker && mouse.button === Qt.RightButton) { resetMenu.updateTypeBeforePopup(objectPicker.gizmoType) resetMenu.popup(window) } @@ -240,9 +315,7 @@ Entity { Transform { id: gizmoDisplayTransform - scale: { - return root.gizmoScale * (camera.position.minus(gizmoDisplayTransform.translation)).length() - } + scale: root.gizmoScale * (camera.position.minus(gizmoDisplayTransform.translation)).length() } Entity { @@ -379,8 +452,6 @@ Entity { onPickedChanged: { this.decomposedObjectModelMat = decomposeModelMatrixFromTransformations(objectTransform.translation, objectTransform.rotation, objectTransform.scale3D) // Save the current transformations root.pickedChanged(picker.isPressed) // Used to prevent camera transformations - mouseHandler.objectPicker = picker.isPressed ? picker : null // Set the objectPicker of the mouseHandler - transformHandler.objectPicker = picker.isPressed ? picker : null // Pass the picker to the global FrameAction } } } @@ -440,8 +511,6 @@ Entity { onPickedChanged: { this.decomposedObjectModelMat = decomposeModelMatrixFromTransformations(objectTransform.translation, objectTransform.rotation, objectTransform.scale3D) // Save the current transformations root.pickedChanged(picker.isPressed) // Used to prevent camera transformations - mouseHandler.objectPicker = picker.isPressed ? picker : null // Set the objectPicker of the mouseHandler - transformHandler.objectPicker = picker.isPressed ? picker : null // Pass the picker to the global FrameAction } } } @@ -487,93 +556,6 @@ Entity { onPickedChanged: { this.decomposedObjectModelMat = decomposeModelMatrixFromTransformations(objectTransform.translation, objectTransform.rotation, objectTransform.scale3D) // Save the current transformations root.pickedChanged(picker.isPressed) // Used to prevent camera transformations - mouseHandler.objectPicker = picker.isPressed ? picker : null // Set the objectPicker of the mouseHandler - transformHandler.objectPicker = picker.isPressed ? picker : null // Pass the picker to the global FrameAction - } - } - } - } - } - - FrameAction { - id: transformHandler - property var objectPicker: null - - onTriggered: { - if (objectPicker) { - - let pickedAxis - switch(objectPicker.gizmoAxis) { - case TransformGizmo.Axis.X: pickedAxis = Qt.vector3d(1,0,0); break - case TransformGizmo.Axis.Y: pickedAxis = Qt.vector3d(0,1,0); break - case TransformGizmo.Axis.Z: pickedAxis = Qt.vector3d(0,0,1); break - } - - switch(objectPicker.gizmoType) { - case TransformGizmo.Type.POSITION: { - const sensibility = 0.02 - - // Compute the current vector PickedPoint -> CurrentMousePoint - const pickedPosition = objectPicker.screenPoint - const mouseVector = Qt.vector2d(mouseHandler.currentPosition.x - pickedPosition.x, -(mouseHandler.currentPosition.y - pickedPosition.y)) - - // Transform the positive picked axis vector from World Coord to Screen Coord - const gizmoLocalPointOnAxis = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 1)) - const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) - const screenPoint2D = pointFromWorldToScreen(gizmoLocalPointOnAxis, camera, windowSize) - const screenCenter2D = pointFromWorldToScreen(gizmoCenterPoint, camera, windowSize) - const screenAxisVector = Qt.vector2d(screenPoint2D.x - screenCenter2D.x, -(screenPoint2D.y - screenCenter2D.y)) - - // Get the cosinus of the angle from the screenAxisVector to the mouseVector - const cosAngle = screenAxisVector.dotProduct(mouseVector) / (screenAxisVector.length() * mouseVector.length()) - const offset = cosAngle * mouseVector.length() * sensibility - - // If the mouse is not at the same spot as the pickedPoint, we do translation - if (offset) doTranslation(objectPicker.decomposedObjectModelMat, pickedAxis.times(offset)) // Do a translation from the initial Object Model Matrix when we picked the gizmo - - return - } - - case TransformGizmo.Type.ROTATION: { - // Get Screen Coordinates of the gizmo center - const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) - const screenCenter2D = pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize) - - // Get the vector screenCenter2D -> PickedPoint - const originalVector = Qt.vector2d(objectPicker.screenPoint.x - screenCenter2D.x, -(objectPicker.screenPoint.y - screenCenter2D.y)) - - // Compute the current vector screenCenter2D -> CurrentMousePoint - const mouseVector = Qt.vector2d(mouseHandler.currentPosition.x - screenCenter2D.x, -(mouseHandler.currentPosition.y - screenCenter2D.y)) - - // Get the angle from the originalVector to the mouseVector - const angle = Math.atan2(-originalVector.y*mouseVector.x + originalVector.x*mouseVector.y, originalVector.x*mouseVector.x + originalVector.y*mouseVector.y) * 180 / Math.PI - - // Get the orientation of the gizmo in function of the camera - const gizmoLocalAxisVector = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 0)) - const gizmoToCameraVector = camera.position.toVector4d().minus(gizmoCenterPoint) - const orientation = gizmoLocalAxisVector.dotProduct(gizmoToCameraVector) > 0 ? 1 : -1 - - if (angle !== 0) doRotation(objectPicker.decomposedObjectModelMat, pickedAxis, angle*orientation) - return - } - - case TransformGizmo.Type.SCALE: { - const sensibility = 0.05 - - // Get Screen Coordinates of the gizmo center - const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) - const screenCenter2D = pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize) - - // Compute the scale unit - const scaleUnit = screenCenter2D.minus(Qt.vector2d(objectPicker.screenPoint.x, objectPicker.screenPoint.y)).length() - - // Compute the current vector screenCenter2D -> CurrentMousePoint - const mouseVector = Qt.vector2d(mouseHandler.currentPosition.x - screenCenter2D.x, -(mouseHandler.currentPosition.y - screenCenter2D.y)) - let offset = (mouseVector.length() - scaleUnit) * sensibility - offset = (offset < 0) ? offset * 3 : offset // Used to make it more sensible when we want to reduce the scale (because the action field is shorter) - - if (offset) doScale(objectPicker.decomposedObjectModelMat, pickedAxis.times(offset)) - return } } } diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml index f99cfb3a4f..d8cd3c24e8 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml @@ -21,10 +21,11 @@ ObjectPicker { hoverEnabled: true onPressed: { + mouseController.enabled = true + mouseController.objectPicker = this root.isPressed = true - pickedChanged(this) screenPoint = pick.position - mouseController.currentPosition = pick.position + pickedChanged(this) } onEntered: { gizmoMaterial.ambient = "white" @@ -35,6 +36,8 @@ ObjectPicker { onReleased: { gizmoMaterial.ambient = gizmoBaseColor root.isPressed = false + mouseController.objectPicker = null + mouseController.enabled = false pickedChanged(this) } } \ No newline at end of file From cb306ffc7d53a3a3e96cad1e0e134eb5f803a3b8 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Fri, 10 Jul 2020 11:32:25 +0200 Subject: [PATCH 15/51] [ui] Viewer3D: DefaultCameraController - removing FrameAction - Apply the same control handling as the TransformGizmo. --- .../qml/Viewer3D/DefaultCameraController.qml | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/DefaultCameraController.qml b/meshroom/ui/qml/Viewer3D/DefaultCameraController.qml index 5f402a0c3c..b922a1cd9c 100644 --- a/meshroom/ui/qml/Viewer3D/DefaultCameraController.qml +++ b/meshroom/ui/qml/Viewer3D/DefaultCameraController.qml @@ -62,6 +62,30 @@ Entity { onPositionChanged: { currentPosition.x = mouse.x; currentPosition.y = mouse.y; + + const dt = 0.02 + + if(panning) { // translate + var d = (root.camera.viewCenter.minus(root.camera.position)).length() * 0.03; + var tx = axisMX.value * root.translateSpeed * d; + var ty = axisMY.value * root.translateSpeed * d; + mouseHandler.hasMoved = true; + root.camera.translate(Qt.vector3d(-tx, -ty, 0).times(dt)); + return; + } + if(moving){ // trackball rotation + trackball.rotate(mouseHandler.lastPosition, mouseHandler.currentPosition, dt); + mouseHandler.lastPosition = mouseHandler.currentPosition; + mouseHandler.hasMoved = true; + return; + } + if(zooming) { // zoom with alt + RMD + var d = (root.camera.viewCenter.minus(root.camera.position)).length() * 0.1; + var tz = axisMX.value * root.translateSpeed * d; + mouseHandler.hasMoved = true; + root.camera.translate(Qt.vector3d(0, 0, tz).times(dt), Camera.DontTranslateViewCenter) + return; + } } onDoubleClicked: mouseDoubleClicked(mouse) onWheel: { @@ -164,32 +188,4 @@ Entity { } ] } - - components: [ - FrameAction { - onTriggered: { - if(panning) { // translate - var d = (root.camera.viewCenter.minus(root.camera.position)).length() * 0.03; - var tx = axisMX.value * root.translateSpeed * d; - var ty = axisMY.value * root.translateSpeed * d; - mouseHandler.hasMoved = true; - root.camera.translate(Qt.vector3d(-tx, -ty, 0).times(dt)); - return; - } - if(moving){ // trackball rotation - trackball.rotate(mouseHandler.lastPosition, mouseHandler.currentPosition, dt); - mouseHandler.lastPosition = mouseHandler.currentPosition; - mouseHandler.hasMoved = true; - return; - } - if(zooming) { // zoom with alt + RMD - var d = (root.camera.viewCenter.minus(root.camera.position)).length() * 0.1; - var tz = axisMX.value * root.translateSpeed * d; - mouseHandler.hasMoved = true; - root.camera.translate(Qt.vector3d(0, 0, tz).times(dt), Camera.DontTranslateViewCenter) - return; - } - } - } - ] } From da765a5f983aadfa35d0154dd35439132a22c3df Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 13 Jul 2020 16:50:32 +0200 Subject: [PATCH 16/51] [ui] Viewer3D: TransformGizmo - moving transform operations to Python - Moving transform operations from QML to Python to get access to more Qt functionalities and to make a better separation of logic/display. --- meshroom/ui/app.py | 3 +- meshroom/ui/components/__init__.py | 3 +- meshroom/ui/components/scene3D.py | 114 +++++++++++- .../ui/qml/Utils/Transformations3DHelper.qml | 6 + meshroom/ui/qml/Utils/qmldir | 1 + meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 162 +++--------------- .../ui/qml/Viewer3D/TransformGizmoPicker.qml | 2 +- 7 files changed, 146 insertions(+), 145 deletions(-) create mode 100644 meshroom/ui/qml/Utils/Transformations3DHelper.qml diff --git a/meshroom/ui/app.py b/meshroom/ui/app.py index 5abe029c53..56502e7b8d 100644 --- a/meshroom/ui/app.py +++ b/meshroom/ui/app.py @@ -13,7 +13,7 @@ from meshroom.ui import components from meshroom.ui.components.clipboard import ClipboardHelper from meshroom.ui.components.filepath import FilepathHelper -from meshroom.ui.components.scene3D import Scene3DHelper +from meshroom.ui.components.scene3D import Scene3DHelper, Transformations3DHelper from meshroom.ui.palette import PaletteManager from meshroom.ui.reconstruction import Reconstruction from meshroom.ui.utils import QmlInstantEngine @@ -127,6 +127,7 @@ def __init__(self, args): # => expose them as context properties instead self.engine.rootContext().setContextProperty("Filepath", FilepathHelper(parent=self)) self.engine.rootContext().setContextProperty("Scene3DHelper", Scene3DHelper(parent=self)) + self.engine.rootContext().setContextProperty("Transformations3DHelper", Transformations3DHelper(parent=self)) self.engine.rootContext().setContextProperty("Clipboard", ClipboardHelper(parent=self)) # additional context properties diff --git a/meshroom/ui/components/__init__.py b/meshroom/ui/components/__init__.py index df9a5bb5ac..260acb0408 100755 --- a/meshroom/ui/components/__init__.py +++ b/meshroom/ui/components/__init__.py @@ -4,12 +4,13 @@ def registerTypes(): from meshroom.ui.components.clipboard import ClipboardHelper from meshroom.ui.components.edge import EdgeMouseArea from meshroom.ui.components.filepath import FilepathHelper - from meshroom.ui.components.scene3D import Scene3DHelper, TrackballController + from meshroom.ui.components.scene3D import Scene3DHelper, TrackballController, Transformations3DHelper from meshroom.ui.components.csvData import CsvData qmlRegisterType(EdgeMouseArea, "GraphEditor", 1, 0, "EdgeMouseArea") qmlRegisterType(ClipboardHelper, "Meshroom.Helpers", 1, 0, "ClipboardHelper") # TODO: uncreatable qmlRegisterType(FilepathHelper, "Meshroom.Helpers", 1, 0, "FilepathHelper") # TODO: uncreatable qmlRegisterType(Scene3DHelper, "Meshroom.Helpers", 1, 0, "Scene3DHelper") # TODO: uncreatable + qmlRegisterType(Transformations3DHelper, "Meshroom.Helpers", 1, 0, "Transformations3DHelper") # TODO: uncreatable qmlRegisterType(TrackballController, "Meshroom.Helpers", 1, 0, "TrackballController") qmlRegisterType(CsvData, "DataObjects", 1, 0, "CsvData") diff --git a/meshroom/ui/components/scene3D.py b/meshroom/ui/components/scene3D.py index bf5b44c5a9..79e2871af7 100644 --- a/meshroom/ui/components/scene3D.py +++ b/meshroom/ui/components/scene3D.py @@ -3,7 +3,7 @@ from PySide2.QtCore import QObject, Slot, QSize, Signal, QPointF from PySide2.Qt3DCore import Qt3DCore from PySide2.Qt3DRender import Qt3DRender -from PySide2.QtGui import QVector3D, QQuaternion, QVector2D +from PySide2.QtGui import QVector3D, QQuaternion, QVector2D, QVector4D, QMatrix4x4 from meshroom.ui.utils import makeProperty @@ -103,3 +103,115 @@ def rotate(self, lastPosition, currentPosition, dt): trackballSize = makeProperty(float, '_trackballSize', trackballSizeChanged) rotationSpeedChanged = Signal() rotationSpeed = makeProperty(float, '_rotationSpeed', rotationSpeedChanged) + + +class Transformations3DHelper(QObject): + + #---------- Exposed to QML ----------# + + @Slot(QVector4D, Qt3DRender.QCamera, QSize, result=QVector2D) + def pointFromWorldToScreen(self, point, camera, windowSize): + """ Compute the Screen point corresponding to a World Point. """ + # Transform the point from World Coord to Normalized Device Coord + viewMatrix = camera.transform().matrix().inverted() + projectedPoint = (camera.projectionMatrix() * viewMatrix[0]).map(point) + projectedPoint2D = QVector2D( + projectedPoint.x()/projectedPoint.w(), + projectedPoint.y()/projectedPoint.w() + ) + + # Transform the point from Normalized Device Coord to Screen Coord + screenPoint2D = QVector2D( + int((projectedPoint2D.x() + 1) * windowSize.width() / 2), + int((projectedPoint2D.y() - 1) * windowSize.height() / -2) + ) + + return screenPoint2D + + @Slot(Qt3DCore.QTransform, QMatrix4x4, QMatrix4x4, QMatrix4x4, QVector3D) + def relativeLocalTranslate(self, transformQtInstance, initialPosMat, initialRotMat, initialScaleMat, translateVec): + """ Translate the QTransform in its local space relatively to an initial state. """ + # Compute the translation transformation matrix + translationMat = QMatrix4x4() + translationMat.translate(translateVec) + + # Compute the new model matrix (POSITION * ROTATION * TRANSLATE * SCALE) and set it to the Transform + mat = initialPosMat * initialRotMat * translationMat * initialScaleMat + transformQtInstance.setMatrix(mat) + + @Slot(Qt3DCore.QTransform, QMatrix4x4, QQuaternion, QMatrix4x4, QVector3D, int) + def relativeLocalRotate(self, transformQtInstance, initialPosMat, initialRotQuat, initialScaleMat, axis, degree): + """ Rotate the QTransform in its local space relatively to an initial state. """ + # Compute the transformation quaternion from axis and angle in degrees + transformQuat = QQuaternion.fromAxisAndAngle(axis, degree) + + # Compute the new rotation quaternion and then calculate the matrix + newRotQuat = initialRotQuat * transformQuat # Order is important + newRotationMat = self.quaternionToRotationMatrix(newRotQuat) + + # Compute the new model matrix (POSITION * NEW_COMPUTED_ROTATION * SCALE) and set it to the Transform + mat = initialPosMat * newRotationMat * initialScaleMat + transformQtInstance.setMatrix(mat) + + @Slot(Qt3DCore.QTransform, QMatrix4x4, QMatrix4x4, QMatrix4x4, QVector3D) + def relativeLocalScale(self, transformQtInstance, initialPosMat, initialRotMat, initialScaleMat, scaleVec): + """ Scale the QTransform in its local space relatively to an initial state. """ + # Make a copy of the scale matrix (otherwise, it is a reference and it does not work as expected) + scaleMat = self.copyMatrix4x4(initialScaleMat) + + # Update the scale matrix copy (X then Y then Z) with the scaleVec values + scaleVecTuple = scaleVec.toTuple() + for i in range(3): + currentRow = list(scaleMat.row(i).toTuple()) # QVector3D does not implement [] operator or easy way to access value by index so this little hack is required + value = currentRow[i] + scaleVecTuple[i] + value = value if value >= 0 else -value # Make sure to have only positive scale (because negative scale can make issues with matrix decomposition) + currentRow[i] = value + + scaleMat.setRow(i, QVector3D(currentRow[0], currentRow[1], currentRow[2])) # Apply the new row to the scale matrix + + # Compute the new model matrix (POSITION * ROTATION * SCALE) and set it to the Transform + mat = initialPosMat * initialRotMat * scaleMat + transformQtInstance.setMatrix(mat) + + @Slot(QMatrix4x4, result="QVariant") + def modelMatrixToMatrices(self, modelMat): + """ Decompose a model matrix into individual matrices. """ + decomposition = self.decomposeModelMatrix(modelMat) + + posMat = QMatrix4x4() + posMat.translate(decomposition.get("translation")) + + rotMat = self.quaternionToRotationMatrix(decomposition.get("quaternion")) + + scaleMat = QMatrix4x4() + scaleMat.scale(decomposition.get("scale")) + + return { "position": posMat, "rotation": rotMat, "scale": scaleMat, "quaternion": decomposition.get("quaternion") } + + + #---------- "Private" Methods ----------# + + def copyMatrix4x4(self, mat): + """ Make a deep copy of a QMatrix4x4. """ + newMat = QMatrix4x4() + for i in range(4): + newMat.setRow(i, mat.row(i)) + return newMat + + def decomposeModelMatrix(self, modelMat): + """ Decompose a model matrix into individual component. """ + translation = modelMat.column(3).toVector3D() + quaternion = QQuaternion.fromDirection(modelMat.column(2).toVector3D(), modelMat.column(1).toVector3D()) + scale = QVector3D(modelMat.column(0).length(), modelMat.column(1).length(), modelMat.column(2).length()) + + return { "translation": translation, "quaternion": quaternion, "scale": scale } + + def quaternionToRotationMatrix(self, q): + """ Return a rotation matrix from a quaternion. """ + rotMat3x3 = q.toRotationMatrix() + return QMatrix4x4( + rotMat3x3(0,0), rotMat3x3(0,1), rotMat3x3(0,2), 0, + rotMat3x3(1,0), rotMat3x3(1,1), rotMat3x3(1,2), 0, + rotMat3x3(2,0), rotMat3x3(2,1), rotMat3x3(2,2), 0, + 0, 0, 0, 1 + ) diff --git a/meshroom/ui/qml/Utils/Transformations3DHelper.qml b/meshroom/ui/qml/Utils/Transformations3DHelper.qml new file mode 100644 index 0000000000..16e90ea661 --- /dev/null +++ b/meshroom/ui/qml/Utils/Transformations3DHelper.qml @@ -0,0 +1,6 @@ +pragma Singleton +import Meshroom.Helpers 1.0 + +Transformations3DHelper { + +} \ No newline at end of file diff --git a/meshroom/ui/qml/Utils/qmldir b/meshroom/ui/qml/Utils/qmldir index c8767f30e7..5f0e8a4b43 100644 --- a/meshroom/ui/qml/Utils/qmldir +++ b/meshroom/ui/qml/Utils/qmldir @@ -8,3 +8,4 @@ Format 1.0 format.js # singleton Clipboard 1.0 Clipboard.qml # singleton Filepath 1.0 Filepath.qml # singleton Scene3DHelper 1.0 Scene3DHelper.qml +# singleton Transformations3DHelper 1.0 Transformations3DHelper.qml diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index feb3347afd..b22d2e8e0d 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -5,6 +5,7 @@ import Qt3D.Extras 2.10 import QtQuick 2.9 import Qt3D.Logic 2.0 import QtQuick.Controls 2.3 +import Utils 1.0 Entity { id: root @@ -34,142 +35,20 @@ Entity { SCALE } - /***** QUATERNIONS *****/ + /***** TRANSFORMATIONS (using local vars) *****/ - function multiplyQuaternion(q1, q2) { - return Qt.quaternion( - q1.scalar * q2.scalar - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z, - q1.scalar * q2.x + q1.x * q2.scalar + q1.y * q2.z - q1.z * q2.y, - q1.scalar * q2.y + q1.y * q2.scalar + q1.z * q2.x - q1.x * q2.z, - q1.scalar * q2.z + q1.z * q2.scalar + q1.x * q2.y - q1.y * q2.x - ) + function doRelativeTranslation(initialModelMatrix, translateVec) { + Transformations3DHelper.relativeLocalTranslate(gizmoDisplayTransform, initialModelMatrix.position, initialModelMatrix.rotation, initialModelMatrix.scale, translateVec) // Update gizmo matrix + Transformations3DHelper.relativeLocalTranslate(objectTransform, initialModelMatrix.position, initialModelMatrix.rotation, initialModelMatrix.scale, translateVec) // Update object matrix } - function dotQuaternion(q) { - return (((q.x * q.x) + (q.y * q.y)) + (q.z * q.z)) + (q.scalar * q.scalar) + function doRelativeRotation(initialModelMatrix, axis, degree) { + Transformations3DHelper.relativeLocalRotate(gizmoDisplayTransform, initialModelMatrix.position, initialModelMatrix.quaternion, initialModelMatrix.scale, axis, degree) // Update gizmo matrix + Transformations3DHelper.relativeLocalRotate(objectTransform, initialModelMatrix.position, initialModelMatrix.quaternion, initialModelMatrix.scale, axis, degree) // Update object matrix } - function normalizeQuaternion(q) { - const dot = dotQuaternion(q) - const inv = 1.0 / (Math.sqrt(dot)) - return Qt.quaternion(q.scalar * inv, q.x * inv, q.y * inv, q.z * inv) - } - - function quaternionFromAxisAngle(vec3, degree) { - const rad = degree * Math.PI/180 - const factor = Math.sin(rad/2) // Used for the quaternion computation - - // Compute the quaternion - const x = vec3.x * factor - const y = vec3.y * factor - const z = vec3.z * factor - const w = Math.cos(rad/2) - - return normalizeQuaternion(Qt.quaternion(w, x, y, z)) - } - - function quaternionToRotationMatrix(q) { - const w = q.scalar - const x = q.x - const y = q.y - const z = q.z - - return Qt.matrix4x4( - w*w + x*x - y*y - z*z, 2*(x*y - w*z), 2*(w*y + x*z), 0, - 2*(x*y + w*z), w*w - x*x + y*y - z*z, 2*(y*z - w*x), 0, - 2*(x*z - w*y), 2*(w*x + y*z), w*w - x*x - y*y + z*z, 0, - 0, 0, 0, 1 - ) - } - - /***** GENERIC MATRIX TRANSFORMATIONS *****/ - - function pointFromWorldToScreen(point, camera, windowSize) { - // Transform the point from World Coord to Normalized Device Coord - const viewMatrix = camera.transform.matrix.inverted() - const projectedPoint = camera.projectionMatrix.times(viewMatrix.times(point)) - const projectedPoint2D = Qt.vector2d(projectedPoint.x/projectedPoint.w, projectedPoint.y/projectedPoint.w) - - // Transform the point from Normalized Device Coord to Screen Coord - const screenPoint2D = Qt.vector2d( - parseInt((projectedPoint2D.x + 1) * windowSize.width / 2), - parseInt((projectedPoint2D.y - 1) * windowSize.height / -2) - ) - - return screenPoint2D - } - - function decomposeModelMatrixFromTransformations(translation, rotation, scale3D) { - const posMat = Qt.matrix4x4() - posMat.translate(translation) - const rotMat = quaternionToRotationMatrix(rotation) - const scaleMat = Qt.matrix4x4() - scaleMat.scale(scale3D) - - const rotQuat = Qt.quaternion(rotation.scalar, rotation.x, rotation.y, rotation.z) - - return { positionMat: posMat, rotationMat: rotMat, scaleMat: scaleMat, quaternion: rotQuat } - } - - function decomposeModelMatrixFromTransform(transformQtInstance) { - return decomposeModelMatrixFromTransformations(transformQtInstance.translation, transformQtInstance.rotation, transformQtInstance.scale3D) - } - - function localTranslate(transformQtInstance, initialDecomposedModelMat, translateVec) { - // Compute the translation transformation matrix - const translationMat = Qt.matrix4x4() - translationMat.translate(translateVec) - - // Compute the new model matrix (POSITION * ROTATION * TRANSLATE * SCALE) and set it to the Transform - const mat = initialDecomposedModelMat.positionMat.times(initialDecomposedModelMat.rotationMat.times(translationMat.times(initialDecomposedModelMat.scaleMat))) - transformQtInstance.setMatrix(mat) - } - - function localRotate(transformQtInstance, initialDecomposedModelMat, axis, degree) { - // Compute the transformation quaternion from axis and angle in degrees - const transformQuat = quaternionFromAxisAngle(axis, degree) - - // Get rotation quaternion of the current model matrix - const initRotQuat = initialDecomposedModelMat.quaternion - // Compute the new rotation quaternion and then calculate the matrix - const newRotQuat = multiplyQuaternion(initRotQuat, transformQuat) // Order is important - const newRotationMat = quaternionToRotationMatrix(newRotQuat) - - // Compute the new model matrix (POSITION * NEW_COMPUTED_ROTATION * SCALE) and set it to the Transform - const mat = initialDecomposedModelMat.positionMat.times(newRotationMat.times(initialDecomposedModelMat.scaleMat)) - transformQtInstance.setMatrix(mat) - } - - function localScale(transformQtInstance, initialDecomposedModelMat, scaleVec) { - // Make a copy of the scale matrix (otherwise, it is a reference and it does not work as expected) - // Unfortunately, we have to proceed like this because, in QML, Qt.matrix4x4(Qt.matrix4x4) does not work - const scaleMat = Qt.matrix4x4() - scaleMat.scale(Qt.vector3d(initialDecomposedModelMat.scaleMat.m11, initialDecomposedModelMat.scaleMat.m22, initialDecomposedModelMat.scaleMat.m33)) - - // Update the scale matrix copy - scaleMat.m11 += scaleVec.x - scaleMat.m22 += scaleVec.y - scaleMat.m33 += scaleVec.z - - // Compute the new model matrix (POSITION * ROTATION * SCALE) and set it to the Transform - const mat = initialDecomposedModelMat.positionMat.times(initialDecomposedModelMat.rotationMat.times(scaleMat)) - transformQtInstance.setMatrix(mat) - } - - /***** SPECIFIC MATRIX TRANSFORMATIONS (using local vars) *****/ - - function doTranslation(initialDecomposedModelMat, translateVec) { - localTranslate(gizmoDisplayTransform, initialDecomposedModelMat, translateVec) // Update gizmo matrix - localTranslate(objectTransform, initialDecomposedModelMat, translateVec) // Update object matrix - } - - function doRotation(initialDecomposedModelMat, axis, degree) { - localRotate(gizmoDisplayTransform, initialDecomposedModelMat, axis, degree) // Update gizmo matrix - localRotate(objectTransform, initialDecomposedModelMat, axis, degree) // Update object matrix - } - - function doScale(initialDecomposedModelMat, scaleVec) { - localScale(objectTransform, initialDecomposedModelMat, scaleVec) // Update object matrix + function doRelativeScale(initialModelMatrix, scaleVec) { + Transformations3DHelper.relativeLocalScale(objectTransform, initialModelMatrix.position, initialModelMatrix.rotation, initialModelMatrix.scale, scaleVec) // Update object matrix } function resetTranslation() { @@ -225,8 +104,8 @@ Entity { // Transform the positive picked axis vector from World Coord to Screen Coord const gizmoLocalPointOnAxis = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 1)) const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) - const screenPoint2D = pointFromWorldToScreen(gizmoLocalPointOnAxis, camera, windowSize) - const screenCenter2D = pointFromWorldToScreen(gizmoCenterPoint, camera, windowSize) + const screenPoint2D = Transformations3DHelper.pointFromWorldToScreen(gizmoLocalPointOnAxis, camera, windowSize) + const screenCenter2D = Transformations3DHelper.pointFromWorldToScreen(gizmoCenterPoint, camera, windowSize) const screenAxisVector = Qt.vector2d(screenPoint2D.x - screenCenter2D.x, -(screenPoint2D.y - screenCenter2D.y)) // Get the cosinus of the angle from the screenAxisVector to the mouseVector @@ -234,7 +113,7 @@ Entity { const offset = cosAngle * mouseVector.length() * sensibility // If the mouse is not at the same spot as the pickedPoint, we do translation - if (offset) doTranslation(objectPicker.decomposedObjectModelMat, pickedAxis.times(offset)) // Do a translation from the initial Object Model Matrix when we picked the gizmo + if (offset) doRelativeTranslation(objectPicker.modelMatrix, pickedAxis.times(offset)) // Do a translation from the initial Object Model Matrix when we picked the gizmo return } @@ -242,7 +121,7 @@ Entity { case TransformGizmo.Type.ROTATION: { // Get Screen Coordinates of the gizmo center const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) - const screenCenter2D = pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize) + const screenCenter2D = Transformations3DHelper.pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize) // Get the vector screenCenter2D -> PickedPoint const originalVector = Qt.vector2d(objectPicker.screenPoint.x - screenCenter2D.x, -(objectPicker.screenPoint.y - screenCenter2D.y)) @@ -258,7 +137,7 @@ Entity { const gizmoToCameraVector = camera.position.toVector4d().minus(gizmoCenterPoint) const orientation = gizmoLocalAxisVector.dotProduct(gizmoToCameraVector) > 0 ? 1 : -1 - if (angle !== 0) doRotation(objectPicker.decomposedObjectModelMat, pickedAxis, angle*orientation) + if (angle !== 0) doRelativeRotation(objectPicker.modelMatrix, pickedAxis, angle*orientation) return } @@ -267,7 +146,7 @@ Entity { // Get Screen Coordinates of the gizmo center const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) - const screenCenter2D = pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize) + const screenCenter2D = Transformations3DHelper.pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize) // Compute the scale unit const scaleUnit = screenCenter2D.minus(Qt.vector2d(objectPicker.screenPoint.x, objectPicker.screenPoint.y)).length() @@ -277,7 +156,7 @@ Entity { let offset = (mouseVector.length() - scaleUnit) * sensibility offset = (offset < 0) ? offset * 3 : offset // Used to make it more sensible when we want to reduce the scale (because the action field is shorter) - if (offset) doScale(objectPicker.decomposedObjectModelMat, pickedAxis.times(offset)) + if (offset) doRelativeScale(objectPicker.modelMatrix, pickedAxis.times(offset)) return } } @@ -450,7 +329,7 @@ Entity { gizmoType: TransformGizmo.Type.SCALE onPickedChanged: { - this.decomposedObjectModelMat = decomposeModelMatrixFromTransformations(objectTransform.translation, objectTransform.rotation, objectTransform.scale3D) // Save the current transformations + this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) // Save the current transformations root.pickedChanged(picker.isPressed) // Used to prevent camera transformations } } @@ -509,7 +388,8 @@ Entity { gizmoType: TransformGizmo.Type.POSITION onPickedChanged: { - this.decomposedObjectModelMat = decomposeModelMatrixFromTransformations(objectTransform.translation, objectTransform.rotation, objectTransform.scale3D) // Save the current transformations + // this.modelMatrix = copyMatrix(objectTransform.matrix) // Save the current transformations + this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) // Save the current transformations root.pickedChanged(picker.isPressed) // Used to prevent camera transformations } } @@ -554,7 +434,7 @@ Entity { gizmoType: TransformGizmo.Type.ROTATION onPickedChanged: { - this.decomposedObjectModelMat = decomposeModelMatrixFromTransformations(objectTransform.translation, objectTransform.rotation, objectTransform.scale3D) // Save the current transformations + this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) // Save the current transformations root.pickedChanged(picker.isPressed) // Used to prevent camera transformations } } diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml index d8cd3c24e8..b5cb21eb84 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml @@ -14,7 +14,7 @@ ObjectPicker { property int gizmoAxis property int gizmoType property point screenPoint - property var decomposedObjectModelMat + property var modelMatrix signal pickedChanged(var picker) From d0a78d96ab7dcb0fbc25cdd72cfc0f54b05764fd Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Wed, 15 Jul 2020 14:55:39 +0200 Subject: [PATCH 17/51] [ui] Viewer3D: TransformGizmo - ready to be set with absolute values - Now, we can set the transformation with a position vector, Euler angles and a scale vector. --- meshroom/ui/components/scene3D.py | 23 ++++++++ meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml | 5 ++ meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 57 +++++++++++++------ .../ui/qml/Viewer3D/TransformGizmoPicker.qml | 2 + 4 files changed, 70 insertions(+), 17 deletions(-) diff --git a/meshroom/ui/components/scene3D.py b/meshroom/ui/components/scene3D.py index 79e2871af7..c117799a95 100644 --- a/meshroom/ui/components/scene3D.py +++ b/meshroom/ui/components/scene3D.py @@ -188,6 +188,29 @@ def modelMatrixToMatrices(self, modelMat): return { "position": posMat, "rotation": rotMat, "scale": scaleMat, "quaternion": decomposition.get("quaternion") } + @Slot(QVector3D, QVector3D, QVector3D, result=QMatrix4x4) + def computeModelMatrixWithEuler(self, translation, rotation, scale): + """ Compute a model matrix from three Vector3D. + Args: + translation (QVector3D): position in space (x, y, z) + rotation (QVector3D): Euler angles in degrees (x, y, z) + scale (QVector3D): scale of the object (x, y, z) + Returns: + QMatrix4x4: corresponding model matrix + """ + posMat = QMatrix4x4() + posMat.translate(translation) + + quaternion = QQuaternion.fromEulerAngles(rotation) + rotMat = self.quaternionToRotationMatrix(quaternion) + + scaleMat = QMatrix4x4() + scaleMat.scale(scale) + + modelMat = posMat * rotMat * scaleMat + + return modelMat + #---------- "Private" Methods ----------# diff --git a/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml b/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml index b11ac3c070..88a2610301 100644 --- a/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml @@ -14,8 +14,10 @@ Entity { readonly property Camera camera : cameraController.camera readonly property var windowSize: cameraController.windowSize readonly property alias objectTransform : transformGizmo.objectTransform // The Transform the object should use + readonly property alias updateTransformations: transformGizmo.updateTransformations // Function to update the transformations signal pickedChanged(bool pressed) + signal gizmoChanged(var translation, var rotation, var scale) onPickedChanged: { cameraController.loseMouseFocus = pressed // Notify the camera if the transform takes/releases the focus @@ -30,5 +32,8 @@ Entity { onPickedChanged: { root.pickedChanged(pressed) } + onGizmoChanged: { + root.gizmoChanged(translation, rotation, scale) + } } } \ No newline at end of file diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index b22d2e8e0d..bc726c6a47 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -14,9 +14,29 @@ Entity { property var windowSize property var frontLayerComponent property var window - readonly property Transform objectTransform : Transform {} + + readonly property Transform objectTransform : Transform { + translation: gizmoDisplayTransform.translation + rotation: gizmoDisplayTransform.rotation + scale3D: Qt.vector3d(1,1,1) + } + readonly property var updateTransformations: function updateTransformations(translation, rotation, scale) { + const gizmoModelMat = Transformations3DHelper.computeModelMatrixWithEuler(translation, rotation, Qt.vector3d(1,1,1)) + gizmoDisplayTransform.setMatrix(gizmoModelMat) // Update gizmo matrix and translation/rotation of the object (with binding) + objectTransform.scale3D = scale // Update the scale of the object + } signal pickedChanged(bool pressed) + signal gizmoChanged(var translation, var rotation, var scale) + + function emitGizmoChanged() { + const translation = gizmoDisplayTransform.translation // Position in space + const rotation = Qt.vector3d(gizmoDisplayTransform.rotationX, gizmoDisplayTransform.rotationY, gizmoDisplayTransform.rotationZ) // Euler angles + const scale = objectTransform.scale3D // Scale of the object + + updateTransformations(translation, rotation, scale) // Optional: just to make sure the absolute values work well + gizmoChanged(translation, rotation, scale) + } components: [gizmoDisplayTransform, mouseHandler, frontLayerComponent] @@ -38,31 +58,27 @@ Entity { /***** TRANSFORMATIONS (using local vars) *****/ function doRelativeTranslation(initialModelMatrix, translateVec) { - Transformations3DHelper.relativeLocalTranslate(gizmoDisplayTransform, initialModelMatrix.position, initialModelMatrix.rotation, initialModelMatrix.scale, translateVec) // Update gizmo matrix - Transformations3DHelper.relativeLocalTranslate(objectTransform, initialModelMatrix.position, initialModelMatrix.rotation, initialModelMatrix.scale, translateVec) // Update object matrix + Transformations3DHelper.relativeLocalTranslate(gizmoDisplayTransform, initialModelMatrix.position, initialModelMatrix.rotation, initialModelMatrix.scale, translateVec) // Update gizmo matrix and object matrix with binding } function doRelativeRotation(initialModelMatrix, axis, degree) { - Transformations3DHelper.relativeLocalRotate(gizmoDisplayTransform, initialModelMatrix.position, initialModelMatrix.quaternion, initialModelMatrix.scale, axis, degree) // Update gizmo matrix - Transformations3DHelper.relativeLocalRotate(objectTransform, initialModelMatrix.position, initialModelMatrix.quaternion, initialModelMatrix.scale, axis, degree) // Update object matrix + Transformations3DHelper.relativeLocalRotate(gizmoDisplayTransform, initialModelMatrix.position, initialModelMatrix.quaternion, initialModelMatrix.scale, axis, degree) // Update gizmo matrix and object matrix with binding } function doRelativeScale(initialModelMatrix, scaleVec) { - Transformations3DHelper.relativeLocalScale(objectTransform, initialModelMatrix.position, initialModelMatrix.rotation, initialModelMatrix.scale, scaleVec) // Update object matrix + Transformations3DHelper.relativeLocalScale(objectTransform, initialModelMatrix.position, initialModelMatrix.rotation, initialModelMatrix.scale, scaleVec) // Update only object matrix } function resetTranslation() { - gizmoDisplayTransform.translation = Qt.vector3d(0,0,0) - objectTransform.translation = Qt.vector3d(0,0,0) + gizmoDisplayTransform.translation = Qt.vector3d(0,0,0) // Reset gizmo matrix and object matrix with binding } function resetRotation() { - gizmoDisplayTransform.rotation = Qt.quaternion(1,0,0,0) - objectTransform.rotation = Qt.quaternion(1,0,0,0) + gizmoDisplayTransform.rotation = Qt.quaternion(1,0,0,0) // Reset gizmo matrix and object matrix with binding } function resetScale() { - objectTransform.scale3D = Qt.vector3d(1,1,1) + objectTransform.scale3D = Qt.vector3d(1,1,1) // Reset only object matrix } function resetATransformType(transformType) { @@ -71,6 +87,7 @@ Entity { case TransformGizmo.Type.ROTATION: resetRotation(); break case TransformGizmo.Type.SCALE: resetScale(); break } + emitGizmoChanged() } /***** DEVICES *****/ @@ -84,7 +101,7 @@ Entity { property bool enabled: false onPositionChanged: { - if (objectPicker) { + if (objectPicker && objectPicker.button === Qt.LeftButton) { // Get the selected axis let pickedAxis switch(objectPicker.gizmoAxis) { @@ -137,7 +154,8 @@ Entity { const gizmoToCameraVector = camera.position.toVector4d().minus(gizmoCenterPoint) const orientation = gizmoLocalAxisVector.dotProduct(gizmoToCameraVector) > 0 ? 1 : -1 - if (angle !== 0) doRelativeRotation(objectPicker.modelMatrix, pickedAxis, angle*orientation) + if (angle !== 0) doRelativeRotation(objectPicker.modelMatrix, pickedAxis, angle*orientation) // Do a rotation from the initial Object Model Matrix when we picked the gizmo + return } @@ -156,18 +174,23 @@ Entity { let offset = (mouseVector.length() - scaleUnit) * sensibility offset = (offset < 0) ? offset * 3 : offset // Used to make it more sensible when we want to reduce the scale (because the action field is shorter) - if (offset) doRelativeScale(objectPicker.modelMatrix, pickedAxis.times(offset)) + if (offset) doRelativeScale(objectPicker.modelMatrix, pickedAxis.times(offset)) // Do a scale from the initial Object Model Matrix when we picked the gizmo + return } } } - } - onReleased: { - if(objectPicker && mouse.button === Qt.RightButton) { + + if(objectPicker && objectPicker.button === Qt.RightButton) { resetMenu.updateTypeBeforePopup(objectPicker.gizmoType) resetMenu.popup(window) } } + onReleased: { + if(objectPicker && mouse.button === Qt.LeftButton) { + emitGizmoChanged() + } + } } Menu { diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml index b5cb21eb84..d472119dfd 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml @@ -15,6 +15,7 @@ ObjectPicker { property int gizmoType property point screenPoint property var modelMatrix + property int button signal pickedChanged(var picker) @@ -25,6 +26,7 @@ ObjectPicker { mouseController.objectPicker = this root.isPressed = true screenPoint = pick.position + button = pick.button pickedChanged(this) } onEntered: { From 7663a6a44b43ae09e8f55a29553f98df73abc0c1 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 20 Jul 2020 10:42:41 +0200 Subject: [PATCH 18/51] [ui] Viewer3D: TransformGizmo - preparing for the bounding box --- meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml | 27 +++++--------- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 38 ++++++++++++-------- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml b/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml index 88a2610301..f282d41aeb 100644 --- a/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml @@ -7,33 +7,22 @@ import Qt3D.Logic 2.0 Entity { id: root - property DefaultCameraController cameraController + property DefaultCameraController sceneCameraController property Layer frontLayerComponent property var window - - readonly property Camera camera : cameraController.camera - readonly property var windowSize: cameraController.windowSize - readonly property alias objectTransform : transformGizmo.objectTransform // The Transform the object should use - readonly property alias updateTransformations: transformGizmo.updateTransformations // Function to update the transformations - - signal pickedChanged(bool pressed) - signal gizmoChanged(var translation, var rotation, var scale) - - onPickedChanged: { - cameraController.loseMouseFocus = pressed // Notify the camera if the transform takes/releases the focus - } - - TransformGizmo { + property TransformGizmo transformGizmo: TransformGizmo { id: transformGizmo camera: root.camera windowSize: root.windowSize frontLayerComponent: root.frontLayerComponent window: root.window + onPickedChanged: { - root.pickedChanged(pressed) - } - onGizmoChanged: { - root.gizmoChanged(translation, rotation, scale) + sceneCameraController.loseMouseFocus = pressed // Notify the camera if the transform takes/releases the focus } } + + readonly property Camera camera : sceneCameraController.camera + readonly property var windowSize: sceneCameraController.windowSize + readonly property alias objectTransform : transformGizmo.objectTransform // The Transform the object should use } \ No newline at end of file diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index bc726c6a47..4f2956e4f9 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -14,17 +14,16 @@ Entity { property var windowSize property var frontLayerComponent property var window - - readonly property Transform objectTransform : Transform { + property bool focusGizmoPriority: false // If true, it is used to give the priority to the current transformation (and not to a upper-level binding) + property Transform gizmoDisplayTransform: Transform { + id: gizmoDisplayTransform + scale: root.gizmoScale * (camera.position.minus(gizmoDisplayTransform.translation)).length() + } + property Transform objectTransform : Transform { translation: gizmoDisplayTransform.translation rotation: gizmoDisplayTransform.rotation scale3D: Qt.vector3d(1,1,1) } - readonly property var updateTransformations: function updateTransformations(translation, rotation, scale) { - const gizmoModelMat = Transformations3DHelper.computeModelMatrixWithEuler(translation, rotation, Qt.vector3d(1,1,1)) - gizmoDisplayTransform.setMatrix(gizmoModelMat) // Update gizmo matrix and translation/rotation of the object (with binding) - objectTransform.scale3D = scale // Update the scale of the object - } signal pickedChanged(bool pressed) signal gizmoChanged(var translation, var rotation, var scale) @@ -34,8 +33,8 @@ Entity { const rotation = Qt.vector3d(gizmoDisplayTransform.rotationX, gizmoDisplayTransform.rotationY, gizmoDisplayTransform.rotationZ) // Euler angles const scale = objectTransform.scale3D // Scale of the object - updateTransformations(translation, rotation, scale) // Optional: just to make sure the absolute values work well gizmoChanged(translation, rotation, scale) + root.focusGizmoPriority = false } components: [gizmoDisplayTransform, mouseHandler, frontLayerComponent] @@ -70,15 +69,26 @@ Entity { } function resetTranslation() { - gizmoDisplayTransform.translation = Qt.vector3d(0,0,0) // Reset gizmo matrix and object matrix with binding + // We have to make the opposite translation because we cannot override gizmoDisplayTransform.translation (because it can be used in upper-level binding) + // The object translation will also be updated because of the binding + const modelMat = Transformations3DHelper.modelMatrixToMatrices(gizmoDisplayTransform.matrix) + doRelativeTranslation(modelMat, gizmoDisplayTransform.translation.times(-1)) } function resetRotation() { + // Here, we can change the rotation property (but not rotationX, rotationY and rotationZ because they can be used in upper-level bindings) gizmoDisplayTransform.rotation = Qt.quaternion(1,0,0,0) // Reset gizmo matrix and object matrix with binding } function resetScale() { - objectTransform.scale3D = Qt.vector3d(1,1,1) // Reset only object matrix + // We have to make the difference scale to 1 because we cannot override objectTransform.scale3D (because it can be used in upper-level binding) + const modelMat = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) + const scaleDiff = Qt.vector3d( + -(objectTransform.scale3D.x - 1), + -(objectTransform.scale3D.y - 1), + -(objectTransform.scale3D.z - 1) + ) + doRelativeScale(modelMat, scaleDiff) } function resetATransformType(transformType) { @@ -102,6 +112,8 @@ Entity { onPositionChanged: { if (objectPicker && objectPicker.button === Qt.LeftButton) { + root.focusGizmoPriority = true + // Get the selected axis let pickedAxis switch(objectPicker.gizmoAxis) { @@ -188,6 +200,7 @@ Entity { } onReleased: { if(objectPicker && mouse.button === Qt.LeftButton) { + objectPicker = null // To prevent going again in the onPositionChanged emitGizmoChanged() } } @@ -215,11 +228,6 @@ Entity { /***** GIZMO'S BASIC COMPONENTS *****/ - Transform { - id: gizmoDisplayTransform - scale: root.gizmoScale * (camera.position.minus(gizmoDisplayTransform.translation)).length() - } - Entity { id: centerSphereEntity components: [centerSphereMesh, centerSphereMaterial, frontLayerComponent] From f4e73117a79f5615f0d58c0b34476fa6b2cfad06 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 20 Jul 2020 10:54:04 +0200 Subject: [PATCH 19/51] [ui] Viewer3D: add a BoundingBox entity --- meshroom/ui/qml/Viewer3D/BoundingBox.qml | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 meshroom/ui/qml/Viewer3D/BoundingBox.qml diff --git a/meshroom/ui/qml/Viewer3D/BoundingBox.qml b/meshroom/ui/qml/Viewer3D/BoundingBox.qml new file mode 100644 index 0000000000..3f956dff3b --- /dev/null +++ b/meshroom/ui/qml/Viewer3D/BoundingBox.qml @@ -0,0 +1,25 @@ +import Qt3D.Core 2.0 +import Qt3D.Render 2.9 +import Qt3D.Input 2.0 +import Qt3D.Extras 2.10 +import QtQuick 2.9 + +Entity { + id: root + property Transform transform: Transform {} + components: [mesh, transform, material] + + CuboidMesh { + id: mesh + property real edge : 2 // Important to have all the cube's vertices with a unit of 1 + xExtent: edge + yExtent: edge + zExtent: edge + } + PhongAlphaMaterial { + id: material + property color base: "#dfe233" + ambient: base + alpha: 0.3 + } +} \ No newline at end of file From 94b2ff3a87f32b6dc55d64959c532736268a04c8 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 20 Jul 2020 11:42:55 +0200 Subject: [PATCH 20/51] [nodes] meshing: add bounding box options - Checkbox in optional settings. - Parameters in advanced settings. --- meshroom/nodes/aliceVision/Meshing.py | 95 +++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/meshroom/nodes/aliceVision/Meshing.py b/meshroom/nodes/aliceVision/Meshing.py index 0c7b19e894..e62fa301b3 100644 --- a/meshroom/nodes/aliceVision/Meshing.py +++ b/meshroom/nodes/aliceVision/Meshing.py @@ -35,6 +35,101 @@ class Meshing(desc.CommandLineNode): value='', uid=[0], ), + desc.BoolParam( + name='useBoundingBox', + label='Custom Bounding Box', + description='Edit the meshing bounding box. If enabled, it takes priority over the Estimate From SfM option. Parameters can be adjusted in advanced settings.', + value=False, + uid=[0], + group='' + ), + desc.GroupAttribute( + name="boundingBox", + label="Bounding Box Settings", + description="Translation, rotation and scale of the bounding box.", + groupDesc=[ + desc.GroupAttribute( + name="bboxTranslation", + label="Translation", + description="Position in space.", + groupDesc=[ + desc.FloatParam( + name="x", label="x", description="X Offset", + value=0.0, + uid=[0], + range=(-20.0, 20.0, 0.01) + ), + desc.FloatParam( + name="y", label="y", description="Y Offset", + value=0.0, + uid=[0], + range=(-20.0, 20.0, 0.01) + ), + desc.FloatParam( + name="z", label="z", description="Z Offset", + value=0.0, + uid=[0], + range=(-20.0, 20.0, 0.01) + ) + ], + joinChar="," + ), + desc.GroupAttribute( + name="bboxRotation", + label="Euler Rotation", + description="Rotation in Euler degrees.", + groupDesc=[ + desc.FloatParam( + name="x", label="x", description="Euler X Rotation", + value=0.0, + uid=[0], + range=(-90.0, 90.0, 1) + ), + desc.FloatParam( + name="y", label="y", description="Euler Y Rotation", + value=0.0, + uid=[0], + range=(-180.0, 180.0, 1) + ), + desc.FloatParam( + name="z", label="z", description="Euler Z Rotation", + value=0.0, + uid=[0], + range=(-180.0, 180.0, 1) + ) + ], + joinChar="," + ), + desc.GroupAttribute( + name="bboxScale", + label="Scale", + description="Scale of the bounding box.", + groupDesc=[ + desc.FloatParam( + name="x", label="x", description="X Scale", + value=1.0, + uid=[0], + range=(0.0, 20.0, 0.01) + ), + desc.FloatParam( + name="y", label="y", description="Y Scale", + value=1.0, + uid=[0], + range=(0.0, 20.0, 0.01) + ), + desc.FloatParam( + name="z", label="z", description="Z Scale", + value=1.0, + uid=[0], + range=(0.0, 20.0, 0.01) + ) + ], + joinChar="," + ) + ], + joinChar=",", + advanced=True + ), desc.BoolParam( name='estimateSpaceFromSfM', label='Estimate Space From SfM', From 208114653ae8292bc1f4a35fb6fc00c8f077ba18 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 20 Jul 2020 11:52:05 +0200 Subject: [PATCH 21/51] [ui] Viewer3D: bounding box linked to meshing node --- meshroom/ui/qml/Viewer3D/Viewer3D.qml | 72 +++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/meshroom/ui/qml/Viewer3D/Viewer3D.qml b/meshroom/ui/qml/Viewer3D/Viewer3D.qml index 7bbcf1cfc1..773304c869 100644 --- a/meshroom/ui/qml/Viewer3D/Viewer3D.qml +++ b/meshroom/ui/qml/Viewer3D/Viewer3D.qml @@ -139,6 +139,63 @@ FocusScope { } } + // BoundingBox: display bounding box for meshing computation + // note: use a NodeInstantiator to evaluate if a Meshing node exists at runtime + NodeInstantiator { + id: boundingBoxInstantiator + property var activeNode: _reconstruction.activeNodes.get("Meshing").node + active: activeNode && activeNode.attribute("useBoundingBox").value + model: 1 + + EntityWithGizmo { + id: boundingBoxEntity + sceneCameraController: cameraController + frontLayerComponent: drawOnFront + window: root + enabled: boundingBoxVisibility.checked + + // Update node meshing slider values when the gizmo has changed: translation, rotation, scale + transformGizmo.onGizmoChanged: { + _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxTranslation.x"), translation.x) + _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxTranslation.y"), translation.y) + _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxTranslation.z"), translation.z) + + _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxRotation.x"), rotation.x) + _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxRotation.y"), rotation.y) + _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxRotation.z"), rotation.z) + + _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxScale.x"), scale.x) + _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxScale.y"), scale.y) + _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxScale.z"), scale.z) + } + + // Automatically evalutate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse. + // When the gizmo has changed (with mouse), the new values are set to the node, the priority is given back to the node and the Transform is re-evaluated once with those values. + property var nodeTranslation : Qt.vector3d( + boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxTranslation.x").value, + boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxTranslation.y").value, + boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxTranslation.z").value + ) + property var nodeRotationX: boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxRotation.x").value + property var nodeRotationY: boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxRotation.y").value + property var nodeRotationZ: boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxRotation.z").value + property var nodeScale: Qt.vector3d( + boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxScale.x").value, + boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxScale.y").value, + boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxScale.z").value + ) + + transformGizmo.gizmoDisplayTransform.translation: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.translation : nodeTranslation + transformGizmo.gizmoDisplayTransform.rotationX: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationX : nodeRotationX + transformGizmo.gizmoDisplayTransform.rotationY: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationY : nodeRotationY + transformGizmo.gizmoDisplayTransform.rotationZ: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationZ : nodeRotationZ + transformGizmo.objectTransform.scale3D: transformGizmo.focusGizmoPriority ? transformGizmo.objectTransform.scale3D : nodeScale + + // The entity + BoundingBox { transform: boundingBoxEntity.objectTransform } + } + } + DefaultCameraController { id: cameraController enabled: cameraSelector.camera == mainCamera @@ -305,6 +362,21 @@ FocusScope { } } + FloatingPane { + anchors.bottom: root.bottom + anchors.right: root.right + padding: 4 + visible: boundingBoxInstantiator.active + + MaterialToolButton { + id: boundingBoxVisibility + text: MaterialIcons.transform + ToolTip.text: "Show BoundingBox" + font.pointSize: 11 + checked: true + } + } + // Menu Menu { id: contextMenu From 6103a4937f7c6322235733f2f6cdafe29f45184a Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 20 Jul 2020 16:57:31 +0200 Subject: [PATCH 22/51] [ui] Viewer3D: TransformGizmo - fix reset transformations --- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 22 +++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index 4f2956e4f9..bfe3964157 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -69,10 +69,16 @@ Entity { } function resetTranslation() { - // We have to make the opposite translation because we cannot override gizmoDisplayTransform.translation (because it can be used in upper-level binding) - // The object translation will also be updated because of the binding - const modelMat = Transformations3DHelper.modelMatrixToMatrices(gizmoDisplayTransform.matrix) - doRelativeTranslation(modelMat, gizmoDisplayTransform.translation.times(-1)) + // We have to reset the translation inside the matrix because we cannot override gizmoDisplayTransform.translation (because it can be used in upper-level binding) + // The object matrix will also be updated because of the binding with the translation property + const mat = gizmoDisplayTransform.matrix + const newMat = Qt.matrix4x4( + mat.m11, mat.m12, mat.m13, 0, + mat.m21, mat.m22, mat.m23, 0, + mat.m31, mat.m32, mat.m33, 0, + mat.m41, mat.m42, mat.m43, 1 + ) + gizmoDisplayTransform.setMatrix(newMat) } function resetRotation() { @@ -214,14 +220,14 @@ Entity { function updateTypeBeforePopup(type) { resetMenu.transformType = type switch(type) { - case TransformGizmo.Type.POSITION: resetMenu.transformTypeToDisplay = "position"; break - case TransformGizmo.Type.ROTATION: resetMenu.transformTypeToDisplay = "rotation"; break - case TransformGizmo.Type.SCALE: resetMenu.transformTypeToDisplay = "scale"; break + case TransformGizmo.Type.POSITION: resetMenu.transformTypeToDisplay = "Translation"; break + case TransformGizmo.Type.ROTATION: resetMenu.transformTypeToDisplay = "Rotation"; break + case TransformGizmo.Type.SCALE: resetMenu.transformTypeToDisplay = "Scale"; break } } MenuItem { - text: `Reset ${resetMenu.transformTypeToDisplay}?` + text: `Reset ${resetMenu.transformTypeToDisplay}` onTriggered: resetATransformType(resetMenu.transformType) } } From 4ab412dd9054eedbc970e65582d0513d5997d615 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 20 Jul 2020 17:05:24 +0200 Subject: [PATCH 23/51] [ui] Viewer3D: move bounding box display to Inspector3D --- meshroom/ui/qml/Viewer3D/Inspector3D.qml | 16 ++++ meshroom/ui/qml/Viewer3D/MediaLibrary.qml | 41 ++++++++++ .../ui/qml/Viewer3D/MeshingBoundingBox.qml | 61 +++++++++++++++ meshroom/ui/qml/Viewer3D/Viewer3D.qml | 77 ++----------------- 4 files changed, 123 insertions(+), 72 deletions(-) create mode 100644 meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml diff --git a/meshroom/ui/qml/Viewer3D/Inspector3D.qml b/meshroom/ui/qml/Viewer3D/Inspector3D.qml index 5c37c73d61..7f6bbc883a 100644 --- a/meshroom/ui/qml/Viewer3D/Inspector3D.qml +++ b/meshroom/ui/qml/Viewer3D/Inspector3D.qml @@ -284,6 +284,22 @@ FloatingPane { } } + // Transform visibility (bbox for meshing) + MaterialToolButton { + visible: model.hasTransform + enabled: model.visible + Layout.alignment: Qt.AlignTop + Layout.fillHeight: true + text: MaterialIcons.transform + font.pointSize: 10 + ToolTip.text: model.displayTransform ? "Hide BBox" : "Show BBox" + flat: true + opacity: model.visible ? (model.displayTransform ? 1.0 : 0.6) : 0.6 + onClicked: { + model.displayTransform = !model.displayTransform + } + } + // Media label and info Item { implicitHeight: childrenRect.height diff --git a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml index 6542e2db74..8f526a7415 100644 --- a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml +++ b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml @@ -16,6 +16,11 @@ Entity { property bool pickingEnabled: false readonly property alias count: instantiator.count // number of instantiated media delegates + // For TransformGizmo in BoundingBox + property DefaultCameraController sceneCameraController + property Layer frontLayerComponent + property var window + /// Camera to consider for positionning property Camera camera: null @@ -41,6 +46,8 @@ Entity { "valid": true, "label": "", "visible": true, + "hasTransform": false, + "displayTransform": true, "section": "", "attribute": null, "entity": null, @@ -149,6 +156,21 @@ Entity { delegate: MediaLoader { id: mediaLoader + property var currentNode: model.attribute ? model.attribute.node : null + property string nodeType: currentNode ? currentNode.nodeType: null + property bool hasTransform: { + if(nodeType !== "Meshing") // Cannot have a Transform + return false + const value = currentNode ? currentNode.attribute("useBoundingBox").value : null + if (value !== null) + mediaLoader.hasTransformPropertyChanged(value) + return value + } + property bool displayTransform: model.displayTransform + + signal hasTransformPropertyChanged(bool transform) + onHasTransformPropertyChanged: model.hasTransform = transform + // whether MediaLoader has been fully instantiated by the NodeInstantiator property bool fullyInstantiated: false @@ -188,6 +210,23 @@ Entity { onObjectRemoved: remove(idx) } + // BoundingBox: display bounding box for meshing computation + // note: use a NodeInstantiator to evaluate if the current node is a MESHING node and if the checkbox is active + NodeInstantiator { + id: boundingBoxInstantiator + property var currentNode: mediaLoader.currentNode + active: currentNode.nodeType === "Meshing" && currentNode.attribute("useBoundingBox").value + model: 1 + + MeshingBoundingBox { + sceneCameraController: root.sceneCameraController + frontLayerComponent: root.frontLayerComponent + window: root.window + currentMeshingNode: boundingBoxInstantiator.currentNode + enabled: mediaLoader.displayTransform + } + } + // 'visible' property drives media loading request onVisibleChanged: { // always request media loading if visible @@ -227,6 +266,8 @@ Entity { // if external media failed to open, remove element from model if(!attribute && !object) remove(index) + // tell the model if it has a Transform (Gizmo) + model.hasTransform = hasTransform } onCurrentSourceChanged: { diff --git a/meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml b/meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml new file mode 100644 index 0000000000..b7e7ece3be --- /dev/null +++ b/meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml @@ -0,0 +1,61 @@ +import Qt3D.Core 2.0 +import Qt3D.Render 2.9 +import Qt3D.Input 2.0 +import Qt3D.Extras 2.10 +import QtQuick 2.9 + +Entity { + id: root + property DefaultCameraController sceneCameraController + property Layer frontLayerComponent + property var window + property var currentMeshingNode: null + enabled: true + + EntityWithGizmo { + id: boundingBoxEntity + sceneCameraController: root.sceneCameraController + frontLayerComponent: root.frontLayerComponent + window: root.window + + // Update node meshing slider values when the gizmo has changed: translation, rotation, scale + transformGizmo.onGizmoChanged: { + _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxTranslation.x"), translation.x) + _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxTranslation.y"), translation.y) + _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxTranslation.z"), translation.z) + + _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxRotation.x"), rotation.x) + _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxRotation.y"), rotation.y) + _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxRotation.z"), rotation.z) + + _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxScale.x"), scale.x) + _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxScale.y"), scale.y) + _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxScale.z"), scale.z) + } + + // Automatically evalutate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse. + // When the gizmo has changed (with mouse), the new values are set to the node, the priority is given back to the node and the Transform is re-evaluated once with those values. + property var nodeTranslation : Qt.vector3d( + root.currentMeshingNode.attribute("boundingBox.bboxTranslation.x").value, + root.currentMeshingNode.attribute("boundingBox.bboxTranslation.y").value, + root.currentMeshingNode.attribute("boundingBox.bboxTranslation.z").value + ) + property var nodeRotationX: root.currentMeshingNode.attribute("boundingBox.bboxRotation.x").value + property var nodeRotationY: root.currentMeshingNode.attribute("boundingBox.bboxRotation.y").value + property var nodeRotationZ: root.currentMeshingNode.attribute("boundingBox.bboxRotation.z").value + property var nodeScale: Qt.vector3d( + root.currentMeshingNode.attribute("boundingBox.bboxScale.x").value, + root.currentMeshingNode.attribute("boundingBox.bboxScale.y").value, + root.currentMeshingNode.attribute("boundingBox.bboxScale.z").value + ) + + transformGizmo.gizmoDisplayTransform.translation: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.translation : nodeTranslation + transformGizmo.gizmoDisplayTransform.rotationX: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationX : nodeRotationX + transformGizmo.gizmoDisplayTransform.rotationY: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationY : nodeRotationY + transformGizmo.gizmoDisplayTransform.rotationZ: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationZ : nodeRotationZ + transformGizmo.objectTransform.scale3D: transformGizmo.focusGizmoPriority ? transformGizmo.objectTransform.scale3D : nodeScale + + // The entity + BoundingBox { transform: boundingBoxEntity.objectTransform } + } +} diff --git a/meshroom/ui/qml/Viewer3D/Viewer3D.qml b/meshroom/ui/qml/Viewer3D/Viewer3D.qml index 773304c869..cead957496 100644 --- a/meshroom/ui/qml/Viewer3D/Viewer3D.qml +++ b/meshroom/ui/qml/Viewer3D/Viewer3D.qml @@ -139,63 +139,6 @@ FocusScope { } } - // BoundingBox: display bounding box for meshing computation - // note: use a NodeInstantiator to evaluate if a Meshing node exists at runtime - NodeInstantiator { - id: boundingBoxInstantiator - property var activeNode: _reconstruction.activeNodes.get("Meshing").node - active: activeNode && activeNode.attribute("useBoundingBox").value - model: 1 - - EntityWithGizmo { - id: boundingBoxEntity - sceneCameraController: cameraController - frontLayerComponent: drawOnFront - window: root - enabled: boundingBoxVisibility.checked - - // Update node meshing slider values when the gizmo has changed: translation, rotation, scale - transformGizmo.onGizmoChanged: { - _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxTranslation.x"), translation.x) - _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxTranslation.y"), translation.y) - _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxTranslation.z"), translation.z) - - _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxRotation.x"), rotation.x) - _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxRotation.y"), rotation.y) - _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxRotation.z"), rotation.z) - - _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxScale.x"), scale.x) - _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxScale.y"), scale.y) - _reconstruction.setAttribute(boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxScale.z"), scale.z) - } - - // Automatically evalutate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse. - // When the gizmo has changed (with mouse), the new values are set to the node, the priority is given back to the node and the Transform is re-evaluated once with those values. - property var nodeTranslation : Qt.vector3d( - boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxTranslation.x").value, - boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxTranslation.y").value, - boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxTranslation.z").value - ) - property var nodeRotationX: boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxRotation.x").value - property var nodeRotationY: boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxRotation.y").value - property var nodeRotationZ: boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxRotation.z").value - property var nodeScale: Qt.vector3d( - boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxScale.x").value, - boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxScale.y").value, - boundingBoxInstantiator.activeNode.attribute("boundingBox.bboxScale.z").value - ) - - transformGizmo.gizmoDisplayTransform.translation: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.translation : nodeTranslation - transformGizmo.gizmoDisplayTransform.rotationX: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationX : nodeRotationX - transformGizmo.gizmoDisplayTransform.rotationY: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationY : nodeRotationY - transformGizmo.gizmoDisplayTransform.rotationZ: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationZ : nodeRotationZ - transformGizmo.objectTransform.scale3D: transformGizmo.focusGizmoPriority ? transformGizmo.objectTransform.scale3D : nodeScale - - // The entity - BoundingBox { transform: boundingBoxEntity.objectTransform } - } - } - DefaultCameraController { id: cameraController enabled: cameraSelector.camera == mainCamera @@ -291,6 +234,11 @@ FocusScope { pickingEnabled: cameraController.pickingActive || doubleClickTimer.running camera: cameraSelector.camera + // Used for TransformGizmo in BoundingBox + sceneCameraController: cameraController + frontLayerComponent: drawOnFront + window: root + components: [ Transform { id: transform @@ -362,21 +310,6 @@ FocusScope { } } - FloatingPane { - anchors.bottom: root.bottom - anchors.right: root.right - padding: 4 - visible: boundingBoxInstantiator.active - - MaterialToolButton { - id: boundingBoxVisibility - text: MaterialIcons.transform - ToolTip.text: "Show BoundingBox" - font.pointSize: 11 - checked: true - } - } - // Menu Menu { id: contextMenu From f2a75861c330ec26f0be8834ed600beb49fff1d1 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Tue, 21 Jul 2020 16:21:55 +0200 Subject: [PATCH 24/51] [ui] Viewer3D: better bounding box appearance - Add edges to the bounding box. Appearance inspired by Blender. - Better gizmo integration over the scene. --- meshroom/ui/qml/Viewer3D/BoundingBox.qml | 93 ++++++++++++++++++++---- meshroom/ui/qml/Viewer3D/Viewer3D.qml | 2 +- 2 files changed, 81 insertions(+), 14 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/BoundingBox.qml b/meshroom/ui/qml/Viewer3D/BoundingBox.qml index 3f956dff3b..a33cb2ba7b 100644 --- a/meshroom/ui/qml/Viewer3D/BoundingBox.qml +++ b/meshroom/ui/qml/Viewer3D/BoundingBox.qml @@ -7,19 +7,86 @@ import QtQuick 2.9 Entity { id: root property Transform transform: Transform {} - components: [mesh, transform, material] - - CuboidMesh { - id: mesh - property real edge : 2 // Important to have all the cube's vertices with a unit of 1 - xExtent: edge - yExtent: edge - zExtent: edge + + components: [transform] + + Entity { + components: [cube, greyMaterial] + + CuboidMesh { + id: cube + property real edge : 1.995 // Almost 2: important to have all the cube's vertices with a unit of 1 + xExtent: edge + yExtent: edge + zExtent: edge + } + PhongAlphaMaterial { + id: greyMaterial + property color base: "#fff" + ambient: base + alpha: 0.15 + + // Pretty convincing combination + blendFunctionArg: BlendEquation.Add + sourceRgbArg: BlendEquationArguments.SourceAlpha + sourceAlphaArg: BlendEquationArguments.OneMinusSourceAlpha + destinationRgbArg: BlendEquationArguments.DestinationColor + destinationAlphaArg: BlendEquationArguments.OneMinusSourceAlpha + } } - PhongAlphaMaterial { - id: material - property color base: "#dfe233" - ambient: base - alpha: 0.3 + + Entity { + components: [edges, orangeMaterial] + + PhongMaterial { + id: orangeMaterial + property color base: "#f49b2b" + ambient: base + } + + GeometryRenderer { + id: edges + primitiveType: GeometryRenderer.Lines + geometry: Geometry { + Attribute { + id: boundingBoxPosition + attributeType: Attribute.VertexAttribute + vertexBaseType: Attribute.Float + vertexSize: 3 + count: 24 + name: defaultPositionAttributeName + buffer: Buffer { + type: Buffer.VertexBuffer + data: new Float32Array([ + 1.0, 1.0, 1.0, + 1.0, -1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, -1.0, + 1.0, 1.0, 1.0, + -1.0, 1.0, 1.0, + -1.0, -1.0, -1.0, + -1.0, 1.0, -1.0, + -1.0, -1.0, -1.0, + 1.0, -1.0, -1.0, + -1.0, -1.0, -1.0, + -1.0, -1.0, 1.0, + 1.0, -1.0, 1.0, + 1.0, -1.0, -1.0, + 1.0, 1.0, -1.0, + 1.0, -1.0, -1.0, + -1.0, 1.0, 1.0, + -1.0, 1.0, -1.0, + 1.0, -1.0, 1.0, + -1.0, -1.0, 1.0, + -1.0, 1.0, 1.0, + -1.0, -1.0, 1.0, + -1.0, 1.0, -1.0, + 1.0, 1.0, -1.0 + ]) + } + } + boundingVolumePositionAttribute: boundingBoxPosition + } + } } } \ No newline at end of file diff --git a/meshroom/ui/qml/Viewer3D/Viewer3D.qml b/meshroom/ui/qml/Viewer3D/Viewer3D.qml index cead957496..597417d4fc 100644 --- a/meshroom/ui/qml/Viewer3D/Viewer3D.qml +++ b/meshroom/ui/qml/Viewer3D/Viewer3D.qml @@ -215,7 +215,7 @@ FocusScope { filterMode: LayerFilter.AcceptAnyMatchingLayers layers: [drawOnFront] RenderStateSet { - renderStates: DepthTest { depthFunction: DepthTest.Equal } + renderStates: DepthTest { depthFunction: DepthTest.GreaterOrEqual } } } } From c45d29976bd912f366c5978f8ff8b2d4f671474d Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Tue, 21 Jul 2020 16:24:03 +0200 Subject: [PATCH 25/51] [ui] Viewer3D: specific bounding box options in the Inspector3D --- meshroom/ui/qml/Viewer3D/Inspector3D.qml | 10 +++---- meshroom/ui/qml/Viewer3D/MediaLibrary.qml | 34 +++++++++++------------ 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/Inspector3D.qml b/meshroom/ui/qml/Viewer3D/Inspector3D.qml index 7f6bbc883a..9f7973ad77 100644 --- a/meshroom/ui/qml/Viewer3D/Inspector3D.qml +++ b/meshroom/ui/qml/Viewer3D/Inspector3D.qml @@ -284,19 +284,19 @@ FloatingPane { } } - // Transform visibility (bbox for meshing) + // BoundingBox visibility (if meshing node) MaterialToolButton { - visible: model.hasTransform + visible: model.hasBoundingBox enabled: model.visible Layout.alignment: Qt.AlignTop Layout.fillHeight: true text: MaterialIcons.transform font.pointSize: 10 - ToolTip.text: model.displayTransform ? "Hide BBox" : "Show BBox" + ToolTip.text: model.displayBoundingBox ? "Hide BBox" : "Show BBox" flat: true - opacity: model.visible ? (model.displayTransform ? 1.0 : 0.6) : 0.6 + opacity: model.visible ? (model.displayBoundingBox ? 1.0 : 0.6) : 0.6 onClicked: { - model.displayTransform = !model.displayTransform + model.displayBoundingBox = !model.displayBoundingBox } } diff --git a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml index 8f526a7415..363d591597 100644 --- a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml +++ b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml @@ -46,8 +46,8 @@ Entity { "valid": true, "label": "", "visible": true, - "hasTransform": false, - "displayTransform": true, + "hasBoundingBox": false, // for Meshing node only + "displayBoundingBox": true, // for Meshing node only "section": "", "attribute": null, "entity": null, @@ -156,20 +156,20 @@ Entity { delegate: MediaLoader { id: mediaLoader + // Get the node property var currentNode: model.attribute ? model.attribute.node : null property string nodeType: currentNode ? currentNode.nodeType: null - property bool hasTransform: { - if(nodeType !== "Meshing") // Cannot have a Transform - return false - const value = currentNode ? currentNode.attribute("useBoundingBox").value : null - if (value !== null) - mediaLoader.hasTransformPropertyChanged(value) - return value - } - property bool displayTransform: model.displayTransform - signal hasTransformPropertyChanged(bool transform) - onHasTransformPropertyChanged: model.hasTransform = transform + // Specific properties to the Meshing node (declared and initialiazed for every MediaLoader anyway) + property bool hasBoundingBox: { + if(nodeType === "Meshing") { // Can have a BoundingBox + const value = currentNode.attribute("useBoundingBox") ? currentNode.attribute("useBoundingBox").value : false + model.hasBoundingBox = value + return value + } + return false + } + property bool displayBoundingBox: model.displayBoundingBox // whether MediaLoader has been fully instantiated by the NodeInstantiator property bool fullyInstantiated: false @@ -210,12 +210,12 @@ Entity { onObjectRemoved: remove(idx) } - // BoundingBox: display bounding box for meshing computation + // BoundingBox: display bounding box for MESHING computation // note: use a NodeInstantiator to evaluate if the current node is a MESHING node and if the checkbox is active NodeInstantiator { id: boundingBoxInstantiator property var currentNode: mediaLoader.currentNode - active: currentNode.nodeType === "Meshing" && currentNode.attribute("useBoundingBox").value + active: mediaLoader.hasBoundingBox model: 1 MeshingBoundingBox { @@ -223,7 +223,7 @@ Entity { frontLayerComponent: root.frontLayerComponent window: root.window currentMeshingNode: boundingBoxInstantiator.currentNode - enabled: mediaLoader.displayTransform + enabled: mediaLoader.displayBoundingBox } } @@ -266,8 +266,6 @@ Entity { // if external media failed to open, remove element from model if(!attribute && !object) remove(index) - // tell the model if it has a Transform (Gizmo) - model.hasTransform = hasTransform } onCurrentSourceChanged: { From f746f0bf9e4cc952dede0d47f888456659069a9d Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Wed, 22 Jul 2020 11:26:18 +0200 Subject: [PATCH 26/51] [ui] Viewer3D: TransformGizmo - better mouse control --- meshroom/ui/components/scene3D.py | 29 ++++ meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 135 ++++++++---------- .../ui/qml/Viewer3D/TransformGizmoPicker.qml | 1 + 3 files changed, 89 insertions(+), 76 deletions(-) diff --git a/meshroom/ui/components/scene3D.py b/meshroom/ui/components/scene3D.py index c117799a95..724bf39c69 100644 --- a/meshroom/ui/components/scene3D.py +++ b/meshroom/ui/components/scene3D.py @@ -211,6 +211,35 @@ def computeModelMatrixWithEuler(self, translation, rotation, scale): return modelMat + @Slot(QVector3D, QMatrix4x4, Qt3DRender.QCamera, QSize, result=float) + def computeScaleUnitFromModelMatrix(self, axis, modelMat, camera, windowSize): + """ Compute the length of the screen projected vector axis unit transformed by the model matrix. + Args: + axis (QVector3D): chosen axis ((1,0,0) or (0,1,0) or (0,0,1)) + modelMat (QMatrix4x4): model matrix used for the transformation + camera (QCamera): camera viewing the scene + windowSize (QSize): size of the window in pixels + Returns: + float: length (in pixels) + """ + decomposition = self.decomposeModelMatrix(modelMat) + + posMat = QMatrix4x4() + posMat.translate(decomposition.get("translation")) + + rotMat = self.quaternionToRotationMatrix(decomposition.get("quaternion")) + + unitScaleModelMat = posMat * rotMat * QMatrix4x4() + + worldCenterPoint = unitScaleModelMat.map(QVector4D(0,0,0,1)) + worldAxisUnitPoint = unitScaleModelMat.map(QVector4D(axis.x(),axis.y(),axis.z(),1)) + screenCenter2D = self.pointFromWorldToScreen(worldCenterPoint, camera, windowSize) + screenAxisUnitPoint2D = self.pointFromWorldToScreen(worldAxisUnitPoint, camera, windowSize) + + screenVector = QVector2D(screenAxisUnitPoint2D.x() - screenCenter2D.x(), -(screenAxisUnitPoint2D.y() - screenCenter2D.y())) + + value = screenVector.length() + return value if (value and value > 10) else 10 # Threshold to avoid problems in extreme case #---------- "Private" Methods ----------# diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index bfe3964157..736c8bdf60 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -54,6 +54,14 @@ Entity { SCALE } + function convertAxisEnum(axis) { + switch(axis) { + case TransformGizmo.Axis.X: return Qt.vector3d(1,0,0) + case TransformGizmo.Axis.Y: return Qt.vector3d(0,1,0) + case TransformGizmo.Axis.Z: return Qt.vector3d(0,0,1) + } + } + /***** TRANSFORMATIONS (using local vars) *****/ function doRelativeTranslation(initialModelMatrix, translateVec) { @@ -121,81 +129,55 @@ Entity { root.focusGizmoPriority = true // Get the selected axis - let pickedAxis - switch(objectPicker.gizmoAxis) { - case TransformGizmo.Axis.X: pickedAxis = Qt.vector3d(1,0,0); break - case TransformGizmo.Axis.Y: pickedAxis = Qt.vector3d(0,1,0); break - case TransformGizmo.Axis.Z: pickedAxis = Qt.vector3d(0,0,1); break + const pickedAxis = convertAxisEnum(objectPicker.gizmoAxis) + + // If it is a TRANSLATION or a SCALE + if(objectPicker.gizmoType === TransformGizmo.Type.POSITION || objectPicker.gizmoType === TransformGizmo.Type.SCALE) { + // Compute the current vector PickedPoint -> CurrentMousePoint + const pickedPosition = objectPicker.screenPoint + const mouseVector = Qt.vector2d(mouse.x - pickedPosition.x, -(mouse.y - pickedPosition.y)) + + // Transform the positive picked axis vector from World Coord to Screen Coord + const gizmoLocalPointOnAxis = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 1)) + const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) + const screenPoint2D = Transformations3DHelper.pointFromWorldToScreen(gizmoLocalPointOnAxis, camera, windowSize) + const screenCenter2D = Transformations3DHelper.pointFromWorldToScreen(gizmoCenterPoint, camera, windowSize) + const screenAxisVector = Qt.vector2d(screenPoint2D.x - screenCenter2D.x, -(screenPoint2D.y - screenCenter2D.y)) + + // Get the cosinus of the angle from the screenAxisVector to the mouseVector + const cosAngle = screenAxisVector.dotProduct(mouseVector) / (screenAxisVector.length() * mouseVector.length()) + const offset = cosAngle * mouseVector.length() / objectPicker.scaleUnit + + // Do the transformation + if(objectPicker.gizmoType === TransformGizmo.Type.POSITION && offset !== 0) + doRelativeTranslation(objectPicker.modelMatrix, pickedAxis.times(offset)) // Do a translation from the initial Object Model Matrix when we picked the gizmo + else if (objectPicker.gizmoType === TransformGizmo.Type.SCALE && offset !== 0) + doRelativeScale(objectPicker.modelMatrix, pickedAxis.times(offset)) // Do a scale from the initial Object Model Matrix when we picked the gizmo + + return } + else if(objectPicker.gizmoType === TransformGizmo.Type.ROTATION) { + // Get Screen Coordinates of the gizmo center + const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) + const screenCenter2D = Transformations3DHelper.pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize) - switch(objectPicker.gizmoType) { - case TransformGizmo.Type.POSITION: { - const sensibility = 0.02 - - // Compute the current vector PickedPoint -> CurrentMousePoint - const pickedPosition = objectPicker.screenPoint - const mouseVector = Qt.vector2d(mouse.x - pickedPosition.x, -(mouse.y - pickedPosition.y)) - - // Transform the positive picked axis vector from World Coord to Screen Coord - const gizmoLocalPointOnAxis = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 1)) - const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) - const screenPoint2D = Transformations3DHelper.pointFromWorldToScreen(gizmoLocalPointOnAxis, camera, windowSize) - const screenCenter2D = Transformations3DHelper.pointFromWorldToScreen(gizmoCenterPoint, camera, windowSize) - const screenAxisVector = Qt.vector2d(screenPoint2D.x - screenCenter2D.x, -(screenPoint2D.y - screenCenter2D.y)) - - // Get the cosinus of the angle from the screenAxisVector to the mouseVector - const cosAngle = screenAxisVector.dotProduct(mouseVector) / (screenAxisVector.length() * mouseVector.length()) - const offset = cosAngle * mouseVector.length() * sensibility - - // If the mouse is not at the same spot as the pickedPoint, we do translation - if (offset) doRelativeTranslation(objectPicker.modelMatrix, pickedAxis.times(offset)) // Do a translation from the initial Object Model Matrix when we picked the gizmo - - return - } - - case TransformGizmo.Type.ROTATION: { - // Get Screen Coordinates of the gizmo center - const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) - const screenCenter2D = Transformations3DHelper.pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize) - - // Get the vector screenCenter2D -> PickedPoint - const originalVector = Qt.vector2d(objectPicker.screenPoint.x - screenCenter2D.x, -(objectPicker.screenPoint.y - screenCenter2D.y)) - - // Compute the current vector screenCenter2D -> CurrentMousePoint - const mouseVector = Qt.vector2d(mouse.x - screenCenter2D.x, -(mouse.y - screenCenter2D.y)) + // Get the vector screenCenter2D -> PickedPoint + const originalVector = Qt.vector2d(objectPicker.screenPoint.x - screenCenter2D.x, -(objectPicker.screenPoint.y - screenCenter2D.y)) - // Get the angle from the originalVector to the mouseVector - const angle = Math.atan2(-originalVector.y*mouseVector.x + originalVector.x*mouseVector.y, originalVector.x*mouseVector.x + originalVector.y*mouseVector.y) * 180 / Math.PI + // Compute the current vector screenCenter2D -> CurrentMousePoint + const mouseVector = Qt.vector2d(mouse.x - screenCenter2D.x, -(mouse.y - screenCenter2D.y)) - // Get the orientation of the gizmo in function of the camera - const gizmoLocalAxisVector = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 0)) - const gizmoToCameraVector = camera.position.toVector4d().minus(gizmoCenterPoint) - const orientation = gizmoLocalAxisVector.dotProduct(gizmoToCameraVector) > 0 ? 1 : -1 + // Get the angle from the originalVector to the mouseVector + const angle = Math.atan2(-originalVector.y*mouseVector.x + originalVector.x*mouseVector.y, originalVector.x*mouseVector.x + originalVector.y*mouseVector.y) * 180 / Math.PI - if (angle !== 0) doRelativeRotation(objectPicker.modelMatrix, pickedAxis, angle*orientation) // Do a rotation from the initial Object Model Matrix when we picked the gizmo - - return - } + // Get the orientation of the gizmo in function of the camera + const gizmoLocalAxisVector = gizmoDisplayTransform.matrix.times(Qt.vector4d(pickedAxis.x, pickedAxis.y, pickedAxis.z, 0)) + const gizmoToCameraVector = camera.position.toVector4d().minus(gizmoCenterPoint) + const orientation = gizmoLocalAxisVector.dotProduct(gizmoToCameraVector) > 0 ? 1 : -1 - case TransformGizmo.Type.SCALE: { - const sensibility = 0.05 + if (angle !== 0) doRelativeRotation(objectPicker.modelMatrix, pickedAxis, angle*orientation) // Do a rotation from the initial Object Model Matrix when we picked the gizmo - // Get Screen Coordinates of the gizmo center - const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) - const screenCenter2D = Transformations3DHelper.pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize) - - // Compute the scale unit - const scaleUnit = screenCenter2D.minus(Qt.vector2d(objectPicker.screenPoint.x, objectPicker.screenPoint.y)).length() - - // Compute the current vector screenCenter2D -> CurrentMousePoint - const mouseVector = Qt.vector2d(mouse.x - screenCenter2D.x, -(mouse.y - screenCenter2D.y)) - let offset = (mouseVector.length() - scaleUnit) * sensibility - offset = (offset < 0) ? offset * 3 : offset // Used to make it more sensible when we want to reduce the scale (because the action field is shorter) - - if (offset) doRelativeScale(objectPicker.modelMatrix, pickedAxis.times(offset)) // Do a scale from the initial Object Model Matrix when we picked the gizmo - - return - } + return } } @@ -272,7 +254,7 @@ Entity { case TransformGizmo.Axis.Z: return "#3387e2" // Blue } } - property real lineRadius: 0.015 + property real lineRadius: 0.011 // SCALE ENTITY Entity { @@ -321,7 +303,7 @@ Entity { CuboidMesh { id: cubeScaleMesh - property real edge: 0.07 + property real edge: 0.06 xExtent: edge yExtent: edge zExtent: edge @@ -366,7 +348,8 @@ Entity { gizmoType: TransformGizmo.Type.SCALE onPickedChanged: { - this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) // Save the current transformations + this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) // Save the current transformations of the OBJECT + this.scaleUnit = Transformations3DHelper.computeScaleUnitFromModelMatrix(convertAxisEnum(gizmoAxis), gizmoDisplayTransform.matrix, camera, root.windowSize) // Compute a scale unit at picking time root.pickedChanged(picker.isPressed) // Used to prevent camera transformations } } @@ -379,11 +362,11 @@ Entity { ConeMesh { id: coneMesh - bottomRadius : 0.04 + bottomRadius : 0.035 topRadius : 0.001 hasBottomEndcap : true hasTopEndcap : true - length : 0.15 + length : 0.13 rings : 2 slices : 8 } @@ -425,8 +408,8 @@ Entity { gizmoType: TransformGizmo.Type.POSITION onPickedChanged: { - // this.modelMatrix = copyMatrix(objectTransform.matrix) // Save the current transformations - this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) // Save the current transformations + this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) // Save the current transformations of the OBJECT + this.scaleUnit = Transformations3DHelper.computeScaleUnitFromModelMatrix(convertAxisEnum(gizmoAxis), gizmoDisplayTransform.matrix, camera, root.windowSize) // Compute a scale unit at picking time root.pickedChanged(picker.isPressed) // Used to prevent camera transformations } } @@ -471,7 +454,7 @@ Entity { gizmoType: TransformGizmo.Type.ROTATION onPickedChanged: { - this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) // Save the current transformations + this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) // Save the current transformations of the OBJECT root.pickedChanged(picker.isPressed) // Used to prevent camera transformations } } diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml index d472119dfd..7eb649be36 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmoPicker.qml @@ -15,6 +15,7 @@ ObjectPicker { property int gizmoType property point screenPoint property var modelMatrix + property real scaleUnit property int button signal pickedChanged(var picker) From e96aae532658a7bad12b9b9aeea449dbdbb49cef Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Wed, 22 Jul 2020 12:19:53 +0200 Subject: [PATCH 27/51] [ui] Viewer3D: TransformGizmo - better right click menu --- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 60 +++++++++++++-------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index 736c8bdf60..672b523b34 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -9,7 +9,7 @@ import Utils 1.0 Entity { id: root - property real gizmoScale: 0.15 + readonly property alias gizmoScale: gizmoScaleLookSlider.value property Camera camera property var windowSize property var frontLayerComponent @@ -87,11 +87,15 @@ Entity { mat.m41, mat.m42, mat.m43, 1 ) gizmoDisplayTransform.setMatrix(newMat) + + emitGizmoChanged() } function resetRotation() { // Here, we can change the rotation property (but not rotationX, rotationY and rotationZ because they can be used in upper-level bindings) gizmoDisplayTransform.rotation = Qt.quaternion(1,0,0,0) // Reset gizmo matrix and object matrix with binding + + emitGizmoChanged() } function resetScale() { @@ -103,14 +107,7 @@ Entity { -(objectTransform.scale3D.z - 1) ) doRelativeScale(modelMat, scaleDiff) - } - function resetATransformType(transformType) { - switch(transformType) { - case TransformGizmo.Type.POSITION: resetTranslation(); break - case TransformGizmo.Type.ROTATION: resetRotation(); break - case TransformGizmo.Type.SCALE: resetScale(); break - } emitGizmoChanged() } @@ -182,7 +179,6 @@ Entity { } if(objectPicker && objectPicker.button === Qt.RightButton) { - resetMenu.updateTypeBeforePopup(objectPicker.gizmoType) resetMenu.popup(window) } } @@ -196,21 +192,41 @@ Entity { Menu { id: resetMenu - property int transformType - property string transformTypeToDisplay - - function updateTypeBeforePopup(type) { - resetMenu.transformType = type - switch(type) { - case TransformGizmo.Type.POSITION: resetMenu.transformTypeToDisplay = "Translation"; break - case TransformGizmo.Type.ROTATION: resetMenu.transformTypeToDisplay = "Rotation"; break - case TransformGizmo.Type.SCALE: resetMenu.transformTypeToDisplay = "Scale"; break - } - } MenuItem { - text: `Reset ${resetMenu.transformTypeToDisplay}` - onTriggered: resetATransformType(resetMenu.transformType) + text: `Reset Translation` + onTriggered: resetTranslation() + } + MenuItem { + text: `Reset Rotation` + onTriggered: resetRotation() + } + MenuItem { + text: `Reset Scale` + onTriggered: resetScale() + } + MenuItem { + text: `Reset All` + onTriggered: { + resetTranslation() + resetRotation() + resetScale() + } + } + MenuItem { + text: "Gizmo Scale Look" + Slider { + id: gizmoScaleLookSlider + anchors.right: parent.right + anchors.rightMargin: 10 + height: parent.height + width: parent.width * 0.40 + + from: 0.06 + to: 0.30 + stepSize: 0.01 + value: 0.15 + } } } From 266ac61c531bbc51e771e3a8a733c01b6f8b2a38 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Thu, 23 Jul 2020 12:26:25 +0200 Subject: [PATCH 28/51] [nodes] SfMTransform : add gizmo transformation --- meshroom/nodes/aliceVision/SfMTransform.py | 72 +++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/meshroom/nodes/aliceVision/SfMTransform.py b/meshroom/nodes/aliceVision/SfMTransform.py index f0b1c61f54..b7eff24ae5 100644 --- a/meshroom/nodes/aliceVision/SfMTransform.py +++ b/meshroom/nodes/aliceVision/SfMTransform.py @@ -34,12 +34,13 @@ class SfMTransform(desc.CommandLineNode): label='Transformation Method', description="Transformation method:\n" " * transformation: Apply a given transformation\n" + " * manual: Apply the gizmo transformation\n" " * auto_from_cameras: Use cameras\n" " * auto_from_landmarks: Use landmarks\n" " * from_single_camera: Use a specific camera as the origin of the coordinate system\n" " * from_markers: Align specific markers to custom coordinates", value='auto_from_landmarks', - values=['transformation', 'auto_from_cameras', 'auto_from_landmarks', 'from_single_camera', 'from_markers'], + values=['transformation', 'manual', 'auto_from_cameras', 'auto_from_landmarks', 'from_single_camera', 'from_markers'], exclusive=True, uid=[0], ), @@ -52,6 +53,75 @@ class SfMTransform(desc.CommandLineNode): value='', uid=[0], ), + desc.GroupAttribute( + name="transformGizmo", + label="Transform Gizmo Settings", + description="Translation, rotation and scale defined by the gizmo.", + groupDesc=[ + desc.GroupAttribute( + name="gizmoTranslation", + label="Translation", + description="Translation in space.", + groupDesc=[ + desc.FloatParam( + name="x", label="x", description="X Offset", + value=0.0, + uid=[0], + range=(-20.0, 20.0, 0.01) + ), + desc.FloatParam( + name="y", label="y", description="Y Offset", + value=0.0, + uid=[0], + range=(-20.0, 20.0, 0.01) + ), + desc.FloatParam( + name="z", label="z", description="Z Offset", + value=0.0, + uid=[0], + range=(-20.0, 20.0, 0.01) + ) + ], + joinChar="," + ), + desc.GroupAttribute( + name="gizmoRotation", + label="Euler Rotation", + description="Rotation in Euler degrees.", + groupDesc=[ + desc.FloatParam( + name="x", label="x", description="Euler X Rotation", + value=0.0, + uid=[0], + range=(-90.0, 90.0, 1) + ), + desc.FloatParam( + name="y", label="y", description="Euler Y Rotation", + value=0.0, + uid=[0], + range=(-180.0, 180.0, 1) + ), + desc.FloatParam( + name="z", label="z", description="Euler Z Rotation", + value=0.0, + uid=[0], + range=(-180.0, 180.0, 1) + ) + ], + joinChar="," + ), + desc.FloatParam( + name="gizmoScale", + label="Scale", + description="Uniform Scale.", + value=1.0, + uid=[0], + range=(0.0, 20.0, 0.01) + ) + ], + joinChar=",", + advanced=True + ), desc.ChoiceParam( name='landmarksDescriberTypes', label='Landmarks Describer Types', From 180b492ba12c00b148544377320e66f468cf39ef Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Thu, 23 Jul 2020 12:34:24 +0200 Subject: [PATCH 29/51] [ui] Viewer3D: add a SfMTransformGizmo - New SfMTransformGizmo entity. - Add a uniform scale option to the TransformGizmo. --- meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml | 1 + .../ui/qml/Viewer3D/SfMTransformGizmo.qml | 53 +++++++++++++++++++ meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 12 +++-- 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml diff --git a/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml b/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml index f282d41aeb..72ab7c3c73 100644 --- a/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml @@ -10,6 +10,7 @@ Entity { property DefaultCameraController sceneCameraController property Layer frontLayerComponent property var window + property alias uniformScale: transformGizmo.uniformScale // by default, if not set, the value is: false property TransformGizmo transformGizmo: TransformGizmo { id: transformGizmo camera: root.camera diff --git a/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml b/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml new file mode 100644 index 0000000000..e1b423feee --- /dev/null +++ b/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml @@ -0,0 +1,53 @@ +import Qt3D.Core 2.0 +import Qt3D.Render 2.9 +import Qt3D.Input 2.0 +import Qt3D.Extras 2.10 +import QtQuick 2.9 + +Entity { + id: root + property DefaultCameraController sceneCameraController + property Layer frontLayerComponent + property var window + property var currentSfMTransformNode: null + enabled: true + + EntityWithGizmo { + sceneCameraController: root.sceneCameraController + frontLayerComponent: root.frontLayerComponent + window: root.window + uniformScale: true // We want to make uniform scale transformations + + // Update node SfMTransform slider values when the gizmo has changed: translation, rotation, scale + transformGizmo.onGizmoChanged: { + _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("transformGizmo.gizmoTranslation.x"), translation.x) + _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("transformGizmo.gizmoTranslation.y"), translation.y) + _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("transformGizmo.gizmoTranslation.z"), translation.z) + + _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("transformGizmo.gizmoRotation.x"), rotation.x) + _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("transformGizmo.gizmoRotation.y"), rotation.y) + _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("transformGizmo.gizmoRotation.z"), rotation.z) + + // Only one scale is needed since the scale is uniform + _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("transformGizmo.gizmoScale"), scale.x) + } + + // Automatically evalutate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse. + // When the gizmo has changed (with mouse), the new values are set to the node, the priority is given back to the node and the Transform is re-evaluated once with those values. + property var nodeTranslation : Qt.vector3d( + root.currentSfMTransformNode.attribute("transformGizmo.gizmoTranslation.x").value, + root.currentSfMTransformNode.attribute("transformGizmo.gizmoTranslation.y").value, + root.currentSfMTransformNode.attribute("transformGizmo.gizmoTranslation.z").value + ) + property var nodeRotationX: root.currentSfMTransformNode.attribute("transformGizmo.gizmoRotation.x").value + property var nodeRotationY: root.currentSfMTransformNode.attribute("transformGizmo.gizmoRotation.y").value + property var nodeRotationZ: root.currentSfMTransformNode.attribute("transformGizmo.gizmoRotation.z").value + property var nodeScale: root.currentSfMTransformNode.attribute("transformGizmo.gizmoScale").value + + transformGizmo.gizmoDisplayTransform.translation: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.translation : nodeTranslation + transformGizmo.gizmoDisplayTransform.rotationX: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationX : nodeRotationX + transformGizmo.gizmoDisplayTransform.rotationY: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationY : nodeRotationY + transformGizmo.gizmoDisplayTransform.rotationZ: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationZ : nodeRotationZ + transformGizmo.objectTransform.scale3D: transformGizmo.focusGizmoPriority ? transformGizmo.objectTransform.scale3D : Qt.vector3d(nodeScale, nodeScale, nodeScale) + } +} \ No newline at end of file diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index 672b523b34..cb33a0a43f 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -14,6 +14,7 @@ Entity { property var windowSize property var frontLayerComponent property var window + property bool uniformScale: false // by default, the scale is not uniform property bool focusGizmoPriority: false // If true, it is used to give the priority to the current transformation (and not to a upper-level binding) property Transform gizmoDisplayTransform: Transform { id: gizmoDisplayTransform @@ -146,10 +147,15 @@ Entity { const offset = cosAngle * mouseVector.length() / objectPicker.scaleUnit // Do the transformation - if(objectPicker.gizmoType === TransformGizmo.Type.POSITION && offset !== 0) + if(objectPicker.gizmoType === TransformGizmo.Type.POSITION && offset !== 0) { doRelativeTranslation(objectPicker.modelMatrix, pickedAxis.times(offset)) // Do a translation from the initial Object Model Matrix when we picked the gizmo - else if (objectPicker.gizmoType === TransformGizmo.Type.SCALE && offset !== 0) - doRelativeScale(objectPicker.modelMatrix, pickedAxis.times(offset)) // Do a scale from the initial Object Model Matrix when we picked the gizmo + } + else if(objectPicker.gizmoType === TransformGizmo.Type.SCALE && offset !== 0) { + if(root.uniformScale) + doRelativeScale(objectPicker.modelMatrix, Qt.vector3d(1,1,1).times(offset)) // Do a uniform scale from the initial Object Model Matrix when we picked the gizmo + else + doRelativeScale(objectPicker.modelMatrix, pickedAxis.times(offset)) // Do a scale on one axis from the initial Object Model Matrix when we picked the gizmo + } return } From 9fd90745e2c2b2bf28c01e97f1d8afa7e1cc3864 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Thu, 23 Jul 2020 12:40:04 +0200 Subject: [PATCH 30/51] [ui] Viewer3D: add SfMTransformGizmo to MediaLibrary and Inspector3D --- meshroom/ui/qml/Viewer3D/Inspector3D.qml | 16 ++++++++++++ meshroom/ui/qml/Viewer3D/MediaLibrary.qml | 32 ++++++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/meshroom/ui/qml/Viewer3D/Inspector3D.qml b/meshroom/ui/qml/Viewer3D/Inspector3D.qml index 9f7973ad77..48198fbb08 100644 --- a/meshroom/ui/qml/Viewer3D/Inspector3D.qml +++ b/meshroom/ui/qml/Viewer3D/Inspector3D.qml @@ -300,6 +300,22 @@ FloatingPane { } } + // Transform visibility (if SfMTransform node) + MaterialToolButton { + visible: model.hasTransform + enabled: model.visible + Layout.alignment: Qt.AlignTop + Layout.fillHeight: true + text: MaterialIcons._3d_rotation + font.pointSize: 10 + ToolTip.text: model.displayTransform ? "Hide Gizmo" : "Show Gizmo" + flat: true + opacity: model.visible ? (model.displayTransform ? 1.0 : 0.6) : 0.6 + onClicked: { + model.displayTransform = !model.displayTransform + } + } + // Media label and info Item { implicitHeight: childrenRect.height diff --git a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml index 363d591597..686632ce87 100644 --- a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml +++ b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml @@ -48,6 +48,8 @@ Entity { "visible": true, "hasBoundingBox": false, // for Meshing node only "displayBoundingBox": true, // for Meshing node only + "hasTransform": false, // for SfMTransform node only + "displayTransform": true, // for SfMTransform node only "section": "", "attribute": null, "entity": null, @@ -160,7 +162,7 @@ Entity { property var currentNode: model.attribute ? model.attribute.node : null property string nodeType: currentNode ? currentNode.nodeType: null - // Specific properties to the Meshing node (declared and initialiazed for every MediaLoader anyway) + // Specific properties to the Meshing node (declared and initialized for every MediaLoader anyway) property bool hasBoundingBox: { if(nodeType === "Meshing") { // Can have a BoundingBox const value = currentNode.attribute("useBoundingBox") ? currentNode.attribute("useBoundingBox").value : false @@ -171,6 +173,17 @@ Entity { } property bool displayBoundingBox: model.displayBoundingBox + // Specific properties to the SfMTransform node (declared and initialized for every MediaLoader anyway) + property bool hasTransform: { + if(nodeType === "SfMTransform") { // Can have a Transform + const value = currentNode.attribute("method") ? currentNode.attribute("method").value === "manual" : false + model.hasTransform = value + return value + } + return false + } + property bool displayTransform: model.displayTransform + // whether MediaLoader has been fully instantiated by the NodeInstantiator property bool fullyInstantiated: false @@ -227,6 +240,23 @@ Entity { } } + // Transform: display a TransformGizmo for SfMTransform node only + // note: use a NodeInstantiator to evaluate if the current node is a SfMTransform node and if the transform mode is set to Manual + NodeInstantiator { + id: sfmTransformGizmoInstantiator + property var currentNode: mediaLoader.currentNode + active: mediaLoader.hasTransform + model: 1 + + SfMTransformGizmo { + sceneCameraController: root.sceneCameraController + frontLayerComponent: root.frontLayerComponent + window: root.window + currentSfMTransformNode: sfmTransformGizmoInstantiator.currentNode + enabled: mediaLoader.displayTransform + } + } + // 'visible' property drives media loading request onVisibleChanged: { // always request media loading if visible From 97fd076877995e70581703bf49c4c4e177a9e6ad Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Fri, 24 Jul 2020 12:56:51 +0200 Subject: [PATCH 31/51] [ui] Viewer3D: SfMTransformGizmo - real-time transformed input rendering - When SfMTransform uses manual method, we display in real-time the input on which we apply the gizmo transformation. - For now, the options "Additional Scale", "Apply Scale/Rotation/Translation" of the node are not handled. The purpose will be to disable them when the method is "manual". --- meshroom/nodes/aliceVision/SfMTransform.py | 2 +- meshroom/ui/qml/Viewer3D/MediaLibrary.qml | 330 +++++++++--------- .../ui/qml/Viewer3D/SfMTransformGizmo.qml | 3 + 3 files changed, 177 insertions(+), 158 deletions(-) diff --git a/meshroom/nodes/aliceVision/SfMTransform.py b/meshroom/nodes/aliceVision/SfMTransform.py index b7eff24ae5..c42a22c1e2 100644 --- a/meshroom/nodes/aliceVision/SfMTransform.py +++ b/meshroom/nodes/aliceVision/SfMTransform.py @@ -34,7 +34,7 @@ class SfMTransform(desc.CommandLineNode): label='Transformation Method', description="Transformation method:\n" " * transformation: Apply a given transformation\n" - " * manual: Apply the gizmo transformation\n" + " * manual: Apply the gizmo transformation (show the transformed input)\n" " * auto_from_cameras: Use cameras\n" " * auto_from_landmarks: Use landmarks\n" " * from_single_camera: Use a specific camera as the origin of the coordinate system\n" diff --git a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml index 686632ce87..7f8af379fe 100644 --- a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml +++ b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml @@ -155,204 +155,220 @@ Entity { id: instantiator model: m.mediaModel - delegate: MediaLoader { - id: mediaLoader + delegate: Entity { + id: instantiatedEntity + property alias fullyInstantiated: mediaLoader.fullyInstantiated + readonly property alias modelSource: mediaLoader.modelSource // Get the node property var currentNode: model.attribute ? model.attribute.node : null property string nodeType: currentNode ? currentNode.nodeType: null - // Specific properties to the Meshing node (declared and initialized for every MediaLoader anyway) + // Specific properties to the MESHING node (declared and initialized for every Entity anyway) property bool hasBoundingBox: { - if(nodeType === "Meshing") { // Can have a BoundingBox - const value = currentNode.attribute("useBoundingBox") ? currentNode.attribute("useBoundingBox").value : false - model.hasBoundingBox = value - return value - } + if(nodeType === "Meshing" && currentNode.attribute("useBoundingBox")) // Can have a BoundingBox + return currentNode.attribute("useBoundingBox").value return false } + onHasBoundingBoxChanged: model.hasBoundingBox = hasBoundingBox property bool displayBoundingBox: model.displayBoundingBox - // Specific properties to the SfMTransform node (declared and initialized for every MediaLoader anyway) + // Specific properties to the SFMTRANSFORM node (declared and initialized for every Entity anyway) property bool hasTransform: { - if(nodeType === "SfMTransform") { // Can have a Transform - const value = currentNode.attribute("method") ? currentNode.attribute("method").value === "manual" : false - model.hasTransform = value - return value - } + if(nodeType === "SfMTransform" && currentNode.attribute("method")) // Can have a Transform + return currentNode.attribute("method").value === "manual" return false } + onHasTransformChanged: model.hasTransform = hasTransform property bool displayTransform: model.displayTransform - // whether MediaLoader has been fully instantiated by the NodeInstantiator - property bool fullyInstantiated: false - - // explicitely store some attached model properties for outside use and ease binding - readonly property var attribute: model.attribute - readonly property int idx: index - readonly property var modelSource: attribute || model.source - readonly property bool visible: model.visible - - // multi-step binding to ensure MediaLoader source is properly - // updated when needed, whether raw source is valid or not - - // raw source path - readonly property string rawSource: attribute ? attribute.value : model.source - // whether dependencies are statified (applies for output/connected input attributes only) - readonly property bool dependencyReady: { - if(attribute && attribute.isOutput) - return attribute.node.globalStatus === "SUCCESS"; - if(attribute && attribute.isLink) - return attribute.linkParam.node.globalStatus === "SUCCESS"; - return true; - } - // source based on raw source + dependency status - readonly property string currentSource: dependencyReady ? rawSource : "" - // source based on currentSource + "requested" property - readonly property string finalSource: model.requested ? currentSource : "" - camera: root.camera - renderMode: root.renderMode - enabled: visible + // Create the media + MediaLoader { + id: mediaLoader - // QObject.destroyed signal is not accessible - // Use the object as NodeInstantiator model to be notified of its deletion - NodeInstantiator { - model: attribute - delegate: Entity { objectName: "DestructionWatcher [" + attribute.toString() + "]" } - onObjectRemoved: remove(idx) - } + // whether MediaLoader has been fully instantiated by the NodeInstantiator + property bool fullyInstantiated: false - // BoundingBox: display bounding box for MESHING computation - // note: use a NodeInstantiator to evaluate if the current node is a MESHING node and if the checkbox is active - NodeInstantiator { - id: boundingBoxInstantiator - property var currentNode: mediaLoader.currentNode - active: mediaLoader.hasBoundingBox - model: 1 + // explicitely store some attached model properties for outside use and ease binding + readonly property var attribute: model.attribute + readonly property int idx: index + readonly property var modelSource: attribute || model.source + readonly property bool visible: model.visible - MeshingBoundingBox { - sceneCameraController: root.sceneCameraController - frontLayerComponent: root.frontLayerComponent - window: root.window - currentMeshingNode: boundingBoxInstantiator.currentNode - enabled: mediaLoader.displayBoundingBox + // multi-step binding to ensure MediaLoader source is properly + // updated when needed, whether raw source is valid or not + + // raw source path + property string rawSource: attribute ? attribute.value : model.source + // whether dependencies are statified (applies for output/connected input attributes only) + readonly property bool dependencyReady: { + if(attribute && attribute.isOutput) + return attribute.node.globalStatus === "SUCCESS"; + if(attribute && attribute.isLink) + return attribute.linkParam.node.globalStatus === "SUCCESS"; + return true; + } + // source based on raw source + dependency status + property string currentSource: dependencyReady ? rawSource : "" + // source based on currentSource + "requested" property + property string finalSource: model.requested ? currentSource : "" + + // To use only if we want to draw the input source and not the current node output (Warning: to use with caution) + // There is maybe a better way to do this to avoid overwritting bindings which should be readonly properties + function drawInputSource() { + rawSource = Qt.binding(() => instantiatedEntity.currentNode.attribute("input").value) + currentSource = Qt.binding(() => rawSource) + finalSource = Qt.binding(() => rawSource) } - } - // Transform: display a TransformGizmo for SfMTransform node only - // note: use a NodeInstantiator to evaluate if the current node is a SfMTransform node and if the transform mode is set to Manual - NodeInstantiator { - id: sfmTransformGizmoInstantiator - property var currentNode: mediaLoader.currentNode - active: mediaLoader.hasTransform - model: 1 + camera: root.camera + renderMode: root.renderMode + enabled: visible - SfMTransformGizmo { - sceneCameraController: root.sceneCameraController - frontLayerComponent: root.frontLayerComponent - window: root.window - currentSfMTransformNode: sfmTransformGizmoInstantiator.currentNode - enabled: mediaLoader.displayTransform + // QObject.destroyed signal is not accessible + // Use the object as NodeInstantiator model to be notified of its deletion + NodeInstantiator { + model: attribute + delegate: Entity { objectName: "DestructionWatcher [" + attribute.toString() + "]" } + onObjectRemoved: remove(idx) } - } - // 'visible' property drives media loading request - onVisibleChanged: { - // always request media loading if visible - if(model.visible) - model.requested = true; - // only cancel loading request if media is not valid - // (a media won't be unloaded if already loaded, only hidden) - else if(!model.valid) - model.requested = false; - } + // 'visible' property drives media loading request + onVisibleChanged: { + // always request media loading if visible + if(model.visible) + model.requested = true; + // only cancel loading request if media is not valid + // (a media won't be unloaded if already loaded, only hidden) + else if(!model.valid) + model.requested = false; + } - function updateCacheAndModel(forceRequest) { - // don't cache explicitely unloaded media - if(model.requested && object && dependencyReady) { - // cache current object - if(cache.add(Filepath.urlToString(mediaLoader.source), object)); - object = null; + function updateCacheAndModel(forceRequest) { + // don't cache explicitely unloaded media + if(model.requested && object && dependencyReady) { + // cache current object + if(cache.add(Filepath.urlToString(mediaLoader.source), object)); + object = null; + } + updateModel(forceRequest); } - updateModel(forceRequest); - } - function updateModel(forceRequest) { - // update model's source path if input is an attribute - if(attribute) { - model.source = rawSource; + function updateModel(forceRequest) { + // update model's source path if input is an attribute + if(attribute) { + model.source = rawSource; + } + // auto-restore entity if raw source is in cache + model.requested = forceRequest || (!model.valid && model.requested) || cache.contains(rawSource); + model.valid = Filepath.exists(rawSource) && dependencyReady; } - // auto-restore entity if raw source is in cache - model.requested = forceRequest || (!model.valid && model.requested) || cache.contains(rawSource); - model.valid = Filepath.exists(rawSource) && dependencyReady; - } - Component.onCompleted: { - // keep 'source' -> 'entity' reference - m.sourceToEntity[modelSource] = mediaLoader; - // always request media loading when delegate has been created - updateModel(true); - // if external media failed to open, remove element from model - if(!attribute && !object) - remove(index) - } + Component.onCompleted: { + // keep 'source' -> 'entity' reference + m.sourceToEntity[modelSource] = mediaLoader; + // always request media loading when delegate has been created + updateModel(true); + // if external media failed to open, remove element from model + if(!attribute && !object) + remove(index) + } - onCurrentSourceChanged: { - updateCacheAndModel(false) - } + onCurrentSourceChanged: { + updateCacheAndModel(false) + } - onFinalSourceChanged: { - // update media visibility - // (useful if media was explicitly unloaded or hidden but loaded back from cache) - model.visible = model.requested; - - var cachedObject = cache.pop(rawSource); - cached = cachedObject !== undefined; - if(cached) { - object = cachedObject; - // only change cached object parent if mediaLoader has been fully instantiated - // by the NodeInstantiator; otherwise re-parenting will fail silently and the object will disappear... - // see "onFullyInstantiatedChanged" and parent NodeInstantiator's "onObjectAdded" - if(fullyInstantiated) { - object.parent = mediaLoader; + onFinalSourceChanged: { + // update media visibility + // (useful if media was explicitly unloaded or hidden but loaded back from cache) + model.visible = model.requested; + + var cachedObject = cache.pop(rawSource); + cached = cachedObject !== undefined; + if(cached) { + object = cachedObject; + // only change cached object parent if mediaLoader has been fully instantiated + // by the NodeInstantiator; otherwise re-parenting will fail silently and the object will disappear... + // see "onFullyInstantiatedChanged" and parent NodeInstantiator's "onObjectAdded" + if(fullyInstantiated) { + object.parent = mediaLoader; + } + } + mediaLoader.source = Filepath.stringToUrl(finalSource); + if(object) { + // bind media info to corresponding model roles + // (test for object validity to avoid error messages right after object has been deleted) + var boundProperties = ["vertexCount", "faceCount", "cameraCount", "textureCount"]; + boundProperties.forEach( function(prop){ + model[prop] = Qt.binding(function() { return object ? object[prop] : 0; }); + }) + } + else if(finalSource) { + // source was valid but no loader was created, remove element + remove(index); } } - mediaLoader.source = Filepath.stringToUrl(finalSource); - if(object) { - // bind media info to corresponding model roles - // (test for object validity to avoid error messages right after object has been deleted) - var boundProperties = ["vertexCount", "faceCount", "cameraCount", "textureCount"]; - boundProperties.forEach( function(prop){ - model[prop] = Qt.binding(function() { return object ? object[prop] : 0; }); - }) + + onFullyInstantiatedChanged: { + // delayed reparenting of object coming from the cache + if(object) + object.parent = mediaLoader; } - else if(finalSource) { - // source was valid but no loader was created, remove element - remove(index); + + onStatusChanged: { + model.status = status + // remove model entry for external media that failed to load + if(status === SceneLoader.Error && !model.attribute) + remove(index); } - } - onFullyInstantiatedChanged: { - // delayed reparenting of object coming from the cache - if(object) - object.parent = mediaLoader; + components: [ + ObjectPicker { + enabled: mediaLoader.enabled && pickingEnabled + hoverEnabled: false + onPressed: root.pressed(pick) + } + ] } - onStatusChanged: { - model.status = status - // remove model entry for external media that failed to load - if(status === SceneLoader.Error && !model.attribute) - remove(index); + // Transform: display a TransformGizmo for SfMTransform node only + // note: use a NodeInstantiator to evaluate if the current node is a SfMTransform node and if the transform mode is set to Manual + NodeInstantiator { + id: sfmTransformGizmoInstantiator + active: instantiatedEntity.hasTransform + model: 1 + + SfMTransformGizmo { + id: sfmTransformGizmoEntity + sceneCameraController: root.sceneCameraController + frontLayerComponent: root.frontLayerComponent + window: root.window + currentSfMTransformNode: instantiatedEntity.currentNode + enabled: mediaLoader.visible && instantiatedEntity.displayTransform + + Component.onCompleted: { + mediaLoader.drawInputSource() // Because we are sure we want to show the input in MANUAL mode only + Scene3DHelper.addComponent(mediaLoader, sfmTransformGizmoEntity.objectTransform) // Add the transform to the media to see real-time transformations + } + } } - components: [ - ObjectPicker { - enabled: mediaLoader.enabled && pickingEnabled - hoverEnabled: false - onPressed: root.pressed(pick) + // BoundingBox: display bounding box for MESHING computation + // note: use a NodeInstantiator to evaluate if the current node is a MESHING node and if the checkbox is active + NodeInstantiator { + id: boundingBoxInstantiator + active: instantiatedEntity.hasBoundingBox + model: 1 + + MeshingBoundingBox { + sceneCameraController: root.sceneCameraController + frontLayerComponent: root.frontLayerComponent + window: root.window + currentMeshingNode: instantiatedEntity.currentNode + enabled: mediaLoader.visible && instantiatedEntity.displayBoundingBox } - ] + } } onObjectAdded: { diff --git a/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml b/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml index e1b423feee..f9d9431d3e 100644 --- a/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml @@ -11,8 +11,11 @@ Entity { property var window property var currentSfMTransformNode: null enabled: true + + readonly property alias objectTransform: sfmTranformGizmoEntity.objectTransform // The Transform the object should use EntityWithGizmo { + id: sfmTranformGizmoEntity sceneCameraController: root.sceneCameraController frontLayerComponent: root.frontLayerComponent window: root.window From 9a7e4fe16d3d74aa1ea4aac1fc2e6de68ed0de26 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Thu, 30 Jul 2020 11:26:01 +0200 Subject: [PATCH 32/51] [nodes] Meshing: add enabled parameter to bounding box - Add enabled parameter to Bounding Box and update the major version of the node (important) --- meshroom/nodes/aliceVision/Meshing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meshroom/nodes/aliceVision/Meshing.py b/meshroom/nodes/aliceVision/Meshing.py index e62fa301b3..78d1a170e7 100644 --- a/meshroom/nodes/aliceVision/Meshing.py +++ b/meshroom/nodes/aliceVision/Meshing.py @@ -1,4 +1,4 @@ -__version__ = "5.0" +__version__ = "6.0" from meshroom.core import desc @@ -128,7 +128,7 @@ class Meshing(desc.CommandLineNode): ) ], joinChar=",", - advanced=True + enabled=lambda node: node.useBoundingBox.value, ), desc.BoolParam( name='estimateSpaceFromSfM', From 1c14159cde010cd3346b13475180ea83731137b6 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Thu, 30 Jul 2020 11:29:30 +0200 Subject: [PATCH 33/51] [nodes] SfMTransform: change name from gizmo to manual + enabled param --- meshroom/nodes/aliceVision/SfMTransform.py | 26 +++++++++------- .../ui/qml/Viewer3D/SfMTransformGizmo.qml | 30 +++++++++---------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/meshroom/nodes/aliceVision/SfMTransform.py b/meshroom/nodes/aliceVision/SfMTransform.py index c42a22c1e2..18b0324e66 100644 --- a/meshroom/nodes/aliceVision/SfMTransform.py +++ b/meshroom/nodes/aliceVision/SfMTransform.py @@ -1,4 +1,4 @@ -__version__ = "2.0" +__version__ = "3.0" from meshroom.core import desc @@ -52,14 +52,15 @@ class SfMTransform(desc.CommandLineNode): " * from_single_camera: Camera UID or image filename", value='', uid=[0], + enabled=lambda node: node.method.value == "transformation" or node.method.value == "from_single_camera", ), desc.GroupAttribute( - name="transformGizmo", - label="Transform Gizmo Settings", - description="Translation, rotation and scale defined by the gizmo.", + name="manualTransform", + label="Manual Transform (Gizmo)", + description="Translation, rotation (Euler ZXY) and uniform scale.", groupDesc=[ desc.GroupAttribute( - name="gizmoTranslation", + name="manualTranslation", label="Translation", description="Translation in space.", groupDesc=[ @@ -85,7 +86,7 @@ class SfMTransform(desc.CommandLineNode): joinChar="," ), desc.GroupAttribute( - name="gizmoRotation", + name="manualRotation", label="Euler Rotation", description="Rotation in Euler degrees.", groupDesc=[ @@ -111,7 +112,7 @@ class SfMTransform(desc.CommandLineNode): joinChar="," ), desc.FloatParam( - name="gizmoScale", + name="manualScale", label="Scale", description="Uniform Scale.", value=1.0, @@ -120,7 +121,7 @@ class SfMTransform(desc.CommandLineNode): ) ], joinChar=",", - advanced=True + enabled=lambda node: node.method.value == "manual", ), desc.ChoiceParam( name='landmarksDescriberTypes', @@ -158,21 +159,24 @@ class SfMTransform(desc.CommandLineNode): label='Scale', description='Apply scale transformation.', value=True, - uid=[0] + uid=[0], + enabled=lambda node: node.method.value != "manual", ), desc.BoolParam( name='applyRotation', label='Rotation', description='Apply rotation transformation.', value=True, - uid=[0] + uid=[0], + enabled=lambda node: node.method.value != "manual", ), desc.BoolParam( name='applyTranslation', label='Translation', description='Apply translation transformation.', value=True, - uid=[0] + uid=[0], + enabled=lambda node: node.method.value != "manual", ), desc.ChoiceParam( name='verboseLevel', diff --git a/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml b/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml index f9d9431d3e..62543eae01 100644 --- a/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml @@ -23,29 +23,29 @@ Entity { // Update node SfMTransform slider values when the gizmo has changed: translation, rotation, scale transformGizmo.onGizmoChanged: { - _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("transformGizmo.gizmoTranslation.x"), translation.x) - _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("transformGizmo.gizmoTranslation.y"), translation.y) - _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("transformGizmo.gizmoTranslation.z"), translation.z) + _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.x"), translation.x) + _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.y"), translation.y) + _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.z"), translation.z) - _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("transformGizmo.gizmoRotation.x"), rotation.x) - _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("transformGizmo.gizmoRotation.y"), rotation.y) - _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("transformGizmo.gizmoRotation.z"), rotation.z) + _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("manualTransform.manualRotation.x"), rotation.x) + _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("manualTransform.manualRotation.y"), rotation.y) + _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("manualTransform.manualRotation.z"), rotation.z) // Only one scale is needed since the scale is uniform - _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("transformGizmo.gizmoScale"), scale.x) + _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("manualTransform.manualScale"), scale.x) } // Automatically evalutate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse. // When the gizmo has changed (with mouse), the new values are set to the node, the priority is given back to the node and the Transform is re-evaluated once with those values. property var nodeTranslation : Qt.vector3d( - root.currentSfMTransformNode.attribute("transformGizmo.gizmoTranslation.x").value, - root.currentSfMTransformNode.attribute("transformGizmo.gizmoTranslation.y").value, - root.currentSfMTransformNode.attribute("transformGizmo.gizmoTranslation.z").value + root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.x").value, + root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.y").value, + root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.z").value ) - property var nodeRotationX: root.currentSfMTransformNode.attribute("transformGizmo.gizmoRotation.x").value - property var nodeRotationY: root.currentSfMTransformNode.attribute("transformGizmo.gizmoRotation.y").value - property var nodeRotationZ: root.currentSfMTransformNode.attribute("transformGizmo.gizmoRotation.z").value - property var nodeScale: root.currentSfMTransformNode.attribute("transformGizmo.gizmoScale").value + property var nodeRotationX: root.currentSfMTransformNode.attribute("manualTransform.manualRotation.x").value + property var nodeRotationY: root.currentSfMTransformNode.attribute("manualTransform.manualRotation.y").value + property var nodeRotationZ: root.currentSfMTransformNode.attribute("manualTransform.manualRotation.z").value + property var nodeScale: root.currentSfMTransformNode.attribute("manualTransform.manualScale").value transformGizmo.gizmoDisplayTransform.translation: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.translation : nodeTranslation transformGizmo.gizmoDisplayTransform.rotationX: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationX : nodeRotationX @@ -53,4 +53,4 @@ Entity { transformGizmo.gizmoDisplayTransform.rotationZ: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationZ : nodeRotationZ transformGizmo.objectTransform.scale3D: transformGizmo.focusGizmoPriority ? transformGizmo.objectTransform.scale3D : Qt.vector3d(nodeScale, nodeScale, nodeScale) } -} \ No newline at end of file +} From e9151b2ee01f4ed8141c3e383cd2e061b98ee01e Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 3 Aug 2020 16:03:22 +0200 Subject: [PATCH 34/51] [ui] Viewer3D: fix bbox disappearing when moving after computed --- meshroom/ui/qml/Viewer3D/MediaLibrary.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml index 7f8af379fe..5b644a38df 100644 --- a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml +++ b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml @@ -277,6 +277,10 @@ Entity { onCurrentSourceChanged: { updateCacheAndModel(false) + + // Avoid the bounding box to disappear when we move it after a mesh already computed + if(instantiatedEntity.hasBoundingBox && !currentSource) + model.visible = true } onFinalSourceChanged: { From cd89a491568f7a3b3a44b082fabd85680144575a Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Thu, 6 Aug 2020 18:07:57 +0200 Subject: [PATCH 35/51] [core] GroupAttribute: add new ways of setting value - Add: set value with JS Object - Add: set value from a JSON String --- meshroom/core/attribute.py | 14 ++++++++++---- meshroom/core/desc.py | 29 ++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/meshroom/core/attribute.py b/meshroom/core/attribute.py index d3756abbe3..1aae047935 100644 --- a/meshroom/core/attribute.py +++ b/meshroom/core/attribute.py @@ -410,10 +410,16 @@ def __getattr__(self, key): raise AttributeError(key) def _set_value(self, exportedValue): - self.desc.validateValue(exportedValue) - # set individual child attribute values - for key, value in exportedValue.items(): - self._value.get(key).value = value + value = self.desc.validateValue(exportedValue) + if isinstance(value, dict): + # set individual child attribute values + for key, v in exportedValue.items(): + self._value.get(key).value = v + elif isinstance(value, (list, tuple)): + for attrDesc, v in zip(self.desc._groupDesc, value): + self._value.get(attrDesc.name).value = v + else: + raise AttributeError("Failed to set on GroupAttribute: {}".format(str(exportedValue))) @Slot(str, result=Attribute) def childAttribute(self, key): diff --git a/meshroom/core/desc.py b/meshroom/core/desc.py index 4cf348cc6c..739cf41529 100644 --- a/meshroom/core/desc.py +++ b/meshroom/core/desc.py @@ -4,7 +4,8 @@ import math import os import psutil - +import PySide2 +import ast class Attribute(BaseObject): """ @@ -95,12 +96,26 @@ def __init__(self, groupDesc, name, label, description, group='allParams', advan groupDesc = Property(Variant, lambda self: self._groupDesc, constant=True) def validateValue(self, value): - """ Ensure value is a dictionary with keys compatible with the group description. """ - if not isinstance(value, dict): - raise ValueError('GroupAttribute only supports dict input values (param:{}, value:{}, type:{})'.format(self.name, value, type(value))) - invalidKeys = set(value.keys()).difference([attr.name for attr in self._groupDesc]) - if invalidKeys: - raise ValueError('Value contains key that does not match group description : {}'.format(invalidKeys)) + """ Ensure value is compatible with the group description and convert value if needed. """ + if isinstance(value, PySide2.QtQml.QJSValue): + # If we receive a QJSValue from QML + # Warning: it will only work with a single array of values (does not work with array of array, dictionnary) + value = ast.literal_eval(value.toString()) + elif isinstance(value, pyCompatibility.basestring): + # Alternative solution to set values from QML is to convert values to JSON string + # In this case, it works with all data types + value = ast.literal_eval(value) + + if isinstance(value, dict): + invalidKeys = set(value.keys()).difference([attr.name for attr in self._groupDesc]) + if invalidKeys: + raise ValueError('Value contains key that does not match group description : {}'.format(invalidKeys)) + elif isinstance(value, (list, tuple)): + if len(value) != len(self._groupDesc): + raise ValueError('Value contains incoherent number of values: desc size: {}, value size: {}'.format(len(self._groupDesc), len(value))) + else: + raise ValueError('GroupAttribute only supports dict input values (param:{}, value:{}, type:{}) or list/tuple'.format(self.name, value, type(value))) + return value def matchDescription(self, value, conform=False): From 0c9dc81a82d7a2c53b302c7cb74895451c79193a Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Fri, 7 Aug 2020 11:26:04 +0200 Subject: [PATCH 36/51] [core] fix validateValue and setValue for Attributes --- meshroom/core/attribute.py | 8 ++++---- meshroom/core/desc.py | 21 ++++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/meshroom/core/attribute.py b/meshroom/core/attribute.py index 1aae047935..7030f7cbd3 100644 --- a/meshroom/core/attribute.py +++ b/meshroom/core/attribute.py @@ -300,8 +300,8 @@ def _set_value(self, value): self._value = value # New value else: - self.desc.validateValue(value) - self.extend(value) + newValue = self.desc.validateValue(value) + self.extend(newValue) self.requestGraphUpdate() @raiseIfLink @@ -413,13 +413,13 @@ def _set_value(self, exportedValue): value = self.desc.validateValue(exportedValue) if isinstance(value, dict): # set individual child attribute values - for key, v in exportedValue.items(): + for key, v in value.items(): self._value.get(key).value = v elif isinstance(value, (list, tuple)): for attrDesc, v in zip(self.desc._groupDesc, value): self._value.get(attrDesc.name).value = v else: - raise AttributeError("Failed to set on GroupAttribute: {}".format(str(exportedValue))) + raise AttributeError("Failed to set on GroupAttribute: {}".format(str(value))) @Slot(str, result=Attribute) def childAttribute(self, key): diff --git a/meshroom/core/desc.py b/meshroom/core/desc.py index 739cf41529..218daec5d0 100644 --- a/meshroom/core/desc.py +++ b/meshroom/core/desc.py @@ -33,12 +33,12 @@ def __init__(self, name, label, description, value, advanced, uid, group, enable type = Property(str, lambda self: self.__class__.__name__, constant=True) def validateValue(self, value): - """ Return validated/conformed 'value'. + """ Return validated/conformed 'value'. Need to be implemented in derived classes. Raises: ValueError: if value does not have the proper type """ - return value + raise NotImplementedError("Attribute.validateValue is an abstract function that should be implemented in the derived class.") def matchDescription(self, value, conform=False): """ Returns whether the value perfectly match attribute's description. @@ -69,6 +69,14 @@ def __init__(self, elementDesc, name, label, description, group='allParams', adv joinChar = Property(str, lambda self: self._joinChar, constant=True) def validateValue(self, value): + if isinstance(value, PySide2.QtQml.QJSValue): + # Note: we could use isArray(), property("length").toInt() to retrieve all values + raise ValueError("ListAttribute.validateValue: cannot recognize QJSValue. Please, use JSON.stringify(value) in QML.") + elif isinstance(value, pyCompatibility.basestring): + # Alternative solution to set values from QML is to convert values to JSON string + # In this case, it works with all data types + value = ast.literal_eval(value) + if not isinstance(value, (list, tuple)): raise ValueError('ListAttribute only supports list/tuple input values (param:{}, value:{}, type:{})'.format(self.name, value, type(value))) return value @@ -98,9 +106,8 @@ def __init__(self, groupDesc, name, label, description, group='allParams', advan def validateValue(self, value): """ Ensure value is compatible with the group description and convert value if needed. """ if isinstance(value, PySide2.QtQml.QJSValue): - # If we receive a QJSValue from QML - # Warning: it will only work with a single array of values (does not work with array of array, dictionnary) - value = ast.literal_eval(value.toString()) + # Note: we could use isArray(), property("length").toInt() to retrieve all values + raise ValueError("GroupAttribute.validateValue: cannot recognize QJSValue. Please, use JSON.stringify(value) in QML.") elif isinstance(value, pyCompatibility.basestring): # Alternative solution to set values from QML is to convert values to JSON string # In this case, it works with all data types @@ -114,7 +121,7 @@ def validateValue(self, value): if len(value) != len(self._groupDesc): raise ValueError('Value contains incoherent number of values: desc size: {}, value size: {}'.format(len(self._groupDesc), len(value))) else: - raise ValueError('GroupAttribute only supports dict input values (param:{}, value:{}, type:{}) or list/tuple'.format(self.name, value, type(value))) + raise ValueError('GroupAttribute only supports dict/list/tuple input values (param:{}, value:{}, type:{})'.format(self.name, value, type(value))) return value @@ -184,7 +191,7 @@ def __init__(self, name, label, description, value, uid, group='allParams', adva def validateValue(self, value): try: - return bool(int(value)) # int cast is useful to handle string values ('0', '1') + return bool(int(value)) # int cast is useful to handle string values ('0', '1') except: raise ValueError('BoolParam only supports bool value (param:{}, value:{}, type:{})'.format(self.name, value, type(value))) From c979ed36c14fed3818a7bc302d1e39b16fd8f07d Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Fri, 7 Aug 2020 12:23:20 +0200 Subject: [PATCH 37/51] [ui] Components: fix typo in Scene3D --- meshroom/ui/components/scene3D.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/meshroom/ui/components/scene3D.py b/meshroom/ui/components/scene3D.py index 724bf39c69..9db47691d0 100644 --- a/meshroom/ui/components/scene3D.py +++ b/meshroom/ui/components/scene3D.py @@ -107,7 +107,7 @@ def rotate(self, lastPosition, currentPosition, dt): class Transformations3DHelper(QObject): - #---------- Exposed to QML ----------# + # ---------- Exposed to QML ---------- # @Slot(QVector4D, Qt3DRender.QCamera, QSize, result=QVector2D) def pointFromWorldToScreen(self, point, camera, windowSize): @@ -186,7 +186,7 @@ def modelMatrixToMatrices(self, modelMat): scaleMat = QMatrix4x4() scaleMat.scale(decomposition.get("scale")) - return { "position": posMat, "rotation": rotMat, "scale": scaleMat, "quaternion": decomposition.get("quaternion") } + return {"position": posMat, "rotation": rotMat, "scale": scaleMat, "quaternion": decomposition.get("quaternion")} @Slot(QVector3D, QVector3D, QVector3D, result=QMatrix4x4) def computeModelMatrixWithEuler(self, translation, rotation, scale): @@ -239,9 +239,9 @@ def computeScaleUnitFromModelMatrix(self, axis, modelMat, camera, windowSize): screenVector = QVector2D(screenAxisUnitPoint2D.x() - screenCenter2D.x(), -(screenAxisUnitPoint2D.y() - screenCenter2D.y())) value = screenVector.length() - return value if (value and value > 10) else 10 # Threshold to avoid problems in extreme case + return value if (value and value > 10) else 10 # Threshold to avoid problems in extreme case - #---------- "Private" Methods ----------# + # ---------- "Private" Methods ---------- # def copyMatrix4x4(self, mat): """ Make a deep copy of a QMatrix4x4. """ @@ -256,14 +256,14 @@ def decomposeModelMatrix(self, modelMat): quaternion = QQuaternion.fromDirection(modelMat.column(2).toVector3D(), modelMat.column(1).toVector3D()) scale = QVector3D(modelMat.column(0).length(), modelMat.column(1).length(), modelMat.column(2).length()) - return { "translation": translation, "quaternion": quaternion, "scale": scale } + return {"translation": translation, "quaternion": quaternion, "scale": scale} def quaternionToRotationMatrix(self, q): """ Return a rotation matrix from a quaternion. """ rotMat3x3 = q.toRotationMatrix() return QMatrix4x4( - rotMat3x3(0,0), rotMat3x3(0,1), rotMat3x3(0,2), 0, - rotMat3x3(1,0), rotMat3x3(1,1), rotMat3x3(1,2), 0, - rotMat3x3(2,0), rotMat3x3(2,1), rotMat3x3(2,2), 0, - 0, 0, 0, 1 + rotMat3x3(0, 0), rotMat3x3(0, 1), rotMat3x3(0, 2), 0, + rotMat3x3(1, 0), rotMat3x3(1, 1), rotMat3x3(1, 2), 0, + rotMat3x3(2, 0), rotMat3x3(2, 1), rotMat3x3(2, 2), 0, + 0, 0, 0, 1 ) From 7486f3b887bbfc49913ea235f951fa4ccfa7b9ed Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Fri, 7 Aug 2020 12:35:18 +0200 Subject: [PATCH 38/51] [ui] Viewer3D: update TransformGizmo signal with transformation type --- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 61 +++++++++++++-------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index cb33a0a43f..46aa91050c 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -27,14 +27,14 @@ Entity { } signal pickedChanged(bool pressed) - signal gizmoChanged(var translation, var rotation, var scale) + signal gizmoChanged(var translation, var rotation, var scale, int type) - function emitGizmoChanged() { + function emitGizmoChanged(type) { const translation = gizmoDisplayTransform.translation // Position in space const rotation = Qt.vector3d(gizmoDisplayTransform.rotationX, gizmoDisplayTransform.rotationY, gizmoDisplayTransform.rotationZ) // Euler angles const scale = objectTransform.scale3D // Scale of the object - gizmoChanged(translation, rotation, scale) + gizmoChanged(translation, rotation, scale, type) root.focusGizmoPriority = false } @@ -50,9 +50,10 @@ Entity { } enum Type { - POSITION, + TRANSLATION, ROTATION, - SCALE + SCALE, + ALL } function convertAxisEnum(axis) { @@ -63,6 +64,15 @@ Entity { } } + function convertTypeEnum(type) { + switch(type) { + case TransformGizmo.Type.TRANSLATION: return "TRANSLATION" + case TransformGizmo.Type.ROTATION: return "ROTATION" + case TransformGizmo.Type.SCALE: return "SCALE" + case TransformGizmo.Type.ALL: return "ALL" + } + } + /***** TRANSFORMATIONS (using local vars) *****/ function doRelativeTranslation(initialModelMatrix, translateVec) { @@ -88,15 +98,11 @@ Entity { mat.m41, mat.m42, mat.m43, 1 ) gizmoDisplayTransform.setMatrix(newMat) - - emitGizmoChanged() } function resetRotation() { // Here, we can change the rotation property (but not rotationX, rotationY and rotationZ because they can be used in upper-level bindings) gizmoDisplayTransform.rotation = Qt.quaternion(1,0,0,0) // Reset gizmo matrix and object matrix with binding - - emitGizmoChanged() } function resetScale() { @@ -108,8 +114,6 @@ Entity { -(objectTransform.scale3D.z - 1) ) doRelativeScale(modelMat, scaleDiff) - - emitGizmoChanged() } /***** DEVICES *****/ @@ -130,7 +134,7 @@ Entity { const pickedAxis = convertAxisEnum(objectPicker.gizmoAxis) // If it is a TRANSLATION or a SCALE - if(objectPicker.gizmoType === TransformGizmo.Type.POSITION || objectPicker.gizmoType === TransformGizmo.Type.SCALE) { + if(objectPicker.gizmoType === TransformGizmo.Type.TRANSLATION || objectPicker.gizmoType === TransformGizmo.Type.SCALE) { // Compute the current vector PickedPoint -> CurrentMousePoint const pickedPosition = objectPicker.screenPoint const mouseVector = Qt.vector2d(mouse.x - pickedPosition.x, -(mouse.y - pickedPosition.y)) @@ -147,7 +151,7 @@ Entity { const offset = cosAngle * mouseVector.length() / objectPicker.scaleUnit // Do the transformation - if(objectPicker.gizmoType === TransformGizmo.Type.POSITION && offset !== 0) { + if(objectPicker.gizmoType === TransformGizmo.Type.TRANSLATION && offset !== 0) { doRelativeTranslation(objectPicker.modelMatrix, pickedAxis.times(offset)) // Do a translation from the initial Object Model Matrix when we picked the gizmo } else if(objectPicker.gizmoType === TransformGizmo.Type.SCALE && offset !== 0) { @@ -190,8 +194,9 @@ Entity { } onReleased: { if(objectPicker && mouse.button === Qt.LeftButton) { + const type = objectPicker.gizmoType objectPicker = null // To prevent going again in the onPositionChanged - emitGizmoChanged() + emitGizmoChanged(type) } } } @@ -200,23 +205,33 @@ Entity { id: resetMenu MenuItem { - text: `Reset Translation` - onTriggered: resetTranslation() + text: "Reset Translation" + onTriggered: { + resetTranslation() + emitGizmoChanged(TransformGizmo.Type.TRANSLATION) + } } MenuItem { - text: `Reset Rotation` - onTriggered: resetRotation() + text: "Reset Rotation" + onTriggered: { + resetRotation() + emitGizmoChanged(TransformGizmo.Type.ROTATION) + } } MenuItem { - text: `Reset Scale` - onTriggered: resetScale() + text: "Reset Scale" + onTriggered: { + resetScale() + emitGizmoChanged(TransformGizmo.Type.SCALE) + } } MenuItem { - text: `Reset All` + text: "Reset All" onTriggered: { resetTranslation() resetRotation() resetScale() + emitGizmoChanged(TransformGizmo.Type.ALL) } } MenuItem { @@ -377,7 +392,7 @@ Entity { } } - // POSITION ENTITY + // TRANSLATION ENTITY Entity { id: positionEntity components: [coneMesh, coneTransform, positionMaterial, positionPicker, frontLayerComponent] @@ -427,7 +442,7 @@ Entity { gizmoMaterial: positionMaterial gizmoBaseColor: baseColor gizmoAxis: axis - gizmoType: TransformGizmo.Type.POSITION + gizmoType: TransformGizmo.Type.TRANSLATION onPickedChanged: { this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) // Save the current transformations of the OBJECT From ceb927b409c05efeb301f3eb900b1bbc86504df6 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Fri, 7 Aug 2020 12:42:18 +0200 Subject: [PATCH 39/51] [ui] Viewer3D: avoid unwanted changes on other transformation types Now, if we change the translation of the gizmo (for instance), we make sure rotation and scale will not be changed. Very useful to avoid approximations and to have a clean undo-redo stack. --- .../ui/qml/Viewer3D/MeshingBoundingBox.qml | 49 ++++++++++++++----- .../ui/qml/Viewer3D/SfMTransformGizmo.qml | 49 ++++++++++++++----- 2 files changed, 73 insertions(+), 25 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml b/meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml index b7e7ece3be..b2e3e47cbc 100644 --- a/meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml +++ b/meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml @@ -18,22 +18,45 @@ Entity { frontLayerComponent: root.frontLayerComponent window: root.window - // Update node meshing slider values when the gizmo has changed: translation, rotation, scale + // Update node meshing slider values when the gizmo has changed: translation, rotation, scale, type transformGizmo.onGizmoChanged: { - _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxTranslation.x"), translation.x) - _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxTranslation.y"), translation.y) - _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxTranslation.z"), translation.z) - - _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxRotation.x"), rotation.x) - _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxRotation.y"), rotation.y) - _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxRotation.z"), rotation.z) - - _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxScale.x"), scale.x) - _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxScale.y"), scale.y) - _reconstruction.setAttribute(root.currentMeshingNode.attribute("boundingBox.bboxScale.z"), scale.z) + switch(type) { + case TransformGizmo.Type.TRANSLATION: { + _reconstruction.setAttribute( + root.currentMeshingNode.attribute("boundingBox.bboxTranslation"), + JSON.stringify([translation.x, translation.y, translation.z]) + ) + break + } + case TransformGizmo.Type.ROTATION: { + _reconstruction.setAttribute( + root.currentMeshingNode.attribute("boundingBox.bboxRotation"), + JSON.stringify([rotation.x, rotation.y, rotation.z]) + ) + break + } + case TransformGizmo.Type.SCALE: { + _reconstruction.setAttribute( + root.currentMeshingNode.attribute("boundingBox.bboxScale"), + JSON.stringify([scale.x, scale.y, scale.z]) + ) + break + } + case TransformGizmo.Type.ALL: { + _reconstruction.setAttribute( + root.currentMeshingNode.attribute("boundingBox"), + JSON.stringify([ + [translation.x, translation.y, translation.z], + [rotation.x, rotation.y, rotation.z], + [scale.x, scale.y, scale.z] + ]) + ) + break + } + } } - // Automatically evalutate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse. + // Automatically evaluate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse. // When the gizmo has changed (with mouse), the new values are set to the node, the priority is given back to the node and the Transform is re-evaluated once with those values. property var nodeTranslation : Qt.vector3d( root.currentMeshingNode.attribute("boundingBox.bboxTranslation.x").value, diff --git a/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml b/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml index 62543eae01..c312d4bfce 100644 --- a/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml @@ -21,21 +21,46 @@ Entity { window: root.window uniformScale: true // We want to make uniform scale transformations - // Update node SfMTransform slider values when the gizmo has changed: translation, rotation, scale + // Update node SfMTransform slider values when the gizmo has changed: translation, rotation, scale, type transformGizmo.onGizmoChanged: { - _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.x"), translation.x) - _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.y"), translation.y) - _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.z"), translation.z) - - _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("manualTransform.manualRotation.x"), rotation.x) - _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("manualTransform.manualRotation.y"), rotation.y) - _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("manualTransform.manualRotation.z"), rotation.z) - - // Only one scale is needed since the scale is uniform - _reconstruction.setAttribute(root.currentSfMTransformNode.attribute("manualTransform.manualScale"), scale.x) + switch(type) { + case TransformGizmo.Type.TRANSLATION: { + _reconstruction.setAttribute( + root.currentSfMTransformNode.attribute("manualTransform.manualTranslation"), + JSON.stringify([translation.x, translation.y, translation.z]) + ) + break + } + case TransformGizmo.Type.ROTATION: { + _reconstruction.setAttribute( + root.currentSfMTransformNode.attribute("manualTransform.manualRotation"), + JSON.stringify([rotation.x, rotation.y, rotation.z]) + ) + break + } + case TransformGizmo.Type.SCALE: { + // Only one scale is needed since the scale is uniform + _reconstruction.setAttribute( + root.currentSfMTransformNode.attribute("manualTransform.manualScale"), + scale.x + ) + break + } + case TransformGizmo.Type.ALL: { + _reconstruction.setAttribute( + root.currentSfMTransformNode.attribute("manualTransform"), + JSON.stringify([ + [translation.x, translation.y, translation.z], + [rotation.x, rotation.y, rotation.z], + scale.x + ]) + ) + break + } + } } - // Automatically evalutate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse. + // Automatically evaluate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse. // When the gizmo has changed (with mouse), the new values are set to the node, the priority is given back to the node and the Transform is re-evaluated once with those values. property var nodeTranslation : Qt.vector3d( root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.x").value, From f614e63a990ced5f0396b23295f7e5f88b2cc29b Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Fri, 7 Aug 2020 17:55:13 +0200 Subject: [PATCH 40/51] [doc] add a lot of developer's information --- meshroom/ui/components/scene3D.py | 51 ++++++- meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml | 9 +- .../ui/qml/Viewer3D/MeshingBoundingBox.qml | 11 +- .../ui/qml/Viewer3D/SfMTransformGizmo.qml | 11 +- meshroom/ui/qml/Viewer3D/TransformGizmo.qml | 141 +++++++++++++++--- 5 files changed, 188 insertions(+), 35 deletions(-) diff --git a/meshroom/ui/components/scene3D.py b/meshroom/ui/components/scene3D.py index 9db47691d0..b4569c19f0 100644 --- a/meshroom/ui/components/scene3D.py +++ b/meshroom/ui/components/scene3D.py @@ -111,7 +111,14 @@ class Transformations3DHelper(QObject): @Slot(QVector4D, Qt3DRender.QCamera, QSize, result=QVector2D) def pointFromWorldToScreen(self, point, camera, windowSize): - """ Compute the Screen point corresponding to a World Point. """ + """ Compute the Screen point corresponding to a World Point. + Args: + point (QVector4D): point in world coordinates + camera (QCamera): camera viewing the scene + windowSize (QSize): size of the Scene3D window + Returns: + QVector2D: point in screen coordinates + """ # Transform the point from World Coord to Normalized Device Coord viewMatrix = camera.transform().matrix().inverted() projectedPoint = (camera.projectionMatrix() * viewMatrix[0]).map(point) @@ -130,7 +137,14 @@ def pointFromWorldToScreen(self, point, camera, windowSize): @Slot(Qt3DCore.QTransform, QMatrix4x4, QMatrix4x4, QMatrix4x4, QVector3D) def relativeLocalTranslate(self, transformQtInstance, initialPosMat, initialRotMat, initialScaleMat, translateVec): - """ Translate the QTransform in its local space relatively to an initial state. """ + """ Translate the QTransform in its local space relatively to an initial state. + Args: + transformQtInstance (QTransform): reference to the Transform to modify + initialPosMat (QMatrix4x4): initial position matrix + initialRotMat (QMatrix4x4): initial rotation matrix + initialScaleMat (QMatrix4x4): initial scale matrix + translateVec (QVector3D): vector used for the local translation + """ # Compute the translation transformation matrix translationMat = QMatrix4x4() translationMat.translate(translateVec) @@ -141,7 +155,15 @@ def relativeLocalTranslate(self, transformQtInstance, initialPosMat, initialRotM @Slot(Qt3DCore.QTransform, QMatrix4x4, QQuaternion, QMatrix4x4, QVector3D, int) def relativeLocalRotate(self, transformQtInstance, initialPosMat, initialRotQuat, initialScaleMat, axis, degree): - """ Rotate the QTransform in its local space relatively to an initial state. """ + """ Rotate the QTransform in its local space relatively to an initial state. + Args: + transformQtInstance (QTransform): reference to the Transform to modify + initialPosMat (QMatrix4x4): initial position matrix + initialRotQuat (QQuaternion): initial rotation quaternion + initialScaleMat (QMatrix4x4): initial scale matrix + axis (QVector3D): axis to rotate around + degree (int): angle of rotation in degree + """ # Compute the transformation quaternion from axis and angle in degrees transformQuat = QQuaternion.fromAxisAndAngle(axis, degree) @@ -155,7 +177,14 @@ def relativeLocalRotate(self, transformQtInstance, initialPosMat, initialRotQuat @Slot(Qt3DCore.QTransform, QMatrix4x4, QMatrix4x4, QMatrix4x4, QVector3D) def relativeLocalScale(self, transformQtInstance, initialPosMat, initialRotMat, initialScaleMat, scaleVec): - """ Scale the QTransform in its local space relatively to an initial state. """ + """ Scale the QTransform in its local space relatively to an initial state. + Args: + transformQtInstance (QTransform): reference to the Transform to modify + initialPosMat (QMatrix4x4): initial position matrix + initialRotMat (QMatrix4x4): initial rotation matrix + initialScaleMat (QMatrix4x4): initial scale matrix + scaleVec (QVector3D): vector used for the relative scale + """ # Make a copy of the scale matrix (otherwise, it is a reference and it does not work as expected) scaleMat = self.copyMatrix4x4(initialScaleMat) @@ -175,7 +204,12 @@ def relativeLocalScale(self, transformQtInstance, initialPosMat, initialRotMat, @Slot(QMatrix4x4, result="QVariant") def modelMatrixToMatrices(self, modelMat): - """ Decompose a model matrix into individual matrices. """ + """ Decompose a model matrix into individual matrices. + Args: + modelMat (QMatrix4x4): model matrix to decompose + Returns: + QVariant: object containing position, rotation and scale matrices + rotation quaternion + """ decomposition = self.decomposeModelMatrix(modelMat) posMat = QMatrix4x4() @@ -251,7 +285,12 @@ def copyMatrix4x4(self, mat): return newMat def decomposeModelMatrix(self, modelMat): - """ Decompose a model matrix into individual component. """ + """ Decompose a model matrix into individual component. + Args: + modelMat (QMatrix4x4): model matrix to decompose + Returns: + QVariant: object containing translation and scale vectors + rotation quaternion + """ translation = modelMat.column(3).toVector3D() quaternion = QQuaternion.fromDirection(modelMat.column(2).toVector3D(), modelMat.column(1).toVector3D()) scale = QVector3D(modelMat.column(0).length(), modelMat.column(1).length(), modelMat.column(2).length()) diff --git a/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml b/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml index 72ab7c3c73..b2cf95e58f 100644 --- a/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/EntityWithGizmo.qml @@ -5,12 +5,19 @@ import Qt3D.Extras 2.10 import QtQuick 2.9 import Qt3D.Logic 2.0 +/** + * Wrapper for TransformGizmo. + * Must be instantiated to control an other entity. + * The goal is to instantiate the other entity inside this wrapper to gather the object and the gizmo. + * objectTranform is the component the other entity should use as a Transform. + */ + Entity { id: root property DefaultCameraController sceneCameraController property Layer frontLayerComponent property var window - property alias uniformScale: transformGizmo.uniformScale // by default, if not set, the value is: false + property alias uniformScale: transformGizmo.uniformScale // By default, if not set, the value is: false property TransformGizmo transformGizmo: TransformGizmo { id: transformGizmo camera: root.camera diff --git a/meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml b/meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml index b2e3e47cbc..e068f3f1f4 100644 --- a/meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml +++ b/meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml @@ -4,6 +4,10 @@ import Qt3D.Input 2.0 import Qt3D.Extras 2.10 import QtQuick 2.9 +/** + * BoundingBox entity for Meshing node. Used to define the area to reconstruct. + * Simple box controlled by a gizmo to give easy and visual representation. + */ Entity { id: root property DefaultCameraController sceneCameraController @@ -56,22 +60,25 @@ Entity { } } - // Automatically evaluate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse. - // When the gizmo has changed (with mouse), the new values are set to the node, the priority is given back to the node and the Transform is re-evaluated once with those values. + // Translation values from node (vector3d because this is the type of QTransform.translation) property var nodeTranslation : Qt.vector3d( root.currentMeshingNode.attribute("boundingBox.bboxTranslation.x").value, root.currentMeshingNode.attribute("boundingBox.bboxTranslation.y").value, root.currentMeshingNode.attribute("boundingBox.bboxTranslation.z").value ) + // Rotation values from node (3 separated values because QTransform stores Euler angles like this) property var nodeRotationX: root.currentMeshingNode.attribute("boundingBox.bboxRotation.x").value property var nodeRotationY: root.currentMeshingNode.attribute("boundingBox.bboxRotation.y").value property var nodeRotationZ: root.currentMeshingNode.attribute("boundingBox.bboxRotation.z").value + // Scale values from node (vector3d because this is the type of QTransform.scale3D) property var nodeScale: Qt.vector3d( root.currentMeshingNode.attribute("boundingBox.bboxScale.x").value, root.currentMeshingNode.attribute("boundingBox.bboxScale.y").value, root.currentMeshingNode.attribute("boundingBox.bboxScale.z").value ) + // Automatically evaluate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse. + // When the gizmo has changed (with mouse), the new values are set to the node, the priority is given back to the node and the Transform is re-evaluated once with those values. transformGizmo.gizmoDisplayTransform.translation: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.translation : nodeTranslation transformGizmo.gizmoDisplayTransform.rotationX: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationX : nodeRotationX transformGizmo.gizmoDisplayTransform.rotationY: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationY : nodeRotationY diff --git a/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml b/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml index c312d4bfce..4844a32979 100644 --- a/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml @@ -4,6 +4,10 @@ import Qt3D.Input 2.0 import Qt3D.Extras 2.10 import QtQuick 2.9 +/** + * Gizmo for SfMTransform node. + * Uses EntityWithGizmo wrapper because we should not instantiate TransformGizmo alone. + */ Entity { id: root property DefaultCameraController sceneCameraController @@ -60,18 +64,21 @@ Entity { } } - // Automatically evaluate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse. - // When the gizmo has changed (with mouse), the new values are set to the node, the priority is given back to the node and the Transform is re-evaluated once with those values. + // Translation values from node (vector3d because this is the type of QTransform.translation) property var nodeTranslation : Qt.vector3d( root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.x").value, root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.y").value, root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.z").value ) + // Rotation values from node (3 separated values because QTransform stores Euler angles like this) property var nodeRotationX: root.currentSfMTransformNode.attribute("manualTransform.manualRotation.x").value property var nodeRotationY: root.currentSfMTransformNode.attribute("manualTransform.manualRotation.y").value property var nodeRotationZ: root.currentSfMTransformNode.attribute("manualTransform.manualRotation.z").value + // Scale value from node (simple number because we use uniform scale) property var nodeScale: root.currentSfMTransformNode.attribute("manualTransform.manualScale").value + // Automatically evaluate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse. + // When the gizmo has changed (with mouse), the new values are set to the node, the priority is given back to the node and the Transform is re-evaluated once with those values. transformGizmo.gizmoDisplayTransform.translation: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.translation : nodeTranslation transformGizmo.gizmoDisplayTransform.rotationX: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationX : nodeRotationX transformGizmo.gizmoDisplayTransform.rotationY: transformGizmo.focusGizmoPriority ? transformGizmo.gizmoDisplayTransform.rotationY : nodeRotationY diff --git a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml index 46aa91050c..05ada07948 100644 --- a/meshroom/ui/qml/Viewer3D/TransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/TransformGizmo.qml @@ -7,19 +7,27 @@ import Qt3D.Logic 2.0 import QtQuick.Controls 2.3 import Utils 1.0 + +/** + * Simple transformation gizmo entirely made with Qt3D entities. + * Uses Python Transformations3DHelper to compute matrices. + * This TransformGizmo entity should only be instantiated in EntityWithGizmo entity which is its wrapper. + * It means, to use it for a specified application, make sure to instantiate EntityWithGizmo. + */ Entity { id: root - readonly property alias gizmoScale: gizmoScaleLookSlider.value property Camera camera property var windowSize - property var frontLayerComponent + property Layer frontLayerComponent // Used to draw gizmo on top of everything property var window - property bool uniformScale: false // by default, the scale is not uniform + readonly property alias gizmoScale: gizmoScaleLookSlider.value + property bool uniformScale: false // By default, the scale is not uniform property bool focusGizmoPriority: false // If true, it is used to give the priority to the current transformation (and not to a upper-level binding) property Transform gizmoDisplayTransform: Transform { id: gizmoDisplayTransform - scale: root.gizmoScale * (camera.position.minus(gizmoDisplayTransform.translation)).length() + scale: root.gizmoScale * (camera.position.minus(gizmoDisplayTransform.translation)).length() // The gizmo needs a constant apparent size } + // Component the object controlled by the gizmo must use property Transform objectTransform : Transform { translation: gizmoDisplayTransform.translation rotation: gizmoDisplayTransform.rotation @@ -75,21 +83,78 @@ Entity { /***** TRANSFORMATIONS (using local vars) *****/ + /** + * @brief Translate locally the gizmo and the object. + * + * @remarks + * To make local translation, we need to recompute a new matrix. + * Update gizmoDisplayTransform's matrix and all its properties while avoiding the override of translation property. + * Update objectTransform in the same time thanks to binding on translation property. + * + * @param initialModelMatrix object containing position, rotation and scale matrices + rotation quaternion + * @param translateVec vector3d used to make the local translation + */ function doRelativeTranslation(initialModelMatrix, translateVec) { - Transformations3DHelper.relativeLocalTranslate(gizmoDisplayTransform, initialModelMatrix.position, initialModelMatrix.rotation, initialModelMatrix.scale, translateVec) // Update gizmo matrix and object matrix with binding + Transformations3DHelper.relativeLocalTranslate( + gizmoDisplayTransform, + initialModelMatrix.position, + initialModelMatrix.rotation, + initialModelMatrix.scale, + translateVec + ) } + /** + * @brief Rotate the gizmo and the object around a specific axis. + * + * @remarks + * To make local rotation around an axis, we need to recompute a new matrix from a quaternion. + * Update gizmoDisplayTransform's matrix and all its properties while avoiding the override of rotation, rotationX, rotationY and rotationZ properties. + * Update objectTransform in the same time thanks to binding on rotation property. + * + * @param initialModelMatrix object containing position, rotation and scale matrices + rotation quaternion + * @param axis vector3d describing the axis to rotate around + * @param degree angle of rotation in degrees + */ function doRelativeRotation(initialModelMatrix, axis, degree) { - Transformations3DHelper.relativeLocalRotate(gizmoDisplayTransform, initialModelMatrix.position, initialModelMatrix.quaternion, initialModelMatrix.scale, axis, degree) // Update gizmo matrix and object matrix with binding + Transformations3DHelper.relativeLocalRotate( + gizmoDisplayTransform, + initialModelMatrix.position, + initialModelMatrix.quaternion, + initialModelMatrix.scale, + axis, + degree + ) } + /** + * @brief Scale the object relatively to its current scale. + * + * @remarks + * To change scale of the object, we need to recompute a new matrix to avoid overriding bindings. + * Update objectTransform properties only (gizmoDisplayTransform is not affected). + * + * @param initialModelMatrix object containing position, rotation and scale matrices + rotation quaternion + * @param scaleVec vector3d used to make the relative scale + */ function doRelativeScale(initialModelMatrix, scaleVec) { - Transformations3DHelper.relativeLocalScale(objectTransform, initialModelMatrix.position, initialModelMatrix.rotation, initialModelMatrix.scale, scaleVec) // Update only object matrix + Transformations3DHelper.relativeLocalScale( + objectTransform, + initialModelMatrix.position, + initialModelMatrix.rotation, + initialModelMatrix.scale, + scaleVec + ) } + /** + * @brief Reset the translation of the gizmo and the object. + * + * @remarks + * Update gizmoDisplayTransform's matrix and all its properties while avoiding the override of translation property. + * Update objectTransform in the same time thanks to binding on translation property. + */ function resetTranslation() { - // We have to reset the translation inside the matrix because we cannot override gizmoDisplayTransform.translation (because it can be used in upper-level binding) - // The object matrix will also be updated because of the binding with the translation property const mat = gizmoDisplayTransform.matrix const newMat = Qt.matrix4x4( mat.m11, mat.m12, mat.m13, 0, @@ -100,13 +165,30 @@ Entity { gizmoDisplayTransform.setMatrix(newMat) } + /** + * @brief Reset the rotation of the gizmo and the object. + * + * @remarks + * Update gizmoDisplayTransform's quaternion while avoiding the override of rotationX, rotationY and rotationZ properties. + * Update objectTransform in the same time thanks to binding on rotation property. + * Here, we can change the rotation property (but not rotationX, rotationY and rotationZ because they can be used in upper-level bindings). + * + * @note + * We could implement a way of changing the matrix instead of overriding rotation (quaternion) property. + */ function resetRotation() { - // Here, we can change the rotation property (but not rotationX, rotationY and rotationZ because they can be used in upper-level bindings) - gizmoDisplayTransform.rotation = Qt.quaternion(1,0,0,0) // Reset gizmo matrix and object matrix with binding + gizmoDisplayTransform.rotation = Qt.quaternion(1,0,0,0) } + /** + * @brief Reset the scale of the object. + * + * @remarks + * To reset the scale, we make the difference of the current one to 1 and recompute the matrix. + * Like this, we kind of apply an inverse scale transformation. + * It prevents overriding scale3D property (because it can be used in upper-level binding). + */ function resetScale() { - // We have to make the difference scale to 1 because we cannot override objectTransform.scale3D (because it can be used in upper-level binding) const modelMat = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) const scaleDiff = Qt.vector3d( -(objectTransform.scale3D.x - 1), @@ -133,9 +215,9 @@ Entity { // Get the selected axis const pickedAxis = convertAxisEnum(objectPicker.gizmoAxis) - // If it is a TRANSLATION or a SCALE + // TRANSLATION or SCALE transformation if(objectPicker.gizmoType === TransformGizmo.Type.TRANSLATION || objectPicker.gizmoType === TransformGizmo.Type.SCALE) { - // Compute the current vector PickedPoint -> CurrentMousePoint + // Compute the vector PickedPosition -> CurrentMousePoint const pickedPosition = objectPicker.screenPoint const mouseVector = Qt.vector2d(mouse.x - pickedPosition.x, -(mouse.y - pickedPosition.y)) @@ -147,6 +229,7 @@ Entity { const screenAxisVector = Qt.vector2d(screenPoint2D.x - screenCenter2D.x, -(screenPoint2D.y - screenCenter2D.y)) // Get the cosinus of the angle from the screenAxisVector to the mouseVector + // It will be used as a intensity factor const cosAngle = screenAxisVector.dotProduct(mouseVector) / (screenAxisVector.length() * mouseVector.length()) const offset = cosAngle * mouseVector.length() / objectPicker.scaleUnit @@ -163,15 +246,16 @@ Entity { return } + // ROTATION transformation else if(objectPicker.gizmoType === TransformGizmo.Type.ROTATION) { // Get Screen Coordinates of the gizmo center const gizmoCenterPoint = gizmoDisplayTransform.matrix.times(Qt.vector4d(0, 0, 0, 1)) const screenCenter2D = Transformations3DHelper.pointFromWorldToScreen(gizmoCenterPoint, camera, root.windowSize) - // Get the vector screenCenter2D -> PickedPoint + // Get the vector screenCenter2D -> PickedPosition const originalVector = Qt.vector2d(objectPicker.screenPoint.x - screenCenter2D.x, -(objectPicker.screenPoint.y - screenCenter2D.y)) - // Compute the current vector screenCenter2D -> CurrentMousePoint + // Compute the vector screenCenter2D -> CurrentMousePoint const mouseVector = Qt.vector2d(mouse.x - screenCenter2D.x, -(mouse.y - screenCenter2D.y)) // Get the angle from the originalVector to the mouseVector @@ -385,9 +469,12 @@ Entity { gizmoType: TransformGizmo.Type.SCALE onPickedChanged: { - this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) // Save the current transformations of the OBJECT - this.scaleUnit = Transformations3DHelper.computeScaleUnitFromModelMatrix(convertAxisEnum(gizmoAxis), gizmoDisplayTransform.matrix, camera, root.windowSize) // Compute a scale unit at picking time - root.pickedChanged(picker.isPressed) // Used to prevent camera transformations + // Save the current transformations of the OBJECT + this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) + // Compute a scale unit at picking time + this.scaleUnit = Transformations3DHelper.computeScaleUnitFromModelMatrix(convertAxisEnum(gizmoAxis), gizmoDisplayTransform.matrix, camera, root.windowSize) + // Prevent camera transformations + root.pickedChanged(picker.isPressed) } } } @@ -445,9 +532,12 @@ Entity { gizmoType: TransformGizmo.Type.TRANSLATION onPickedChanged: { - this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) // Save the current transformations of the OBJECT - this.scaleUnit = Transformations3DHelper.computeScaleUnitFromModelMatrix(convertAxisEnum(gizmoAxis), gizmoDisplayTransform.matrix, camera, root.windowSize) // Compute a scale unit at picking time - root.pickedChanged(picker.isPressed) // Used to prevent camera transformations + // Save the current transformations of the OBJECT + this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) + // Compute a scale unit at picking time + this.scaleUnit = Transformations3DHelper.computeScaleUnitFromModelMatrix(convertAxisEnum(gizmoAxis), gizmoDisplayTransform.matrix, camera, root.windowSize) + // Prevent camera transformations + root.pickedChanged(picker.isPressed) } } } @@ -491,8 +581,11 @@ Entity { gizmoType: TransformGizmo.Type.ROTATION onPickedChanged: { - this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) // Save the current transformations of the OBJECT - root.pickedChanged(picker.isPressed) // Used to prevent camera transformations + // Save the current transformations of the OBJECT + this.modelMatrix = Transformations3DHelper.modelMatrixToMatrices(objectTransform.matrix) + // No need to compute a scale unit for rotation + // Prevent camera transformations + root.pickedChanged(picker.isPressed) } } } From 06372bc89e707ac582b0ad7e3a527bb596dbc810 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Fri, 7 Aug 2020 18:27:08 +0200 Subject: [PATCH 41/51] [core] fix elif after raise --- meshroom/core/desc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meshroom/core/desc.py b/meshroom/core/desc.py index 218daec5d0..1a571a72c1 100644 --- a/meshroom/core/desc.py +++ b/meshroom/core/desc.py @@ -72,7 +72,7 @@ def validateValue(self, value): if isinstance(value, PySide2.QtQml.QJSValue): # Note: we could use isArray(), property("length").toInt() to retrieve all values raise ValueError("ListAttribute.validateValue: cannot recognize QJSValue. Please, use JSON.stringify(value) in QML.") - elif isinstance(value, pyCompatibility.basestring): + if isinstance(value, pyCompatibility.basestring): # Alternative solution to set values from QML is to convert values to JSON string # In this case, it works with all data types value = ast.literal_eval(value) @@ -108,7 +108,7 @@ def validateValue(self, value): if isinstance(value, PySide2.QtQml.QJSValue): # Note: we could use isArray(), property("length").toInt() to retrieve all values raise ValueError("GroupAttribute.validateValue: cannot recognize QJSValue. Please, use JSON.stringify(value) in QML.") - elif isinstance(value, pyCompatibility.basestring): + if isinstance(value, pyCompatibility.basestring): # Alternative solution to set values from QML is to convert values to JSON string # In this case, it works with all data types value = ast.literal_eval(value) From 887b33490e10812f3887cc7816d6bd2208d92bf5 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 10 Aug 2020 13:10:39 +0200 Subject: [PATCH 42/51] [core] avoid direct includes to PySide2 --- meshroom/common/__init__.py | 5 +++-- meshroom/common/core.py | 1 + meshroom/common/qt.py | 3 ++- meshroom/core/desc.py | 7 +++---- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/meshroom/common/__init__.py b/meshroom/common/__init__.py index 8607447b13..3331e9c680 100644 --- a/meshroom/common/__init__.py +++ b/meshroom/common/__init__.py @@ -8,13 +8,14 @@ BaseObject = None Variant = None VariantList = None +JSValue = None if meshroom.backend == meshroom.Backend.PYSIDE: # PySide types - from .qt import DictModel, ListModel, Slot, Signal, Property, BaseObject, Variant, VariantList + from .qt import DictModel, ListModel, Slot, Signal, Property, BaseObject, Variant, VariantList, JSValue elif meshroom.backend == meshroom.Backend.STANDALONE: # Core types - from .core import DictModel, ListModel, Slot, Signal, Property, BaseObject, Variant, VariantList + from .core import DictModel, ListModel, Slot, Signal, Property, BaseObject, Variant, VariantList, JSValue class _BaseModel: diff --git a/meshroom/common/core.py b/meshroom/common/core.py index 54ba9cbef9..830de126bd 100644 --- a/meshroom/common/core.py +++ b/meshroom/common/core.py @@ -146,3 +146,4 @@ def parent(self): BaseObject = CoreObject Variant = object VariantList = object +JSValue = None diff --git a/meshroom/common/qt.py b/meshroom/common/qt.py index cb1087d5d1..ee7c0f6238 100644 --- a/meshroom/common/qt.py +++ b/meshroom/common/qt.py @@ -1,4 +1,4 @@ -from PySide2 import QtCore +from PySide2 import QtCore, QtQml class QObjectListModel(QtCore.QAbstractListModel): @@ -374,3 +374,4 @@ def sort(self): BaseObject = QtCore.QObject Variant = "QVariant" VariantList = "QVariantList" +JSValue = QtQml.QJSValue diff --git a/meshroom/core/desc.py b/meshroom/core/desc.py index 1a571a72c1..84f6f10ca7 100644 --- a/meshroom/core/desc.py +++ b/meshroom/core/desc.py @@ -1,10 +1,9 @@ -from meshroom.common import BaseObject, Property, Variant, VariantList +from meshroom.common import BaseObject, Property, Variant, VariantList, JSValue from meshroom.core import pyCompatibility from enum import Enum # available by default in python3. For python2: "pip install enum34" import math import os import psutil -import PySide2 import ast class Attribute(BaseObject): @@ -69,7 +68,7 @@ def __init__(self, elementDesc, name, label, description, group='allParams', adv joinChar = Property(str, lambda self: self._joinChar, constant=True) def validateValue(self, value): - if isinstance(value, PySide2.QtQml.QJSValue): + if JSValue is not None and isinstance(value, JSValue): # Note: we could use isArray(), property("length").toInt() to retrieve all values raise ValueError("ListAttribute.validateValue: cannot recognize QJSValue. Please, use JSON.stringify(value) in QML.") if isinstance(value, pyCompatibility.basestring): @@ -105,7 +104,7 @@ def __init__(self, groupDesc, name, label, description, group='allParams', advan def validateValue(self, value): """ Ensure value is compatible with the group description and convert value if needed. """ - if isinstance(value, PySide2.QtQml.QJSValue): + if JSValue is not None and isinstance(value, JSValue): # Note: we could use isArray(), property("length").toInt() to retrieve all values raise ValueError("GroupAttribute.validateValue: cannot recognize QJSValue. Please, use JSON.stringify(value) in QML.") if isinstance(value, pyCompatibility.basestring): From 79e96e7c06e58b7cf01954d25284f4310eb335f0 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 17 Aug 2020 11:16:53 +0200 Subject: [PATCH 43/51] [ui] fix typos --- meshroom/ui/qml/Viewer3D/MediaLibrary.qml | 2 +- meshroom/ui/qml/Viewer3D/MediaLoader.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml index 5b644a38df..9f7cd9e4f8 100644 --- a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml +++ b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml @@ -246,7 +246,7 @@ Entity { } function updateCacheAndModel(forceRequest) { - // don't cache explicitely unloaded media + // don't cache explicitly unloaded media if(model.requested && object && dependencyReady) { // cache current object if(cache.add(Filepath.urlToString(mediaLoader.source), object)); diff --git a/meshroom/ui/qml/Viewer3D/MediaLoader.qml b/meshroom/ui/qml/Viewer3D/MediaLoader.qml index fd48f6c87b..38a50fd1d5 100644 --- a/meshroom/ui/qml/Viewer3D/MediaLoader.qml +++ b/meshroom/ui/qml/Viewer3D/MediaLoader.qml @@ -31,7 +31,7 @@ import Utils 1.0 return; } - // clear previously created objet if any + // clear previously created object if any if(object) { object.destroy(); object = null; From b79795a3c99a06fd69794f39a53c2b4280e8594e Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 17 Aug 2020 11:24:37 +0200 Subject: [PATCH 44/51] [ui] fix binding errors --- meshroom/ui/qml/ImageGallery/ImageGallery.qml | 2 +- meshroom/ui/qml/Viewer3D/MediaLibrary.qml | 2 +- .../ui/qml/Viewer3D/MeshingBoundingBox.qml | 18 +++++++++--------- meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml | 14 +++++++------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/meshroom/ui/qml/ImageGallery/ImageGallery.qml b/meshroom/ui/qml/ImageGallery/ImageGallery.qml index 5e87ab08e0..7f3b1afd6d 100644 --- a/meshroom/ui/qml/ImageGallery/ImageGallery.qml +++ b/meshroom/ui/qml/ImageGallery/ImageGallery.qml @@ -76,7 +76,7 @@ Panel { SensorDBDialog { id: sensorDBDialog - sensorDatabase: Filepath.stringToUrl(cameraInit.attribute("sensorDatabase").value) + sensorDatabase: cameraInit ? Filepath.stringToUrl(cameraInit.attribute("sensorDatabase").value) : "" readOnly: _reconstruction.computing onUpdateIntrinsicsRequest: _reconstruction.rebuildIntrinsics(cameraInit) } diff --git a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml index 9f7cd9e4f8..1828884ed0 100644 --- a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml +++ b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml @@ -217,7 +217,7 @@ Entity { // To use only if we want to draw the input source and not the current node output (Warning: to use with caution) // There is maybe a better way to do this to avoid overwritting bindings which should be readonly properties function drawInputSource() { - rawSource = Qt.binding(() => instantiatedEntity.currentNode.attribute("input").value) + rawSource = Qt.binding(() => instantiatedEntity.currentNode ? instantiatedEntity.currentNode.attribute("input").value: "") currentSource = Qt.binding(() => rawSource) finalSource = Qt.binding(() => rawSource) } diff --git a/meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml b/meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml index e068f3f1f4..4baf412154 100644 --- a/meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml +++ b/meshroom/ui/qml/Viewer3D/MeshingBoundingBox.qml @@ -62,19 +62,19 @@ Entity { // Translation values from node (vector3d because this is the type of QTransform.translation) property var nodeTranslation : Qt.vector3d( - root.currentMeshingNode.attribute("boundingBox.bboxTranslation.x").value, - root.currentMeshingNode.attribute("boundingBox.bboxTranslation.y").value, - root.currentMeshingNode.attribute("boundingBox.bboxTranslation.z").value + root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxTranslation.x").value : 0, + root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxTranslation.y").value : 0, + root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxTranslation.z").value : 0 ) // Rotation values from node (3 separated values because QTransform stores Euler angles like this) - property var nodeRotationX: root.currentMeshingNode.attribute("boundingBox.bboxRotation.x").value - property var nodeRotationY: root.currentMeshingNode.attribute("boundingBox.bboxRotation.y").value - property var nodeRotationZ: root.currentMeshingNode.attribute("boundingBox.bboxRotation.z").value + property var nodeRotationX: root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxRotation.x").value : 0 + property var nodeRotationY: root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxRotation.y").value : 0 + property var nodeRotationZ: root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxRotation.z").value : 0 // Scale values from node (vector3d because this is the type of QTransform.scale3D) property var nodeScale: Qt.vector3d( - root.currentMeshingNode.attribute("boundingBox.bboxScale.x").value, - root.currentMeshingNode.attribute("boundingBox.bboxScale.y").value, - root.currentMeshingNode.attribute("boundingBox.bboxScale.z").value + root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxScale.x").value : 1, + root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxScale.y").value : 1, + root.currentMeshingNode ? root.currentMeshingNode.attribute("boundingBox.bboxScale.z").value : 1 ) // Automatically evaluate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse. diff --git a/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml b/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml index 4844a32979..e9998264ad 100644 --- a/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml +++ b/meshroom/ui/qml/Viewer3D/SfMTransformGizmo.qml @@ -66,16 +66,16 @@ Entity { // Translation values from node (vector3d because this is the type of QTransform.translation) property var nodeTranslation : Qt.vector3d( - root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.x").value, - root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.y").value, - root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.z").value + root.currentSfMTransformNode ? root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.x").value : 0, + root.currentSfMTransformNode ? root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.y").value : 0, + root.currentSfMTransformNode ? root.currentSfMTransformNode.attribute("manualTransform.manualTranslation.z").value : 0 ) // Rotation values from node (3 separated values because QTransform stores Euler angles like this) - property var nodeRotationX: root.currentSfMTransformNode.attribute("manualTransform.manualRotation.x").value - property var nodeRotationY: root.currentSfMTransformNode.attribute("manualTransform.manualRotation.y").value - property var nodeRotationZ: root.currentSfMTransformNode.attribute("manualTransform.manualRotation.z").value + property var nodeRotationX: root.currentSfMTransformNode ? root.currentSfMTransformNode.attribute("manualTransform.manualRotation.x").value : 0 + property var nodeRotationY: root.currentSfMTransformNode ? root.currentSfMTransformNode.attribute("manualTransform.manualRotation.y").value : 0 + property var nodeRotationZ: root.currentSfMTransformNode ? root.currentSfMTransformNode.attribute("manualTransform.manualRotation.z").value : 0 // Scale value from node (simple number because we use uniform scale) - property var nodeScale: root.currentSfMTransformNode.attribute("manualTransform.manualScale").value + property var nodeScale: root.currentSfMTransformNode ? root.currentSfMTransformNode.attribute("manualTransform.manualScale").value : 1 // Automatically evaluate the Transform: value is taken from the node OR from the actual modification if the gizmo is moved by mouse. // When the gizmo has changed (with mouse), the new values are set to the node, the priority is given back to the node and the Transform is re-evaluated once with those values. From fe91d07a6a2f09154746cc077cbcd35519902166 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 17 Aug 2020 11:39:22 +0200 Subject: [PATCH 45/51] [core] Node: add alive property for QML --- meshroom/core/graph.py | 6 +++++- meshroom/core/node.py | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/meshroom/core/graph.py b/meshroom/core/graph.py index c994dc0cef..1617de3b13 100644 --- a/meshroom/core/graph.py +++ b/meshroom/core/graph.py @@ -223,8 +223,11 @@ def __init__(self, name, parent=None): def clear(self): self.header.clear() self._compatibilityNodes.clear() - self._nodes.clear() self._edges.clear() + # Tell QML nodes are going to be deleted + for node in self._nodes: + node.alive = False + self._nodes.clear() @property def fileFeatures(self): @@ -437,6 +440,7 @@ def removeNode(self, nodeName): self.removeEdge(edge.dst) inEdges[edge.dst.getFullName()] = edge.src.getFullName() + node.alive = False self._nodes.remove(node) self.update() diff --git a/meshroom/core/node.py b/meshroom/core/node.py index 302c5a31a3..89ba8fae8c 100644 --- a/meshroom/core/node.py +++ b/meshroom/core/node.py @@ -442,6 +442,7 @@ def __init__(self, nodeType, position=None, parent=None, **kwargs): self._position = position or Position() self._attributes = DictModel(keyAttrName='name', parent=self) self.attributesPerUid = defaultdict(set) + self._alive = True # for QML side to know if the node can be used or is going to be deleted def __getattr__(self, k): try: @@ -526,6 +527,17 @@ def position(self, value): self._position = value self.positionChanged.emit() + @property + def alive(self): + return self._alive + + @alive.setter + def alive(self, value): + if self._alive == value: + return + self._alive = value + self.aliveChanged.emit() + @property def depth(self): return self.graph.getDepth(self) @@ -792,6 +804,8 @@ def __repr__(self): globalStatusChanged = Signal() globalStatus = Property(str, lambda self: self.getGlobalStatus().name, notify=globalStatusChanged) isComputed = Property(bool, _isComputed, notify=globalStatusChanged) + aliveChanged = Signal() + alive = Property(bool, alive.fget, alive.fset, notify=aliveChanged) class Node(BaseNode): From 8020cf515816a5dbb53e781a5c6f93d50615d9e0 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 17 Aug 2020 11:43:33 +0200 Subject: [PATCH 46/51] [ui] Reconstruction: add clear methods --- meshroom/ui/reconstruction.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/meshroom/ui/reconstruction.py b/meshroom/ui/reconstruction.py index a16d3d0cc5..54518b8838 100755 --- a/meshroom/ui/reconstruction.py +++ b/meshroom/ui/reconstruction.py @@ -453,6 +453,10 @@ def __init__(self, defaultPipeline='', parent=None): self.setDefaultPipeline(defaultPipeline) + def clear(self): + self.clearActiveNodes() + super(Reconstruction, self).clear() + def setDefaultPipeline(self, defaultPipeline): self._defaultPipeline = defaultPipeline @@ -463,6 +467,10 @@ def initActiveNodes(self): for nodeType, _ in meshroom.core.nodesDesc.items(): self._activeNodes.add(ActiveNode(nodeType, self)) + def clearActiveNodes(self): + for key in self._activeNodes.keys(): + self._activeNodes.get(key).node = None + def onCameraInitChanged(self): # Update active nodes when CameraInit changes nodes = self._graph.nodesFromNode(self._cameraInit)[0] @@ -559,7 +567,7 @@ def runAsync(func, args=(), kwargs=None): def getViewpoints(self): """ Return the Viewpoints model. """ # TODO: handle multiple Viewpoints models - return self._cameraInit.viewpoints.value if self._cameraInit else None + return self._cameraInit.viewpoints.value if self._cameraInit else QObjectListModel(parent=self) def updateCameraInits(self): cameraInits = self._graph.nodesByType("CameraInit", sortedByIndex=True) From b9e68b7c3e54a05a2d5019ba1703548bc100f1cc Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 17 Aug 2020 11:45:17 +0200 Subject: [PATCH 47/51] [ui] Graph: change setGraph and clear --- meshroom/ui/graph.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/meshroom/ui/graph.py b/meshroom/ui/graph.py index b4ba2ab75e..85b399aabb 100644 --- a/meshroom/ui/graph.py +++ b/meshroom/ui/graph.py @@ -275,7 +275,11 @@ def setGraph(self, g): if self._graph: self.stopExecution() self.clear() + oldGraph = self._graph self._graph = g + if oldGraph: + oldGraph.deleteLater() + self._graph.updated.connect(self.onGraphUpdated) self._graph.update() # perform auto-layout if graph does not provide nodes positions @@ -320,8 +324,7 @@ def clear(self): if self._graph: self.clearNodeHover() self.clearNodeSelection() - self._graph.deleteLater() - self._graph = None + self._graph.clear() self._sortedDFSChunks.clear() self._undoStack.clear() From 424f7e5b0eee7090771de55815a5ec017a0b4d54 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 17 Aug 2020 11:50:00 +0200 Subject: [PATCH 48/51] [ui] MediaLibrary: add alive property and fix issue --- meshroom/ui/qml/Viewer3D/MediaLibrary.qml | 28 +++++++++++++++-------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml index 1828884ed0..2e47488ec0 100644 --- a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml +++ b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml @@ -203,11 +203,14 @@ Entity { property string rawSource: attribute ? attribute.value : model.source // whether dependencies are statified (applies for output/connected input attributes only) readonly property bool dependencyReady: { - if(attribute && attribute.isOutput) - return attribute.node.globalStatus === "SUCCESS"; - if(attribute && attribute.isLink) - return attribute.linkParam.node.globalStatus === "SUCCESS"; - return true; + if(!attribute) + // if the node is removed, the attribute will be invalid + return false + if(attribute.isOutput) + return attribute.node.globalStatus === "SUCCESS" + if(attribute.isLink) + return attribute.linkParam.node.globalStatus === "SUCCESS" + return true // is an input param so no dependency } // source based on raw source + dependency status property string currentSource: dependencyReady ? rawSource : "" @@ -230,8 +233,14 @@ Entity { // Use the object as NodeInstantiator model to be notified of its deletion NodeInstantiator { model: attribute - delegate: Entity { objectName: "DestructionWatcher [" + attribute.toString() + "]" } - onObjectRemoved: remove(idx) + delegate: Entity { objectName: "DestructionWatcher [" + model.toString() + "]" } + onObjectRemoved: remove(index) + } + + property bool alive: attribute ? attribute.node.alive : false + onAliveChanged: { + if(!alive && index >= 0) + remove(index) } // 'visible' property drives media loading request @@ -267,7 +276,7 @@ Entity { Component.onCompleted: { // keep 'source' -> 'entity' reference - m.sourceToEntity[modelSource] = mediaLoader; + m.sourceToEntity[modelSource] = instantiatedEntity; // always request media loading when delegate has been created updateModel(true); // if external media failed to open, remove element from model @@ -381,7 +390,8 @@ Entity { } onObjectRemoved: { - delete m.sourceToEntity[object.modelSource]; + if(m.sourceToEntity[object.modelSource]) + delete m.sourceToEntity[object.modelSource] } } } From bd5f515f98977cad13a877debb1e7eb01f20f905 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 17 Aug 2020 15:59:07 +0200 Subject: [PATCH 49/51] [ui] MediaLibrary: fix SfMTransform loading issue --- meshroom/ui/qml/Viewer3D/MediaLibrary.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml index 2e47488ec0..1ef3324524 100644 --- a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml +++ b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml @@ -317,9 +317,10 @@ Entity { model[prop] = Qt.binding(function() { return object ? object[prop] : 0; }); }) } - else if(finalSource) { + else if(finalSource && status === Component.Ready) { // source was valid but no loader was created, remove element - remove(index); + // check if component is ready to avoid removing element from the model before adding instance to the node + remove(index) } } From 12de900e96fae6720cfc2d2bbe212184a3d22486 Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Mon, 17 Aug 2020 17:50:44 +0200 Subject: [PATCH 50/51] [ui] fix binding warnings when closing Meshroom --- meshroom/ui/qml/GraphEditor/AttributePin.qml | 16 ++++++++-------- .../ui/qml/GraphEditor/CompatibilityManager.qml | 8 ++++---- meshroom/ui/qml/GraphEditor/GraphEditor.qml | 10 +++++----- meshroom/ui/qml/GraphEditor/Node.qml | 16 ++++++++-------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/AttributePin.qml b/meshroom/ui/qml/GraphEditor/AttributePin.qml index 27f78f0e1d..87ad4e71fa 100755 --- a/meshroom/ui/qml/GraphEditor/AttributePin.qml +++ b/meshroom/ui/qml/GraphEditor/AttributePin.qml @@ -16,15 +16,15 @@ RowLayout { readonly property point edgeAnchorPos: Qt.point(edgeAnchor.x + edgeAnchor.width/2, edgeAnchor.y + edgeAnchor.height/2) - readonly property bool isList: attribute.type == "ListAttribute" + readonly property bool isList: attribute && attribute.type === "ListAttribute" signal childPinCreated(var childAttribute, var pin) signal childPinDeleted(var childAttribute, var pin) signal pressed(var mouse) - objectName: attribute.name + "." - layoutDirection: attribute.isOutput ? Qt.RightToLeft : Qt.LeftToRight + objectName: attribute ? attribute.name + "." : "" + layoutDirection: attribute && attribute.isOutput ? Qt.RightToLeft : Qt.LeftToRight spacing: 2 // Instantiate empty Items for each child attribute @@ -49,7 +49,7 @@ RowLayout { color: { if(connectMA.containsMouse || connectMA.drag.active || (dropArea.containsDrag && dropArea.acceptableDrop)) return nameLabel.palette.highlight - else if(attribute.isLink) + else if(attribute && attribute.isLink) return "#3e3e3e" return "white" } @@ -93,7 +93,7 @@ RowLayout { objectName: "edgeConnector" readonly property alias attribute: root.attribute readonly property alias nodeItem: root.nodeItem - readonly property bool isOutput: attribute.isOutput + readonly property bool isOutput: attribute && attribute.isOutput readonly property alias isList: root.isList anchors.centerIn: root.state == "Dragging" ? undefined : parent width: 4 @@ -155,12 +155,12 @@ RowLayout { id: nameLabel property bool hovered: (connectMA.containsMouse || connectMA.drag.active || dropArea.containsDrag) - text: attribute.name + text: attribute ? attribute.name : "" elide: hovered ? Text.ElideNone : Text.ElideMiddle width: hovered ? contentWidth : parent.width font.pointSize: 5 - horizontalAlignment: attribute.isOutput ? Text.AlignRight : Text.AlignLeft - anchors.right: attribute.isOutput ? parent.right : undefined + horizontalAlignment: attribute && attribute.isOutput ? Text.AlignRight : Text.AlignLeft + anchors.right: attribute && attribute.isOutput ? parent.right : undefined background: Rectangle { visible: parent.hovered && metrics.truncated diff --git a/meshroom/ui/qml/GraphEditor/CompatibilityManager.qml b/meshroom/ui/qml/GraphEditor/CompatibilityManager.qml index c9cf40a1c5..d9cad1eed9 100644 --- a/meshroom/ui/qml/GraphEditor/CompatibilityManager.qml +++ b/meshroom/ui/qml/GraphEditor/CompatibilityManager.qml @@ -91,15 +91,15 @@ MessageDialog { Label { Layout.preferredWidth: 130 - text: compatibilityNodeDelegate.node.nodeType + text: compatibilityNodeDelegate.node ? compatibilityNodeDelegate.node.nodeType : "" } Label { Layout.fillWidth: true - text: compatibilityNodeDelegate.node.issueDetails + text: compatibilityNodeDelegate.node ? compatibilityNodeDelegate.node.issueDetails : "" } Label { - text: compatibilityNodeDelegate.node.canUpgrade ? MaterialIcons.check : MaterialIcons.clear - color: compatibilityNodeDelegate.node.canUpgrade ? "#4CAF50" : "#F44336" + text: compatibilityNodeDelegate.node && compatibilityNodeDelegate.node.canUpgrade ? MaterialIcons.check : MaterialIcons.clear + color: compatibilityNodeDelegate.node && compatibilityNodeDelegate.node.canUpgrade ? "#4CAF50" : "#F44336" font.family: MaterialIcons.fontFamily font.pointSize: 14 font.bold: true diff --git a/meshroom/ui/qml/GraphEditor/GraphEditor.qml b/meshroom/ui/qml/GraphEditor/GraphEditor.qml index b87fc085d5..fce38325fe 100755 --- a/meshroom/ui/qml/GraphEditor/GraphEditor.qml +++ b/meshroom/ui/qml/GraphEditor/GraphEditor.qml @@ -240,11 +240,11 @@ Item { id: edgesRepeater // delay edges loading after nodes (edges needs attribute pins to be created) - model: nodeRepeater.loaded ? root.graph.edges : undefined + model: nodeRepeater.loaded && root.graph ? root.graph.edges : undefined delegate: Edge { - property var src: root._attributeToDelegate[edge.src] - property var dst: root._attributeToDelegate[edge.dst] + property var src: edge ? root._attributeToDelegate[edge.src] : undefined + property var dst: edge ? root._attributeToDelegate[edge.dst] : undefined property var srcAnchor: src.nodeItem.mapFromItem(src, src.edgeAnchorPos.x, src.edgeAnchorPos.y) property var dstAnchor: dst.nodeItem.mapFromItem(dst, dst.edgeAnchorPos.x, dst.edgeAnchorPos.y) @@ -382,8 +382,8 @@ Item { Repeater { id: nodeRepeater - model: root.graph.nodes - property bool loaded: count === model.count + model: root.graph ? root.graph.nodes : undefined + property bool loaded: model ? count === model.count : false delegate: Node { id: nodeDelegate diff --git a/meshroom/ui/qml/GraphEditor/Node.qml b/meshroom/ui/qml/GraphEditor/Node.qml index b2c8e24907..858de8e9dc 100755 --- a/meshroom/ui/qml/GraphEditor/Node.qml +++ b/meshroom/ui/qml/GraphEditor/Node.qml @@ -10,7 +10,7 @@ Item { property bool readOnly: false property color baseColor: defaultColor property color shadowColor: "black" - readonly property bool isCompatibilityNode: node.hasOwnProperty("compatibilityIssue") + readonly property bool isCompatibilityNode: node ? node.hasOwnProperty("compatibilityIssue") : false readonly property color defaultColor: isCompatibilityNode ? "#444" : "#607D8B" property bool selected: false property bool hovered: false @@ -24,13 +24,13 @@ Item { signal attributePinDeleted(var attribute, var pin) implicitHeight: childrenRect.height - objectName: node.name + objectName: node ? node.name : "" SystemPalette { id: activePalette } // initialize position with node coordinates - x: root.node.x - y: root.node.y + x: root.node ? root.node.x : undefined + y: root.node ? root.node.y : undefined Connections { target: root.node @@ -93,7 +93,7 @@ Item { width: parent.width horizontalAlignment: Text.AlignHCenter padding: 4 - text: node.label + text: node ? node.label : "" color: "#EEE" font.pointSize: 8 background: Rectangle { @@ -106,7 +106,7 @@ Item { defaultColor: Qt.darker(baseColor, 1.3) implicitHeight: 3 width: parent.width - model: node.chunks + model: node ? node.chunks : undefined } Item { width: 1; height: 2} @@ -121,7 +121,7 @@ Item { width: parent.width / 2 spacing: 1 Repeater { - model: node.attributes + model: node ? node.attributes : undefined delegate: Loader { active: !object.isOutput && isDisplayableAsPin(object) width: inputs.width @@ -146,7 +146,7 @@ Item { anchors.right: parent.right spacing: 1 Repeater { - model: node.attributes + model: node ? node.attributes : undefined delegate: Loader { active: object.isOutput && isDisplayableAsPin(object) From 688027a69fcdee3af70e9d4f61883d11abfe23ad Mon Sep 17 00:00:00 2001 From: Julien-Haudegond <44610840+Julien-Haudegond@users.noreply.github.com> Date: Fri, 21 Aug 2020 17:25:18 +0200 Subject: [PATCH 51/51] [ui] MediaLibrary: fix dependency binding --- meshroom/core/attribute.py | 1 + meshroom/ui/qml/Viewer3D/MediaLibrary.qml | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/meshroom/core/attribute.py b/meshroom/core/attribute.py index 7030f7cbd3..f0489fc484 100644 --- a/meshroom/core/attribute.py +++ b/meshroom/core/attribute.py @@ -257,6 +257,7 @@ def updateInternals(self): isLink = Property(bool, isLink.fget, notify=isLinkChanged) isDefault = Property(bool, _isDefault, notify=valueChanged) linkParam = Property(BaseObject, getLinkParam, notify=isLinkChanged) + rootLinkParam = Property(BaseObject, lambda self: self.getLinkParam(recursive=True), notify=isLinkChanged) node = Property(BaseObject, node.fget, constant=True) enabledChanged = Signal() enabled = Property(bool, getEnabled, setEnabled, notify=enabledChanged) diff --git a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml index 1ef3324524..1c201cffe3 100644 --- a/meshroom/ui/qml/Viewer3D/MediaLibrary.qml +++ b/meshroom/ui/qml/Viewer3D/MediaLibrary.qml @@ -206,10 +206,10 @@ Entity { if(!attribute) // if the node is removed, the attribute will be invalid return false - if(attribute.isOutput) - return attribute.node.globalStatus === "SUCCESS" - if(attribute.isLink) - return attribute.linkParam.node.globalStatus === "SUCCESS" + + const rootAttribute = attribute.isLink ? attribute.rootLinkParam : attribute + if(rootAttribute.isOutput) + return rootAttribute.node.globalStatus === "SUCCESS" return true // is an input param so no dependency } // source based on raw source + dependency status