[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:
Yann Lanthony 2017-09-25 20:18:27 +02:00
parent 547e6c93b6
commit 48941a5782
6 changed files with 288 additions and 0 deletions

1
.gitignore vendored
View file

@ -37,3 +37,4 @@ __pycache__
.idea .idea
.cache .cache
*.qmlc

0
meshroom/ui/__init__.py Normal file
View file

26
meshroom/ui/__main__.py Normal file
View 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
View 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
View 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()
}
}
}
}
}

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