Meshroom/meshroom/ui/qml/main.qml
Candice Bentéjac 614f831457 [ui] Force nodes' status update before performing a submit/compute/delete
This ensures that each node's status is correct before being computed,
submitted, or before having its data deleted. This is especially useful
when the File Poller is in minimal node, and only monitors the nodes that
are currently submitted or running.

If a graph is being opened in two different instances of Meshroom, and
computations are started on it in one of the instances, the other one will
not be aware of it (as the signals indicating computations have started
will have been emitted in the first instance, so no chunk will be added
to the monitoring in the second one). By forcing the update of the statuses
before actually starting computations or deleting data, we ensure that
there will not be any computational conflicts (same nodes submitted twice
in farm, for example) and that the users can know at all times what they
are doing without manually triggering a refresh.

This is not critical for the "Delete Data" part, as the action can already
be triggered even with there is no data to delete, but is still useful
to keep the displayed nodes as up-to-date with their actual status as
possible.
2023-03-02 19:13:00 +01:00

1137 lines
42 KiB
QML

import QtQuick 2.7
import QtQuick.Controls 2.3
import QtQuick.Controls 1.4 as Controls1 // For SplitView
import QtQuick.Layouts 1.1
import QtQuick.Window 2.3
import QtQml.Models 2.2
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
ApplicationWindow {
id: _window
width: settings_General.windowWidth
height: settings_General.windowHeight
minimumWidth: 650
minimumHeight: 500
visible: true
title: {
var t = (_reconstruction && _reconstruction.graph && _reconstruction.graph.filepath) ? _reconstruction.graph.filepath : "Untitled"
if (_reconstruction && !_reconstruction.undoStack.clean)
t += "*"
t += " - " + Qt.application.name + " " + Qt.application.version
return t
}
onClosing: {
// make sure document is saved before exiting application
close.accepted = false
if(!ensureNotComputing())
return
ensureSaved(function(){ Qt.quit() })
}
// force Application palette assignation
// note: should be implicit (PySide bug)
palette: _PaletteManager.palette
SystemPalette { id: activePalette }
SystemPalette { id: disabledPalette; colorGroup: SystemPalette.Disabled }
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 ""
}
Settings {
id: settings_General
category: 'General'
property int windowWidth: 1280
property int windowHeight: 720
}
Settings {
id: settings_UILayout
category: 'UILayout'
property alias showLiveReconstruction: liveSfMVisibilityCB.checked
property alias showGraphEditor: graphEditorVisibilityCB.checked
property alias showImageViewer: imageViewerVisibilityCB.checked
property alias showViewer3D: viewer3DVisibilityCB.checked
}
Component.onDestruction: {
// store main window dimensions in persisting Settings
settings_General.windowWidth = _window.width
settings_General.windowHeight = _window.height
}
Shortcut {
// Ensures the Ctrl+N shortcut is always valid and creates a default pipeline
sequence: "Ctrl+N"
context: Qt.ApplicationShortcut
onActivated: ensureSaved(function() { _reconstruction.new() })
}
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()
}
onAccepted: {
// save current file
if(saveAction.enabled)
{
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()
}
}
Platform.FileDialog {
id: saveFileDialog
signal closed(var result)
title: "Save File"
nameFilters: ["Meshroom Graphs (*.mg)"]
defaultSuffix: ".mg"
fileMode: Platform.FileDialog.SaveFile
onAccepted: {
_reconstruction.saveAs(file)
closed(Platform.Dialog.Accepted)
MeshroomApp.addRecentProjectFile(file.toString())
}
onRejected: closed(Platform.Dialog.Rejected)
}
Platform.FileDialog {
id: saveTemplateDialog
signal closed(var result)
title: "Save Template"
nameFilters: ["Meshroom Graphs (*.mg)"]
defaultSuffix: ".mg"
fileMode: Platform.FileDialog.SaveFile
onAccepted: {
_reconstruction.saveAsTemplate(file)
closed(Platform.Dialog.Accepted)
MeshroomApp.reloadTemplateList()
}
onRejected: closed(Platform.Dialog.Rejected)
}
Item {
id: computeManager
property bool warnIfUnsaved: true
// evaluate if global reconstruction computation can be started
property bool canStartComputation: _reconstruction ?
_reconstruction.viewpoints.count >= 2 // at least 2 images
&& !_reconstruction.computing // computation is not started
&& _reconstruction.graph.canComputeLeaves : // graph has no uncomputable nodes
false
// evaluate if graph computation can be submitted externally
property bool canSubmit: _reconstruction ?
_reconstruction.canSubmit // current setup allows to compute externally
&& canStartComputation // can be computed
&& _reconstruction.graph.filepath : // graph is saved on disk
false
function compute(node, force) {
if(!force && warnIfUnsaved && !_reconstruction.graph.filepath)
{
unsavedComputeDialog.currentNode = node;
unsavedComputeDialog.open();
}
else {
try {
_reconstruction.execute(node)
}
catch (error) {
const data = ErrorHandler.analyseError(error)
if(data.context === "COMPUTATION")
computeSubmitErrorDialog.openError(data.type, data.msg, node)
}
}
}
function submit(node) {
try {
_reconstruction.submit(node)
}
catch (error) {
const data = ErrorHandler.analyseError(error)
if(data.context === "SUBMITTING")
computeSubmitErrorDialog.openError(data.type, data.msg, node)
}
}
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()
}
default: close()
}
}
onRejected: close()
}
MessageDialog {
id: unsavedComputeDialog
property var currentNode: 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(currentNode, true) }
onAccepted: saveAsAction.trigger()
}
}
FileDialog {
id: openFileDialog
title: "Open File"
nameFilters: ["Meshroom Graphs (*.mg)"]
onAccepted: {
if(_reconstruction.loadUrl(fileUrl))
{
MeshroomApp.addRecentProjectFile(fileUrl.toString())
}
}
}
FileDialog {
id: importImagesDialog
title: "Import Images"
selectExisting: true
selectMultiple: true
nameFilters: []
onAccepted: {
_reconstruction.importImagesUrls(importImagesDialog.fileUrls)
imagesFolder = Filepath.dirname(importImagesDialog.fileUrls[0])
MeshroomApp.addRecentImportedImagesFolder(imagesFolder)
}
}
FileDialog {
id: importProjectDialog
title: "Import Project"
selectMultiple: false
nameFilters: ["Meshroom Graphs (*.mg)"]
onAccepted: {
graphEditor.uigraph.importProject(importProjectDialog.fileUrl)
}
}
AboutDialog {
id: aboutDialog
}
// Check if document has been saved
function ensureSaved(callback)
{
var saved = _reconstruction.undoStack.clean
// If current document is modified, open "unsaved dialog"
if(!saved)
{
unsavedDialog.prompt(callback)
}
else // otherwise, directly call the callback
{
callback()
}
return saved
}
MessageDialog {
id: computingAtExitDialog
title: "Operation in progress"
modal: true
canCopy: false
Label {
text: "Please stop any local computation before exiting Meshroom"
}
}
// Check and return whether no local computation is in progress
function ensureNotComputing()
{
if(_reconstruction.computingLocally)
{
// Open a warning dialog to ask for computation to be stopped
computingAtExitDialog.open()
return false
}
return true
}
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
}
}
DialogsFactory {
id: dialogsFactory
}
CompatibilityManager {
id: compatibilityManager
uigraph: _reconstruction
}
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: 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()
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
}
}
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 {
shortcut: "Ctrl+Shift+P"
onTriggered: _PaletteManager.togglePalette()
}
// Utility functions for elements in the menubar
function initFileDialogFolder(dialog, importImages = false) {
let folder = "";
if (imagesFolder.toString() === "" && workspaceView.imageGallery.galleryGrid.itemAtIndex(0) !== null) {
imagesFolder = Filepath.stringToUrl(Filepath.dirname(workspaceView.imageGallery.galleryGrid.itemAtIndex(0).source));
}
if (_reconstruction.graph && _reconstruction.graph.filepath) {
folder = Filepath.stringToUrl(Filepath.dirname(_reconstruction.graph.filepath));
} else {
var projects = MeshroomApp.recentProjectFiles;
if (projects.length > 0 && Filepath.exists(projects[0])) {
folder = Filepath.stringToUrl(Filepath.dirname(projects[0]));
}
}
if (importImages && imagesFolder.toString() !== "" && Filepath.exists(imagesFolder)) {
folder = imagesFolder;
}
dialog.folder = folder;
}
header: MenuBar {
palette.window: Qt.darker(activePalette.window, 1.15)
Menu {
title: "File"
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.text: modelData["path"]
ToolTip.visible: hovered
ToolTip.delay: 200
}
}
}
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))
{
MeshroomApp.addRecentProjectFile(modelData);
}
else
{
MeshroomApp.removeRecentProjectFile(modelData);
}
})
text: fileTextMetrics.elidedText
TextMetrics {
id: fileTextMetrics
text: modelData
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) {
_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();
}
}
Action {
id: clearImagesAction
text: "Clear Images"
onTriggered: {
//Loop through all the camera inits
for(var i = 0 ; i < _reconstruction.cameraInits.count; i++){
var cameraInit = _reconstruction.cameraInits.at(i)
//Delete all viewpoints
var viewpoints = cameraInit.attribute('viewpoints')
for(var y = viewpoints.value.count - 1 ; y >= 0 ; y--){
_reconstruction.removeAttribute(viewpoints.value.at(y))
}
//Delete all intrinsics
var intrinsics = cameraInit.attribute('intrinsics')
for(var z = intrinsics.value.count - 1 ; z >= 0 ; z--){
_reconstruction.removeAttribute(intrinsics.value.at(z))
}
}
}
}
MenuSeparator { }
Menu {
id: advancedMenu
title: "Advanced"
Action {
id: saveAsTemplateAction
text: "Save As Template..."
shortcut: Shortcut {
sequence: "Ctrl+Shift+T"
context: Qt.ApplicationShortcut
onActivated: saveAsTemplateAction.triggered()
}
onTriggered: {
initFileDialogFolder(saveTemplateDialog);
saveTemplateDialog.open();
}
}
Action {
id: importProjectAction
text: "Import Project"
shortcut: Shortcut {
sequence: "Ctrl+Shift+I"
context: Qt.ApplicationShortcut
onActivated: importProjectAction.triggered()
}
onTriggered: {
initFileDialogFolder(importProjectDialog);
importProjectDialog.open();
}
}
}
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: 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: "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"
}
}
}
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
}
onGraphChanged: {
// open CompatibilityManager after file loading if any issue is detected
if(compatibilityManager.issueCount)
compatibilityManager.open()
// trigger fit to visualize all nodes
graphEditor.fit()
}
onInfo: createDialog(dialogsFactory.info, arguments[0])
onWarning: createDialog(dialogsFactory.warning, arguments[0])
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
Layout.topMargin: 2
implicitHeight: Math.round(parent.height * 0.7)
spacing: 4
RowLayout {
Layout.rightMargin: 4
Layout.leftMargin: 4
Layout.fillHeight: false
Item { Layout.fillWidth: true }
Row {
// disable controls if graph is executed externally
Layout.alignment: Qt.AlignHCenter
Button {
property color buttonColor: Qt.darker("#4CAF50", 1.8)
text: "Start"
palette.button: enabled ? buttonColor : disabledPalette.button
palette.window: enabled ? buttonColor : disabledPalette.window
palette.buttonText: enabled ? "white" : disabledPalette.buttonText
onClicked: computeManager.compute(null)
}
Button {
text: "Stop"
enabled: _reconstruction ? _reconstruction.computingLocally : false
onClicked: _reconstruction.stopExecution()
}
Item { width: 20; height: 1 }
Button {
visible: _reconstruction ? _reconstruction.canSubmit : false
text: "Submit"
onClicked: computeManager.submit(null)
}
}
Item { Layout.fillWidth: true; Layout.fillHeight: true }
// CompatibilityManager indicator
ToolButton {
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
}
}
// "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
}
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
for (var i = 0; i < node.attributes.count; i++) {
var attr = node.attributes.at(i)
if(attr.isOutput && attr.desc.semantic != "image" && workspaceView.viewIn3D(attr, mouse))
break;
}
}
function viewIn3D(attribute, mouse) {
if(!panel3dViewer)
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: settings_UILayout.showGraphEditor
TabPanel {
id: graphEditorPanel
Layout.fillWidth: true
padding: 4
tabs: ["Graph Editor", "Task Manager"]
headerBar: RowLayout {
MaterialToolButton {
text: MaterialIcons.refresh
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 {
id: filePollerRefreshStatus
text: {
if (_reconstruction.filePollerRefresh === 0)
return MaterialIcons.published_with_changes
else if (_reconstruction.filePollerRefresh === 2)
return MaterialIcons.sync
else
return MaterialIcons.sync_disabled
}
font.pointSize: 11
padding: 2
enabled: true
ToolTip {
id: filePollerTooltip
property string title: "Auto-Refresh Nodes Status For External Changes. "
property string description: "Check if the status of a node is changed by another instance on the same network, " +
"such as when computing in render farm."
text: {
var status = ""
if (_reconstruction.filePollerRefresh === 0)
status = "Enabled"
else if (_reconstruction.filePollerRefresh === 2)
status = "Minimal"
else
status = "Disabled"
return title + "(Current: " + status + ")\n\n" + description
}
visible: filePollerRefreshStatus.hovered
contentWidth: 420
}
onClicked: {
refreshFilesMenu.open()
}
Menu {
id: refreshFilesMenu
width: 210
y: parent.height
x: -width + parent.width
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
filePollerRefreshStatus.text = MaterialIcons.published_with_changes
filePollerTooltip.text = filePollerTooltip.title + "(Current: Enabled)\n\n" + filePollerTooltip.description
}
}
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
filePollerRefreshStatus.text = MaterialIcons.sync_disabled
filePollerTooltip.text = filePollerTooltip.title + "(Current: Disabled)\n\n" + filePollerTooltip.description
}
}
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
filePollerRefreshStatus.text = MaterialIcons.sync
filePollerTooltip.text = filePollerTooltip.title + "(Current: Minimal)\n\n" + filePollerTooltip.description
}
}
}
}
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()
}
}
}
}
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(node)
}
onSubmitRequest: {
_reconstruction.forceNodesStatusUpdate();
computeManager.submit(node)
}
}
TaskManager {
id: taskManager
visible: graphEditorPanel.currentTab === 1
uigraph: _reconstruction
taskManager: _reconstruction ? _reconstruction.taskManager : null
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;
}
}
}
}
background: MouseArea {
onPressed: {
forceActiveFocus();
}
}
}