Forráskód Böngészése

Overlay Variables for Schema

this is not thoroughly tested to work for every edge case yet
also I have not implemented connections in interface yet

anyhow the most important variables, Index and Length work
also, i removed the UUID from the schema index string
I don't think there is a possibility for a name clash
I thought about this and researched and tested it, should be a safe removal

left to do:
 - test!
 - connections for between- interfaces in schema
 - and of course I want to make a parser for number logic that will be good for schema
Joseph Brandenburg 1 hete
szülő
commit
6d31fdf63d
4 módosított fájl, 60 hozzáadás és 34 törlés
  1. 5 3
      base_definitions.py
  2. 25 7
      execution/readtree.py
  3. 20 8
      execution/schema_solve.py
  4. 10 16
      internal_nodes.py

+ 5 - 3
base_definitions.py

@@ -932,7 +932,7 @@ class MantisNode:
         "unmet_hierarchy_dependency_count",
         "prepared", "executed", "execution_prepared",
         "mContext", "is_schema_prototype",
-        "generated_at_schema_index", "schema_uuid"
+        "generated_at_schema_index",
         )
     node_type = 'UNINITIALIZED'
     socket_templates = None
@@ -957,8 +957,10 @@ class MantisNode:
         #  again without changing the readtree code much.
         self.mContext = None # for now I am gonna set this at runtime
         self.is_schema_prototype = False
-        # For nodes generated in a schema.
-        self.generated_at_schema_index = -1; self.schema_uuid = ""
+        # 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)
         # 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+

+ 25 - 7
execution/readtree.py

