[Bf-blender-cvs] [9843ca071c3] functions: prevent recursive node groups
Jacques Lucke
noreply at git.blender.org
Thu Nov 14 16:31:34 CET 2019
Commit: 9843ca071c3179c1df4d8df2333365c908e4ab7e
Author: Jacques Lucke
Date: Thu Nov 14 16:31:24 2019 +0100
Branches: functions
https://developer.blender.org/rB9843ca071c3179c1df4d8df2333365c908e4ab7e
prevent recursive node groups
===================================================================
M release/scripts/startup/nodes/base.py
M release/scripts/startup/nodes/function_nodes/groups.py
M release/scripts/startup/nodes/function_tree.py
M release/scripts/startup/nodes/graph.py
M release/scripts/startup/nodes/node_operators.py
M release/scripts/startup/nodes/sync.py
===================================================================
diff --git a/release/scripts/startup/nodes/base.py b/release/scripts/startup/nodes/base.py
index d77faeaa9d5..fa6c871462f 100644
--- a/release/scripts/startup/nodes/base.py
+++ b/release/scripts/startup/nodes/base.py
@@ -119,7 +119,7 @@ class BaseNode:
def draw_advanced(self, layout):
pass
- def iter_dependency_trees(self):
+ def iter_directly_used_trees(self):
return
yield
@@ -138,6 +138,13 @@ class BaseNode:
self._set_common_invoke_props(props, function_name, settings)
props.mode = mode
+ def invoke_group_selector(self,
+ layout, function_name, text,
+ *, icon="NONE", settings=tuple()):
+ assert isinstance(settings, tuple)
+ props = layout.operator("fn.node_group_selector", text=text, icon=icon)
+ self._set_common_invoke_props(props, function_name, settings)
+
def _set_common_invoke_props(self, props, function_name, settings):
props.tree_name = self.id_data.name
props.node_name = self.name
diff --git a/release/scripts/startup/nodes/function_nodes/groups.py b/release/scripts/startup/nodes/function_nodes/groups.py
index f0f3c1e3798..a3d765d29a8 100644
--- a/release/scripts/startup/nodes/function_nodes/groups.py
+++ b/release/scripts/startup/nodes/function_nodes/groups.py
@@ -91,9 +91,14 @@ class GroupNode(bpy.types.Node, FunctionNode):
output_node.data_type)
def draw(self, layout):
- layout.prop(self, "node_group", text="")
+ text = "Select Group" if self.node_group is None else self.node_group.name
+ layout.scale_y = 1.3
+ self.invoke_group_selector(layout, "set_group", text, icon="NODETREE")
- def iter_dependency_trees(self):
+ def set_group(self, group):
+ self.node_group = group
+
+ def iter_directly_used_trees(self):
if self.node_group is not None:
yield self.node_group
diff --git a/release/scripts/startup/nodes/function_tree.py b/release/scripts/startup/nodes/function_tree.py
index a23f74323a4..48c7bbc40d4 100644
--- a/release/scripts/startup/nodes/function_tree.py
+++ b/release/scripts/startup/nodes/function_tree.py
@@ -2,6 +2,7 @@ import bpy
from collections import namedtuple
from . base import BaseTree, BaseNode
+from . graph import DirectedGraphBuilder, DirectedGraph
class FunctionTree(bpy.types.NodeTree, BaseTree):
bl_idname = "FunctionTree"
@@ -18,9 +19,33 @@ class FunctionTree(bpy.types.NodeTree, BaseTree):
sorted_output_nodes = sorted(output_nodes, key=lambda node: (node.sort_index, node.name))
return sorted_output_nodes
- def iter_dependency_trees(self):
+ def get_directly_used_trees(self):
trees = set()
for node in self.nodes:
if isinstance(node, BaseNode):
- trees.update(node.iter_dependency_trees())
- yield from trees
+ trees.update(node.iter_directly_used_trees())
+ return trees
+
+ @staticmethod
+ def BuildTreeCallGraph() -> DirectedGraph:
+ '''
+ Every vertex is a tree.
+ Every edge (A, B) means: Tree A uses tree B.
+ '''
+ builder = DirectedGraphBuilder()
+ for tree in bpy.data.node_groups:
+ if isinstance(tree, FunctionTree):
+ builder.add_vertex(tree)
+ for dependency_tree in tree.get_directly_used_trees():
+ builder.add_directed_edge(
+ from_v=tree,
+ to_v=dependency_tree)
+ return builder.build()
+
+ @staticmethod
+ def BuildInvertedCallGraph() -> DirectedGraph:
+ '''
+ Builds a directed graph in which every tree is a vertex.
+ Every edge (A, B) means: Changes in A might affect B.
+ '''
+ return FunctionTree.BuildTreeCallGraph().inverted()
diff --git a/release/scripts/startup/nodes/graph.py b/release/scripts/startup/nodes/graph.py
index 62049a478d7..f005da463ed 100644
--- a/release/scripts/startup/nodes/graph.py
+++ b/release/scripts/startup/nodes/graph.py
@@ -16,6 +16,9 @@ class DirectedGraph:
self.neighbors[v1].add(v2)
self.neighbors[v2].add(v1)
+ def inverted(self):
+ return DirectedGraph(self.V, [(v2, v1) for v1, v2 in self.E])
+
def reachable(self, start_verts):
return self._reachable(start_verts, self.outgoing)
diff --git a/release/scripts/startup/nodes/node_operators.py b/release/scripts/startup/nodes/node_operators.py
index e331b3e562d..adf3487dab3 100644
--- a/release/scripts/startup/nodes/node_operators.py
+++ b/release/scripts/startup/nodes/node_operators.py
@@ -1,6 +1,7 @@
import bpy
from bpy.props import *
from . types import type_infos
+from . function_tree import FunctionTree
def try_find_node(tree_name, node_name):
tree = bpy.data.node_groups.get(tree_name)
@@ -61,6 +62,36 @@ class NodeDataTypeSelector(bpy.types.Operator, NodeOperatorBase):
def execute(self, context):
return self.call(self.item)
+class NodeGroupSelector(bpy.types.Operator, NodeOperatorBase):
+ bl_idname = "fn.node_group_selector"
+ bl_label = "Node Group Selector"
+ bl_options = {'INTERNAL'}
+ bl_property = "item"
+
+ def get_items(self, context):
+ tree = bpy.data.node_groups.get(self.tree_name)
+ used_by_trees = FunctionTree.BuildInvertedCallGraph().reachable(tree)
+
+ items = []
+ for tree in bpy.data.node_groups:
+ if isinstance(tree, FunctionTree):
+ if tree not in used_by_trees:
+ items.append((tree.name, tree.name, ""))
+ items.append(("NONE", "None", ""))
+ return items
+
+ item: EnumProperty(items=get_items)
+
+ def invoke(self, context, event):
+ context.window_manager.invoke_search_popup(self)
+ return {'CANCELLED'}
+
+ def execute(self, context):
+ if self.item == "NONE":
+ return self.call(None)
+ else:
+ return self.call(bpy.data.node_groups.get(self.item))
+
class MoveViewToNode(bpy.types.Operator):
bl_idname = "fn.move_view_to_node"
bl_label = "Move View to Node"
diff --git a/release/scripts/startup/nodes/sync.py b/release/scripts/startup/nodes/sync.py
index 15dfa1741f5..c3f2f744db6 100644
--- a/release/scripts/startup/nodes/sync.py
+++ b/release/scripts/startup/nodes/sync.py
@@ -3,6 +3,7 @@ from pprint import pprint
from . base import BaseNode
from . tree_data import TreeData
from . graph import DirectedGraphBuilder
+from . function_tree import FunctionTree
from contextlib import contextmanager
_is_syncing = False
@@ -61,28 +62,11 @@ def iter_trees_to_sync_in_order(trees):
# can happen after undo or on load
return
- dependency_graph = build_tree_dependency_graph()
+ dependency_graph = FunctionTree.BuildInvertedCallGraph()
all_trees_to_sync = dependency_graph.reachable(trees)
trees_in_sync_order = dependency_graph.toposort_partial(all_trees_to_sync)
yield from trees_in_sync_order
-def build_tree_dependency_graph():
- '''
- Builds a directed graph in which every tree is a vertex.
- Every edge (A, B) means: Changes in A might affect B.
- '''
- from . function_tree import FunctionTree
-
- builder = DirectedGraphBuilder()
- for tree in bpy.data.node_groups:
- if isinstance(tree, FunctionTree):
- builder.add_vertex(tree)
- for dependency_tree in tree.iter_dependency_trees():
- builder.add_directed_edge(
- from_v=dependency_tree,
- to_v=tree)
- return builder.build()
-
# Rebuild already outdated nodes
############################################
More information about the Bf-blender-cvs
mailing list