瀏覽代碼

WIP: Route Group I/O through interface nodes

This commit does not work! But it is a good start.

The purpose of this commit is to move towards bundling connections
at the base of each group in/out so that I can cache things, manage
variables, and most importantly for present purposes, send arrays
in and out in a more consistent way. Probably I revert this commit
and do something very similar without the intervening node until I
get that slightly simpler strategy working.
Brandenburg 2 周之前
父節點
當前提交
0da7ca5e02
共有 4 個文件被更改,包括 159 次插入132 次删除
  1. 49 48
      base_definitions.py
  2. 23 5
      internal_containers.py
  3. 64 62
      readtree.py
  4. 23 17
      visualize.py

+ 49 - 48
base_definitions.py

@@ -88,7 +88,7 @@ class MantisTree(NodeTree):
     bl_idname = 'MantisTree'
     bl_label = "Rigging Nodes"
     bl_icon = 'OUTLINER_OB_ARMATURE'
-    
+
     tree_valid:BoolProperty(default=False)
     hash:StringProperty(default='')
     do_live_update:BoolProperty(default=True) # use this to disable updates for e.g. scripts
@@ -109,14 +109,14 @@ class MantisTree(NodeTree):
 
     #added to work around a bug in 4.5.0 LTS
     interface_helper : StringProperty(default='')
-    
+
     parsed_tree={}
 
     if (bpy.app.version < (4, 4, 0) or bpy.app.version >= (4,5,0)):  # in 4.4 this leads to a crash
         @classmethod
         def valid_socket_type(cls : NodeTree, socket_idname: str):
             return valid_interface_types(cls, socket_idname)
-    
+
     def update(self): # set the reroute colors
         if (bpy.app.version >= (4,4,0)):
             fix_reroute_colors(self)
@@ -136,6 +136,8 @@ class MantisTree(NodeTree):
                 scene = bpy.context.scene
                 scene.render.use_lock_interface = True
                 self.parsed_tree = readtree.parse_tree(self, error_popups)
+                from .visualize import visualize_tree
+                visualize_tree(self.parsed_tree, self, context)
                 if context:
                     self.display_update(context)
                 self.tree_valid = True
@@ -160,7 +162,7 @@ class MantisTree(NodeTree):
                 except Exception as e:
                     print("Node \"%s\" failed to update display with error: %s" %(wrapGreen(node.name), wrapRed(e)))
         self.is_executing = False
-        
+
         # TODO: deal with invalid links properly.
         #    - Non-hierarchy links should be ignored in the circle-check and so the links should be marked valid in such a circle
         #    - hierarchy-links should be marked invalid and prevent the tree from executing.
@@ -225,7 +227,7 @@ class MantisSocketTemplate():
     hide             : bool = field(default=False)
     use_multi_input  : bool = field(default=False)
     default_value    : Any = field(default=None)
-    
+
 
 
 #TODO: do a better job explaining how MantisNode and MantisUINode relate.
@@ -242,7 +244,7 @@ class MantisUINode:
     @classmethod
     def poll(cls, ntree):
         return (ntree.bl_idname in ['MantisTree', 'SchemaTree'])
-                
+
     @classmethod
     def set_mantis_class(self):
         from importlib import import_module
@@ -266,7 +268,7 @@ class MantisUINode:
                     node_tree.num_links+=1
                 elif (link.to_socket.is_multi_input):
                     node_tree.num_links+=1
-    
+
     def init_sockets(self, socket_templates : tuple[MantisSocketTemplate]):
         for template in socket_templates:
             collection = self.outputs
@@ -292,7 +294,7 @@ class MantisUINode:
                 #   responsibility to send the right type.
             if template.use_multi_input: # this is an array
                 socket.display_shape = 'SQUARE_DOT'
-            
+
 class SchemaUINode(MantisUINode):
     mantis_node_library='.schema_nodes'
     is_updating:BoolProperty(default=False)
@@ -305,7 +307,7 @@ class LinkNode(MantisUINode):
     @classmethod
     def poll(cls, ntree):
         return (ntree.bl_idname in ['MantisTree', 'SchemaTree'])
-    
+
 class xFormNode(MantisUINode):
     mantis_node_library='.xForm_nodes'
     @classmethod
