Implementation of Homepage

This commit is contained in:
Aurore LAFAURIE 2024-07-01 15:56:27 +02:00
parent f48785ff77
commit 5b92df328f
18 changed files with 1151 additions and 688 deletions

View file

@ -235,6 +235,7 @@ class Graph(BaseObject):
node.alive = False
self._importedNodes.clear()
self._nodes.clear()
self._unsetFilepath()
@property
def fileFeatures(self):

View file

@ -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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
meshroom/ui/img/MILL_TG.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
meshroom/ui/img/MPC_TG.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

View 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
}
}
}
}
}

View file

@ -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)
}

View 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
}
}
}
}
}
}
}

View file

@ -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

View file

@ -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 {

View file

@ -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,))