Skip to content

Commit 730852c

Browse files
authored
Merge pull request #2861 from alicevision/dev/reload_plugins_popup
[ui] Implement status bar to display messages
2 parents 7b8f447 + fe4fe0c commit 730852c

File tree

7 files changed

+170
-4
lines changed

7 files changed

+170
-4
lines changed

meshroom/core/node.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ def process(self, forceCompute=False, inCurrentEnv=False):
522522
executionStatus = None
523523
self.statThread = stats.StatisticsThread(self)
524524
self.statThread.start()
525+
525526
try:
526527
self.node.nodeDesc.processChunk(self)
527528
# NOTE: this assumes saving the output attributes for each chunk

meshroom/ui/app.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from PySide6 import __version__ as PySideVersion
88
from PySide6 import QtCore
9-
from PySide6.QtCore import QUrl, QJsonValue, qInstallMessageHandler, QtMsgType, QSettings
9+
from PySide6.QtCore import QObject, QUrl, QJsonValue, qInstallMessageHandler, QtMsgType, QSettings
1010
from PySide6.QtGui import QIcon
1111
from PySide6.QtQml import QQmlDebuggingEnabler
1212
from PySide6.QtQuickControls2 import QQuickStyle
@@ -25,6 +25,7 @@
2525
from meshroom.ui.components.scene3D import Scene3DHelper, Transformations3DHelper
2626
from meshroom.ui.components.scriptEditor import ScriptEditorManager
2727
from meshroom.ui.components.thumbnail import ThumbnailCache
28+
from meshroom.ui.components.statusBar import MessageController
2829
from meshroom.ui.palette import PaletteManager
2930
from meshroom.ui.reconstruction import Reconstruction
3031
from meshroom.ui.utils import QmlInstantEngine
@@ -281,6 +282,8 @@ def __init__(self, inputArgs):
281282
self.engine.rootContext().setContextProperty("ThumbnailCache", ThumbnailCache(parent=self))
282283

283284
# additional context properties
285+
self._messageController = MessageController(parent=self)
286+
self.engine.rootContext().setContextProperty("_messageController", self._messageController)
284287
self.engine.rootContext().setContextProperty("_PaletteManager", PaletteManager(self.engine, parent=self))
285288
self.engine.rootContext().setContextProperty("ScriptEditorManager", ScriptEditorManager(parent=self))
286289
self.engine.rootContext().setContextProperty("MeshroomApp", self)
@@ -353,6 +356,16 @@ def _pipelineTemplateNames(self):
353356
def reloadTemplateList(self):
354357
meshroom.core.initPipelines()
355358
self.pipelineTemplateFilesChanged.emit()
359+
360+
@Slot()
361+
def forceUIUpdate(self):
362+
""" Force UI to process pending events
363+
Necessary when we want to update the UI while a trigger is still running (e.g. reloadPlugins)
364+
"""
365+
self.processEvents()
366+
367+
def showMessage(self, message, status=None, duration=5000):
368+
self._messageController.sendMessage(message, status, duration)
356369

