Meshroom/meshroom/ui/qml/Viewer3D/Inspector3D.qml
Candice Bentéjac 5f41542ed5 [Viewer3D] Inspector3D: Use Fusion style instead of Material
`Material` was used by mistake following an erroneous import.
2024-11-25 14:48:51 +01:00

658 lines
32 KiB
QML

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt3D.Core 2.6
import Qt3D.Render 2.6
import Controls 1.0
import MaterialIcons 2.2
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: function(wheel) {
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
}
MaterialToolButton {
text: MaterialIcons.light_mode
ToolTip.text: "Display Light Controller"
checked: Viewer3DSettings.displayLightController
onClicked: Viewer3DSettings.displayLightController = !Viewer3DSettings.displayLightController
}
}
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
color: palette.text
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 &&
Viewer3DSettings.resectionGroups.length > 0)
id = Math.min(Viewer3DSettings.resectionId, Viewer3DSettings.resectionIdCount)
if (id !== undefined && Viewer3DSettings.resectionGroups[id] !== undefined)
return Viewer3DSettings.resectionGroups[id]
return 0
}
labelIconColor: palette.text
ToolTip.text: "Number Of Cameras In Current Resection Group"
visible: Viewer3DSettings.displayResectionIds
}
MaterialToolLabel {
iconText: MaterialIcons.auto_awesome_motion
label: {
let currentCameras = 0
if (Viewer3DSettings.resectionGroups) {
for (let i = 0; i <= Viewer3DSettings.resectionIdCount; i++) {
if (i <= Viewer3DSettings.resectionId)
currentCameras += Viewer3DSettings.resectionGroups[i]
}
}
return currentCameras
}
labelIconColor: palette.text
ToolTip.text: "Number Of Cumulated Cameras"
visible: Viewer3DSettings.displayResectionIds
}
MaterialToolLabel {
iconText: MaterialIcons.videocam
label: {
let totalCameras = 0
if (Viewer3DSettings.resectionGroups) {
for (let i = 0; i <= Viewer3DSettings.resectionIdCount; i++) {
totalCameras += Viewer3DSettings.resectionGroups[i]
}
}
return totalCameras
}
labelIconColor: palette.text
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: MScrollBar { 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: function(mouse) {
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: function(mouse) {
modifiers = mouse.modifiers
}
onExited: modifiers = Qt.NoModifier
onPressed: function(mouse) {
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
color: palette.text
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)
color: palette.text
}
}
RowLayout {
spacing: 1
visible: model.faceCount
MaterialLabel { text: MaterialIcons.details; rotation: -180 }
Label {
text: Format.intToString(model.faceCount)
color: palette.text
}
}
RowLayout {
spacing: 1
visible: model.cameraCount
MaterialLabel { text: MaterialIcons.videocam }
Label {
text: model.cameraCount
color: palette.text
}
}
RowLayout {
spacing: 1
visible: model.textureCount
MaterialLabel { text: MaterialIcons.texture }
Label {
text: model.textureCount
color: palette.text
}
}
}
}
}
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
}
}
}
}
}
}
}
}
}