Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better UI for for loop concept #2510

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions meshroom/core/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,65 @@ class ExecMode(Enum):
LOCAL = 1
EXTERN = 2

class ForLoopData(BaseObject):
"""
"""

def __init__(self, parentNode=None, connectedAttribute=None, parent=None):
super(ForLoopData, self).__init__(parent)
self._countForLoop = 0
self._iterations = ListModel(parent=self) # list of nodes for each iteration
self._parentNode = parentNode # parent node
self.connectedAttribute = connectedAttribute # attribute connected to the ForLoop node from parent node

def update(self, currentNode=None):
# set the connectedAttribute
forLoopAttribute = None
if currentNode is not None:
for attr in currentNode._attributes:
if attr.isInput and attr.isLink:
forLoopAttribute = currentNode._attributes.indexOf(attr)
srcAttr = attr.getLinkParam()
# If the srcAttr is a ListAttribute, it means that the node is in a ForLoop
if isinstance(srcAttr.root, ListAttribute) and srcAttr.type == attr.type:
self.connectedAttribute = srcAttr.root
self._parentNode = srcAttr.root.node
break

# set the countForLoop
if self.connectedAttribute is not None:
self._countForLoop = self._parentNode._forLoopData._countForLoop + 1
if self.connectedAttribute.isInput:
self._countForLoop -= 1 if self._countForLoop > 1 else 1

# set the iterations by creating iteration nodes for each connected attribute value and will add them to the core graph and not the ui one
for i in range(len(self.connectedAttribute.value)):
# name of the iteration node
name = "{}_{}".format(currentNode.name, i)
# check if node already exists
if name not in [n.name for n in self._parentNode.graph.nodes]:
iterationNode = IterationNode(currentNode, i, forLoopAttribute)
self._parentNode.graph.addNode(iterationNode, iterationNode.name)
else :
# find node by name
iterationNode = self._parentNode.graph.node(name)
iterationNode._updateChunks()

self._iterations.append(iterationNode)

print("parent internal folder: ", currentNode.internalFolder)
self.parentNodeChanged.emit()
self.iterationsChanged.emit()
self.countForLoopChanged.emit()

countForLoopChanged = Signal()
countForLoop = Property(int, lambda self: self._countForLoop, notify=countForLoopChanged)
iterationsChanged = Signal()
iterations = Property(Variant, lambda self: self._iterations, notify=iterationsChanged)
parentNodeChanged = Signal()
parentNode = Property(Variant, lambda self: self._parentNode, notify=parentNodeChanged)



class StatusData(BaseObject):
"""
Expand Down Expand Up @@ -513,6 +572,7 @@ def __init__(self, nodeType, position=None, parent=None, uids=None, **kwargs):
self._locked = False
self._duplicates = ListModel(parent=self) # list of nodes with the same uid
self._hasDuplicates = False
self._forLoopData = ForLoopData()

self.globalStatusChanged.connect(self.updateDuplicatesStatusAndLocked)

Expand Down Expand Up @@ -979,6 +1039,10 @@ def updateInternals(self, cacheDir=None):
}
self._computeUids()
self._buildCmdVars()

# try to update for loopdata as node is created
self._forLoopData.update(self)

if self.nodeDesc:
self.nodeDesc.postUpdate(self)
# Notify internal folder change if needed
Expand Down Expand Up @@ -1320,6 +1384,13 @@ def has3DOutputAttribute(self):
if attr.enabled and attr.isOutput and hasSupportedExt:
return True
return False

def getForLoopData(self):
"""
Return the ForLoopData of the node.
"""
return self._forLoopData



name = Property(str, getName, constant=True)
Expand Down Expand Up @@ -1373,6 +1444,9 @@ def has3DOutputAttribute(self):
hasSequenceOutput = Property(bool, hasSequenceOutputAttribute, notify=outputAttrEnabledChanged)
has3DOutput = Property(bool, has3DOutputAttribute, notify=outputAttrEnabledChanged)

forLoopDataChanged = Signal()
forLoopData = Property(Variant, getForLoopData, notify=forLoopDataChanged)

class Node(BaseNode):
"""
A standard Graph node based on a node type.
Expand Down Expand Up @@ -1530,7 +1604,39 @@ def _updateChunks(self):
else:
self._chunks[0].range = desc.Range()

