mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-06-02 19:02:29 +02:00
* fix random segfault due to signals with 2+ arguments (PySide bug) * group Connections to '_reconstruction' instance
451 lines
15 KiB
QML
Executable file
451 lines
15 KiB
QML
Executable file
import QtQuick 2.7
|
|
import QtQuick.Controls 2.3
|
|
import QtQuick.Controls 1.4 as Controls1 // For SplitView
|
|
import QtQuick.Layouts 1.1
|
|
import QtQuick.Window 2.3
|
|
import QtQml.Models 2.2
|
|
import Qt.labs.platform 1.0 as Platform
|
|
import Qt.labs.settings 1.0
|
|
import GraphEditor 1.0
|
|
import MaterialIcons 2.2
|
|
import Utils 1.0
|
|
|
|
ApplicationWindow {
|
|
id: _window
|
|
|
|
width: 1280
|
|
height: 720
|
|
visible: true
|
|
title: (_reconstruction.graph.filepath ? _reconstruction.graph.filepath : "Untitled") + (_reconstruction.undoStack.clean ? "" : "*") + " - Meshroom"
|
|
|
|
property variant node: null
|
|
// supported 3D files extensions
|
|
readonly property var _3dFileExtensions: ['.obj', '.abc']
|
|
|
|
onClosing: {
|
|
// make sure document is saved before exiting application
|
|
close.accepted = false
|
|
if(!ensureNotComputing())
|
|
return
|
|
ensureSaved(function(){ Qt.quit() })
|
|
}
|
|
|
|
SystemPalette { id: palette }
|
|
SystemPalette { id: disabledPalette; colorGroup: SystemPalette.Disabled}
|
|
|
|
palette.window: palette.window
|
|
|
|
Settings {
|
|
id: settings_UILayout
|
|
category: 'UILayout'
|
|
property alias showLiveReconstruction: liveSfMVisibilityCB.checked
|
|
property alias showGraphEditor: graphEditorVisibilityCB.checked
|
|
}
|
|
|
|
Dialog {
|
|
id: unsavedDialog
|
|
|
|
property var _callback: undefined
|
|
|
|
title: "Unsaved Document"
|
|
modal: true
|
|
x: parent.width/2 - width/2
|
|
y: parent.height/2 - height/2
|
|
standardButtons: Dialog.Save | Dialog.Cancel | Dialog.Discard
|
|
padding: 15
|
|
onDiscarded: {
|
|
close() // BUG ? discard does not close window
|
|
fireCallback()
|
|
}
|
|
|
|
onAccepted: {
|
|
// save current file
|
|
if(saveAction.enabled)
|
|
{
|
|
saveAction.trigger()
|
|
fireCallback()
|
|
}
|
|
// open "save as" dialog
|
|
else
|
|
{
|
|
saveFileDialog.open()
|
|
function _callbackWrapper(rc) {
|
|
if(rc == Platform.Dialog.Accepted)
|
|
fireCallback()
|
|
saveFileDialog.closed.disconnect(_callbackWrapper)
|
|
}
|
|
saveFileDialog.closed.connect(_callbackWrapper)
|
|
}
|
|
}
|
|
|
|
function fireCallback()
|
|
{
|
|
// call the callback and reset it
|
|
if(_callback)
|
|
_callback()
|
|
_callback = undefined
|
|
}
|
|
|
|
/// Open the unsaved dialog warning with an optional
|
|
/// callback to fire when the dialog is accepted/discarded
|
|
function prompt(callback)
|
|
{
|
|
_callback = callback
|
|
open()
|
|
}
|
|
|
|
Label {
|
|
text: "Your current Graph is not saved"
|
|
}
|
|
}
|
|
|
|
Platform.FileDialog {
|
|
id: saveFileDialog
|
|
|
|
signal closed(var result)
|
|
|
|
title: "Save File"
|
|
nameFilters: ["Meshroom Graphs (*.mg)"]
|
|
defaultSuffix: ".mg"
|
|
fileMode: Platform.FileDialog.SaveFile
|
|
onAccepted: {
|
|
_reconstruction.saveAs(file)
|
|
closed(Platform.Dialog.Accepted)
|
|
}
|
|
onRejected: closed(Platform.Dialog.Rejected)
|
|
}
|
|
|
|
Platform.FileDialog {
|
|
id: openFileDialog
|
|
title: "Open File"
|
|
nameFilters: ["Meshroom Graphs (*.mg)"]
|
|
onAccepted: {
|
|
_reconstruction.loadUrl(file.toString())
|
|
graphEditor.doAutoLayout()
|
|
}
|
|
}
|
|
|
|
// Check if document has been saved
|
|
function ensureSaved(callback)
|
|
{
|
|
var saved = _reconstruction.undoStack.clean
|
|
// If current document is modified, open "unsaved dialog"
|
|
if(!saved)
|
|
{
|
|
unsavedDialog.prompt(callback)
|
|
}
|
|
else // otherwise, directly call the callback
|
|
{
|
|
callback()
|
|
}
|
|
return saved
|
|
}
|
|
|
|
Dialog {
|
|
id: computingAtExitDialog
|
|
title: "Operation in progress"
|
|
x: parent.width/2 - width/2
|
|
y: parent.height/2 - height/2
|
|
padding: 15
|
|
standardButtons: Dialog.Ok
|
|
modal: true
|
|
Label {
|
|
text: "Please stop any local computation before exiting Meshroom"
|
|
}
|
|
}
|
|
|
|
// Check and return whether no local computation is in progress
|
|
function ensureNotComputing()
|
|
{
|
|
if(_reconstruction.computingLocally)
|
|
{
|
|
// Open a warning dialog to ask for computation to be stopped
|
|
computingAtExitDialog.open()
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
Dialog {
|
|
// Popup displayed while the application
|
|
// is busy building intrinsics while importing images
|
|
id: buildingIntrinsicsDialog
|
|
modal: true
|
|
x: _window.width / 2 - width/2
|
|
y: _window.height / 2 - height/2
|
|
visible: _reconstruction.buildingIntrinsics
|
|
closePolicy: Popup.NoAutoClose
|
|
title: "Import Images"
|
|
padding: 15
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
Label {
|
|
text: "Extracting images metadata... "
|
|
horizontalAlignment: Text.AlignHCenter
|
|
|
|
Layout.fillWidth: true
|
|
}
|
|
ProgressBar {
|
|
indeterminate: true
|
|
Layout.fillWidth: true
|
|
}
|
|
}
|
|
}
|
|
|
|
DialogsFactory {
|
|
id: dialogsFactory
|
|
}
|
|
|
|
Action {
|
|
id: undoAction
|
|
|
|
property string tooltip: 'Undo "' +_reconstruction.undoStack.undoText +'"'
|
|
text: "Undo"
|
|
shortcut: "Ctrl+Z"
|
|
enabled: _reconstruction.undoStack.canUndo && !_reconstruction.computing
|
|
onTriggered: _reconstruction.undoStack.undo()
|
|
}
|
|
Action {
|
|
id: redoAction
|
|
|
|
property string tooltip: 'Redo "' +_reconstruction.undoStack.redoText +'"'
|
|
text: "Redo"
|
|
shortcut: "Ctrl+Shift+Z"
|
|
enabled: _reconstruction.undoStack.canRedo && !_reconstruction.computing
|
|
onTriggered: _reconstruction.undoStack.redo()
|
|
}
|
|
|
|
Action {
|
|
shortcut: "Ctrl+Shift+P"
|
|
onTriggered: _PaletteManager.togglePalette()
|
|
}
|
|
|
|
header: MenuBar {
|
|
palette.window: Qt.darker(palette.window, 1.15)
|
|
Menu {
|
|
title: "File"
|
|
Action {
|
|
text: "New"
|
|
onTriggered: ensureSaved(function() { _reconstruction.new(); graphEditor.doAutoLayout() })
|
|
}
|
|
Action {
|
|
text: "Open"
|
|
shortcut: "Ctrl+O"
|
|
onTriggered: ensureSaved(function() { openFileDialog.open() })
|
|
}
|
|
Action {
|
|
id: saveAction
|
|
text: "Save"
|
|
shortcut: "Ctrl+S"
|
|
enabled: _reconstruction.graph.filepath != "" && !_reconstruction.undoStack.clean
|
|
onTriggered: _reconstruction.save()
|
|
}
|
|
Action {
|
|
id: saveAsAction
|
|
text: "Save As..."
|
|
shortcut: "Ctrl+Shift+S"
|
|
onTriggered: saveFileDialog.open()
|
|
}
|
|
MenuSeparator { }
|
|
Action {
|
|
text: "Quit"
|
|
onTriggered: Qt.quit()
|
|
}
|
|
}
|
|
Menu {
|
|
title: "Edit"
|
|
MenuItem {
|
|
action: undoAction
|
|
ToolTip.visible: hovered
|
|
ToolTip.text: undoAction.tooltip
|
|
}
|
|
MenuItem {
|
|
action: redoAction
|
|
ToolTip.visible: hovered
|
|
ToolTip.text: redoAction.tooltip
|
|
}
|
|
}
|
|
Menu {
|
|
title: "View"
|
|
MenuItem {
|
|
id: graphEditorVisibilityCB
|
|
text: "Graph Editor"
|
|
checkable: true
|
|
checked: true
|
|
}
|
|
MenuItem {
|
|
id: liveSfMVisibilityCB
|
|
text: "Live Reconstruction"
|
|
checkable: true
|
|
checked: false
|
|
}
|
|
MenuSeparator {}
|
|
Action {
|
|
text: "Fullscreen"
|
|
checkable: true
|
|
checked: _window.visibility == ApplicationWindow.FullScreen
|
|
shortcut: "Ctrl+F"
|
|
onTriggered: _window.visibility == ApplicationWindow.FullScreen ? _window.showNormal() : showFullScreen()
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Connections {
|
|
target: _reconstruction
|
|
// Request graph auto-layout when an augmentation step is added for readability
|
|
onSfmAugmented: graphEditor.doAutoLayout(_reconstruction.graph.nodes.indexOf(arguments[0]),
|
|
_reconstruction.graph.nodes.indexOf(arguments[1]),
|
|
0,
|
|
graphEditor.boundingBox().height + graphEditor.gridSpacing
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
onInfo: createDialog(dialogsFactory.info, arguments[0])
|
|
onWarning: createDialog(dialogsFactory.warning, arguments[0])
|
|
onError: createDialog(dialogsFactory.error, arguments[0])
|
|
}
|
|
|
|
Controls1.SplitView {
|
|
anchors.fill: parent
|
|
orientation: Qt.Vertical
|
|
|
|
// Setup global tooltip style
|
|
ToolTip.toolTip.background: Rectangle { color: palette.base; border.color: palette.mid }
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
Layout.topMargin: 2
|
|
implicitHeight: Math.round(parent.height * 0.7)
|
|
spacing: 4
|
|
Row {
|
|
enabled: !_reconstruction.computingExternally
|
|
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
|
|
enabled: _reconstruction.viewpoints.count > 2 && !_reconstruction.computing
|
|
onClicked: _reconstruction.execute(null)
|
|
}
|
|
Button {
|
|
text: "Stop"
|
|
enabled: _reconstruction.computingLocally
|
|
onClicked: _reconstruction.stopExecution()
|
|
}
|
|
Item { width: 20; height: 1 }
|
|
Button {
|
|
enabled: _reconstruction.viewpoints.count > 2 && !_reconstruction.computing && _reconstruction.graph.filepath != ""
|
|
text: "Submit"
|
|
onClicked: _reconstruction.submit(null)
|
|
}
|
|
}
|
|
Label {
|
|
text: "Graph is being computed externally"
|
|
font.italic: true
|
|
Layout.alignment: Qt.AlignHCenter
|
|
visible: _reconstruction.computingExternally
|
|
}
|
|
|
|
// "ProgressBar" reflecting status of all the chunks in the graph, in their process order
|
|
ListView {
|
|
id: chunksListView
|
|
Layout.fillWidth: true
|
|
height: 6
|
|
model: _reconstruction.sortedDFSNodes
|
|
orientation: ListView.Horizontal
|
|
interactive: false
|
|
|
|
delegate: NodeChunks {
|
|
model: object.chunks
|
|
height: 6
|
|
chunkWidth: chunksListView.width / _reconstruction.chunksCount
|
|
width: childrenRect.width
|
|
}
|
|
}
|
|
|
|
WorkspaceView {
|
|
id: workspaceView
|
|
reconstruction: _reconstruction
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
Layout.minimumHeight: 50
|
|
onRequestGraphAutoLayout: graphEditor.doAutoLayout()
|
|
}
|
|
}
|
|
Panel {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: false
|
|
height: Math.round(parent.height * 0.3)
|
|
title: "Graph Editor"
|
|
visible: settings_UILayout.showGraphEditor
|
|
|
|
Controls1.SplitView {
|
|
orientation: Qt.Horizontal
|
|
anchors.fill: parent
|
|
|
|
Item {
|
|
Layout.fillHeight: true
|
|
Layout.fillWidth: true
|
|
Layout.margins: 10
|
|
GraphEditor {
|
|
id: graphEditor
|
|
anchors.fill: parent
|
|
uigraph: _reconstruction
|
|
nodeTypesModel: _nodeTypes
|
|
readOnly: _reconstruction.computing
|
|
onNodeDoubleClicked: {
|
|
if(node.nodeType == "StructureFromMotion")
|
|
{
|
|
_reconstruction.sfm = node
|
|
return
|
|
}
|
|
for(var i=0; i < node.attributes.count; ++i)
|
|
{
|
|
var attr = node.attributes.at(i)
|
|
if(attr.isOutput
|
|
&& attr.desc.type === "File"
|
|
&& _3dFileExtensions.indexOf(Filepath.extension(attr.value)) > - 1 )
|
|
{
|
|
workspaceView.load3DMedia(Filepath.stringToUrl(attr.value))
|
|
break // only load first model found
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Item {
|
|
implicitHeight: Math.round(parent.height * 0.2)
|
|
implicitWidth: Math.round(parent.width * 0.3)
|
|
|
|
Loader {
|
|
anchors.fill: parent
|
|
anchors.margins: 2
|
|
active: graphEditor.selectedNode != null
|
|
sourceComponent: Component {
|
|
AttributeEditor {
|
|
node: graphEditor.selectedNode
|
|
// Make AttributeEditor readOnly when computing
|
|
readOnly: _reconstruction.computing
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|