mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-07-30 23:08:25 +02:00
[ui] first basic debugging UI + elementary commands
* nodes as a ListView (right click to delete a node) * editable attributes * undo/redo for adding/removing nodes and attribute edition
This commit is contained in:
parent
547e6c93b6
commit
48941a5782
6 changed files with 288 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -37,3 +37,4 @@ __pycache__
|
|||
.idea
|
||||
.cache
|
||||
|
||||
*.qmlc
|
||||
|
|
0
meshroom/ui/__init__.py
Normal file
0
meshroom/ui/__init__.py
Normal file
26
meshroom/ui/__main__.py
Normal file
26
meshroom/ui/__main__.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
import PySide2
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PySide2.QtGui import QGuiApplication
|
||||
from PySide2.QtQml import QQmlApplicationEngine
|
||||
|
||||
from meshroom.ui.reconstruction import Reconstruction
|
||||
|
||||
# TODO: remove this
|
||||
pysideDir = os.path.dirname(PySide2.__file__)
|
||||
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = pysideDir + '/plugins/platforms'
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QGuiApplication(sys.argv)
|
||||
url = os.path.join(os.path.dirname(__file__), "qml", "main.qml")
|
||||
engine = QQmlApplicationEngine()
|
||||
|
||||
r = Reconstruction()
|
||||
engine.rootContext().setContextProperty("_reconstruction", r)
|
||||
|
||||
engine.addImportPath(pysideDir + "/qml/") # TODO: remove this
|
||||
engine.load(os.path.normpath(url))
|
||||
app.exec_()
|
||||
|
97
meshroom/ui/commands.py
Normal file
97
meshroom/ui/commands.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
from PySide2 import QtWidgets
|
||||
from meshroom.processGraph.graph import Node
|
||||
|
||||
|
||||
class UndoCommand(QtWidgets.QUndoCommand):
|
||||
def __init__(self, parent=None):
|
||||
super(UndoCommand, self).__init__(parent)
|
||||
self._enabled = True
|
||||
|
||||
def setEnabled(self, enabled):
|
||||
self._enabled = enabled
|
||||
|
||||
def redo(self):
|
||||
if not self._enabled:
|
||||
return
|
||||
self.redoImpl()
|
||||
|
||||
def undo(self):
|
||||
if not self._enabled:
|
||||
return
|
||||
self.undoImpl()
|
||||
|
||||
def redoImpl(self) -> bool:
|
||||
pass
|
||||
|
||||
def undoImpl(self) -> bool:
|
||||
pass
|
||||
|
||||
|
||||
class UndoStack(QtWidgets.QUndoStack):
|
||||
def __init__(self, parent=None):
|
||||
super(UndoStack, self).__init__(parent)
|
||||
|
||||
def tryAndPush(self, command: UndoCommand):
|
||||
if command.redoImpl():
|
||||
command.setEnabled(False)
|
||||
self.push(command) # takes ownership
|
||||
command.setEnabled(True)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class GraphCommand(UndoCommand):
|
||||
def __init__(self, graph, parent=None):
|
||||
super(GraphCommand, self).__init__(parent)
|
||||
self.graph = graph
|
||||
|
||||
|
||||
class AddNodeCommand(GraphCommand):
|
||||
def __init__(self, graph, nodeType, parent=None):
|
||||
super(AddNodeCommand, self).__init__(graph, parent)
|
||||
self.nodeType = nodeType
|
||||
self.node = None
|
||||
|
||||
def redoImpl(self):
|
||||
self.node = self.graph.addNewNode(self.nodeType)
|
||||
self.setText("Add Node {}".format(self.node.getName()))
|
||||
return True
|
||||
|
||||
def undoImpl(self):
|
||||
self.graph.removeNode(self.node.getName())
|
||||
self.node = None
|
||||
|
||||
|
||||
class RemoveNodeCommand(GraphCommand):
|
||||
def __init__(self, graph, node, parent=None):
|
||||
super(RemoveNodeCommand, self).__init__(graph, parent)
|
||||
self.nodeDesc = node.toDict()
|
||||
self.nodeName = node.getName()
|
||||
self.setText("Remove Node {}".format(self.nodeName))
|
||||
|
||||
def redoImpl(self):
|
||||
self.graph.removeNode(self.nodeName)
|
||||
return True
|
||||
|
||||
def undoImpl(self):
|
||||
node = self.graph.addNode(Node(nodeDesc=self.nodeDesc["nodeType"],
|
||||
parent=self.graph, **self.nodeDesc["attributes"]
|
||||
), self.nodeName)
|
||||
assert (node.getName() == self.nodeName)
|
||||
|
||||
|
||||
class SetAttributeCommand(GraphCommand):
|
||||
def __init__(self, graph, attribute, value, parent=None):
|
||||
super(SetAttributeCommand, self).__init__(graph, parent)
|
||||
self.nodeName = attribute.node.getName()
|
||||
self.attrName = attribute.getName()
|
||||
self.value = value
|
||||
self.oldValue = attribute.getValue()
|
||||
|
||||
def redoImpl(self):
|
||||
self.graph.node(self.nodeName).attribute(self.attrName).setValue(self.value)
|
||||
return True
|
||||
|
||||
def undoImpl(self):
|
||||
self.graph.node(self.nodeName).attribute(self.attrName).setValue(self.oldValue)
|
132
meshroom/ui/qml/main.qml
Normal file
132
meshroom/ui/qml/main.qml
Normal file
|
@ -0,0 +1,132 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4 // as Controls1
|
||||
//import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.1
|
||||
|
||||
ApplicationWindow {
|
||||
id: _window
|
||||
|
||||
width: 1280
|
||||
height: 720
|
||||
visible: true
|
||||
title: "Meshroom"
|
||||
color: "#fafafa"
|
||||
|
||||
property variant node: null
|
||||
|
||||
Connections {
|
||||
target: _reconstruction.undoStack
|
||||
onIndexChanged: graphStr.update()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 4
|
||||
Row
|
||||
{
|
||||
Layout.fillWidth: true
|
||||
TextField {
|
||||
id: filepath
|
||||
width: 200
|
||||
}
|
||||
Button {
|
||||
text: "Load"
|
||||
onClicked: _reconstruction.graph.load(filepath.text)
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Add Node"
|
||||
onClicked: {
|
||||
_reconstruction.addNode("FeatureExtraction")
|
||||
}
|
||||
}
|
||||
|
||||
Item {width: 4; height: 1}
|
||||
Button {
|
||||
text: "Undo"
|
||||
activeFocusOnPress: true
|
||||
onClicked: {
|
||||
_reconstruction.undoStack.undo()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Redo"
|
||||
activeFocusOnPress: true
|
||||
onClicked: {
|
||||
_reconstruction.undoStack.redo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout{
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
ListView {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: 150
|
||||
model: _reconstruction.graph.nodes
|
||||
onCountChanged: {
|
||||
graphStr.update()
|
||||
}
|
||||
spacing: 2
|
||||
delegate: Rectangle {
|
||||
width: 130
|
||||
height: 40
|
||||
Label {
|
||||
text: object.name
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.AllButtons
|
||||
onClicked: {
|
||||
if(mouse.button == Qt.RightButton)
|
||||
_reconstruction.removeNode(object)
|
||||
else
|
||||
_window.node = object
|
||||
}
|
||||
|
||||
}
|
||||
color: "#81d4fa"
|
||||
border.color: _window.node == object ? Qt.darker(color) : "transparent"
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: attributesListView
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
model: _window.node != null ? _window.node.attributes : null
|
||||
delegate: RowLayout {
|
||||
width: attributesListView.width
|
||||
spacing: 4
|
||||
Label {
|
||||
text: object.label
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
Layout.preferredWidth: 200
|
||||
}
|
||||
TextField {
|
||||
text: object.value
|
||||
Layout.fillWidth: true
|
||||
onEditingFinished: _reconstruction.setAttribute(object, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TextArea {
|
||||
id: graphStr
|
||||
Layout.preferredWidth: 400
|
||||
Layout.fillHeight: true
|
||||
wrapMode: TextEdit.WrapAnywhere
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
function update() {
|
||||
graphStr.text = _reconstruction.graph.asString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
32
meshroom/ui/reconstruction.py
Normal file
32
meshroom/ui/reconstruction.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
from PySide2 import QtCore
|
||||
|
||||
from meshroom.processGraph import graph
|
||||
from meshroom.ui import commands
|
||||
|
||||
|
||||
class Reconstruction(QtCore.QObject):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(Reconstruction, self).__init__(parent)
|
||||
self._graph = graph.Graph("")
|
||||
self._undoStack = commands.UndoStack(self)
|
||||
|
||||
@QtCore.Slot(str)
|
||||
def addNode(self, nodeType):
|
||||
self._undoStack.tryAndPush(commands.AddNodeCommand(self._graph, nodeType))
|
||||
|
||||
@QtCore.Slot(graph.Node)
|
||||
def removeNode(self, node):
|
||||
self._undoStack.tryAndPush(commands.RemoveNodeCommand(self._graph, node))
|
||||
|
||||
@QtCore.Slot(graph.Attribute, "QVariant")
|
||||
def setAttribute(self, attribute, value):
|
||||
self._undoStack.tryAndPush(commands.SetAttributeCommand(self._graph, attribute, value))
|
||||
|
||||
@QtCore.Slot(str)
|
||||
def load(self, filepath):
|
||||
self._graph.load(filepath)
|
||||
|
||||
undoStack = QtCore.Property(QtCore.QObject, lambda self: self._undoStack, constant=True)
|
||||
graph = QtCore.Property(QtCore.QObject, lambda self: self._graph, constant=True)
|
||||
nodes = QtCore.Property(QtCore.QObject, lambda self: self._graph.nodes, constant=True)
|
Loading…
Add table
Add a link
Reference in a new issue