mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-07-23 19:47:39 +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
|
.idea
|
||||||
.cache
|
.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