Browse Source

WIP string variables

string variables work in the simplest case already
all the trouble comes from the stuff that is in schemas or made at
 runtime
so I have a lot of complex stuff to figure out
and even more important: to figure out how the user interacts
for example: should frame variables be inherited or not?
do I need a way to get this stuff recursively?
then how do i evaluate a string from a node in a different frame?
I would need to use THAT node to look up the variables, right?
NO SPOOKY stuff can be allowed

anyhow this is a WIP, might forget about this for a while and come back
Brandenburg 1 month ago
parent
commit
82613019b2
6 changed files with 253 additions and 172 deletions
  1. 96 51
      base_definitions.py
  2. 10 2
      internal_containers.py
  3. 46 28
      readtree.py
  4. 30 28
      schema_solve.py
  5. 56 28
      utilities.py
  6. 15 35
      visualize.py

+ 96 - 51
base_definitions.py

@@ -84,7 +84,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
@@ -104,14 +104,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)
@@ -155,7 +155,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.
@@ -219,7 +219,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.
@@ -236,7 +236,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
@@ -260,7 +260,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
@@ -286,7 +286,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)
@@ -299,7 +299,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
@@ -344,12 +344,12 @@ def node_group_update(node, force = False):
            (node.id_data.is_exporting == True):
             return
     # note: if (node.id_data.is_exporting == True) I need to be able to update so I can make links.
-    
+
     if node.node_tree is None:
         node.inputs.clear(); node.outputs.clear()
         node.id_data.do_live_update = toggle_update
         return
-    
+
     toggle_update = node.id_data.do_live_update
     node.id_data.do_live_update = False
 
@@ -391,11 +391,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,)
@@ -408,7 +408,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:
@@ -444,7 +444,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:
@@ -568,10 +568,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
@@ -606,7 +606,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)
 
@@ -618,7 +618,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.
@@ -702,6 +702,7 @@ class MantisExecutionContext():
         self.execution_id = base_tree.execution_id
         self.execution_failed=False
         self.b_objects={} # objects created by Mantis during execution
+        self.string_variables={}
 
 class MantisNode:
     """
@@ -737,12 +738,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
@@ -760,7 +761,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:
@@ -772,7 +773,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])
@@ -783,12 +784,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)
@@ -838,7 +839,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):
@@ -846,7 +847,42 @@ class MantisNode:
         if self.prepared==False: return # all good from here
         for conn in self.hierarchy_connections:
             conn.reset_execution_recursive()
-    
+
+
+    # TODO: make this MUCH more efficient!
+    # alternatively: call this ONCE when initializing the tree, precache results?
+    def apply_string_variables(self, string):
+        # We get the mContext, iterate through the signature, and string-replace variables
+        # this function should be called by evaluate_input if the result is a string.
+        all_vars = {} # maybe the individual nodes should store this as a class member, too...
+        name=""
+        do_once = False
+        for i in range(len(self.signature[:-1])):
+            if i == 0:
+                continue
+            name+=self.signature[i]
+            prWhite(name)
+            vars = self.mContext.string_variables.get(name, None)
+            if vars is None:
+                prRed("Can't get string variables for node")
+                print (vars, type(vars))
+                prRed (name)
+                prWhite(self.mContext.string_variables.keys())
+                for k in self.mContext.string_variables.keys():
+                    print (name == k)
+                    print (len(name), name,)
+                    print (len(k), k)
+                raise RuntimeError
+                continue
+            elif not vars:
+                prRed(self)
+            for var_name, var_value in vars.items():
+                do_once=True
+                string = string.replace("$"+var_name, var_value)
+        if do_once == False:
+            raise NotImplementedError
+        return string
+
     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
@@ -854,14 +890,24 @@ class MantisNode:
         # this trace() should give a key error if there is a problem
         #  it is NOT handled here because it should NOT happen - so I want the error message.
         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
+        try:
+            prop = trace[0][-1].parameters[trace[1].name] #trace[0] = the list of traced nodes; read its parameters
+        except Exception as e:
+            print (trace[0][-1])
+            print (trace[1].name)
+            print (trace[0][-1].parameters.keys())
+            print (trace[0][-1].parameters.values())
+            raise e
+        if isinstance(prop, str) and "$" in prop:
+            print (self, prop)
+            prop = self.apply_string_variables(prop)
         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??
@@ -916,7 +962,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
@@ -928,7 +974,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()
@@ -936,7 +982,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" %
@@ -963,12 +1009,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?
@@ -1004,7 +1050,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.")
@@ -1018,7 +1064,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.
@@ -1026,12 +1072,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
@@ -1049,7 +1095,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):
@@ -1062,7 +1108,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
@@ -1082,22 +1128,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
 
@@ -1105,7 +1151,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):
@@ -1115,15 +1161,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())
-

+ 10 - 2
internal_containers.py

@@ -11,6 +11,7 @@ class DummyNode(MantisNode):
         self.prepared = True
         self.uuid = uuid4()
         self.solver = None
+        self.did_set_variables = False
         if prototype:
             if prototype.bl_idname in ["MantisSchemaGroup"]:
                 self.node_type = 'DUMMY_SCHEMA'
@@ -32,6 +33,13 @@ class DummyNode(MantisNode):
         # this is ugly and I hate it.
         self.execution_prepared=True # in case it gets left behind in the tree as a dependency
 
+    def bPrepare(self, bContext=None):
+        from .utilities import set_string_variables_during_exec
+        set_string_variables_during_exec(self, self.mContext)
+        self.did_set_variables = True # I just need to know if this is getting them all
+
+    def __del__(self):
+        print (self, self.did_set_variables)
 
 class NoOpNode(MantisNode):
     def __init__(self, signature, base_tree):
@@ -50,7 +58,7 @@ class AutoGenNode(MantisNode):
         super().__init__(signature, base_tree)
         self.node_type = 'UTILITY'
         self.prepared, self.executed = True, True
-    
+
     def reset_execution(self):
         super().reset_execution()
-        self.prepared, self.executed = True, True
+        self.prepared, self.executed = True, True

+ 46 - 28
readtree.py

@@ -1,7 +1,7 @@
 from .utilities import prRed, prGreen, prPurple, prWhite, prOrange, \
                         wrapRed, wrapGreen, wrapPurple, wrapWhite, wrapOrange
 
-    
+
 
 
 def grp_node_reroute_common(nc, nc_to, all_nc):
@@ -22,7 +22,7 @@ def grp_node_reroute_common(nc, nc_to, all_nc):
                     from_link = from_links.pop()
                     if from_link == in_link:
                         from_link.die()
-                        continue # DELETE the dummy node link 
+                        continue # DELETE the dummy node link
                     links.append(from_link)
                 from_nc.outputs[from_socket].links = links
                 down = nc_to.outputs[inp_name]
@@ -63,7 +63,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()
@@ -71,11 +71,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,
@@ -122,7 +122,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.
@@ -132,7 +132,7 @@ def make_connections_to_ng_dummy(base_tree, tree_path_names, local_nc, all_nc, n
             else:
                 prRed("No available auto-generated class for input %s in %s" % (inp.name, np.name))
 
-def gen_node_containers(base_tree, current_tree, tree_path_names, all_nc, local_nc, dummy_nodes, group_nodes, schema_nodes ):
+def gen_mantis_nodes(base_tree, current_tree, tree_path_names, mContext, all_nc, local_nc, dummy_nodes, group_nodes, schema_nodes ):
     from .internal_containers import DummyNode
     for ui_node in current_tree.nodes:
         # HACK I found that this isn't being set sometimes. I wonder why? It makes the most sense to do this here.
@@ -146,13 +146,18 @@ def gen_node_containers(base_tree, current_tree, tree_path_names, all_nc, local_
             ui_sig = (None, *tree_path_names, ui_node.name)
             if not local_nc.get(sig):
                 nc = DummyNode( signature=sig , base_tree=base_tree, prototype=ui_node, ui_signature=ui_sig )
+                nc.mContext = mContext
                 local_nc[sig] = nc; all_nc[sig] = nc; dummy_nodes[sig] = nc
                 if ui_node.bl_idname in ["NodeGroupOutput"]:
                     nc.reroute_links = reroute_links_grpout
         elif ui_node.bl_idname in  ["MantisNodeGroup", "MantisSchemaGroup"]:
             nc = DummyNode( signature= (sig := (None, *tree_path_names, ui_node.name) ), base_tree=base_tree, prototype=ui_node )
+            nc.mContext = mContext
             local_nc[sig] = nc; all_nc[sig] = nc; dummy_nodes[sig] = nc
             make_connections_to_ng_dummy(base_tree, tree_path_names, local_nc, all_nc, nc)
+            # This will catch any variable set in a non-schema node-group.
+            from .utilities import set_string_variables_at_creation_time
+            set_string_variables_at_creation_time(nc, ui_node, mContext)
             if ui_node.bl_idname == "MantisNodeGroup":
                 group_nodes.append(nc)
                 nc.reroute_links = reroute_links_grp
@@ -167,6 +172,7 @@ def gen_node_containers(base_tree, current_tree, tree_path_names, all_nc, local_
                 if local_nc.get(sig):
                     continue # already made
             nc = nc_cls( sig , base_tree)
+            nc.mContext = mContext
             local_nc[sig] = nc; all_nc[sig] = nc
             nc.ui_signature = (*nc.ui_signature[:-1], ui_node.name) # just to ensure it points to a real node.
         else:
@@ -176,7 +182,7 @@ def gen_node_containers(base_tree, current_tree, tree_path_names, all_nc, local_
         if nc.signature[0] not in ['MANTIS_AUTOGENERATED'] and nc.node_type not in ['SCHEMA', 'DUMMY', 'DUMMY_SCHEMA']:
             nc.fill_parameters()
 
-def data_from_tree(base_tree, tree_path, dummy_nodes, all_nc, all_schema):#
+def data_from_tree(base_tree, tree_path, mContext, dummy_nodes, all_nc, all_schema):#
     # TODO: it should be relatively easy to make this use a while loop instead of recursion.
     local_nc, group_nodes = {}, []
     tree_path_names = [tree.name for tree in tree_path if hasattr(tree, "name")]
@@ -188,14 +194,14 @@ def data_from_tree(base_tree, tree_path, dummy_nodes, all_nc, all_schema):#
     if current_tree: # the node-group may not have a tree set - if so, ignore it.
         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)
-        
+        gen_mantis_nodes(base_tree, current_tree, tree_path_names, mContext, 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)
         # Now, descend into the Node Groups and recurse
         for nc in group_nodes:
-            data_from_tree(base_tree, tree_path+[nc.prototype], dummy_nodes, all_nc, all_schema)
+            data_from_tree(base_tree, tree_path+[nc.prototype], mContext, dummy_nodes, all_nc, all_schema)
     return dummy_nodes, all_nc, all_schema
 
 from .utilities import check_and_add_root, init_connections, init_dependencies, init_schema_dependencies
@@ -246,7 +252,7 @@ schema_bl_idnames = [   "SchemaIndex",
                         "SchemaConstInput",
                         "SchemaConstOutput",
                         "SchemaOutgoingConnection",
-                        "SchemaIncomingConnection", 
+                        "SchemaIncomingConnection",
                     ]
 
 from .utilities import get_all_dependencies
@@ -261,7 +267,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)
@@ -296,7 +302,7 @@ def get_schema_length_dependencies(node, all_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)
 
@@ -305,13 +311,13 @@ 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!      
-    dummy_nodes, all_mantis_nodes, all_schema =  data_from_tree(base_tree, tree_path = [None], dummy_nodes = {}, all_nc = {}, all_schema={})
+    # 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], mContext=mContext, 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")
-    
+
     start_time = time.time()
     solve_only_these = []; solve_only_these.extend(list(all_schema.values()))
     roots, array_nodes = [], []
@@ -365,7 +371,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:
@@ -398,7 +404,7 @@ 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:
@@ -410,12 +416,25 @@ def parse_tree(base_tree, error_popups=False):
 
     all_mantis_nodes = list(all_mantis_nodes.values())
     kept_nc = {}
+
     while (all_mantis_nodes):
         nc = all_mantis_nodes.pop()
         if nc in array_nodes:
             continue
-        if nc.node_type in ["DUMMY", 'SCHEMA', 'DUMMY_SCHEMA']:
-            continue # screen out the prototype schema nodes, group in/out, and group placeholders
+        if nc.node_type in ['SCHEMA', 'DUMMY_SCHEMA']:
+            # this is BAD because it "deletes" this implicitly instead of explicitly
+            continue # screen out the prototype schema nodes and schema nodes
+        if nc.node_type in ['DUMMY']:
+            if nc.signature[-1] in ["NodeGroupInput", "NodeGroupOutput"]:
+                continue
+            else:
+                prGreen(nc)
+                for input in nc.inputs:
+                    for l in input.links:
+                        print (l)
+                        raise RuntimeError
+                # hold it. its purpose is to gather its string variables at runtime
+                pass
         # cleanup autogen nodes
         if nc.signature[0] == "MANTIS_AUTOGENERATED" and len(nc.inputs) == 0 and len(nc.outputs) == 1:
             from .base_definitions import can_remove_socket_for_autogen
@@ -499,7 +518,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
@@ -599,7 +618,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'
@@ -612,8 +631,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'
@@ -627,10 +646,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")
@@ -656,4 +675,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
-

+ 30 - 28
schema_solve.py

@@ -51,7 +51,7 @@ class SchemaSolver:
         self.solve_length = self.node.evaluate_input("Schema Length")
         # I'm making this a property of the solver because the solver's data is modified as it solves each iteration
         self.index = 0
-        
+
         prWhite(f"\nExpanding schema {self.tree.name} in node {self.node.signature}"
                  f" with length {self.solve_length}.")
 
@@ -66,14 +66,14 @@ class SchemaSolver:
         from bpy.types import NodeGroupInput, NodeGroupOutput
         for ui_node in self.tree.nodes:
             # first we need to fill the parameters of the schema nodes.
-            # we use the bl_idname because all schema nodes should be single-instance 
+            # we use the bl_idname because all schema nodes should be single-instance
             signature = (*self.tree_path_names, ui_node.bl_idname)
             if isinstance(ui_node, (SchemaUINode, NodeGroupInput, NodeGroupOutput)):
                 # We use the schema node's "natural signature" here because it represents
                 # the "original" signature of the schema UI group node since this schema
                 # solver may be in a nested schema, and its node's signature may have
                 # uuid/index attached.
-                get_sig = (*self.node.ui_signature, ui_node.bl_idname) 
+                get_sig = (*self.node.ui_signature, ui_node.bl_idname)
                 if not (mantis_node := self.all_nodes.get(get_sig)):
                     raise RuntimeError(wrapRed(f"Not found: {get_sig}"))
                 self.schema_nodes[signature] = mantis_node
@@ -89,14 +89,14 @@ class SchemaSolver:
                 mantis_node = SchemaConstOutput(signature=signature, base_tree=self.node.base_tree, parent_schema_node=self.node)
                 self.schema_nodes[signature] = mantis_node
                 mantis_node.fill_parameters(ui_node)
-    
+
     def set_index_strings(self):
         self.index_str = lambda : '.'+str(self.uuid)+'.'+str(self.index).zfill(4)
         self.prev_index_str = lambda : '.'+str(self.uuid)+'.'+str(self.index-1).zfill(4)
         if self.is_node_group:
             self.index_str=lambda : ''
             self.prev_index_str=lambda : ''
-    
+
     def init_schema_links(self,):
         """ Sort and store the links to/from the Schema group node."""
         for item in self.tree.interface.items_tree:
@@ -110,7 +110,7 @@ class SchemaSolver:
                         if incoming_links := self.node.inputs[item.identifier].links:
                             self.incoming_connections[item.name] = incoming_links[0]
                         else:
-                            self.incoming_connections[item.name] = None 
+                            self.incoming_connections[item.name] = None
                     else: # OUTPUT
                         if outgoing_links := self.node.outputs[item.identifier].links:
                             self.outgoing_connections[item.name] = outgoing_links.copy()
@@ -143,9 +143,9 @@ class SchemaSolver:
                             self.array_output_connections[item.identifier]=[]
                         if out_links := self.node.outputs[item.identifier].links:
                             self.array_output_connections[item.identifier] = out_links.copy()
-    
 
-    
+
+
 
     def gen_solve_iteration_mantis_nodes(self, frame_mantis_nodes, unprepared):
         for prototype_ui_node in self.tree.nodes:
@@ -173,6 +173,9 @@ class SchemaSolver:
                     mantis_node = prototype_mantis_node.__class__(
                         signature, prototype_mantis_node.base_tree, prototype=ui_node,
                         ui_signature =  prototype_mantis_node.signature)
+                    # establish string variables
+                    from .utilities import set_string_variables_at_creation_time
+                    set_string_variables_at_creation_time(mantis_node, prototype_ui_node, mContext)
                     # now let's copy the links from the prototype node
                     if ui_node.bl_idname in ["MantisNodeGroup"]:
                         mantis_node.prepared = False
@@ -213,17 +216,17 @@ class SchemaSolver:
             signature = ("MANTIS_AUTOGENERATED", *self.tree_path_names[1:-1], unique_name)
             from_node = self.all_nodes.get(signature)
             if not from_node:
-                from_node = autogen_node(self.node.base_tree, ui_link.from_socket, 
+                from_node = autogen_node(self.node.base_tree, ui_link.from_socket,
                                 signature=signature, mContext=self.node.mContext)
                 from_node.parameters = {ui_link.from_socket.name:index}
                 frame_mantis_nodes[signature]=from_node; self.solved_nodes[signature]=from_node
                 self.all_nodes[signature]=from_node
             _connection = from_node.outputs[ui_link.from_socket.name].connect(node=to_node, socket=ui_link.to_socket.identifier)
-            return 
+            return
         # Since the index is already determined, it is safe to remove the socket and just keep the value.
         to_node.parameters[ui_link.to_socket.name] = index
         del to_node.inputs[ui_link.to_socket.name]
-    
+
     def handle_link_from_schema_length_input(self, frame_mantis_nodes, ui_link):
         # see, here I can just use the schema node
         _from_name, to_name = get_link_in_out(ui_link)
@@ -273,7 +276,7 @@ class SchemaSolver:
                         existing_link.die()
             # BUG may exist here.
             self.incoming_connections[ui_link.to_socket.name] = connection
-        
+
 
     def handle_link_from_constant_input(self, frame_mantis_nodes, ui_link, to_ui_node):
         incoming = self.constant_in[ui_link.from_socket.name]
@@ -288,7 +291,7 @@ class SchemaSolver:
             to_socket=ui_link.to_socket.identifier
         connection = from_node.outputs[incoming.from_socket].connect(node=to_node, socket=to_socket)
         init_connections(from_node)
-    
+
     def handle_link_from_array_input_get(self, frame_mantis_nodes, ui_link ):
         from_ui_node = ui_link.from_socket.node
         from_node = self.schema_nodes[(*self.node.ui_signature, from_ui_node.bl_idname)]
@@ -478,7 +481,7 @@ class SchemaSolver:
         # connected up correctly. between the output and the schema's generated nodes.
         # so seek BACK from the output node and grab the from-node that is connected to
         #  the link. then modify the ui_link to point to that node.
-        from .base_definitions import DummyLink 
+        from .base_definitions import DummyLink
         if not isinstance(ui_link, DummyLink): # make it a Dummy so i can modify it
             ui_link = DummyLink(ui_link.from_socket, ui_link.to_socket,
                                 multi_input_sort_id=ui_link.multi_input_sort_id)
@@ -591,7 +594,7 @@ class SchemaSolver:
             if isinstance(from_ui_node, (SchemaConstInput, NodeGroupInput)):
                 if ui_link.from_socket.name in self.constant_in.keys():
                     self.handle_link_from_constant_input( frame_mantis_nodes, ui_link, to_ui_node)
-                continue 
+                continue
             if isinstance(to_ui_node, SchemaArrayInputGet):
                 self.handle_link_to_array_input_get( frame_mantis_nodes, ui_link)
                 continue
@@ -613,7 +616,7 @@ class SchemaSolver:
                     raise e # always raise this error because it is not implemented.
                     self.handle_link_from_subschema_to_output(frame_mantis_nodes, ui_link, to_ui_node)
                 self.held_links.append(ui_link)
-                continue 
+                continue
             # HOLD these links until prep is done a little later
             if isinstance(to_ui_node, (SchemaConstOutput, NodeGroupOutput)) or isinstance(to_ui_node, SchemaArrayOutput):
                 if isinstance(from_ui_node, (MantisNodeGroup, SchemaGroup)):
@@ -630,12 +633,12 @@ class SchemaSolver:
             if isinstance(from_ui_node, SchemaArrayInputGet):
                 array_input_get_link.append(ui_link)
                 continue
-            
+
             # for any of the special cases, we hit a 'continue' block. So this connection is not special, and is made here.
             connection = link_node_containers(self.autogen_path_names, ui_link,
                                 frame_mantis_nodes, from_suffix=self.index_str(),
                                 to_suffix=self.index_str())
-        
+
         for signature, node in frame_mantis_nodes.items():
             self.solved_nodes[signature]=node
             if node.node_type == "DUMMY_SCHEMA":
@@ -651,15 +654,15 @@ class SchemaSolver:
                 init_schema_dependencies(node, self.all_nodes)
             else:
                 init_dependencies(node) # it is hard to overstate how important this single line of code is
-    
+
         # We have to prepare the nodes leading to Schema Length
         unprepared=deque()
-        for node in frame_mantis_nodes.values(): 
+        for node in frame_mantis_nodes.values():
             if node.node_type == 'DUMMY_SCHEMA' and (schema_len_in := node.inputs.get("Schema Length")):
                 for l in schema_len_in.links:
                     unprepared.append(l.from_node)
             self.prepare_nodes(unprepared)
-        
+
         # We have to prepare the nodes leading to Array Input Get
         for ui_link in array_input_get_link:
             from_name = get_link_in_out(ui_link)[0]
@@ -706,7 +709,7 @@ class SchemaSolver:
                 else:
                     self.handle_link_to_array_output(frame_mantis_nodes, self.index, ui_link, to_ui_node, from_ui_node)
         return frame_mantis_nodes
-    
+
     def solve_nested_schema(self, schema_nc):
         """ Solves all schema node groups found in this Schema. This is a recursive function, which will
             solve all levels of nested schema - since this function is called by solver.solve().
@@ -748,7 +751,7 @@ class SchemaSolver:
                             # we need to kill the link between the Schema itself and the next node and update the deps. Otherwise:confusing bugs.
                             outgoing.die(); init_dependencies(to_node)
                     # else: # the node just isn't connected out this socket.
-        
+
 
         # # solve all unsolved nested schemas...
         for schema_sig, schema_nc in self.nested_schemas.items():
@@ -770,18 +773,18 @@ class SchemaSolver:
         for conn in self.array_output_connections.values():
             for outgoing in conn:
                 all_outgoing_links.append(outgoing)
-        
+
         for outgoing in all_outgoing_links:
             to_node = outgoing.to_node
             for l in to_node.inputs[outgoing.to_socket].links:
                 if self.node == l.from_node:
                     l.die()
-        
+
         for inp in self.node.inputs.values():
             for l in inp.links:
                 init_connections(l.from_node) # to force it to have hierarchy connections with the new nodes.
-                    
-        
+
+
     def solve(self):
         if self.solve_length < 1:
             from .base_definitions import GraphError
@@ -802,4 +805,3 @@ class SchemaSolver:
         self.node.prepared = True
         prGreen(f"Schema declared {len(self.solved_nodes)} nodes.\n")
         return self.solved_nodes
-        

+ 56 - 28
utilities.py

@@ -104,10 +104,38 @@ def tree_from_nc(sig, base_tree):
             continue
         tree = tree.nodes.get(path_item).node_tree
     return tree
-    
+
 def get_node_prototype(sig, base_tree):
     return tree_from_nc(sig, base_tree).nodes.get( sig[-1] )
 
+# This one is the simplest case so it is easiest to use its own function.
+def set_string_variables_at_creation_time(n, prototype, mContext):
+    # we're gonna store the variables using the node's signature
+    prev_group_key = ''
+    prev_group_vars = {}
+    for i in range(len(n.signature[:-1])):
+        if i == 0: continue # this will cut any AUTOGEN or None in the base
+        prev_group_key+=n.signature[i]
+        prev_group_vars=mContext.string_variables.get(prev_group_key, {})
+    # IS THIS WISE???
+    from copy import deepcopy # so nothing spooky will happen
+    group_vars = mContext.string_variables["".join(n.signature[1:])] = deepcopy(prev_group_vars)
+    for input in prototype.inputs:
+        if hasattr(input, "default_value") and not input.is_linked:
+            if isinstance (input.default_value, str):
+                group_vars[input.name]=input.default_value
+            elif hasattr(input.default_value, "name"):
+                group_vars[input.name]=input.default_value.name
+
+
+def set_string_variables_during_exec(n, mContext):
+    print(n)
+    pass
+    # so for this we need to get the UI node to get the string
+    # when a node is executed, we check for string variables that were set at runtime
+    # we need the dummy node for this
+
+
 
 ##################################################################################################
 # groups and changing sockets -- this is used extensively by Schema.
