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 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 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]["path"])) { folder = Filepath.stringToUrl(Filepath.dirname(projects[0]["path"])) } } if (importImages && imagesFolder.toString() !== "" && Filepath.exists(imagesFolder)) { folder = imagesFolder } dialog.folder = folder } 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: openFileDialog options: Platform.FileDialog.DontUseNativeDialog title: "Open File" nameFilters: ["Meshroom Graphs (*.mg)"] onAccepted: { if (_reconstruction.loadUrl(currentFile)) { MeshroomApp.addRecentProjectFile(currentFile.toString()) } } } 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() } 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: 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.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["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) ? "Start the computation" : "Stop the computation" 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" 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.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(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 } } } } }