Meshroom/meshroom/ui/qml/Viewer3D/Inspector3D.qml
Aurore LAFAURIE 361f777118 [ui] Quick Fix of syncViewpointCamera when no element in Scene
Disable View Through the active camera if no element in inspector 3d
2024-04-10 19:39:36 +02:00

623 lines
30 KiB
QML

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.11
import MaterialIcons 2.2
import Qt3D.Core 2.15
import Qt3D.Render 2.15
import QtQuick.Controls.Material 2.12
import Controls 1.0
import Utils 1.0
FloatingPane {
id: root
implicitWidth: 200
property int renderMode: 2
property Grid3D grid: null
property MediaLibrary mediaLibrary
property Camera camera
property var uigraph: null
signal mediaFocusRequest(var index)
signal mediaRemoveRequest(var index)
signal nodeActivated(var node)
padding: 0
MouseArea { anchors.fill: parent; onWheel: wheel.accepted = true }
ColumnLayout {
anchors.fill: parent
spacing: 4
ExpandableGroup {
id: displayGroup
Layout.fillWidth: true
title: "DISPLAY"
GridLayout {
width: parent.width
columns: 2
columnSpacing: 6
rowSpacing: 3
Flow {
Layout.columnSpan: 2
Layout.fillWidth: true
visible: displayGroup.expanded
spacing: 1
MaterialToolButton {
text: MaterialIcons.grid_on
ToolTip.text: "Display Grid"
checked: Viewer3DSettings.displayGrid
onClicked: Viewer3DSettings.displayGrid = !Viewer3DSettings.displayGrid
}
MaterialToolButton {
text: MaterialIcons.adjust
checked: Viewer3DSettings.displayGizmo
ToolTip.text: "Display Trackball"
onClicked: Viewer3DSettings.displayGizmo = !Viewer3DSettings.displayGizmo
}
MaterialToolButton {
text: MaterialIcons.call_merge
ToolTip.text: "Display Origin"
checked: Viewer3DSettings.displayOrigin
onClicked: Viewer3DSettings.displayOrigin = !Viewer3DSettings.displayOrigin
}
}
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
onValueChanged: Viewer3DSettings.pointSize = value
ToolTip.text: "Point Size: " + value.toFixed(2)
ToolTip.visible: hovered || pressed
ToolTip.delay: 150
}
MaterialToolButton {
text: MaterialIcons.center_focus_strong
ToolTip.text: "Fixed Point Size"
font.pointSize: 10
padding: 3
checked: Viewer3DSettings.fixedPointSize
onClicked: Viewer3DSettings.fixedPointSize = !Viewer3DSettings.fixedPointSize
}
}
MaterialLabel {
text: MaterialIcons.videocam
padding: 2
visible: displayGroup.expanded
}
Slider {
visible: displayGroup.expanded
value: Viewer3DSettings.cameraScale
from: 0
to: 2
stepSize: 0.01
Layout.fillWidth: true
padding: 0
onMoved: Viewer3DSettings.cameraScale = value
ToolTip.text: "Camera Scale: " + value.toFixed(2)
ToolTip.visible: hovered || pressed
ToolTip.delay: 150
}
}
}
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 && mediaLibrary.count > 0 ? _reconstruction.sfmReport : false
text: MaterialIcons.linked_camera
ToolTip.text: "View Through The Active Camera"
checkable: true
checked: enabled && Viewer3DSettings.syncViewpointCamera
onCheckedChanged: Viewer3DSettings.syncViewpointCamera = !Viewer3DSettings.syncViewpointCamera
}
// Image Overlay controls
RowLayout {
visible: syncViewpointCamera.enabled && Viewer3DSettings.syncViewpointCamera
spacing: 2
// Activation
MaterialToolButton {
text: MaterialIcons.image
ToolTip.text: "Image Overlay"
checked: Viewer3DSettings.viewpointImageOverlay
onClicked: Viewer3DSettings.viewpointImageOverlay = !Viewer3DSettings.viewpointImageOverlay
}
// Opacity
Slider {
visible: Viewer3DSettings.showViewpointImageOverlay
implicitWidth: 60
from: 0
to: 100
value: Viewer3DSettings.viewpointImageOverlayOpacity * 100
onValueChanged: Viewer3DSettings.viewpointImageOverlayOpacity = value / 100
ToolTip.text: "Image Opacity: " + Viewer3DSettings.viewpointImageOverlayOpacity.toFixed(2)
ToolTip.visible: hovered || pressed
ToolTip.delay: 100
}
}
// Image Frame control
MaterialToolButton {
visible: syncViewpointCamera.enabled && Viewer3DSettings.showViewpointImageOverlay
enabled: Viewer3DSettings.syncViewpointCamera
text: MaterialIcons.crop_free
ToolTip.text: "Frame Overlay"
checked: Viewer3DSettings.viewpointImageFrame
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
}
}
}
}
}
// 3D Scene content
Group {
title: "SCENE"
Layout.fillWidth: true
Layout.fillHeight: true
sidePadding: 0
toolBarContent: MaterialToolButton {
id: infoButton
ToolTip.text: "Media Info"
text: MaterialIcons.info_outline
font.pointSize: 10
implicitHeight: parent.height
checkable: true
checked: true
}
ColumnLayout {
anchors.fill: parent
SearchBar {
id: searchBar
Layout.minimumWidth: 150
Layout.fillWidth: true
Layout.rightMargin: 10
Layout.leftMargin: 10
}
ListView {
id: mediaListView
Layout.fillHeight: true
Layout.fillWidth: true
clip: true
spacing: 4
ScrollBar.vertical: ScrollBar { id: scrollBar }
onCountChanged: {
if (mediaListView.count === 0) {
Viewer3DSettings.resectionIdCount = 0
}
}
currentIndex: -1
Connections {
target: uigraph
function onSelectedNodeChanged() {
mediaListView.currentIndex = -1
}
}
Connections {
target: mediaLibrary
function onLoadRequest(idx) {
mediaListView.positionViewAtIndex(idx, ListView.Visible)
}
}
model: SortFilterDelegateModel {
model: mediaLibrary.model
sortRole: ""
filters: [{role: "label", value: searchBar.text}]
delegate: MouseArea {
id: mediaDelegate
// 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 isSelectedNode: model.attribute ? (uigraph ? uigraph.selectedNode === model.attribute.node : false) : false
onIsSelectedNodeChanged: updateCurrentIndex()
function updateCurrentIndex() {
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
width: {
if (parent != null)
return parent.width - scrollBar.width
return undefined
}
hoverEnabled: true
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
else
uigraph.selectedNode = null
if (mouse.button == Qt.RightButton)
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
property string src: model.source
onSrcChanged: focusAnim.restart()
Connections {
target: mediaListView
function onCountChanged() {
mediaDelegate.updateCurrentIndex()
}
}
// Current/selected element indicator
Rectangle {
Layout.fillHeight: true
width: 2
color: {
if (mediaListView.currentIndex == index || mediaDelegate.isSelectedNode)
return label.palette.highlight;
if (mediaDelegate.hovered)
return Qt.darker(label.palette.highlight, 1.5);
return "transparent";
}
}
// Media visibility/loading control
MaterialToolButton {
Layout.alignment: Qt.AlignTop
Layout.fillHeight: true
text: model.visible ? MaterialIcons.visibility : MaterialIcons.visibility_off
font.pointSize: 10
ToolTip.text: model.visible ? "Hide" : model.requested ? "Show" : model.valid ? "Load and Show" : "Load and Show when Available"
flat: true
opacity: model.visible ? 1.0 : 0.6
onClicked: {
if (hoverArea.modifiers & Qt.ControlModifier)
mediaLibrary.solo(index);
else
model.visible = !model.visible
}
// Handle modifiers on button click
MouseArea {
id: hoverArea
property int modifiers
anchors.fill: parent
hoverEnabled: true
onPositionChanged: modifiers = mouse.modifiers
onExited: modifiers = Qt.NoModifier
onPressed: {
modifiers = mouse.modifiers;
mouse.accepted = false;
}
}
}
// BoundingBox visibility (if meshing node)
MaterialToolButton {
visible: model.hasBoundingBox
enabled: model.visible
Layout.alignment: Qt.AlignTop
Layout.fillHeight: true
text: MaterialIcons.transform
font.pointSize: 10
ToolTip.text: model.displayBoundingBox ? "Hide BBox" : "Show BBox"
flat: true
opacity: model.visible ? (model.displayBoundingBox ? 1.0 : 0.6) : 0.6
onClicked: {
model.displayBoundingBox = !model.displayBoundingBox
}
}
// 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
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
ColumnLayout {
id: centralLayout
width: parent.width
spacing: 1
Label {
id: label
Layout.fillWidth: true
leftPadding: 0
rightPadding: 0
topPadding: 3
bottomPadding: topPadding
text: model.label
opacity: model.valid ? 1.0 : 0.6
elide: Text.ElideMiddle
font.weight: mediaListView.currentIndex === index ? Font.DemiBold : Font.Normal
background: Rectangle {
Connections {
target: mediaLibrary
function onLoadRequest(idx) {
if (idx === index)
focusAnim.restart()
}
}
ColorAnimation on color {
id: focusAnim
from: label.palette.highlight
to: "transparent"
duration: 2000
}
}
}
Item {
visible: infoButton.checked
Layout.fillWidth: true
implicitHeight: childrenRect.height
Flow {
width: parent.width
spacing: 4
visible: model.status === SceneLoader.Ready
RowLayout {
spacing: 1
visible: model.vertexCount
MaterialLabel { text: MaterialIcons.grain }
Label { text: Format.intToString(model.vertexCount) }
}
RowLayout {
spacing: 1
visible: model.faceCount
MaterialLabel { text: MaterialIcons.details; rotation: -180 }
Label { text: Format.intToString(model.faceCount) }
}
RowLayout {
spacing: 1
visible: model.cameraCount
MaterialLabel { text: MaterialIcons.videocam }
Label { text: model.cameraCount }
}
RowLayout {
spacing: 1
visible: model.textureCount
MaterialLabel { text: MaterialIcons.texture }
Label { text: model.textureCount }
}
}
}
}
Menu {
id: contextMenu
MenuItem {
text: "Open Containing Folder"
enabled: model.valid
onTriggered: Qt.openUrlExternally(Filepath.dirname(model.source))
}
MenuItem {
text: "Copy Path"
onTriggered: Clipboard.setText(Filepath.normpath(model.source))
}
MenuSeparator {}
MenuItem {
text: model.requested ? "Unload Media" : "Load Media"
enabled: model.valid
onTriggered: model.requested = !model.requested
}
}
}
// Remove media from library button
MaterialToolButton {
id: removeButton
Layout.alignment: Qt.AlignTop
Layout.fillHeight: true
visible: !loading && mediaDelegate.containsMouse
text: MaterialIcons.clear
font.pointSize: 10
ToolTip.text: "Remove"
ToolTip.delay: 500
onClicked: mediaLibrary.remove(index)
}
// Media loading indicator
BusyIndicator {
visible: loading
running: visible
padding: removeButton.padding
implicitHeight: implicitWidth
implicitWidth: removeButton.width
}
}
}
}
}
}
}
}
}