Skip to content

Commit 2420169

Browse files
committed
[WIP] For Loop with IterationNodes process
1 parent 123ef97 commit 2420169

File tree

8 files changed

+179
-56
lines changed

8 files changed

+179
-56
lines changed

meshroom/core/graph.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,6 @@ def addEdge(self, srcAttr, dstAttr):
899899
dstAttr.valueChanged.emit()
900900
dstAttr.isLinkChanged.emit()
901901
srcAttr.hasOutputConnectionsChanged.emit()
902-
dstAttr.node.countForLoopChanged.emit()
903902
return edge
904903

905904
def addEdges(self, *edges):
@@ -916,7 +915,6 @@ def removeEdge(self, dstAttr):
916915
dstAttr.valueChanged.emit()
917916
dstAttr.isLinkChanged.emit()
918917
edge.src.hasOutputConnectionsChanged.emit()
919-
dstAttr.node.countForLoopChanged.emit()
920918

921919
def getDepth(self, node, minimal=False):
922920
""" Return node's depth in this Graph.
@@ -1487,7 +1485,6 @@ def markNodesDirty(self, fromNode):
14871485
nodes, edges = self.dfsOnDiscover(startNodes=[fromNode], reverse=True)
14881486
for node in nodes:
14891487
node.dirty = True
1490-
node.countForLoopChanged.emit()
14911488

14921489
def stopExecution(self):
14931490
""" Request graph execution to be stopped by terminating running chunks"""

meshroom/core/node.py

Lines changed: 101 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,65 @@ class ExecMode(Enum):
5858
LOCAL = 1
5959
EXTERN = 2
6060

61+
class ForLoopData(BaseObject):
62+
"""
63+
"""
64+
65+
def __init__(self, parentNode=None, connectedAttribute=None, parent=None):
66+
super(ForLoopData, self).__init__(parent)
67+
self._countForLoop = 0
68+
self._iterations = ListModel(parent=self) # list of nodes for each iteration
69+
self._parentNode = parentNode # parent node
70+
self.connectedAttribute = connectedAttribute # attribute connected to the ForLoop node from parent node
71+
72+
def update(self, currentNode=None):
73+
# set the connectedAttribute
74+
forLoopAttribute = None
75+
if currentNode is not None:
76+
for attr in currentNode._attributes:
77+
if attr.isInput and attr.isLink:
78+
forLoopAttribute = currentNode._attributes.indexOf(attr)
79+
srcAttr = attr.getLinkParam()
80+
# If the srcAttr is a ListAttribute, it means that the node is in a ForLoop
81+
if isinstance(srcAttr.root, ListAttribute) and srcAttr.type == attr.type:
82+
self.connectedAttribute = srcAttr.root
83+
self._parentNode = srcAttr.root.node
84+
break
85+
86+
# set the countForLoop
87+
if self.connectedAttribute is not None:
88+
self._countForLoop = self._parentNode._forLoopData._countForLoop + 1
89+
if self.connectedAttribute.isInput:
90+
self._countForLoop -= 1 if self._countForLoop > 1 else 1
91+
92+
# 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
93+
for i in range(len(self.connectedAttribute.value)):
94+
# name of the iteration node
95+
name = "{}_{}".format(currentNode.name, i)
96+
# check if node already exists
97+
if name not in [n.name for n in self._parentNode.graph.nodes]:
98+
iterationNode = IterationNode(currentNode, i, forLoopAttribute)
99+
self._parentNode.graph.addNode(iterationNode, iterationNode.name)
100+
else :
101+
# find node by name
102+
iterationNode = self._parentNode.graph.node(name)
103+
iterationNode._updateChunks()
104+
105+
self._iterations.append(iterationNode)
106+
107+
print("parent internal folder: ", currentNode.internalFolder)
108+
self.parentNodeChanged.emit()
109+
self.iterationsChanged.emit()
110+
self.countForLoopChanged.emit()
111+
112+
countForLoopChanged = Signal()
113+
countForLoop = Property(int, lambda self: self._countForLoop, notify=countForLoopChanged)
114+
iterationsChanged = Signal()
115+
iterations = Property(Variant, lambda self: self._iterations, notify=iterationsChanged)
116+
parentNodeChanged = Signal()
117+
parentNode = Property(Variant, lambda self: self._parentNode, notify=parentNodeChanged)
118+
119+
61120

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

517577
self.globalStatusChanged.connect(self.updateDuplicatesStatusAndLocked)
518578

@@ -979,6 +1039,10 @@ def updateInternals(self, cacheDir=None):
9791039
}
9801040
self._computeUids()
9811041
self._buildCmdVars()
1042+
1043+
# try to update for loopdata as node is created
1044+
self._forLoopData.update(self)
1045+
9821046
if self.nodeDesc:
9831047
self.nodeDesc.postUpdate(self)
9841048
# Notify internal folder change if needed
@@ -1321,23 +1385,11 @@ def has3DOutputAttribute(self):
13211385
return True
13221386
return False
13231387

1324-
def _countForLoop(self):
1388+
def getForLoopData(self):
13251389
"""
1326-
Return in how many ForLoop nodes this node is.
1390+
Return the ForLoopData of the node.
13271391
"""
1328-
count = 0
1329-
# Access to the input attributes of the node
1330-
for attr in self._attributes:
1331-
if attr.isInput and attr.isLink:
1332-
# Access to the attribute connected to the input attribute
1333-
srcAttr = attr.getLinkParam()
1334-
# If the srcAttr is a ListAttribute, it means that the node is in a ForLoop
1335-
if isinstance(srcAttr.root, ListAttribute) and srcAttr.type == attr.type:
1336-
# Access the countForLoop of the node of the ListAttribute
1337-
count = srcAttr.root.node.countForLoop + 1
1338-
if srcAttr.root.isInput:
1339-
count = count - 1 if count > 1 else 1
1340-
return count
1392+
return self._forLoopData
13411393

13421394

13431395

@@ -1392,8 +1444,8 @@ def _countForLoop(self):
13921444
hasSequenceOutput = Property(bool, hasSequenceOutputAttribute, notify=outputAttrEnabledChanged)
13931445
has3DOutput = Property(bool, has3DOutputAttribute, notify=outputAttrEnabledChanged)
13941446

1395-
countForLoopChanged = Signal()
1396-
countForLoop = Property(int, _countForLoop, notify=countForLoopChanged)
1447+
forLoopDataChanged = Signal()
1448+
forLoopData = Property(Variant, getForLoopData, notify=forLoopDataChanged)
13971449

13981450
class Node(BaseNode):
13991451
"""
@@ -1552,7 +1604,39 @@ def _updateChunks(self):
15521604
else:
15531605
self._chunks[0].range = desc.Range()
15541606

