Loop Nodes #4

Open
Denys Hsu wants to merge 5 commits from cgtinker/powership:loop into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
2 changed files with 29 additions and 24 deletions
Showing only changes of commit 7d6e455518 - Show all commits

View File

@ -81,10 +81,7 @@ class RigNodesNodeTree(bpy.types.NodeTree):
# Set iteration count foreach (nested) loop. # Set iteration count foreach (nested) loop.
for output_node in output_nodes: for output_node in output_nodes:
input_node = output_node._get_connection_node() input_node = output_node._get_connection_node()
nodes = list(output_node._get_loop_nodes(input_node)) for node in output_node._get_innern_loop_nodes(input_node):
for node in nodes:
if node == output_node:
continue
node.iterations *= output_node.num_socks node.iterations *= output_node.num_socks
cgtinker marked this conversation as resolved

There is no need to construct a list here, just loop over the generator returned by output_node._get_loop_nodes()

There is no need to construct a list here, just loop over the generator returned by `output_node._get_loop_nodes()`
def _run_loop( def _run_loop(
@ -108,10 +105,17 @@ class RigNodesNodeTree(bpy.types.NodeTree):
while to_visit: while to_visit:
node = to_visit.popleft() node = to_visit.popleft()
if node in visited:
# Nested nodes are added to visited nodes as they get executed in advance.
# A Node can also be visited when a node has inputs from two different
# nodes; both of those will list this node as 'successor'.
continue
if isinstance(node, LoopInputNode) and node != input_node: if isinstance(node, LoopInputNode) and node != input_node:
# Find the nested loop. # Find the nested loop.
nested_output_node = node._get_connection_node() nested_output_node = node._get_connection_node()
nested_loop = deque(nested_output_node._get_loop_nodes(node)) nested_loop = deque(nested_output_node._get_innern_loop_nodes(node))
nested_loop.appendleft(node)
nested_loop.append(nested_output_node) nested_loop.append(nested_output_node)
# Don't revisit nested nodes in the parent loop. # Don't revisit nested nodes in the parent loop.
@ -123,12 +127,6 @@ class RigNodesNodeTree(bpy.types.NodeTree):
self._run_loop(node, depsgraph, nested_loop.copy(), depth + 1) self._run_loop(node, depsgraph, nested_loop.copy(), depth + 1)
continue continue
if node in visited:
# Nested nodes are added to visited nodes as they get executed in advance.
# A Node can also be visited when a node has inputs from two different
# nodes; both of those will list this node as 'successor'.
continue
print(f"{indent}\tRunning: {node}, remaining runs: {node.iterations}") print(f"{indent}\tRunning: {node}, remaining runs: {node.iterations}")
visited.add(node) visited.add(node)
node.run(depsgraph) node.run(depsgraph)
@ -164,17 +162,17 @@ class RigNodesNodeTree(bpy.types.NodeTree):
# Find the loop. # Find the loop.
output_node = node._get_connection_node() output_node = node._get_connection_node()
loop = deque(output_node._get_loop_nodes(node)) innern_loop = deque(output_node._get_innern_loop_nodes(node))
loop.append(output_node)
# Add (nested) loop nodes to visited. # Add (nested) loop nodes to visited.
for loop_node in loop: visited.add(node)
visited.add(output_node)
for loop_node in innern_loop:
visited.add(loop_node) visited.add(loop_node)
# Run all iterations of the found loop (recursive if nested). # Run all iterations of the found loop (recursive if nested).
for _ in range(0, output_node.num_socks): for _ in range(0, output_node.num_socks):
self._run_loop(node, depsgraph, to_visit.copy()) self._run_loop(node, depsgraph, to_visit.copy())
continue
# Everything that this node depends on has run, so time to run it # Everything that this node depends on has run, so time to run it
# itself. It can be taken off the queue. # itself. It can be taken off the queue.
@ -300,16 +298,23 @@ class AbstractRigNodesNode(bpy.types.Node):
def _outputs(self): def _outputs(self):
return self.outputs return self.outputs
def _get_loop_nodes( def _get_innern_loop_nodes(
self: "LoopOutputNode", input_node: "LoopInputNode" self: "LoopOutputNode", input_node: "LoopInputNode",
) -> Iterable["AbstractRigNodesNode"]: ) -> Iterable["AbstractRigNodesNode"]:
"""Generator, yields nodes that should be executed before the loop output node.""" """Generator, yields nodes between the loop input and the loop output node"""
nodes = list(self._connected_nodes(self._inputs, "from_socket")) visited: Set["AbstractRigNodesNode"] = set()
to_visit = [self]
if not input_node in nodes and len(nodes) > 0: while to_visit:
from_node = nodes[-1] from_node = to_visit.pop()
cgtinker marked this conversation as resolved Outdated
  • Replace not X in Y with X not in Y.
  • Don't count the number of nodes unless you want to know the count. In this case and nodes will be enough to express 'list is not empty'
  • A 'list is not empty' check is likely to be faster than a list lookup, so do that first

The result:

if nodes and input_node not in nodes:
    ...
- Replace `not X in Y` with `X not in Y`. - Don't count the number of nodes unless you want to know the count. In this case `and nodes` will be enough to express 'list is not empty' - A 'list is not empty' check is likely to be faster than a list lookup, so do that first The result: ```py if nodes and input_node not in nodes: ... ```

I agree, actually rewrote that part completely. It skipped nodes when there were looping nodes within the loops.

ED: Due to the new logic I think it shouldn't be to hard to implement the points that have been out of scope. For the backwards solver that should only change how to search for innern loop nodes. But I'm not sure if I'd even like to have "setting attributes within loops" to be supported - doesn't make a lot of sense especially for nested loops.

I agree, actually rewrote that part completely. It skipped nodes when there were looping nodes within the loops. ED: Due to the new logic I think it shouldn't be to hard to implement the points that have been out of scope. For the backwards solver that should only change how to search for innern loop nodes. But I'm not sure if I'd even like to have "setting attributes within loops" to be supported - doesn't make a lot of sense especially for nested loops.
yield from from_node._get_loop_nodes(input_node) for node in from_node.exec_order_prerequisites():
yield from nodes if node in visited:
continue
visited.add(node)
if node != input_node:
to_visit.append(node)
yield node
def exec_order_prerequisites(self) -> Iterable["AbstractRigNodesNode"]: def exec_order_prerequisites(self) -> Iterable["AbstractRigNodesNode"]:
"""Generator, yields the nodes that should be executed before this one.""" """Generator, yields the nodes that should be executed before this one."""
@ -593,7 +598,6 @@ class LoopInputNode(AbstractRigNodesNode):
raise RuntimeError( raise RuntimeError(
"Loop Output Node is required to execute a loop." "Loop Output Node is required to execute a loop."
) )
if isinstance(connected_socket.node, LoopOutputNode): if isinstance(connected_socket.node, LoopOutputNode):
return connected_socket.node return connected_socket.node
raise RuntimeError("Loop Output Node is required to execute a loop.") raise RuntimeError("Loop Output Node is required to execute a loop.")
@ -607,6 +611,7 @@ class LoopInputNode(AbstractRigNodesNode):
def reset_run(self): def reset_run(self):
super().reset_run() super().reset_run()
self.index = 0 self.index = 0
self.iterations = self.num_socks
def init(self, context: bpy.types.Context) -> None: def init(self, context: bpy.types.Context) -> None:
for index in range(self.num_socks): for index in range(self.num_socks):

Binary file not shown.