1
0

11 Sitoutukset a0037c00ff ... 0d1adf3908

Tekijä SHA1 Viesti Päivämäärä
  Joseph Brandenburg 0d1adf3908 metarig cache scaffolding 3 päivää sitten
  Joseph Brandenburg ce0e972f0d reorganize, fix socket update 3 päivää sitten
  Joseph Brandenburg 6eca9a08aa Fix: 5.2 compatibility for Morph Deform 4 päivää sitten
  Joseph Brandenburg 2bacd23f5e fix: deformers stopped working in chains 4 päivää sitten
  Joseph Brandenburg c15145a4d0 fix data type retrieval for overlays 4 päivää sitten
  Joseph Brandenburg ec80582aa4 cleanup 4 päivää sitten
  Joseph Brandenburg df0b93d5ab fix overlay types display name when linked 4 päivää sitten
  Joseph Brandenburg cdca761478 error for schema length less than zero 4 päivää sitten
  Joseph Brandenburg 33e767dc0a fix: Interface init for group output 4 päivää sitten
  Joseph Brandenburg 4795a1cfa0 execute tree on interface update 4 päivää sitten
  Joseph Brandenburg 686e70834f refactor and optimize overlay 5 päivää sitten

+ 6 - 10
base_definitions.py

@@ -914,7 +914,7 @@ class MantisExecutionContext():
         self.base_tree = base_tree
         self.execution_id = base_tree.execution_id
         self.execution_failed=False
-        self.interface_nodes = {}
+        self.input_interface_nodes = {} # each one is a list, because of Schema 
         self.b_objects={} # objects created by Mantis during execution
         self.tagged_for_overlay = [] # nodes mutated by overlay system
         self.overlay_data = [] # dict of list of Overlay objects
@@ -924,7 +924,6 @@ from typing import Any
 # using these frozen sets accelerate lookup when checking for this
 FILL_PARAMETERS_SKIP_ROOTS = frozenset({ 'MANTIS_AUTOGENERATED', 'SCHEMA_AUTOGENERATED', })
 FILL_PARAMETERS_SKIP_NAMES = frozenset({ "NodeGroupOutput", "NodeGroupInput", })
-FILL_PARAMETERS_SKIP_SOCKETS = frozenset({ 'RelationshipSocket', 'xFormSocket', })
 from .socket_definitions import no_default_value
 
 class MantisNode:
@@ -970,7 +969,7 @@ class MantisNode:
         # For nodes generated in a schema, it's a list so that we can store the whole path
         # this is not strictly necessary for interface resolution but it is much easier
         # initialize it as all -1, we'll modify this if we need to, e.g. in the schema
-        self.generated_at_schema_index = [-1] * len(self.signature)
+        self.generated_at_schema_index = [0] * len(self.signature)
         # I know it isn't "beautiful OOP" or whatever, but it is just easier
         # code should be simple and do things in the simplest way.
         # in the future I can refactor it, but it will require changes to 100+
@@ -1208,17 +1207,14 @@ class MantisNode:
                 (self.signature[-1] in FILL_PARAMETERS_SKIP_NAMES) ): # I think this is harmless
                 return None
             raise RuntimeError(wrapRed("No UI Node found for... %s" % ( [self.base_tree] + list(self.signature[1:]) ) ) )
-        ui_inputs = ui_node.inputs; ui_outputs = ui_node.outputs
+        ui_inputs = ui_node.inputs
         for key in self.parameters.keys():
             if self.parameters[key] is not None: # the parameters are usually initialized as None.
                 continue # will be filled by the node itself
             if node_socket := ui_inputs.get(key):
-                if node_socket.bl_idname in FILL_PARAMETERS_SKIP_SOCKETS: continue
-                elif node_socket.is_linked and (not node_socket.is_output): continue
-                # we will get the value from the link, because this is a linked input port.
-                # very importantly, we do not pass linked outputs
-                # fill these because they are probably Input nodes.
                 if node_socket.bl_idname in no_default_value: continue
+                elif node_socket.is_linked: continue
+                # We do not need to consider outputs, simple input classes have their own fill method
                 if node_socket.bl_idname == 'MatrixSocket':
                     v = node_socket.default_value
                     self.parameters[key] = Matrix(( tuple(v[0:4]),
@@ -1235,7 +1231,7 @@ class MantisNode:
                         # but for now I am just gonna hamfist it like this lol
                     if isinstance(value, str) and "$" in value:
                         value = handle_overlay_detect(self,
-                            node_socket.identifier, node_socket.bl_idname, value)
+                            node_socket.name, node_socket.bl_idname, value)
                     self.parameters[key] = value
                 else:
                     raise RuntimeError(wrapRed("No value found for " + self.__repr__() + " when filling out node parameters for " + ui_node.name + "::"+node_socket.name))

+ 13 - 31
deformer_nodes.py

@@ -27,6 +27,7 @@ def TellClasses():
 # object instance probably can't use the deformer but it doesn't hurt to try.
 deformable_types= (xFormGeometryObject, InputExistingGeometryObject, xFormObjectInstance)
 
+# This is used by Armature Deformer for finding xForm to copy skin weights from
 def trace_xForm_back(mantis_node, socket):
     if (trace := trace_single_line(mantis_node, socket)[0] ) :
         for i in range(len(trace)): # have to look in reverse, actually
@@ -46,6 +47,10 @@ class MantisDeformerNode(MantisNode):
                          additional_parameters= {"Name":None})
         self.prepared = True
         self.bObject=[]
+        # almost all of the deformer nodes need this
+        if 'Deformer' in self.inputs.keys() and 'Deformer' in self.outputs.keys():
+            self.set_traverse( [('Deformer', 'Deformer')])
+
     # we need evaluate_input to have the same behaviour as links.
     def evaluate_input(self, input_name, index=0):
         if (input_name in ['Target', 'Object', 'Hook Target']):
@@ -476,14 +481,15 @@ class DeformerMorphTargetDeform(MantisDeformerNode):
         
         # at this point we make the node tree
         from .utilities.geometry_node_graphgen import gen_morph_target_nodes
-        d, props_sockets = gen_morph_target_nodes(
-                            self.evaluate_input("Name"),
-                            xf.bGetObject(),
-                            targets,
-                            context,
-                            use_offset=self.evaluate_input("Use Offset"))
+        d, props_sockets, b_obs = gen_morph_target_nodes(
+                                    self.evaluate_input("Name"),
+                                    xf.bGetObject(),
+                                    targets,
+                                    context,
+                                    use_offset=self.evaluate_input("Use Offset"))
+        b_obs['DEFAULT']=d
         self.bObject.append(d)
-        evaluate_sockets(self, {"DEFAULT":d}, props_sockets)
+        evaluate_sockets(self, b_obs, props_sockets)
         finish_drivers(self)
 
     def gen_shape_key_lattice(self, xf, context):
@@ -681,12 +687,6 @@ class DeformerSurfaceDeform(MantisDeformerNode):
     __slots__ = ()
     socket_templates = SurfaceDeformSockets
     passes=frozenset({'bRelationship', 'bFinalize', 'bModifierApply'})
-
-    def GetxForm(self, socket="Deformer"):
-        if socket == "Deformer":
-            return super().GetxForm()
-        else:
-            trace_xForm_back(self, socket)
     
     def bRelationshipPass(self, bContext = None,):
         self.executed = True
@@ -713,12 +713,6 @@ class DeformerMeshDeform(MantisDeformerNode):
     __slots__ = ()
     socket_templates = MeshDeformSockets
     passes=frozenset({'bRelationship', 'bFinalize', 'bModifierApply'})
