Meshroom/meshroom/ui/qml/Application.qml

1210 lines
No EOL
44 KiB
QML

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Controls 1.4 as Controls1 // For SplitView
import QtQuick.Layouts 1.11
import QtQuick.Window 2.15
import QtQml.Models 2.15
import Qt.labs.platform 1.0 as Platform
import QtQuick.Dialogs 1.3
import Qt.labs.settings 1.0
import GraphEditor 1.0
import MaterialIcons 2.2
import Utils 1.0
import Controls 1.0
Page {
id: root
property alias computingAtExitDialog: computingAtExitDialog
property alias unsavedDialog: unsavedDialog
property alias workspaceView: workspaceView
Settings {
id: settingsUILayout
category: 'UILayout'
property alias showLiveReconstruction: liveSfMVisibilityCB.checked
property alias showGraphEditor: graphEditorVisibilityCB.checked
property alias showImageViewer: imageViewerVisibilityCB.checked
property alias showViewer3D: viewer3DVisibilityCB.checked
}
// Utility functions for elements in the menubar
function getSelectedNodesName() {
if (!_reconstruction)
return ""
var nodesName = ""
for (var i = 0; i < _reconstruction.selectedNodes.count; i++) {
if (nodesName !== "")
nodesName += ", "
var node = _reconstruction.selectedNodes.at(i)
nodesName += node.name
}
return nodesName
}
property url imagesFolder: {
var recentImportedImagesFolders = MeshroomApp.recentImportedImagesFolders
if (recentImportedImagesFolders.length > 0) {
for (var i = 0; i < recentImportedImagesFolders.length; i++) {
if (Filepath.exists(recentImportedImagesFolders[i]))
return Filepath.stringToUrl(recentImportedImagesFolders[i])
else
MeshroomApp.removeRecentImportedImagesFolder(Filepath.stringToUrl(recentImportedImagesFolders[i]))
}
}
return ""
}
// File dialogs
Platform.FileDialog {
id: saveFileDialog
options: Platform.FileDialog.DontUseNativeDialog
signal closed(var result)
title: "Save File"
nameFilters: ["Meshroom Graphs (*.mg)"]
defaultSuffix: ".mg"
fileMode: Platform.FileDialog.SaveFile
onAccepted: {
_reconstruction.saveAs(currentFile)
MeshroomApp.addRecentProjectFile(currentFile.toString())
closed(Platform.Dialog.Accepted)
}
onRejected: closed(Platform.Dialog.Rejected)
}
Platform.FileDialog {
id: saveTemplateDialog
options: Platform.FileDialog.DontUseNativeDialog
signal closed(var result)
title: "Save Template"
nameFilters: ["Meshroom Graphs (*.mg)"]
defaultSuffix: ".mg"
fileMode: Platform.FileDialog.SaveFile
onAccepted: {
_reconstruction.saveAsTemplate(currentFile)
closed(Platform.Dialog.Accepted)
MeshroomApp.reloadTemplateList()
}
onRejected: closed(Platform.Dialog.Rejected)
}
Platform.FileDialog {
id: loadTemplateDialog
options: Platform.FileDialog.DontUseNativeDialog
title: "Load Template"
nameFilters: ["Meshroom Graphs (*.mg)"]
onAccepted: {
// Open the template as a regular file
if (_reconstruction.loadUrl(currentFile, true, true)) {
MeshroomApp.addRecentProjectFile(currentFile.toString())
}
}
}
Platform.FileDialog {
id: importImagesDialog
options: Platform.FileDialog.DontUseNativeDialog
title: "Import Images"
fileMode: Platform.FileDialog.OpenFiles
nameFilters: []
onAccepted: {
_reconstruction.importImagesUrls(currentFiles)
imagesFolder = Filepath.dirname(currentFiles[0])
MeshroomApp.addRecentImportedImagesFolder(imagesFolder)
}
}
Platform.FileDialog {
id: importProjectDialog
options: Platform.FileDialog.DontUseNativeDialog
title: "Import Project"
fileMode: Platform.FileDialog.OpenFile
nameFilters: ["Meshroom Graphs (*.mg)"]
onAccepted: {
graphEditor.uigraph.importProject(currentFile)
}
}
Item {
id: computeManager
property bool warnIfUnsaved: true
// Evaluate if graph computation can be submitted externally
property bool canSubmit: _reconstruction ?
_reconstruction.canSubmit // current setup allows to compute externally
&& _reconstruction.graph.filepath : // graph is saved on disk
false
function compute(nodes, force) {
if (!force && warnIfUnsaved && !_reconstruction.graph.filepath) {
unsavedComputeDialog.selectedNodes = nodes;
unsavedComputeDialog.open();
}
else {
try {
_reconstruction.execute(nodes)
}
catch (error) {
const data = ErrorHandler.analyseError(error)
if (data.context === "COMPUTATION")
computeSubmitErrorDialog.openError(data.type, data.msg, nodes)
}
}
}
function submit(nodes) {
if (!canSubmit) {
unsavedSubmitDialog.open()
} else {
try {
_reconstruction.submit(nodes)
}
catch (error) {
const data = ErrorHandler.analyseError(error)
if (data.context === "SUBMITTING")
computeSubmitErrorDialog.openError(data.type, data.msg, nodes)
}
}
}
MessageDialog {
id: computeSubmitErrorDialog
property string errorType // Used to specify signals' behavior
property var currentNode: null
function openError(type, msg, node) {
errorType = type
switch (type) {
case "Already Submitted": {
this.setupPendingStatusError(msg, node)
break
}
case "Compatibility Issue": {
this.setupCompatibilityIssue(msg)
break
}
default: {
this.onlyDisplayError(msg)
}
}
this.open()
}
function onlyDisplayError(msg) {
text = msg
standardButtons = Dialog.Ok
}
function setupPendingStatusError(msg, node) {
currentNode = node
text = msg + "\n\nDo you want to Clear Pending Status and Start Computing?"
standardButtons = (Dialog.Ok | Dialog.Cancel)
}
function setupCompatibilityIssue(msg) {
text = msg + "\n\nDo you want to open the Compatibility Manager?"
standardButtons = (Dialog.Ok | Dialog.Cancel)
}
canCopy: false
icon.text: MaterialIcons.warning
parent: Overlay.overlay
preset: "Warning"
title: "Computation/Submitting"
text: ""
onAccepted: {
switch (errorType) {
case "Already Submitted": {
close()
_reconstruction.graph.clearSubmittedNodes()
_reconstruction.execute(currentNode)
break
}
case "Compatibility Issue": {
close()
compatibilityManager.open()
break
}
default: close()
}
}
onRejected: close()
}
MessageDialog {
id: unsavedComputeDialog
property var selectedNodes: null
canCopy: false
icon.text: MaterialIcons.warning
parent: Overlay.overlay
preset: "Warning"
title: "Unsaved Project"
text: "Data will be computed in the default cache folder if project remains unsaved."
detailedText: "Default cache folder: " + (_reconstruction ? _reconstruction.graph.cacheDir : "unknown")
helperText: "Save project first?"
standardButtons: Dialog.Discard | Dialog.Cancel | Dialog.Save
CheckBox {
Layout.alignment: Qt.AlignRight
text: "Don't ask again for this session"
padding: 0
onToggled: computeManager.warnIfUnsaved = !checked
}
Component.onCompleted: {
// Set up discard button text
standardButton(Dialog.Discard).text = "Continue without Saving"
}
onDiscarded: {
close()
computeManager.compute(selectedNodes, true)
}
onAccepted: saveAsAction.trigger()
}
MessageDialog {
id: unsavedSubmitDialog
canCopy: false
icon.text: MaterialIcons.warning
parent: Overlay.overlay
preset: "Warning"
title: "Unsaved Project"
text: "The project cannot be submitted if it remains unsaved."
helperText: "Save the project to be able to submit it?"
standardButtons: Dialog.Cancel | Dialog.Save
onDiscarded: close()
onAccepted: saveAsAction.trigger()
}
MessageDialog {
id: fileModifiedDialog
canCopy: false
icon.text: MaterialIcons.warning
parent: Overlay.overlay
preset: "Warning"
title: "File Modified"
text: "The file has been modified by another instance."
detailedText: "Do you want to overwrite the file?"
// Add a reload file button next to the save button
footer: DialogButtonBox {
position: DialogButtonBox.Footer
standardButtons: Dialog.Save | Dialog.Cancel
Button {
text: "Reload File"
onClicked: {
_reconstruction.loadUrl(_reconstruction.graph.filepath)
fileModifiedDialog.close()
}
}
}
onAccepted: _reconstruction.save()
onDiscarded: close()
}
}
// Message dialogs
MessageDialog {
id: unsavedDialog
property var _callback: undefined
title: (_reconstruction ? Filepath.basename(_reconstruction.graph.filepath) : "") || "Unsaved Project"
preset: "Info"
canCopy: false
text: _reconstruction && _reconstruction.graph.filepath ? "Current project has unsaved modifications."
: "Current project has not been saved."
helperText: _reconstruction && _reconstruction.graph.filepath ? "Would you like to save those changes?"
: "Would you like to save this project?"
standardButtons: Dialog.Save | Dialog.Cancel | Dialog.Discard
onDiscarded: {
close() // BUG ? discard does not close window
fireCallback()
}
onRejected: {
_window.isClosing = false
}
onAccepted: {
// Save current file
if (saveAction.enabled && _reconstruction.graph.filepath) {
saveAction.trigger()
fireCallback()
}
// Open "Save As" dialog
else {
saveFileDialog.open()
function _callbackWrapper(rc) {
if (rc === Platform.Dialog.Accepted)
fireCallback()
saveFileDialog.closed.disconnect(_callbackWrapper)
}
saveFileDialog.closed.connect(_callbackWrapper)
}
}
function fireCallback()
{
// Call the callback and reset it
if (_callback)
_callback()
_callback = undefined
}
/// Open the unsaved dialog warning with an optional
/// callback to fire when the dialog is accepted/discarded
function prompt(callback)
{
_callback = callback
open()
}
}
MessageDialog {
id: computingAtExitDialog
title: "Operation in progress"
modal: true
canCopy: false
Label {
text: "Please stop any local computation before exiting Meshroom"
}
}
MessageDialog {
// Popup displayed while the application
// is busy building intrinsics while importing images
id: buildingIntrinsicsDialog
modal: true
visible: _reconstruction ? _reconstruction.buildingIntrinsics : false
closePolicy: Popup.NoAutoClose
title: "Initializing Cameras"
icon.text: MaterialIcons.camera
icon.font.pointSize: 10
canCopy: false
standardButtons: Dialog.NoButton
detailedText: "Extracting images metadata and creating Camera intrinsics..."
ProgressBar {
indeterminate: true
Layout.fillWidth: true
}
}
AboutDialog {
id: aboutDialog
}
DialogsFactory {
id: dialogsFactory
}
CompatibilityManager {
id: compatibilityManager
uigraph: _reconstruction
}
// Actions
Action {
id: removeAllImagesAction
property string tooltip: "Remove all the images from the current CameraInit group"
text: "Remove All Images"
onTriggered: {
_reconstruction.removeAllImages()
_reconstruction.selectedViewId = "-1"
}
}
Action {
id: removeImagesFromAllGroupsAction
property string tooltip: "Remove all the images from all the CameraInit groups"
text: "Remove Images From All CameraInit Nodes"
onTriggered: {
_reconstruction.removeImagesFromAllGroups()
_reconstruction.selectedViewId = "-1"
}
}
Action {
id: undoAction
property string tooltip: 'Undo "' + (_reconstruction ? _reconstruction.undoStack.undoText : "Unknown") + '"'
text: "Undo"
shortcut: "Ctrl+Z"
enabled: _reconstruction ? _reconstruction.undoStack.canUndo && _reconstruction.undoStack.isUndoableIndex : false
onTriggered: _reconstruction.undoStack.undo()
}
Action {
id: redoAction
property string tooltip: 'Redo "' + (_reconstruction ? _reconstruction.undoStack.redoText : "Unknown") + '"'
text: "Redo"
shortcut: "Ctrl+Shift+Z"
enabled: _reconstruction ? _reconstruction.undoStack.canRedo && !_reconstruction.undoStack.lockedRedo : false
onTriggered: _reconstruction.undoStack.redo()
}
Action {
id: cutAction
property string tooltip: {
var s = "Copy selected node"
s += (_reconstruction && _reconstruction.selectedNodes.count > 1 ? "s (" : " (") + getSelectedNodesName()
s += ") to the clipboard and remove them from the graph"
return s
}
text: "Cut Node" + (_reconstruction && _reconstruction.selectedNodes.count > 1 ? "s " : " ")
enabled: _reconstruction ? _reconstruction.selectedNodes.count > 0 : false
onTriggered: {
graphEditor.copyNodes()
graphEditor.uigraph.removeNodes(graphEditor.uigraph.selectedNodes)
}
}
Action {
id: copyAction
property string tooltip: {
var s = "Copy selected node"
s += (_reconstruction && _reconstruction.selectedNodes.count > 1 ? "s (" : " (") + getSelectedNodesName()
s += ") to the clipboard"
return s
}
text: "Copy Node" + (_reconstruction && _reconstruction.selectedNodes.count > 1 ? "s " : " ")
enabled: _reconstruction ? _reconstruction.selectedNodes.count > 0 : false
onTriggered: graphEditor.copyNodes()
}
Action {
id: pasteAction
property string tooltip: "Paste the clipboard content to the project if it contains valid nodes"
text: "Paste Node(s)"
onTriggered: graphEditor.pasteNodes()
}
Action {
id: loadTemplateAction
property string tooltip: "Load a template like a regular project file (any \"Publish\" node will be displayed)"
text: "Load Template"
onTriggered: {
ensureSaved(function() {
initFileDialogFolder(loadTemplateDialog);
loadTemplateDialog.open();
})
}
}
header: RowLayout {
spacing: 0
MaterialToolButton {
id: homeButton
text: MaterialIcons.home
font.pointSize: 18
background: Rectangle {
color: homeButton.hovered ? activePalette.highlight : Qt.darker(activePalette.window, 1.15)
border.color: Qt.darker(activePalette.window, 1.15)
}
onClicked: {
if (!ensureNotComputing())
return
ensureSaved(function() {
_reconstruction.clear()
if (mainStack.depth == 1)
mainStack.replace("Homepage.qml")
else
mainStack.pop()
})
}
}
MenuBar {
palette.window: Qt.darker(activePalette.window, 1.15)
Menu {
title: "File"
Action {
id: newAction
text: "New"
shortcut: "Ctrl+N"
onTriggered: ensureSaved(function() {
_reconstruction.new()
})
}
Menu {
id: newPipelineMenu
title: "New Pipeline"
enabled: newPipelineMenuItems.model !== undefined && newPipelineMenuItems.model.length > 0
property int maxWidth: 1000
property int fullWidth: {
var result = 0;
for (var i = 0; i < count; ++i) {
var item = itemAt(i)
result = Math.max(item.implicitWidth + item.padding * 2, result)
}
return result;
}
implicitWidth: fullWidth
Repeater {
id: newPipelineMenuItems
model: MeshroomApp.pipelineTemplateFiles
MenuItem {
onTriggered: ensureSaved(function() {
_reconstruction.new(modelData["key"])
})
text: fileTextMetrics.elidedText
TextMetrics {
id: fileTextMetrics
text: modelData["name"]
elide: Text.ElideLeft
elideWidth: newPipelineMenu.maxWidth
}
ToolTip {
id: toolTip
delay: 200
text: modelData["path"]
visible: hovered
}
}
}
}
Action {
id: openActionItem
text: "Open"
shortcut: "Ctrl+O"
onTriggered: ensureSaved(function() {
initFileDialogFolder(openFileDialog)
openFileDialog.open()
})
}
Menu {
id: openRecentMenu
title: "Open Recent"
enabled: recentFilesMenuItems.model !== undefined && recentFilesMenuItems.model.length > 0
property int maxWidth: 1000
property int fullWidth: {
var result = 0;
for (var i = 0; i < count; ++i) {
var item = itemAt(i)
result = Math.max(item.implicitWidth + item.padding * 2, result)
}
return result
}
implicitWidth: fullWidth
Repeater {
id: recentFilesMenuItems
model: MeshroomApp.recentProjectFiles
MenuItem {
onTriggered: ensureSaved(function() {
openRecentMenu.dismiss()
if (_reconstruction.loadUrl(modelData["path"])) {
MeshroomApp.addRecentProjectFile(modelData["path"])
} else {
MeshroomApp.removeRecentProjectFile(modelData["path"])
}
})
text: fileTextMetrics.elidedText
TextMetrics {
id: fileTextMetrics
text: modelData["path"]
elide: Text.ElideLeft
elideWidth: openRecentMenu.maxWidth
}
}
}
}
MenuSeparator { }
Action {
id: saveAction
text: "Save"
shortcut: "Ctrl+S"
enabled: _reconstruction ? (_reconstruction.graph && !_reconstruction.graph.filepath) || !_reconstruction.undoStack.clean : false
onTriggered: {
if (_reconstruction.graph.filepath) {
// Get current time date
var date = _reconstruction.graph.getFileDateVersionFromPath(_reconstruction.graph.filepath)
// Check if the file has been modified by another instance
if (_reconstruction.graph.fileDateVersion !== date) {
fileModifiedDialog.open()
} else
_reconstruction.save()
} else {
initFileDialogFolder(saveFileDialog)
saveFileDialog.open()
}
}
}
Action {
id: saveAsAction
text: "Save As..."
shortcut: "Ctrl+Shift+S"
onTriggered: {
initFileDialogFolder(saveFileDialog)
saveFileDialog.open()
}
}
MenuSeparator { }
Action {
id: importImagesAction
text: "Import Images"
shortcut: "Ctrl+I"
onTriggered: {
initFileDialogFolder(importImagesDialog, true)
importImagesDialog.open()
}
}
MenuItem {
action: removeAllImagesAction
ToolTip.visible: hovered
ToolTip.text: removeAllImagesAction.tooltip
}
MenuSeparator { }
Menu {
id: advancedMenu
title: "Advanced"
implicitWidth: 300
Action {
id: saveAsTemplateAction
text: "Save As Template..."
shortcut: Shortcut {
sequence: "Ctrl+Shift+T"
context: Qt.ApplicationShortcut
onActivated: saveAsTemplateAction.triggered()
}
onTriggered: {
initFileDialogFolder(saveTemplateDialog)
saveTemplateDialog.open()
}
}
MenuItem {
action: loadTemplateAction
ToolTip.visible: hovered
ToolTip.text: loadTemplateAction.tooltip
}
Action {
id: importProjectAction
text: "Import Project"
shortcut: Shortcut {
sequence: "Ctrl+Shift+I"
context: Qt.ApplicationShortcut
onActivated: importProjectAction.triggered()
}
onTriggered: {
initFileDialogFolder(importProjectDialog)
importProjectDialog.open()
}
}
MenuItem {
action: removeImagesFromAllGroupsAction
ToolTip.visible: hovered
ToolTip.text: removeImagesFromAllGroupsAction.tooltip
}
}
MenuSeparator { }
Action {
text: "Quit"
onTriggered: _window.close()
}
}
Menu {
title: "Edit"
MenuItem {
action: undoAction
ToolTip.visible: hovered
ToolTip.text: undoAction.tooltip
}
MenuItem {
action: redoAction
ToolTip.visible: hovered
ToolTip.text: redoAction.tooltip
}
MenuItem {
action: cutAction
ToolTip.visible: hovered
ToolTip.text: cutAction.tooltip
}
MenuItem {
action: copyAction
ToolTip.visible: hovered
ToolTip.text: copyAction.tooltip
}
MenuItem {
action: pasteAction
ToolTip.visible: hovered
ToolTip.text: pasteAction.tooltip
}
}
Menu {
title: "View"
MenuItem {
id: graphEditorVisibilityCB
text: "Graph Editor"
checkable: true
checked: true
}
MenuItem {
id: liveSfMVisibilityCB
text: "Live Reconstruction"
checkable: true
checked: false
}
MenuItem {
id: imageViewerVisibilityCB
text: "Image Viewer"
checkable: true
checked: true
}
MenuItem {
id: viewer3DVisibilityCB
text: "3D Viewer"
checkable: true
checked: true
}
MenuSeparator {}
Action {
text: "Fullscreen"
checkable: true
checked: _window.visibility == ApplicationWindow.FullScreen
shortcut: "Ctrl+F"
onTriggered: _window.visibility == ApplicationWindow.FullScreen ? _window.showNormal() : showFullScreen()
}
}
Menu {
title: "Process"
Action {
text: "Compute all nodes"
onTriggered: computeManager.compute(null)
enabled: _reconstruction ? !_reconstruction.computingLocally : false
}
Action {
text: "Submit all nodes"
onTriggered: computeManager.submit(null)
enabled: _reconstruction ? _reconstruction.canSubmit : false
}
MenuSeparator {}
Action {
text: "Stop computation"
onTriggered: _reconstruction.stopExecution()
enabled: _reconstruction ? _reconstruction.computingLocally : false
}
}
Menu {
title: "Help"
Action {
text: "Online Documentation"
onTriggered: Qt.openUrlExternally("https://meshroom-manual.readthedocs.io")
}
Action {
text: "About Meshroom"
onTriggered: aboutDialog.open()
// Should be StandardKey.HelpContents, but for some reason it's not stable
// (may cause crash, requires pressing F1 twice after closing the popup)
shortcut: "F1"
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Qt.darker(activePalette.window, 1.15)
}
Row {
// Process buttons
MaterialToolButton {
id: processButton
font.pointSize: 18
text: !(_reconstruction.computingLocally) ? MaterialIcons.send : MaterialIcons.cancel_schedule_send
ToolTip.text: !(_reconstruction.computingLocally) ? "Compute" : "Stop Computing"
ToolTip.visible: hovered
background: Rectangle {
color: processButton.hovered ? activePalette.highlight : Qt.darker(activePalette.window, 1.15)
border.color: Qt.darker(activePalette.window, 1.15)
}
onClicked: !(_reconstruction.computingLocally) ? computeManager.compute(null) : _reconstruction.stopExecution()
}
MaterialToolButton {
id: submitButton
font.pointSize: 18
visible: _reconstruction ? _reconstruction.canSubmit : false
text: MaterialIcons.rocket_launch
ToolTip.text: "Submit on Render Farm"
ToolTip.visible: hovered
background: Rectangle {
color: submitButton.hovered ? activePalette.highlight : Qt.darker(activePalette.window, 1.15)
border.color: Qt.darker(activePalette.window, 1.15)
}
onClicked: computeManager.submit(null)
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Qt.darker(activePalette.window, 1.15)
}
// CompatibilityManager indicator
ToolButton {
id: compatibilityIssuesButton
visible: compatibilityManager.issueCount
text: MaterialIcons.warning
font.family: MaterialIcons.fontFamily
palette.buttonText: "#FF9800"
font.pointSize: 12
onClicked: compatibilityManager.open()
ToolTip.text: "Compatibility Issues"
ToolTip.visible: hovered
background: Rectangle {
color: compatibilityIssuesButton.hovered ? activePalette.highlight : Qt.darker(activePalette.window, 1.15)
border.color: Qt.darker(activePalette.window, 1.15)
}
}
}
footer: ToolBar {
id: footer
padding: 1
leftPadding: 4
rightPadding: 4
palette.window: Qt.darker(activePalette.window, 1.15)
// Cache Folder
RowLayout {
spacing: 0
MaterialToolButton {
font.pointSize: 8
text: MaterialIcons.folder_open
ToolTip.text: "Open Cache Folder"
onClicked: Qt.openUrlExternally(Filepath.stringToUrl(_reconstruction.graph.cacheDir))
}
TextField {
readOnly: true
selectByMouse: true
text: _reconstruction ? _reconstruction.graph.cacheDir : "Unknown"
color: Qt.darker(palette.text, 1.2)
background: Item {}
}
}
}
Connections {
target: _reconstruction
// Bind messages to DialogsFactory
function createDialog(func, message) {
var dialog = func(_window)
// Set text afterwards to avoid dialog sizing issues
dialog.title = message.title
dialog.text = message.text
dialog.detailedText = message.detailedText
}
function onGraphChanged() {
// Open CompatibilityManager after file loading if any issue is detected
if (compatibilityManager.issueCount)
compatibilityManager.open()
// Trigger fit to visualize all nodes
graphEditor.fit()
}
function onInfo() { createDialog(dialogsFactory.info, arguments[0]) }
function onWarning() { createDialog(dialogsFactory.warning, arguments[0]) }
function onError() { createDialog(dialogsFactory.error, arguments[0]) }
}
Controls1.SplitView {
anchors.fill: parent
orientation: Qt.Vertical
// Setup global tooltip style
ToolTip.toolTip.background: Rectangle { color: activePalette.base; border.color: activePalette.mid }
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
implicitHeight: Math.round(parent.height * 0.7)
spacing: 4
// "ProgressBar" reflecting status of all the chunks in the graph, in their process order
NodeChunks {
id: chunksListView
Layout.fillWidth: true
height: 6
model: _reconstruction ? _reconstruction.sortedDFSChunks : null
highlightChunks: false
}
WorkspaceView {
id: workspaceView
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: 50
reconstruction: _reconstruction
readOnly: _reconstruction ? _reconstruction.computing : false
function viewNode(node, mouse) {
// 2D viewer
viewer2D.tryLoadNode(node)
// 3D viewer
// By default we only display the first 3D item, except if it has the semantic flag "3D"
var alreadyDisplay = false
for (var i = 0; i < node.attributes.count; i++) {
var attr = node.attributes.at(i)
if (attr.isOutput && attr.desc.semantic !== "image")
if (!alreadyDisplay || attr.desc.semantic == "3D")
if (workspaceView.viewIn3D(attr, mouse))
alreadyDisplay = true
}
}
function viewIn3D(attribute, mouse) {
if (!panel3dViewer || (!attribute.node.has3DOutput && !attribute.node.hasAttribute("useBoundingBox")))
return false
var loaded = panel3dViewer.viewer3D.view(attribute)
// solo media if Control modifier was held
if (loaded && mouse && mouse.modifiers & Qt.ControlModifier)
panel3dViewer.viewer3D.solo(attribute)
return loaded
}
}
}
Controls1.SplitView {
orientation: Qt.Horizontal
width: parent.width
height: Math.round(parent.height * 0.3)
visible: settingsUILayout.showGraphEditor
TabPanel {
id: graphEditorPanel
Layout.fillWidth: true
padding: 4
tabs: ["Graph Editor", "Task Manager", "Script Editor"]
headerBar: RowLayout {
MaterialToolButton {
text: MaterialIcons.sync
ToolTip.text: "Refresh Nodes Status"
ToolTip.visible: hovered
font.pointSize: 11
padding: 2
onClicked: {
updatingStatus = true
_reconstruction.forceNodesStatusUpdate()
updatingStatus = false
}
property bool updatingStatus: false
enabled: !updatingStatus
}
MaterialToolButton {
text: MaterialIcons.more_vert
font.pointSize: 11
padding: 2
onClicked: graphEditorMenu.open()
checkable: true
checked: graphEditorMenu.visible
Menu {
id: graphEditorMenu
y: parent.height
x: -width + parent.width
MenuItem {
text: "Clear Pending Status"
enabled: _reconstruction ? !_reconstruction.computingLocally : false
onTriggered: _reconstruction.graph.clearSubmittedNodes()
}
MenuItem {
text: "Force Unlock Nodes"
onTriggered: _reconstruction.graph.forceUnlockNodes()
}
Menu {
title: "Refresh Nodes Status"
MenuItem {
id: enableAutoRefresh
text: "Enable Auto-Refresh"
checkable: true
checked: _reconstruction.filePollerRefresh === 0
ToolTip.text: "Check every file's status periodically"
ToolTip.visible: hovered
ToolTip.delay: 200
onToggled: {
if (checked) {
disableAutoRefresh.checked = false
minimalAutoRefresh.checked = false
_reconstruction.filePollerRefreshChanged(0)
}
// Prevents cases where the user unchecks the currently checked option
enableAutoRefresh.checked = true
}
}
MenuItem {
id: disableAutoRefresh
text: "Disable Auto-Refresh"
checkable: true
checked: _reconstruction.filePollerRefresh === 1
ToolTip.text: "No file status will be checked"
ToolTip.visible: hovered
ToolTip.delay: 200
onToggled: {
if (checked) {
enableAutoRefresh.checked = false
minimalAutoRefresh.checked = false
_reconstruction.filePollerRefreshChanged(1)
}
// Prevents cases where the user unchecks the currently checked option
disableAutoRefresh.checked = true
}
}
MenuItem {
id: minimalAutoRefresh
text: "Enable Minimal Auto-Refresh"
checkable: true
checked: _reconstruction.filePollerRefresh === 2
ToolTip.text: "Check the file status of submitted or running chunks periodically"
ToolTip.visible: hovered
ToolTip.delay: 200
onToggled: {
if (checked) {
disableAutoRefresh.checked = false
enableAutoRefresh.checked = false
_reconstruction.filePollerRefreshChanged(2)
}
// Prevents cases where the user unchecks the currently checked option
minimalAutoRefresh.checked = true
}
}
}
}
}
}
GraphEditor {
id: graphEditor
visible: graphEditorPanel.currentTab === 0
anchors.fill: parent
uigraph: _reconstruction
nodeTypesModel: _nodeTypes
onNodeDoubleClicked: {
_reconstruction.setActiveNode(node);
workspaceView.viewNode(node, mouse);
}
onComputeRequest: {
_reconstruction.forceNodesStatusUpdate();
computeManager.compute(nodes)
}
onSubmitRequest: {
_reconstruction.forceNodesStatusUpdate();
computeManager.submit(nodes)
}
onFilesDropped: {
var filesByType = _reconstruction.getFilesByTypeFromDrop(drop.urls)
if (filesByType["meshroomScenes"].length == 1) {
ensureSaved(function() {
if (_reconstruction.handleFilesUrl(filesByType, null, mousePosition)) {
MeshroomApp.addRecentProjectFile(filesByType["meshroomScenes"][0])
}
})
} else {
_reconstruction.handleFilesUrl(filesByType, null, mousePosition)
}
}
}
TaskManager {
id: taskManager
visible: graphEditorPanel.currentTab === 1
uigraph: _reconstruction
taskManager: _reconstruction ? _reconstruction.taskManager : null
anchors.fill: parent
}
ScriptEditor {
id: scriptEditor
visible: graphEditorPanel.currentTab === 2
anchors.fill: parent
}
}
NodeEditor {
id: nodeEditor
width: Math.round(parent.width * 0.3)
node: _reconstruction ? _reconstruction.selectedNode : null
property bool computing: _reconstruction ? _reconstruction.computing : false
// Make NodeEditor readOnly when computing
readOnly: node ? node.locked : false
onUpgradeRequest: {
var n = _reconstruction.upgradeNode(node)
_reconstruction.selectedNode = n
}
}
}
}
}