class IterationNode(Node):
"""
A node that is not added to the graph but used to process a specific iteration of a ForLoop node.
"""
def __init__(self, node, iteration, attributeIndex):
super(IterationNode, self).__init__(node.nodeType, parent=node.graph)
self._name = f"{node.name}_{iteration}"
self._originalNode = node

# By recognizing the connected attribute linked to the ForLoop node, we can set the value of the iteration
# attribute to the current iteration value
self._attributes.at(attributeIndex).value = node._forLoopData.connectedAttribute.at(iteration).value

print("Attributes of IterationNode: ", [attr.value for attr in self._attributes.values()])

# Internal folder should correspond to each possibility of uid
# print(self._uids)
# self._buildCmdVars()
# self._computeUids()
# print("after: ", self._uids)
print(self.nodeType)
print(self._cmdVars)
print(self._internalFolder)

def _updateChunks(self):
# find node in graph
node = self.graph.node(self._originalNode.name)
# Setting chunks to the same chunks as the parent node
self._chunks.setObjectList([NodeChunk(self, desc.Range()) for _ in node._chunks])
for c in self._chunks:
c.statusChanged.connect(self.globalStatusChanged)

self.chunksChanged.emit()
class CompatibilityIssue(Enum):
"""
Enum describing compatibility issues when deserializing a Node.
Expand Down
25 changes: 22 additions & 3 deletions meshroom/ui/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from meshroom.core.taskManager import TaskManager

from meshroom.core.node import NodeChunk, Node, Status, ExecMode, CompatibilityNode, Position
from meshroom.core.node import NodeChunk, Node, Status, ExecMode, CompatibilityNode, Position, IterationNode
from meshroom.core import submitters
from meshroom.ui import commands
from meshroom.ui.utils import makeProperty
Expand Down Expand Up @@ -634,7 +634,10 @@ def addNewNode(self, nodeType, position=None, **kwargs):
"""
if isinstance(position, QPoint):
position = Position(position.x(), position.y())
return self.push(commands.AddNodeCommand(self._graph, nodeType, position=position, **kwargs))

node = self.push(commands.AddNodeCommand(self._graph, nodeType, position=position, **kwargs))
self.nodesChanged.emit()
return node

def filterNodes(self, nodes):
"""Filter out the nodes that do not exist on the graph."""
Expand Down Expand Up @@ -818,6 +821,7 @@ def clearDataFrom(self, nodes):
def addEdge(self, src, dst):
if isinstance(src, ListAttribute) and not isinstance(dst, ListAttribute):
self._addEdge(src.at(0), dst)
self.nodesChanged.emit()
elif isinstance(dst, ListAttribute) and not isinstance(src, ListAttribute):
with self.groupedGraphModification("Insert and Add Edge on {}".format(dst.getFullNameToNode())):
self.appendAttribute(dst)
Expand Down Expand Up @@ -1119,12 +1123,27 @@ def pasteNodes(self, clipboardContent, position=None, centerPosition=False):
positions.append(finalPosition)

return self.push(commands.PasteNodesCommand(self.graph, d, position=positions))

def getNodes(self):
"""
Return all the nodes that are not Iteration nodes.
"""
nodes = self._graph.nodes
toRemove = []
for node in nodes.values():
if isinstance(node, IterationNode):
toRemove.append(node)
for node in toRemove:
nodes.pop(node.name)
return nodes


undoStack = Property(QObject, lambda self: self._undoStack, constant=True)
graphChanged = Signal()
graph = Property(Graph, lambda self: self._graph, notify=graphChanged)
taskManager = Property(TaskManager, lambda self: self._taskManager, constant=True)
nodes = Property(QObject, lambda self: self._graph.nodes, notify=graphChanged)
nodesChanged = Signal()
nodes = Property(QObject, getNodes, notify=nodesChanged)
layout = Property(GraphLayout, lambda self: self._layout, constant=True)