-
-    def GetxForm(self, socket="Deformer"):
-        if socket == "Deformer":
-            return super().GetxForm()
-        else:
-            trace_xForm_back(self, socket)
     
     def bRelationshipPass(self, bContext = None,):
         self.executed = True
@@ -744,12 +738,6 @@ class DeformerLatticeDeform(MantisDeformerNode):
     '''A node representing a lattice deform modifier'''
     __slots__ = ()
     socket_templates = LatticeDeformSockets
-
-    def GetxForm(self, socket="Deformer"):
-        if socket == "Deformer":
-            return super().GetxForm()
-        else:
-            trace_xForm_back(self, socket)
     
     def bRelationshipPass(self, bContext = None,):
         self.executed = True
@@ -771,12 +759,6 @@ class DeformerSmoothCorrectiveDeform(MantisDeformerNode):
     '''A node representing a corrective smooth deform modifier'''
     __slots__ = ()
     socket_templates = SmoothDeformSockets
-
-    def GetxForm(self, socket="Deformer"):
-        if socket == "Deformer":
-            return super().GetxForm()
-        else:
-            trace_xForm_back(self, socket)
     
     def bRelationshipPass(self, bContext = None,):
         self.executed = True

+ 73 - 100
execution/overlays.py

@@ -4,7 +4,7 @@ from ..utilities.utilities import (prRed, prGreen, prPurple, prWhite,
                               wrapRed, wrapGreen, wrapPurple, wrapWhite,
                               wrapOrange,)
 
-from ..base_definitions import OverlayApplyError
+from ..base_definitions import OverlayApplyError, GraphError
 
 # For now, this is not yet implemented. lol
 def parser_handle_text(text):
@@ -53,80 +53,19 @@ def fill_root_tree_overlays(mContext, socket_idname, value):
                     value = socket_data_type(value.replace("$"+key, interface_item.default_string))
     return value
 
-STRING_PADDING = 4
-overlay_valid_socket_types = [
-    "FloatSocket", "FloatPositiveSocket", "FloatFactorSocket",
-    "FloatAngleSocket", "UnsignedIntSocket", "IntSocket", "StringSocket",
-]
-# Do not use these input names as overlay keys:
-forbidden_key_names = ["",]
-forbidden_in_key_name = ['$',]
-def node_scan_overlays(node, socket_id,):
-    from ..misc_nodes import SimpleInputNode
-    from ..internal_nodes import AutoGenNode
-    from ..utilities.utilities import get_ui_node
-    from ..utilities.node_common import GraphError
-    interface_nodes = node.mContext.interface_nodes
-    if not isinstance(node, AutoGenNode):
-        ui_node = get_ui_node(node.ui_signature, node.base_tree)
-        # we'll assume that the overlay tag system checked if this is linked.
-        # TODO this is probably redundant with the check above this
-        # First let's check if it is a simple input node, by checking outputs that are marked "input"
-        for socket in ui_node.outputs:
-            # weird edge case for simple input nodes with unnamed sockets
-            # the socket.identifier is not used by Mantis but it defaults to 'socket'\
-            if socket.identifier == socket_id and socket.input == True: break
-        else:
-            for socket in ui_node.inputs:
-                if socket.identifier == socket_id: break
-            else:
-                raise KeyError(f"Socket not found by ID {socket_id} in node {node.ui_signature}\n"
-                            f"Candidates: {[socket.identifier for socket in ui_node.inputs]}")
-        value = socket.default_value # get the ACTUAL UI value
-        if hasattr(socket, 'overlay_text'):
-            value = socket.overlay_text
-            if get_socket_idname_data_type(socket.bl_idname) != str:
-                value = parser_handle_text(value)
-    else: # since it doesn't have a UI node
-        value = node.parameters[socket_id]
-    
-    overlay_data_type = str
-    if isinstance(node, (AutoGenNode) ):
-        overlay_data_type = type(node.parameters[list(node.outputs.values())[0].name])
-    else: # we have the UI socket
-        overlay_data_type = get_socket_idname_data_type(socket.bl_idname)
-    
-    if not isinstance(value, str): return
-    # First step: get the interface nodes and the overlay keys
-    # this should only check interfaces that are ancestors of this node
+def get_interfaces_for_node(mantis_node,):
+    all_interface_nodes = mantis_node.mContext.input_interface_nodes
     my_interfaces = []
-    for index in range(len(node.signature)-1):
-        if index == 0: continue # root
-        # we add one here because the node inside the schema references the schema's interface...
-        current_schema_interface_index = node.generated_at_schema_index[index+1]
-        if current_schema_interface_index < 0: # this indicates that it is not in a schema
-            interface = interface_nodes.get(
-                interface_signature := (None, *node.signature[1:index+1], "InputInterface"))
-        else: # First we get the interface that enters the schema (index 0)
-            interface = interface_nodes.get(
-                interface_signature := (None, *node.signature[1:index+1],
-                "InputInterface."+str( 0 ).zfill(4)))
-            if interface and interface not in my_interfaces: my_interfaces.append(interface)
-            if current_schema_interface_index == 0: continue # just did it
-            # we need to get the schema of the current index
-            interface = interface_nodes.get(
-                interface_signature := (None, *node.signature[1:index+1],
-                "InputInterface."+str(current_schema_interface_index).zfill(4)))
-        if interface:
-            my_interfaces.append( interface )
-    # the my_interfaces list is sorted in REVERSE order of importance
-    # Next, get all the overlay providers.
-    name_to_id_map = {}
-    name_to_type_map = {} # Just for printing reasonable error messages
-    overlay_keys = set()
-    root_overlay_values = {}
-    # First, we'll get the base tree's default inputs
-    for interface_item in node.mContext.base_tree.interface.items_tree:
+    for index in range(len(mantis_node.signature)-1):
+        if index == 0: continue # the root isn't relevant unless I refactor to make an interface for it
+        current_schema_interface_index = mantis_node.generated_at_schema_index[index+1]
+        interfaces_from_schema = all_interface_nodes[ ( None, *mantis_node.signature[1:index+1] ) ]
+        # we need to get ONLY the interfaces up to the current index, which is 0 for non-schema
+        my_interfaces.extend( interfaces_from_schema[:current_schema_interface_index+1] ) # +1 (slice end is not inclusive)
+    return my_interfaces
+
+def get_base_tree_overlay_values(items_tree, name_to_type_map, overlay_keys, root_overlay_values):
+    for interface_item in items_tree:
         if interface_item.item_type != "SOCKET": continue
         if interface_item.in_out != "INPUT": continue
         base_value=None
@@ -138,24 +77,24 @@ def node_scan_overlays(node, socket_id,):
                 base_value = interface_item.default_float
             case "StringSocket" | "BoneCollectionSocket":
                 base_value = interface_item.default_string
-        if base_value:
+        if base_value is not None:
             root_overlay_values[interface_item.name] = base_value
         overlay_keys.add(interface_item.name)
-        name_to_id_map[interface_item.name] = None # don't worry about it
         name_to_type_map[interface_item.name] = interface_item.bl_socket_idname
-    # BUG? should this be my_interfaces ? why bother with interfaces this may not be a part of?
-    for interface_node in interface_nodes.values():
-        # problem: the interface node stores socket identifiers as keys
-        # so in reality we need to get the group UI node and ask for its input names
-        # if there is a duplicate name, too bad!
-        group_ui_node = get_ui_node(interface_node.ui_signature[:-1], node.base_tree)
-        for interface_item in group_ui_node.node_tree.interface.items_tree:
+
+def get_overlay_keys_types_ids(my_interfaces, overlay_keys, name_to_type_map, name_to_id_map):
+    # We need to get all the relevant keys to search for, all the types for error messages
+    # and all the IDs because interface nodes store stuff by ID, not by name
+    # and if there is a duplicate name, it will shadow - too bad!
+    for interface_node in my_interfaces:
+        # fill in the overlay keys and name-to-type-map (used for errors)
+        interface_items_tree = interface_node.get_tree_interface_items()
+        for interface_item in interface_items_tree:
             if interface_item.item_type != 'SOCKET': continue
             if interface_item.in_out != 'INPUT': continue
             if interface_item.name in forbidden_key_names: continue
             for substr in forbidden_in_key_name:
