Loop Nodes #4
3
gui.py
3
gui.py
@ -1,6 +1,7 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
class RIGNODES_MT_snap_pie(bpy.types.Menu):
|
class RIGNODES_MT_snap_pie(bpy.types.Menu):
|
||||||
@ -60,7 +61,7 @@ def draw_nodetree_header_menu(self, context):
|
|||||||
row.operator("rignodes.rebuild_node")
|
row.operator("rignodes.rebuild_node")
|
||||||
|
|
||||||
|
|
||||||
addon_keymaps = []
|
addon_keymaps: List[bpy.types.KeyMap] = []
|
||||||
classes = (RIGNODES_MT_snap_pie,)
|
classes = (RIGNODES_MT_snap_pie,)
|
||||||
_register, _unregister = bpy.utils.register_classes_factory(classes)
|
_register, _unregister = bpy.utils.register_classes_factory(classes)
|
||||||
|
|
||||||
|
121
nodes.py
121
nodes.py
@ -81,7 +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_nodes_between(input_node))
|
nodes = list(output_node._get_loop_nodes(input_node))
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
cgtinker marked this conversation as resolved
|
|||||||
if node == output_node:
|
if node == output_node:
|
||||||
continue
|
continue
|
||||||
@ -93,12 +93,12 @@ class RigNodesNodeTree(bpy.types.NodeTree):
|
|||||||
depsgraph: bpy.types.Depsgraph,
|
depsgraph: bpy.types.Depsgraph,
|
||||||
to_visit: deque["AbstractRigNodesNode"],
|
to_visit: deque["AbstractRigNodesNode"],
|
||||||
depth: int = 1,
|
depth: int = 1,
|
||||||
) -> deque['AbstractRigNodesNode']:
|
) -> deque["AbstractRigNodesNode"]:
|
||||||
"""Run one iteration of a loop.
|
"""Runs (nested) loops.
|
||||||
|
|
||||||
:return: Node(s) to visit next."""
|
:return: Node(s) to visit next."""
|
||||||
cgtinker marked this conversation as resolved
Sybren A. Stüvel
commented
Formatting: when writing multi-line docstrings, the closing Formatting: when writing multi-line docstrings, the closing `"""` should be on a line of its own (see [PEP 257](https://peps.python.org/pep-0257/#multi-line-docstrings))
Denys Hsu
commented
Sorry, still stumbling. Sorry, still stumbling.
|
|||||||
|
|
||||||
indent = '\t' * depth
|
indent = "\t" * depth
|
||||||
print(f"{indent}\033[38;5;214mRunning {node}\033[0m")
|
print(f"{indent}\033[38;5;214mRunning {node}\033[0m")
|
||||||
|
|
||||||
input_node = node
|
input_node = node
|
||||||
@ -111,7 +111,7 @@ class RigNodesNodeTree(bpy.types.NodeTree):
|
|||||||
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_nodes_between(node))
|
nested_loop = deque(nested_output_node._get_loop_nodes(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.
|
||||||
@ -120,13 +120,12 @@ class RigNodesNodeTree(bpy.types.NodeTree):
|
|||||||
|
|
||||||
# Run the nested loop using the current inputs.
|
# Run the nested loop using the current inputs.
|
||||||
for _ in range(node.num_socks):
|
for _ in range(node.num_socks):
|
||||||
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:
|
if node in visited:
|
||||||
# Nested nodes are added to visited nodes as they get executed in advance.
|
# Nested nodes are added to visited nodes as they get executed in advance.
|
||||||
# A Node can alsp be visited when a node has inputs from two different
|
# A Node can also be visited when a node has inputs from two different
|
||||||
# nodes; both of those will list this node as 'successor'.
|
# nodes; both of those will list this node as 'successor'.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -139,7 +138,6 @@ class RigNodesNodeTree(bpy.types.NodeTree):
|
|||||||
|
|
||||||
return to_visit
|
return to_visit
|
||||||
|
|
||||||
|
|
||||||
def _run_from_node(
|
def _run_from_node(
|
||||||
self, depsgraph: bpy.types.Depsgraph, start_node: "AbstractRigNodesNode"
|
self, depsgraph: bpy.types.Depsgraph, start_node: "AbstractRigNodesNode"
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -160,11 +158,22 @@ class RigNodesNodeTree(bpy.types.NodeTree):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(node, LoopInputNode):
|
if isinstance(node, LoopInputNode):
|
||||||
# Run all iterations if the found loop (recursive if nested).
|
if node in visited:
|
||||||
# nested_output_node = node._get_connection_node()
|
to_visit.popleft()
|
||||||
# nested_loop = deque(nested_output_node._get_nodes_between(node))
|
continue
|
||||||
# nested_loop.append(nested_output_node)
|
|
||||||
self._run_loop(node, depsgraph, to_visit)
|
# Find the loop.
|
||||||
|
output_node = node._get_connection_node()
|
||||||
|
loop = deque(output_node._get_loop_nodes(node))
|
||||||
|
loop.append(output_node)
|
||||||
|
|
||||||
|
# Add (nested) loop nodes to visited.
|
||||||
|
for loop_node in loop:
|
||||||
|
visited.add(loop_node)
|
||||||
|
|
||||||
|
# Run all iterations of the found loop (recursive if nested).
|
||||||
|
for _ in range(0, output_node.num_socks):
|
||||||
|
self._run_loop(node, depsgraph, to_visit.copy())
|
||||||
continue
|
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
|
||||||
@ -291,15 +300,15 @@ class AbstractRigNodesNode(bpy.types.Node):
|
|||||||
def _outputs(self):
|
def _outputs(self):
|
||||||
return self.outputs
|
return self.outputs
|
||||||
|
|
||||||
def _get_nodes_between(
|
def _get_loop_nodes(
|
||||||
self, to_node: "AbstractRigNodesNode"
|
self: "LoopOutputNode", input_node: "LoopInputNode"
|
||||||
) -> Iterable["AbstractRigNodesNode"]:
|
) -> Iterable["AbstractRigNodesNode"]:
|
||||||
# TODO: the naming is bad here I think. Check that it's actually from -> to or to -> from
|
"""Generator, yields nodes that should be executed before the loop output node."""
|
||||||
nodes = list(self._connected_nodes(self._inputs, "from_socket"))
|
nodes = list(self._connected_nodes(self._inputs, "from_socket"))
|
||||||
|
|
||||||
if not to_node in nodes and len(nodes) > 0:
|
if not input_node in nodes and len(nodes) > 0:
|
||||||
from_node = nodes[-1]
|
from_node = nodes[-1]
|
||||||
yield from from_node._get_nodes_between(to_node)
|
yield from from_node._get_loop_nodes(input_node)
|
||||||
yield from nodes
|
yield from nodes
|
||||||
|
|
||||||
def exec_order_prerequisites(self) -> Iterable["AbstractRigNodesNode"]:
|
def exec_order_prerequisites(self) -> Iterable["AbstractRigNodesNode"]:
|
||||||
@ -521,6 +530,20 @@ class AbstractRigNodesNode(bpy.types.Node):
|
|||||||
tree.links.new(from_socket, to_socket)
|
tree.links.new(from_socket, to_socket)
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectInputNode(AbstractRigNodesNode):
|
||||||
|
bl_idname = "ObjectInputNode"
|
||||||
|
bl_label = "Object Input Node"
|
||||||
|
bl_icon = "EMPTY_ARROWS"
|
||||||
|
|
||||||
|
def init(self, context: bpy.types.Context) -> None:
|
||||||
|
self.inputs.new("NodeSocketObject", f"Object")
|
||||||
|
self.outputs.new("NodeSocketObject", f"Object")
|
||||||
|
|
||||||
|
def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
|
||||||
|
obj = self._get_input_value(f"Object", bpy.types.Object)
|
||||||
|
self.outputs["Object"].default_value = obj
|
||||||
|
|
||||||
|
|
||||||
def _on_update_recreate_node(self: "SequenceNode", context: bpy.types.Context) -> None:
|
def _on_update_recreate_node(self: "SequenceNode", context: bpy.types.Context) -> None:
|
||||||
self.recreate(context)
|
self.recreate(context)
|
||||||
|
|
||||||
@ -559,13 +582,6 @@ class LoopInputNode(AbstractRigNodesNode):
|
|||||||
super().draw_buttons(context, layout)
|
super().draw_buttons(context, layout)
|
||||||
layout.prop(self, "dtype")
|
layout.prop(self, "dtype")
|
||||||
layout.prop(self, "num_socks")
|
layout.prop(self, "num_socks")
|
||||||
layout.prop(self, "index")
|
|
||||||
|
|
||||||
# 67649: The socket_value_update fn does not work when changing values in a node tree.
|
|
||||||
# Therefore using update for prototying.
|
|
||||||
def update(self):
|
|
||||||
if "Loop Connection" in self.outputs:
|
|
||||||
setattr(self.outputs["Loop Connection"], "default_value", self.num_socks)
|
|
||||||
|
|
||||||
def _get_connection_node(self) -> "LoopOutputNode":
|
def _get_connection_node(self) -> "LoopOutputNode":
|
||||||
# The 'Loop Connection' should be connected to a 'Loop Input Node',
|
# The 'Loop Connection' should be connected to a 'Loop Input Node',
|
||||||
@ -582,6 +598,12 @@ class LoopInputNode(AbstractRigNodesNode):
|
|||||||
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.")
|
||||||
|
|
||||||
|
# 67649: The socket_value_update fn does not work when changing values in a node tree.
|
||||||
|
# Therefore using update for prototying.
|
||||||
|
def update(self):
|
||||||
|
if "Loop Connection" in self.outputs:
|
||||||
|
setattr(self.outputs["Loop Connection"], "default_value", self.num_socks)
|
||||||
|
|
||||||
def reset_run(self):
|
def reset_run(self):
|
||||||
super().reset_run()
|
super().reset_run()
|
||||||
self.index = 0
|
self.index = 0
|
||||||
@ -657,7 +679,10 @@ class LoopOutputNode(AbstractRigNodesNode):
|
|||||||
) -> None:
|
) -> None:
|
||||||
super().draw_buttons(context, layout)
|
super().draw_buttons(context, layout)
|
||||||
layout.prop(self, "dtype")
|
layout.prop(self, "dtype")
|
||||||
layout.prop(self, "index")
|
|
||||||
|
@AbstractRigNodesNode._inputs.getter # type: ignore
|
||||||
|
def _inputs(self):
|
||||||
|
return self.inputs[1:]
|
||||||
|
|
||||||
def _get_connection_node(self) -> "LoopInputNode":
|
def _get_connection_node(self) -> "LoopInputNode":
|
||||||
# The 'Loop Connection' should be connected to a 'Loop Input Node',
|
# The 'Loop Connection' should be connected to a 'Loop Input Node',
|
||||||
@ -672,9 +697,21 @@ class LoopOutputNode(AbstractRigNodesNode):
|
|||||||
return connected_socket.node
|
return connected_socket.node
|
||||||
raise RuntimeError("Loop Input Node is required to execute a loop.")
|
raise RuntimeError("Loop Input Node is required to execute a loop.")
|
||||||
|
|
||||||
@AbstractRigNodesNode._inputs.getter # type: ignore
|
def reset_run(self) -> None:
|
||||||
def _inputs(self):
|
super().reset_run()
|
||||||
return self.inputs[1:]
|
self.index = 0
|
||||||
|
self.iterations = self.num_socks
|
||||||
|
|
||||||
|
# 67649: The socket_value_update fn does not work when changing values in a node tree.
|
||||||
|
# Therefore using update for prototying the loop function.
|
||||||
|
# This allows to dynamically change the sockets based loop input socket amount.
|
||||||
|
def update(self):
|
||||||
|
if "Loop Connection" not in self.inputs:
|
||||||
|
return
|
||||||
|
|
||||||
|
val = self._get_optional_input_value("Loop Connection", int)
|
||||||
|
if val != self.num_socks and isinstance(val, int):
|
||||||
|
self.num_socks = val
|
||||||
|
|
||||||
def init(self, context: bpy.types.Context) -> None:
|
def init(self, context: bpy.types.Context) -> None:
|
||||||
self.inputs.new("NodeSocketInt", "Loop Connection")
|
self.inputs.new("NodeSocketInt", "Loop Connection")
|
||||||
@ -720,22 +757,6 @@ class LoopOutputNode(AbstractRigNodesNode):
|
|||||||
obj = self._get_input_value(f"Value", float)
|
obj = self._get_input_value(f"Value", float)
|
||||||
self.outputs[f"Value {index}"].default_value = obj
|
self.outputs[f"Value {index}"].default_value = obj
|
||||||
|
|
||||||
def reset_run(self) -> None:
|
|
||||||
super().reset_run()
|
|
||||||
self.index = 0
|
|
||||||
self.iterations = self.num_socks
|
|
||||||
|
|
||||||
# 67649: The socket_value_update fn does not work when changing values in a node tree.
|
|
||||||
# Therefore using update for prototying the loop function.
|
|
||||||
# This allows to dynamically change the sockets based loop input socket amount.
|
|
||||||
def update(self):
|
|
||||||
if "Loop Connection" not in self.inputs:
|
|
||||||
return
|
|
||||||
|
|
||||||
val = self._get_optional_input_value("Loop Connection", int)
|
|
||||||
if val != self.num_socks and isinstance(val, int):
|
|
||||||
self.num_socks = val
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractRigNodesEventNode(AbstractRigNodesNode):
|
class AbstractRigNodesEventNode(AbstractRigNodesNode):
|
||||||
"""Node that can only exist once in a tree."""
|
"""Node that can only exist once in a tree."""
|
||||||
@ -1629,6 +1650,13 @@ node_categories = [
|
|||||||
nodeitems_utils.NodeItem("LoopOutputNode"),
|
nodeitems_utils.NodeItem("LoopOutputNode"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
RigNodesNodeCategory(
|
||||||
|
"INPUTS",
|
||||||
|
"Inputs",
|
||||||
|
items=[
|
||||||
|
nodeitems_utils.NodeItem("ObjectInputNode"),
|
||||||
|
],
|
||||||
|
),
|
||||||
RigNodesNodeCategory(
|
RigNodesNodeCategory(
|
||||||
"CONTROL",
|
"CONTROL",
|
||||||
"Control",
|
"Control",
|
||||||
@ -1693,6 +1721,7 @@ classes = (
|
|||||||
SequenceNode,
|
SequenceNode,
|
||||||
LoopInputNode,
|
LoopInputNode,
|
||||||
LoopOutputNode,
|
LoopOutputNode,
|
||||||
|
ObjectInputNode,
|
||||||
# Math Nodes
|
# Math Nodes
|
||||||
RotateTowards,
|
RotateTowards,
|
||||||
AngleFromVectors,
|
AngleFromVectors,
|
||||||
|
@ -60,7 +60,7 @@ class RigNodes_OT_prepare_tree(bpy.types.Operator):
|
|||||||
"""Prepare node tree manually."""
|
"""Prepare node tree manually."""
|
||||||
|
|
||||||
bl_idname = "rignodes.prepare_tree"
|
bl_idname = "rignodes.prepare_tree"
|
||||||
bl_label = "Prepare Tree"
|
bl_label = "Reset Tree"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user
There is no need to construct a list here, just loop over the generator returned by
output_node._get_loop_nodes()