1607+
class IterationNode(Node):
1608+
"""
1609+
A node that is not added to the graph but used to process a specific iteration of a ForLoop node.
1610+
"""
1611+
def __init__(self, node, iteration, attributeIndex):
1612+
super(IterationNode, self).__init__(node.nodeType, parent=node.graph)
1613+
self._name = f"{node.name}_{iteration}"
1614+
self._originalNode = node
1615+
1616+
# By recognizing the connected attribute linked to the ForLoop node, we can set the value of the iteration
1617+
# attribute to the current iteration value
1618+
self._attributes.at(attributeIndex).value = node._forLoopData.connectedAttribute.at(iteration).value
1619+
1620+
print("Attributes of IterationNode: ", [attr.value for attr in self._attributes.values()])
1621+
1622+
# Internal folder should correspond to each possibility of uid
1623+
# print(self._uids)
1624+
# self._buildCmdVars()
1625+
# self._computeUids()
1626+
# print("after: ", self._uids)
1627+
print(self.nodeType)
1628+
print(self._cmdVars)
1629+
print(self._internalFolder)
15551630

1631+
def _updateChunks(self):
1632+
# find node in graph
1633+
node = self.graph.node(self._originalNode.name)
1634+
# Setting chunks to the same chunks as the parent node
1635+
self._chunks.setObjectList([NodeChunk(self, desc.Range()) for _ in node._chunks])
1636+
for c in self._chunks:
1637+
c.statusChanged.connect(self.globalStatusChanged)
1638+
1639+
self.chunksChanged.emit()
15561640
class CompatibilityIssue(Enum):
15571641
"""
15581642
Enum describing compatibility issues when deserializing a Node.

meshroom/ui/graph.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from meshroom.core.taskManager import TaskManager
1919

20-
from meshroom.core.node import NodeChunk, Node, Status, ExecMode, CompatibilityNode, Position
20+
from meshroom.core.node import NodeChunk, Node, Status, ExecMode, CompatibilityNode, Position, IterationNode
2121
from meshroom.core import submitters
2222
from meshroom.ui import commands
2323
from meshroom.ui.utils import makeProperty
@@ -634,7 +634,10 @@ def addNewNode(self, nodeType, position=None, **kwargs):
634634
"""
635635
if isinstance(position, QPoint):
636636
position = Position(position.x(), position.y())
637-
return self.push(commands.AddNodeCommand(self._graph, nodeType, position=position, **kwargs))
637+
638+
node = self.push(commands.AddNodeCommand(self._graph, nodeType, position=position, **kwargs))
639+
self.nodesChanged.emit()
640+
return node
638641