-                if substr in interface_item.name:
-                    break
+                if substr in interface_item.name: break
             else: # if we do not find a bad substr
                 name_to_id_map[interface_item.name]=interface_item.identifier
                 name_to_type_map[interface_item.name] = interface_item.bl_socket_idname
@@ -164,24 +103,62 @@ def node_scan_overlays(node, socket_id,):
         if 'Index' in interface_node.outputs:
             overlay_keys.add('Index'); overlay_keys.add('Length')
     if 'Index' in overlay_keys:
-        name_to_id_map['Index']='Index'
-        name_to_type_map['Index']= "IntSocket"
-        name_to_id_map['Length']='Length'
-        name_to_type_map['Length']= "IntSocket"
+        name_to_id_map['Index']='Index'; name_to_type_map['Index']= "IntSocket"
+        name_to_id_map['Length']='Length'; name_to_type_map['Length']= "IntSocket"
+
+STRING_PADDING = 4
+overlay_valid_socket_types = [
+    "FloatSocket", "FloatPositiveSocket", "FloatFactorSocket",
+    "FloatAngleSocket", "UnsignedIntSocket", "IntSocket", "StringSocket",
+]
+# Do not use these input names as overlay keys:
+forbidden_key_names = ["",]
+forbidden_in_key_name = ['$',]
+def node_scan_overlays(node, socket_id,):
+    from ..internal_nodes import AutoGenNode
+    overlay_data_type = str; socket = None
+    try:
+        socket = node.inputs[socket_id]
+    except KeyError:
+        socket = node.outputs[socket_id]
+    if socket_id == 'Schema Length':
+        # this happens in schema solve init, the actual value lives in an autogen node...
+        # it doesn't matter, we only need the overlay value and the intended type
+        value = node.evaluate_input('Schema Length')
+        overlay_data_type = int
+    else:
+        value = node.parameters[socket_id]
+    if overlay_data_type == str: # It is still default, not set to int by the above
+        if not socket or not socket.socket_type: # can't determine.
+            prRed(f"WARNING: cannot determine data type for overlay"
+                  f"variable {socket_id} in {node}" )
+            overlay_data_type = type(node.parameters[list(node.outputs.values())[0].name])
+        else: # we have the mantis socket which has a type field
+            overlay_data_type = get_socket_idname_data_type(socket.socket_type)
+    if not isinstance(value, str): return
+    # First step: get the interface nodes and the overlay keys
+    # this should only check interfaces that are ancestors of this node
+    my_interfaces = get_interfaces_for_node(mantis_node=node)
+    # the my_interfaces list is sorted in REVERSE order of importance
+    # Next, get all the overlay providers.
+    name_to_type_map, root_overlay_values, name_to_id_map = {}, {}, {}
+    overlay_keys = set()
+    # First, we'll get the base tree's default inputs
+    items_tree = node.mContext.base_tree.interface.items_tree
+    get_base_tree_overlay_values(items_tree, name_to_type_map, overlay_keys, root_overlay_values)
+    get_overlay_keys_types_ids(my_interfaces, overlay_keys, name_to_type_map, name_to_id_map)
     # sort by reverse length so we find "CatsAndFrogs" before "Cats"
     overlay_keys = sorted(overlay_keys, key= lambda a :-1*len(a))
     # First I need to break it into substrings based on quotation marks
     from ..utilities.utilities import split_quoted_substrings_to_strings
     quoted_substrings = split_quoted_substrings_to_strings(value)
     overlay_tries = []
-    for quoted_substring in  quoted_substrings:
+    for quoted_substring in quoted_substrings:
         overlay_tries.extend(quoted_substring.split("$"))
-    if overlay_data_type == str:
-        result = overlay_tries[0] # the 0th element is always before the split character
-    else: # TODO HACK later use the parser feature
-        result = 0
+    if overlay_data_type == str: result = overlay_tries[0] # the 0th element is always before the split character
+    else: result = 0# TODO HACK later use the parser feature
     # the reason for the difference between overlay_try and key is because
-    #  we don't actually know if the $text part has a key in it
+    #  we don't actually know if the $text part has a key in it yet!
     for overlay_try in overlay_tries[1:]:
         if not overlay_try: continue # some of these are empty
         overlay_resolved_value = "$" + overlay_try
@@ -214,7 +191,7 @@ def node_scan_overlays(node, socket_id,):
                             overlay_resolved_value = overlay_value
                         break
                 else: # we'll try the base tree
-                    if base_tree_result := root_overlay_values.get(key):
+                    if (base_tree_result := root_overlay_values.get(key)) is not None:
                         if overlay_data_type == str and not isinstance(base_tree_result, str):
                             base_tree_result = str(base_tree_result).zfill(STRING_PADDING)
                         overlay_resolved_value = base_tree_result
@@ -225,9 +202,5 @@ def node_scan_overlays(node, socket_id,):
         if type(overlay_resolved_value) != overlay_data_type:
             raise OverlayApplyError( f"Could not apply overlay variable to {node}::{socket_id}.")
         result += overlay_resolved_value
-    # Simple Input sockets with unnamed sockets assign this ID automatically
-    # But Mantis uses the name instead. Usually the name and ID are the same.
-    if socket_id == 'socket' and ui_node.outputs[0].name == "":
-        socket_id = ''
     node.parameters[socket_id] = result
     return result # because sometimes I need to get the result, too

+ 8 - 18
execution/readtree.py

@@ -46,7 +46,8 @@ def autogen_node(base_tree, ui_socket, signature, mContext):
     from ..internal_nodes import AutoGenNode
     mantis_node = AutoGenNode(signature, base_tree)
     mantis_node.mContext = mContext
-    mantis_node.outputs[ui_socket.identifier] = NodeSocket(is_input=False, name=ui_socket.identifier, node=mantis_node)
+    mantis_node.outputs[ui_socket.identifier] = NodeSocket(is_input=False,
+        name=ui_socket.identifier, node=mantis_node, socket_type=ui_socket.bl_idname)
     mantis_node.parameters[ui_socket.identifier] = None
     mantis_node.ui_signature = None # does not exist in the UI
     return mantis_node
