mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-06-06 12:51:57 +02:00
* API Instead of having a single `load` function that exposes in its API some elements only applicable to initializing a graph from a templates, split it into 2 distinct functions: `load` and `initFromTemplate`. Apply those changes to users of the API (UI, CLI), and simplify Graph wrapper classes to better align with those concepts. * Deserialization Reduce the cognitive complexity of the deserizalization process by splitting it into more atomic functions, while maintaining the current behavior.
1335 lines
No EOL
49 KiB
QML
1335 lines
No EOL
49 KiB
QML
import QtCore
|
|
|
|
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
import QtQml.Models
|
|
|
|
import Qt.labs.platform 1.0 as Platform
|
|
import QtQuick.Dialogs
|
|
|
|
import GraphEditor 1.0
|
|
import MaterialIcons 2.2
|
|
import Utils 1.0
|
|
import Controls 1.0
|
|
|
|
Page {
|
|
id: root
|
|
|
|
property alias computingAtExitDialog: computingAtExitDialog
|
|
property alias unsavedDialog: unsavedDialog
|
|
property alias workspaceView: workspaceView
|
|
|
|
readonly property var scenefile: _reconstruction ? _reconstruction.graph.filepath : "";
|
|
|
|
onScenefileChanged: {
|
|
// Check if we're not currently saving and emit the currentProjectChanged signal
|
|
if (! _reconstruction.graph.isSaving) {
|
|
// Refresh the NodeEditor
|
|
nodeEditor.refresh();
|
|
}
|
|
}
|
|
|
|
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
|
|
property alias showImageGallery: imageGalleryVisibilityCB.checked
|
|
}
|
|
|
|
|
|
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 ""
|
|
}
|
|
|
|
Component {
|
|
id: invalidFilepathDialog
|
|
|
|
MessageDialog {
|
|
title: "Invalid Filepath"
|
|
|
|
required property string filepath
|
|
|
|
preset: "Warning"
|
|
text: "The provided filepath is not valid."
|
|
detailedText: "Filepath: " + filepath
|
|
helperText: "Please provide a valid filepath to save the file."
|
|
|
|
standardButtons: Dialog.Ok
|
|
onClosed: destroy()
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: permissionsDialog
|
|
|
|
MessageDialog {
|
|
title: "Permission Denied"
|
|
|
|
required property string filepath
|
|
|
|
preset: "Warning"
|
|
text: "The location does not exist or you do not have necessary permissions to save to the provided filepath."
|
|
detailedText: "Filepath: " + filepath
|
|
helperText: "Please check the location or permissions and try again or choose a different location."
|
|
|
|
standardButtons: Dialog.Ok
|
|
onClosed: destroy()
|
|
}
|
|
}
|
|
|
|
function validateFilepathForSave(filepath: string, sourceSaveDialog: Dialog): bool {
|
|
/**
|
|
* Return true if `filepath` is valid for saving a file to disk.
|
|
* Otherwise, show a warning dialog and returns false.
|
|
* Closing the warning dialog reopens the specified `sourceSaveDialog`, to allow the user to try again.
|
|
*/
|
|
const emptyFilename = Filepath.basename(filepath).trim() === ".mg";
|
|
|
|
// Provided filename is not valid
|
|
if (emptyFilename) {
|
|
// Instantiate the Warning Dialog with the provided filepath
|
|
const warningDialog = invalidFilepathDialog.createObject(root, {"filepath": Filepath.urlToString(filepath)});
|
|
|
|
// And open the dialog
|
|
warningDialog.closed.connect(sourceSaveDialog.open);
|
|
warningDialog.open();
|
|
|
|
return false;
|
|
}
|
|
|
|
// Check if the user has access to the directory where the file is to be saved
|
|
const hasPermission = Filepath.accessible(Filepath.dirname(filepath));
|
|
|
|
// Either the directory does not exist or is inaccessible for the user
|
|
if (!hasPermission) {
|
|
// Intantiate the permissions dialog with the provided filepath
|
|
const warningDialog = permissionsDialog.createObject(root, {"filepath": Filepath.urlToString(filepath)});
|
|
|
|
// Connect and show the dialog
|
|
warningDialog.closed.connect(sourceSaveDialog.open);
|
|
warningDialog.open();
|
|
|
|
return false;
|
|
}
|
|
|
|
// Everything is valid
|
|
return true;
|
|
}
|
|
|
|
// 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: {
|
|
if (!validateFilepathForSave(currentFile, saveFileDialog))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Only save a valid file
|
|
_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: {
|
|
if (!validateFilepathForSave(currentFile, saveTemplateDialog))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Only save a valid template
|
|
_reconstruction.saveAsTemplate(currentFile)
|
|
closed(Platform.Dialog.Accepted)
|
|
MeshroomApp.reloadTemplateList()
|
|
}
|
|
onRejected: closed(Platform.Dialog.Rejected)
|
|
}
|
|
|
|
Platform.FileDialog {
|
|
id: loadTemplateDialog
|
|
options: Platform.FileDialog.DontUseNativeDialog
|
|
title: "Load Template"
|
|
nameFilters: ["Meshroom Graphs (*.mg)"]
|
|
onAccepted: {
|
|
// Open the template as a regular file
|
|
if (_reconstruction.load(currentFile)) {
|
|
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.load(_reconstruction.graph.filepath)
|
|
fileModifiedDialog.close()
|
|
}
|
|
}
|
|
}
|
|
|
|
onAccepted: _reconstruction.save()
|
|
onDiscarded: close()
|
|
}
|
|
}
|
|
|
|
// Message dialogs
|
|
MessageDialog {
|
|
id: unsavedDialog
|
|
|
|
property var _callback: undefined
|
|
|
|
title: (_reconstruction ? Filepath.basename(_reconstruction.graph.filepath) : "") || "Unsaved Project"
|
|
preset: "Info"
|
|
canCopy: false
|
|
text: _reconstruction && _reconstruction.graph.filepath ? "Current project has unsaved modifications."
|
|
: "Current project has not been saved."
|
|
helperText: _reconstruction && _reconstruction.graph.filepath ? "Would you like to save those changes?"
|
|
: "Would you like to save this project?"
|
|
|
|
standardButtons: Dialog.Save | Dialog.Cancel | Dialog.Discard
|
|
|
|
onDiscarded: {
|
|
close() // BUG ? discard does not close window
|
|
fireCallback()
|
|
}
|
|
|
|
onRejected: {
|
|
_window.isClosing = false
|
|
}
|
|
|
|
onAccepted: {
|
|
// Save current file
|
|
if (saveAction.enabled && _reconstruction.graph.filepath) {
|
|
saveAction.trigger()
|
|
fireCallback()
|
|
}
|
|
// Open "Save As" dialog
|
|
else {
|
|
saveFileDialog.open()
|
|
function _callbackWrapper(rc) {
|
|
if (rc === Platform.Dialog.Accepted)
|
|
fireCallback()
|
|
|
|
saveFileDialog.closed.disconnect(_callbackWrapper)
|
|
}
|
|
saveFileDialog.closed.connect(_callbackWrapper)
|
|
}
|
|
}
|
|
|
|
function fireCallback()
|
|
{
|
|
// Call the callback and reset it
|
|
if (_callback)
|
|
_callback()
|
|
_callback = undefined
|
|
}
|
|
|
|
/// Open the unsaved dialog warning with an optional
|
|
/// callback to fire when the dialog is accepted/discarded
|
|
function prompt(callback)
|
|
{
|
|
_callback = callback
|
|
open()
|
|
}
|
|
}
|
|
|
|
MessageDialog {
|
|
id: computingAtExitDialog
|
|
title: "Operation in progress"
|
|
modal: true
|
|
canCopy: false
|
|
Label {
|
|
text: "Please stop any local computation before exiting Meshroom"
|
|
}
|
|
}
|
|
|
|
MessageDialog {
|
|
// Popup displayed while the application
|
|
// is busy building intrinsics while importing images
|
|
id: buildingIntrinsicsDialog
|
|
modal: true
|
|
visible: _reconstruction ? _reconstruction.buildingIntrinsics : false
|
|
closePolicy: Popup.NoAutoClose
|
|
title: "Initializing Cameras"
|
|
icon.text: MaterialIcons.camera
|
|
icon.font.pointSize: 10
|
|
canCopy: false
|
|
standardButtons: Dialog.NoButton
|
|
|
|
detailedText: "Extracting images metadata and creating Camera intrinsics..."
|
|
ProgressBar {
|
|
indeterminate: true
|
|
Layout.fillWidth: true
|
|
}
|
|
}
|
|
|
|
AboutDialog {
|
|
id: aboutDialog
|
|
}
|
|
|
|
DialogsFactory {
|
|
id: dialogsFactory
|
|
}
|
|
|
|
CompatibilityManager {
|
|
id: compatibilityManager
|
|
uigraph: _reconstruction
|
|
}
|
|
|
|
|
|
// Actions
|
|
Action {
|
|
id: removeAllImagesAction
|
|
property string tooltip: "Remove all the images from the current CameraInit group"
|
|
text: "Remove All Images"
|
|
onTriggered: {
|
|
_reconstruction.removeAllImages()
|
|
_reconstruction.selectedViewId = "-1"
|
|
}
|
|
}
|
|
|
|
Action {
|
|
id: removeImagesFromAllGroupsAction
|
|
property string tooltip: "Remove all the images from all the CameraInit groups"
|
|
text: "Remove Images From All CameraInit Nodes"
|
|
onTriggered: {
|
|
_reconstruction.removeImagesFromAllGroups()
|
|
_reconstruction.selectedViewId = "-1"
|
|
}
|
|
}
|
|
|
|
Action {
|
|
id: undoAction
|
|
|
|
property string tooltip: 'Undo "' + (_reconstruction ? _reconstruction.undoStack.undoText : "Unknown") + '"'
|
|
text: "Undo"
|
|
shortcut: "Ctrl+Z"
|
|
enabled: _reconstruction ? _reconstruction.undoStack.canUndo && _reconstruction.undoStack.isUndoableIndex : false
|
|
onTriggered: _reconstruction.undoStack.undo()
|
|
}
|
|
|
|
Action {
|
|
id: redoAction
|
|
|
|
property string tooltip: 'Redo "' + (_reconstruction ? _reconstruction.undoStack.redoText : "Unknown") + '"'
|
|
text: "Redo"
|
|
shortcut: "Ctrl+Shift+Z"
|
|
enabled: _reconstruction ? _reconstruction.undoStack.canRedo && !_reconstruction.undoStack.lockedRedo : false
|
|
onTriggered: _reconstruction.undoStack.redo()
|
|
}
|
|
|
|
Action {
|
|
id: cutAction
|
|
|
|
property string tooltip: "Cut Selected Node(s)"
|
|
text: "Cut Node(s)"
|
|
enabled: _reconstruction ? _reconstruction.nodeSelection.hasSelection : false
|
|
onTriggered: {
|
|
graphEditor.copyNodes()
|
|
graphEditor.uigraph.removeSelectedNodes()
|
|
}
|
|
}
|
|
|
|
Action {
|
|
id: copyAction
|
|
|
|
property string tooltip: "Copy Selected Node(s)"
|
|
text: "Copy Node(s)"
|
|
enabled: _reconstruction ? _reconstruction.nodeSelection.hasSelection : false
|
|
onTriggered: graphEditor.copyNodes()
|
|
}
|
|
|
|
Action {
|
|
id: pasteAction
|
|
|
|
property string tooltip: "Paste the clipboard content to the project if it contains valid nodes"
|
|
text: "Paste Node(s)"
|
|
onTriggered: graphEditor.pasteNodes()
|
|
}
|
|
|
|
Action {
|
|
id: loadTemplateAction
|
|
|
|
property string tooltip: "Load a template like a regular project file (any \"Publish\" node will be displayed)"
|
|
text: "Load Template"
|
|
onTriggered: {
|
|
ensureSaved(function() {
|
|
initFileDialogFolder(loadTemplateDialog)
|
|
loadTemplateDialog.open()
|
|
})
|
|
}
|
|
}
|
|
|
|
header: RowLayout {
|
|
spacing: 0
|
|
MaterialToolButton {
|
|
id: homeButton
|
|
text: MaterialIcons.home
|
|
|
|
font.pointSize: 18
|
|
|
|
background: Rectangle {
|
|
color: homeButton.hovered ? activePalette.highlight : Qt.darker(activePalette.window, 1.15)
|
|
border.color: Qt.darker(activePalette.window, 1.15)
|
|
}
|
|
|
|
onClicked: {
|
|
if (!ensureNotComputing())
|
|
return
|
|
ensureSaved(function() {
|
|
_reconstruction.clear()
|
|
if (mainStack.depth == 1)
|
|
mainStack.replace("Homepage.qml")
|
|
else
|
|
mainStack.pop()
|
|
})
|
|
}
|
|
}
|
|
MenuBar {
|
|
palette.window: Qt.darker(activePalette.window, 1.15)
|
|
Menu {
|
|
title: "File"
|
|
Action {
|
|
id: newAction
|
|
text: "New"
|
|
shortcut: "Ctrl+N"
|
|
onTriggered: ensureSaved(function() {
|
|
_reconstruction.new()
|
|
})
|
|
}
|
|
Menu {
|
|
id: newPipelineMenu
|
|
title: "New Pipeline"
|
|
enabled: newPipelineMenuItems.model !== undefined && newPipelineMenuItems.model.length > 0
|
|
property int maxWidth: 1000
|
|
property int fullWidth: {
|
|
var result = 0;
|
|
for (var i = 0; i < count; ++i) {
|
|
var item = itemAt(i)
|
|
result = Math.max(item.implicitWidth + item.padding * 2, result)
|
|
}
|
|
return result;
|
|
}
|
|
implicitWidth: fullWidth
|
|
Repeater {
|
|
id: newPipelineMenuItems
|
|
model: MeshroomApp.pipelineTemplateFiles
|
|
MenuItem {
|
|
onTriggered: ensureSaved(function() {
|
|
_reconstruction.new(modelData["key"])
|
|
})
|
|
|
|
text: fileTextMetrics.elidedText
|
|
TextMetrics {
|
|
id: fileTextMetrics
|
|
text: modelData["name"]
|
|
elide: Text.ElideLeft
|
|
elideWidth: newPipelineMenu.maxWidth
|
|
}
|
|
|
|
ToolTip {
|
|
id: toolTip
|
|
|
|
delay: 200
|
|
text: modelData["path"]
|
|
visible: hovered
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Action {
|
|
id: openActionItem
|
|
text: "Open"
|
|
shortcut: "Ctrl+O"
|
|
onTriggered: ensureSaved(function() {
|
|
initFileDialogFolder(openFileDialog)
|
|
openFileDialog.open()
|
|
})
|
|
}
|
|
Menu {
|
|
id: openRecentMenu
|
|
title: "Open Recent"
|
|
enabled: recentFilesMenuItems.model !== undefined && recentFilesMenuItems.model.length > 0
|
|
property int maxWidth: 1000
|
|
property int fullWidth: {
|
|
var result = 0;
|
|
for (var i = 0; i < count; ++i) {
|
|
var item = itemAt(i)
|
|
result = Math.max(item.implicitWidth + item.padding * 2, result)
|
|
}
|
|
return result
|
|
}
|
|
implicitWidth: fullWidth
|
|
Repeater {
|
|
id: recentFilesMenuItems
|
|
model: MeshroomApp.recentProjectFiles
|
|
MenuItem {
|
|
onTriggered: ensureSaved(function() {
|
|
openRecentMenu.dismiss()
|
|
if (_reconstruction.load(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
|
|
}
|
|
MenuItem {
|
|
id: imageGalleryVisibilityCB
|
|
text: "Image Gallery"
|
|
checkable: true
|
|
checked: true
|
|
}
|
|
MenuSeparator {}
|
|
Action {
|
|
text: "Fullscreen"
|
|
checkable: true
|
|
checked: _window.visibility == ApplicationWindow.FullScreen
|
|
shortcut: "Ctrl+F"
|
|
onTriggered: _window.visibility == ApplicationWindow.FullScreen ? _window.showNormal() : showFullScreen()
|
|
}
|
|
}
|
|
Menu {
|
|
title: "Process"
|
|
Action {
|
|
text: "Compute all nodes"
|
|
onTriggered: computeManager.compute(null)
|
|
enabled: _reconstruction ? !_reconstruction.computingLocally : false
|
|
}
|
|
Action {
|
|
text: "Submit all nodes"
|
|
onTriggered: computeManager.submit(null)
|
|
enabled: _reconstruction ? _reconstruction.canSubmit : false
|
|
}
|
|
MenuSeparator {}
|
|
Action {
|
|
text: "Stop computation"
|
|
onTriggered: _reconstruction.stopExecution()
|
|
enabled: _reconstruction ? _reconstruction.computingLocally : false
|
|
}
|
|
}
|
|
Menu {
|
|
title: "Help"
|
|
Action {
|
|
text: "Online Documentation"
|
|
onTriggered: Qt.openUrlExternally("https://meshroom-manual.readthedocs.io")
|
|
}
|
|
Action {
|
|
text: "About Meshroom"
|
|
onTriggered: aboutDialog.open()
|
|
// Should be StandardKey.HelpContents, but for some reason it's not stable
|
|
// (may cause crash, requires pressing F1 twice after closing the popup)
|
|
shortcut: "F1"
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
color: Qt.darker(activePalette.window, 1.15)
|
|
}
|
|
|
|
Row {
|
|
// Process buttons
|
|
MaterialToolButton {
|
|
id: processButton
|
|
|
|
font.pointSize: 18
|
|
|
|
text: !(_reconstruction.computingLocally) ? MaterialIcons.send : MaterialIcons.cancel_schedule_send
|
|
|
|
ToolTip.text: !(_reconstruction.computingLocally) ? "Compute" : "Stop Computing"
|
|
ToolTip.visible: hovered
|
|
|
|
background: Rectangle {
|
|
color: processButton.hovered ? activePalette.highlight : Qt.darker(activePalette.window, 1.15)
|
|
border.color: Qt.darker(activePalette.window, 1.15)
|
|
}
|
|
|
|
onClicked: !(_reconstruction.computingLocally) ? computeManager.compute(null) : _reconstruction.stopExecution()
|
|
}
|
|
|
|
MaterialToolButton {
|
|
id: submitButton
|
|
|
|
font.pointSize: 18
|
|
|
|
visible: _reconstruction ? _reconstruction.canSubmit : false
|
|
text: MaterialIcons.rocket_launch
|
|
|
|
ToolTip.text: "Submit on Render Farm"
|
|
ToolTip.visible: hovered
|
|
|
|
background: Rectangle {
|
|
color: submitButton.hovered ? activePalette.highlight : Qt.darker(activePalette.window, 1.15)
|
|
border.color: Qt.darker(activePalette.window, 1.15)
|
|
}
|
|
|
|
onClicked: computeManager.submit(null)
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
color: Qt.darker(activePalette.window, 1.15)
|
|
}
|
|
|
|
// CompatibilityManager indicator
|
|
ToolButton {
|
|
id: compatibilityIssuesButton
|
|
visible: compatibilityManager.issueCount
|
|
text: MaterialIcons.warning
|
|
font.family: MaterialIcons.fontFamily
|
|
palette.buttonText: "#FF9800"
|
|
font.pointSize: 12
|
|
onClicked: compatibilityManager.open()
|
|
ToolTip.text: "Compatibility Issues"
|
|
ToolTip.visible: hovered
|
|
|
|
background: Rectangle {
|
|
color: compatibilityIssuesButton.hovered ? activePalette.highlight : Qt.darker(activePalette.window, 1.15)
|
|
border.color: Qt.darker(activePalette.window, 1.15)
|
|
}
|
|
}
|
|
}
|
|
|
|
footer: ToolBar {
|
|
id: footer
|
|
padding: 1
|
|
leftPadding: 4
|
|
rightPadding: 4
|
|
palette.window: Qt.darker(activePalette.window, 1.15)
|
|
|
|
// Cache Folder
|
|
RowLayout {
|
|
spacing: 0
|
|
MaterialToolButton {
|
|
font.pointSize: 8
|
|
text: MaterialIcons.folder_open
|
|
ToolTip.text: "Open Cache Folder"
|
|
onClicked: Qt.openUrlExternally(Filepath.stringToUrl(_reconstruction.graph.cacheDir))
|
|
}
|
|
|
|
TextField {
|
|
readOnly: true
|
|
selectByMouse: true
|
|
text: _reconstruction ? _reconstruction.graph.cacheDir : "Unknown"
|
|
color: Qt.darker(palette.text, 1.2)
|
|
background: Item {}
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: _reconstruction
|
|
|
|
// Bind messages to DialogsFactory
|
|
function createDialog(func, message) {
|
|
var dialog = func(_window)
|
|
// Set text afterwards to avoid dialog sizing issues
|
|
dialog.title = message.title
|
|
dialog.text = message.text
|
|
dialog.detailedText = message.detailedText
|
|
}
|
|
|
|
function onGraphChanged() {
|
|
// Open CompatibilityManager after file loading if any issue is detected
|
|
if (compatibilityManager.issueCount)
|
|
compatibilityManager.open()
|
|
// Trigger fit to visualize all nodes
|
|
graphEditor.fit()
|
|
}
|
|
|
|
function onInfo() { createDialog(dialogsFactory.info, arguments[0]) }
|
|
function onWarning() { createDialog(dialogsFactory.warning, arguments[0]) }
|
|
function onError() { createDialog(dialogsFactory.error, arguments[0]) }
|
|
}
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
spacing: 4
|
|
|
|
// "ProgressBar" reflecting status of all the chunks in the graph, in their process order
|
|
NodeChunks {
|
|
id: chunksListView
|
|
height: 6
|
|
Layout.fillWidth: true
|
|
model: _reconstruction ? _reconstruction.sortedDFSChunks : null
|
|
highlightChunks: false
|
|
}
|
|
|
|
MSplitView {
|
|
id: topBottomSplit
|
|
Layout.fillHeight: true
|
|
Layout.fillWidth: true
|
|
|
|
orientation: Qt.Vertical
|
|
|
|
// Setup global tooltip style
|
|
ToolTip.toolTip.background: Rectangle { color: activePalette.base; border.color: activePalette.mid }
|
|
|
|
WorkspaceView {
|
|
id: workspaceView
|
|
SplitView.fillHeight: true
|
|
SplitView.preferredHeight: 300
|
|
SplitView.minimumHeight: 80
|
|
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
|
|
}
|
|
}
|
|
|
|
MSplitView {
|
|
id: bottomContainer
|
|
orientation: Qt.Horizontal
|
|
visible: settingsUILayout.showGraphEditor
|
|
SplitView.preferredHeight: 300
|
|
SplitView.minimumHeight: 80
|
|
|
|
TabPanel {
|
|
id: graphEditorPanel
|
|
SplitView.fillWidth: true
|
|
SplitView.minimumWidth: 80
|
|
|
|
padding: 4
|
|
tabs: ["Graph Editor", "Task Manager", "Script Editor"]
|
|
|
|
headerBar: RowLayout {
|
|
MaterialToolButton {
|
|
text: MaterialIcons.sync
|
|
ToolTip.text: "Refresh Nodes Status"
|
|
ToolTip.visible: hovered
|
|
font.pointSize: 11
|
|
padding: 2
|
|
onClicked: {
|
|
updatingStatus = true
|
|
_reconstruction.forceNodesStatusUpdate()
|
|
updatingStatus = false
|
|
}
|
|
property bool updatingStatus: false
|
|
enabled: !updatingStatus
|
|
}
|
|
MaterialToolButton {
|
|
text: MaterialIcons.more_vert
|
|
font.pointSize: 11
|
|
padding: 2
|
|
onClicked: graphEditorMenu.open()
|
|
checkable: true
|
|
checked: graphEditorMenu.visible
|
|
Menu {
|
|
id: graphEditorMenu
|
|
y: parent.height
|
|
x: -width + parent.width
|
|
MenuItem {
|
|
text: "Clear Pending Status"
|
|
enabled: _reconstruction ? !_reconstruction.computingLocally : false
|
|
onTriggered: _reconstruction.graph.clearSubmittedNodes()
|
|
}
|
|
MenuItem {
|
|
text: "Force Unlock Nodes"
|
|
onTriggered: _reconstruction.graph.forceUnlockNodes()
|
|
}
|
|
|
|
Menu {
|
|
title: "Auto Layout Depth"
|
|
|
|
MenuItem {
|
|
id: autoLayoutMinimum
|
|
text: "Minimum"
|
|
checkable: true
|
|
checked: _reconstruction.layout.depthMode === 0
|
|
ToolTip.text: "Sets the Auto Layout Depth Mode to use Node's Minimum depth"
|
|
ToolTip.visible: hovered
|
|
ToolTip.delay: 200
|
|
onToggled: {
|
|
if (checked) {
|
|
_reconstruction.layout.depthMode = 0;
|
|
autoLayoutMaximum.checked = false;
|
|
}
|
|
// Prevents cases where the user unchecks the currently checked option
|
|
autoLayoutMinimum.checked = true;
|
|
}
|
|
}
|
|
MenuItem {
|
|
id: autoLayoutMaximum
|
|
text: "Maximum"
|
|
checkable: true
|
|
checked: _reconstruction.layout.depthMode === 1
|
|
ToolTip.text: "Sets the Auto Layout Depth Mode to use Node's Maximum depth"
|
|
ToolTip.visible: hovered
|
|
ToolTip.delay: 200
|
|
onToggled: {
|
|
if (checked) {
|
|
_reconstruction.layout.depthMode = 1;
|
|
autoLayoutMinimum.checked = false;
|
|
}
|
|
// Prevents cases where the user unchecks the currently checked option
|
|
autoLayoutMaximum.checked = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
Menu {
|
|
title: "Refresh Nodes Method"
|
|
|
|
MenuItem {
|
|
id: enableAutoRefresh
|
|
text: "Enable Auto-Refresh"
|
|
checkable: true
|
|
checked: _reconstruction.filePollerRefresh === 0
|
|
ToolTip.text: "Check every file's status periodically"
|
|
ToolTip.visible: hovered
|
|
ToolTip.delay: 200
|
|
onToggled: {
|
|
if (checked) {
|
|
disableAutoRefresh.checked = false
|
|
minimalAutoRefresh.checked = false
|
|
_reconstruction.filePollerRefreshChanged(0)
|
|
}
|
|
// Prevents cases where the user unchecks the currently checked option
|
|
enableAutoRefresh.checked = true
|
|
}
|
|
}
|
|
MenuItem {
|
|
id: disableAutoRefresh
|
|
text: "Disable Auto-Refresh"
|
|
checkable: true
|
|
checked: _reconstruction.filePollerRefresh === 1
|
|
ToolTip.text: "No file status will be checked"
|
|
ToolTip.visible: hovered
|
|
ToolTip.delay: 200
|
|
onToggled: {
|
|
if (checked) {
|
|
enableAutoRefresh.checked = false
|
|
minimalAutoRefresh.checked = false
|
|
_reconstruction.filePollerRefreshChanged(1)
|
|
}
|
|
// Prevents cases where the user unchecks the currently checked option
|
|
disableAutoRefresh.checked = true
|
|
}
|
|
}
|
|
MenuItem {
|
|
id: minimalAutoRefresh
|
|
text: "Enable Minimal Auto-Refresh"
|
|
checkable: true
|
|
checked: _reconstruction.filePollerRefresh === 2
|
|
ToolTip.text: "Check the file status of submitted or running chunks periodically"
|
|
ToolTip.visible: hovered
|
|
ToolTip.delay: 200
|
|
onToggled: {
|
|
if (checked) {
|
|
disableAutoRefresh.checked = false
|
|
enableAutoRefresh.checked = false
|
|
_reconstruction.filePollerRefreshChanged(2)
|
|
}
|
|
// Prevents cases where the user unchecks the currently checked option
|
|
minimalAutoRefresh.checked = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GraphEditor {
|
|
id: graphEditor
|
|
anchors.fill: parent
|
|
|
|
visible: graphEditorPanel.currentTab === 0
|
|
|
|
uigraph: _reconstruction
|
|
nodeTypesModel: _nodeTypes
|
|
|
|
onNodeDoubleClicked: function(mouse, node) {
|
|
_reconstruction.setActiveNode(node);
|
|
workspaceView.viewNode(node, mouse);
|
|
}
|
|
onComputeRequest: function(nodes) {
|
|
_reconstruction.forceNodesStatusUpdate();
|
|
computeManager.compute(nodes)
|
|
}
|
|
onSubmitRequest: function(nodes) {
|
|
_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
|
|
anchors.fill: parent
|
|
|
|
visible: graphEditorPanel.currentTab === 1
|
|
|
|
uigraph: _reconstruction
|
|
taskManager: _reconstruction ? _reconstruction.taskManager : null
|
|
}
|
|
|
|
ScriptEditor {
|
|
id: scriptEditor
|
|
anchors.fill: parent
|
|
rootApplication: root
|
|
|
|
visible: graphEditorPanel.currentTab === 2
|
|
}
|
|
}
|
|
|
|
NodeEditor {
|
|
id: nodeEditor
|
|
SplitView.preferredWidth: 500
|
|
SplitView.minimumWidth: 80
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |