diff --git a/meshroom/core/graph.py b/meshroom/core/graph.py index e63aceca1a..7808f1e235 100644 --- a/meshroom/core/graph.py +++ b/meshroom/core/graph.py @@ -215,6 +215,7 @@ def __init__(self, name, parent=None): super(Graph, self).__init__(parent) self.name = name self._loading = False + self._saving = False self._updateEnabled = True self._updateRequested = False self.dirtyTopology = False @@ -251,6 +252,11 @@ def fileFeatures(self): def isLoading(self): """ Return True if the graph is currently being loaded. """ return self._loading + + @property + def isSaving(self): + """ Return True if the graph is currently being saved. """ + return self._saving @Slot(str) def load(self, filepath, setupProjectFile=True, importProject=False, publishOutputs=False): @@ -1347,6 +1353,23 @@ def asString(self): return str(self.toDict()) def save(self, filepath=None, setupProjectFile=True, template=False): + """ + Save the current Meshroom graph as a serialized ".mg" file. + + Args: + filepath: project filepath to save as. + setupProjectFile: Store the reference to the project file and setup the cache directory. + If false, it only saves the graph of the project file as a template. + template: If true, saves the current graph as a template. + """ + # Update the saving flag indicating that the current graph is being saved + self._saving = True + try: + self._save(filepath=filepath, setupProjectFile=setupProjectFile, template=template) + finally: + self._saving = False + + def _save(self, filepath=None, setupProjectFile=True, template=False): path = filepath or self._filepath if not path: raise ValueError("filepath must be specified for unsaved files.") @@ -1636,6 +1659,7 @@ def setVerbose(self, v): edges = Property(BaseObject, edges.fget, constant=True) filepathChanged = Signal() filepath = Property(str, lambda self: self._filepath, notify=filepathChanged) + isSaving = Property(bool, isSaving.fget, constant=True) fileReleaseVersion = Property(str, lambda self: self.header.get(Graph.IO.Keys.ReleaseVersion, "0.0"), notify=filepathChanged) fileDateVersion = Property(float, fileDateVersion.fget, fileDateVersion.fset, notify=filepathChanged) diff --git a/meshroom/ui/qml/Application.qml b/meshroom/ui/qml/Application.qml index 48884e2f33..903c9241fb 100644 --- a/meshroom/ui/qml/Application.qml +++ b/meshroom/ui/qml/Application.qml @@ -20,6 +20,16 @@ Page { property alias unsavedDialog: unsavedDialog property alias workspaceView: workspaceView + readonly property var scenefile: _reconstruction ? _reconstruction.graph.filepath : ""; + + onScenefileChanged: { + // Check if we're not currently saving and emit the currentProjectChanged signal + if (! _reconstruction.graph.isSaving) { + // Refresh the NodeEditor + nodeEditor.refresh(); + } + } + Settings { id: settingsUILayout category: "UILayout" diff --git a/meshroom/ui/qml/GraphEditor/NodeEditor.qml b/meshroom/ui/qml/GraphEditor/NodeEditor.qml index a97400ab7a..5f222f4231 100644 --- a/meshroom/ui/qml/GraphEditor/NodeEditor.qml +++ b/meshroom/ui/qml/GraphEditor/NodeEditor.qml @@ -42,6 +42,14 @@ Panel { } } + function refresh() { + /** + * Refresh properties of the Node Editor. + */ + // Reset tab bar's current index + tabBar.currentIndex = 0; + } + headerBar: RowLayout { Label { id: computationInfo @@ -372,6 +380,9 @@ Panel { property bool isComputable: root.node !== null && root.node.isComputable + // The indices of the tab bar which can be shown for incomputable nodes + readonly property var nonComputableTabIndices: [0, 4, 5]; + Layout.fillWidth: true width: childrenRect.width position: TabBar.Footer @@ -415,9 +426,11 @@ Panel { rightPadding: leftPadding } - onIsComputableChanged: { - if (!isComputable) { - tabBar.currentIndex = 0 + onVisibleChanged: { + // If we have a node selected and the node is not Computable + // Reset the currentIndex to 0, if the current index is not allowed for an incomputable node + if ((root.node && !root.node.isComputable) && (nonComputableTabIndices.indexOf(tabBar.currentIndex) === -1)) { + tabBar.currentIndex = 0; } } } diff --git a/meshroom/ui/qml/GraphEditor/StatViewer.qml b/meshroom/ui/qml/GraphEditor/StatViewer.qml index 91e224681a..ed25d577ee 100644 --- a/meshroom/ui/qml/GraphEditor/StatViewer.qml +++ b/meshroom/ui/qml/GraphEditor/StatViewer.qml @@ -310,220 +310,208 @@ Item { *** CPU UI *** **************************/ - ColumnLayout { + Button { + id: toggleCpuBtn Layout.fillWidth: true + text: "Toggle CPU's" + state: "closed" - Button { - id: toggleCpuBtn - Layout.fillWidth: true - text: "Toggle CPU's" - state: "closed" + onClicked: state === "opened" ? state = "closed" : state = "opened" - onClicked: state === "opened" ? state = "closed" : state = "opened" + MaterialLabel { + text: MaterialIcons.arrow_drop_down + font.pointSize: 14 + anchors.right: parent.right + } - MaterialLabel { - text: MaterialIcons.arrow_drop_down - font.pointSize: 14 - anchors.right: parent.right + states: [ + State { + name: "opened" + PropertyChanges { target: cpuBtnContainer; visible: true } + PropertyChanges { target: toggleCpuBtn; down: true } + }, + State { + name: "closed" + PropertyChanges { target: cpuBtnContainer; visible: false } + PropertyChanges { target: toggleCpuBtn; down: false } } + ] + } - states: [ - State { - name: "opened" - PropertyChanges { target: cpuBtnContainer; visible: true } - PropertyChanges { target: toggleCpuBtn; down: true } - }, - State { - name: "closed" - PropertyChanges { target: cpuBtnContainer; visible: false } - PropertyChanges { target: toggleCpuBtn; down: false } - } - ] - } + Item { + id: cpuBtnContainer - Item { - id: cpuBtnContainer - - Layout.fillWidth: true - implicitHeight: childrenRect.height - Layout.leftMargin: 25 - - RowLayout { - width: parent.width - anchors.horizontalCenter: parent.horizontalCenter - - ChartViewCheckBox { - id: allCPU - text: "ALL" - color: textColor - checkState: cpuLegend.buttonGroup.checkState - leftPadding: 0 - onClicked: { - var _checked = checked; - for (var i = 0; i < cpuChart.count; ++i) { - cpuChart.series(i).visible = _checked - } - } - } + Layout.fillWidth: true + implicitHeight: childrenRect.height + Layout.leftMargin: 25 - ChartViewLegend { - id: cpuLegend - Layout.fillWidth: true - Layout.fillHeight: true - chartView: cpuChart - } - } - } + RowLayout { + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter - InteractiveChartView { - id: cpuChart - - Layout.fillWidth: true - Layout.preferredHeight: width / 2 - margins.top: 0 - margins.bottom: 0 - antialiasing: true - - legend.visible: false - theme: ChartView.ChartThemeLight - backgroundColor: "transparent" - plotAreaColor: "transparent" - titleColor: textColor - - visible: (root.fileVersion > 0.0) // Only visible if we have valid information - title: "CPU: " + root.nbCores + " cores, " + root.cpuFrequency + "MHz" - - ValueAxis { - id: valueCpuY - min: 0 - max: 100 - titleText: "%" + ChartViewCheckBox { + id: allCPU + text: "ALL" color: textColor - gridLineColor: textColor - minorGridLineColor: textColor - shadesColor: textColor - shadesBorderColor: textColor - labelsColor: textColor + checkState: cpuLegend.buttonGroup.checkState + leftPadding: 0 + onClicked: { + var _checked = checked; + for (var i = 0; i < cpuChart.count; ++i) { + cpuChart.series(i).visible = _checked + } + } } - ValueAxis { - id: valueCpuX - min: 0 - max: root.deltaTime * Math.max(1, root.nbReads) - titleText: "Minutes" - color: textColor - gridLineColor: textColor - minorGridLineColor: textColor - shadesColor: textColor - shadesBorderColor: textColor - labelsColor: textColor + ChartViewLegend { + id: cpuLegend + Layout.fillWidth: true + Layout.fillHeight: true + chartView: cpuChart } } } + InteractiveChartView { + id: cpuChart + + Layout.fillWidth: true + Layout.preferredHeight: width / 2 + margins.top: 0 + margins.bottom: 0 + antialiasing: true + + legend.visible: false + theme: ChartView.ChartThemeLight + backgroundColor: "transparent" + plotAreaColor: "transparent" + titleColor: textColor + + visible: (root.fileVersion > 0.0) // Only visible if we have valid information + title: "CPU: " + root.nbCores + " cores, " + root.cpuFrequency + "MHz" + + ValueAxis { + id: valueCpuY + min: 0 + max: 100 + titleText: "%" + color: textColor + gridLineColor: textColor + minorGridLineColor: textColor + shadesColor: textColor + shadesBorderColor: textColor + labelsColor: textColor + } + + ValueAxis { + id: valueCpuX + min: 0 + max: root.deltaTime * Math.max(1, root.nbReads) + titleText: "Minutes" + color: textColor + gridLineColor: textColor + minorGridLineColor: textColor + shadesColor: textColor + shadesBorderColor: textColor + labelsColor: textColor + } + } /************************** *** RAM UI *** **************************/ - ColumnLayout { - - InteractiveChartView { - id: ramChart - margins.top: 0 - margins.bottom: 0 - Layout.fillWidth: true - Layout.preferredHeight: width / 2 - antialiasing: true - legend.color: textColor - legend.labelColor: textColor - legend.visible: false - theme: ChartView.ChartThemeLight - backgroundColor: "transparent" - plotAreaColor: "transparent" - titleColor: textColor - - visible: (root.fileVersion > 0.0) // Only visible if we have valid information - title: root.ramLabel + root.ramTotal + "GB" - - ValueAxis { - id: valueRamY - min: 0 - max: 100 - titleText: "%" - color: textColor - gridLineColor: textColor - minorGridLineColor: textColor - shadesColor: textColor - shadesBorderColor: textColor - labelsColor: textColor - } + InteractiveChartView { + id: ramChart + margins.top: 0 + margins.bottom: 0 + Layout.fillWidth: true + Layout.preferredHeight: width / 2 + antialiasing: true + legend.color: textColor + legend.labelColor: textColor + legend.visible: false + theme: ChartView.ChartThemeLight + backgroundColor: "transparent" + plotAreaColor: "transparent" + titleColor: textColor + + visible: (root.fileVersion > 0.0) // Only visible if we have valid information + title: root.ramLabel + root.ramTotal + "GB" + + ValueAxis { + id: valueRamY + min: 0 + max: 100 + titleText: "%" + color: textColor + gridLineColor: textColor + minorGridLineColor: textColor + shadesColor: textColor + shadesBorderColor: textColor + labelsColor: textColor + } - ValueAxis { - id: valueRamX - min: 0 - max: root.deltaTime * Math.max(1, root.nbReads) - titleText: "Minutes" - color: textColor - gridLineColor: textColor - minorGridLineColor: textColor - shadesColor: textColor - shadesBorderColor: textColor - labelsColor: textColor - } + ValueAxis { + id: valueRamX + min: 0 + max: root.deltaTime * Math.max(1, root.nbReads) + titleText: "Minutes" + color: textColor + gridLineColor: textColor + minorGridLineColor: textColor + shadesColor: textColor + shadesBorderColor: textColor + labelsColor: textColor } } - /************************** *** GPU UI *** **************************/ - ColumnLayout { - - InteractiveChartView { - id: gpuChart - - Layout.fillWidth: true - Layout.preferredHeight: width/2 - margins.top: 0 - margins.bottom: 0 - antialiasing: true - legend.color: textColor - legend.labelColor: textColor - theme: ChartView.ChartThemeLight - backgroundColor: "transparent" - plotAreaColor: "transparent" - titleColor: textColor - - visible: (root.fileVersion >= 2.0) // No GPU information was collected before stats 2.0 fileVersion - title: (root.gpuName || root.gpuTotalMemory) ? ("GPU: " + root.gpuName + ", " + root.gpuTotalMemory + "MB") : "No GPU" - - ValueAxis { - id: valueGpuY - min: 0 - max: root.gpuMaxAxis - titleText: "%, °C" - color: textColor - gridLineColor: textColor - minorGridLineColor: textColor - shadesColor: textColor - shadesBorderColor: textColor - labelsColor: textColor - } + InteractiveChartView { + id: gpuChart - ValueAxis { - id: valueGpuX - min: 0 - max: root.deltaTime * Math.max(1, root.nbReads) - titleText: "Minutes" - color: textColor - gridLineColor: textColor - minorGridLineColor: textColor - shadesColor: textColor - shadesBorderColor: textColor - labelsColor: textColor - } + Layout.fillWidth: true + Layout.preferredHeight: width/2 + margins.top: 0 + margins.bottom: 0 + antialiasing: true + legend.color: textColor + legend.labelColor: textColor + theme: ChartView.ChartThemeLight + backgroundColor: "transparent" + plotAreaColor: "transparent" + titleColor: textColor + + visible: (root.fileVersion >= 2.0) // No GPU information was collected before stats 2.0 fileVersion + title: (root.gpuName || root.gpuTotalMemory) ? ("GPU: " + root.gpuName + ", " + root.gpuTotalMemory + "MB") : "No GPU" + + ValueAxis { + id: valueGpuY + min: 0 + max: root.gpuMaxAxis + titleText: "%, °C" + color: textColor + gridLineColor: textColor + minorGridLineColor: textColor + shadesColor: textColor + shadesBorderColor: textColor + labelsColor: textColor + } + + ValueAxis { + id: valueGpuX + min: 0 + max: root.deltaTime * Math.max(1, root.nbReads) + titleText: "Minutes" + color: textColor + gridLineColor: textColor + minorGridLineColor: textColor + shadesColor: textColor + shadesBorderColor: textColor + labelsColor: textColor } } }