357370
def _retrieveThumbnailPath(self, filepath: str) -> str:
358371
"""
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from PySide6.QtCore import QObject, Signal
2+
3+
4+
class MessageController(QObject):
5+
"""
6+
Handles messages sent from the Python side to the StatusBar component
7+
"""
8+
9+
message = Signal(str, str, int)
10+
11+
def __init__(self, parent):
12+
super().__init__(parent)
13+
14+
def sendMessage(self, msg, status, duration):
15+
""" Sends a message that will be displayed on the status bar """
16+
self.message.emit(msg, status, duration)

meshroom/ui/qml/Application.qml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,8 @@ Page {
552552
text: "Reload Plugins Source Code"
553553
shortcut: "Ctrl+Shift+R"
554554
onTriggered: {
555-
_reconstruction.reloadPlugins()
555+
statusBar.showMessage("Reloading plugins...")
556+
_reconstruction.reloadPlugins() // This will handle the message to show that it finished properly
556557
}
557558
}
558559

@@ -1070,9 +1071,10 @@ Page {
10701071
rightPadding: 4
10711072
palette.window: Qt.darker(activePalette.window, 1.15)
10721073

1073-
// Cache Folder
10741074
RowLayout {
1075+
anchors.fill: parent
10751076
spacing: 0
1077+
10761078
MaterialToolButton {
10771079
font.pointSize: 8
10781080
text: MaterialIcons.folder_open
@@ -1087,6 +1089,16 @@ Page {
10871089
color: Qt.darker(palette.text, 1.2)
10881090
background: Item {}
10891091
}
1092+
1093+
// Spacer to push status bar to the right
1094+
Item { Layout.fillWidth: true }
1095+
1096+
StatusBar {
1097+
id: statusBar
1098+
objectName: "statusBar" // Expose to python
1099+
height: parent.height
1100+
defaultIcon : MaterialIcons.comment
1101+
}
10901102
}
10911103
}
10921104

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import QtQuick
2+
import QtQuick.Controls
3+
import QtQuick.Layouts
4+
import MaterialIcons 2.2
5+
6+
import Utils 1.0
7+
8+
RowLayout {
9+
id: root
10+
11+
property color defaultColor: Qt.darker(palette.text, 1.2)
12+
property string defaultIcon : MaterialIcons.circle
13+
property int interval : 5000
14+
property bool logMessage : false
15+
16+
TextField {
17+
id: statusBarField
18+
Layout.fillHeight: true
19+
readOnly: true
20+
selectByMouse: true
21+
text: statusBar.message
22+
color: defaultColor
23+
background: Item {}
24+
visible: statusBar.message !== ""
25+
}
26+
27+
// TODO : Idea for later : implement a ProgressBar here
28+
29+
MaterialToolButton {
30+
id: statusBarButton
31+
Layout.fillHeight: true
32+
Layout.preferredWidth: 17
33+
visible: true
34+
font.pointSize: 8
35+
text: defaultIcon
36+
ToolTip.text: "Open Messages UI"
37+
// TODO : Open messages UI
38+
// onClicked: statusBar.showMessage("NotImplementedError : Cannot open interface", "error", 2000)
39+
enabled: false // TODO: to remove when implemented
40+
ToolTip.visible: false // TODO: to remove when implemented
41+
Component.onCompleted: {
42+
statusBarButton.contentItem.color = defaultColor
43+
}
44+
}
45+
46+
Timer {
47+
id: statusBarTimer
48+
interval: root.interval
49+
running: false
50+
repeat: false
51+
onTriggered: {
52+
// Erase message and reset button icon
53+
statusBar.message = ""
54+
statusBarField.color = defaultColor
55+
statusBarButton.contentItem.color = defaultColor
56+
statusBarButton.text = defaultIcon
57+
}
58+
}
59+
60+
QtObject {
61+
id: statusBar
62+
property string message: ""
63+
64+
function showMessage(msg, status=undefined, duration=root.interval) {
65+
var textColor = defaultColor
66+
var logLevel = "info"
67+
switch (status) {
68+
case "ok": {
69+
statusBarField.color = Colors.green
70+
statusBarButton.text = MaterialIcons.check_circle
71+
break
72+
}
73+
case "warning": {
74+
logLevel = "warn"
75+
statusBarField.color = Colors.orange
76+
statusBarButton.text = MaterialIcons.warning
77+
break
78+
}
79+
case "error": {
80+
logLevel = "error"
81+
statusBarField.color = Colors.red
82+
statusBarButton.text = MaterialIcons.error
83+
break
84+
}
85+
default: {
86+
statusBarButton.text = defaultIcon
87+
}
88+
}
89+
if (logMessage === true) {
90+
console.log("[Message][" + logLevel.toUpperCase().padEnd(5) + "] " + msg)
91+
}
92+
statusBarButton.contentItem.color = statusBarField.color
93+
statusBar.message = msg
94+
statusBarTimer.interval = duration
95+
statusBarTimer.restart()
96+
MeshroomApp.forceUIUpdate()
97+
}
98+
}
99+
100+
function showMessage(msg, status=undefined, duration=root.interval) {
101+
statusBar.showMessage(msg, status, duration)
102+
}
103+
104+
Connections {
105+
target: _messageController
106+
function onMessage(message, color, duration) {
107+
showMessage(message, color, duration)
108+
}
109+
}
110+
}

meshroom/ui/qml/Controls/qmldir

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ SelectionBox 1.0 SelectionBox.qml
2121
SelectionLine 1.0 SelectionLine.qml
2222
DelegateSelectionBox 1.0 DelegateSelectionBox.qml
2323
DelegateSelectionLine 1.0 DelegateSelectionLine.qml
24+
StatusBar 1.0 StatusBar.qml

meshroom/ui/reconstruction.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,9 @@ def __init__(self, undoStack: commands.UndoStack, taskManager: TaskManager, defa
386386
# react to internal graph changes to update those variables
387387
self.graphChanged.connect(self.onGraphChanged)
388388

389+
# Connect the pluginsReloaded signal to the onPluginsReloaded function
390+
self.pluginsReloaded.connect(self._onPluginsReloaded)
391+
389392
self.setDefaultPipeline(defaultPipeline)
390393

391394
def __del__(self):
@@ -432,6 +435,10 @@ def onCameraInitChanged(self):
432435

433436
@Slot()
434437
def reloadPlugins(self):
438+
""" Launch _reloadPlugins in a worker thread to avoid blocking the ui. """
439+
self._workerThreads.apply_async(func=self._reloadPlugins, args=())
440+
441+
def _reloadPlugins(self):
435442
"""
436443
Reload all the NodePlugins from all the registered plugins.
437444
The nodes in the graph will be updated to match the changes in the description, if
@@ -442,8 +449,12 @@ def reloadPlugins(self):
442449
for node in plugin.nodes.values():
443450
if node.reload():
444451
nodeTypes.append(node.nodeDescriptor.__name__)
452+
self.pluginsReloaded.emit(nodeTypes)
445453

454+
@Slot(list)
455+
def _onPluginsReloaded(self, nodeTypes: list):
446456
self._graph.reloadNodePlugins(nodeTypes)
457+
self.parent().showMessage("Plugins reloaded!", "ok")
447458

448459
@Slot()
449460
@Slot(str)
@@ -930,7 +941,9 @@ def setBuildingIntrinsics(self, value):
930941
displayedAttr2D = makeProperty(QObject, "_displayedAttr2D", displayedAttr2DChanged)
931942

932943
displayedAttrs3DChanged = Signal()
933-
displayedAttrs3D = Property(QObject, lambda self: self._displayedAttrs3D, notify=displayedAttrs3DChanged)
944+
displayedAttrs3D = Property(QObject, lambda self: self._displayedAttrs3D, notify=displayedAttrs3DChanged)
945+
946+
pluginsReloaded = Signal(list)
934947

935948
@Slot(QObject)
936949
def setActiveNode(self, node, categories=True, inputs=True):

0 commit comments

Comments
 (0)