computeStatusChanged = Signal()
Expand Down
2 changes: 1 addition & 1 deletion meshroom/ui/qml/GraphEditor/GraphEditor.qml
Original file line number Diff line number Diff line change
Expand Up @@ -1085,7 +1085,7 @@ Item {
Repeater {
id: filteredNodes
model: SortFilterDelegateModel {
model: root.graph ? root.graph.nodes : undefined
model: root.uigraph ? root.uigraph.nodes : undefined
sortRole: "label"
filters: [{role: "label", value: graphSearchBar.text}]
delegate: Item {
Expand Down
61 changes: 48 additions & 13 deletions meshroom/ui/qml/GraphEditor/Node.qml
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,16 @@ Item {
}
}

// Is in for loop indicator
MaterialLabel {
visible: node.forLoopData.countForLoop > 0
text: MaterialIcons.loop
padding: 2
font.pointSize: 7
palette.text: Colors.sysPalette.text
ToolTip.text: "Is in " + node.forLoopData.countForLoop + " for loop(s)"
}

// Submitted externally indicator
MaterialLabel {
visible: ["SUBMITTED", "RUNNING"].includes(node.globalStatus) && node.chunks.count > 0 && node.isExternal
Expand Down Expand Up @@ -367,19 +377,44 @@ Item {
}

// Node Chunks
NodeChunks {
visible: node.isComputable
defaultColor: Colors.sysPalette.mid
implicitHeight: 3
width: parent.width
model: node ? node.chunks : undefined

Rectangle {
anchors.fill: parent
color: Colors.sysPalette.mid
z: -1
}
}
Column {
width: parent.width

spacing: 2
Repeater {
// the model is the number of iterations for the for loop
// so if the count is 0 we display only one iteration
// else we display the number of iterations
model: {
if (node.forLoopData.countForLoop === 0) {
return node
} else {
// convert the iterations to a list
let list = []
for (let i = 0; i < node.forLoopData.iterations.count; ++i) {
list.push(node.forLoopData.iterations.at(i))
}
return list
}
}

delegate: NodeChunks {
visible: node.isComputable
defaultColor: Colors.sysPalette.mid
height: 3
width: parent.width
model: {
return modelData.chunks
}

Rectangle {
anchors.fill: parent
color: Colors.sysPalette.mid
z: -1
}
}
}
}

// Vertical Spacer
Item { width: parent.width; height: 2 }
Expand Down
47 changes: 42 additions & 5 deletions meshroom/ui/qml/GraphEditor/NodeEditor.qml
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,48 @@ Panel {
Controls1.SplitView {
anchors.fill: parent

// The list of iterations

Repeater {
id: iterationsRepeater
visible: root.node.forLoopData.countForLoop > 0

model: {
let currentNode = root.node
let count = root.node.forLoopData.countForLoop
let list = []
for (let i = 0; i < count; i++) {
let parent = currentNode.forLoopData.parentNode
list.push(currentNode.forLoopData.iterations)
currentNode = parent
}

// reverse the list
list.reverse()
return list
}

NodeEditorElementsListView {
id: iterationsLV
elements: {
if (root.node.forLoopData.countForLoop == 0)
return []
return modelData
}

// TODO to remove when the elements would be correct
// currentElement: elements[0]

isChunk: false
title: "Iterations"
}
}

// The list of chunks
ChunksListView {
NodeEditorElementsListView {
id: chunksLV
visible: (tabBar.currentIndex >= 1 && tabBar.currentIndex <= 3)
chunks: root.node.chunks
elements: root.node.chunks
}

StackLayout {
Expand Down Expand Up @@ -295,7 +332,7 @@ Panel {
id: nodeLog
node: root.node
currentChunkIndex: chunksLV.currentIndex
currentChunk: chunksLV.currentChunk
currentChunk: chunksLV.currentElement
}
}

Expand All @@ -310,7 +347,7 @@ Panel {
Layout.fillWidth: true
node: root.node
currentChunkIndex: chunksLV.currentIndex
currentChunk: chunksLV.currentChunk
currentChunk: chunksLV.currentElement
}
}

Expand All @@ -325,7 +362,7 @@ Panel {
Layout.fillWidth: true
node: root.node
currentChunkIndex: chunksLV.currentIndex
currentChunk: chunksLV.currentChunk
currentChunk: chunksLV.currentElement
}
}

Expand Down
Loading
Loading