639642
def filterNodes(self, nodes):
640643
"""Filter out the nodes that do not exist on the graph."""
@@ -776,7 +779,6 @@ def expandForLoop(self, currentEdge):
776779
newNode = duplicates[0]
777780
previousEdge = self.graph.edge(newNode.attribute(dst.name))
778781
self.replaceEdge(previousEdge, listAttribute.at(i), previousEdge.dst)
779-
newNode.countForLoopChanged.emit()
780782

781783
# Last, replace the edge with the first element of the list
782784
return self.replaceEdge(currentEdge, listAttribute.at(0), dst)
@@ -819,6 +821,7 @@ def clearDataFrom(self, nodes):
819821
def addEdge(self, src, dst):
820822
if isinstance(src, ListAttribute) and not isinstance(dst, ListAttribute):
821823
self._addEdge(src.at(0), dst)
824+
self.nodesChanged.emit()
822825
elif isinstance(dst, ListAttribute) and not isinstance(src, ListAttribute):
823826
with self.groupedGraphModification("Insert and Add Edge on {}".format(dst.getFullNameToNode())):
824827
self.appendAttribute(dst)
@@ -1120,12 +1123,27 @@ def pasteNodes(self, clipboardContent, position=None, centerPosition=False):
11201123
positions.append(finalPosition)
11211124

11221125
return self.push(commands.PasteNodesCommand(self.graph, d, position=positions))
1126+
1127+
def getNodes(self):
1128+
"""
1129+
Return all the nodes that are not Iteration nodes.
1130+
"""
1131+
nodes = self._graph.nodes
1132+
toRemove = []
1133+
for node in nodes.values():
1134+
if isinstance(node, IterationNode):
1135+
toRemove.append(node)
1136+
for node in toRemove:
1137+
nodes.pop(node.name)
1138+
return nodes
1139+
11231140

11241141
undoStack = Property(QObject, lambda self: self._undoStack, constant=True)
11251142
graphChanged = Signal()
11261143
graph = Property(Graph, lambda self: self._graph, notify=graphChanged)
11271144
taskManager = Property(TaskManager, lambda self: self._taskManager, constant=True)
1128-
nodes = Property(QObject, lambda self: self._graph.nodes, notify=graphChanged)
1145+
nodesChanged = Signal()
1146+
nodes = Property(QObject, getNodes, notify=nodesChanged)
11291147
layout = Property(GraphLayout, lambda self: self._layout, constant=True)
11301148

11311149
computeStatusChanged = Signal()

