Skip to content

Commit

Permalink
[WIP] For Loop with IterationNodes process
Browse files Browse the repository at this point in the history
  • Loading branch information
Just-Kiel committed Sep 12, 2024
1 parent 123ef97 commit 2420169
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 56 deletions.
3 changes: 0 additions & 3 deletions meshroom/core/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,6 @@ def addEdge(self, srcAttr, dstAttr):
dstAttr.valueChanged.emit()
dstAttr.isLinkChanged.emit()
srcAttr.hasOutputConnectionsChanged.emit()
dstAttr.node.countForLoopChanged.emit()
return edge

def addEdges(self, *edges):
Expand All @@ -916,7 +915,6 @@ def removeEdge(self, dstAttr):
dstAttr.valueChanged.emit()
dstAttr.isLinkChanged.emit()
edge.src.hasOutputConnectionsChanged.emit()
dstAttr.node.countForLoopChanged.emit()

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

def stopExecution(self):
""" Request graph execution to be stopped by terminating running chunks"""
Expand Down
118 changes: 101 additions & 17 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 @@ -1321,23 +1385,11 @@ def has3DOutputAttribute(self):
return True
return False

def _countForLoop(self):
def getForLoopData(self):
"""
Return in how many ForLoop nodes this node is.
Return the ForLoopData of the node.
"""
count = 0
# Access to the input attributes of the node
for attr in self._attributes:
if attr.isInput and attr.isLink:
# Access to the attribute connected to the input attribute
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:
# Access the countForLoop of the node of the ListAttribute
count = srcAttr.root.node.countForLoop + 1
if srcAttr.root.isInput:
count = count - 1 if count > 1 else 1
return count
return self._forLoopData



Expand Down Expand Up @@ -1392,8 +1444,8 @@ def _countForLoop(self):
hasSequenceOutput = Property(bool, hasSequenceOutputAttribute, notify=outputAttrEnabledChanged)
has3DOutput = Property(bool, has3DOutputAttribute, notify=outputAttrEnabledChanged)

countForLoopChanged = Signal()
countForLoop = Property(int, _countForLoop, notify=countForLoopChanged)
forLoopDataChanged = Signal()
forLoopData = Property(Variant, getForLoopData, notify=forLoopDataChanged)

class Node(BaseNode):
"""
Expand Down Expand Up @@ -1552,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
26 changes: 22 additions & 4 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 @@ -776,7 +779,6 @@ def expandForLoop(self, currentEdge):
newNode = duplicates[0]
previousEdge = self.graph.edge(newNode.attribute(dst.name))
self.replaceEdge(previousEdge, listAttribute.at(i), previousEdge.dst)
newNode.countForLoopChanged.emit()

# Last, replace the edge with the first element of the list
return self.replaceEdge(currentEdge, listAttribute.at(0), dst)
Expand Down Expand Up @@ -819,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 @@ -1120,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
23 changes: 13 additions & 10 deletions meshroom/ui/qml/GraphEditor/Node.qml
Original file line number Diff line number Diff line change
Expand Up @@ -278,12 +278,12 @@ Item {

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

// Submitted externally indicator
Expand Down Expand Up @@ -386,14 +386,15 @@ Item {
// so if the count is 0 we display only one iteration
// else we display the number of iterations
model: {
if (node.countForLoop === 0)
return 1

for (let i = 0; i < node.attributes.count; ++i) {
if (node.attributes.at(i).isLink) {
var srcAttr = node.attributes.at(i).linkParam
return srcAttr.root.value.count
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
}
}

Expand All @@ -402,7 +403,9 @@ Item {
defaultColor: Colors.sysPalette.mid
height: 3
width: parent.width
model: node ? node.chunks : undefined
model: {
return modelData.chunks
}

Rectangle {
anchors.fill: parent
Expand Down
51 changes: 31 additions & 20 deletions meshroom/ui/qml/GraphEditor/NodeEditor.qml
Original file line number Diff line number Diff line change
Expand Up @@ -258,29 +258,40 @@ Panel {
anchors.fill: parent

// The list of iterations
NodeEditorElementsListView {
id: iterationsLV
visible: root.node.countForLoop > 0
elements: {
if (root.node.countForLoop == 0)
return []
var elements = []
for (let i = 0; i < node.attributes.count; ++i) {
if (node.attributes.at(i).isLink) {
var srcAttr = node.attributes.at(i).linkParam
for (let j = 0; j < srcAttr.root.value.count; ++j) {
elements.push(j)
}
return elements
}

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
}

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

isChunk: false
title: "Iterations"
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
Expand Down
8 changes: 7 additions & 1 deletion meshroom/ui/qml/GraphEditor/NodeEditorElementsListView.qml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,13 @@ ColumnLayout {
Rectangle {
width: 4
height: parent.height
color: isChunk ? Common.getChunkColor(parent.element) : palette.mid
color: {
if (isChunk) {
return Common.getChunkColor(parent.element)
} else {
return Common.getNodeColor(parent.element)
}
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions meshroom/ui/qml/GraphEditor/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@ function getChunkColor(chunk, overrides) {
console.warn("Unknown status : " + chunk.status)
return "magenta"
}

function getNodeColor(node) {
return statusColors[node.globalStatus] || "magenta"
}

0 comments on commit 2420169

Please sign in to comment.