diff --git a/meshroom/core/graph.py b/meshroom/core/graph.py index 40528dfb..cad9e4d8 100644 --- a/meshroom/core/graph.py +++ b/meshroom/core/graph.py @@ -235,6 +235,7 @@ class Graph(BaseObject): node.alive = False self._importedNodes.clear() self._nodes.clear() + self._unsetFilepath() @property def fileFeatures(self): diff --git a/meshroom/ui/app.py b/meshroom/ui/app.py index 42b3d542..5db34b33 100644 --- a/meshroom/ui/app.py +++ b/meshroom/ui/app.py @@ -2,6 +2,7 @@ import logging import os import re import argparse +import json from PySide2 import QtCore from PySide2.QtCore import Qt, QUrl, QJsonValue, qInstallMessageHandler, QtMsgType, QSettings @@ -96,6 +97,8 @@ class MeshroomApp(QApplication): args = parser.parse_args(args[1:]) + self._projectOpened = True if getattr(args, "import", None) or args.project or args.importRecursive or args.save or args.pipeline else False + logStringToPython = { 'fatal': logging.FATAL, 'error': logging.ERROR, @@ -181,7 +184,7 @@ class MeshroomApp(QApplication): args.project = os.path.abspath(args.project) self._activeProject.load(args.project) self.addRecentProjectFile(args.project) - else: + elif getattr(args, "import", None) or args.importRecursive or args.save or args.pipeline: self._activeProject.new() # import is a python keyword, so we have to access the attribute by a string @@ -235,6 +238,18 @@ class MeshroomApp(QApplication): settings.setArrayIndex(i) p = settings.value("filepath") if p: + # get the first image path from the project + file = open(p) + file = json.load(file) + # find the first camerainit node + file = file["graph"] + thumbnail = "" + for node in file: + if file[node]["nodeType"] == "CameraInit": + if len(file[node]["inputs"]["viewpoints"]) > 0: + thumbnail = ThumbnailCache.thumbnailPath(file[node]["inputs"]["viewpoints"][0]["path"]) + break + p = {"path": p, "thumbnail": thumbnail} projects.append(p) settings.endArray() return projects @@ -254,6 +269,7 @@ class MeshroomApp(QApplication): projectFileNorm = QUrl.fromLocalFile(projectFile).toLocalFile() projects = self._recentProjectFiles() + projects = [p["path"] for p in projects] # remove duplicates while preserving order from collections import OrderedDict @@ -471,6 +487,11 @@ class MeshroomApp(QApplication): activeProjectChanged = Signal() activeProject = Property(Variant, lambda self: self._activeProject, notify=activeProjectChanged) + + # As activeProject is a Reconstruction, we can't use it directly in QML to know if a project is opened or not + # So we expose a boolean property to know if a project is opened or not + # TODO: find a way to have empty activeProject property + projectOpened = Property(bool, lambda self: self._projectOpened, constant=True) changelogModel = Property("QVariantList", _changelogModel, constant=True) licensesModel = Property("QVariantList", _licensesModel, constant=True) pipelineTemplateFilesChanged = Signal() diff --git a/meshroom/ui/img/MIKROS_TG.png b/meshroom/ui/img/MIKROS_TG.png new file mode 100644 index 00000000..32921ba9 Binary files /dev/null and b/meshroom/ui/img/MIKROS_TG.png differ diff --git a/meshroom/ui/img/MILL_TG.png b/meshroom/ui/img/MILL_TG.png new file mode 100644 index 00000000..00acb0e3 Binary files /dev/null and b/meshroom/ui/img/MILL_TG.png differ diff --git a/meshroom/ui/img/MPC_TG.png b/meshroom/ui/img/MPC_TG.png new file mode 100644 index 00000000..5ad0d120 Binary files /dev/null and b/meshroom/ui/img/MPC_TG.png differ diff --git a/meshroom/ui/img/Technicolor-Group_RGB_MIKROS_P-TG_Col-Rev.png b/meshroom/ui/img/Technicolor-Group_RGB_MIKROS_P-TG_Col-Rev.png new file mode 100644 index 00000000..cae38fa7 Binary files /dev/null and b/meshroom/ui/img/Technicolor-Group_RGB_MIKROS_P-TG_Col-Rev.png differ diff --git a/meshroom/ui/img/Technicolor-Group_RGB_MILL_P-TG_Col-Rev.png b/meshroom/ui/img/Technicolor-Group_RGB_MILL_P-TG_Col-Rev.png new file mode 100644 index 00000000..c2930a1d Binary files /dev/null and b/meshroom/ui/img/Technicolor-Group_RGB_MILL_P-TG_Col-Rev.png differ diff --git a/meshroom/ui/img/Technicolor-Group_RGB_MPC_P-TG_Col-Rev.png b/meshroom/ui/img/Technicolor-Group_RGB_MPC_P-TG_Col-Rev.png new file mode 100644 index 00000000..afacbc8e Binary files /dev/null and b/meshroom/ui/img/Technicolor-Group_RGB_MPC_P-TG_Col-Rev.png differ diff --git a/meshroom/ui/img/Technicolor-Group_RGB_TechnicolorGames_P-TG_Col-Rev.png b/meshroom/ui/img/Technicolor-Group_RGB_TechnicolorGames_P-TG_Col-Rev.png new file mode 100644 index 00000000..67ea83d3 Binary files /dev/null and b/meshroom/ui/img/Technicolor-Group_RGB_TechnicolorGames_P-TG_Col-Rev.png differ diff --git a/meshroom/ui/img/TechnicolorGames_TG.png b/meshroom/ui/img/TechnicolorGames_TG.png new file mode 100644 index 00000000..0b347a8b Binary files /dev/null and b/meshroom/ui/img/TechnicolorGames_TG.png differ diff --git a/meshroom/ui/img/meshroom-anim-once.gif b/meshroom/ui/img/meshroom-anim-once.gif new file mode 100644 index 00000000..37fb870e Binary files /dev/null and b/meshroom/ui/img/meshroom-anim-once.gif differ diff --git a/meshroom/ui/img/technicolor-group_rgb_primary_col-rev.png b/meshroom/ui/img/technicolor-group_rgb_primary_col-rev.png new file mode 100644 index 00000000..f0c21314 Binary files /dev/null and b/meshroom/ui/img/technicolor-group_rgb_primary_col-rev.png differ diff --git a/meshroom/ui/qml/Application.qml b/meshroom/ui/qml/Application.qml new file mode 100644 index 00000000..0fd9e41c --- /dev/null +++ b/meshroom/ui/qml/Application.qml @@ -0,0 +1,774 @@ +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 + 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 + } + + // 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 + } + + 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" + 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: settings_UILayout.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 + } + } + } + } +} \ No newline at end of file diff --git a/meshroom/ui/qml/Controls/TabPanel.qml b/meshroom/ui/qml/Controls/TabPanel.qml index a162cd5c..b33d0857 100644 --- a/meshroom/ui/qml/Controls/TabPanel.qml +++ b/meshroom/ui/qml/Controls/TabPanel.qml @@ -41,7 +41,7 @@ Page { text: modelData y: mainTabBar.padding padding: 4 - width: 150 + width: text.length * font.pointSize background: Rectangle { color: index === mainTabBar.currentIndex ? root.palette.window : Qt.darker(root.palette.window, 1.30) } diff --git a/meshroom/ui/qml/Homepage.qml b/meshroom/ui/qml/Homepage.qml new file mode 100644 index 00000000..32b65172 --- /dev/null +++ b/meshroom/ui/qml/Homepage.qml @@ -0,0 +1,328 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Controls 1.4 as Controls1 // For SplitView +import QtQuick.Layouts 1.11 +import Utils 1.0 +import MaterialIcons 2.2 +import Controls 1.0 + +Page { + id: root + + onVisibleChanged: { + logo.playing = false + if (visible) { + logo.playing = true + } + } + + Controls1.SplitView { + id: splitView + anchors.fill: parent + + + ColumnLayout { + id: leftColumn + height: parent.height + + Layout.minimumWidth: 200 + Layout.maximumWidth: 400 + + AnimatedImage { + id: logo + property var ratio: sourceSize.width / sourceSize.height + + Layout.fillWidth: true + Layout.preferredHeight: width / ratio + + source: "../img/meshroom-anim-once.gif" + } + + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + + MaterialToolLabelButton { + id: manualButton + Layout.topMargin: 20 + iconText: MaterialIcons.open_in_new + label: "Manual" + font.pointSize: 16 + iconSize: 24 + flat: true + leftPadding: 20 + rightPadding: leftColumn.width - labelItem.width - iconItem.width - leftPadding + + onClicked: Qt.openUrlExternally("https://meshroom-manual.readthedocs.io/en/latest") + } + + MaterialToolLabelButton { + id: releaseNotesButton + iconText: MaterialIcons.open_in_new + label: "Release Notes" + font.pointSize: 16 + iconSize: 24 + flat: true + leftPadding: 20 + rightPadding: leftColumn.width - labelItem.width - iconItem.width - leftPadding + + onClicked: Qt.openUrlExternally("https://github.com/alicevision/Meshroom/blob/develop/CHANGES.md") + } + + MaterialToolLabelButton { + id: websiteButton + iconText: MaterialIcons.open_in_new + label: "Website" + font.pointSize: 16 + iconSize: 24 + flat: true + leftPadding: 20 + rightPadding: leftColumn.width - labelItem.width - iconItem.width - leftPadding + + onClicked: Qt.openUrlExternally("https://alicevision.org/") + } + } + + ColumnLayout { + id: sponsors + Layout.fillWidth: true + + Rectangle { + // find better alternative + color: "transparent" + Layout.fillWidth: true + Layout.fillHeight: true + } + + Label { + Layout.alignment: Qt.AlignHCenter + text: "Sponsors" + font.pointSize: 16 + } + + Image { + Layout.alignment: Qt.AlignHCenter + property var ratio: sourceSize.width / sourceSize.height + + Layout.preferredWidth: leftColumn.width * 0.6 + Layout.preferredHeight: width / ratio + source: "../img/technicolor-group_rgb_primary_col-rev.png" + smooth: true + mipmap: true + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: Qt.openUrlExternally("https://www.technicolor.com/") + } + } + + RowLayout { + id: brandsRow + + Layout.leftMargin: leftColumn.width * 0.05 + Layout.rightMargin: leftColumn.width * 0.05 + + Image { + property var ratio: sourceSize.width / sourceSize.height + + Layout.fillWidth: true + Layout.preferredHeight: width / ratio + source: "../img/MPC_TG.png" + smooth: true + mipmap: true + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: Qt.openUrlExternally("https://www.mpcvfx.com/") + } + } + + Image { + property var ratio: sourceSize.width / sourceSize.height + + Layout.fillWidth: true + Layout.preferredHeight: width / ratio + source: "../img/MILL_TG.png" + smooth: true + mipmap: true + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: Qt.openUrlExternally("https://www.themill.com/") + } + } + + Image { + property var ratio: sourceSize.width / sourceSize.height + + Layout.fillWidth: true + Layout.preferredHeight: width / ratio + source: "../img/MIKROS_TG.png" + smooth: true + mipmap: true + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: Qt.openUrlExternally("https://www.mikrosanimation.com/") + } + } + + Image { + property var ratio: sourceSize.width / sourceSize.height + + Layout.fillWidth: true + Layout.preferredHeight: width / ratio + source: "../img/TechnicolorGames_TG.png" + smooth: true + mipmap: true + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: Qt.openUrlExternally("https://www.technicolorgames.com/") + } + } + } + + MaterialToolLabelButton { + Layout.topMargin: 20 + iconText: MaterialIcons.favorite + label: "Support AliceVision" + font.pointSize: 16 + iconSize: 24 + flat: true + + leftPadding: (leftColumn.width - labelItem.width - iconItem.width - 5) / 2 + rightPadding: leftPadding + + onClicked: Qt.openUrlExternally("https://alicevision.org/association/#donate") + } + } + } + + ColumnLayout { + id: rightColumn + + TabPanel { + id: tabPanel + tabs: ["Pipelines", "Recent Projects"] + + font.pointSize: 16 + + Layout.fillWidth: true + Layout.fillHeight: true + + ListView { + id: pipelinesListView + visible: tabPanel.currentTab === 0 + + anchors.fill: parent + + model: [{ "name": "New Empty Project", "path": null }].concat(MeshroomApp.pipelineTemplateFiles) + + delegate: Button { + id: pipelineDelegate + width: pipelinesListView.width + height: pipelineContent.implicitHeight + + Column { + id: pipelineContent + topPadding: 8 + bottomPadding: 10 + leftPadding: 30 + + Label { + id: pipeline + text: modelData["name"] + font.pointSize: 10 + } + } + + Connections { + target: pipelineDelegate + function onClicked() { + // Open pipeline + mainStack.push("Application.qml") + console.log("Open pipeline", modelData["path"]) + _reconstruction.new(modelData["path"]) + } + } + } + } + + GridView { + id: gridView + visible: tabPanel.currentTab === 1 + anchors.fill: parent + anchors.topMargin: cellHeight * 0.1 + + cellWidth: 200 + cellHeight: cellWidth + + model: MeshroomApp.recentProjectFiles + + delegate: Column { + id: projectContent + + width: gridView.cellWidth + height: gridView.cellHeight + Button { + id: projectDelegate + height: gridView.cellHeight * 0.8 + width: gridView.cellWidth * 0.8 + x: gridView.cellWidth * 0.1 + + ToolTip.visible: hovered + ToolTip.text: modelData["path"] + + text: modelData["thumbnail"] ? "" : MaterialIcons.description + + Image { + visible: modelData["thumbnail"] + source: modelData["thumbnail"] + + fillMode: Image.PreserveAspectCrop + + width: projectDelegate.width + height: projectDelegate.height + } + + Connections { + target: projectDelegate + function onClicked() { + // Open project + mainStack.push("Application.qml") + if (_reconstruction.loadUrl(modelData["path"])) { + MeshroomApp.addRecentProjectFile(modelData["path"]) + } else { + MeshroomApp.removeRecentProjectFile(modelData["path"]) + } + } + } + } + Label { + id: project + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + width: projectDelegate.width + wrapMode: Text.WrapAnywhere + text: { + if (Filepath.removeExtension(Filepath.basename(modelData["path"])).length > 40) { + var length = Filepath.basename(modelData["path"]).length + return Filepath.basename(modelData["path"]).substring(0, 30) + "…" + Filepath.basename(modelData["path"]).substring(length - 10, length) + } else { + return Filepath.basename(modelData["path"]) + } + } + font.pointSize: 10 + } + } + } + } + } + } +} \ No newline at end of file diff --git a/meshroom/ui/qml/MaterialIcons/MaterialToolLabelButton.qml b/meshroom/ui/qml/MaterialIcons/MaterialToolLabelButton.qml index ebf1b58f..c1eb5305 100644 --- a/meshroom/ui/qml/MaterialIcons/MaterialToolLabelButton.qml +++ b/meshroom/ui/qml/MaterialIcons/MaterialToolLabelButton.qml @@ -15,9 +15,14 @@ ToolButton { padding: 0 ToolTip.visible: ToolTip.text && hovered ToolTip.delay: 100 - width: childrenRect.width height: childrenRect.height + + property alias labelItem: labelItem + property alias iconItem: icon + property alias rowIconLabel: rowIconLabel + contentItem: RowLayout { + id: rowIconLabel Layout.margins: 0 Label { id: icon diff --git a/meshroom/ui/qml/main.qml b/meshroom/ui/qml/main.qml index 1194d615..3f1d8861 100644 --- a/meshroom/ui/qml/main.qml +++ b/meshroom/ui/qml/main.qml @@ -90,15 +90,6 @@ ApplicationWindow { 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 @@ -611,18 +602,7 @@ ApplicationWindow { } - 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(); - }) - } - } + // TODO: uncomment for Qt6 to re-enable the alternative palette (the alternative palette and the disabled items currently cannot both be supported) /* Action { @@ -631,674 +611,24 @@ ApplicationWindow { 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) { - // 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: "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 - } - - 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 { + StackView { + id: mainStack 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 - 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 - } + Component.onCompleted: { + if (MeshroomApp.projectOpened) { + mainStack.push("Application.qml") + } else { + mainStack.push("Homepage.qml") } } - 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", "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 - } - } - } + pushExit: Transition {} + pushEnter: Transition {} + popExit: Transition {} + popEnter: Transition {} + replaceEnter: Transition {} + replaceExit: Transition {} } background: MouseArea { diff --git a/meshroom/ui/reconstruction.py b/meshroom/ui/reconstruction.py index 63254fb9..dd661f32 100755 --- a/meshroom/ui/reconstruction.py +++ b/meshroom/ui/reconstruction.py @@ -477,6 +477,7 @@ class Reconstruction(UIGraph): self._workerThreads.terminate() self._workerThreads.join() + @Slot() def clear(self): self.clearActiveNodes() super(Reconstruction, self).clear() @@ -879,6 +880,9 @@ class Reconstruction(UIGraph): """ logging.debug("importImagesFromFolder: " + str(path)) filesByType = multiview.findFilesByTypeInFolder(path, recursive) + if not self.cameraInit: + # Create a CameraInit node if none exists + self.cameraInit = self.addNewNode("CameraInit") if filesByType.images: self._workerThreads.apply_async(func=self.importImagesSync, args=(filesByType.images, self.cameraInit,)) @@ -1260,4 +1264,4 @@ class Reconstruction(UIGraph): # Signals to propagate high-level messages error = Signal(Message) warning = Signal(Message) - info = Signal(Message) + info = Signal(Message) \ No newline at end of file