@@ -394,11 +396,11 @@ def node_group_update(node, force = False):
                 if update_input: continue # done here
                 if s.bl_idname != item.bl_socket_idname: update_input = True; continue
             else: update_input = True; continue
-    
+
     # Schema has an extra input for Length and for Extend.
     if node.bl_idname == 'MantisSchemaGroup':
         found_in.extend(['Schema Length', ''])
-    
+
     # get the socket maps before modifying stuff
     if update_input or update_output:
         socket_maps = get_socket_maps(node,)
@@ -411,7 +413,7 @@ def node_group_update(node, force = False):
             # We have to initialize the node because it only has its base inputs.
         elif socket_maps is None:
             node.id_data.do_live_update = toggle_update
-    
+
     # if we have too many elements, just get rid of the ones we don't need
     if len(node.inputs) > len(found_in):#
         for inp in node.inputs:
@@ -447,7 +449,7 @@ def node_group_update(node, force = False):
                     remove_me.append(socket)
             while remove_me:
                 node.inputs.remove(remove_me.pop())
-            
+
         if update_output:
             remove_me=[]
             for socket in node.outputs:
@@ -571,10 +573,10 @@ class MantisNodeGroup(Node, MantisUINode):
             return "Node Group"
         else:
             return self.node_tree.name
-    
+
     def draw_buttons(self, context, layout):
         group_draw_buttons(self, context, layout)
-        
+
     def update(self):
         if self.node_tree is None:
             return
@@ -609,7 +611,7 @@ def poll_node_tree_schema(self, object):
 class SchemaGroup(Node, MantisUINode):
     bl_idname = "MantisSchemaGroup"
     bl_label = "Node Schema"
-    
+
     node_tree:PointerProperty(type=NodeTree, poll=poll_node_tree_schema, update=node_tree_prop_update,)
     is_updating:BoolProperty(default=False)
 
@@ -621,7 +623,7 @@ class SchemaGroup(Node, MantisUINode):
             return "Schema Group"
         else:
             return self.node_tree.name
-        
+
     def update(self):
         if self.is_updating: # update() can be called from update() and that leads to an infinite loop.
             return           # so we check if an update is currently running.
@@ -740,12 +742,12 @@ class MantisNode:
     @property
     def name(self):
         return self.ui_signature[-1]
-    
+
     @property
     def bl_idname(self): # this and the above exist solely to maintain interface w/bpy.types.Node
         from .utilities import get_node_prototype
         return get_node_prototype(self.ui_signature, self.base_tree).bl_idname
-    
+
     def reset_execution(self) -> None:
         """ Reset the node for additional execution without re-building the tree."""
         self.drivers={}; self.bObject=None
@@ -763,7 +765,7 @@ class MantisNode:
             self.parameters[socket.name] = None
         for key, value in additional_parameters.items():
             self.parameters[key]=value
-    
+
     def gen_property_socket_map(self) -> dict:
         props_sockets = {}
         for s_template in self.socket_templates:
@@ -775,7 +777,7 @@ class MantisNode:
                 for index, sub_prop in enumerate(s_template.blender_property):
                     props_sockets[sub_prop]=( (s_template.name, index),s_template.default_value[index] )
         return props_sockets
-    
+
     def set_traverse(self, traversal_pairs = [(str, str)]) -> None:
         for (a, b) in traversal_pairs:
             self.inputs[a].set_traverse_target(self.outputs[b])
@@ -786,12 +788,12 @@ class MantisNode:
             inp.flush_links()
         for out in self.outputs.values():
             out.flush_links()
-    
+
     def update_socket_value(self, blender_property, value) -> bool:
         change_handled=False
         if self.node_type == 'LINK':
             if len(self.bObject) == 0: # - there are no downstream xForms
-                return True # so there is nothing to do here 
+                return True # so there is nothing to do here
             for b_ob in self.bObject:
                 try:
                     setattr(b_ob, blender_property, value)
@@ -841,7 +843,7 @@ class MantisNode:
                             change_handled=False
                 break # we don't have to look through any more socket templates
         return change_handled
-    
+
     # the goal here is to tag the node as unprepared
     # but some nodes are always prepared, so we have to kick it forward.
     def reset_execution_recursive(self):
@@ -849,7 +851,7 @@ class MantisNode:
         if self.prepared==False: return # all good from here
         for conn in self.hierarchy_connections:
             conn.reset_execution_recursive()
