浏览代码

refactor and optimize overlay

this commit introduces a more efficient and optimal lookup for interfaces
it makes the overlay socekt ID use the name or ID based on whether the mantis node
is using the name or ID in its parameters/sockets to streamline and remove
a lot of code for figuring out the socket
it decouples things more from the UI
it makes interface nodes generate with their context items
general refactor too
Joseph Brandenburg 5 天之前
父节点
当前提交
686e70834f
共有 6 个文件被更改,包括 151 次插入 和 143 次删除
  1. 6 10
      base_definitions.py
  2. 69 98
      execution/overlays.py
  3. 6 17
      execution/readtree.py
  4. 8 14
      execution/schema_solve.py
  5. 61 3
      internal_nodes.py
  6. 1 1
      misc_nodes.py

+ 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))

+ 69 - 98
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
@@ -141,21 +80,21 @@ def node_scan_overlays(node, socket_id,):
         if base_value:
             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,60 @@ 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
+    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 isinstance(node, (AutoGenNode) ):  # we need to check it for the general case
+            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
@@ -225,9 +200,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

+ 6 - 17
execution/readtree.py

@@ -62,7 +62,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 +91,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 +111,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 +370,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 +606,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):

+ 8 - 14
execution/schema_solve.py

@@ -172,20 +172,16 @@ 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
+            if i == 0: interface.parameters['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 +196,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)

+ 61 - 3
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,18 +135,52 @@ 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
+        self.tree_name = ui_node.node_tree.name
         # init the sockets based on in/out, then set up traversal
         
         # the index and length are like constant inputs.
@@ -174,4 +212,24 @@ 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):
+        value = super().evaluate_input(input_name, index)
+        # This node should cache the result for faster lookup later
+        self.parameters[input_name] = value
+        return value

+ 1 - 1
misc_nodes.py

@@ -379,7 +379,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
         
 #*#-------------------------------#++#-------------------------------#*#