mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-04-28 17:57:16 +02:00
Merge pull request #2235 from alicevision/dev/resectionIdFiltering
[Viewer3D] Add slider to display cameras based on their resection IDs
This commit is contained in:
commit
df6d38f391
9 changed files with 301 additions and 18 deletions
76
meshroom/ui/qml/Controls/ExpandableGroup.qml
Normal file
76
meshroom/ui/qml/Controls/ExpandableGroup.qml
Normal file
|
@ -0,0 +1,76 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.11
|
||||
import MaterialIcons 2.2
|
||||
|
||||
/**
|
||||
* A custom GroupBox with predefined header that can be hidden and expanded.
|
||||
*/
|
||||
GroupBox {
|
||||
id: root
|
||||
|
||||
title: ""
|
||||
property int sidePadding: 6
|
||||
property alias labelBackground: labelBg
|
||||
property alias toolBarContent: toolBar.data
|
||||
property bool expanded: expandButton.checked
|
||||
|
||||
padding: 2
|
||||
leftPadding: sidePadding
|
||||
rightPadding: sidePadding
|
||||
topPadding: label.height + padding
|
||||
background: Item {}
|
||||
|
||||
label: Pane {
|
||||
background: Rectangle {
|
||||
id: labelBg
|
||||
color: palette.base
|
||||
opacity: 0.8
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
expandButton.checked = !expandButton.checked
|
||||
}
|
||||
}
|
||||
}
|
||||
padding: 2
|
||||
width: root.width
|
||||
|
||||
RowLayout {
|
||||
width: parent.width
|
||||
Label {
|
||||
text: root.title
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
padding: 3
|
||||
font.bold: true
|
||||
font.pointSize: 8
|
||||
}
|
||||
RowLayout {
|
||||
id: toolBar
|
||||
height: parent.height
|
||||
|
||||
MaterialToolButton {
|
||||
id: expandButton
|
||||
ToolTip.text: "Expand More"
|
||||
text: MaterialIcons.expand_more
|
||||
font.pointSize: 10
|
||||
implicitHeight: parent.height
|
||||
checkable: true
|
||||
checked: false
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked) {
|
||||
ToolTip.text = "Expand Less"
|
||||
text = MaterialIcons.expand_less
|
||||
} else {
|
||||
ToolTip.text = "Expand More"
|
||||
text = MaterialIcons.expand_more
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
module Controls
|
||||
|
||||
ColorChart 1.0 ColorChart.qml
|
||||
ExpandableGroup 1.0 ExpandableGroup.qml
|
||||
FloatingPane 1.0 FloatingPane.qml
|
||||
Group 1.0 Group.qml
|
||||
KeyValue 1.0 KeyValue.qml
|
||||
|
|
|
@ -92,6 +92,7 @@ QtObject {
|
|||
readonly property string attachment: "\ue2bc"
|
||||
readonly property string audiotrack: "\ue3a1"
|
||||
readonly property string autorenew: "\ue863"
|
||||
readonly property string auto_awesome_motion: "\ue661"
|
||||
readonly property string av_timer: "\ue01b"
|
||||
readonly property string backspace: "\ue14a"
|
||||
readonly property string backup: "\ue864"
|
||||
|
|
|
@ -31,7 +31,8 @@ FloatingPane {
|
|||
anchors.fill: parent
|
||||
spacing: 4
|
||||
|
||||
Group {
|
||||
ExpandableGroup {
|
||||
id: displayGroup
|
||||
Layout.fillWidth: true
|
||||
title: "DISPLAY"
|
||||
|
||||
|
@ -43,6 +44,7 @@ FloatingPane {
|
|||
Flow {
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
visible: displayGroup.expanded
|
||||
spacing: 1
|
||||
MaterialToolButton {
|
||||
text: MaterialIcons.grid_on
|
||||
|
@ -63,8 +65,13 @@ FloatingPane {
|
|||
onClicked: Viewer3DSettings.displayOrigin = !Viewer3DSettings.displayOrigin
|
||||
}
|
||||
}
|
||||
MaterialLabel { text: MaterialIcons.grain; padding: 2 }
|
||||
MaterialLabel {
|
||||
text: MaterialIcons.grain
|
||||
padding: 2
|
||||
visible: displayGroup.expanded
|
||||
}
|
||||
RowLayout {
|
||||
visible: displayGroup.expanded
|
||||
Slider {
|
||||
Layout.fillWidth: true; from: 0; to: 5; stepSize: 0.001
|
||||
value: Viewer3DSettings.pointSize
|
||||
|
@ -83,8 +90,13 @@ FloatingPane {
|
|||
}
|
||||
|
||||
}
|
||||
MaterialLabel { text: MaterialIcons.videocam; padding: 2 }
|
||||
MaterialLabel {
|
||||
text: MaterialIcons.videocam
|
||||
padding: 2
|
||||
visible: displayGroup.expanded
|
||||
}
|
||||
Slider {
|
||||
visible: displayGroup.expanded
|
||||
value: Viewer3DSettings.cameraScale
|
||||
from: 0
|
||||
to: 2
|
||||
|
@ -99,25 +111,30 @@ FloatingPane {
|
|||
}
|
||||
}
|
||||
|
||||
Group {
|
||||
ExpandableGroup {
|
||||
id: cameraGroup
|
||||
Layout.fillWidth: true
|
||||
title: "CAMERA"
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
|
||||
// Image/Camera synchronization
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
visible: cameraGroup.expanded
|
||||
spacing: 2
|
||||
|
||||
// Synchronization
|
||||
MaterialToolButton {
|
||||
id: syncViewpointCamera
|
||||
enabled: _reconstruction ? _reconstruction.sfmReport : false
|
||||
text: MaterialIcons.linked_camera
|
||||
ToolTip.text: "Sync with Image Selection"
|
||||
ToolTip.text: "View Through The Active Camera"
|
||||
checked: enabled && Viewer3DSettings.syncViewpointCamera
|
||||
onClicked: Viewer3DSettings.syncViewpointCamera = !Viewer3DSettings.syncViewpointCamera
|
||||
}
|
||||
|
||||
// Image Overlay controls
|
||||
RowLayout {
|
||||
visible: syncViewpointCamera.enabled && Viewer3DSettings.syncViewpointCamera
|
||||
|
@ -152,6 +169,109 @@ FloatingPane {
|
|||
onClicked: Viewer3DSettings.viewpointImageFrame = !Viewer3DSettings.viewpointImageFrame
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
visible: cameraGroup.expanded
|
||||
|
||||
RowLayout {
|
||||
Layout.fillHeight: true
|
||||
spacing: 2
|
||||
|
||||
MaterialToolButton {
|
||||
id: resectionIdButton
|
||||
text: MaterialIcons.switch_video
|
||||
ToolTip.text: "Timeline Of Camera Reconstruction Groups"
|
||||
ToolTip.visible: hovered
|
||||
enabled: Viewer3DSettings.resectionIdCount
|
||||
checked: enabled && Viewer3DSettings.displayResectionIds
|
||||
onClicked: {
|
||||
Viewer3DSettings.displayResectionIds = !Viewer3DSettings.displayResectionIds
|
||||
Viewer3DSettings.resectionId = Viewer3DSettings.resectionIdCount
|
||||
resectionIdSlider.value = Viewer3DSettings.resectionId
|
||||
}
|
||||
|
||||
onEnabledChanged: {
|
||||
Viewer3DSettings.resectionId = Viewer3DSettings.resectionIdCount
|
||||
resectionIdSlider.value = Viewer3DSettings.resectionId
|
||||
if (!enabled) {
|
||||
Viewer3DSettings.displayResectionIds = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: resectionIdSlider
|
||||
value: Viewer3DSettings.resectionId
|
||||
from: 0
|
||||
to: Viewer3DSettings.resectionIdCount
|
||||
stepSize: 1
|
||||
onMoved: Viewer3DSettings.resectionId = value
|
||||
Layout.fillWidth: true
|
||||
leftPadding: 2
|
||||
rightPadding: 2
|
||||
visible: Viewer3DSettings.displayResectionIds
|
||||
}
|
||||
|
||||
Label {
|
||||
text: resectionIdSlider.value + "/" + Viewer3DSettings.resectionIdCount
|
||||
visible: Viewer3DSettings.displayResectionIds
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 2
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
|
||||
MaterialToolLabel {
|
||||
iconText: MaterialIcons.stop
|
||||
label: {
|
||||
var id = undefined
|
||||
// Ensure there are entries in resectionGroups and a valid resectionId before accessing anything
|
||||
if (Viewer3DSettings.resectionId !== undefined && Viewer3DSettings.resectionGroups.length > 0)
|
||||
id = Math.min(Viewer3DSettings.resectionId, Viewer3DSettings.resectionIdCount)
|
||||
if (id !== undefined && Viewer3DSettings.resectionGroups[id] !== undefined)
|
||||
return Viewer3DSettings.resectionGroups[id]
|
||||
return 0
|
||||
|
||||
}
|
||||
ToolTip.text: "Number Of Cameras In Current Resection Group"
|
||||
visible: Viewer3DSettings.displayResectionIds
|
||||
}
|
||||
|
||||
MaterialToolLabel {
|
||||
iconText: MaterialIcons.auto_awesome_motion
|
||||
label: {
|
||||
let currentCameras = 0
|
||||
for (let i = 0; i <= Viewer3DSettings.resectionIdCount; i++) {
|
||||
if (i <= Viewer3DSettings.resectionId)
|
||||
currentCameras += Viewer3DSettings.resectionGroups[i]
|
||||
}
|
||||
|
||||
return currentCameras
|
||||
}
|
||||
ToolTip.text: "Number Of Cumulated Cameras"
|
||||
visible: Viewer3DSettings.displayResectionIds
|
||||
}
|
||||
|
||||
MaterialToolLabel {
|
||||
iconText: MaterialIcons.videocam
|
||||
label: {
|
||||
let totalCameras = 0
|
||||
for (let i = 0; i <= Viewer3DSettings.resectionIdCount; i++) {
|
||||
totalCameras += Viewer3DSettings.resectionGroups[i]
|
||||
}
|
||||
|
||||
return totalCameras
|
||||
}
|
||||
ToolTip.text: "Total Number Of Cameras"
|
||||
visible: Viewer3DSettings.displayResectionIds
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,6 +312,12 @@ FloatingPane {
|
|||
|
||||
ScrollBar.vertical: ScrollBar { id: scrollBar }
|
||||
|
||||
onCountChanged: {
|
||||
if (mediaListView.count === 0) {
|
||||
Viewer3DSettings.resectionIdCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
currentIndex: -1
|
||||
|
||||
Connections {
|
||||
|
@ -217,13 +343,25 @@ FloatingPane {
|
|||
// add mediaLibrary.count in the binding to ensure 'entity'
|
||||
// is re-evaluated when mediaLibrary delegates are modified
|
||||
property bool loading: model.status === SceneLoader.Loading
|
||||
property bool hovered: model.attribute ? (uigraph ? uigraph.hoveredNode === model.attribute.node : false) : containsMouse
|
||||
property bool hovered: model.attribute ? (uigraph ? uigraph.hoveredNode === model.attribute.node : false) : containsMouse
|
||||
property bool isSelectedNode: model.attribute ? (uigraph ? uigraph.selectedNode === model.attribute.node : false) : false
|
||||
|
||||
onIsSelectedNodeChanged: updateCurrentIndex()
|
||||
|
||||
function updateCurrentIndex() {
|
||||
if(isSelectedNode) { mediaListView.currentIndex = index }
|
||||
if (isSelectedNode) {
|
||||
mediaListView.currentIndex = index
|
||||
}
|
||||
|
||||
// If the index is updated, and the resection ID count is available, update every resection-related variable:
|
||||
// this covers the changes of index that occur when a node whose output is already loaded in the 3D viewer is
|
||||
// clicked/double-clicked, and when the active entry is removed from the list.
|
||||
if (model.resectionIdCount) {
|
||||
Viewer3DSettings.resectionIdCount = model.resectionIdCount
|
||||
Viewer3DSettings.resectionGroups = model.resectionGroups
|
||||
Viewer3DSettings.resectionId = model.resectionId
|
||||
resectionIdSlider.value = model.resectionId
|
||||
}
|
||||
}
|
||||
|
||||
height: childrenRect.height
|
||||
|
@ -234,22 +372,41 @@ FloatingPane {
|
|||
}
|
||||
|
||||
hoverEnabled: true
|
||||
onEntered: { if (model.attribute) uigraph.hoveredNode = model.attribute.node }
|
||||
onExited: { if (model.attribute) uigraph.hoveredNode = null }
|
||||
onEntered: {
|
||||
if (model.attribute)
|
||||
uigraph.hoveredNode = model.attribute.node
|
||||
}
|
||||
onExited: {
|
||||
if (model.attribute)
|
||||
uigraph.hoveredNode = null
|
||||
}
|
||||
onClicked: {
|
||||
if (model.attribute)
|
||||
uigraph.selectedNode = model.attribute.node;
|
||||
uigraph.selectedNode = model.attribute.node
|
||||
else
|
||||
uigraph.selectedNode = null;
|
||||
uigraph.selectedNode = null
|
||||
if (mouse.button == Qt.RightButton)
|
||||
contextMenu.popup();
|
||||
mediaListView.currentIndex = index;
|
||||
contextMenu.popup()
|
||||
mediaListView.currentIndex = index
|
||||
|
||||
// Update the resection ID-related objects based on the active model
|
||||
Viewer3DSettings.resectionIdCount = model.resectionIdCount
|
||||
Viewer3DSettings.resectionGroups = model.resectionGroups
|
||||
Viewer3DSettings.resectionId = model.resectionId
|
||||
resectionIdSlider.value = model.resectionId
|
||||
}
|
||||
onDoubleClicked: {
|
||||
model.visible = true;
|
||||
nodeActivated(model.attribute.node);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: resectionIdSlider
|
||||
function onValueChanged() {
|
||||
model.resectionId = resectionIdSlider.value
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
width: parent.width
|
||||
spacing: 2
|
||||
|
|
|
@ -58,6 +58,9 @@ Entity {
|
|||
"faceCount": 0,
|
||||
"cameraCount": 0,
|
||||
"textureCount": 0,
|
||||
"resectionIdCount": 0,
|
||||
"resectionId": 0,
|
||||
"resectionGroups": [],
|
||||
"status": SceneLoader.None
|
||||
})
|
||||
}
|
||||
|
@ -308,12 +311,11 @@ Entity {
|
|||
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"]
|
||||
var boundProperties = ["vertexCount", "faceCount", "cameraCount", "textureCount", "resectionIdCount", "resectionId", "resectionGroups"]
|
||||
boundProperties.forEach(function(prop) {
|
||||
model[prop] = Qt.binding(function() { return object ? object[prop] : 0 })
|
||||
})
|
||||
}
|
||||
else if (finalSource && status === Component.Ready) {
|
||||
} else if (finalSource && status === Component.Ready) {
|
||||
// source was valid but no loader was created, remove element
|
||||
// check if component is ready to avoid removing element from the model before adding instance to the node
|
||||
remove(index)
|
||||
|
|
|
@ -99,8 +99,9 @@ import Utils 1.0
|
|||
'source': source,
|
||||
'pointSize': Qt.binding(function() { return 0.01 * Viewer3DSettings.pointSize }),
|
||||
'locatorScale': Qt.binding(function() { return Viewer3DSettings.cameraScale }),
|
||||
'cameraPickingEnabled': Qt.binding(function() { return root.enabled })
|
||||
})
|
||||
'cameraPickingEnabled': Qt.binding(function() { return root.enabled }),
|
||||
'resectionId': Qt.binding(function() { return Viewer3DSettings.resectionId })
|
||||
});
|
||||
|
||||
obj.statusChanged.connect(function() {
|
||||
if (obj.status === SceneLoader.Ready) {
|
||||
|
@ -109,6 +110,11 @@ import Utils 1.0
|
|||
}
|
||||
cameraCount = obj.spawnCameraSelectors();
|
||||
}
|
||||
Viewer3DSettings.resectionIdCount = obj.countResectionIds();
|
||||
Viewer3DSettings.resectionGroups = obj.countResectionGroups(Viewer3DSettings.resectionIdCount + 1);
|
||||
resectionIdCount = Viewer3DSettings.resectionIdCount
|
||||
resectionGroups = Viewer3DSettings.resectionGroups
|
||||
resectionId = Viewer3DSettings.resectionIdCount
|
||||
root.status = obj.status;
|
||||
})
|
||||
}
|
||||
|
|
|
@ -17,4 +17,10 @@ Entity {
|
|||
property int cameraCount
|
||||
/// Number of textures
|
||||
property int textureCount
|
||||
/// Number of resection IDs
|
||||
property int resectionIdCount
|
||||
/// Current resection ID
|
||||
property int resectionId
|
||||
/// Groups of cameras based on resection IDs
|
||||
property var resectionGroups
|
||||
}
|
||||
|
|
|
@ -33,6 +33,34 @@ SfmDataEntity {
|
|||
return validCameras;
|
||||
}
|
||||
|
||||
function countResectionIds() {
|
||||
var maxResectionId = 0
|
||||
for (var i = 0; i < root.cameras.length; i++) {
|
||||
var cam = root.cameras[i]
|
||||
var resectionId = cam.resectionId
|
||||
if (resectionId === undefined || resectionId === 4294967295)
|
||||
continue
|
||||
if (resectionId > maxResectionId)
|
||||
maxResectionId = resectionId
|
||||
}
|
||||
|
||||
return maxResectionId
|
||||
}
|
||||
|
||||
|
||||
function countResectionGroups(resectionIdCount) {
|
||||
var arr = Array(resectionIdCount).fill(0)
|
||||
for (var i = 0; i < root.cameras.length; i++) {
|
||||
var cam = root.cameras[i]
|
||||
var resectionId = cam.resectionId
|
||||
if (resectionId === undefined || resectionId === 4294967295)
|
||||
continue
|
||||
arr[resectionId] = arr[resectionId] + 1
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
|
||||
SystemPalette {
|
||||
id: activePalette
|
||||
}
|
||||
|
|
|
@ -56,4 +56,10 @@ Item {
|
|||
readonly property bool showViewpointImageOverlay: syncViewpointCamera && viewpointImageOverlay
|
||||
property bool viewpointImageFrame: false
|
||||
readonly property bool showViewpointImageFrame: syncViewpointCamera && viewpointImageFrame
|
||||
|
||||
// Cameras' resection IDs
|
||||
property bool displayResectionIds: false
|
||||
property int resectionIdCount: 0
|
||||
property int resectionId: resectionIdCount
|
||||
property var resectionGroups: [] // Number of cameras for each resection ID
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue