56 Commits fcdc86d053 ... bb161c0f79

Author SHA1 Message Date
  Joseph Brandenburg bb161c0f79 v 0.13.0 Beta 2 weeks ago
  Joseph Brandenburg 2f545bea82 Cleanup misguided changes in DummyNode 2 weeks ago
  Joseph Brandenburg 909a4fe43e Get String Variables at Execution Time 2 weeks ago
  Joseph Brandenburg 9c3089b090 Fix syntax error 2 weeks ago
  Brandenburg 49b4285bb4 Fix Schema fail when Group linked to In/Outgoing 3 weeks ago
  Brandenburg 4343696338 some unsaved stuff I forgot to add. trash? 1 month ago
  Brandenburg 980a110e2e WIP string variables 1 month ago
  Joseph Brandenburg d987aeeef1 Basic implementation of Interface nodes in Schema 2 weeks ago
  Joseph Brandenburg 618292c1fb Fix: Spline IK broken 2 weeks ago
  Joseph Brandenburg 8afbe08f36 Fix: lazy parents broken 2 weeks ago
  Joseph Brandenburg 0ae5f8987f cleanup xForm get_parent_node 2 weeks ago
  Joseph Brandenburg 079bb2c4f3 Fix: Correctly Sort Links to Group Arrays 2 weeks ago
  Joseph Brandenburg 78e29ad9a5 Fix: remove many instances of hardcoded node get 2 weeks ago
  Joseph Brandenburg 29192e3deb clean up useless prints 2 weeks ago
  Joseph Brandenburg 826cb475e3 GroupInterfaceNodes at group in/out 2 weeks ago
  Brandenburg 669737d63b WIP: Route Group I/O through interface nodes 2 weeks ago
  Joseph Brandenburg fc2190cdaf Initialize Tree with correct version number 3 weeks ago
  Joseph Brandenburg 65f0a9b19d Fix: make default values work with more socket types 3 weeks ago
  Joseph Brandenburg 6a1569004d Fix: interface panel doesn't have an identifier 3 weeks ago
  Joseph Brandenburg c3db52cfbe New Feature: Default Values for base tree 3 weeks ago
  Joseph Brandenburg 01ee680b48 Fix: correct default value type for vectors 3 weeks ago
  Joseph Brandenburg 0a2a483050 Fix: default value disabled for Matrix 3 weeks ago
  Joseph Brandenburg e14e03e97e Disable "Connected To" feature 3 weeks ago
  Joseph Brandenburg c0b20ed5c9 Implement Custom Interface Classes 3 weeks ago
  Joseph Brandenburg 371c0cc05a initial versioning for new interface classes 3 weeks ago
  Joseph Brandenburg 501c025165 fix: unbound local error when updating group interface 3 weeks ago
  Joseph Brandenburg fb77125b3d update interface draw for correct UI and clarity 3 weeks ago
  Joseph Brandenburg 05bf3a0188 Interface Classes set the multi and default value now 3 weeks ago
  Joseph Brandenburg 85783ff580 Add Custom Interface Socket Types 3 weeks ago
  Joseph Brandenburg 965c6c48d6 v0.12.28 revert unhelpful broken patch 2 weeks ago
  Joseph Brandenburg d0d622a238 Revert "Fix: Nested Choose fails when linked to group output" 2 weeks ago
  Joseph Brandenburg 0eb22ed488 v 0.12.27 bug fix release 2 weeks ago
  Joseph Brandenburg 003e6b573a Fix: Nested Choose fails when linked to group output 2 weeks ago
  Joseph Brandenburg cac82c21f8 Cleanup Drivers 2 weeks ago
  Brandenburg aa15119835 Fix: Custom properties fail in Drivers 3 weeks ago
  Brandenburg 1a642ea8bb Fix: DriverVariable fails when linked to non-xForm 3 weeks ago
  Brandenburg 595c799eb8 cleanup: remove prints 3 weeks ago
  Brandenburg 022e7e8952 IO: set Mantis Version on new tree immediately 3 weeks ago
  Joseph Brandenburg 82d0d38a04 Cleanup: remove green color from visualize code 3 weeks ago
  Joseph Brandenburg 68beca0a49 Cleanup: unboundlocal error in node_group_update 3 weeks ago
  Joseph Brandenburg 21d2200458 Cleanup: Remove prints from array_choose_relink 3 weeks ago
  Joseph Brandenburg e8c9d1a784 Clean up print in file load 3 weeks ago
  Brandenburg fcdc86d053 Fix: Custom Properties don't trigger tree update 3 weeks ago
  Brandenburg b68cbab0b4 Fix: custom properties borked due to syntax error 3 weeks ago
  Brandenburg 1fbf709b10 Fix Schema fail when Group linked to In/Outgoing 3 weeks ago
  Brandenburg 2c7bfbe798 Fix: Generate Tree skill issue always led to error 4 weeks ago
  Brandenburg 31b7265818 Fix: rare buffer overflow in custom properties 4 weeks ago
  Joseph Brandenburg 8d9c4deb79 Fix: Custom Properties not Library Overridable 1 month ago
  Brandenburg 66fd20f7ea Fix: Custom Properties don't trigger tree update 3 weeks ago
  Brandenburg 190f01686b Fix: custom properties borked due to syntax error 3 weeks ago
  Brandenburg a11e4be7da Fix Schema fail when Group linked to In/Outgoing 3 weeks ago
  Brandenburg d3fdd0f109 Fix: Generate Tree skill issue always led to error 4 weeks ago
  Brandenburg 1781ab8df0 Fix: rare buffer overflow in custom properties 4 weeks ago
  Brandenburg baf1847975 some unsaved stuff I forgot to add. trash? 1 month ago
  Brandenburg 82613019b2 WIP string variables 1 month ago
  Joseph Brandenburg 60a0c8b177 Fix: Custom Properties not Library Overridable 1 month ago
21 changed files with 783 additions and 441 deletions
  1. 3 1
      .gitignore
  2. 11 8
      __init__.py
  3. 55 54
      base_definitions.py
  4. 3 3
      blender_manifest.toml
  5. 14 14
      drivers.py
  6. 43 42
      i_o.py
  7. 21 12
      internal_containers.py
  8. 46 35
      link_nodes.py
  9. 72 75
      misc_nodes.py
  10. 2 3
      node_container_common.py
  11. 1 1
      ops_nodegroup.py
  12. 3 3
      preferences.py
  13. 153 83
      readtree.py
  14. 2 2
      schema_nodes.py
  15. 18 10
      schema_nodes_ui.py
  16. 47 6
      schema_solve.py
  17. 127 0
      socket_definitions.py
  18. 82 43
      utilities.py
  19. 37 1
      versioning.py
  20. 27 27
      visualize.py
  21. 16 18
      xForm_nodes.py

+ 3 - 1
.gitignore

@@ -6,4 +6,6 @@ index.json
 mantis.zip
 mantis.zip
 mantis.*.zip
 mantis.*.zip
 widgets/*
 widgets/*
-components/*
+components/*
+mantis-beta.zip
+mantis_beta.zip

+ 11 - 8
__init__.py

@@ -16,9 +16,9 @@ from .ops_generate_tree import GenerateMantisTree
 
 
 from .utilities import prRed
 from .utilities import prRed
 
 
-MANTIS_VERSION_MAJOR=0
-MANTIS_VERSION_MINOR=12
-MANTIS_VERSION_SUB=26
+from .base_definitions import (MANTIS_VERSION_MAJOR,
+                               MANTIS_VERSION_MINOR,
+                               MANTIS_VERSION_SUB)
 
 
 classLists = [module.TellClasses() for module in [
 classLists = [module.TellClasses() for module in [
  link_nodes_ui,
  link_nodes_ui,
@@ -267,8 +267,7 @@ def execute_handler(scene):
                 node_tree.tree_valid=False
                 node_tree.tree_valid=False
 
 
 from .versioning import versioning_tasks
 from .versioning import versioning_tasks
-def node_version_update(node):
-    do_once = True
+def node_version_update(node, do_once):
     for bl_idname, task, required_kwargs in versioning_tasks:
     for bl_idname, task, required_kwargs in versioning_tasks:
         arg_map = {}
         arg_map = {}
         if 'node' in required_kwargs:
         if 'node' in required_kwargs:
@@ -279,11 +278,11 @@ def node_version_update(node):
             if do_once:
             if do_once:
                 print (f"Updating tree {node.id_data.name} to "
                 print (f"Updating tree {node.id_data.name} to "
                        f"{MANTIS_VERSION_MAJOR}.{MANTIS_VERSION_MINOR}.{MANTIS_VERSION_SUB}")
                        f"{MANTIS_VERSION_MAJOR}.{MANTIS_VERSION_MINOR}.{MANTIS_VERSION_SUB}")
-                do_once=False
             task(**arg_map)
             task(**arg_map)
 
 
 def do_version_update(node_tree):
 def do_version_update(node_tree):
     # set updating status for dynamic nodes to prevent bugs in socket remapping
     # set updating status for dynamic nodes to prevent bugs in socket remapping
+    do_once = True
     for node in node_tree.nodes:
     for node in node_tree.nodes:
         if hasattr(node, 'is_updating'):
         if hasattr(node, 'is_updating'):
             node.is_updating = True
             node.is_updating = True
@@ -296,7 +295,8 @@ def do_version_update(node_tree):
         task(**arguments)
         task(**arguments)
     # run the updates that have no prerequisites
     # run the updates that have no prerequisites
     for node in node_tree.nodes:
     for node in node_tree.nodes:
-        node_version_update(node)
+        node_version_update(node, do_once)
+        do_once = False
     # NOTE: if future versoning tasks have prerequisites, resolve them here and update again
     # NOTE: if future versoning tasks have prerequisites, resolve them here and update again
     # reset the updating status for dynamic nodes
     # reset the updating status for dynamic nodes
     for node in node_tree.nodes:
     for node in node_tree.nodes:
@@ -312,7 +312,6 @@ def version_update_handler(filename):
     for node_tree in bpy.data.node_groups: # ensure it can update again after file load.
     for node_tree in bpy.data.node_groups: # ensure it can update again after file load.
         if node_tree.bl_idname in ["MantisTree", "SchemaTree"]:
         if node_tree.bl_idname in ["MantisTree", "SchemaTree"]:
                 node_tree.is_exporting=False; node_tree.is_executing=False
                 node_tree.is_exporting=False; node_tree.is_executing=False
-
     for node_tree in bpy.data.node_groups:
     for node_tree in bpy.data.node_groups:
         if node_tree.bl_idname in ["MantisTree", "SchemaTree"]:
         if node_tree.bl_idname in ["MantisTree", "SchemaTree"]:
             if (node_tree.mantis_version[0] < MANTIS_VERSION_MAJOR) or \
             if (node_tree.mantis_version[0] < MANTIS_VERSION_MAJOR) or \
@@ -386,6 +385,10 @@ def on_undo_post_handler(scene): # the undo will trigger a depsgraph update
 from .menu_classes import (node_context_menu_draw, node_add_menu_draw,
 from .menu_classes import (node_context_menu_draw, node_add_menu_draw,
                            armature_add_menu_draw, import_menu_draw)
                            armature_add_menu_draw, import_menu_draw)
 
 
+from .socket_definitions import generate_custom_interface_types
+generated_classes = generate_custom_interface_types()
+classes.extend(generated_classes)
+
 def register():
 def register():
     from bpy.utils import register_class
     from bpy.utils import register_class
 
 

+ 55 - 54
base_definitions.py

@@ -13,6 +13,7 @@ from .utilities import (prRed, prGreen, prPurple, prWhite,
 from .utilities import get_socket_maps, relink_socket_map, do_relink
 from .utilities import get_socket_maps, relink_socket_map, do_relink
 
 
 FLOAT_EPSILON=0.0001 # used to check against floating point inaccuracy
 FLOAT_EPSILON=0.0001 # used to check against floating point inaccuracy
+links_sort_key= lambda a : (-a.multi_input_sort_id, -a.sub_sort_id)
 
 
 def TellClasses():
 def TellClasses():
     #Why use a function to do this? Because I don't need every class to register.
     #Why use a function to do this? Because I don't need every class to register.
@@ -79,6 +80,10 @@ def hash_tree(tree):
     links.sort(); hash_data+=''.join(links)
     links.sort(); hash_data+=''.join(links)
     return hash(hash_data)
     return hash(hash_data)
 
 
+MANTIS_VERSION_MAJOR=0
+MANTIS_VERSION_MINOR=13
+MANTIS_VERSION_SUB=0
+
 class MantisTree(NodeTree):
 class MantisTree(NodeTree):
     '''A custom node tree type that will show up in the editor type list'''
     '''A custom node tree type that will show up in the editor type list'''
     bl_idname = 'MantisTree'
     bl_idname = 'MantisTree'
@@ -97,7 +102,8 @@ class MantisTree(NodeTree):
     is_exporting:BoolProperty(default=False)
     is_exporting:BoolProperty(default=False)
     execution_id:StringProperty(default='')
     execution_id:StringProperty(default='')
     # prev_execution_id:StringProperty(default='')
     # prev_execution_id:StringProperty(default='')
-    mantis_version:IntVectorProperty(default=[0,9,2])
+    mantis_version:IntVectorProperty(default=[
+        MANTIS_VERSION_MAJOR, MANTIS_VERSION_MINOR, MANTIS_VERSION_SUB])
     # this prevents the node group from executing on the next depsgraph update
     # this prevents the node group from executing on the next depsgraph update
     # because I don't always have control over when the dg upadte happens.
     # because I don't always have control over when the dg upadte happens.
     prevent_next_exec:BoolProperty(default=False)
     prevent_next_exec:BoolProperty(default=False)
@@ -189,7 +195,8 @@ class SchemaTree(NodeTree):
     is_executing:BoolProperty(default=False)
     is_executing:BoolProperty(default=False)
     is_exporting:BoolProperty(default=False)
     is_exporting:BoolProperty(default=False)
 
 
-    mantis_version:IntVectorProperty(default=[0,9,2])
+    mantis_version:IntVectorProperty(default=[
+        MANTIS_VERSION_MAJOR, MANTIS_VERSION_MINOR, MANTIS_VERSION_SUB])
     # see the note in MantisTree
     # see the note in MantisTree
     interface_helper : StringProperty(default='')
     interface_helper : StringProperty(default='')
 
 
@@ -344,12 +351,9 @@ def node_group_update(node, force = False):
            (node.id_data.is_exporting == True):
            (node.id_data.is_exporting == True):
             return
             return
     # note: if (node.id_data.is_exporting == True) I need to be able to update so I can make links.
     # 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:
     if node.node_tree is None:
         node.inputs.clear(); node.outputs.clear()
         node.inputs.clear(); node.outputs.clear()
-        node.id_data.do_live_update = toggle_update
         return
         return
-
     toggle_update = node.id_data.do_live_update
     toggle_update = node.id_data.do_live_update
     node.id_data.do_live_update = False
     node.id_data.do_live_update = False
 
 
@@ -468,18 +472,18 @@ def node_group_update(node, force = False):
             reorder_collection = reorder_me_input if is_input else reorder_me_output
             reorder_collection = reorder_me_input if is_input else reorder_me_output
             if socket_map:
             if socket_map:
                 if item.identifier in socket_map.keys():
                 if item.identifier in socket_map.keys():
-                    socket = relink_socket_map_add_socket(node, socket_collection, item)
+                    socket = relink_socket_map_add_socket(node, socket_collection, item, item.in_out)
                     do_relink(node, socket, socket_map, item.in_out)
                     do_relink(node, socket, socket_map, item.in_out)
                 else:
                 else:
                     for has_socket in socket_collection:
                     for has_socket in socket_collection:
                         if has_socket.bl_idname == item.bl_socket_idname and \
                         if has_socket.bl_idname == item.bl_socket_idname and \
-                            has_socket.name == item.name:
+                                has_socket.name == item.name:
                             reorder_collection.append((has_socket, counter))
                             reorder_collection.append((has_socket, counter))
                             break
                             break
                     else:
                     else:
-                        socket = relink_socket_map_add_socket(node, socket_collection, item)
+                        socket = relink_socket_map_add_socket(node, socket_collection, item, item.in_out)
             else:
             else:
-                socket = relink_socket_map_add_socket(node, socket_collection, item)
+                socket = relink_socket_map_add_socket(node, socket_collection, item, item.in_out)
             counter += 1
             counter += 1
 
 
         # TODO: de-duplicate this hideous stuff
         # TODO: de-duplicate this hideous stuff
@@ -505,7 +509,7 @@ def node_group_update(node, force = False):
                         if exists.identifier == item.identifier:
                         if exists.identifier == item.identifier:
                             break
                             break
                     else:
                     else:
-                        update_group_sockets(item, True)
+                        update_group_sockets(item, False)
                 else:
                 else:
                     update_group_sockets(item, False)
                     update_group_sockets(item, False)
                 output_index += 1
                 output_index += 1
@@ -848,40 +852,45 @@ class MantisNode:
         for conn in self.hierarchy_connections:
         for conn in self.hierarchy_connections:
             conn.reset_execution_recursive()
             conn.reset_execution_recursive()
 
 
+    def get_interface_signature(self):
+        interface_signature = (*self.signature[:-1], 'InputInterface')
+        ui_name = self.ui_signature[-1]
+        name = self.signature[-1]
+        if ui_name != name:
+            schema_suffix = ''
+            for i in range(len(name)):
+                if name[i] == ui_name[i]: continue
+                break
+            schema_suffix = name[i+1:]
+            interface_signature = (*interface_signature[:-1],
+                                'InputInterface'+schema_suffix)
+        return interface_signature
 
 
     # TODO: make this MUCH more efficient!
     # TODO: make this MUCH more efficient!
     # alternatively: call this ONCE when initializing the tree, precache results?
     # alternatively: call this ONCE when initializing the tree, precache results?
     def apply_string_variables(self, string):
     def apply_string_variables(self, string):
         # We get the mContext, iterate through the signature, and string-replace variables
         # 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.
         # 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
+        result=string; name=""
         for i in range(len(self.signature[:-1])):
         for i in range(len(self.signature[:-1])):
-            if i == 0:
-                continue
+            if i == 0: continue # it is None or AUTOGENERATED or something
             name+=self.signature[i]
             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
+        vars = self.mContext.string_variables.get(name, None)
+        if vars is None:
+            raise RuntimeError("Can't get string variables for node")
+        elif not vars:
+            prWhite(f"INFO: No vars available for {self}")
+        var_name_keys = list(vars.keys()); var_name_keys.sort(key=lambda a : -len(a))
+        for var_name in var_name_keys:
+            var_value = vars[var_name]
+            if var_value is None:
+                interface_signature = self.get_interface_signature()
+                interface_node = self.base_tree.parsed_tree.get(interface_signature)
+                from .utilities import set_string_variables_at_execution
+                set_string_variables_at_execution(interface_node, var_name)
+                var_value = vars[var_name]                
+            result = result.replace("$"+var_name, var_value)
+        return result
 
 
     def evaluate_input(self, input_name, index=0)  -> Any:
     def evaluate_input(self, input_name, index=0)  -> Any:
         from .node_container_common import trace_single_line
         from .node_container_common import trace_single_line
@@ -890,17 +899,9 @@ class MantisNode:
         # this trace() should give a key error if there is a problem
         # 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.
         #  it is NOT handled here because it should NOT happen - so I want the error message.
         trace = trace_single_line(self, input_name, index)
         trace = trace_single_line(self, input_name, index)
-        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)
+        prop = trace[0][-1].parameters[trace[1].name] #trace[0] = the list of traced nodes; read its parameters
+        # apply the string variables if possible
+        if isinstance(prop, str) and "$" in prop: prop = self.apply_string_variables(prop)
         return prop
         return prop
 
 
     def fill_parameters(self, ui_node=None)  -> None:
     def fill_parameters(self, ui_node=None)  -> None:
@@ -943,7 +944,6 @@ class MantisNode:
            nodes are discovered, the method is called by each node in dependency order.
            nodes are discovered, the method is called by each node in dependency order.
            The first argument MUST be the name of the method as a string.
            The first argument MUST be the name of the method as a string.
         """
         """
-        prGreen(self)
         if args[0] == 'call_on_all_ancestors': raise RuntimeError("Very funny!")
         if args[0] == 'call_on_all_ancestors': raise RuntimeError("Very funny!")
         from .utilities import get_all_dependencies
         from .utilities import get_all_dependencies
         from collections import deque
         from collections import deque
@@ -954,7 +954,6 @@ class MantisNode:
         solved = set()
         solved = set()
         while can_solve:
         while can_solve:
             node = can_solve.pop()
             node = can_solve.pop()
-            print(node)
             method = getattr(node, args[0])
             method = getattr(node, args[0])
             method(*args[0:], **kwargs)
             method(*args[0:], **kwargs)
             solved.add(node)
             solved.add(node)
@@ -1051,7 +1050,7 @@ class NodeLink:
     to_node = None
     to_node = None
     to_socket = None
     to_socket = None
 
 
-    def __init__(self, from_node, from_socket, to_node, to_socket, multi_input_sort_id=0):
+    def __init__(self, from_node, from_socket, to_node, to_socket, multi_input_sort_id=0, sub_sort_id=0):
         if from_node.signature == to_node.signature:
         if from_node.signature == to_node.signature:
             raise RuntimeError("Cannot connect a node to itself.")
             raise RuntimeError("Cannot connect a node to itself.")
         self.from_node = from_node
         self.from_node = from_node
@@ -1060,7 +1059,8 @@ class NodeLink:
         self.to_socket = to_socket
         self.to_socket = to_socket
         self.from_node.outputs[self.from_socket].links.append(self)
         self.from_node.outputs[self.from_socket].links.append(self)
         # it is the responsibility of the node that uses these links to sort them correctly based on the sort_id
         # it is the responsibility of the node that uses these links to sort them correctly based on the sort_id
-        self.multi_input_sort_id = multi_input_sort_id
+        self.multi_input_sort_id = multi_input_sort_id # this is the sort_id of the link in the UI
+        self.sub_sort_id = sub_sort_id # this is for sorting within a  bundled link (one link in the UI)
         self.to_node.inputs[self.to_socket].links.append(self)
         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_hierarchy = detect_hierarchy_link(from_node, from_socket, to_node, to_socket,)
         self.is_alive = True
         self.is_alive = True
@@ -1109,7 +1109,7 @@ class NodeSocket:
         if (traverse_target):
         if (traverse_target):
             self.can_traverse = True
             self.can_traverse = True
 
 
-    def connect(self, node, socket, sort_id=0):
+    def connect(self, node, socket, sort_id=0, sub_sort_id=0):
         if  (self.is_input):
         if  (self.is_input):
             to_node   = self.node; from_node = node
             to_node   = self.node; from_node = node
             to_socket = self.name; from_socket = socket
             to_socket = self.name; from_socket = socket
@@ -1126,7 +1126,8 @@ class NodeSocket:
                 from_socket,
                 from_socket,
                 to_node,
                 to_node,
                 to_socket,
                 to_socket,
-                sort_id)
+                sort_id,
+                sub_sort_id)
         return new_link
         return new_link
 
 
     def set_traverse_target(self, traverse_target):
     def set_traverse_target(self, traverse_target):
@@ -1136,7 +1137,7 @@ class NodeSocket:
     def flush_links(self):
     def flush_links(self):
         """ Removes dead links from this socket."""
         """ Removes dead links from this socket."""
         self.links = [l for l in self.links if l.is_alive]
         self.links = [l for l in self.links if l.is_alive]
-        self.links.sort(key=lambda a : -a.multi_input_sort_id)
+        self.links.sort(key=links_sort_key)
         self.is_linked = bool(self.links)
         self.is_linked = bool(self.links)
 
 
     @property
     @property

+ 3 - 3
blender_manifest.toml

@@ -2,9 +2,9 @@ schema_version = "1.0.0"
 
 
 # Example of manifest file for a Blender extension
 # Example of manifest file for a Blender extension
 # Change the values according to your extension
 # Change the values according to your extension
-id = "mantis"
-version = "0.12.26"
-name = "Mantis"
+id = "mantis_beta"
+version = "0.13.0"
+name = "Mantis (Beta)"
 tagline = "Mantis is a rigging nodes toolkit"
 tagline = "Mantis is a rigging nodes toolkit"
 maintainer = "Nodespaghetti <josephbburg@protonmail.com>"
 maintainer = "Nodespaghetti <josephbburg@protonmail.com>"
 # Supported types: "add-on", "theme"
 # Supported types: "add-on", "theme"

+ 14 - 14
drivers.py

@@ -68,7 +68,7 @@ def CreateDrivers(drivers):
         drv.type = driver["type"]
         drv.type = driver["type"]
         if (expr := driver.get("expression")) and isinstance(expr, str):
         if (expr := driver.get("expression")) and isinstance(expr, str):
             drv.expression = expr
             drv.expression = expr
-        
+
         fc.extrapolation = "CONSTANT"
         fc.extrapolation = "CONSTANT"
         if (extrapolation_mode := driver.get("extrapolation")) in ("CONSTANT", "LINEAR"):
         if (extrapolation_mode := driver.get("extrapolation")) in ("CONSTANT", "LINEAR"):
             fc.extrapolation = extrapolation_mode
             fc.extrapolation = extrapolation_mode
@@ -76,12 +76,12 @@ def CreateDrivers(drivers):
             prRed(f"Extrapolation Mode in driver has incorrect data: {extrapolation_mode}")
             prRed(f"Extrapolation Mode in driver has incorrect data: {extrapolation_mode}")
 
 
         # logic for handling type can go here
         # logic for handling type can go here
-        
+
         # start by clearing
         # start by clearing
         while (len(drv.variables) > 0):
         while (len(drv.variables) > 0):
             v = drv.variables[0]
             v = drv.variables[0]
             dVar = drv.variables.remove(v)
             dVar = drv.variables.remove(v)
-            
+
         for v in driver["vars"]:
         for v in driver["vars"]:
             pose_bone = False
             pose_bone = False
             bone = ''; target2bone = ''
             bone = ''; target2bone = ''
@@ -93,40 +93,42 @@ def CreateDrivers(drivers):
                 vob = v["owner"].id_data
                 vob = v["owner"].id_data
                 bone = v["owner"].name
                 bone = v["owner"].name
             #
             #
-            
+
             if "xForm 2" in v.keys() and v["xForm 2"]:
             if "xForm 2" in v.keys() and v["xForm 2"]:
                 if (isinstance(v["xForm 2"], Object)):
                 if (isinstance(v["xForm 2"], Object)):
                     target2ob = v["xForm 2"]
                     target2ob = v["xForm 2"]
                 else:
                 else:
                     target2ob = v["xForm 2"].id_data
                     target2ob = v["xForm 2"].id_data
                     target2bone = v["xForm 2"].name
                     target2bone = v["xForm 2"].name
-            
+
             dVar = drv.variables.new()
             dVar = drv.variables.new()
-            
-            
+
+
             dVar.name = v["name"]
             dVar.name = v["name"]
             dVar.type = v["type"]
             dVar.type = v["type"]
             #for now, assume this is always true:
             #for now, assume this is always true:
             #dVar.targets[0].id_type = "OBJECT"
             #dVar.targets[0].id_type = "OBJECT"
             #it's possible to use other datablocks, but this is not commonly done
             #it's possible to use other datablocks, but this is not commonly done
             #actually, it looks like Blender figures this out for me.
             #actually, it looks like Blender figures this out for me.
-            
+
             dVar.targets[0].id = vob
             dVar.targets[0].id = vob
             dVar.targets[0].bone_target = bone
             dVar.targets[0].bone_target = bone
             if len(dVar.targets) > 1:
             if len(dVar.targets) > 1:
                 dVar.targets[1].id = target2ob
                 dVar.targets[1].id = target2ob
                 dVar.targets[1].bone_target = target2bone
                 dVar.targets[1].bone_target = target2bone
-            
+
             if (dVar.type == "TRANSFORMS"):
             if (dVar.type == "TRANSFORMS"):
                 dVar.targets[0].transform_space = v["space"]
                 dVar.targets[0].transform_space = v["space"]
                 dVar.targets[0].transform_type = v["channel"]
                 dVar.targets[0].transform_type = v["channel"]
             if (dVar.type == 'SINGLE_PROP'):
             if (dVar.type == 'SINGLE_PROP'):
                 if pose_bone:
                 if pose_bone:
                     stub = "pose.bones[\""+v["owner"].name+"\"]"
                     stub = "pose.bones[\""+v["owner"].name+"\"]"
+                    dVar.targets[0].data_path = stub + brackets(v["prop"])
                     if (hasattr( v["owner"], v["prop"] )):
                     if (hasattr( v["owner"], v["prop"] )):
                         dVar.targets[0].data_path = stub + "."+ (v["prop"])
                         dVar.targets[0].data_path = stub + "."+ (v["prop"])
-                    else:
-                        dVar.targets[0].data_path = stub + brackets(v["prop"])
+                    # else: # the property may be added later.
+                    # TODO BUG I want a guarantee that this property is already there.
+
                 else:
                 else:
                     if (hasattr( v["owner"], v["prop"] )):
                     if (hasattr( v["owner"], v["prop"] )):
                         dVar.targets[0].data_path = (v["prop"])
                         dVar.targets[0].data_path = (v["prop"])
@@ -136,7 +138,7 @@ def CreateDrivers(drivers):
         kp = fc.keyframe_points
         kp = fc.keyframe_points
         for key in driver["keys"]:
         for key in driver["keys"]:
             k = kp.insert(frame=key["co"][0], value = key["co"][1],)
             k = kp.insert(frame=key["co"][0], value = key["co"][1],)
-            
+
             k.interpolation     = key["interpolation"]
             k.interpolation     = key["interpolation"]
             if (key["interpolation"] == 'BEZIER'):
             if (key["interpolation"] == 'BEZIER'):
                 k.handle_left_type  = key["handle_left_type" ]
                 k.handle_left_type  = key["handle_left_type" ]
@@ -146,5 +148,3 @@ def CreateDrivers(drivers):
                 if (k.handle_right_type in ("ALIGNED", "VECTOR", "FREE")):
                 if (k.handle_right_type in ("ALIGNED", "VECTOR", "FREE")):
                     k.handle_right      = (k.co[0] + key["handle_right"][0], k.co[1] + key["handle_right"][1])
                     k.handle_right      = (k.co[0] + key["handle_right"][0], k.co[1] + key["handle_right"][1])
             k.type = key["type"]
             k.type = key["type"]
-      
-      

+ 43 - 42
i_o.py

@@ -49,7 +49,7 @@ prop_ignore = [ "__dict__", "__doc__", "__module__", "__weakref__",# "name",
                 # these are in Bone
                 # these are in Bone
                 "socket_count", "display_bb_settings", "display_def_settings",
                 "socket_count", "display_bb_settings", "display_def_settings",
                 "display_ik_settings", "display_vp_settings",
                 "display_ik_settings", "display_vp_settings",
-                ] 
+                ]
 # don't ignore: "bl_idname", "bl_label",
 # don't ignore: "bl_idname", "bl_label",
 # ignore the name, it's the dict - key for the node props
 # ignore the name, it's the dict - key for the node props
     # no that's stupid don't ignore the name good grief
     # no that's stupid don't ignore the name good grief
@@ -127,7 +127,7 @@ def fix_custom_parameter(n, property_definition, ):
     if n.bl_idname in ['xFormNullNode', 'xFormBoneNode', 'xFormArmatureNode', 'xFormGeometryObjectNode',]:
     if n.bl_idname in ['xFormNullNode', 'xFormBoneNode', 'xFormArmatureNode', 'xFormGeometryObjectNode',]:
         prop_name = property_definition["name"]
         prop_name = property_definition["name"]
         prop_type = property_definition["bl_idname"]
         prop_type = property_definition["bl_idname"]
-        
+
         if prop_type in ['ParameterBoolSocket', 'ParameterIntSocket', 'ParameterFloatSocket', 'ParameterVectorSocket' ]:
         if prop_type in ['ParameterBoolSocket', 'ParameterIntSocket', 'ParameterFloatSocket', 'ParameterVectorSocket' ]:
             # is it good to make both of them?
             # is it good to make both of them?
             input = n.inputs.new( prop_type, prop_name)
             input = n.inputs.new( prop_type, prop_name)
@@ -135,7 +135,7 @@ def fix_custom_parameter(n, property_definition, ):
             if property_definition["is_output"] == True:
             if property_definition["is_output"] == True:
                 return output
                 return output
             return input
             return input
-    
+
     elif n.bl_idname in ['LinkArmature']:
     elif n.bl_idname in ['LinkArmature']:
         prop_name = property_definition["name"]
         prop_name = property_definition["name"]
         prop_type = property_definition["bl_idname"]
         prop_type = property_definition["bl_idname"]
@@ -173,7 +173,7 @@ def fix_custom_parameter(n, property_definition, ):
 # to see if a dependency is created by node connections.
 # to see if a dependency is created by node connections.
 # TODO it remains to be seen if that is even a desirable behaviour.
 # TODO it remains to be seen if that is even a desirable behaviour.
 def scan_tree_for_objects(base_tree, current_tree):
 def scan_tree_for_objects(base_tree, current_tree):
-    # this should work 
+    # this should work
     armatures, curves    = set(), set()
     armatures, curves    = set(), set()
     if current_tree == base_tree:
     if current_tree == base_tree:
         scan_tree_dependencies(base_tree, curves, armatures,)
         scan_tree_dependencies(base_tree, curves, armatures,)
@@ -310,7 +310,7 @@ def get_curve_for_pack(object):
 
 
 def matrix_as_tuple(matrix):
 def matrix_as_tuple(matrix):
     return ( matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3],
     return ( matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3],
-             matrix[1][0], matrix[1][1], matrix[1][2], matrix[1][3], 
+             matrix[1][0], matrix[1][1], matrix[1][2], matrix[1][3],
              matrix[2][0], matrix[2][1], matrix[2][2], matrix[2][3],
              matrix[2][0], matrix[2][1], matrix[2][2], matrix[2][3],
              matrix[3][0], matrix[3][1], matrix[3][2], matrix[3][3], )
              matrix[3][0], matrix[3][1], matrix[3][2], matrix[3][3], )
 
 
@@ -322,7 +322,7 @@ class metabone_data:
     matrix                : tuple[float] = field(default=()),
     matrix                : tuple[float] = field(default=()),
     parent                : str = field(default=''),
     parent                : str = field(default=''),
     length                : float = field(default=-1.0),
     length                : float = field(default=-1.0),
-    children              : list[str] = field(default_factory=[]), 
+    children              : list[str] = field(default_factory=[]),
 # keep it really simple for now. I'll add BBone and envelope later on
 # keep it really simple for now. I'll add BBone and envelope later on
 # when I make them accessible from the meta-rig
 # when I make them accessible from the meta-rig
 
 
@@ -361,7 +361,7 @@ def get_socket_data(socket, ignore_if_default=False):
     socket_data["bl_idname"] = socket.bl_idname
     socket_data["bl_idname"] = socket.bl_idname
     socket_data["is_output"] = socket.is_output
     socket_data["is_output"] = socket.is_output
     socket_data["is_multi_input"] = socket.is_multi_input
     socket_data["is_multi_input"] = socket.is_multi_input
-    
+
     # here is where we'll handle a socket_data'socket special data
     # here is where we'll handle a socket_data'socket special data
     if socket.bl_idname == "EnumMetaBoneSocket":
     if socket.bl_idname == "EnumMetaBoneSocket":
         socket_data["bone"] = socket.bone
         socket_data["bone"] = socket.bone
@@ -454,7 +454,7 @@ def get_tree_data(tree):
     tree_info = {}
     tree_info = {}
     for propname  in dir(tree):
     for propname  in dir(tree):
         # if getattr(tree, propname):
         # if getattr(tree, propname):
-        #     pass  
+        #     pass
         if (propname in prop_ignore_tree) or ( callable(getattr(tree, propname)) ):
         if (propname in prop_ignore_tree) or ( callable(getattr(tree, propname)) ):
             continue
             continue
         v = getattr(tree, propname)
         v = getattr(tree, propname)
@@ -489,7 +489,7 @@ def get_interface_data(tree, tree_in_out):
                 bl_socket_idname = 'VectorSocket'
                 bl_socket_idname = 'VectorSocket'
             elif bl_socket_idname == 'NodeSocketInt':
             elif bl_socket_idname == 'NodeSocketInt':
                 bl_socket_idname = 'IntSocket'
                 bl_socket_idname = 'IntSocket'
-            
+
             try:
             try:
                 socket_class = getattr(socket_definitions, bl_socket_idname)
                 socket_class = getattr(socket_definitions, bl_socket_idname)
             except AttributeError: # sometimes the class doesn't work.
             except AttributeError: # sometimes the class doesn't work.
@@ -524,7 +524,7 @@ def get_interface_data(tree, tree_in_out):
             else:
             else:
                 sock_data["socket_type"] = sock.bl_socket_idname
                 sock_data["socket_type"] = sock.bl_socket_idname
             tree_in_out[sock.identifier] = sock_data
             tree_in_out[sock.identifier] = sock_data
-            
+
 
 
 def export_to_json(trees, base_tree=None, path="", write_file=True, only_selected=False, ):
 def export_to_json(trees, base_tree=None, path="", write_file=True, only_selected=False, ):
     export_data = {}
     export_data = {}
@@ -532,7 +532,7 @@ def export_to_json(trees, base_tree=None, path="", write_file=True, only_selecte
         current_tree_is_base_tree = False
         current_tree_is_base_tree = False
         if tree is trees[-1]:
         if tree is trees[-1]:
             current_tree_is_base_tree = True
             current_tree_is_base_tree = True
-        
+
         tree_info, tree_in_out = {}, {}
         tree_info, tree_in_out = {}, {}
         tree_info = get_tree_data(tree)
         tree_info = get_tree_data(tree)
         curves, metarig_data = {}, {}
         curves, metarig_data = {}, {}
@@ -558,7 +558,7 @@ def export_to_json(trees, base_tree=None, path="", write_file=True, only_selecte
             if only_selected and node.select == False:
             if only_selected and node.select == False:
                 continue
                 continue
             nodes[node.name] = get_node_data(node)
             nodes[node.name] = get_node_data(node)
-            
+
         links = []
         links = []
         in_sockets, out_sockets = {}, {}
         in_sockets, out_sockets = {}, {}
         unique_sockets_from, unique_sockets_to = {}, {}
         unique_sockets_from, unique_sockets_to = {}, {}
@@ -587,7 +587,7 @@ def export_to_json(trees, base_tree=None, path="", write_file=True, only_selecte
             else:
             else:
                 problem = link.to_node.name + "::" + link.to_socket.name
                 problem = link.to_node.name + "::" + link.to_socket.name
                 raise RuntimeError(wrapRed(f"Error saving index of socket: {problem}"))
                 raise RuntimeError(wrapRed(f"Error saving index of socket: {problem}"))
-            
+
             if current_tree_is_base_tree:
             if current_tree_is_base_tree:
                 if (only_selected and link.from_node.select) and (not link.to_node.select):
                 if (only_selected and link.from_node.select) and (not link.to_node.select):
                     # handle an output in the tree
                     # handle an output in the tree
@@ -670,7 +670,7 @@ def export_to_json(trees, base_tree=None, path="", write_file=True, only_selecte
                         sock_data["in_out"]="INPUT"
                         sock_data["in_out"]="INPUT"
                         sock_data["index"]=in_sock["index"]
                         sock_data["index"]=in_sock["index"]
 
 
-                        
+
                         tree_in_out[sock_name] = sock_data
                         tree_in_out[sock_name] = sock_data
 
 
                     from_node_name=in_node.get("name")
                     from_node_name=in_node.get("name")
@@ -690,8 +690,8 @@ def export_to_json(trees, base_tree=None, path="", write_file=True, only_selecte
                            to_input_index,
                            to_input_index,
                            from_socket_name,
                            from_socket_name,
                            to_socket_name) ) # it's a tuple
                            to_socket_name) ) # it's a tuple
-        
-        
+
+
         if add_input_node or add_output_node:
         if add_input_node or add_output_node:
             all_nodes_bounding_box=[Vector((float("inf"),float("inf"))), Vector((-float("inf"),-float("inf")))]
             all_nodes_bounding_box=[Vector((float("inf"),float("inf"))), Vector((-float("inf"),-float("inf")))]
             for n in nodes.values():
             for n in nodes.values():
@@ -714,7 +714,7 @@ def export_to_json(trees, base_tree=None, path="", write_file=True, only_selecte
             nodes["MANTIS_AUTOGEN_GROUP_OUTPUT"]=out_node
             nodes["MANTIS_AUTOGEN_GROUP_OUTPUT"]=out_node
 
 
         export_data[tree.name] = (tree_info, tree_in_out, nodes, links, curves, metarig_data,) # f_curves)
         export_data[tree.name] = (tree_info, tree_in_out, nodes, links, curves, metarig_data,) # f_curves)
-    
+
     return export_data
     return export_data
 
 
 def write_json_data(data, path):
 def write_json_data(data, path):
@@ -722,7 +722,7 @@ def write_json_data(data, path):
     with open(path, "w") as file:
     with open(path, "w") as file:
         print(wrapWhite("Writing mantis tree data to: "), wrapGreen(file.name))
         print(wrapWhite("Writing mantis tree data to: "), wrapGreen(file.name))
         file.write( json.dumps(data, indent = 4) )
         file.write( json.dumps(data, indent = 4) )
-        
+
 def get_link_sockets(link, tree, tree_socket_id_map):
 def get_link_sockets(link, tree, tree_socket_id_map):
     from_node_name = link[0]
     from_node_name = link[0]
     from_socket_id = link[1]
     from_socket_id = link[1]
@@ -759,7 +759,7 @@ def get_link_sockets(link, tree, tree_socket_id_map):
         id1 = tree_socket_id_map.get(from_socket_id)
         id1 = tree_socket_id_map.get(from_socket_id)
     for from_sock in from_node.outputs:
     for from_sock in from_node.outputs:
         if from_sock.identifier == id1: break
         if from_sock.identifier == id1: break
-    else: 
+    else:
         from_sock = None
         from_sock = None
 
 
     id2 = to_socket_id
     id2 = to_socket_id
@@ -908,7 +908,7 @@ def do_import_from_file(filepath, context):
     for tree in all_trees:
     for tree in all_trees:
         tree.is_exporting = True
         tree.is_exporting = True
         tree.do_live_update = False
         tree.do_live_update = False
-    
+
     def do_cleanup(tree):
     def do_cleanup(tree):
         tree.is_exporting = False
         tree.is_exporting = False
         tree.do_live_update = True
         tree.do_live_update = True
@@ -954,6 +954,8 @@ def do_import(data, context, search_multi_files=False, filepath='', skip_existin
         tree_info = tree_data[0]
         tree_info = tree_data[0]
         tree_in_out = tree_data[1]
         tree_in_out = tree_data[1]
 
 
+        print (tree_info)
+
 
 
         # TODO: IMPORT THIS DATA HERE!!!
         # TODO: IMPORT THIS DATA HERE!!!
         try:
         try:
@@ -962,14 +964,12 @@ def do_import(data, context, search_multi_files=False, filepath='', skip_existin
         except IndexError: # shouldn't happen but maybe someone has an old file
         except IndexError: # shouldn't happen but maybe someone has an old file
             curves = {}
             curves = {}
             armatures = {}
             armatures = {}
-        
+
         for curve_name, curve_data in curves.items():
         for curve_name, curve_data in curves.items():
             from .utilities import import_curve_data_to_object, import_metarig_data
             from .utilities import import_curve_data_to_object, import_metarig_data
             import_curve_data_to_object(curve_name, curve_data)
             import_curve_data_to_object(curve_name, curve_data)
             for armature_name, armature_data in armatures.items():
             for armature_name, armature_data in armatures.items():
                 import_metarig_data(armature_data)
                 import_metarig_data(armature_data)
-        
-
 
 
         # need to make a new tree; first, try to get it:
         # need to make a new tree; first, try to get it:
         tree = bpy.data.node_groups.get(tree_info["name"])
         tree = bpy.data.node_groups.get(tree_info["name"])
@@ -978,21 +978,22 @@ def do_import(data, context, search_multi_files=False, filepath='', skip_existin
             continue # already done here because the tree already exists.
             continue # already done here because the tree already exists.
         if tree is None:
         if tree is None:
             tree = bpy.data.node_groups.new(tree_info["name"], tree_info["bl_idname"])
             tree = bpy.data.node_groups.new(tree_info["name"], tree_info["bl_idname"])
+        tree.mantis_version = tree_info['mantis_version']
         tree.nodes.clear(); tree.links.clear(); tree.interface.clear()
         tree.nodes.clear(); tree.links.clear(); tree.interface.clear()
         # this may be a bad bad thing to do without some kind of warning TODO TODO
         # this may be a bad bad thing to do without some kind of warning TODO TODO
         tree.is_executing = True
         tree.is_executing = True
         tree.do_live_update = False
         tree.do_live_update = False
         trees.append(tree)
         trees.append(tree)
-        
+
         tree_sock_id_map = {}
         tree_sock_id_map = {}
         tree_sock_id_maps[tree.name] = tree_sock_id_map
         tree_sock_id_maps[tree.name] = tree_sock_id_map
-        
+
         interface_parent_me = {}
         interface_parent_me = {}
 
 
         # I need to guarantee that the interface items are in the right order.
         # I need to guarantee that the interface items are in the right order.
         interface_sockets = [] # I'll just sort them afterwards so I hold them here.
         interface_sockets = [] # I'll just sort them afterwards so I hold them here.
         default_position=0 # We'll use this if the position attribute is not set when e.g. making groups.
         default_position=0 # We'll use this if the position attribute is not set when e.g. making groups.
-        
+
 
 
         for s_name, s_props in tree_in_out.items():
         for s_name, s_props in tree_in_out.items():
             if s_props["item_type"] == 'SOCKET':
             if s_props["item_type"] == 'SOCKET':
@@ -1016,14 +1017,14 @@ def do_import(data, context, search_multi_files=False, filepath='', skip_existin
                     interface_parent_me[sock] = (panel, s_props["position"])
                     interface_parent_me[sock] = (panel, s_props["position"])
             else: # it's a panel
             else: # it's a panel
                 panel = tree.interface.new_panel(s_props["name"], description=s_props.get("description"), default_closed=s_props.get("default_closed"))
                 panel = tree.interface.new_panel(s_props["name"], description=s_props.get("description"), default_closed=s_props.get("default_closed"))
-    
+
         for socket, (panel, index) in interface_parent_me.items():
         for socket, (panel, index) in interface_parent_me.items():
             tree.interface.move_to_parent(
             tree.interface.move_to_parent(
                                     socket,
                                     socket,
                                     tree.interface.items_tree.get(panel),
                                     tree.interface.items_tree.get(panel),
                                     index,
                                     index,
                                     )
                                     )
-        
+
         # BUG this was screwing up the order of things
         # BUG this was screwing up the order of things
         # so I want to fix it and re-enable it
         # so I want to fix it and re-enable it
         if True:
         if True:
@@ -1031,7 +1032,7 @@ def do_import(data, context, search_multi_files=False, filepath='', skip_existin
             interface_sockets.sort(key=lambda a : a[1])
             interface_sockets.sort(key=lambda a : a[1])
             for (socket, position) in interface_sockets:
             for (socket, position) in interface_sockets:
                 tree.interface.move(socket, position)
                 tree.interface.move(socket, position)
-        
+
     # Now go and do nodes and links
     # Now go and do nodes and links
     for tree_name, tree_data in data.items():
     for tree_name, tree_data in data.items():
         if tree_name in skip_trees:
         if tree_name in skip_trees:
@@ -1041,7 +1042,7 @@ def do_import(data, context, search_multi_files=False, filepath='', skip_existin
         tree_info = tree_data[0]
         tree_info = tree_data[0]
         nodes = tree_data[2]
         nodes = tree_data[2]
         links = tree_data[3]
         links = tree_data[3]
-        
+
         parent_me = []
         parent_me = []
 
 
         tree = bpy.data.node_groups.get(tree_info["name"])
         tree = bpy.data.node_groups.get(tree_info["name"])
@@ -1051,9 +1052,9 @@ def do_import(data, context, search_multi_files=False, filepath='', skip_existin
         trees.append(tree)
         trees.append(tree)
 
 
         tree_sock_id_map=tree_sock_id_maps[tree.name]
         tree_sock_id_map=tree_sock_id_maps[tree.name]
-        
+
         interface_parent_me = {}
         interface_parent_me = {}
-        
+
 #        from mantis.utilities import prRed, prWhite, prOrange, prGreen
 #        from mantis.utilities import prRed, prWhite, prOrange, prGreen
         for name, propslist in nodes.items():
         for name, propslist in nodes.items():
             bl_idname = propslist["bl_idname"]
             bl_idname = propslist["bl_idname"]
@@ -1089,7 +1090,7 @@ def do_import(data, context, search_multi_files=False, filepath='', skip_existin
                                 "SchemaIncomingConnection",]:
                                 "SchemaIncomingConnection",]:
                 n.update()
                 n.update()
 
 
-            
+
             if sub_tree := propslist.get("node_tree"):
             if sub_tree := propslist.get("node_tree"):
                 # now that I am doing multi-file exports, this is tricky
                 # now that I am doing multi-file exports, this is tricky
                 # we need to see if the tree exists and if not, recurse
                 # we need to see if the tree exists and if not, recurse
@@ -1127,13 +1128,13 @@ def do_import(data, context, search_multi_files=False, filepath='', skip_existin
                          "sockets",
                          "sockets",
                          "inputs",
                          "inputs",
                          "outputs",
                          "outputs",
-                         "warning_propagation", 
+                         "warning_propagation",
                          "socket_idname"]:
                          "socket_idname"]:
                     continue
                     continue
                 # will throw AttributeError if read-only
                 # will throw AttributeError if read-only
                 # will throw TypeError if wrong type...
                 # will throw TypeError if wrong type...
                 if n.bl_idname == "NodeFrame" and p in ["width, height, location"]:
                 if n.bl_idname == "NodeFrame" and p in ["width, height, location"]:
-                    continue 
+                    continue
                 if version  < (4,4,0) and p == 'location_absolute':
                 if version  < (4,4,0) and p == 'location_absolute':
                     continue
                     continue
                 if p == "parent" and v is not None:
                 if p == "parent" and v is not None:
@@ -1144,7 +1145,7 @@ def do_import(data, context, search_multi_files=False, filepath='', skip_existin
                 except Exception as e:
                 except Exception as e:
                     prRed (p)
                     prRed (p)
                     raise e
                     raise e
-                
+
 
 
         for l in links:
         for l in links:
             from_socket_name = l[6]
             from_socket_name = l[6]
@@ -1168,16 +1169,16 @@ def do_import(data, context, search_multi_files=False, filepath='', skip_existin
                     raise RuntimeError
                     raise RuntimeError
                 else:
                 else:
                     prRed(f"Failed to add link in {tree.name}: {name1}:{from_socket_name}, {name2}:{to_socket_name}")
                     prRed(f"Failed to add link in {tree.name}: {name1}:{from_socket_name}, {name2}:{to_socket_name}")
-            
+
             # if at this point it doesn't work... we need to fix
             # if at this point it doesn't work... we need to fix
         for name, p in parent_me:
         for name, p in parent_me:
             if (n := tree.nodes.get(name)) and (p := tree.nodes.get(p)):
             if (n := tree.nodes.get(name)) and (p := tree.nodes.get(p)):
                 n.parent = p
                 n.parent = p
             # otherwise the frame node is missing because it was not included in the data e.g. when grouping nodes.
             # otherwise the frame node is missing because it was not included in the data e.g. when grouping nodes.
-        
+
         tree.is_executing = False
         tree.is_executing = False
         tree.do_live_update = True
         tree.do_live_update = True
-        
+
 
 
 def export_multi_file(trees : list,  base_tree, filepath : str, base_name :str) -> None:
 def export_multi_file(trees : list,  base_tree, filepath : str, base_name :str) -> None:
     for t in trees:
     for t in trees:
@@ -1191,7 +1192,7 @@ def export_multi_file(trees : list,  base_tree, filepath : str, base_name :str)
                         clean_name(t.name)+'.rig'))
                         clean_name(t.name)+'.rig'))
         write_json_data(export_data, os_path.join(directory,
         write_json_data(export_data, os_path.join(directory,
                         clean_name(t.name)+'.rig'))
                         clean_name(t.name)+'.rig'))
-        
+
 
 
 import bpy
 import bpy
 
 
@@ -1228,7 +1229,7 @@ class MantisExportNodeTreeSaveAs(Operator, ExportHelper):
         # we need to get the dependent trees from self.tree...
         # we need to get the dependent trees from self.tree...
         # there is no self.tree
         # there is no self.tree
         # how do I choose a tree?
         # how do I choose a tree?
-        
+
         base_tree=context.space_data.path[-1].node_tree
         base_tree=context.space_data.path[-1].node_tree
         from .utilities import all_trees_in_tree
         from .utilities import all_trees_in_tree
         trees = all_trees_in_tree(base_tree)[::-1]
         trees = all_trees_in_tree(base_tree)[::-1]
@@ -1396,4 +1397,4 @@ class MantisReloadNodeTree(Operator):
 # todo:
 # todo:
 #  - export metarig and option to import it
 #  - export metarig and option to import it
 #  - same with controls
 #  - same with controls
-#  - it would be nice to have a library of these that can be imported alongside the mantis graph
+#  - it would be nice to have a library of these that can be imported alongside the mantis graph

+ 21 - 12
internal_containers.py

@@ -1,6 +1,6 @@
 from .node_container_common import *
 from .node_container_common import *
 from bpy.types import Node
 from bpy.types import Node
-from .base_definitions import MantisNode
+from .base_definitions import MantisNode, MantisSocketTemplate
 from uuid import uuid4
 from uuid import uuid4
 
 
 class DummyNode(MantisNode):
 class DummyNode(MantisNode):
@@ -11,7 +11,6 @@ class DummyNode(MantisNode):
         self.prepared = True
         self.prepared = True
         self.uuid = uuid4()
         self.uuid = uuid4()
         self.solver = None
         self.solver = None
-        self.did_set_variables = False
         if prototype:
         if prototype:
             if prototype.bl_idname in ["MantisSchemaGroup"]:
             if prototype.bl_idname in ["MantisSchemaGroup"]:
                 self.node_type = 'DUMMY_SCHEMA'
                 self.node_type = 'DUMMY_SCHEMA'
@@ -33,14 +32,6 @@ class DummyNode(MantisNode):
         # this is ugly and I hate it.
         # this is ugly and I hate it.
         self.execution_prepared=True # in case it gets left behind in the tree as a dependency
         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):
 class NoOpNode(MantisNode):
     def __init__(self, signature, base_tree):
     def __init__(self, signature, base_tree):
         super().__init__(signature, base_tree)
         super().__init__(signature, base_tree)
@@ -49,8 +40,8 @@ class NoOpNode(MantisNode):
         self.init_parameters()
         self.init_parameters()
         self.set_traverse([("Input", "Output")])
         self.set_traverse([("Input", "Output")])
         self.node_type = 'UTILITY'
         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.
     # this node is useful for me to insert in the tree and use for debugging especially connections.
 
 
 class AutoGenNode(MantisNode):
 class AutoGenNode(MantisNode):
@@ -58,7 +49,25 @@ class AutoGenNode(MantisNode):
         super().__init__(signature, base_tree)
         super().__init__(signature, base_tree)
         self.node_type = 'UTILITY'
         self.node_type = 'UTILITY'
         self.prepared, self.executed = True, True
         self.prepared, self.executed = True, True
+        self.execution_prepared=True
 
 
     def reset_execution(self):
     def reset_execution(self):
         super().reset_execution()
         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

+ 46 - 35
link_nodes.py

@@ -53,7 +53,10 @@ class MantisLinkNode(MantisNode):
         if ('Target' in input_name) and input_name not in  ["Target Space", "Use Target Z"]:
         if ('Target' in input_name) and input_name not in  ["Target Space", "Use Target Z"]:
             socket = self.inputs.get(input_name)
             socket = self.inputs.get(input_name)
             if socket.is_linked:
             if socket.is_linked:
-                return socket.links[0].from_node
+                node_line, _last_socket = trace_single_line(self, input_name, index)
+                for other_node in node_line:
+                    if other_node.node_type == 'XFORM':
+                        return other_node
             return None
             return None
 
 
         else:
         else:
@@ -61,28 +64,46 @@ class MantisLinkNode(MantisNode):
 
 
     def gen_property_socket_map(self) -> dict:
     def gen_property_socket_map(self) -> dict:
         props_sockets = super().gen_property_socket_map()
         props_sockets = super().gen_property_socket_map()
-        if (os := self.inputs.get("Owner Space")) and os.is_connected and os.links[0].from_node.node_type == 'XFORM':
-            del props_sockets['owner_space']
-        if (ts := self.inputs.get("Target Space")) and ts.is_connected and ts.links[0].from_node.node_type == 'XFORM':
-            del props_sockets['target_space']
+        if (os := self.inputs.get("Owner Space")) and os.is_connected:
+            node_line, _last_socket = trace_single_line(self, "Owner Space")
+            for other_node in node_line:
+                if other_node.node_type == 'XFORM':
+                    del props_sockets['owner_space']; break
+        if (ts := self.inputs.get("Target Space")) and ts.is_connected:
+            node_line, _last_socket = trace_single_line(self, "Target Space")
+            for other_node in node_line:
+                if other_node.node_type == 'XFORM':
+                    del props_sockets['target_space']; break
         return props_sockets
         return props_sockets
 
 
     def set_custom_space(self):
     def set_custom_space(self):
         for c in self.bObject:
         for c in self.bObject:
-            if (os := self.inputs.get("Owner Space")) and os.is_connected and os.links[0].from_node.node_type == 'XFORM':
-                c.owner_space='CUSTOM'
-                xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
-                if isinstance(xf, Bone):
-                    c.space_object=self.inputs["Owner Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
-                else:
-                    c.space_object=xf
-            if ts := self.inputs.get("Target_Space") and ts.is_connected and ts.links[0].from_node.node_type == 'XFORM':
-                c.target_space='CUSTOM'
-                xf = self.inputs["Target_Space Space"].links[0].from_node.bGetObject(mode="OBJECT")
-                if isinstance(xf, Bone):
-                    c.space_object=self.inputs["Target_Space Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
-                else:
-                    c.space_object=xf
+            if (os := self.inputs.get("Owner Space")) and os.is_connected:
+                node_line, _last_socket = trace_single_line(self, "Owner Space")
+                owner_space_target = None
+                for other_node in node_line:
+                    if other_node.node_type == 'XFORM':
+                        owner_space_target = other_node; break
+                if owner_space_target:
+                    c.owner_space='CUSTOM'
+                    xf = owner_space_target.bGetObject(mode="OBJECT")
+                    if isinstance(xf, Bone):
+                        c.space_object=owner_space_target.bGetParentArmature(); c.space_subtarget=xf.name
+                    else:
+                        c.space_object=xf
+            if (ts := self.inputs.get("Target Space")) and ts.is_connected:
+                node_line, _last_socket = trace_single_line(self, "Target Space")
+                target_space_target = None
+                for other_node in node_line:
+                    if other_node.node_type == 'XFORM':
+                        target_space_target = other_node; break
+                if target_space_target:
+                    c.target_space='CUSTOM'
+                    xf = target_space_target.bGetObject(mode="OBJECT")
+                    if isinstance(xf, Bone):
+                        c.space_object=target_space_target.bGetParentArmature(); c.space_subtarget=xf.name
+                    else:
+                        c.space_object=xf
 
 
     def GetxForm(nc, output_name="Output Relationship"):
     def GetxForm(nc, output_name="Output Relationship"):
         break_condition= lambda node : node.node_type=='XFORM'
         break_condition= lambda node : node.node_type=='XFORM'
@@ -207,20 +228,7 @@ class LinkCopyScale(MantisLinkNode):
             if constraint_name := self.evaluate_input("Name"):
             if constraint_name := self.evaluate_input("Name"):
                 c.name = constraint_name
                 c.name = constraint_name
             self.bObject.append(c)
             self.bObject.append(c)
-            if self.inputs["Owner Space"].is_connected and self.inputs["Owner Space"].links[0].from_node.node_type == 'XFORM':
-                c.owner_space='CUSTOM'
-                xf = self.inputs["Owner Space"].links[0].from_node.bGetObject(mode="OBJECT")
-                if isinstance(xf, Bone):
-                    c.space_object=self.inputs["Owner Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
-                else:
-                    c.space_object=xf
-            if self.inputs["Target Space"].is_connected and self.inputs["Target Space"].links[0].from_node.node_type == 'XFORM':
-                c.target_space='CUSTOM'
-                xf = self.inputs["Target Space"].links[0].from_node.bGetObject(mode="OBJECT")
-                if isinstance(xf, Bone):
-                    c.space_object=self.inputs["Owner Space"].links[0].from_node.bGetParentArmature(); c.space_subtarget=xf.name
-                else:
-                    c.space_object=xf
+            self.set_custom_space()
             props_sockets = self.gen_property_socket_map()
             props_sockets = self.gen_property_socket_map()
             evaluate_sockets(self, c, props_sockets)
             evaluate_sockets(self, c, props_sockets)
         self.executed = True
         self.executed = True
@@ -806,7 +814,6 @@ class LinkArmature(MantisLinkNode):
                     weight_value=0
                     weight_value=0
                 targets_weights[i]=weight_value
                 targets_weights[i]=weight_value
                 props_sockets["targets[%d].weight" % i] = (weight_input_name, 0)
                 props_sockets["targets[%d].weight" % i] = (weight_input_name, 0)
-                # targets_weights.append({"weight":(weight_input_name, 0)})
             evaluate_sockets(self, c, props_sockets)
             evaluate_sockets(self, c, props_sockets)
             for target, value in targets_weights.items():
             for target, value in targets_weights.items():
                 c.targets[target].weight=value
                 c.targets[target].weight=value
@@ -831,8 +838,12 @@ class LinkSplineIK(MantisLinkNode):
             c = xf.bGetObject().constraints.new('SPLINE_IK')
             c = xf.bGetObject().constraints.new('SPLINE_IK')
             # set the spline - we need to get the right one
             # set the spline - we need to get the right one
             spline_index = self.evaluate_input("Spline Index")
             spline_index = self.evaluate_input("Spline Index")
+            proto_curve = None
+            node_line, _last_socket = trace_single_line(self, "Target")
+            for other_node in node_line: # trace and get the input
+                if other_node.node_type == 'XFORM':
+                    proto_curve = other_node.bGetObject(); break
             from .utilities import get_extracted_spline_object
             from .utilities import get_extracted_spline_object
-            proto_curve = self.inputs['Target'].links[0].from_node.bGetObject()
             curve = get_extracted_spline_object(proto_curve, spline_index, self.mContext)
             curve = get_extracted_spline_object(proto_curve, spline_index, self.mContext)
             # link it to the view layer
             # link it to the view layer
             if (curve.name not in bContext.view_layer.active_layer_collection.collection.objects):
             if (curve.name not in bContext.view_layer.active_layer_collection.collection.objects):

+ 72 - 75
misc_nodes.py

@@ -145,22 +145,16 @@ def array_choose_relink(node, indices, array_input, output, ):
     """
     """
         Used to choose the correct link to send out of an array-choose node.
         Used to choose the correct link to send out of an array-choose node.
     """
     """
-    prGreen(node)
-    for l in node.inputs[array_input].links:
-        print(l)
     keep_links = []
     keep_links = []
     for index in indices:
     for index in indices:
-        prOrange(index)
         l = node.inputs[array_input].links[index]
         l = node.inputs[array_input].links[index]
         keep_links.append(l)
         keep_links.append(l)
     for link in node.outputs[output].links:
     for link in node.outputs[output].links:
-        prOrange(link)
         to_node = link.to_node
         to_node = link.to_node
         for l in keep_links:
         for l in keep_links:
             new_link = l.from_node.outputs[l.from_socket].connect(to_node, link.to_socket)
             new_link = l.from_node.outputs[l.from_socket].connect(to_node, link.to_socket)
             array_link_init_hierarchy(new_link)
             array_link_init_hierarchy(new_link)
             node.rerouted.append(new_link) # so I can access this in Schema Solve
             node.rerouted.append(new_link) # so I can access this in Schema Solve
-            prPurple(new_link)
         link.die()
         link.die()
 
 
 
 
@@ -217,7 +211,7 @@ class SimpleInputNode(MantisNode):
 
 
 class InputFloat(SimpleInputNode):
 class InputFloat(SimpleInputNode):
     '''A node representing float input'''
     '''A node representing float input'''
-    
+
     def __init__(self, signature, base_tree):
     def __init__(self, signature, base_tree):
         super().__init__(signature, base_tree)
         super().__init__(signature, base_tree)
         outputs = ["Float Input"]
         outputs = ["Float Input"]
@@ -226,16 +220,16 @@ class InputFloat(SimpleInputNode):
 
 
 class InputIntNode(SimpleInputNode):
 class InputIntNode(SimpleInputNode):
     '''A node representing integer input'''
     '''A node representing integer input'''
-    
+
     def __init__(self, signature, base_tree):
     def __init__(self, signature, base_tree):
         super().__init__(signature, base_tree)
         super().__init__(signature, base_tree)
         outputs = ["Integer"]
         outputs = ["Integer"]
         self.outputs.init_sockets(outputs)
         self.outputs.init_sockets(outputs)
         self.init_parameters()
         self.init_parameters()
-    
+
 class InputVector(SimpleInputNode):
 class InputVector(SimpleInputNode):
     '''A node representing vector input'''
     '''A node representing vector input'''
-    
+
     def __init__(self, signature, base_tree):
     def __init__(self, signature, base_tree):
         super().__init__(signature, base_tree)
         super().__init__(signature, base_tree)
         outputs = [""]
         outputs = [""]
@@ -244,7 +238,7 @@ class InputVector(SimpleInputNode):
 
 
 class InputBoolean(SimpleInputNode):
 class InputBoolean(SimpleInputNode):
     '''A node representing boolean input'''
     '''A node representing boolean input'''
-    
+
     def __init__(self, signature, base_tree):
     def __init__(self, signature, base_tree):
         super().__init__(signature, base_tree)
         super().__init__(signature, base_tree)
         outputs = [""]
         outputs = [""]
@@ -258,31 +252,31 @@ class InputBooleanThreeTuple(SimpleInputNode):
         outputs = [""]
         outputs = [""]
         self.outputs.init_sockets(outputs)
         self.outputs.init_sockets(outputs)
         self.init_parameters()
         self.init_parameters()
-    
+
 class InputRotationOrder(SimpleInputNode):
 class InputRotationOrder(SimpleInputNode):
     '''A node representing string input for rotation order'''
     '''A node representing string input for rotation order'''
-        
+
     def __init__(self, signature, base_tree):
     def __init__(self, signature, base_tree):
         super().__init__(signature, base_tree)
         super().__init__(signature, base_tree)
         outputs = [""]
         outputs = [""]
         self.outputs.init_sockets(outputs)
         self.outputs.init_sockets(outputs)
         self.init_parameters()
         self.init_parameters()
-    
+
 class InputTransformSpace(SimpleInputNode):
 class InputTransformSpace(SimpleInputNode):
     '''A node representing string input for transform space'''
     '''A node representing string input for transform space'''
-        
+
     def __init__(self, signature, base_tree):
     def __init__(self, signature, base_tree):
         super().__init__(signature, base_tree)
         super().__init__(signature, base_tree)
         outputs = [""]
         outputs = [""]
         self.outputs.init_sockets(outputs)
         self.outputs.init_sockets(outputs)
         self.init_parameters()
         self.init_parameters()
-        
+
     def evaluate_input(self, input_name):
     def evaluate_input(self, input_name):
         return self.parameters[""]
         return self.parameters[""]
-    
+
 class InputString(SimpleInputNode):
 class InputString(SimpleInputNode):
     '''A node representing string input'''
     '''A node representing string input'''
-        
+
     def __init__(self, signature, base_tree):
     def __init__(self, signature, base_tree):
         super().__init__(signature, base_tree)
         super().__init__(signature, base_tree)
         outputs = [""]
         outputs = [""]
@@ -291,7 +285,7 @@ class InputString(SimpleInputNode):
 
 
 class InputMatrix(SimpleInputNode):
 class InputMatrix(SimpleInputNode):
     '''A node representing axis-angle quaternion input'''
     '''A node representing axis-angle quaternion input'''
-        
+
     def __init__(self, signature, base_tree):
     def __init__(self, signature, base_tree):
         super().__init__(signature, base_tree)
         super().__init__(signature, base_tree)
         outputs  = ["Matrix",]
         outputs  = ["Matrix",]
@@ -300,7 +294,7 @@ class InputMatrix(SimpleInputNode):
 
 
 class InputThemeBoneColorSets(SimpleInputNode):
 class InputThemeBoneColorSets(SimpleInputNode):
     '''A node representing the theme's colors'''
     '''A node representing the theme's colors'''
-        
+
     def __init__(self, signature, base_tree):
     def __init__(self, signature, base_tree):
         super().__init__(signature, base_tree)
         super().__init__(signature, base_tree)
         from .base_definitions import MantisSocketTemplate
         from .base_definitions import MantisSocketTemplate
@@ -325,7 +319,7 @@ class InputColorSetPallete(SimpleInputNode):
     '''A node representing the theme's colors'''
     '''A node representing the theme's colors'''
     def __init__(self, signature, base_tree):
     def __init__(self, signature, base_tree):
         super().__init__(signature, base_tree)
         super().__init__(signature, base_tree)
-    
+
     def fill_parameters(self, ui_node=None):
     def fill_parameters(self, ui_node=None):
         if not ui_node:
         if not ui_node:
             from .utilities import get_node_prototype
             from .utilities import get_node_prototype
@@ -403,7 +397,7 @@ class UtilityMatrixFromCurve(MantisNode):
         self.parameters["Matrix"] = mat
         self.parameters["Matrix"] = mat
         self.prepared = True
         self.prepared = True
         self.executed = True
         self.executed = True
-    
+
     def bFinalize(self, bContext=None):
     def bFinalize(self, bContext=None):
         cleanup_curve(self.evaluate_input("Curve"), self.base_tree.execution_id)
         cleanup_curve(self.evaluate_input("Curve"), self.base_tree.execution_id)
 
 
@@ -437,7 +431,7 @@ class UtilityPointFromCurve(MantisNode):
             p = data[spline_index][0][0] - curve.location
             p = data[spline_index][0][0] - curve.location
         self.parameters["Point"] = p
         self.parameters["Point"] = p
         self.prepared, self.executed = True, True
         self.prepared, self.executed = True, True
-    
+
     def bFinalize(self, bContext=None):
     def bFinalize(self, bContext=None):
         cleanup_curve(self.evaluate_input("Curve"), self.base_tree.execution_id)
         cleanup_curve(self.evaluate_input("Curve"), self.base_tree.execution_id)
 
 
@@ -500,7 +494,7 @@ class UtilityMatricesFromCurve(MantisNode):
         self.prepared = True
         self.prepared = True
         self.executed = True
         self.executed = True
         # prGreen(f"Matrices from curves took {time.time() - start_time} seconds.")
         # prGreen(f"Matrices from curves took {time.time() - start_time} seconds.")
-    
+
     def bFinalize(self, bContext=None):
     def bFinalize(self, bContext=None):
         import bpy
         import bpy
         curve_name = self.evaluate_input("Curve")
         curve_name = self.evaluate_input("Curve")
@@ -524,7 +518,7 @@ class UtilityNumberOfCurveSegments(MantisNode):
         self.outputs.init_sockets(outputs)
         self.outputs.init_sockets(outputs)
         self.init_parameters()
         self.init_parameters()
         self.node_type = "UTILITY"
         self.node_type = "UTILITY"
-    
+
     def bPrepare(self, bContext = None,):
     def bPrepare(self, bContext = None,):
         curve_name = self.evaluate_input("Curve")
         curve_name = self.evaluate_input("Curve")
         curve = bpy_object_get_guarded( curve_name, self)
         curve = bpy_object_get_guarded( curve_name, self)
@@ -541,7 +535,7 @@ class UtilityNumberOfSplines(MantisNode):
         super().__init__(signature, base_tree, NumberOfSplinesSockets)
         super().__init__(signature, base_tree, NumberOfSplinesSockets)
         self.init_parameters()
         self.init_parameters()
         self.node_type = "UTILITY"
         self.node_type = "UTILITY"
-    
+
     def bPrepare(self, bContext = None,):
     def bPrepare(self, bContext = None,):
         curve_name = self.evaluate_input("Curve")
         curve_name = self.evaluate_input("Curve")
         curve = bpy_object_get_guarded( curve_name, self)
         curve = bpy_object_get_guarded( curve_name, self)
@@ -602,7 +596,7 @@ class UtilityMatrixFromCurveSegment(MantisNode):
             m.translation = head - curve.location
             m.translation = head - curve.location
             self.parameters["Matrix"] = m
             self.parameters["Matrix"] = m
         self.prepared, self.executed = True, True
         self.prepared, self.executed = True, True
-    
+
     def bFinalize(self, bContext=None):
     def bFinalize(self, bContext=None):
         cleanup_curve(self.evaluate_input("Curve"), self.base_tree.execution_id)
         cleanup_curve(self.evaluate_input("Curve"), self.base_tree.execution_id)
 
 
@@ -611,7 +605,7 @@ class UtilityGetCurvePoint(MantisNode):
         super().__init__(signature, base_tree, GetCurvePointSockets)
         super().__init__(signature, base_tree, GetCurvePointSockets)
         self.init_parameters()
         self.init_parameters()
         self.node_type = "UTILITY"
         self.node_type = "UTILITY"
-    
+
     def bPrepare(self, bContext=None):
     def bPrepare(self, bContext=None):
         import bpy
         import bpy
         curve_name = self.evaluate_input("Curve")
         curve_name = self.evaluate_input("Curve")
@@ -636,7 +630,7 @@ class UtilityGetNearestFactorOnCurve(MantisNode):
         super().__init__(signature, base_tree, GetNearestFactorOnCurveSockets)
         super().__init__(signature, base_tree, GetNearestFactorOnCurveSockets)
         self.init_parameters()
         self.init_parameters()
         self.node_type = "UTILITY"
         self.node_type = "UTILITY"
-    
+
     def bPrepare(self, bContext = None,):
     def bPrepare(self, bContext = None,):
         import bpy
         import bpy
         curve_name = self.evaluate_input("Curve")
         curve_name = self.evaluate_input("Curve")
@@ -770,14 +764,14 @@ class UtilityMetaRig(MantisNode):
         import bpy
         import bpy
         from mathutils import Matrix
         from mathutils import Matrix
         m = Matrix.Identity(4)
         m = Matrix.Identity(4)
-        
+
         meta_rig  = self.evaluate_input("Meta-Armature")
         meta_rig  = self.evaluate_input("Meta-Armature")
         if meta_rig is None:
         if meta_rig is None:
             raise RuntimeError("Invalid input for Meta-Armature.")
             raise RuntimeError("Invalid input for Meta-Armature.")
         meta_bone = self.evaluate_input("Meta-Bone")
         meta_bone = self.evaluate_input("Meta-Bone")
         if meta_rig is None or meta_bone is None:
         if meta_rig is None or meta_bone is None:
             raise RuntimeError("Invalid input for Meta-Bone.")
             raise RuntimeError("Invalid input for Meta-Bone.")
-        
+
         if meta_rig:
         if meta_rig:
             if ( armOb := bpy.data.objects.get(meta_rig) ):
             if ( armOb := bpy.data.objects.get(meta_rig) ):
                 m = armOb.matrix_world
                 m = armOb.matrix_world
@@ -794,7 +788,7 @@ class UtilityMetaRig(MantisNode):
                 #     prRed("no bone for MetaRig node ", self)
                 #     prRed("no bone for MetaRig node ", self)
         else:
         else:
             raise RuntimeError(wrapRed(f"No meta-rig input for MetaRig node {self}"))
             raise RuntimeError(wrapRed(f"No meta-rig input for MetaRig node {self}"))
-        
+
         self.parameters["Matrix"] = m
         self.parameters["Matrix"] = m
         self.prepared = True
         self.prepared = True
         self.executed = True
         self.executed = True
@@ -820,7 +814,7 @@ class UtilityBoneProperties(SimpleInputNode):
 
 
     def fill_parameters(self, prototype=None):
     def fill_parameters(self, prototype=None):
         return
         return
-        
+
 # TODO this should probably be moved to Links
 # TODO this should probably be moved to Links
 class UtilityDriverVariable(MantisNode):
 class UtilityDriverVariable(MantisNode):
     '''A node representing an armature object'''
     '''A node representing an armature object'''
@@ -844,23 +838,24 @@ class UtilityDriverVariable(MantisNode):
         self.init_parameters()
         self.init_parameters()
         self.node_type = "DRIVER" # MUST be run in Pose mode
         self.node_type = "DRIVER" # MUST be run in Pose mode
         self.prepared = True
         self.prepared = True
-    
+
     def reset_execution(self):
     def reset_execution(self):
         super().reset_execution()
         super().reset_execution()
         # clear this to ensure there are no stale reference pointers
         # clear this to ensure there are no stale reference pointers
         self.parameters["Driver Variable"] = None
         self.parameters["Driver Variable"] = None
         self.prepared=True
         self.prepared=True
-        
+
     def evaluate_input(self, input_name):
     def evaluate_input(self, input_name):
         if input_name == 'Property':
         if input_name == 'Property':
             if self.inputs.get('Property'):
             if self.inputs.get('Property'):
                 if self.inputs['Property'].is_linked:
                 if self.inputs['Property'].is_linked:
-                # get the name instead...
                     trace = trace_single_line(self, input_name)
                     trace = trace_single_line(self, input_name)
-                    return trace[1].name # the name of the socket
+                    # CANNOT UNDERSTATE HOW CRITICAL THIS CHECK IS
+                    if trace[0][-1].node_type == 'XFORM':
+                        return trace[1].name # the name of the socket
             return self.parameters["Property"]
             return self.parameters["Property"]
         return super().evaluate_input(input_name)
         return super().evaluate_input(input_name)
-        
+
     def GetxForm(self, index=1):
     def GetxForm(self, index=1):
         trace = trace_single_line(self, "xForm 1" if index == 1 else "xForm 2")
         trace = trace_single_line(self, "xForm 1" if index == 1 else "xForm 2")
         for node in trace[0]:
         for node in trace[0]:
@@ -903,7 +898,7 @@ class UtilityDriverVariable(MantisNode):
             if self.evaluate_input("Property") == 'scale_average':
             if self.evaluate_input("Property") == 'scale_average':
                 dVarChannel = "SCALE_AVG"
                 dVarChannel = "SCALE_AVG"
         if dVarChannel: v_type = "TRANSFORMS"
         if dVarChannel: v_type = "TRANSFORMS"
-        
+
         my_var = {
         my_var = {
             "owner"         : xForm1, # will be filled in by Driver
             "owner"         : xForm1, # will be filled in by Driver
             "prop"          : self.evaluate_input("Property"), # will be filled in by Driver
             "prop"          : self.evaluate_input("Property"), # will be filled in by Driver
@@ -913,10 +908,11 @@ class UtilityDriverVariable(MantisNode):
             "xForm 1"       : xForm1,#self.GetxForm(index = 1),
             "xForm 1"       : xForm1,#self.GetxForm(index = 1),
             "xForm 2"       : xForm2,#self.GetxForm(index = 2),
             "xForm 2"       : xForm2,#self.GetxForm(index = 2),
             "channel"       : dVarChannel,}
             "channel"       : dVarChannel,}
-        
+
         self.parameters["Driver Variable"] = my_var
         self.parameters["Driver Variable"] = my_var
+        print (my_var['prop'])
         self.executed = True
         self.executed = True
-            
+
 class UtilityKeyframe(MantisNode):
 class UtilityKeyframe(MantisNode):
     '''A node representing a keyframe for a F-Curve'''
     '''A node representing a keyframe for a F-Curve'''
 
 
@@ -964,7 +960,7 @@ class UtilityFCurve(MantisNode):
         self.node_type = "UTILITY"
         self.node_type = "UTILITY"
         setup_custom_props(self)
         setup_custom_props(self)
         self.prepared = True
         self.prepared = True
-    
+
     def reset_execution(self):
     def reset_execution(self):
         super().reset_execution()
         super().reset_execution()
         self.prepared=True
         self.prepared=True
@@ -990,7 +986,7 @@ class UtilityFCurve(MantisNode):
         keys.append(extrap_mode)
         keys.append(extrap_mode)
         self.parameters["fCurve"] = keys
         self.parameters["fCurve"] = keys
         self.executed = True
         self.executed = True
-#TODO make the fCurve data a data class instead of a dict 
+#TODO make the fCurve data a data class instead of a dict
 
 
 class UtilityDriver(MantisNode):
 class UtilityDriver(MantisNode):
     '''A node representing an armature object'''
     '''A node representing an armature object'''
@@ -1007,7 +1003,7 @@ class UtilityDriver(MantisNode):
         ]
         ]
         from .drivers import MantisDriver
         from .drivers import MantisDriver
         additional_parameters = {
         additional_parameters = {
-          "Driver":MantisDriver(), 
+          "Driver":MantisDriver(),
         }
         }
         self.inputs.init_sockets(inputs)
         self.inputs.init_sockets(inputs)
         self.outputs.init_sockets(outputs)
         self.outputs.init_sockets(outputs)
@@ -1015,13 +1011,13 @@ class UtilityDriver(MantisNode):
         self.node_type = "DRIVER" # MUST be run in Pose mode
         self.node_type = "DRIVER" # MUST be run in Pose mode
         setup_custom_props(self)
         setup_custom_props(self)
         self.prepared = True
         self.prepared = True
-    
+
     def reset_execution(self):
     def reset_execution(self):
         super().reset_execution()
         super().reset_execution()
         from .drivers import MantisDriver
         from .drivers import MantisDriver
         self.parameters["Driver"]=MantisDriver()
         self.parameters["Driver"]=MantisDriver()
         self.prepared=True
         self.prepared=True
-    
+
     def bRelationshipPass(self, bContext = None,):
     def bRelationshipPass(self, bContext = None,):
         prepare_parameters(self)
         prepare_parameters(self)
         from .drivers import MantisDriver
         from .drivers import MantisDriver
@@ -1049,9 +1045,9 @@ class UtilityDriver(MantisNode):
                      "vars"          :  my_vars,
                      "vars"          :  my_vars,
                      "keys"          :  keys[:-1],
                      "keys"          :  keys[:-1],
                      "extrapolation" :  keys[-1] }
                      "extrapolation" :  keys[-1] }
-        
+
         my_driver = MantisDriver(my_driver)
         my_driver = MantisDriver(my_driver)
-        
+
         self.parameters["Driver"].update(my_driver)
         self.parameters["Driver"].update(my_driver)
         print("Initializing driver %s " % (wrapPurple(self.__repr__())) )
         print("Initializing driver %s " % (wrapPurple(self.__repr__())) )
         self.executed = True
         self.executed = True
@@ -1071,7 +1067,7 @@ class UtilitySwitch(MantisNode):
         ]
         ]
         from .drivers import MantisDriver
         from .drivers import MantisDriver
         additional_parameters = {
         additional_parameters = {
-          "Driver":MantisDriver(), 
+          "Driver":MantisDriver(),
         }
         }
         self.inputs.init_sockets(inputs)
         self.inputs.init_sockets(inputs)
         self.outputs.init_sockets(outputs)
         self.outputs.init_sockets(outputs)
@@ -1093,7 +1089,7 @@ class UtilitySwitch(MantisNode):
             if (node.__class__ in [xFormArmature, xFormBone]):
             if (node.__class__ in [xFormArmature, xFormBone]):
                 return node #this will fetch the first one, that's good!
                 return node #this will fetch the first one, that's good!
         return None
         return None
-    
+
     def reset_execution(self):
     def reset_execution(self):
         super().reset_execution()
         super().reset_execution()
         from .drivers import MantisDriver
         from .drivers import MantisDriver
@@ -1104,12 +1100,12 @@ class UtilitySwitch(MantisNode):
         #prepare_parameters(self)
         #prepare_parameters(self)
         #prPurple ("Executing Switch Node")
         #prPurple ("Executing Switch Node")
         xForm = self.GetxForm()
         xForm = self.GetxForm()
-        if xForm : xForm = xForm.bGetObject() 
+        if xForm : xForm = xForm.bGetObject()
         if not xForm:
         if not xForm:
             raise RuntimeError("Could not evaluate xForm for %s" % self)
             raise RuntimeError("Could not evaluate xForm for %s" % self)
         from .drivers import MantisDriver
         from .drivers import MantisDriver
         my_driver ={ "owner" : None,
         my_driver ={ "owner" : None,
-                     "prop"  : None, # will be filled out in the node that uses the driver 
+                     "prop"  : None, # will be filled out in the node that uses the driver
                      "ind"   : -1, # same here
                      "ind"   : -1, # same here
                      "type"  : "SCRIPTED",
                      "type"  : "SCRIPTED",
                      "vars"  : [ { "owner" : xForm,
                      "vars"  : [ { "owner" : xForm,
@@ -1124,13 +1120,13 @@ class UtilitySwitch(MantisNode):
                                    "type":"KEYFRAME",},],
                                    "type":"KEYFRAME",},],
                       "extrapolation": 'CONSTANT', }
                       "extrapolation": 'CONSTANT', }
         my_driver   ["expression"] = "a"
         my_driver   ["expression"] = "a"
-        
+
         my_driver = MantisDriver(my_driver)
         my_driver = MantisDriver(my_driver)
     # this makes it so I can check for type later!
     # this makes it so I can check for type later!
-        
+
         if self.evaluate_input("Invert Switch") == True:
         if self.evaluate_input("Invert Switch") == True:
             my_driver   ["expression"] = "1 - a"
             my_driver   ["expression"] = "1 - a"
-        
+
         # this way, regardless of what order things are handled, the
         # this way, regardless of what order things are handled, the
         #  driver is sent to the next node.
         #  driver is sent to the next node.
         # In the case of some drivers, the parameter may be sent out
         # In the case of some drivers, the parameter may be sent out
@@ -1159,7 +1155,7 @@ class UtilityCombineThreeBool(MantisNode):
         self.outputs.init_sockets(outputs)
         self.outputs.init_sockets(outputs)
         self.init_parameters()
         self.init_parameters()
         self.node_type = "UTILITY"
         self.node_type = "UTILITY"
-    
+
     def reset_execution(self): # need to make sure any references are deleted
     def reset_execution(self): # need to make sure any references are deleted
         super().reset_execution() # so we prepare the node again to reset them
         super().reset_execution() # so we prepare the node again to reset them
         if self.parameters["Three-Bool"] is not None:
         if self.parameters["Three-Bool"] is not None:
@@ -1201,7 +1197,7 @@ class UtilityCombineVector(MantisNode):
             for param in self.parameters["Vector"]:
             for param in self.parameters["Vector"]:
                 if isinstance(param, dict):
                 if isinstance(param, dict):
                     self.prepared=False; break
                     self.prepared=False; break
-    
+
     def bPrepare(self, bContext = None,):
     def bPrepare(self, bContext = None,):
         #prPurple("Executing CombineVector Node")
         #prPurple("Executing CombineVector Node")
         prepare_parameters(self)
         prepare_parameters(self)
@@ -1210,7 +1206,7 @@ class UtilityCombineVector(MantisNode):
           self.evaluate_input("Y"),
           self.evaluate_input("Y"),
           self.evaluate_input("Z"), )
           self.evaluate_input("Z"), )
         self.prepared, self.executed = True, True
         self.prepared, self.executed = True, True
-  
+
 class UtilitySeparateVector(MantisNode):
 class UtilitySeparateVector(MantisNode):
     '''A node for separating a vector into three floats'''
     '''A node for separating a vector into three floats'''
 
 
@@ -1251,7 +1247,7 @@ class UtilityCatStrings(MantisNode):
         self.outputs.init_sockets(outputs)
         self.outputs.init_sockets(outputs)
         self.init_parameters()
         self.init_parameters()
         self.node_type = "UTILITY"
         self.node_type = "UTILITY"
-    
+
     def bPrepare(self, bContext = None,):
     def bPrepare(self, bContext = None,):
         self.parameters["OutputString"] = self.evaluate_input("String_1")+self.evaluate_input("String_2")
         self.parameters["OutputString"] = self.evaluate_input("String_1")+self.evaluate_input("String_2")
         self.prepared, self.executed = True, True
         self.prepared, self.executed = True, True
@@ -1263,7 +1259,7 @@ class InputWidget(MantisNode):
         super().__init__(signature, base_tree, InputWidgetSockets)
         super().__init__(signature, base_tree, InputWidgetSockets)
         self.init_parameters()
         self.init_parameters()
         self.node_type = "XFORM"
         self.node_type = "XFORM"
-    
+
     def reset_execution(self):
     def reset_execution(self):
         super().reset_execution()
         super().reset_execution()
         self.prepared=False
         self.prepared=False
@@ -1338,10 +1334,10 @@ class InputWidget(MantisNode):
             flip_modifier["Socket_3"]=axes_flipped[1]
             flip_modifier["Socket_3"]=axes_flipped[1]
             flip_modifier["Socket_4"]=axes_flipped[2]
             flip_modifier["Socket_4"]=axes_flipped[2]
         self.prepared, self.executed = True, True
         self.prepared, self.executed = True, True
-    
+
     def bGetObject(self, mode=''):
     def bGetObject(self, mode=''):
         return self.bObject
         return self.bObject
-    
+
 # TODO move this to the Xform file
 # TODO move this to the Xform file
 class InputExistingGeometryObject(MantisNode):
 class InputExistingGeometryObject(MantisNode):
     '''A node representing an existing object'''
     '''A node representing an existing object'''
@@ -1358,7 +1354,7 @@ class InputExistingGeometryObject(MantisNode):
         self.outputs.init_sockets(outputs)
         self.outputs.init_sockets(outputs)
         self.init_parameters()
         self.init_parameters()
         self.node_type = "XFORM"
         self.node_type = "XFORM"
-    
+
     def reset_execution(self):
     def reset_execution(self):
         super().reset_execution()
         super().reset_execution()
         self.prepared=False
         self.prepared=False
@@ -1372,7 +1368,7 @@ class InputExistingGeometryObject(MantisNode):
             prRed(f"No object found with name {name} in {self}")
             prRed(f"No object found with name {name} in {self}")
         self.bObject=ob
         self.bObject=ob
         self.prepared, self.executed = True, True
         self.prepared, self.executed = True, True
-    
+
     def bGetObject(self, mode=''):
     def bGetObject(self, mode=''):
         return self.bObject
         return self.bObject
 
 
@@ -1418,7 +1414,7 @@ class UtilityDeclareCollections(MantisNode):
     def reset_execution(self):
     def reset_execution(self):
         super().reset_execution()
         super().reset_execution()
         self.prepared, self.executed = True, True
         self.prepared, self.executed = True, True
-    
+
     def fill_parameters(self, ui_node=None):
     def fill_parameters(self, ui_node=None):
         if ui_node is None:
         if ui_node is None:
             from .utilities import get_node_prototype
             from .utilities import get_node_prototype
@@ -1428,7 +1424,7 @@ class UtilityDeclareCollections(MantisNode):
         for out in ui_node.outputs:
         for out in ui_node.outputs:
             if not (out.name in self.outputs.keys()) :
             if not (out.name in self.outputs.keys()) :
                 templates.append(SockTemplate(name=out.name,
                 templates.append(SockTemplate(name=out.name,
-                        identifier=out.identifier, is_input=False,))    
+                        identifier=out.identifier, is_input=False,))
         self.outputs.init_sockets(templates)
         self.outputs.init_sockets(templates)
         # now we have our parameters, fill them. This is a little inefficient I guess.
         # now we have our parameters, fill them. This is a little inefficient I guess.
         for out in ui_node.outputs:
         for out in ui_node.outputs:
@@ -1445,7 +1441,7 @@ class UtilityCollectionJoin(MantisNode):
     def reset_execution(self):
     def reset_execution(self):
         super().reset_execution()
         super().reset_execution()
         self.prepared, self.executed = False, False
         self.prepared, self.executed = False, False
-    
+
     def bPrepare(self, bContext = None,):
     def bPrepare(self, bContext = None,):
         if self.inputs['Collections'].links:
         if self.inputs['Collections'].links:
             bCol_groups = []
             bCol_groups = []
@@ -1613,7 +1609,7 @@ class UtilitySetBoneLength(MantisNode):
         self.outputs.init_sockets(outputs)
         self.outputs.init_sockets(outputs)
         self.init_parameters()
         self.init_parameters()
         self.node_type = "UTILITY"
         self.node_type = "UTILITY"
-    
+
     def bPrepare(self, bContext = None,):
     def bPrepare(self, bContext = None,):
         from mathutils import Vector
         from mathutils import Vector
         if matrix := self.evaluate_input("Bone Matrix"):
         if matrix := self.evaluate_input("Bone Matrix"):
@@ -1642,7 +1638,7 @@ class UtilityMatrixSetLocation(MantisNode):
         self.outputs.init_sockets(outputs)
         self.outputs.init_sockets(outputs)
         self.init_parameters()
         self.init_parameters()
         self.node_type = "UTILITY"
         self.node_type = "UTILITY"
-    
+
     def bPrepare(self, bContext = None,):
     def bPrepare(self, bContext = None,):
         from mathutils import Vector
         from mathutils import Vector
         if matrix := self.evaluate_input("Matrix"):
         if matrix := self.evaluate_input("Matrix"):
@@ -1667,7 +1663,7 @@ class UtilityMatrixGetLocation(MantisNode):
         self.outputs.init_sockets(outputs)
         self.outputs.init_sockets(outputs)
         self.init_parameters()
         self.init_parameters()
         self.node_type = "UTILITY"
         self.node_type = "UTILITY"
-    
+
     def bPrepare(self, bContext = None,):
     def bPrepare(self, bContext = None,):
         from mathutils import Vector
         from mathutils import Vector
         if matrix := self.evaluate_input("Matrix"):
         if matrix := self.evaluate_input("Matrix"):
@@ -1688,14 +1684,14 @@ class UtilityMatrixFromXForm(MantisNode):
         self.inputs.init_sockets(inputs)
         self.inputs.init_sockets(inputs)
         self.outputs.init_sockets(outputs)
         self.outputs.init_sockets(outputs)
         self.init_parameters()
         self.init_parameters()
-    
+
     def GetxForm(self):
     def GetxForm(self):
         trace = trace_single_line(self, "xForm")
         trace = trace_single_line(self, "xForm")
         for node in trace[0]:
         for node in trace[0]:
             if (node.node_type == 'XFORM'):
             if (node.node_type == 'XFORM'):
                 return node
                 return node
         raise GraphError("%s is not connected to an xForm" % self)
         raise GraphError("%s is not connected to an xForm" % self)
-        
+
     def bPrepare(self, bContext = None,):
     def bPrepare(self, bContext = None,):
         from mathutils import Vector, Matrix
         from mathutils import Vector, Matrix
         self.parameters["Matrix"] = Matrix.Identity(4)
         self.parameters["Matrix"] = Matrix.Identity(4)
@@ -1725,7 +1721,7 @@ class UtilityAxesFromMatrix(MantisNode):
         self.outputs.init_sockets(outputs)
         self.outputs.init_sockets(outputs)
         self.init_parameters()
         self.init_parameters()
         self.node_type = "UTILITY"
         self.node_type = "UTILITY"
-        
+
     def bPrepare(self, bContext = None,):
     def bPrepare(self, bContext = None,):
         from mathutils import Vector
         from mathutils import Vector
         if matrix := self.evaluate_input("Matrix"):
         if matrix := self.evaluate_input("Matrix"):
@@ -1865,7 +1861,7 @@ class UtilityMatrixAlignRoll(MantisNode):
         #  it directly from the Y axis, the normalized projection of the align
         #  it directly from the Y axis, the normalized projection of the align
         #  axis, and their cross-product. That only nearly worked.
         #  axis, and their cross-product. That only nearly worked.
         # this calculation should not work better, but it does. Why?
         # this calculation should not work better, but it does. Why?
-        
+
 class UtilityTransformationMatrix(MantisNode):
 class UtilityTransformationMatrix(MantisNode):
     def __init__(self, signature, base_tree):
     def __init__(self, signature, base_tree):
         super().__init__(signature, base_tree)
         super().__init__(signature, base_tree)
@@ -1939,13 +1935,14 @@ class UtilityArrayGet(MantisNode):
         self.rerouted=[]
         self.rerouted=[]
 
 
     def bPrepare(self, bContext = None,):
     def bPrepare(self, bContext = None,):
+        from .base_definitions import links_sort_key
         if len(self.rerouted)>0:
         if len(self.rerouted)>0:
             self.prepared, self.executed = True, True
             self.prepared, self.executed = True, True
             return #Either it is already done or it doesn't matter.
             return #Either it is already done or it doesn't matter.
         elif self.prepared == False:
         elif self.prepared == False:
             # sort the array entries
             # sort the array entries
             for inp in self.inputs.values():
             for inp in self.inputs.values():
-                inp.links.sort(key=lambda a : -a.multi_input_sort_id)
+                inp.links.sort(key=links_sort_key)
             oob   = self.evaluate_input("OoB Behaviour")
             oob   = self.evaluate_input("OoB Behaviour")
             index = self.evaluate_input("Index")
             index = self.evaluate_input("Index")
 
 

+ 2 - 3
node_container_common.py

@@ -19,7 +19,6 @@ def get_socket_value(node_socket):
 
 
 # TODO: modify this to work with multi-input nodes
 # TODO: modify this to work with multi-input nodes
 def trace_single_line(node_container, input_name, link_index=0):
 def trace_single_line(node_container, input_name, link_index=0):
-    # DO: refactor this for new link class
     """Traces a line to its input."""
     """Traces a line to its input."""
     nodes = [node_container]
     nodes = [node_container]
     # Trace a single line
     # Trace a single line
@@ -231,8 +230,8 @@ def evaluate_sockets(nc, b_object, props_sockets,):
 def finish_driver(nc, b_object, driver_item, prop):
 def finish_driver(nc, b_object, driver_item, prop):
     # prWhite(nc, prop)
     # prWhite(nc, prop)
     index = driver_item[1]; driver_sock = driver_item[0]
     index = driver_item[1]; driver_sock = driver_item[0]
-    driver_trace = trace_single_line(nc, driver_sock)
-    driver_provider, driver_socket = driver_trace[0][-1], driver_trace[1]
+    node_line, last_socket = trace_single_line(nc, driver_sock)
+    driver_provider, driver_socket = node_line[-1], last_socket
     if index is not None:
     if index is not None:
         driver = driver_provider.parameters[driver_socket.name][index].copy()
         driver = driver_provider.parameters[driver_socket.name][index].copy()
         # this is harmless and necessary for the weird ones where the property is a vector too
         # this is harmless and necessary for the weird ones where the property is a vector too

+ 1 - 1
ops_nodegroup.py

@@ -1,4 +1,4 @@
-qimport bpy
+import bpy
 from bpy.types import Operator
 from bpy.types import Operator
 from mathutils import Vector
 from mathutils import Vector
 
 

+ 3 - 3
preferences.py

@@ -6,8 +6,8 @@ dir_path = os.path.dirname(os.path.realpath(__file__))
 
 
 def get_bl_addon_object(raise_error = False):
 def get_bl_addon_object(raise_error = False):
     from bpy import context
     from bpy import context
-    try_these_first = ['bl_ext.nodes_tools.mantis', 
-        'bl_ext.repos.mantis', 'bl_ext.blender_modules_enabled.mantis',]
+    try_these_first = ['bl_ext.nodes_tools.mantis_beta', 
+        'bl_ext.repos.mantis_beta', 'bl_ext.blender_modules_enabled.mantis_beta',]
     for mantis_key in try_these_first:
     for mantis_key in try_these_first:
         bl_mantis_addon = context.preferences.addons.get(mantis_key)
         bl_mantis_addon = context.preferences.addons.get(mantis_key)
         if bl_mantis_addon is not None: # chekc the addon AND the prefs
         if bl_mantis_addon is not None: # chekc the addon AND the prefs
@@ -16,7 +16,7 @@ def get_bl_addon_object(raise_error = False):
             # the prefs will be None if the addon is disabled.
             # the prefs will be None if the addon is disabled.
     else:
     else:
         for k in context.preferences.addons.keys():
         for k in context.preferences.addons.keys():
-            if k.endswith("mantis"):
+            if k.endswith("mantis_beta"):
                 bl_mantis_addon = context.preferences.addons[k]
                 bl_mantis_addon = context.preferences.addons[k]
                 if bl_mantis_addon is not None:
                 if bl_mantis_addon is not None:
                     if bl_mantis_addon.preferences is not None:
                     if bl_mantis_addon.preferences is not None:

+ 153 - 83
readtree.py

@@ -2,91 +2,103 @@ from .utilities import prRed, prGreen, prPurple, prWhite, prOrange, \
                         wrapRed, wrapGreen, wrapPurple, wrapWhite, wrapOrange
                         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)
+# this function is kind of confusing and is very important,
+# so it bears a full explanation: its purpose is to connect
+# the links going into a group to the nodes in that group.
+# FIRST we connect all the incoming links into the Group Node to
+# a Group Interface node that does nothing but mark the entrance.
+# Then, we connect all the outgoing links back to the nodes
+# that had incoming links, so the nodes OUTSIDE the Node Group
+# are connected directly to BOTH the GroupInterface and the
+# nodes INSIDE the node group.
+# we give the GroupInterface nodes an obscenely high
+# multi_input_sort_id so that they are always last.
+# but since these links are going IN, they shouldn't cause any problems.
+# the sub_sort_id is set here in case there are UI links which represent
+# multiple Mantis links - the mantis links are sorted within the UI links
+# and the UI links are sorted as normal, so all the links are in the right
+# order.... probably. BUG here?
+# I want the Group Interface nodes to be part of the hierarchy...
+# but I want to cut the links. hmmm what to do? Fix it if it causes problems.
+# solution to hypothetical BUG could be to do traversal on the links
+# instead of the sockets.
+def grp_node_reroute_common(in_node, out_node, interface):
+    from .base_definitions import links_sort_key
+    for in_node_input in in_node.inputs:
+        i = 0
+        if len(in_node_input.links)>1: # sort here to ensure correct sub_sort_id
+            in_node_input.links.sort(key=links_sort_key)
+        while (in_node_input.links):
+            in_link = in_node_input.links.pop()
+            from_node = in_link.from_node; from_socket = in_link.from_socket
+            link = from_node.outputs[from_socket].connect(
+                interface,in_node_input.name, sort_id = 2**16, sub_sort_id=i)
+            i += 1; in_link.die()
+    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 j, l in enumerate(interface.inputs[out_node_output.name].links):
+                # we are connecting the link from the ORIGINAL output to the FINAL input.
+                link = l.from_node.outputs[l.from_socket].connect(
+                    to_node, to_socket, sort_id = out_link.multi_input_sort_id)
+                link.sub_sort_id = j
+            out_link.die()
+
+def reroute_links_grp(group, all_nodes):
+    from .internal_containers import GroupInterface
+    interface = GroupInterface(
+        ( *group.signature, "InputInterface"),
+        group.base_tree, group.prototype, 'INPUT',)
+    all_nodes[interface.signature] = interface
+    if group.inputs:
+        if group_input := all_nodes.get(( *group.signature, "NodeGroupInput")):
+            grp_node_reroute_common(group, group_input, interface)
         else:
         else:
             raise RuntimeError("internal error: failed to enter a node group ")
             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',)
+        all_nodes[interface.signature] = interface
+        grp_node_reroute_common(group_output, group, interface)
     else:
     else:
-        raise RuntimeError("error leaving a node group (maybe you are running the tree from inside a node group?)")
+        prOrange(f"WARN: unconnected outputs from a node group "
+                 "(maybe you are running the tree from inside a node group?)")
 
 
 # FIXME I don't think these signatures are unique.
 # FIXME I don't think these signatures are unique.
+# TODO this is a really silly and bad and also really dumb way to do this
 def insert_lazy_parents(nc):
 def insert_lazy_parents(nc):
     from .link_nodes import LinkInherit
     from .link_nodes import LinkInherit
-    from .base_definitions import NodeLink
     inherit_nc = None
     inherit_nc = None
     if nc.inputs["Relationship"].is_connected:
     if nc.inputs["Relationship"].is_connected:
-        link = nc.inputs["Relationship"].links[0]
-        # print(nc)
-        from_nc = link.from_node
-        if from_nc.node_type in ["XFORM"] and link.from_socket in ["xForm Out"]:
+        from .node_container_common import trace_single_line
+        node_line, last_socket = trace_single_line(nc, 'Relationship')
+        # if last_socket is from a valid XFORM, it is the relationship in
+        # because it was traversed from the xForm Out... so get the traverse target.
+        if last_socket.traverse_target is None:
+            return # this is not a valid lazy parent.
+        for other_node in node_line[1:]: # skip the first one, it is the same node
+            if other_node.node_type == 'LINK':
+                return # this one has a realtionship connection.
+            elif other_node.node_type == 'XFORM':
+                break
+        if other_node.node_type in ["XFORM"] and last_socket.traverse_target.name in ["xForm Out"]:
+            for link in other_node.outputs['xForm Out'].links:
+                if link.to_node == nc: link.die()
             inherit_nc = LinkInherit(("MANTIS_AUTOGENERATED", *nc.signature[1:], "LAZY_INHERIT"), nc.base_tree)
             inherit_nc = LinkInherit(("MANTIS_AUTOGENERATED", *nc.signature[1:], "LAZY_INHERIT"), nc.base_tree)
-            for from_link in from_nc.outputs["xForm Out"].links:
-                if from_link.to_node == nc and from_link.to_socket == "Relationship":
-                    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()
-                if to_link.from_node == from_nc and to_link.from_socket == "xForm Out":
-                    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,
-                                     "Inherit Scale":'FULL',
-                                     "Connected":False,
-                                    }
+            l = other_node.outputs['xForm Out'].connect(inherit_nc, 'Parent')
+            l1 = inherit_nc.outputs['Inheritance'].connect(nc, 'Relationship')
+            inherit_nc.parameters = { "Parent":None,
+                                      "Inherit Rotation":True,
+                                      "Inherit Scale":'FULL',
+                                      "Connected":False, }
             # because the from node may have already been done.
             # because the from node may have already been done.
-            init_connections(from_nc)
-            init_dependencies(from_nc)
-            init_connections(inherit_nc)
-            init_dependencies(inherit_nc)
+            init_connections(other_node); init_dependencies(other_node)
+            init_connections(inherit_nc); init_dependencies(inherit_nc)
     return inherit_nc
     return inherit_nc
 
 
 # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** #
 # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** # *** #
@@ -97,9 +109,6 @@ from .base_definitions import replace_types, NodeSocket
 
 
 def autogen_node(base_tree, ui_socket, signature, mContext):
 def autogen_node(base_tree, ui_socket, signature, mContext):
     mantis_node=None
     mantis_node=None
-    from .utilities import  gen_nc_input_for_data
-    # nc_cls = gen_nc_input_for_data(ui_socket)
-    # if (nc_cls):
     from .internal_containers import AutoGenNode
     from .internal_containers import AutoGenNode
     mantis_node = AutoGenNode(signature, base_tree)
     mantis_node = AutoGenNode(signature, base_tree)
     mantis_node.mContext = mContext
     mantis_node.mContext = mContext
@@ -199,6 +208,9 @@ def data_from_tree(base_tree, tree_path, mContext, dummy_nodes, all_nc, all_sche
         from .utilities import link_node_containers
         from .utilities import link_node_containers
         for link in links:
         for link in links:
             link_node_containers((None, *tree_path_names), link, local_nc)
             link_node_containers((None, *tree_path_names), link, local_nc)
+        if current_tree == base_tree:
+            # in the base tree, we need to auto-gen the default values in a slightly different way to node groups.
+            insert_default_values_base_tree(base_tree, all_nc)
         # Now, descend into the Node Groups and recurse
         # Now, descend into the Node Groups and recurse
         for nc in group_nodes:
         for nc in group_nodes:
             data_from_tree(base_tree, tree_path+[nc.prototype], mContext, dummy_nodes, all_nc, all_schema)
             data_from_tree(base_tree, tree_path+[nc.prototype], mContext, dummy_nodes, all_nc, all_schema)
@@ -298,6 +310,68 @@ def get_schema_length_dependencies(node, all_nodes={}):
                     trees.append((sub_node.prototype.node_tree, sub_node.signature))
                     trees.append((sub_node.prototype.node_tree, sub_node.signature))
     return list(filter(deps_filter, deps))
     return list(filter(deps_filter, deps))
 
 
+def insert_default_values_base_tree(base_tree, all_mantis_nodes):
+    # we can get this by name because group inputs are gathered to the bl_idname
+    InputNode = all_mantis_nodes.get((None, 'NodeGroupInput'))
+    if InputNode is None: return # nothing to do here.
+    ui_node = InputNode.prototype
+
+    for i, output in enumerate(InputNode.outputs):
+        ui_output = ui_node.outputs[i] # I need this for the error messages to make sense
+        assert ui_output.identifier == output.name, "Cannot find UI Socket for Default Value"
+        for interface_item in base_tree.interface.items_tree:
+            if interface_item.item_type == 'PANEL': continue
+            if interface_item.identifier == output.name: break
+        else:
+            raise RuntimeError(f"Default value {ui_output.name} does not exist in {base_tree.name} ")
+        if interface_item.item_type == "PANEL":
+            raise RuntimeError(f"Cannot get default value for {ui_output.name} in {base_tree.name} ")
+        default_value = None
+        from bpy.types import bpy_prop_array
+        from mathutils import Vector
+        val_type = None
+        if hasattr(ui_output, 'default_value'):
+            val_type = type(ui_output.default_value) # why tf can't I match/case here?
+        if val_type is bool: default_value = interface_item.default_bool
+        elif val_type is int: default_value = interface_item.default_int
+        elif val_type is float: default_value = interface_item.default_float
+        elif val_type is Vector: default_value = interface_item.default_vector
+        elif val_type is str: default_value = interface_item.default_string
+        elif val_type is bpy_prop_array: default_value = interface_item.default_bool_vector
+        elif interface_item.bl_socket_idname == "xFormSocket":
+            if interface_item.default_xForm == 'ARMATURE':
+                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:
+                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:
+                from .xForm_nodes import xFormArmature
+                autogen_mantis_node = xFormArmature(signature, base_tree)
+                autogen_mantis_node.parameters['Name']=base_tree.name+'_MANTIS_AUTOGEN'
+                autogen_mantis_node.mContext =  InputNode.mContext
+                from mathutils import Matrix
+                autogen_mantis_node.parameters['Matrix'] = Matrix.Identity(4)
+            output_name = 'xForm Out'
+        while output.links:
+            l = output.links.pop()
+            to_node = l.to_node; to_socket = l.to_socket
+            l.die()
+            autogen_mantis_node.outputs[output_name].connect(to_node, to_socket)
+            init_connections(l.from_node); init_dependencies(l.from_node)
+        all_mantis_nodes[autogen_mantis_node.signature]=autogen_mantis_node
 
 
 def parse_tree(base_tree, error_popups=False):
 def parse_tree(base_tree, error_popups=False):
     from uuid import uuid4
     from uuid import uuid4
@@ -404,7 +478,6 @@ def parse_tree(base_tree, error_popups=False):
                     e = execution_error_cleanup(n, e, show_error=error_popups)
                     e = execution_error_cleanup(n, e, show_error=error_popups)
                     if error_popups == False:
                     if error_popups == False:
                         raise e
                         raise e
-
                 schema_solve_done.add(n)
                 schema_solve_done.add(n)
                 for conn in n.hierarchy_connections:
                 for conn in n.hierarchy_connections:
                     if conn not in schema_solve_done and conn not in solve_layer:
                     if conn not in schema_solve_done and conn not in solve_layer:
@@ -436,7 +509,8 @@ def parse_tree(base_tree, error_popups=False):
         if nc.signature[0] == "MANTIS_AUTOGENERATED" and len(nc.inputs) == 0 and len(nc.outputs) == 1:
         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
             from .base_definitions import can_remove_socket_for_autogen
             output=list(nc.outputs.values())[0]
             output=list(nc.outputs.values())[0]
-            value=list(nc.parameters.values())[0]   # IDEA modify the dependecy get function to exclude these nodes completely
+            value=list(nc.parameters.values())[0]
+            # We can remove this node if it is safe to push it into the other node's socket.
             keep_me = False
             keep_me = False
             for l in output.links:
             for l in output.links:
                 to_node = l.to_node; to_socket = l.to_socket
                 to_node = l.to_node; to_socket = l.to_socket
@@ -572,9 +646,8 @@ def execute_tree(nodes, base_tree, context, error_popups = False):
         check_and_add_root(nc, xForm_pass)
         check_and_add_root(nc, xForm_pass)
     mContext.execution_failed = False
     mContext.execution_failed = False
 
 
-    switch_me = [] # switch the mode on these objects
+    select_me, switch_me = [], [] # switch the mode on these objects
     active = None # only need it for switching modes
     active = None # only need it for switching modes
-    select_me = []
     try:
     try:
         sorted_nodes, execution_failed = sort_execution(nodes, xForm_pass)
         sorted_nodes, execution_failed = sort_execution(nodes, xForm_pass)
         for n in sorted_nodes:
         for n in sorted_nodes:
@@ -611,12 +684,10 @@ def execute_tree(nodes, base_tree, context, error_popups = False):
                     raise e
                     raise e
                 execution_failed = True; break
                 execution_failed = True; break
 
 
-
         switch_mode(mode='OBJECT', objects=switch_me)
         switch_mode(mode='OBJECT', objects=switch_me)
         # switch to pose mode here so that the nodes can use the final pose data
         # switch to pose mode here so that the nodes can use the final pose data
         # this will require them to update the depsgraph.
         # this will require them to update the depsgraph.
 
 
-
         for ob in switch_me:
         for ob in switch_me:
             ob.data.pose_position = 'POSE'
             ob.data.pose_position = 'POSE'
 
 
@@ -629,7 +700,6 @@ def execute_tree(nodes, base_tree, context, error_popups = False):
                     raise e
                     raise e
                 execution_failed = True; break
                 execution_failed = True; break
 
 
-
         # REST pose for deformer bind, so everything is in the rest position
         # REST pose for deformer bind, so everything is in the rest position
         for ob in switch_me:
         for ob in switch_me:
             ob.data.pose_position = 'REST'
             ob.data.pose_position = 'REST'

+ 2 - 2
schema_nodes.py

@@ -16,7 +16,7 @@ def TellClasses():
     ]
     ]
 
 
 def schema_init_sockets(nc, is_input = True, in_out='INPUT', category=''):
 def schema_init_sockets(nc, is_input = True, in_out='INPUT', category=''):
-    from .utilities import tree_from_nc
+    from .utilities import tree_from_nc, read_schema_type
     parent_tree = tree_from_nc(nc.signature, nc.base_tree)
     parent_tree = tree_from_nc(nc.signature, nc.base_tree)
     if is_input:
     if is_input:
         sockets=nc.inputs
         sockets=nc.inputs
@@ -25,7 +25,7 @@ def schema_init_sockets(nc, is_input = True, in_out='INPUT', category=''):
     if category in ['Constant', 'Array', 'Connection']:
     if category in ['Constant', 'Array', 'Connection']:
         for item in parent_tree.interface.items_tree:
         for item in parent_tree.interface.items_tree:
             if item.item_type == 'PANEL': continue
             if item.item_type == 'PANEL': continue
-            if item.parent and item.parent.name == category:
+            if item.parent and read_schema_type(item) == category:
                 if item.in_out == in_out:
                 if item.in_out == in_out:
                     sockets.init_sockets([item.name])
                     sockets.init_sockets([item.name])
     nc.init_parameters()
     nc.init_parameters()

+ 18 - 10
schema_nodes_ui.py

@@ -7,7 +7,8 @@ from .utilities import (prRed, prGreen, prPurple, prWhite,
                               wrapOrange,)
                               wrapOrange,)
 from bpy.props import BoolProperty
 from bpy.props import BoolProperty
 
 
-from .utilities import get_socket_maps, relink_socket_map, do_relink
+from .utilities import (get_socket_maps, relink_socket_map,
+                        do_relink, read_schema_type)
 
 
 
 
 def TellClasses():
 def TellClasses():
@@ -29,7 +30,6 @@ def TellClasses():
 # - check what happens when these get plugged into each other
 # - check what happens when these get plugged into each other
 # - probably disallow all or most of these connections in insert_link or update
 # - probably disallow all or most of these connections in insert_link or update
 
 
-
 class SchemaIndex(Node, SchemaUINode):
 class SchemaIndex(Node, SchemaUINode):
     '''The current index of the schema execution'''
     '''The current index of the schema execution'''
     bl_idname = 'SchemaIndex'
     bl_idname = 'SchemaIndex'
@@ -66,7 +66,8 @@ class SchemaArrayInput(Node, SchemaUINode):
         self.outputs.clear()
         self.outputs.clear()
         for item in self.id_data.interface.items_tree:
         for item in self.id_data.interface.items_tree:
             if item.item_type == 'PANEL': continue
             if item.item_type == 'PANEL': continue
-            if item.parent and item.in_out == 'INPUT' and item.parent.name == 'Array':
+            parent_name = read_schema_type(item)
+            if item.in_out == 'INPUT' and parent_name == 'Array':
                 relink_socket_map(self, self.outputs, output_map, item, in_out='OUTPUT')
                 relink_socket_map(self, self.outputs, output_map, item, in_out='OUTPUT')
         if '__extend__' in output_map.keys() and output_map['__extend__']:
         if '__extend__' in output_map.keys() and output_map['__extend__']:
             do_relink(self, None, output_map, in_out='OUTPUT', parent_name='Array' )
             do_relink(self, None, output_map, in_out='OUTPUT', parent_name='Array' )
@@ -98,7 +99,8 @@ class SchemaArrayInputAll(Node, SchemaUINode):
         self.outputs.clear()
         self.outputs.clear()
         for item in self.id_data.interface.items_tree:
         for item in self.id_data.interface.items_tree:
             if item.item_type == 'PANEL': continue
             if item.item_type == 'PANEL': continue
-            if item.parent and item.in_out == 'INPUT' and item.parent.name == 'Array':
+            parent_name = read_schema_type(item)
+            if item.in_out == 'INPUT' and parent_name == 'Array':
                 relink_socket_map(self, self.outputs, output_map, item, in_out='OUTPUT')
                 relink_socket_map(self, self.outputs, output_map, item, in_out='OUTPUT')
         if '__extend__' in output_map.keys() and output_map['__extend__']:
         if '__extend__' in output_map.keys() and output_map['__extend__']:
             do_relink(self, None, output_map, in_out='OUTPUT', parent_name='Array' )
             do_relink(self, None, output_map, in_out='OUTPUT', parent_name='Array' )
@@ -132,7 +134,8 @@ class SchemaArrayInputGet(Node, SchemaUINode):
         self.outputs.clear()
         self.outputs.clear()
         for item in self.id_data.interface.items_tree:
         for item in self.id_data.interface.items_tree:
             if item.item_type == 'PANEL': continue
             if item.item_type == 'PANEL': continue
-            if item.parent and item.in_out == 'INPUT' and item.parent.name == 'Array':
+            parent_name = read_schema_type(item)
+            if item.in_out == 'INPUT' and parent_name == 'Array':
                 relink_socket_map(self, self.outputs, output_map, item, in_out='OUTPUT')
                 relink_socket_map(self, self.outputs, output_map, item, in_out='OUTPUT')
         if '__extend__' in output_map.keys() and output_map['__extend__']:
         if '__extend__' in output_map.keys() and output_map['__extend__']:
             do_relink(self, None, output_map, in_out='OUTPUT', parent_name='Array' )
             do_relink(self, None, output_map, in_out='OUTPUT', parent_name='Array' )
@@ -164,7 +167,8 @@ class SchemaArrayOutput(Node, SchemaUINode):
         self.inputs.clear()
         self.inputs.clear()
         for item in self.id_data.interface.items_tree:
         for item in self.id_data.interface.items_tree:
             if item.item_type == 'PANEL': continue
             if item.item_type == 'PANEL': continue
-            if item.parent and item.in_out == 'OUTPUT' and item.parent.name == 'Array':
+            parent_name = read_schema_type(item)
+            if item.in_out == 'OUTPUT' and parent_name == 'Array':
                 relink_socket_map(self, self.inputs, input_map, item, in_out='INPUT')
                 relink_socket_map(self, self.inputs, input_map, item, in_out='INPUT')
         if '__extend__' in input_map.keys() and input_map['__extend__']:
         if '__extend__' in input_map.keys() and input_map['__extend__']:
             do_relink(self, None, input_map, in_out='INPUT', parent_name='Array' )
             do_relink(self, None, input_map, in_out='INPUT', parent_name='Array' )
@@ -198,7 +202,8 @@ class SchemaConstInput(Node, SchemaUINode):
         self.outputs.clear()
         self.outputs.clear()
         for item in self.id_data.interface.items_tree:
         for item in self.id_data.interface.items_tree:
             if item.item_type == 'PANEL': continue
             if item.item_type == 'PANEL': continue
-            if item.parent and item.in_out == 'INPUT' and item.parent.name == 'Constant':
+            parent_name = read_schema_type(item)
+            if item.in_out == 'INPUT' and parent_name == 'Constant':
                 relink_socket_map(self, self.outputs, output_map, item, in_out='OUTPUT')
                 relink_socket_map(self, self.outputs, output_map, item, in_out='OUTPUT')
         if '__extend__' in output_map.keys() and output_map['__extend__']:
         if '__extend__' in output_map.keys() and output_map['__extend__']:
             do_relink(self, None, output_map, in_out='OUTPUT', parent_name='Constant' )
             do_relink(self, None, output_map, in_out='OUTPUT', parent_name='Constant' )
@@ -233,7 +238,8 @@ class SchemaConstOutput(Node, SchemaUINode):
         s = self.inputs.new('UnsignedIntSocket', "Expose at Index")
         s = self.inputs.new('UnsignedIntSocket', "Expose at Index")
         for item in self.id_data.interface.items_tree:
         for item in self.id_data.interface.items_tree:
             if item.item_type == 'PANEL': continue
             if item.item_type == 'PANEL': continue
-            if item.parent and item.in_out == 'OUTPUT' and item.parent.name == 'Constant':
+            parent_name = read_schema_type(item)
+            if item.in_out == 'OUTPUT' and parent_name == 'Constant':
                 relink_socket_map(self, self.inputs, input_map, item, in_out='INPUT')
                 relink_socket_map(self, self.inputs, input_map, item, in_out='INPUT')
         if '__extend__' in input_map.keys() and input_map['__extend__']:
         if '__extend__' in input_map.keys() and input_map['__extend__']:
             do_relink(self, None, input_map, in_out='INPUT', parent_name='Constant' )
             do_relink(self, None, input_map, in_out='INPUT', parent_name='Constant' )
@@ -271,7 +277,8 @@ class SchemaOutgoingConnection(Node, SchemaUINode):
         self.inputs.clear()
         self.inputs.clear()
         for item in self.id_data.interface.items_tree:
         for item in self.id_data.interface.items_tree:
             if item.item_type == 'PANEL': continue
             if item.item_type == 'PANEL': continue
-            if item.parent and item.in_out == 'OUTPUT' and item.parent.name == 'Connection':
+            parent_name = read_schema_type(item)
+            if item.in_out == 'OUTPUT' and parent_name == 'Connection':
                 relink_socket_map(self, self.inputs, input_map, item, in_out='INPUT')
                 relink_socket_map(self, self.inputs, input_map, item, in_out='INPUT')
         if '__extend__' in input_map.keys() and input_map['__extend__']:
         if '__extend__' in input_map.keys() and input_map['__extend__']:
             do_relink(self, None, input_map, in_out='INPUT', parent_name='Connection' )
             do_relink(self, None, input_map, in_out='INPUT', parent_name='Connection' )
@@ -307,7 +314,8 @@ class SchemaIncomingConnection(Node, SchemaUINode):
         self.outputs.clear()
         self.outputs.clear()
         for item in self.id_data.interface.items_tree:
         for item in self.id_data.interface.items_tree:
             if item.item_type == 'PANEL': continue
             if item.item_type == 'PANEL': continue
-            if item.parent and item.in_out == 'INPUT' and item.parent.name == 'Connection':
+            parent_name = read_schema_type(item)
+            if item.in_out == 'INPUT' and parent_name == 'Connection':
                 relink_socket_map(self, self.outputs, output_map, item, in_out='OUTPUT')
                 relink_socket_map(self, self.outputs, output_map, item, in_out='OUTPUT')
         if '__extend__' in output_map.keys() and output_map['__extend__']:
         if '__extend__' in output_map.keys() and output_map['__extend__']:
             do_relink(self, None, output_map, in_out='OUTPUT', parent_name='Connection' )
             do_relink(self, None, output_map, in_out='OUTPUT', parent_name='Connection' )

+ 47 - 6
schema_solve.py

@@ -4,7 +4,7 @@ from .utilities import (prRed, prGreen, prPurple, prWhite,
                               wrapOrange,)
                               wrapOrange,)
 from .utilities import init_connections, init_dependencies, get_link_in_out
 from .utilities import init_connections, init_dependencies, get_link_in_out
 from .base_definitions import (SchemaUINode, custom_props_types, \
 from .base_definitions import (SchemaUINode, custom_props_types, \
-    MantisNodeGroup, SchemaGroup, replace_types, GraphError)
+    MantisNodeGroup, SchemaGroup, replace_types, GraphError, links_sort_key)
 from .node_container_common import setup_custom_props_from_np
 from .node_container_common import setup_custom_props_from_np
 # a class that solves Schema nodes
 # a class that solves Schema nodes
 from bpy.types import NodeGroupInput, NodeGroupOutput
 from bpy.types import NodeGroupInput, NodeGroupOutput
@@ -61,7 +61,7 @@ class SchemaSolver:
         # Sort the multi-input nodes in reverse order of ID, this ensures that they are
         # Sort the multi-input nodes in reverse order of ID, this ensures that they are
         #   read in the order they were created
         #   read in the order they were created
         for inp in self.node.inputs.values():
         for inp in self.node.inputs.values():
-            inp.links.sort(key=lambda a : -a.multi_input_sort_id)
+            inp.links.sort(key=links_sort_key)
 
 
         from bpy.types import NodeGroupInput, NodeGroupOutput
         from bpy.types import NodeGroupInput, NodeGroupOutput
         for ui_node in self.tree.nodes:
         for ui_node in self.tree.nodes:
@@ -89,6 +89,35 @@ class SchemaSolver:
                 mantis_node = SchemaConstOutput(signature=signature, base_tree=self.node.base_tree, parent_schema_node=self.node)
                 mantis_node = SchemaConstOutput(signature=signature, base_tree=self.node.base_tree, parent_schema_node=self.node)
                 self.schema_nodes[signature] = mantis_node
                 self.schema_nodes[signature] = mantis_node
                 mantis_node.fill_parameters(ui_node)
                 mantis_node.fill_parameters(ui_node)
+        interface = self.setup_interface_node(signature=( *self.signature, "InputInterface"))
+    
+    def setup_interface_node(self, signature):
+        from .internal_containers import GroupInterface
+        interface = GroupInterface(
+            signature,
+            self.node.base_tree, self.node.prototype, 'INPUT',)
+        # now we need to connect it up
+        # adapted from readtree.grp_node_reroute_common
+        from .base_definitions import links_sort_key
+        for in_node_input in self.node.inputs:
+            if in_node_input.name != 'Schema Length':
+                for interface_socket in self.tree.interface.items_tree:
+                    if interface_socket.item_type == 'PANEL': continue
+                    if interface_socket.identifier == in_node_input.name: break
+                if hasattr(interface_socket, "is_array") and interface_socket.is_array: continue
+                if hasattr(interface_socket, "is_connection") and interface_socket.is_connection: continue
+                # for now, do not try and connect to the array/connection inputs.
+            i = 0
+            if len(in_node_input.links)>1: # sort here to ensure correct sub_sort_id
+                in_node_input.links.sort(key=links_sort_key)
+            for in_link in in_node_input.links:
+                from_node = in_link.from_node; from_socket = in_link.from_socket
+                link = from_node.outputs[from_socket].connect(
+                    interface,in_node_input.name, sort_id = 2**16, sub_sort_id=i)
+                i += 1
+            in_node_input.links.sort(key=links_sort_key)
+        return interface # I want to be able to set this up elsewhere
+
 
 
     def set_index_strings(self):
     def set_index_strings(self):
         self.index_str = lambda : '.'+str(self.uuid)+'.'+str(self.index).zfill(4)
         self.index_str = lambda : '.'+str(self.uuid)+'.'+str(self.index).zfill(4)
@@ -101,9 +130,9 @@ class SchemaSolver:
         """ Sort and store the links to/from the Schema group node."""
         """ Sort and store the links to/from the Schema group node."""
         for item in self.tree.interface.items_tree:
         for item in self.tree.interface.items_tree:
             if item.item_type == 'PANEL': continue
             if item.item_type == 'PANEL': continue
-            parent_name='Constant'
-            if item.parent.name != '': # in an "prphan" item this is left blank , it is not None or an AttributeError.
-                parent_name = item.parent.name
+            from .utilities import read_schema_type
+            parent_name = read_schema_type(item)
+            # just gonna try and make the 
             match parent_name:
             match parent_name:
                 case 'Connection':
                 case 'Connection':
                     if item.in_out == 'INPUT':
                     if item.in_out == 'INPUT':
@@ -202,6 +231,11 @@ class SchemaSolver:
             mantis_node.fill_parameters(prototype_ui_node)
             mantis_node.fill_parameters(prototype_ui_node)
             # be sure to pass on the Mantis Context to them
             # be sure to pass on the Mantis Context to them
             mantis_node.mContext=mContext
             mantis_node.mContext=mContext
+        # set up an interface node at this point.
+        interface = self.setup_interface_node(signature=( *self.signature, "InputInterface"+self.index_str() ))
+        interface.mContext = mContext
+        frame_mantis_nodes[interface.signature] = interface
+        self.all_nodes[interface.signature] = interface
 
 
     def handle_link_from_index_input(self, index, frame_mantis_nodes, ui_link):
     def handle_link_from_index_input(self, index, frame_mantis_nodes, ui_link):
         from .base_definitions import can_remove_socket_for_autogen
         from .base_definitions import can_remove_socket_for_autogen
@@ -246,6 +280,12 @@ class SchemaSolver:
         else:
         else:
             raise RuntimeError("247 I This code should be unreachable. Please report this as a bug!")
             raise RuntimeError("247 I This code should be unreachable. Please report this as a bug!")
 
 
+    # TODO: link the incoming connection to the current Interface node
+    #       It is complicated because the Interface needs the identifier
+    #       but most of this stuff uses the name and I donno how to get the ID
+    #       this isn't very important. I'll add this feature when someone tries
+    #       to use it and complains.
+
     def handle_link_from_incoming_connection_input(self, frame_mantis_nodes, ui_link):
     def handle_link_from_incoming_connection_input(self, frame_mantis_nodes, ui_link):
         incoming = self.incoming_connections[ui_link.from_socket.name]
         incoming = self.incoming_connections[ui_link.from_socket.name]
         if incoming is not None:
         if incoming is not None:
@@ -389,7 +429,8 @@ class SchemaSolver:
             to_socket_name=ui_link.to_socket.name
             to_socket_name=ui_link.to_socket.name
             if to_node.node_type in ['DUMMY_SCHEMA']:
             if to_node.node_type in ['DUMMY_SCHEMA']:
                 to_socket_name=ui_link.to_socket.identifier
                 to_socket_name=ui_link.to_socket.identifier
-            connection = NodeLink(l.from_node, l.from_socket, to_node, to_socket_name, l.multi_input_sort_id)
+            connection = NodeLink(l.from_node, l.from_socket, to_node, to_socket_name,
+                                  l.multi_input_sort_id, l.sub_sort_id)
             to_node.flush_links()
             to_node.flush_links()
 
 
     def handle_link_to_constant_output(self, frame_mantis_nodes, index, ui_link,  to_ui_node):
     def handle_link_to_constant_output(self, frame_mantis_nodes, index, ui_link,  to_ui_node):

+ 127 - 0
socket_definitions.py

@@ -292,6 +292,133 @@ def tell_valid_bl_idnames():
     valid_classes = filter(lambda cls : cls.is_valid_interface_type, [cls for cls in TellClasses()])
     valid_classes = filter(lambda cls : cls.is_valid_interface_type, [cls for cls in TellClasses()])
     return (cls.bl_idname for cls in valid_classes)
     return (cls.bl_idname for cls in valid_classes)
 
 
+
+enum_default_xForm_values =(
+        ('NONE', "None", "None - fail if unconnected (RECOMMENDED)."),
+        ('ARMATURE', "Generated Armature", "Generate an armature automatically. "
+                     "PLEASE use this only for development and testing. "
+                     "If you use this as a feature in your rigs you will be sorry."),)
+    
+# Custom Interface Types give the user the ability to set properties for the interface
+# we'll define a base class, and generate the individual classes from the base class
+# but we'll leave the option to define a few of them directly.
+from bpy.types import NodeTreeInterfaceSocket
+
+def interface_socket_update(self, context):
+    # we're just gonna do this the dumb way for now and invalidate the tree
+    # BUG HACK TODO actually I am gonna do this stuff later
+    # later, I can do this based on the connections in the tree
+    # and the socket updater can use the same code for group interface modifications
+    # TODO do this stuff because the tree will be a lot snappier
+    pass
+
+
+interface_default_value_description="The default value of the socket when it is not connected."
+class MantisInterfaceSocketBaseClass():
+    is_array : bpy.props.BoolProperty(default =False, update=interface_socket_update,
+            description="Whether the socket is an array, otherwise it is constant." ) 
+    is_connection : bpy.props.BoolProperty(default =False, update=interface_socket_update,
+            description="If the socket is a connection or not. Ensure this is always paired"
+                        " with an input and an output." ) 
+    connected_to : bpy.props.StringProperty(default="", update=interface_socket_update,
+            description="The name of the socket this one is connected to." ) 
+    # we are just gonna use ONE base class (it's easier)
+    # so generate ALL properties and show only what is needed.
+    default_string : bpy.props.StringProperty(default="", update=interface_socket_update,
+            description=interface_default_value_description, ) 
+    default_float : bpy.props.FloatProperty(default=0.0, update=interface_socket_update,
+            description=interface_default_value_description, ) 
+    default_vector : bpy.props.FloatVectorProperty( size = 3, default = (0.0, 0.0, 0.0, ),
+            description=interface_default_value_description, update=interface_socket_update,) 
+    default_int : bpy.props.IntProperty(default=0, update=interface_socket_update,
+            description=interface_default_value_description, ) 
+    default_bool : bpy.props.BoolProperty(default=False, update=interface_socket_update,
+            description=interface_default_value_description, ) 
+    default_bool_vector : bpy.props.BoolVectorProperty(subtype = "XYZ", update=interface_socket_update,
+            description=interface_default_value_description, ) 
+    default_xForm : bpy.props.EnumProperty( default = 'NONE', update = interface_socket_update,
+        items=enum_default_xForm_values, description=interface_default_value_description,)
+
+def interface_draw(self, context, layout):
+    if not self.is_connection:
+        layout.prop(self, "is_array", text="Is Array", toggle=True,)
+    if not self.is_array and self.id_data.bl_idname == 'SchemaTree':
+        layout.prop(self, "is_connection", text="Is Connection", toggle=True,)
+        if False: # DISABLED for now because it will take a big change to Schema to make this work.
+            if self.is_connection: # only show this if in a Schema AND set to is_connection
+                layout.prop(self, "connected_to", text="Connected To", toggle=True,)
+
+# Different classes to handle different data types. In the future, these should also
+#  have settable min/max and such where appropriate
+def interface_string_draw(self, context, layout):
+    layout.prop(self, "default_string", text="Default Value", toggle=True,)
+    interface_draw(self, context, layout)
+
+def interface_float_draw(self, context, layout):
+    layout.prop(self, "default_float", text="Default Value", toggle=True,)
+    interface_draw(self, context, layout)
+
+def interface_vector_draw(self, context, layout):
+    layout.prop(self, "default_vector", text="Default Value", toggle=True,)
+    interface_draw(self, context, layout)
+
+def interface_int_draw(self, context, layout):
+    layout.prop(self, "default_int", text="Default Value", toggle=True,)
+    interface_draw(self, context, layout)
+
+def interface_bool_draw(self, context, layout):
+    layout.prop(self, "default_bool", text="Default Value", toggle=True,)
+    interface_draw(self, context, layout)
+
+def interface_bool_vector_draw(self, context, layout):
+    layout.prop(self, "default_bool_vector", text="Default Value", toggle=True,)
+    interface_draw(self, context, layout)
+
+def interface_xform_draw(self, context, layout):
+    if self.in_out == 'INPUT':
+        layout.prop(self, "default_xForm", text="Default Value", toggle=True,)
+    interface_draw(self, context, layout)
+
+
+def generate_custom_interface_types():
+    generated_classes = []
+    # copied from above
+    valid_classes = filter(lambda cls : cls.is_valid_interface_type, [cls for cls in TellClasses()])
+    for cls in valid_classes:
+        name = cls.__name__ + "Interface"
+            
+        my_interface_draw = interface_draw
+        # set the right draw function by the value's type
+        match map_color_to_socket_type(cls.color_simple): #there has to be a better way to do this
+            case "BooleanSocket":
+                my_interface_draw = interface_bool_draw
+            case "IntSocket":
+                my_interface_draw = interface_int_draw
+            case "FloatSocket":
+                my_interface_draw = interface_float_draw
+            case "BooleanThreeTupleSocket":
+                my_interface_draw = interface_bool_vector_draw
+            case "VectorSocket":
+                my_interface_draw = interface_vector_draw
+            case "StringSocket":
+                my_interface_draw = interface_string_draw
+            case "xFormSocket":
+                my_interface_draw = interface_xform_draw
+
+        interface = type(
+                      name,
+                      (MantisInterfaceSocketBaseClass, NodeTreeInterfaceSocket,),
+                      {
+                          "draw"             : my_interface_draw,
+                          "bl_idname"        : name,
+                          "bl_socket_idname" : cls.bl_idname,
+                          "socket_type"      : cls.bl_idname,
+                      },
+                  )
+        generated_classes.append(interface)
+    return generated_classes
+
+
 # Was setting color like this:
 # Was setting color like this:
 # color : bpy.props.FloatVectorProperty(size = 4, default = cFCurve,)
 # color : bpy.props.FloatVectorProperty(size = 4, default = cFCurve,)
 # but this didn't work when Blender automatically generated interface classes?
 # but this didn't work when Blender automatically generated interface classes?

+ 82 - 43
utilities.py

@@ -111,31 +111,33 @@ def get_node_prototype(sig, base_tree):
 # This one is the simplest case so it is easiest to use its own function.
 # 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):
 def set_string_variables_at_creation_time(n, prototype, mContext):
     # we're gonna store the variables using the node's signature
     # 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)
+    group_key = ''.join(n.signature[1:])
+    # Get the variables if they exist, otherwise just get a dict
+    group_vars=mContext.string_variables.get(group_key, {})
+    mContext.string_variables[group_key]=group_vars
     for input in prototype.inputs:
     for input in prototype.inputs:
         if hasattr(input, "default_value") and not input.is_linked:
         if hasattr(input, "default_value") and not input.is_linked:
             if isinstance (input.default_value, str):
             if isinstance (input.default_value, str):
                 group_vars[input.name]=input.default_value
                 group_vars[input.name]=input.default_value
             elif hasattr(input.default_value, "name"):
             elif hasattr(input.default_value, "name"):
                 group_vars[input.name]=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
-
-
+        elif hasattr(input, "default_value") and input.is_linked:
+            group_vars[input.name]=None
+
+def set_string_variables_at_execution(interface_node, var_name):
+    # get the ID of the input
+    prototype = get_node_prototype(interface_node.signature[1:-1], 
+                                   interface_node.base_tree)
+    tree = prototype.node_tree
+    for interface_item in tree.interface.items_tree:
+        if interface_item.item_type == 'PANEL': continue
+        if interface_item.name == var_name: break
+    else:
+        prRed(f"Failed to get value for variable ${var_name}."); return
+    var_value = interface_node.evaluate_input(interface_item.identifier)
+    group_key = ''.join(interface_node.signature[1:-1])
+    group_vars=interface_node.mContext.string_variables.get(group_key, {})
+    group_vars[var_name]=var_value
 
 
 ##################################################################################################
 ##################################################################################################
 # groups and changing sockets -- this is used extensively by Schema.
 # groups and changing sockets -- this is used extensively by Schema.
@@ -275,6 +277,28 @@ def do_relink(node, socket, map, in_out='INPUT', parent_name = ''):
             except (AttributeError, ValueError): # must be readonly or maybe it doesn't have a d.v.
             except (AttributeError, ValueError): # must be readonly or maybe it doesn't have a d.v.
                 pass
                 pass
 
 
+def read_schema_type(interface_item):
+    # VERSIONING CODE
+    tree=interface_item.id_data
+    version = tree.mantis_version
+    old_version = False
+    if  version[0] == 0: 
+        if version[1] < 12: old_version = True
+        elif version[1] == 12 and version[2] < 27: old_version = True
+    # unfortunately we need to check this stuff for the versioning code to run correctly the first time.
+    # UNLESS I can find a way to prevent this code from running before versioning
+
+    if old_version or (not hasattr(interface_item, 'is_array')):
+        # it is not a custom interface class and/or the file is old.
+        if interface_item.parent:
+            return interface_item.parent.name
+    else:
+        if interface_item.is_array:
+            return 'Array'
+        if interface_item.is_connection:
+            return 'Connection'
+    return 'Constant'
+
 def update_interface(interface, name, in_out, sock_type, parent_name):
 def update_interface(interface, name, in_out, sock_type, parent_name):
     from bpy.app import version as bpy_version
     from bpy.app import version as bpy_version
     if parent_name:
     if parent_name:
@@ -296,26 +320,40 @@ def update_interface(interface, name, in_out, sock_type, parent_name):
 
 
 # D.node_groups['Rigging Nodes'].interface.new_socket('beans', description='the b word', socket_type='NodeSocketGeometry')
 # D.node_groups['Rigging Nodes'].interface.new_socket('beans', description='the b word', socket_type='NodeSocketGeometry')
 #UGLY BAD REFACTOR
 #UGLY BAD REFACTOR
-def relink_socket_map_add_socket(node, socket_collection, item, in_out=None,):
+def relink_socket_map_add_socket(node, socket_collection, item,  in_out=None,):
     from bpy.app import version as bpy_version
     from bpy.app import version as bpy_version
-    if not in_out: in_out=item.in_out
-    if node.bl_idname in ['MantisSchemaGroup'] and item.parent and item.parent.name == 'Array':
-        multi = True if in_out == 'INPUT' else False
-        # have to work around a bug in 4.5.0 that prevents me from declaring custom socket types
-        # I have arbitrarily chosen to use the NodeSocketGeometry type to signal that this one is affected.
-        if bpy_version == (4, 5, 0) and item.bl_socket_idname == 'NodeSocketGeometry':
-            from .versioning import socket_add_workaround_for_4_5_0_LTS
-            s = socket_add_workaround_for_4_5_0_LTS(item, socket_collection, multi)
-        else:
-            s = socket_collection.new(type=item.bl_socket_idname, name=item.name, identifier=item.identifier,  use_multi_input=multi)
+    # if not in_out: in_out=item.in_out
+    multi=False
+    if in_out == 'INPUT' and read_schema_type(item) == 'Array':
+        multi = True
+    # have to work around a bug in 4.5.0 that prevents me from declaring custom socket types
+    # I have arbitrarily chosen to use the NodeSocketGeometry type to signal that this one is affected.
+    if bpy_version == (4, 5, 0) and item.bl_socket_idname == 'NodeSocketGeometry':
+        from .versioning import socket_add_workaround_for_4_5_0_LTS
+        s = socket_add_workaround_for_4_5_0_LTS(item, socket_collection, multi)
     else:
     else:
-        if bpy_version == (4, 5, 0) and item.bl_socket_idname == 'NodeSocketGeometry':
-            from .versioning import socket_add_workaround_for_4_5_0_LTS
-            s = socket_add_workaround_for_4_5_0_LTS(item, socket_collection, multi=False,)
-        else:
-            s = socket_collection.new(type=item.bl_socket_idname, name=item.name, identifier=item.identifier)
-    if item.parent.name == 'Array': s.display_shape = 'SQUARE_DOT'
-    elif item.parent.name == 'Constant': s.display_shape='CIRCLE_DOT'
+        s = socket_collection.new(type=item.bl_socket_idname, name=item.name, identifier=item.identifier,  use_multi_input=multi)
+    if hasattr(s, 'default_value') and hasattr(s, 'is_valid_interface_type') and \
+          s.is_valid_interface_type == True:
+        if s.bl_idname not in ['MatrixSocket']: # no default value implemented
+            from bpy.types import bpy_prop_array
+            from mathutils import Vector
+            default_value = 'REPORT BUG ON GITLAB' # default to bug string
+            val_type = type(s.default_value) # why tf can't I match/case here?
+            if val_type is bool: default_value = item.default_bool
+            if val_type is int: default_value = item.default_int
+            if val_type is float: default_value = item.default_float
+            if val_type is Vector: default_value = item.default_vector
+            if val_type is str: default_value = item.default_string
+            if val_type is bpy_prop_array: default_value = item.default_bool_vector
+            s.default_value = default_value
+
+    if read_schema_type(item) == 'Array': s.display_shape = 'SQUARE_DOT'
+    elif node.bl_idname in ['MantisSchemaGroup'] and read_schema_type(item) == 'Constant':
+        s.display_shape='CIRCLE_DOT'
+
+    # if item.parent.name == 'Array': s.display_shape = 'SQUARE_DOT'
+    # elif item.parent.name == 'Constant': s.display_shape='CIRCLE_DOT'
     return s
     return s
 
 
 # TODO REFACTOR THIS
 # TODO REFACTOR THIS
@@ -323,9 +361,9 @@ def relink_socket_map_add_socket(node, socket_collection, item, in_out=None,):
 # but I have provided this interface to Mantis
 # but I have provided this interface to Mantis
 # I did not follow the Single Responsibility Principle
 # I did not follow the Single Responsibility Principle
 # I am now suffering for it, as I rightly deserve.
 # I am now suffering for it, as I rightly deserve.
-def relink_socket_map(node, socket_collection, map, item, in_out=None,):
-    s = relink_socket_map_add_socket(node, socket_collection, item, in_out=None,)
-    do_relink(node, s, map)
+def relink_socket_map(node, socket_collection, map, item, in_out):
+    new_socket = relink_socket_map_add_socket(node, socket_collection, item, in_out,)
+    do_relink(node, new_socket, map, in_out, parent_name=read_schema_type(item))
 
 
 def unique_socket_name(node, other_socket, tree):
 def unique_socket_name(node, other_socket, tree):
     name_stem = other_socket.bl_label; num=0
     name_stem = other_socket.bl_label; num=0
@@ -617,7 +655,8 @@ def schema_dependency_handle_item(schema, all_nc, item,):
     if item.in_out == 'INPUT':
     if item.in_out == 'INPUT':
         dependencies = schema.dependencies
         dependencies = schema.dependencies
         hierarchy_dependencies = schema.hierarchy_dependencies
         hierarchy_dependencies = schema.hierarchy_dependencies
-        if item.parent and item.parent.name == 'Array':
+        parent_name = read_schema_type(item)
+        if parent_name == 'Array':
             for schema_idname in ['SchemaArrayInput', 'SchemaArrayInputGet', 'SchemaArrayInputAll']:
             for schema_idname in ['SchemaArrayInput', 'SchemaArrayInputGet', 'SchemaArrayInputAll']:
                 if (nc := all_nc.get( (*schema.signature, schema_idname) )):
                 if (nc := all_nc.get( (*schema.signature, schema_idname) )):
                     for to_link in nc.outputs[item.name].links:
                     for to_link in nc.outputs[item.name].links:
@@ -632,7 +671,7 @@ def schema_dependency_handle_item(schema, all_nc, item,):
                             if hierarchy:
                             if hierarchy:
                                 hierarchy_dependencies.append(from_link.from_node)
                                 hierarchy_dependencies.append(from_link.from_node)
                             dependencies.append(from_link.from_node)
                             dependencies.append(from_link.from_node)
-        if item.parent and item.parent.name == 'Constant':
+        if parent_name == 'Constant':
             if nc := all_nc.get((*schema.signature, 'SchemaConstInput')):
             if nc := all_nc.get((*schema.signature, 'SchemaConstInput')):
                 for to_link in nc.outputs[item.name].links:
                 for to_link in nc.outputs[item.name].links:
                     if to_link.to_socket in to_name_filter:
                     if to_link.to_socket in to_name_filter:
@@ -646,7 +685,7 @@ def schema_dependency_handle_item(schema, all_nc, item,):
                         if hierarchy:
                         if hierarchy:
                             hierarchy_dependencies.append(from_link.from_node)
                             hierarchy_dependencies.append(from_link.from_node)
                         dependencies.append(from_link.from_node)
                         dependencies.append(from_link.from_node)
-        if item.parent and item.parent.name == 'Connection':
+        if parent_name == 'Connection':
             if nc := all_nc.get((*schema.signature, 'SchemaIncomingConnection')):
             if nc := all_nc.get((*schema.signature, 'SchemaIncomingConnection')):
                 for to_link in nc.outputs[item.name].links:
                 for to_link in nc.outputs[item.name].links:
                     if to_link.to_socket in to_name_filter:
                     if to_link.to_socket in to_name_filter:

+ 37 - 1
versioning.py

@@ -220,7 +220,6 @@ def cleanup_4_5_0_LTS_interface_workaround(*args, **kwargs):
             interface_item.description = ''
             interface_item.description = ''
     # that should be enough!
     # that should be enough!
 
 
-
 def up_0_12_25_replace_floor_offset_type(*args, **kwargs):
 def up_0_12_25_replace_floor_offset_type(*args, **kwargs):
     # add an inherit color input.
     # add an inherit color input.
     node = kwargs['node']
     node = kwargs['node']
@@ -244,6 +243,42 @@ def up_0_12_25_replace_floor_offset_type(*args, **kwargs):
         print(e)
         print(e)
 
 
 
 
+def schema_enable_custom_interface_types(*args, **kwargs):
+    # return
+    tree = kwargs['tree']
+    current_major_version = tree.mantis_version[0]
+    current_minor_version = tree.mantis_version[1]
+    current_sub_version = tree.mantis_version[2]
+    if  current_major_version > 0: return# major version must be 0
+    if current_minor_version >= 13: return# minor version must be 12 or less
+    # we need to set the new interface values on the schema interface stuff
+    prGreen(f"Updating Schema tree {tree.name} to support new, improved UI!")
+    try:
+        for item in tree.interface.items_tree:
+            if item.item_type == 'PANEL':
+                continue
+            parent_name = 'Constant'
+            if item.parent:
+                parent_name=item.parent.name
+            if hasattr(item, "is_array"):
+                # if is_array exists we're in the custom interface class
+                # so we'll assume the other attributes exist
+                if parent_name == 'Array':
+                    item.is_array = True
+                if parent_name == 'Connection':
+                    item.is_array = False
+                    item.is_connection=True
+                    item.connected_to=item.name
+                    # since heretofore it has been a requirement that the names match
+    except Exception as e:
+        prRed(f"Error updating version in tree: {tree.name}; see error:")
+        print(e)
+            
+
+
+
+
+
 
 
 versioning_tasks = [
 versioning_tasks = [
     # node bl_idname    task                required keyword arguments
     # node bl_idname    task                required keyword arguments
@@ -253,6 +288,7 @@ versioning_tasks = [
     (['MantisTree', 'SchemaTree'], cleanup_4_5_0_LTS_interface_workaround, ['tree']),
     (['MantisTree', 'SchemaTree'], cleanup_4_5_0_LTS_interface_workaround, ['tree']),
     (['InputWidget'], up_0_12_13_add_widget_scale, ['node']),
     (['InputWidget'], up_0_12_13_add_widget_scale, ['node']),
     (['LinkFloor'], up_0_12_25_replace_floor_offset_type, ['node']),
     (['LinkFloor'], up_0_12_25_replace_floor_offset_type, ['node']),
+    (['SchemaTree'], schema_enable_custom_interface_types, ['tree']),
 ]
 ]
 
 
 
 

+ 27 - 27
visualize.py

@@ -50,11 +50,6 @@ class MantisVisualizeNode(Node):
             case 'DUMMY_SCHEMA': self.color = (0.85 ,0.95, 0.9)
             case 'DUMMY_SCHEMA': self.color = (0.85 ,0.95, 0.9)
             case 'DUMMY':        self.color = (0.05 ,0.05, 0.15)
             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_debug_tag:
-        #     self.color = (0.02 ,0.02, 0.02)
 
 
         self.name = '.'.join(mantis_node.signature[1:]) # this gets trunc'd
         self.name = '.'.join(mantis_node.signature[1:]) # this gets trunc'd
         self.signature = '|'.join(mantis_node.signature[1:])
         self.signature = '|'.join(mantis_node.signature[1:])
@@ -70,7 +65,10 @@ class MantisVisualizeNode(Node):
                     case "DEBUG_CONNECTIONS":
                     case "DEBUG_CONNECTIONS":
                         if not inp.is_connected:
                         if not inp.is_connected:
                             continue
                             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:
                 try:
                     if sock := np.inputs.get(inp.name):
                     if sock := np.inputs.get(inp.name):
                         s.color = sock.color_simple
                         s.color = sock.color_simple
@@ -100,7 +98,10 @@ class MantisVisualizeNode(Node):
                     case "DEBUG_CONNECTIONS":
                     case "DEBUG_CONNECTIONS":
                         if not inp.is_connected:
                         if not inp.is_connected:
                             continue
                             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:
             for out in mantis_node.outputs:
                 match mode:
                 match mode:
                     case "DEBUG_CONNECTIONS":
                     case "DEBUG_CONNECTIONS":
@@ -111,12 +112,12 @@ class MantisVisualizeNode(Node):
 def gen_vis_node( mantis_node,
 def gen_vis_node( mantis_node,
                   vis_tree,
                   vis_tree,
                   links,
                   links,
-                  omit_simple=True,
+                  omit_simple=False,
                  ):
                  ):
     from .base_definitions import array_output_types
     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
     base_tree= mantis_node.base_tree
     vis = vis_tree.nodes.new('MantisVisualizeNode')
     vis = vis_tree.nodes.new('MantisVisualizeNode')
     vis.gen_data(mantis_node)
     vis.gen_data(mantis_node)
@@ -140,12 +141,11 @@ def visualize_tree(m_nodes, base_tree, context):
     import cProfile
     import cProfile
     import pstats, io
     import pstats, io
     from pstats import SortKey
     from pstats import SortKey
+    cull_no_links = False
     with cProfile.Profile() as pr:
     with cProfile.Profile() as pr:
         try:
         try:
             trace_from_roots = True
             trace_from_roots = True
-            all_links = set()
-            mantis_nodes=set()
-            nodes={}
+            all_links = set(); mantis_nodes=set(); nodes={}
             if trace_from_roots:
             if trace_from_roots:
                 roots=[]
                 roots=[]
                 for n in m_nodes.values():
                 for n in m_nodes.values():
@@ -156,6 +156,7 @@ def visualize_tree(m_nodes, base_tree, context):
                     print ("No nodes to visualize")
                     print ("No nodes to visualize")
                     return
                     return
             else:
             else:
+                mantis_keys  = list(base_tree.parsed_tree.keys())
                 mantis_nodes = list(base_tree.parsed_tree.values())
                 mantis_nodes = list(base_tree.parsed_tree.values())
 
 
             vis_tree = bpy.data.node_groups.new(base_tree.name+'_visualized', type='MantisVisualizeTree')
             vis_tree = bpy.data.node_groups.new(base_tree.name+'_visualized', type='MantisVisualizeTree')
@@ -166,11 +167,11 @@ def visualize_tree(m_nodes, base_tree, context):
                 # not in the parsed tree or available from trace_all_nodes_from_root.
                 # not in the parsed tree or available from trace_all_nodes_from_root.
 
 
             for l in all_links:
             for l in all_links:
-                if l.to_node.node_type in ['DUMMY_SCHEMA', 'DUMMY'] or \
-                l.from_node.node_type in ['DUMMY_SCHEMA', 'DUMMY']:
-                    pass
-                from_node=nodes.get(l.from_node.signature)
-                to_node=nodes.get(l.to_node.signature)
+                # if l.to_node.node_type in ['DUMMY_SCHEMA', 'DUMMY'] or \
+                # l.from_node.node_type in ['DUMMY_SCHEMA', 'DUMMY']:
+                #     pass
+                from_node = nodes.get(l.from_node.signature)
+                to_node   = nodes.get(l.to_node.signature)
                 from_socket, to_socket = None, None
                 from_socket, to_socket = None, None
                 if from_node and to_node:
                 if from_node and to_node:
                     from_socket = from_node.outputs.get(l.from_socket)
                     from_socket = from_node.outputs.get(l.from_socket)
@@ -196,14 +197,13 @@ def visualize_tree(m_nodes, base_tree, context):
                 return False
                 return False
 
 
             no_links=[]
             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)
+            if cull_no_links == True:
+                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)
 
 
         finally:
         finally:
             s = io.StringIO()
             s = io.StringIO()

+ 16 - 18
xForm_nodes.py

@@ -26,8 +26,7 @@ def reset_object_data(ob):
 def get_parent_node(node_container, type = 'XFORM'):
 def get_parent_node(node_container, type = 'XFORM'):
     # type variable for selecting whether to get either
     # type variable for selecting whether to get either
     #   the parent xForm  or the inheritance node
     #   the parent xForm  or the inheritance node
-    node_line, socket = trace_single_line(node_container, "Relationship")
-    parent_nc = None
+    node_line, _last_socket = trace_single_line(node_container, "Relationship")
     for i in range(len(node_line)):
     for i in range(len(node_line)):
         # check each of the possible parent types.
         # check each of the possible parent types.
         if ( (node_line[ i ].__class__.__name__ == 'LinkInherit') ):
         if ( (node_line[ i ].__class__.__name__ == 'LinkInherit') ):
@@ -267,23 +266,22 @@ class xFormBone(xFormNode):
         #should do the trick...
         #should do the trick...
 
 
     def bSetParent(self, eb):
     def bSetParent(self, eb):
-        # print (self.bObject)
         from bpy.types import EditBone
         from bpy.types import EditBone
         parent_nc = get_parent_node(self, type='LINK')
         parent_nc = get_parent_node(self, type='LINK')
-        # print (self, parent_nc.inputs['Parent'].from_node)
-        parent=None
-        if parent_nc.inputs['Parent'].links[0].from_node.node_type == 'XFORM':
-            parent = parent_nc.inputs['Parent'].links[0].from_node.bGetObject(mode = 'EDIT')
+        if parent_nc is None:
+            raise RuntimeError(wrapRed(f"Cannot set parent for node {self}"))
+        node_lines, _last_socket = trace_single_line(parent_nc, 'Parent')
+        for other_node in node_lines:
+            if isinstance(other_node, (xFormArmature, xFormBone)):
+                parent = other_node.bGetObject(mode = 'EDIT'); break
         else:
         else:
             raise RuntimeError(wrapRed(f"Cannot set parent for node {self}"))
             raise RuntimeError(wrapRed(f"Cannot set parent for node {self}"))
-
-        if isinstance(parent, EditBone):
+        if isinstance(parent, EditBone): # otherwise, no need to do anything.
             eb.parent = parent
             eb.parent = parent
+            eb.use_connect = parent_nc.evaluate_input("Connected")
+            eb.use_inherit_rotation = parent_nc.evaluate_input("Inherit Rotation")
+            eb.inherit_scale = parent_nc.evaluate_input("Inherit Scale")
 
 
-        eb.use_connect = parent_nc.evaluate_input("Connected")
-        eb.use_inherit_rotation = parent_nc.evaluate_input("Inherit Rotation")
-        eb.inherit_scale = parent_nc.evaluate_input("Inherit Scale")
-        # otherwise, no need to do anything.
 
 
     def bPrepare(self, bContext=None):
     def bPrepare(self, bContext=None):
         self.parameters['Matrix'] = get_matrix(self)
         self.parameters['Matrix'] = get_matrix(self)
@@ -435,9 +433,6 @@ class xFormBone(xFormNode):
             try:
             try:
                 if (custom_handle := self.evaluate_input("BBone Custom Start Handle")):
                 if (custom_handle := self.evaluate_input("BBone Custom Start Handle")):
                     b.bbone_custom_handle_start = self.bGetParentArmature().data.bones[custom_handle]
                     b.bbone_custom_handle_start = self.bGetParentArmature().data.bones[custom_handle]
-                # hypothetically we should support xForm inputs.... but we won't do that for now
-                # elif custom_handle is None:
-                #     b.bbone_custom_handle_start = self.inputs["BBone Custom Start Handle"].links[0].from_node.bGetObject().name
                 if (custom_handle := self.evaluate_input("BBone Custom End Handle")):
                 if (custom_handle := self.evaluate_input("BBone Custom End Handle")):
                     b.bbone_custom_handle_end = self.bGetParentArmature().data.bones[custom_handle]
                     b.bbone_custom_handle_end = self.bGetParentArmature().data.bones[custom_handle]
             except KeyError:
             except KeyError:
@@ -847,8 +842,11 @@ class xFormCurvePin(xFormNode):
         for socket_name in ["Curve Pin Factor", "Forward Axis","Up Axis",]:
         for socket_name in ["Curve Pin Factor", "Forward Axis","Up Axis",]:
             if self.inputs.get(socket_name) is None: continue # in case it has been bypassed
             if self.inputs.get(socket_name) is None: continue # in case it has been bypassed
             if self.inputs[socket_name].is_linked:
             if self.inputs[socket_name].is_linked:
-                link = self.inputs[socket_name].links[0]
-                driver = link.from_node
+                node_line, _last_socket = trace_single_line(self, socket_name)
+                driver = None
+                for other_node in node_line:
+                    if other_node.node_type == 'DRIVER':
+                        driver = other_node; break
                 if isinstance(driver, UtilityDriver):
                 if isinstance(driver, UtilityDriver):
                     prop_amount = driver.evaluate_input("Property")
                     prop_amount = driver.evaluate_input("Property")
                 elif isinstance(driver, UtilitySwitch):
                 elif isinstance(driver, UtilitySwitch):