Implementation of Homepage
|
@ -235,6 +235,7 @@ class Graph(BaseObject):
|
|||
node.alive = False
|
||||
self._importedNodes.clear()
|
||||
self._nodes.clear()
|
||||
self._unsetFilepath()
|
||||
|
||||
@property
|
||||
def fileFeatures(self):
|
||||
|
|
|
@ -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()
|
||||
|
|
BIN
meshroom/ui/img/MIKROS_TG.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
meshroom/ui/img/MILL_TG.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
meshroom/ui/img/MPC_TG.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
meshroom/ui/img/Technicolor-Group_RGB_MIKROS_P-TG_Col-Rev.png
Normal file
After Width: | Height: | Size: 188 KiB |
BIN
meshroom/ui/img/Technicolor-Group_RGB_MILL_P-TG_Col-Rev.png
Normal file
After Width: | Height: | Size: 113 KiB |
BIN
meshroom/ui/img/Technicolor-Group_RGB_MPC_P-TG_Col-Rev.png
Normal file
After Width: | Height: | Size: 126 KiB |
After Width: | Height: | Size: 148 KiB |
BIN
meshroom/ui/img/TechnicolorGames_TG.png
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
meshroom/ui/img/meshroom-anim-once.gif
Normal file
After Width: | Height: | Size: 689 KiB |
BIN
meshroom/ui/img/technicolor-group_rgb_primary_col-rev.png
Normal file
After Width: | Height: | Size: 201 KiB |
774
meshroom/ui/qml/Application.qml
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
328
meshroom/ui/qml/Homepage.qml
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,))
|
||||
|
||||
|
|