@@ -92,7 +92,7 @@ def gen_mantis_nodes(base_tree, current_tree, tree_path_names, mContext, all_man
             if not local_mantis_nodes.get(sig):
                 mantis_node = DummyNode( signature=sig , base_tree=base_tree, ui_node=ui_node, ui_signature=ui_sig )
                 local_mantis_nodes[sig] = mantis_node; all_mantis_nodes[sig] = mantis_node; dummy_nodes[sig] = mantis_node
-                if ui_node.bl_idname in ["NodeGroupOutput"]:
+                if ui_node.bl_idname in {"NodeGroupOutput"}:
                     mantis_node.reroute_links_type = 'out'
         elif ui_node.bl_idname in  ["MantisNodeGroup", "MantisSchemaGroup"]:
             mantis_node = DummyNode( signature= (sig := (None, *tree_path_names, ui_node.name) ), base_tree=base_tree, ui_node=ui_node )
@@ -102,7 +102,6 @@ def gen_mantis_nodes(base_tree, current_tree, tree_path_names, mContext, all_man
             if ui_node.bl_idname == "MantisNodeGroup":
                 mantis_node.reroute_links_type = 'group'
             else:
-                mantis_node.reroute_links_type = 'schema'
                 schema_nodes[sig] = mantis_node
         # if it wasn't the types we ignore or the types we make a Dummy for, use this to catch all non-special cases.
         elif (mantis_class := ui_node.mantis_class):
@@ -184,8 +183,16 @@ def node_scan_overlays(node, socket_id,):
     my_interfaces = []
     for index in range(len(node.signature)-1):
         if index == 0: continue # root
-        interface_signature = (*node.signature[:index+1], "InputInterface")
-        my_interfaces.append( interface_nodes[interface_signature] )
+        # 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 := (*node.signature[:index+1], "InputInterface"))
+        else:
+            interface = interface_nodes.get(
+                interface_signature := (*node.signature[:index+1],
+                "InputInterface."+str(current_schema_interface_index).zfill(4)))
+        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 = {}
@@ -207,6 +214,7 @@ def node_scan_overlays(node, socket_id,):
         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
@@ -223,6 +231,14 @@ def node_scan_overlays(node, socket_id,):
                 name_to_id_map[interface_item.name]=interface_item.identifier
                 name_to_type_map[interface_item.name] = interface_item.bl_socket_idname
                 overlay_keys.add(interface_item.name)
+        # For schema interfaces, we also need to get the length and index.
+        if 'Index' in interface.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"
     # 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
@@ -241,7 +257,11 @@ def node_scan_overlays(node, socket_id,):
             if key in overlay_try:
                 overlay_resolved_value = "$" + key
                 for interface in reversed(my_interfaces): # since this is reversed when we build it
-                    if interface.inputs.get(actual_key := name_to_id_map.get(key)):
+                    actual_key = name_to_id_map.get(key)
+                    socket = interface.inputs.get(actual_key)
+                    if key in ['Index', 'Length']:
+                        socket = interface.outputs.get(actual_key)
+                    if socket:
                         overlay_value = None
                         try:
                             overlay_value = interface.evaluate_input(actual_key)
@@ -251,10 +271,8 @@ def node_scan_overlays(node, socket_id,):
                                                  f"an invalid input in the node group: {interface.signature[:-1]}.")
                             raise GraphError( f"Error: Variable {key} is not supported because it is "
                                               f"type {socket_type}" )
-                            continue
                             # NOTE that the above can also be caused by any BUG occuring in node group update
                             # because it causes the interface to be invalid for the UI node that spawns the interface node.
-                        
                         if isinstance(overlay_value, (float, int)):
                             overlay_value = str(overlay_value).zfill(STRING_PADDING)
                         assert (isinstance(overlay_value, str)), f"Error: Variable {key} is not supported because it is type {type(overlay_value)}"

+ 20 - 8
execution/schema_solve.py

@@ -147,8 +147,11 @@ class SchemaSolver:
     # Using lambdas here causes a lot of function calls, should be optimized out
     # since after all this only needs to change when the index updates
     def set_schema_per_interation_constants(self):
-        self.index_str = '.'+str(self.uuid)+'.'+str(self.index).zfill(4)
-        self.prev_index_str = '.'+str(self.uuid)+'.'+str(self.index-1).zfill(4)
+        # NOTE: I removed the UUID that has always been here
+        #  I can't figure out why I needed it and as far as I can tell there
+        #  really is no way for this to have name clashes. Tests work OK, too.
+        self.index_str = '.'+str(self.index).zfill(4)
+        self.prev_index_str = '.'+str(self.index-1).zfill(4)
         self.current_interface = self.interfaces[self.index]
         self.next_interface = self.interfaces[self.index + 1] # this is guaranteed
         if self.is_node_group:
@@ -158,18 +161,22 @@ class SchemaSolver:
         from ..internal_nodes import GroupInterface
         # One input interface per iteration, and one output interface
         items_tree = self.tree.interface.items_tree
+        mContext=self.node.mContext
         for i in range(self.solve_length): # one input per
-            index_str = "."+str(i).zfill(4) # self.index_str
+            index_str = '.'+str(i).zfill(4)
             name = 'InputInterface'+index_str
             if i == 0: in_out = 'SCHEMA_INPUT'
             else:      in_out = 'SCHEMA_BETWEEN'
             signature = (*self.autogen_path_names, name)
             interface = GroupInterface(signature=signature, base_tree=self.tree, in_out=in_out, 
                         ui_node=None, items_tree=items_tree)
+            interface.mContext = mContext
+            mContext.interface_nodes[interface.signature] = interface
+            interface.generated_at_schema_index = self.node.generated_at_schema_index + [i]
             # add the index and length socket constants
             interface.parameters['Index'] = i
-            if i == 0:
-                interface.parameters['Length'] = self.solve_length
+            # if i == 0: # TODO I am duplicating Length for the interface feature, this is not necessary
+            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
@@ -186,11 +193,14 @@ class SchemaSolver:
             self.solved_nodes[signature]=interface
 
         # now do the output
-        signature = (*self.autogen_path_names, "OutputInterface")
+        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)
         self.interfaces.append(interface)
+        interface.generated_at_schema_index = i
         self.solved_nodes[signature]=interface
+        interface.mContext = mContext
+        mContext.interface_nodes[interface.signature] = interface
 
     def is_node_deeper_nested(self, queried_node, compare_node):
         return len(compare_node.signature) < len(queried_node.signature)