-    
+
     def evaluate_input(self, input_name, index=0)  -> Any:
         from .node_container_common import trace_single_line
         if not (self.inputs.get(input_name)): # get the named parameter if there is no input
@@ -859,12 +861,12 @@ class MantisNode:
         trace = trace_single_line(self, input_name, index)
         prop = trace[0][-1].parameters[trace[1].name] #trace[0] = the list of traced nodes; read its parameters
         return prop
-    
+
     def fill_parameters(self, ui_node=None)  -> None:
         from .utilities import get_node_prototype
         from .node_container_common import get_socket_value
         if not ui_node:
-            if ( (self.signature[0] in  ["MANTIS_AUTOGENERATED", "SCHEMA_AUTOGENERATED" ]) or 
+            if ( (self.signature[0] in  ["MANTIS_AUTOGENERATED", "SCHEMA_AUTOGENERATED" ]) or
                 (self.signature[-1] in ["NodeGroupOutput", "NodeGroupInput"]) ): # I think this is harmless
                 return None
             else: # BUG shouldn't this use ui_signature??
@@ -919,7 +921,7 @@ class MantisNode:
             if self in solved:
                 break
         return
-    
+
     # gets targets for constraints and deformers and should handle all cases
     def get_target_and_subtarget(self, constraint_or_deformer, input_name = "Target"):
         from bpy.types import PoseBone, Object, SplineIKConstraint
@@ -931,7 +933,7 @@ class MantisNode:
                 else:
                     name = 'NAME NOT FOUND'
                 prRed(f"No {input_name} target found for {name} in {self} because there is no connected node, or node is wrong type")
-                return 
+                return
             if (isinstance(target.bGetObject(), PoseBone)):
                 subtarget = target.bGetObject().name
                 target = target.bGetParentArmature()
@@ -939,7 +941,7 @@ class MantisNode:
                 target = target.bGetObject()
             else:
                 raise RuntimeError("Cannot interpret constraint or deformer target!")
-        
+
         if   (isinstance(constraint_or_deformer, SplineIKConstraint)):
                 if target and target.type not in ["CURVE"]:
                     raise GraphError(wrapRed("Error: %s requires a Curve input, not %s" %
@@ -966,12 +968,12 @@ class MantisNode:
         return
     def bModifierApply(self, bContext=None):
         return
-    
+
     if environ.get("DOERROR"):
-        def __repr__(self): 
+        def __repr__(self):
             return self.signature.__repr__()
     else:
-        def __repr__(self): 
+        def __repr__(self):
             return self.ui_signature.__repr__()
 
 # do I need this and the link class above?
@@ -1007,7 +1009,7 @@ class NodeLink:
     from_socket = None
     to_node = None
     to_socket = None
-    
+
     def __init__(self, from_node, from_socket, to_node, to_socket, multi_input_sort_id=0):
         if from_node.signature == to_node.signature:
             raise RuntimeError("Cannot connect a node to itself.")
@@ -1021,7 +1023,7 @@ class NodeLink:
         self.to_node.inputs[self.to_socket].links.append(self)
         self.is_hierarchy = detect_hierarchy_link(from_node, from_socket, to_node, to_socket,)
         self.is_alive = True
-    
+
     def __repr__(self):
         return self.from_node.outputs[self.from_socket].__repr__() + " --> " + self.to_node.inputs[self.to_socket].__repr__()
         # link_string =   # if I need to colorize output for debugging.
@@ -1029,12 +1031,12 @@ class NodeLink:
         #     return wrapOrange(link_string)
         # else:
         #     return wrapWhite(link_string)
-    
+
     def die(self):
         self.is_alive = False
         self.to_node.inputs[self.to_socket].flush_links()
         self.from_node.outputs[self.from_socket].flush_links()
-    
+
     def insert_node(self, middle_node, middle_node_in, middle_node_out, re_init_hierarchy = True):
         to_node = self.to_node
         to_socket = self.to_socket
@@ -1052,7 +1054,7 @@ class NodeSocket:
     # @property # this is a read-only property.
     # def is_linked(self):
     #     return bool(self.links)
-        
+
     def __init__(self, is_input = False,
                  node = None, name = None,
                  traverse_target = None):
@@ -1065,7 +1067,7 @@ class NodeSocket:
         self.is_linked = False
         if (traverse_target):
             self.can_traverse = True
-        
+
     def connect(self, node, socket, sort_id=0):
         if  (self.is_input):
             to_node   = self.node; from_node = node
@@ -1085,22 +1087,22 @@ class NodeSocket:
                 to_socket,
                 sort_id)
         return new_link
-    
+
     def set_traverse_target(self, traverse_target):
         self.traverse_target = traverse_target
         self.can_traverse = True
-    
+
     def flush_links(self):
         """ Removes dead links from this socket."""
         self.links = [l for l in self.links if l.is_alive]
         self.links.sort(key=lambda a : -a.multi_input_sort_id)
         self.is_linked = bool(self.links)
-        
+
     @property
     def is_connected(self):
         return len(self.links)>0
-    
-    
+
+
     def __repr__(self):
         return self.node.__repr__() + "::" + self.name
 
@@ -1108,7 +1110,7 @@ class MantisNodeSocketCollection(dict):
     def __init__(self, node, is_input=False):
         self.is_input = is_input
         self.node = node
-    
+
     def init_sockets(self, sockets):
         for socket in sockets:
             if isinstance(socket, str):
@@ -1118,15 +1120,14 @@ class MantisNodeSocketCollection(dict):
                 self[socket.name] = NodeSocket(is_input=self.is_input, name=socket.name, node=self.node)
             else:
                 raise RuntimeError(f"NodeSocketCollection keys must be str or MantisSocketTemplate, not {type(socket)}")
-            
+
     def __delitem__(self, key):
         """Deletes a node socket by name, and all its links."""
         socket = self[key]
         for l in socket.links:
             l.die()
         super().__delitem__(key)
-    
+
     def __iter__(self):
         """Makes the class iterable"""
         return iter(self.values())
-

+ 23 - 5
internal_containers.py

@@ -1,6 +1,6 @@
 from .node_container_common import *
 from bpy.types import Node
-from .base_definitions import MantisNode
+from .base_definitions import MantisNode, MantisSocketTemplate
 from uuid import uuid4
 
 class DummyNode(MantisNode):
@@ -41,8 +41,8 @@ class NoOpNode(MantisNode):
         self.init_parameters()
         self.set_traverse([("Input", "Output")])
         self.node_type = 'UTILITY'
-        self.prepared = True
-        self.executed = True
+        self.prepared, self.executed = True, True
+        self.execution_prepared=True
     # this node is useful for me to insert in the tree and use for debugging especially connections.
 
 class AutoGenNode(MantisNode):
@@ -50,7 +50,25 @@ class AutoGenNode(MantisNode):
         super().__init__(signature, base_tree)
         self.node_type = 'UTILITY'
         self.prepared, self.executed = True, True
-    
+        self.execution_prepared=True
+
     def reset_execution(self):
         super().reset_execution()
-        self.prepared, self.executed = True, True
+        self.prepared, self.executed = True, True
+
+
+# The Group Interface node is responsible for gathering node connections
+#   going in or out of the group and connecting back out the other side
+# this is also where caching and overlays live
+class GroupInterface(MantisNode):
+    def __init__(self, signature, base_tree, prototype, in_out):
+        super().__init__(signature, base_tree)
+        self.node_type = 'UTILITY'
+        self.prepared, self.executed = True, True; sockets = []
+        self.in_out = in_out
+        # init the sockets based on in/out, then set up traversal
+        collection = prototype.inputs if in_out == 'INPUT' else prototype.outputs
+        for socket in collection: sockets.append(socket.identifier)
+        self.inputs.init_sockets(sockets); self.outputs.init_sockets(sockets)
+        for socket in self.inputs.keys(): self.set_traverse( [(socket, socket)] )
+        self.execution_prepared=True

+ 64 - 62
readtree.py

@@ -1,49 +1,50 @@
 from .utilities import prRed, prGreen, prPurple, prWhite, prOrange, \
                         wrapRed, wrapGreen, wrapPurple, wrapWhite, wrapOrange
 
-    
-
-
-def grp_node_reroute_common(nc, nc_to, all_nc):
-    # we need to do this: go  to the to-node
-    # then reroute the link in the to_node all the way to the beginning
-    # so that the number of links in "real" nodes is unchanged
-    # then the links in the dummy nodes need to be deleted
-    for inp_name, inp in nc.inputs.items():
-        # assume each input socket only has one input for now
-        if inp.is_connected:
-            while (inp.links):
-                in_link = inp.links.pop()
-                from_nc = in_link.from_node
-                from_socket = in_link.from_socket
-                links = []
-                from_links = from_nc.outputs[from_socket].links.copy()
-                while(from_links):
-                    from_link = from_links.pop()
-                    if from_link == in_link:
-                        from_link.die()
-                        continue # DELETE the dummy node link 
-                    links.append(from_link)
-                from_nc.outputs[from_socket].links = links
-                down = nc_to.outputs[inp_name]
-                for downlink in down.links:
-                    downlink.from_node = from_nc
-                    downlink.from_socket = from_socket
-                    from_nc.outputs[from_socket].links.append(downlink)
-                    if hasattr(downlink.to_node, "reroute_links"):
-                        downlink.to_node.reroute_links(downlink.to_node, all_nc)
-                in_link.die()
-
-def reroute_links_grp(nc, all_nc):
-    if nc.inputs:
-        if (nc_to := all_nc.get( ( *nc.signature, "NodeGroupInput") )):
-            grp_node_reroute_common(nc, nc_to, all_nc)
+
+
+# we need to reroute the incoming links to a new GroupInterface node
+# then we need to reroute the outgoing links from the GroupInterface
+# down into the tree -  or visa versa (NodeGroupOutput input to Group output)
+def grp_node_reroute_common(in_node, out_node, interface):
+    for in_node_input in in_node.inputs:
+        while (in_node_input.links):
+            in_link = in_node_input.links.pop()
+            from_node = in_link.from_node; from_socket = in_link.from_socket
+            # the inputs/outputs on the group and in/out nodes are IDs
+            from_node.outputs[from_socket].connect(
+                interface,in_node_input.name, sort_id = in_link.multi_input_sort_id)
+            # in_link.die()
+            init_connections(from_node)
+    for out_node_output in out_node.outputs:
+        while (out_node_output.links):
+            out_link = out_node_output.links.pop()
+            to_node = out_link.to_node; to_socket = out_link.to_socket
+            for l in interface.inputs[out_node_output.name].links:
+                interface.outputs[out_node_output.name].connect(
+                    to_node, to_socket, sort_id = l.multi_input_sort_id)
+            # out_link.die()
+            init_dependencies(to_node)
+    init_dependencies(interface); init_connections(interface)
+
+def reroute_links_grp(group, all_nodes):
+    from .internal_containers import GroupInterface
+    interface = GroupInterface(
+        ( *group.signature, "InputInterface"),
+        group.base_tree, group.prototype, 'INPUT',)
+    if group.inputs:
+        if group_input := all_nodes.get(( *group.signature, "NodeGroupInput")):
+            grp_node_reroute_common(group, group_input, interface)
         else:
             raise RuntimeError("internal error: failed to enter a node group ")
 
-def reroute_links_grpout(nc, all_nc):
-    if (nc_to := all_nc.get( ( *nc.signature[:-1],) )):
-        grp_node_reroute_common(nc, nc_to, all_nc)
+def reroute_links_grpout(group_output, all_nodes):
+    if (group := all_nodes.get( ( *group_output.signature[:-1],) )):
+        from .internal_containers import GroupInterface
+        interface = GroupInterface(
+            ( *group.signature, "OutputInterface"),
+            group.base_tree, group.prototype, 'OUTPUT',)
+        grp_node_reroute_common(group_output, group, interface)
     else:
         prOrange(f"WARN: unconnected outputs from a node group "
                  "(maybe you are running the tree from inside a node group?)")
@@ -64,7 +65,7 @@ def insert_lazy_parents(nc):
                     break # this is it
             from_link.to_node = inherit_nc; from_link.to_socket="Parent"
             from_link.to_node.inputs[from_link.to_socket].is_linked=True
-            
+
             links=[]
             while (nc.inputs["Relationship"].links):
                 to_link = nc.inputs["Relationship"].links.pop()
@@ -72,11 +73,11 @@ def insert_lazy_parents(nc):
                     continue # don't keep this one
                 links.append(to_link)
                 to_link.from_node.outputs[from_link.from_socket].is_linked=True
-            
+
             nc.inputs["Relationship"].links=links
             link=NodeLink(from_node=inherit_nc, from_socket="Inheritance", to_node=nc, to_socket="Relationship")
             inherit_nc.inputs["Parent"].links.append(from_link)
-            
+
             inherit_nc.parameters = {
                                      "Parent":None,
                                      "Inherit Rotation":True,
@@ -120,7 +121,7 @@ def make_connections_to_ng_dummy(base_tree, tree_path_names, local_nc, all_nc, n
             signature = ("MANTIS_AUTOGENERATED", *tree_path_names, nc_to.ui_signature[-1], inp.name, inp.identifier, str(uuid4()))
             nc_from = all_nc.get(signature) # creating this without checking and
             #  using UUID signature leads to TERRIBLE CONFUSING BUGS.
-            if nc_from is None: 
+            if nc_from is None:
                 nc_from = autogen_node(base_tree, inp, signature, nc_to.mContext)
             from .node_container_common import get_socket_value
             if nc_from: # autogen can fail and we should catch it.
@@ -187,7 +188,7 @@ def data_from_tree(base_tree, tree_path, dummy_nodes, all_nc, all_schema):#
         from .utilities import clear_reroutes
         links = clear_reroutes(list(current_tree.links))
         gen_node_containers(base_tree, current_tree, tree_path_names, all_nc, local_nc, dummy_nodes, group_nodes, all_schema)
-        
+
         from .utilities import link_node_containers
         for link in links:
             link_node_containers((None, *tree_path_names), link, local_nc)
@@ -247,7 +248,7 @@ schema_bl_idnames = [   "SchemaIndex",
                         "SchemaConstInput",
                         "SchemaConstOutput",
                         "SchemaOutgoingConnection",
-                        "SchemaIncomingConnection", 
+                        "SchemaIncomingConnection",
                     ]
 
 from .utilities import get_all_dependencies
@@ -262,7 +263,7 @@ def get_schema_length_dependencies(node, all_nodes={}):
             for l in inp.links:
                 if not l.from_node in node.hierarchy_dependencies:
                     continue
-                if "MANTIS_AUTOGENERATED" in l.from_node.signature: 
+                if "MANTIS_AUTOGENERATED" in l.from_node.signature:
                     deps.extend([l.from_node]) # why we need this lol
                 if inp.name in prepare_links_to:
                     deps.append(l.from_node)
@@ -326,21 +327,21 @@ def insert_default_values_base_tree(base_tree, all_mantis_nodes):
                 default_value = 'MANTIS_DEFAULT_ARMATURE'
             else:
                 raise RuntimeError(f"No xForm connected for {ui_output.name} in {base_tree.name}.")
-        
+
         else:
             raise RuntimeError(f"Cannot get default value for {ui_output.name} in {base_tree.name} ")
         output_name = output.name
         if interface_item.bl_socket_idname not in ['xFormSocket']:
             signature = ("MANTIS_AUTOGENERATED", f"Default Value {output.name}",)
             autogen_mantis_node = all_mantis_nodes.get(signature)
-            if autogen_mantis_node is None: 
+            if autogen_mantis_node is None:
                 autogen_mantis_node = autogen_node(base_tree, output, signature, InputNode.mContext)
                 autogen_mantis_node.parameters[output_name]=default_value
         elif interface_item.bl_socket_idname == 'xFormSocket' \
                                         and default_value == 'MANTIS_DEFAULT_ARMATURE':
             signature = ("MANTIS_AUTOGENERATED", "MANTIS_DEFAULT_ARMATURE",)
             autogen_mantis_node = all_mantis_nodes.get(signature)
-            if autogen_mantis_node is None: 
+            if autogen_mantis_node is None:
                 from .xForm_nodes import xFormArmature
                 autogen_mantis_node = xFormArmature(signature, base_tree)
                 autogen_mantis_node.parameters['Name']=base_tree.name+'_MANTIS_AUTOGEN'
@@ -359,7 +360,7 @@ def insert_default_values_base_tree(base_tree, all_mantis_nodes):
 def parse_tree(base_tree, error_popups=False):
     from uuid import uuid4
     base_tree.execution_id = uuid4().__str__() # set the unique id of this execution
-    
+
     from .base_definitions import MantisExecutionContext
     mContext = MantisExecutionContext(base_tree=base_tree)
 
@@ -368,13 +369,15 @@ def parse_tree(base_tree, error_popups=False):
     # annoyingly I have to pass in values for all of the dicts because if I initialize them in the function call
     #  then they stick around because the function definition inits them once and keeps a reference
     # so instead I have to supply them to avoid ugly code or bugs elsewhere
-    # it's REALLY confusing when you run into this sort of problem. So it warrants four entire lines of comments!      
+    # it's REALLY confusing when you run into this sort of problem. So it warrants four entire lines of comments!
     dummy_nodes, all_mantis_nodes, all_schema =  data_from_tree(base_tree, tree_path = [None], dummy_nodes = {}, all_nc = {}, all_schema={})
     for dummy in dummy_nodes.values():    # reroute the links in the group nodes
         if (hasattr(dummy, "reroute_links")):
             dummy.reroute_links(dummy, all_mantis_nodes)
     prGreen(f"Pulling data from tree took {time.time() - data_start_time} seconds")
-    
+
+    # base_tree.parsed_tree = all_mantis_nodes # for debugging
+
     start_time = time.time()
     solve_only_these = []; solve_only_these.extend(list(all_schema.values()))
     roots, array_nodes = [], []
@@ -410,6 +413,7 @@ def parse_tree(base_tree, error_popups=False):
             init_schema_dependencies(schema, all_mantis_nodes)
             solve_only_these.extend(get_schema_length_dependencies(schema, all_mantis_nodes))
             unsolved_schema.append(schema)
+    print (solve_only_these)
     for array in array_nodes:
         if array not in solve_only_these: continue
         solve_only_these.extend(get_schema_length_dependencies(array))
@@ -428,7 +432,7 @@ def parse_tree(base_tree, error_popups=False):
             for dep in n.hierarchy_dependencies:
                 if dep not in schema_solve_done and (dep in solve_only_these):
                     if dep.prepared:
-                        continue 
+                        continue
                     solve_layer.appendleft(n)
                     break
             else:
@@ -461,7 +465,6 @@ def parse_tree(base_tree, error_popups=False):
                     e = execution_error_cleanup(n, e, show_error=error_popups)
                     if error_popups == False:
                         raise e
-                    
                 schema_solve_done.add(n)
                 for conn in n.hierarchy_connections:
                     if conn not in schema_solve_done and conn not in solve_layer:
@@ -562,7 +565,7 @@ def sort_execution(nodes, xForm_pass):
             execution_failed = True
             raise GraphError("There is probably a cycle somewhere in the graph. "
                                 "Or a connection missing in a Group/Schema Input")
-        i+=1    
+        i+=1
         n = xForm_pass.pop()
         if visited.get(n.signature) is not None:
             visited[n.signature]+=1
@@ -662,7 +665,7 @@ def execute_tree(nodes, base_tree, context, error_popups = False):
         switch_mode(mode='OBJECT', objects=switch_me)
         # switch to pose mode here so that the nodes can use the final pose data
         # this will require them to update the depsgraph.
-        
+
 
         for ob in switch_me:
             ob.data.pose_position = 'POSE'
@@ -675,8 +678,8 @@ def execute_tree(nodes, base_tree, context, error_popups = False):
                 if error_popups == False:
                     raise e
                 execution_failed = True; break
-        
-        
+
+
         # REST pose for deformer bind, so everything is in the rest position
         for ob in switch_me:
             ob.data.pose_position = 'REST'
@@ -690,10 +693,10 @@ def execute_tree(nodes, base_tree, context, error_popups = False):
                 if error_popups == False:
                     raise e
                 execution_failed = True; break
-                
+
         for ob in switch_me:
             ob.data.pose_position = 'POSE'
-        
+
         tot_time = (time() - start_execution_time)
         if not execution_failed:
             prGreen(f"Executed tree of {len(sorted_nodes)} nodes in {tot_time} seconds")
@@ -719,4 +722,3 @@ def execute_tree(nodes, base_tree, context, error_popups = False):
                 ob.select_set(True)
             except RuntimeError: # it isn't in the view layer
                 pass
-

+ 23 - 17
visualize.py

@@ -21,7 +21,7 @@ class MantisVisualizeNode(Node):
     @classmethod
     def poll(cls, ntree):
         return (ntree.bl_idname in ['MantisVisualizeTree'])
-    
+
     def init(self, context):
         pass
 
@@ -32,7 +32,7 @@ class MantisVisualizeNode(Node):
             label+=elem+', '
         label = label[:-2] # cut the last comma
         return label
-    
+
     def gen_data(self, mantis_node, mode='DEBUG_CONNECTIONS'):
         from .utilities import get_node_prototype
         if mantis_node.node_type in ['SCHEMA', 'DUMMY']:
@@ -49,7 +49,7 @@ class MantisVisualizeNode(Node):
             case 'DRIVER':       self.color = (0.7, 0.05, 0.8)
             case 'DUMMY_SCHEMA': self.color = (0.85 ,0.95, 0.9)
             case 'DUMMY':        self.color = (0.05 ,0.05, 0.15)
-        
+
 
         self.name = '.'.join(mantis_node.signature[1:]) # this gets trunc'd
         self.signature = '|'.join(mantis_node.signature[1:])
@@ -65,7 +65,10 @@ class MantisVisualizeNode(Node):
                     case "DEBUG_CONNECTIONS":
                         if not inp.is_connected:
                             continue
-                s = self.inputs.new('WildcardSocket', inp.name)
+                multi = False
+                if len(inp.links) > 1: multi = True
+                s = self.inputs.new('WildcardSocket', inp.name, use_multi_input=multi)
+                s.link_limit = 4000
                 try:
                     if sock := np.inputs.get(inp.name):
                         s.color = sock.color_simple
@@ -95,7 +98,10 @@ class MantisVisualizeNode(Node):
                     case "DEBUG_CONNECTIONS":
                         if not inp.is_connected:
                             continue
-                self.inputs.new('WildcardSocket', inp.name)
+                multi = False
+                if len(inp.links) > 1: multi = True
+                s = self.inputs.new('WildcardSocket', inp.name)
+                s.link_limit = 4000
             for out in mantis_node.outputs:
                 match mode:
                     case "DEBUG_CONNECTIONS":
@@ -103,15 +109,15 @@ class MantisVisualizeNode(Node):
                             continue
                 self.outputs.new('WildcardSocket', out.name)
 
-def gen_vis_node( mantis_node, 
-                  vis_tree, 
+def gen_vis_node( mantis_node,
+                  vis_tree,
                   links,
                   omit_simple=True,
                  ):
     from .base_definitions import array_output_types
-    if mantis_node.node_type == 'UTILITY' and \
-         mantis_node.execution_prepared == True:
-            return
+    # if mantis_node.node_type == 'UTILITY' and \
+    #      mantis_node.execution_prepared == True:
+    #         return
     base_tree= mantis_node.base_tree
     vis = vis_tree.nodes.new('MantisVisualizeNode')
     vis.gen_data(mantis_node)
@@ -124,7 +130,7 @@ def gen_vis_node( mantis_node,
             if l.to_node in mantis_node.hierarchy_connections:
                 links.add(l)
     return vis
-                
+
 def visualize_tree(m_nodes, base_tree, context):
     # first create a MantisVisualizeTree
     from .readtree import check_and_add_root
@@ -137,7 +143,7 @@ def visualize_tree(m_nodes, base_tree, context):
     from pstats import SortKey
     with cProfile.Profile() as pr:
         try:
-            trace_from_roots = True
+            trace_from_roots = False
             all_links = set()
             mantis_nodes=set()
             nodes={}
@@ -189,17 +195,17 @@ def visualize_tree(m_nodes, base_tree, context):
                     if output.is_linked:
                         return True
                 return False
-            
+
             no_links=[]
 
             for n in vis_tree.nodes:
                 if not has_links(n):
                     no_links.append(n)
-            
+
             while (no_links):
                 n = no_links.pop()
                 vis_tree.nodes.remove(n)
-            
+
             # def side_len(n):
             #     from math import floor
             #     side = floor(n**(1/2)) + 1
@@ -244,10 +250,10 @@ class MantisVisualizeOutput(Operator):
     def execute(self, context):
         from time import time
         from .utilities import wrapGreen, prGreen
-        
+
         tree=context.space_data.path[0].node_tree
         tree.update_tree(context)
         prGreen(f"Visualize Tree: {tree.name}")
         nodes = tree.parsed_tree
         visualize_tree(nodes, tree, context)
-        return {"FINISHED"}
+        return {"FINISHED"}