@@ -460,14 +488,14 @@ def import_metarig_data(metarig_data : dict, ):
                     armature_data['matrix'][12:16], )
             )
         prGreen (armature_data['name'])
-        
+
         # have to add it to the view layer to switch modes.
         collection = get_default_collection(collection_type="ARMATURE")
         collection.objects.link(armature_object)
         # we'll do this to ensure it is actually in the scene for the mode switch
         context.scene.collection.objects.link(armature_object)
         switch_mode('EDIT', objects = [armature_object])
-        
+
         while (children):
             child_name = children.pop()
             child_data = metarig_data[child_name]
@@ -489,8 +517,8 @@ def import_metarig_data(metarig_data : dict, ):
         context.scene.collection.objects.unlink(armature_object)
     # note that this will not correct if the object exists and is wrong.
     return armature_object
-    
-        
+
+
 def import_curve_data_to_object(curve_name, curve_data):
     # the curve data will come as a single curve's data
     from bpy import data
@@ -508,7 +536,7 @@ def import_curve_data_to_object(curve_name, curve_data):
             points_collection = spline.bezier_points
         else:
             spline.points.add(len(points_data)-1) # it starts with 1 already
-            
+
         for i, point_data in enumerate(points_data):
             if spline.type == 'BEZIER':
                 pt = spline.bezier_points[i]