@@ -62,7 +63,7 @@ def make_connections_to_ng_dummy(base_tree, tree_path_names, local_mantis_nodes,
         if not inp.is_linked: # make an autogenerated NC for the inputs of the group node
             # This can be run inside schema. Make it unique with uuid() to be safe.
             from uuid import uuid4
-            signature = (None, *tree_path_names, to_mantis_node.ui_signature[-1]+"_"+inp.name+"_"+inp.identifier+"_"+str(uuid4()) )
+            signature = ("MANTIS_AUTOGENERATED", *tree_path_names, to_mantis_node.ui_signature[-1]+"_"+inp.name+"_"+inp.identifier+"_"+str(uuid4()) )
             from_mantis_node = all_mantis_nodes.get(signature) # creating this without checking and
                                                                #  using UUID signature leads to TERRIBLE CONFUSING BUGS.
             if from_mantis_node is None: 
@@ -91,12 +92,9 @@ def gen_mantis_nodes(base_tree, current_tree, tree_path_names, mContext, all_man
                 if ui_node.bl_idname in {"NodeGroupOutput"}:
                     # We need an interface node, let's make it now.
                     items_tree = ui_node.id_data.interface.items_tree
-                    interface = GroupInterface(
-                        # we'll clip the 'NodeGroupOutput' from the signature
-                        ( *mantis_node.signature[:-1], "OutputInterface"),
-                        mantis_node.base_tree, 'OUTPUT', ui_node=mantis_node.ui_node,
-                        items_tree=items_tree,)
-                    interface.mContext = mContext
+                    interface = GroupInterface( # we'll clip the 'NodeGroupOutput' from the signature
+                        ( *mantis_node.signature[:-1], "OutputInterface"), mantis_node.base_tree,
+                        'OUTPUT', mContext=mContext, ui_node=mantis_node.ui_node, items_tree=items_tree, )
                     all_mantis_nodes[interface.signature] = interface; local_mantis_nodes[interface.signature] = interface
                     mantis_node.reroute_links_type = 'out'
         elif ui_node.bl_idname in  ["MantisNodeGroup", "MantisSchemaGroup"]:
@@ -114,9 +112,8 @@ def gen_mantis_nodes(base_tree, current_tree, tree_path_names, mContext, all_man
                 interface_in_out = 'SCHEMA_INPUT'
             # let's make the interface node for this now
             interface = GroupInterface(
-                ( *mantis_node.signature, name), mantis_node.base_tree,
-                   interface_in_out, mantis_node.ui_node, items_tree=items_tree )
-            interface.mContext = mContext
+                ( *mantis_node.signature, name), mantis_node.base_tree, interface_in_out,
+                   mContext, mantis_node.ui_node, items_tree=items_tree  )
             all_mantis_nodes[interface.signature] = interface; local_mantis_nodes[interface.signature] = interface
             group_nodes.append(mantis_node)
             # This function depends on generating the interface
@@ -374,8 +371,6 @@ def parse_tree(base_tree, error_popups=False):
         if mantis_node.__class__.__name__ in array_output_types:
             solve_only_these.append(mantis_node)
             array_nodes.append(mantis_node)
-        if mantis_node.node_type == 'INTERFACE': # strip the SCHEMA/AUTOGEN tag if it is there
-            mContext.interface_nodes[( None, *mantis_node.signature[1:]) ] = mantis_node
 
     from itertools import chain
     for schema in chain(all_schema.values(), array_nodes):
@@ -612,11 +607,6 @@ def execute_tree(nodes, base_tree, context, error_popups = False, profile=False)
         mantis_node.reset_execution()
     mContext.execution_failed = False
 
-    interface_nodes = {}
-    from ..internal_nodes import GroupInterface
-    for node in nodes.values():
-        if isinstance(node, GroupInterface):
-            interface_nodes[node.signature] = node
     overlay_tagged_nodes = {}
     for node_signature, socket_id in mContext.tagged_for_overlay:
         if overlay_tagged_nodes.get(node_signature):

+ 11 - 14
execution/schema_solve.py

@@ -63,6 +63,8 @@ class SchemaSolver:
         if isinstance(self.solve_length, str):
             self.solve_length = node_scan_overlays(self.node, 'Schema Length')
         if self.solve_length == 0: return
+        elif self.solve_length < 0:
+            raise GraphError(f"Schema Length of less than 0 is not allowed in {self.node}")
         # I'm making this a property of the solver because the solver's data is modified as it solves each iteration
         self.index = 0
         self.interfaces = []
@@ -172,20 +174,17 @@ class SchemaSolver:
             name = 'InputInterface'+index_str
             if i == 0: in_out = 'SCHEMA_INPUT'
             else:      in_out = 'SCHEMA_BETWEEN'
-            signature = (*self.autogen_path_names, name)
+            signature = (None, *self.autogen_path_names[1:], name)
             if not (interface := self.all_nodes.get( signature)):
                 interface = GroupInterface(signature=signature, base_tree=self.tree, in_out=in_out, 
-                            ui_node=None, items_tree=items_tree)
-                interface.mContext = mContext
-            # strip the AUTOGEN/SCHEMA tag if it is there
-            mContext.interface_nodes[( None, *interface.signature[1:]) ] = interface
+                            mContext=mContext, ui_node=self.node.ui_node, items_tree=items_tree)
             interface.generated_at_schema_index = self.node.generated_at_schema_index + [i]
-            # add the index and length socket constants
+            # add the index and length socket constants. Only the first one needs Length
+            #  (it's best to avoid having multiple places to read single-source values.)
             interface.parameters['Index'] = i
-            if i == 0: # TODO I am duplicating Length for the interface feature, this is not necessary
-                interface.parameters['Length'] = self.solve_length
+            # This has already been fully resolved, we do not need to relink anything.
+            if i == 0: interface.parameters['Schema Length'] = self.solve_length
             # the GroupInterface will initialize with the right sockets, but we still have to link them
-
             if in_out == 'SCHEMA_INPUT': # Take the links into the schema and plug them here
                 for item in items_tree:
                     if item.item_type == 'PANEL': continue
@@ -200,15 +199,13 @@ class SchemaSolver:
             self.solved_nodes[signature]=interface
 
         # now do the output
-        signature = (*self.autogen_path_names, "OutputInterface" + index_str)
-        interface = GroupInterface(signature=signature, base_tree=self.tree,
-                    in_out='OUTPUT', ui_node=None, items_tree=items_tree)
+        signature = (None, *self.autogen_path_names[1:], "OutputInterface" + index_str)
+        interface = GroupInterface(signature=signature, base_tree=self.tree, in_out='OUTPUT',
+                                   mContext=mContext, ui_node=self.node.ui_node, items_tree=items_tree)
         self.interfaces.append(interface)
         interface.generated_at_schema_index = i
         self.solved_nodes[signature]=interface
-        interface.mContext = mContext
         # strip the AUTOGEN/SCHEMA tag if it is there
-        mContext.interface_nodes[( None, *interface.signature[1:]) ] = interface
 
     def is_node_deeper_nested(self, queried_node, compare_node):
         return len(compare_node.signature) < len(queried_node.signature)

+ 66 - 6
internal_nodes.py

@@ -119,6 +119,10 @@ class NoOpNode(MantisNode):
         self.execution_prepared=True
     # this node is useful for me to insert in the tree and use for debugging especially connections.
 
+from .socket_definitions import no_default_value
+from .execution.overlays import handle_overlay_detect, get_socket_idname_data_type, parser_handle_text
+from mathutils import Matrix
+
 class AutoGenNode(MantisNode):
     __slots__ = ()
     node_type = 'UTILITY'
@@ -131,26 +135,60 @@ class AutoGenNode(MantisNode):
         super().reset_execution()
         self.prepared, self.executed = True, True
 
+    # This is a re-implementation of the fill parameters in the base class
+    # but it is possible to streamline a few things here
+    # so rather than add another branch in the main class I have
+    # decided to duplicate some code. This isn't great but it's a little faster.
+    def fill_parameters(self, ui_node=None)  -> None:
+        ui_inputs = ui_node.inputs
+        for key in self.parameters.keys():
+            if node_socket := ui_inputs.get(key):
+                if node_socket.bl_idname in no_default_value: continue
+                elif node_socket.is_linked: continue
+                # unlike the general case, we don't need to bother with outputs at all for autogen nodes
+                if node_socket.bl_idname == 'MatrixSocket':
+                    v = node_socket.default_value
+                    self.parameters[key] = Matrix(( tuple(v[0:4]),
+                                                    tuple(v[4:8]),
+                                                    tuple(v[8:12]),
+                                                    tuple(v[12:16]) ))
+                elif (value := node_socket.default_value) is not None:
+                    if hasattr(node_socket, 'use_overlay') and node_socket.use_overlay:
+                        value = node_socket.overlay_text
+                        if get_socket_idname_data_type(node_socket.bl_idname) != str:
+                            value = parser_handle_text(value)
+                    # THIS is the most important reason I am using a custom fill here:
+                    # The overlay for this kind of node must use the identifier, not the name
+                    if isinstance(value, str) and "$" in value:
+                        value = handle_overlay_detect(self,
+                            node_socket.identifier, node_socket.bl_idname, value)
+                    self.parameters[key] = value
+                else:
+                    raise RuntimeError(wrapRed("No value found for " + self.__repr__() + " when filling out node parameters for " + ui_node.name + "::"+node_socket.name))
+
+
+
+    
 
 # 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):
-    __slots__ = ("in_out")
+    __slots__ = ("in_out", "tree_name")
     node_type = 'INTERFACE'
-    def __init__(self, signature, base_tree, in_out, ui_node = None, items_tree = None):
-        # DO: refactor me to use the tree interface
+    def __init__(self, signature, base_tree, in_out, mContext, ui_node = None, items_tree = None, ):
         super().__init__(signature, base_tree)
         self.prepared, self.executed = True, True
         self.in_out = in_out
+        if hasattr(ui_node, "node_tree"):
+            self.tree_name = ui_node.node_tree.name
+        else: # It is an output node
+            self.tree_name = ui_node.id_data.name
         # init the sockets based on in/out, then set up traversal
         
         # the index and length are like constant inputs.
         if self.in_out == 'SCHEMA_INPUT':
-            # this one is here because the tree expects it when linking things together
             self.outputs["Schema Length"] = NodeSocket(is_input=False, node=self, name="Schema Length")
-            # HACK this one here is just there for overlays, really, doesn't hurt
-            self.outputs["Length"] = self.outputs["Schema Length"]
         if 'SCHEMA' in self.in_out: 
             self.outputs["Index"]  = NodeSocket(is_input=False, node=self, name="Index")
                
@@ -174,4 +212,26 @@ class GroupInterface(MantisNode):
                     source = self.inputs[item.identifier] = NodeSocket(is_input=True, node=self, name=item.identifier)
                     target = self.outputs[item.identifier] = NodeSocket(is_input=False, node=self, name=item.identifier)
                     self.set_traverse( [(source.name, target.name)] )
+        if self.in_out in {'INPUT', 'SCHEMA_INPUT', 'SCHEMA_BETWEEN'}:
+            # We need to store the input interface nodes for looking up overlay variables
+            # Important to set the mContext's interface stuff up so the rest of the tree has access to this
+            # Why not include the name of the node? It's easier to look up based on tree path
+            # And in order to prevent ambiguity, we make lists for each one so that schema interfaces come together
+            if mContext.input_interface_nodes.get(self.signature[:-1], None) is None:
+                mContext.input_interface_nodes[ self.signature[:-1] ] = [ self ]
+            else: # it's a schema so we add the next interface to the list
+                mContext.input_interface_nodes[ self.signature[:-1] ].append(self)
         self.execution_prepared=True
+
+    def get_tree_interface_items(self):
+        from bpy import data as bpy_data
+        if tree := bpy_data.node_groups.get(self.tree_name):
+            return tree.interface.items_tree
+
+    def evaluate_input(self, input_name, index=0):
+        # I'm hardcoding this so that the schema length overlay is available as just 'length'
+        if input_name == 'Length': input_name = 'Schema'
+        value = super().evaluate_input(input_name, index)
+        # This node should cache the result for faster lookup later
+        self.parameters[input_name] = value
+        return value

+ 28 - 6
mantis_dataclasses.py

@@ -64,12 +64,12 @@ class MantisBoneTransform():
 
 @dataclass(slots=True)
 class MantisBBoneTransform():
-    num_bbone_segmetns: int = field(default=1)
+    bbone_segments: int = field(default=1)
     # Offsets & Curve Handles
     bbone_curveinx: float = field(default=0.0)
-    bbone_curveiny: float = field(default=0.0)
+    bbone_curveinz: float = field(default=0.0)
     bbone_curveoutx: float = field(default=0.0)
-    bbone_curveouty: float = field(default=0.0)
+    bbone_curveoutz: float = field(default=0.0)
     # Easing
     bbone_easein: float = field(default=1.0)
     bbone_easeout: float = field(default=1.0)
@@ -82,13 +82,35 @@ class MantisBBoneTransform():
     bbone_scaleout: Tuple[float, float, float] = field(
         default_factory=lambda: (1.0, 1.0, 1.0) )
     # Custom Handles
-    use_bbone_custom_handles: bool = field(default=False)
-    bbone_custom_handle_start: str = field(default=None)
-    bbone_custom_handle_end: str = field(default=None)
+    bbone_handle_type_start: str = field(default="AUTO")
+    bbone_handle_type_end:   str = field(default="AUTO")
+    bbone_custom_handle_start: str = field(default="")
+    bbone_custom_handle_end:   str = field(default="")
+
+    @classmethod
+    def bbone_transform_from_bone(cls, bone):
+        return cls(
+            bbone_segments = bone.bbone_segments,
+            bbone_curveinx = bone.bbone_curveinx,
+            bbone_curveinz = bone.bbone_curveinz,
+            bbone_curveoutx = bone.bbone_curveoutx,
+            bbone_curveoutz = bone.bbone_curveoutz,
+            bbone_easein = bone.bbone_easein,
+            bbone_easeout = bone.bbone_easeout,
+            bbone_rollin = bone.bbone_rollin,
+            bbone_rollout = bone.bbone_rollout,
+            bbone_scalein = bone.bbone_scalein,
+            bbone_scaleout = bone.bbone_scaleout,
+            bbone_handle_type_start = bone.bbone_handle_type_start,
+            bbone_handle_type_end = bone.bbone_handle_type_end,
+            bbone_custom_handle_start = bone.bbone_custom_handle_start,
+            bbone_custom_handle_end = bone.bbone_custom_handle_end,
+        )
 
 # used to apply values or drivers to blender properties in a generic way
 @dataclass(slots=True)
 class MantisSocketFiller():
+
     socket_name: str = field(default="")
     property_group : str = field(default="")
     property_name : str = field(default="")

+ 45 - 1
misc_nodes.py

@@ -79,6 +79,7 @@ CURVE_LUT_CACHE = {}
 CURVE_HASHES = {}
 SPLINE_HASHES ={}
 
+
 # todo: make a "matrix from 2 u" function, a "matrix for segments" function
 # basically I want the SplineLUT to handle all curve matrix getters
 class SplineLUT:
@@ -233,6 +234,43 @@ def get_from_curve_lut(curve_name, spline_index, bContext, node_for_error=None)
     return spline_lookup
 
 
+# Store this stuff so we can reuse it or check it for updates
+METARIG_DATA_CACHE = {}
+
+# recursing like this is slightly easier than managing state myself
+# and it is easier to read/understand
+# it is unlikely that this will run into Recursion Depth Limit
+# if it does, I'll rewrite this without recursion
+def calculate_matrix_recursive_branching(parent_matrix, children, cache):
+    from collections import deque
+    from .mantis_dataclasses import MantisBoneTransform, MantisBBoneTransform
+    from mathutils import Matrix
+    children = deque( children )
+    for child in children:
+        m = parent_matrix @ child.matrix
+        transform_matrix = Matrix.Translation(child.head_local) @ m.to_4x4()
+        cache[child.name] = ( MantisBoneTransform(
+            matrix = transform_matrix, matrix_space = 'OBJECT', length=child.length),
+            MantisBBoneTransform.bbone_transform_from_bone(child)
+        )
+        calculate_matrix_recursive_branching(m, child.children, cache)
+
+def fill_metarig_cache_for_object(armature):
+    from .mantis_dataclasses import MantisBoneTransform, MantisBBoneTransform
+    from mathutils import Matrix
+    my_cache = {}
+    # recursively fill the armature with local matrices
+    roots = [ b for b in armature.data.bones if b.parent is None]
+    for b in roots:
+        parent_matrix = b.matrix
+        transform_matrix = Matrix.Translation(b.head_local) @ parent_matrix.to_4x4()
+        my_cache[b.name] = ( MantisBoneTransform(
+            matrix = transform_matrix, matrix_space = 'OBJECT', length=b.length),
+            MantisBBoneTransform.bbone_transform_from_bone(b)
+        )
+        calculate_matrix_recursive_branching(parent_matrix, b.children, my_cache)
+    METARIG_DATA_CACHE[armature.name] = my_cache
+
 def matrix_from_head_tail(head, tail, normal=None):
     from mathutils import Vector, Matrix
     if normal is None:
@@ -379,7 +417,7 @@ class SimpleInputNode(MantisMiscNode):
                 if get_socket_idname_data_type(node_socket.bl_idname) != str:
                     value = parser_handle_text(value)
             if isinstance(value, str) and "$" in value:
-                value = handle_overlay_detect(self, node_socket.identifier, node_socket.bl_idname, value)
+                value = handle_overlay_detect(self, node_socket.name, node_socket.bl_idname, value)
             self.parameters[node_socket.name] = value
         
 #*#-------------------------------#++#-------------------------------#*#
@@ -767,6 +805,12 @@ class UtilityMetaRig(MantisMiscNode):
     __slots__ = ()
     socket_templates = MetaRigSockets
 
+    def bDataFromScene(self):
+        # TWO possibilities:
+        #   - this node has no real dependencies and is ready to prep
+        #   - this node has dependencies and can't be prepped until building the tree
+        pass
+
     def bPrepare(self, bContext = None,):
         #kinda clumsy, whatever
         import bpy

+ 158 - 146
socket_definitions.py

@@ -81,15 +81,16 @@ def overlay_text_update(self, context):
         handled = try_update_mantis_node_for_socket(mantis_node, root_tree, self) and handled
     if not handled:
         root_tree.update_tree(context, force = True)
+        root_tree.execute_tree(context)
 
 class MantisSocketOverlayEnabled(MantisSocket):
     # this class just needs a couple properties
     use_overlay: bpy.props.BoolProperty( default=False, update=overlay_toggle_update)
     overlay_text: bpy.props.StringProperty( default="", update=overlay_text_update)
     def draw(self, context, layout, node, text):
-        is_simple_out = ((hasattr(self, "default_value")) and hasattr(self, "input") and getattr(self, "input"))
+        is_simple_out = ((hasattr(self, "default_value")) and (hasattr(self, "input") and getattr(self, "input")))
         if self.is_linked and not is_simple_out:
-            layout.label(text=getattr(self, "display_text", "") )
+            layout.label( text=text, )
         elif hasattr(self, "default_value"):
             row = layout.row(align=True)
             # We'll show the property or the overlay field here
@@ -384,10 +385,9 @@ enum_default_xForm_values =(
                      "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
+########################################################################
+#  Update Callbacks
+########################################################################
 
 def interface_socket_update(self, context):
     from .execution.overlays import fill_root_tree_overlays, parser_handle_text
@@ -417,8 +417,14 @@ def interface_socket_update(self, context):
                     for l in o.links:
                         # We're in the root tree, that makes this easier
                         if mantis_node := parsed_tree.get( (None, l.to_node.name) ):
-                            prPurple (mantis_node)
-                            mantis_node.reset_execution_recursive()
+                            #rebuild the tree, it's unlikely the root tree change will not require this
+                            root_tree.update_tree(force=True)
+                            try: # if only I had a goto in this confounded language
+                                prPurple(f"Executing tree after interface change")
+                                root_tree.execute_tree(context)
+                            except Exception as e:
+                                prRed("Automatic Tree Execution failed because of %s" % e)
+                            return
             else: # The overlays in the root tree get set directly, let's handle that here, also
                 for socket in chain(node.inputs, node.outputs):   
                     
@@ -430,6 +436,11 @@ def interface_socket_update(self, context):
                     if isinstance(value, str) and '$' in value:
                         if socket.name  == 'Schema Length': # this one has to force a new parse
                             root_tree.update_tree(force=True) # because it modifies tree topology
+                            try: # if only I had a goto in this confounded language
+                                prPurple(f"Executing tree after interface change")
+                                root_tree.execute_tree(context)
+                            except Exception as e:
+                                prRed("Automatic Tree Execution failed because of %s" % e)
                             return
                         value = fill_root_tree_overlays(mContext, socket.bl_idname, value)
 
@@ -450,15 +461,153 @@ def interface_socket_update(self, context):
                         for socket in ui_node.inputs:
                             if socket_id == socket.identifier: break
                         else: # Failed to update it, just update the tree.
-                            root_tree.update_tree(force=True); return
+                            break
                 # Now we have the ui socket
                 mantis_node.ui_modify_socket(ui_socket, ui_socket.name)
     else:
         return
     # if we break from the for loop above, we failed to update the socket and have to update the tree
     root_tree.update_tree(force=True)
+    try:
+        prPurple(f"Executing tree after interface change")
+        root_tree.execute_tree(context)
+    except Exception as e:
+        prRed("Automatic Tree Execution failed because of %s" % e)
     # TODO: I can update these values instead of resetting them
 
+def socket_update(mantis_node, ui_socket, socket_name=None):
+    node_updated = mantis_node.ui_modify_socket(ui_socket, socket_name)
+    if not node_updated: # so that we can tag its dependencies
+        mantis_node.reset_execution_recursive()
+    return node_updated
+
+def default_update(ui_socket, context, do_execute=True):
+    if not context:
+        context = bpy.context
+    root_tree = socket_update_get_root_tree(ui_socket, context)
+    if root_tree is None: return
+    # if it is a Schema Node, it will fail the checks below -- but we need it to update the tree.
+    from .base_definitions import SchemaUINode
+    if isinstance(ui_socket.node, SchemaUINode):
+        # This was causing infinite loops before checking for is_updating
+        # It seems to be happening when adding/removing links that are in schema trees
+        # even if they are not connected to the schema node
+        # I suspect that this completely disables this feature... whatever. TODO checc this
+        if ui_socket.node.is_updating: return
+        updating = ui_socket.node.is_updating
+        ui_socket.node.is_updating=True
+        try:
+            root_tree.update_tree(context, force = True)
+            prPurple(f"Executing tree after socket change: {ui_socket.node.name}:{ui_socket.name}")
+            root_tree.execute_tree(context)
+        finally:
+            ui_socket.node.is_updating=updating
+        return
+    if hasattr(ui_socket.node, "initialized"):
+        if not ui_socket.node.initialized: return
+    elif hasattr(ui_socket.node, 'is_updating'):
+        if ui_socket.node.is_updating: return
+    else: return
+    # if the socket has survived THAT ordeal, then the context is OK.
+    # first, we try to update the Mantis tree in-situ.
+    # Some nodes can update their b-objects, others will have to force-update the tree
+    # because we just modified the data without modifying the topology of the graph.
+    # finally, try and execute it if mantis couldn't update the b_objects itself.
+    from .base_definitions import array_output_types
+    mantis_updated=True
+    if (ui_socket.node.bl_idname in ["MantisNodeGroup", "MantisSchemaGroup"]):
+        mantis_updated=False # this kind of socket can't be updated here (yet)
+        root_tree.update_tree(context, force=True)
+    elif ui_socket.node.bl_idname in array_output_types:
+        mantis_updated=False
+        root_tree.update_tree(context, force=True)
+    elif hasattr(ui_socket, 'default_value'):
+        from .utilities.utilities import tree_from_mantis_node
+        # we may not have to regenerate the tree; try and update the socket
+        for mantis_node in root_tree.parsed_tree.values():
+            if mantis_node.ui_signature is None: continue
+            if mantis_node.ui_signature[-1] == ui_socket.node.name and \
+                        tree_from_mantis_node(mantis_node.ui_signature, root_tree) == ui_socket.node.id_data:
+                mantis_updated = try_update_mantis_node_for_socket(mantis_node, root_tree, ui_socket) and mantis_updated
+        root_tree.update_tree(context, force = (mantis_updated == False) )
+    root_tree.display_update(context)
+    if mantis_updated==False:
+        try:
+            prPurple(f"Executing tree after socket change: {ui_socket.node.name}:{ui_socket.name}")
+            root_tree.execute_tree(context)
+        except Exception as e:
+            prRed("Automatic Tree Execution failed because of %s" % e)
+
+def try_update_mantis_node_for_socket(mantis_node, root_tree, ui_socket):
+    # check to see if the mantis node is in the same ui-tree as this ui_socket
+    node_updated = True
+    from .misc_nodes import SimpleInputNode
+    if isinstance(mantis_node, SimpleInputNode):
+        node_updated = socket_update(mantis_node, ui_socket)
+        for l in mantis_node.outputs[ui_socket.name].links:
+            node_updated = node_updated and socket_update(l.to_node, ui_socket, l.to_socket)
+    else:
+        node_updated = socket_update(mantis_node, ui_socket)
+    # execute the tree if even one node didn't update
+    return node_updated
+
+
+def update_socket(self, context,):
+    default_update(self,context)
+
+def driver_variable_socket_update(self, context):
+    default_update(self,context)
+
+def driver_socket_update(self, context):
+    default_update(self,context)
+
+def update_mute_socket(self, context):
+    self.node.mute = not self.default_value
+    default_update(self,context)
+
+def update_hide_socket(self, context):
+    self.node.mute = self.default_value
+    default_update(self,context)
+
+def ik_chain_length_update_socket(self, context):
+    default_update(self,context)
+    # self.node.update_chain_length(context)
+
+def update_parent_node(self, context):
+    default_update(self,context)
+    if hasattr(self.node, "display_update"):
+        self.node.display_update(context)
+
+def update_metarig_armature(self, context,):
+    if self.search_prop:
+        self.node.armature = self.search_prop.name
+        self.node.inputs[1].armature = self.search_prop
+    default_update(self,context)
+
+def update_metarig_posebone(self, context,):
+    self.node.pose_bone = self.default_value
+    default_update(self,context)
+
+def update_socket_external_load(self, context):
+    # this is a socket update for sockets that load data from a pack
+    # e.g. widget, metarig, curve, or component selector sockets
+    # currently no plans to add any but widgets, but whatever
+    default_update(self, context)
+    self.previous_value = self.default_value # this is all I need to do lol
+    items = get_widget_library_items(self, context) # feels silly to do this here
+    for item in items:
+        if item[0] == self.default_value:
+            self.previous_index = item[-1]
+            break
+
+########################################################################
+#  Interface Types
+########################################################################
+
+# 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
 
 interface_default_value_description="The default value of the socket when it is not connected."
 class MantisInterfaceSocketBaseClass():
@@ -577,143 +726,6 @@ def generate_custom_interface_types():
 # I actually think I was wrong about all of that lol
 # TODO change it back, dingus
 
-########################################################################
-#  Update Callbacks
-########################################################################
-
-
-def socket_update(mantis_node, ui_socket, socket_name=None):
-    node_updated = mantis_node.ui_modify_socket(ui_socket, socket_name)
-    if not node_updated: # so that we can tag its dependencies
-        mantis_node.reset_execution_recursive()
-    return node_updated
-
-def default_update(ui_socket, context, do_execute=True):
-    if not context:
-        context = bpy.context
-    root_tree = socket_update_get_root_tree(ui_socket, context)
-    if root_tree is None: return
-    # if it is a Schema Node, it will fail the checks below -- but we need it to update the tree.
-    from .base_definitions import SchemaUINode
-    if isinstance(ui_socket.node, SchemaUINode):
-        # This was causing infinite loops before checking for is_updating
-        # It seems to be happening when adding/removing links that are in schema trees
-        # even if they are not connected to the schema node
-        # I suspect that this completely disables this feature... whatever. TODO checc this
-        if ui_socket.node.is_updating: return
-        updating = ui_socket.node.is_updating
-        ui_socket.node.is_updating=True
-        try:
-            root_tree.update_tree(context, force = True)
-            prPurple(f"Executing tree after socket change: {ui_socket.node.name}:{ui_socket.name}")
-            root_tree.execute_tree(context)
-        finally:
-            ui_socket.node.is_updating=updating
-        return
-    if hasattr(ui_socket.node, "initialized"):
-        if not ui_socket.node.initialized: return
-    elif hasattr(ui_socket.node, 'is_updating'):
-        if ui_socket.node.is_updating: return
-    else: return
-    # if the socket has survived THAT ordeal, then the context is OK.
-    # first, we try to update the Mantis tree in-situ.
-    # Some nodes can update their b-objects, others will have to force-update the tree
-    # because we just modified the data without modifying the topology of the graph.
-    # finally, try and execute it if mantis couldn't update the b_objects itself.
-    from .base_definitions import array_output_types
-    mantis_updated=True
-    if (ui_socket.node.bl_idname in ["MantisNodeGroup", "MantisSchemaGroup"]):
-        mantis_updated=False # this kind of socket can't be updated here (yet)
-        root_tree.update_tree(context, force=True)
-    elif ui_socket.node.bl_idname in array_output_types:
-        mantis_updated=False
-        root_tree.update_tree(context, force=True)
-    elif hasattr(ui_socket, 'default_value'):
-        # NOTE: there may be many mantis nodes for each UI node... so we need to try all of them.
-        for mantis_node in root_tree.parsed_tree.values():
-            # we may not have to regenerate the tree; try and update the socket
-            # the 'and' here ensures we trigger an update if even one of the mantis nodes fails
-            mantis_updated = try_update_mantis_node_for_socket(mantis_node, root_tree, ui_socket) and mantis_updated
-        # we want to force it if we have made an unhandled change inside of a schema.
-        root_tree.update_tree(context, force = (mantis_updated == False))
-    root_tree.display_update(context)
-    if mantis_updated==False:
-        try:
-            prPurple(f"Executing tree after socket change: {ui_socket.node.name}:{ui_socket.name}")
-            root_tree.execute_tree(context)
-        except Exception as e:
-            prRed("Automatic Tree Execution failed because of %s" % e)
-
-def try_update_mantis_node_for_socket(mantis_node, node_tree, ui_socket):
-    # we may not have to regenerate the tree; try and update the socket
-    from .utilities.utilities import tree_from_mantis_node
-    # check to see if the mantis node is in the same ui-tree as this ui_socket
-    if mantis_node.ui_signature is None: return False# autogenerated nodes
-    if mantis_node.ui_signature[-1] == ui_socket.node.name and \
-                tree_from_mantis_node(mantis_node.ui_signature, node_tree) == ui_socket.node.id_data:
-        node_updated = True
-        from .misc_nodes import SimpleInputNode
-        if isinstance(mantis_node, SimpleInputNode):
-            node_updated = socket_update(mantis_node, ui_socket)
-            for l in mantis_node.outputs[ui_socket.name].links:
-                node_updated = node_updated and socket_update(l.to_node, ui_socket, l.to_socket)
-        else:
-            node_updated = socket_update(mantis_node, ui_socket)
-        # execute the tree if even one node didn't update
-        return node_updated
-    return False
-
-def update_socket(self, context,):
-    default_update(self,context)
-
-def driver_variable_socket_update(self, context):
-    default_update(self,context)
-
-def driver_socket_update(self, context):
-    default_update(self,context)
-
-def update_mute_socket(self, context):
-    self.node.mute = not self.default_value
-    default_update(self,context)
-
-def update_hide_socket(self, context):
-    self.node.mute = self.default_value
-    default_update(self,context)
-
-def ik_chain_length_update_socket(self, context):
-    default_update(self,context)
-    # self.node.update_chain_length(context)
-
-def update_parent_node(self, context):
-    default_update(self,context)
-    if hasattr(self.node, "display_update"):
-        self.node.display_update(context)
-
-def update_metarig_armature(self, context,):
-    if self.search_prop:
-        self.node.armature = self.search_prop.name
-        self.node.inputs[1].armature = self.search_prop
-    default_update(self,context)
-
-def update_metarig_posebone(self, context,):
-    self.node.pose_bone = self.default_value
-    default_update(self,context)
-
-def update_socket_external_load(self, context):
-    # this is a socket update for sockets that load data from a pack
-    # e.g. widget, metarig, curve, or component selector sockets
-    # currently no plans to add any but widgets, but whatever
-    default_update(self, context)
-    self.previous_value = self.default_value # this is all I need to do lol
-    items = get_widget_library_items(self, context) # feels silly to do this here
-    for item in items:
-        if item[0] == self.default_value:
-            self.previous_index = item[-1]
-            break
-
-
-
-
 ########################################################################
 #  Sockets
 ########################################################################

+ 31 - 9
utilities/geometry_node_graphgen.py

@@ -5,6 +5,7 @@ from .utilities import (prRed, prGreen, prPurple, prWhite, prOrange,
 
 def gen_morph_target_nodes(mod_name, mod_ob, targets, context, use_offset=True):
     from bpy import data
+    from bpy.app import version as bpy_version
     modifier = mod_ob.modifiers.new(mod_name, type='NODES')
     mod_ob.add_rest_position_attribute = True
     ng = data.node_groups.new(mod_name, "GeometryNodeTree")
@@ -24,6 +25,7 @@ def gen_morph_target_nodes(mod_name, mod_ob, targets, context, use_offset=True):
     add_these = []
 
     props_sockets={}
+    props_sockets_b_objects={}
     object_map = {}
 
     for i, t in enumerate(targets):
@@ -78,17 +80,34 @@ def gen_morph_target_nodes(mod_name, mod_ob, targets, context, use_offset=True):
         else:
             ng.links.new(input=inp.outputs[mt_name+" Value"], output=scale1.inputs["Scale"])
         add_these.append(scale1)
-        object_map["Socket_"+str((i+1)*2)]=mt_node.GetxForm().bGetObject()
+        object_map["Socket_"+str((i+1)*2)] = mt_node.GetxForm().bGetObject()
         from ..mantis_dataclasses import MantisSocketFiller
-        socket_value = MantisSocketFiller(
-            property_name = (prop := "Socket_"+str((i+1)*2+1)),
-            socket_name = (socket_name := "Value."+str(i).zfill(3)),
-            default_value = 1.0
-        )
-        props_sockets[prop] = socket_value
+
+        prop = "Socket_"+str((i+1)*2+1)
+        if bpy_version < (5, 2, 0):
+            socket_value = MantisSocketFiller(
+                property_name = prop,
+                property_group = prop,
+                socket_name = (socket_name := "Value."+str(i).zfill(3)),
+                default_value = 1.0
+            )
+            props_sockets_b_objects[prop] = modifier
+            props_sockets[prop] = socket_value
+        else:
+            socket_value = MantisSocketFiller(
+                property_name = prop,
+                property_group = prop,
+                socket_name = (socket_name := "Value."+str(i).zfill(3)),
+                default_value = 1.0
+            )
+            props_sockets_b_objects[prop] = modifier
+            props_sockets[prop] = socket_value
+            
     
     set_position = ng.nodes.new("GeometryNodeSetPosition")
     bake = ng.nodes.new("GeometryNodeBake")
+    if bpy_version >= (5,0,0):
+        bake.bake_items.new("GEOMETRY", 'Geometry')
     ng.links.new(inp.outputs["Geometry"], output=set_position.inputs["Geometry"])
     ng.links.new(set_position.outputs["Geometry"], output=bake.inputs[0])
     ng.links.new(bake.outputs[0], output=out.inputs[0])
@@ -114,8 +133,11 @@ def gen_morph_target_nodes(mod_name, mod_ob, targets, context, use_offset=True):
         pass # this is unlikely to fail since I package the wheel but if it does it shouldn't crash out.
 
     for socket, ob in object_map.items():
-        modifier[socket]=ob
-    return modifier, props_sockets
+        if bpy_version < (5, 2, 0):
+            modifier[socket]=ob
+        else: # the syntax for this was changed in Blender 5.2.0
+            setattr( getattr(modifier.properties.inputs, socket), 'value',  ob)
+    return modifier, props_sockets, props_sockets_b_objects
 
 def gen_object_instance_node_group():
     from bpy import data

+ 19 - 3
utilities/node_common.py

@@ -384,7 +384,10 @@ def safe_setattr(ob, att_name, val):
     ob_class_name = ob.__class__.__name__
     match ob_class_name:
         case "NodesModifier":
-            ob[att_name]=val
+            if bpy_version < (5, 2, 0):
+                ob[att_name]=val
+            else:
+                setattr( getattr( ob.properties.inputs, att_name), 'value', val)
         case "Key":
             ob.key_blocks[att_name].value=val
         case _:
@@ -457,7 +460,17 @@ def evaluate_sockets(mantis_node, b_objects, props_sockets,):
                         # NOW we can set up the driver.
                         # TODO: Maybe can prefill this in the socket template or cache the result per-socket
                         driver_properties = {} # use this dict to pack this data without mutating the driver itself
-                        driver_properties["path_from_id"] = b_object.path_from_id(prop,)
+
+                        ob_class_name = b_object.__class__.__name__
+                        if ob_class_name == "NodesModifier":
+                            # this doesn't even support path_from_id because it is is a bpy_props property group
+                            # So we'll construct it directly
+                            if bpy_version < (5, 2, 0): # get the path as normal, but with ["brackets and quotes"]
+                                driver_properties["path_from_id"] = b_object.path_from_id(f"[\"{prop}\"]")
+                            else:
+                                driver_properties["path_from_id"] = b_object.path_from_id("properties") + f".inputs.{prop}.value"
+                        else:
+                            driver_properties["path_from_id"] = b_object.path_from_id(prop,)
                         driver_properties['owner'] = b_object # it is the CALLER's responsibility to send the right object!!
                         # we need a special case here for Bones :/
                         # TODO: refactor so I don't need this
@@ -493,7 +506,10 @@ def evaluate_sockets(mantis_node, b_objects, props_sockets,):
             if ob_class_name not in {'NodesModifier', 'Key'}:
                 setattr(b_object, prop, assign_this_value)
             elif ob_class_name == "NodesModifier":
-                b_object[prop] = assign_this_value
+                if bpy_version < (5, 2, 0):
+                    b_object[prop] = assign_this_value
+                else:
+                    setattr( getattr( b_object.properties.inputs, prop), 'value', assign_this_value)
             else: # ob_class_name == "Key":
                 b_object.key_blocks[prop].value = assign_this_value