@@ -243,9 +253,11 @@ class SchemaSolver:
                 unprepared.append(mantis_node)
             if mantis_node.__class__.__name__ in custom_props_types:
                 setup_custom_props_from_np(mantis_node, ui_node)
-            mantis_node.fill_parameters(ui_node)
-            # be sure to pass on the Mantis Context to them
+            # be sure to pass on the Mantis Context to them.
             mantis_node.mContext=mContext
+            mantis_node.generated_at_schema_index = self.node.generated_at_schema_index + [self.index]
+            mantis_node.fill_parameters(ui_node)
+            assert mantis_node.mContext is not None
 
     def handle_link_from_index_input(self, index, frame_mantis_nodes, ui_link):
         from ..base_definitions import can_remove_socket_for_autogen

+ 10 - 16
internal_nodes.py

@@ -49,11 +49,7 @@ def grp_node_reroute_common(in_node, out_node, interface):
             out_link.die()
 
 def reroute_links_grp(group, all_nodes):
-    # TODO: find out why I need 2 paths here. shouldn't it always be a group?
-    if group.ui_node.bl_idname == 'MantisNodeGroup':
-        items_tree = group.ui_node.node_tree.interface.items_tree
-    else: # it's an input or output or something. Why?
-        items_tree = group.ui_node.id_data.interface.items_tree
+    items_tree = group.ui_node.node_tree.interface.items_tree
     interface = GroupInterface(
         ( *group.signature, "InputInterface"), group.base_tree,
             'INPUT', group.ui_node, items_tree=items_tree )
@@ -66,11 +62,7 @@ def reroute_links_grp(group, all_nodes):
 
 def reroute_links_grpout(group_output, all_nodes):
     if (group_output_node := all_nodes.get( ( *group_output.signature[:-1],) )):
-        # TODO: see above
-        if group_output_node.ui_node.bl_idname == 'MantisNodeGroup':
-            items_tree = group_output_node.ui_node.node_tree.interface.items_tree
-        else:
-            items_tree = group_output_node.ui_node.id_data.interface.items_tree
+        items_tree = group_output_node.ui_node.id_data.interface.items_tree
         interface = GroupInterface(
             ( *group_output_node.signature, "OutputInterface"),
             group_output_node.base_tree, 'OUTPUT', ui_node=group_output_node.ui_node,
@@ -94,7 +86,7 @@ class DummyNode(MantisNode):
         self.uuid = uuid4()
         self.solver = None
         self.node_type = 'DUMMY'
-        self.reroute_links_type='group' # group, out, schema
+        self.reroute_links_type=""
         if ui_node:
             if ui_node.bl_idname in ["MantisSchemaGroup"]:
                 self.node_type = 'DUMMY_SCHEMA'
@@ -120,8 +112,6 @@ class DummyNode(MantisNode):
         match self.reroute_links_type:
             case "group":
                 reroute_links_grp(self, all_nodes)
-            case "schema":
-                pass # gonna implement this later
             case "out":
                 reroute_links_grpout(self, all_nodes)
 
@@ -163,11 +153,15 @@ class GroupInterface(MantisNode):
         self.prepared, self.executed = True, True
         self.in_out = in_out
         # init the sockets based on in/out, then set up traversal
-    
-        if self.in_out == 'SCHEMA_INPUT': # the length is, in reality, a constant input
-            self.outputs["Length"] = NodeSocket(is_input=False, node=self, name="Length")
+        
+        # TODO: I'm adding length everywhere for now so overlays work
+        # need a more complex interface lookup for overlays to work w/out dupe length
+        # if self.in_out == 'SCHEMA_INPUT': # the length is, in reality, a constant input
+        #     self.outputs["Length"] = NodeSocket(is_input=False, node=self, name="Length")
+        # so the above should be here and below should omit length, end TODO
         if 'SCHEMA' in self.in_out: # the index like a constant in for each iteration
             self.outputs["Index"]  = NodeSocket(is_input=False, node=self, name="Index")
+            self.outputs["Length"] = NodeSocket(is_input=False, node=self, name="Length")
                
         for i, item in enumerate(items_tree):
             if item.item_type == 'PANEL': continue