@@ -652,7 +680,7 @@ def init_schema_dependencies(schema, all_nc):
 
 def check_and_add_root(n, roots, include_non_hierarchy=False):
     if (include_non_hierarchy * len(n.dependencies)) > 0:
-        return 
+        return
     elif len(n.hierarchy_dependencies) > 0:
         return
     roots.append(n)
@@ -662,7 +690,7 @@ def get_link_in_out(link):
     from_name, to_name = link.from_socket.node.name, link.to_socket.node.name
     # catch special bl_idnames and bunch the connections up
     if link.from_socket.node.bl_idname in replace_types:
-        from_name = link.from_socket.node.bl_idname 
+        from_name = link.from_socket.node.bl_idname
     if link.to_socket.node.bl_idname in replace_types:
         to_name = link.to_socket.node.bl_idname
     return from_name, to_name
@@ -689,7 +717,7 @@ def link_node_containers(tree_path_names, link, local_nc, from_suffix='', to_suf
     else:
         prRed(nc_from, nc_to, (*tree_path_names, from_name+from_suffix), (*tree_path_names, to_name+to_suffix))
         raise RuntimeError(wrapRed(f"Link not connected: {nc_from} -> {nc_to} in tree" ))
-    
+
 def get_all_dependencies(nc):
     from .base_definitions import GraphError
     """ find all dependencies for a mantis node"""
@@ -707,7 +735,7 @@ def get_all_dependencies(nc):
             if new_node not in nodes_checked:
                 check_nodes.append(new_node)
     return nodes
-                
+
 def get_all_nodes_of_type(base_tree, bl_idname):
     nodes = []
     check_nodes = list(base_tree.nodes)
@@ -736,7 +764,7 @@ def trace_all_nodes_from_root(root, nodes):
             if new_node not in nodes_checked:
                 check_nodes.append(new_node)
     return nodes
-            
+
 ##################################################################################################
 # misc
 ##################################################################################################
@@ -768,7 +796,7 @@ def all_trees_in_tree(base_tree, selected=False):
                 if selected == True and node.select == False:
                     continue
                 if new_tree := getattr(node, "node_tree", None):
-                    if new_tree in trees: continue 
+                    if new_tree in trees: continue
                     new_trees.append(new_tree)
                     trees.append(new_tree)
         check_trees = new_trees
@@ -780,7 +808,7 @@ def SugiyamaGraph(tree, iterations):
         class defaultview(object):
             w,h = 1,1
             xz = (0,0)
-        
+
         graph = Graph()
         no_links = set()
         verts = {}
@@ -795,7 +823,7 @@ def SugiyamaGraph(tree, iterations):
                 no_links.add(n.name)
                 graph.add_vertex(v)
             n.select=False
-            
+
         edges = []
         inverted_edges=[]
         not_a_root = set()
@@ -816,7 +844,7 @@ def SugiyamaGraph(tree, iterations):
         try:
             from grandalf.layouts import SugiyamaLayout
             # .C[0] is the first "graph core" that contains a connected graph.
-            sug = SugiyamaLayout(graph.C[0]) 
+            sug = SugiyamaLayout(graph.C[0])
             sug.init_all()
             sug.draw(iterations)
             # Digco is good for small graphs.
@@ -833,7 +861,7 @@ def SugiyamaGraph(tree, iterations):
                     n.location.x = v.view.xy[1]
                     n.location.y = v.view.xy[0]
                     n.select = True
-        
+
         # now we can take all the input nodes and try to put them in a sensible place
         # not sure why but this absolutely does not do anything
         for n_name in no_links:
@@ -860,7 +888,7 @@ def SugiyamaGraph(tree, iterations):
                 else: # we'll just position it next to the next node
                     n.location = next_node.location
                     n.location.x -= next_node.width*1.5
-        
+
 
 def project_point_to_plane(point, origin, normal):
     return point - normal.dot(point- origin)*normal
@@ -889,8 +917,8 @@ def gen_nc_input_for_data(socket):
                         "HideSocket"                           : classes["InputBoolean"],
                         #
                         "DriverSocket"                         : None,
-                        "DriverVariableSocket"                 : None, 
-                        "FCurveSocket"                         : None, 
+                        "DriverVariableSocket"                 : None,
+                        "FCurveSocket"                         : None,
                         "KeyframeSocket"                       : None,
                         "BoneCollectionSocket"                 : classes["InputString"],
                         #
@@ -937,7 +965,7 @@ def gen_nc_input_for_data(socket):
                         "VectorEulerSocket"                    : classes["InputVector"],
                         "VectorTranslationSocket"              : classes["InputVector"],
                         "VectorScaleSocket"                    : classes["InputVector"],
-                        # Drivers             
+                        # Drivers
                         "EnumDriverVariableType"               : classes["InputString"],
                         "EnumDriverVariableEvaluationSpace"    : classes["InputString"],
                         "EnumDriverRotationMode"               : classes["InputString"],
@@ -1084,9 +1112,9 @@ def RibbonMeshEdgeLengths(m, ribbon):
         else:
             v2NextInd = bE[cap((i+1) , len(bE) - 1 )]
         v2 = m.vertices[bE[i]]; v2Next = m.vertices[v2NextInd]
-        
+
         v = v1.co.lerp(v2.co, 0.5); vNext = v1Next.co.lerp(v2Next.co, 0.5)
-        # get the center, edges may not be straight so total length 
+        # get the center, edges may not be straight so total length
         #  of one edge may be more than the ribbon center's length
         lengths.append(( v - vNext ).length)
     return lengths
@@ -1211,7 +1239,7 @@ def FindNearestPointOnWireMesh(m, pointsList):
                     v1 = vertData[i]
                     v2 = vertData[i+1]
                     prevDist = curDist
-                    offset = intersect_point_line(p, m.vertices[v1[0]].co, 
+                    offset = intersect_point_line(p, m.vertices[v1[0]].co,
                                                      m.vertices[v2[0]].co)[1]
             if (offset < 0):
                 offset = 0
@@ -1284,7 +1312,7 @@ def DetectRibbon(f, bm, skipMe):
         bEdge.append (f.loops[3].vert.index) # bottom-left
         nEdge = bm.edges.get([f.loops[1].vert, f.loops[2].vert])
         nFaces = nEdge.link_faces
-        if (len(nFaces) == 1): 
+        if (len(nFaces) == 1):
             cont = False
         else:
             for nFace in nFaces:
@@ -1297,7 +1325,7 @@ def DetectRibbon(f, bm, skipMe):
         if (cont == False): # we've reached the end, get the last two:
             tEdge.append (f.loops[1].vert.index) # top-right
             bEdge.append (f.loops[2].vert.index) # bottom-right
-            # this will create a loop for rings -- 
+            # this will create a loop for rings --
             #  "the first shall be the last and the last shall be first"
     return (tEdge,bEdge,circle)
 
@@ -1360,7 +1388,7 @@ def data_from_ribbon_mesh(m, factorsList, mat, ribbons = None, fReport = None):
         if (ribbons is None):
             if (fReport):
                 fReport(type = {'ERROR'}, message="No ribbon to get data from.")
-            else:  
+            else:
                 print ("No ribbon to get data from.")
             return None
     ret = []
@@ -1439,7 +1467,7 @@ def data_from_ribbon_mesh(m, factorsList, mat, ribbons = None, fReport = None):
 # If the sign of the error is meaningful, a simpler function
 # can be used.
 def do_bisect_search_by_magnitude(
-        owner, 
+        owner,
         attribute,
         index = None,
         test_function = None,
@@ -1492,4 +1520,4 @@ def do_bisect_search_by_magnitude(
             update_dg.update()
     else: # Loop has completed without finding a solution
         i = best_so_far
-        modify(owner, attribute, best_so_far, context = context); i+=1
+        modify(owner, attribute, best_so_far, context = context); i+=1

+ 15 - 35
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,10 +49,10 @@ 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)
-        
-        if mantis_node.execution_prepared:
-            self.color = (0.02, 0.98, 0.02) # GREEN!
-                
+
+        # if mantis_node.execution_prepared:
+        #     self.color = (0.02, 0.98, 0.02) # GREEN!
+
         # if mantis_node.execution_debug_tag:
         #     self.color = (0.02 ,0.02, 0.02)
 
@@ -108,8 +108,8 @@ 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,
                  ):
@@ -129,7 +129,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
@@ -161,7 +161,7 @@ def visualize_tree(m_nodes, base_tree, context):
             vis_tree = bpy.data.node_groups.new(base_tree.name+'_visualized', type='MantisVisualizeTree')
 
             for m in mantis_nodes:
-                nodes[m.signature]=gen_vis_node(m, vis_tree,all_links)
+                nodes[m.signature]=gen_vis_node(m, vis_tree, all_links)
                 # useful for debugging: check the connections for nodes that are
                 # not in the parsed tree or available from trace_all_nodes_from_root.
 
@@ -194,37 +194,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
-            #     return side
-            # side=side_len(len(no_links))
-            # break_me = True
-            # for i in range(side):
-            #     for j in range(side):
-            #         index = side*i+j
-            #         try:
-            #             n = no_links[index]
-            #             n.location.x = i*200
-            #             n.location.y = j*200
-            #         except IndexError:
-            #             break_me = True # it's too big, that's OK the square is definitely bigger
-            #             break
-            #     if break_me:
-            #         break
-            # from .utilities import SugiyamaGraph
-            # SugiyamaGraph(vis_tree, 1) # this can take a really long time
+
         finally:
             s = io.StringIO()
             sortby = SortKey.TIME
@@ -249,10 +229,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"}