meshroom/ui/qml/GraphEditor/GraphEditor.qml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1085,7 +1085,7 @@ Item {
10851085
Repeater {
10861086
id: filteredNodes
10871087
model: SortFilterDelegateModel {
1088-
model: root.graph ? root.graph.nodes : undefined
1088+
model: root.uigraph ? root.uigraph.nodes : undefined
10891089
sortRole: "label"
10901090
filters: [{role: "label", value: graphSearchBar.text}]
10911091
delegate: Item {

meshroom/ui/qml/GraphEditor/Node.qml

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -278,12 +278,12 @@ Item {
278278

279279
// Is in for loop indicator
280280
MaterialLabel {
281-
visible: node.countForLoop > 0
281+
visible: node.forLoopData.countForLoop > 0
282282
text: MaterialIcons.loop
283283
padding: 2
284284
font.pointSize: 7
285285
palette.text: Colors.sysPalette.text
286-
ToolTip.text: "Is in " + node.countForLoop + " for loop(s)"
286+
ToolTip.text: "Is in " + node.forLoopData.countForLoop + " for loop(s)"
287287
}
288288

289289
// Submitted externally indicator
@@ -386,14 +386,15 @@ Item {
386386
// so if the count is 0 we display only one iteration
387387
// else we display the number of iterations
388388
model: {
389-
if (node.countForLoop === 0)
390-
return 1
391-
392-
for (let i = 0; i < node.attributes.count; ++i) {
393-
if (node.attributes.at(i).isLink) {
394-
var srcAttr = node.attributes.at(i).linkParam
395-
return srcAttr.root.value.count
389+
if (node.forLoopData.countForLoop === 0) {
390+
return node
391+
} else {
392+
// convert the iterations to a list
393+
let list = []
394+
for (let i = 0; i < node.forLoopData.iterations.count; ++i) {
395+
list.push(node.forLoopData.iterations.at(i))
396396
}
397+
return list
397398
}
398399
}
399400

@@ -402,7 +403,9 @@ Item {
402403
defaultColor: Colors.sysPalette.mid
403404
height: 3
404405
width: parent.width
405-
model: node ? node.chunks : undefined
406+
model: {
407+
return modelData.chunks
408+
}
406409

407410
Rectangle {
408411
anchors.fill: parent

meshroom/ui/qml/GraphEditor/NodeEditor.qml

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -258,29 +258,40 @@ Panel {
258258
anchors.fill: parent
259259

260260
// The list of iterations
261-
NodeEditorElementsListView {
262-
id: iterationsLV
263-
visible: root.node.countForLoop > 0
264-
elements: {
265-
if (root.node.countForLoop == 0)
266-
return []
267-
var elements = []
268-
for (let i = 0; i < node.attributes.count; ++i) {
269-
if (node.attributes.at(i).isLink) {
270-
var srcAttr = node.attributes.at(i).linkParam
271-
for (let j = 0; j < srcAttr.root.value.count; ++j) {
272-
elements.push(j)
273-
}
274-
return elements
275-
}
261+
262+
Repeater {
263+
id: iterationsRepeater
264+
visible: root.node.forLoopData.countForLoop > 0
265+
266+
model: {
267+
let currentNode = root.node
268+
let count = root.node.forLoopData.countForLoop
269+
let list = []
270+
for (let i = 0; i < count; i++) {
271+
let parent = currentNode.forLoopData.parentNode
272+
list.push(currentNode.forLoopData.iterations)
273+
currentNode = parent
276274
}
275+
276+
// reverse the list
277+
list.reverse()
278+
return list
277279
}
278280

279-
// TODO to remove when the elements would be correct
280-
currentElement: elements[0]
281-
282-
isChunk: false
283-
title: "Iterations"
281+
NodeEditorElementsListView {
282+
id: iterationsLV
283+
elements: {
284+
if (root.node.forLoopData.countForLoop == 0)
285+
return []
286+
return modelData
287+
}
288+
289+
// TODO to remove when the elements would be correct
290+
// currentElement: elements[0]
291+
292+
isChunk: false
293+
title: "Iterations"
294+
}
284295
}
285296

286297
// The list of chunks

meshroom/ui/qml/GraphEditor/NodeEditorElementsListView.qml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,13 @@ ColumnLayout {
9292
Rectangle {
9393
width: 4
9494
height: parent.height
95-
color: isChunk ? Common.getChunkColor(parent.element) : palette.mid
95+
color: {
96+
if (isChunk) {
97+
return Common.getChunkColor(parent.element)
98+
} else {
99+
return Common.getNodeColor(parent.element)
100+
}
101+
}
96102
}
97103
}
98104
}

meshroom/ui/qml/GraphEditor/common.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,7 @@ function getChunkColor(chunk, overrides) {
2525
console.warn("Unknown status : " + chunk.status)
2626
return "magenta"
2727
}
28+
29+
function getNodeColor(node) {
30+
return statusColors[node.globalStatus] || "magenta"
31+
}

0 commit comments

Comments
 (0)