ソースを参照

Interfaces for Schema

this commit doesn't finish implementing them, it just gets them working well
enough for me to use them for overlay variables
Joseph Brandenburg 1 週間 前
親
コミット
f3f14b9658
4 ファイル変更、135 行追加、51 行削除
  1. 4 2
      base_definitions.py
  2. 13 0
      execution/readtree.py
  3. 73 37
      execution/schema_solve.py
  4. 45 12
      internal_nodes.py

+ 4 - 2
base_definitions.py

@@ -931,8 +931,8 @@ class MantisNode:
         "hierarchy_dependencies", "dependencies",
         "unmet_hierarchy_dependency_count",
         "prepared", "executed", "execution_prepared",
-        "mContext",
-        "is_schema_prototype"
+        "mContext", "is_schema_prototype",
+        "generated_at_schema_index", "schema_uuid"
         )
     node_type = 'UNINITIALIZED'
     socket_templates = None
@@ -957,6 +957,8 @@ 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 = ""
         # 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+

+ 13 - 0
execution/readtree.py

@@ -503,6 +503,18 @@ def parse_tree(base_tree, error_popups=False):
 
     solve_only_these = set(solve_only_these)
 
+
+    overlay_tagged_nodes = {}
+    for node_signature, socket_id in mContext.tagged_for_overlay:
+        if overlay_tagged_nodes.get(node_signature):
+            overlay_tagged_nodes[node_signature].append(socket_id)
+        else:
+            overlay_tagged_nodes[node_signature] = [socket_id]
+    def check_and_apply_overlay(n):
+        if overlays_for_this_node := overlay_tagged_nodes.get(n.signature):
+            for socket_id in overlays_for_this_node:
+                node_scan_overlays(n, socket_id,)
+
     solve_layer = unsolved_schema.copy(); solve_layer.extend(roots)
     while(solve_layer):
         n = solve_layer.pop()
@@ -541,6 +553,7 @@ def parse_tree(base_tree, error_popups=False):
                 if dep not in schema_solve_done:
                     break
             else:
+                check_and_apply_overlay(n)
                 try:
                     n.bPrepare()
                 except Exception as e:

+ 73 - 37
execution/schema_solve.py

@@ -12,6 +12,20 @@ from bpy.types import NodeGroupInput, NodeGroupOutput, NodeSocket
 from .readtree import execution_error_cleanup
 
 class SchemaSolver:
+    __slots__ = (
+        "all_nodes", "node", "solved", "tree",
+        "uuid", "error_popups", "signature",
+        "schema_nodes", "solved_nodes",
+        "incoming_connections", "outgoing_connections",
+        "constant_in", "constant_out",
+        "array_input_connections", "array_output_connections",
+        "nested_schemas", "autogenerated_nodes",
+        "held_links", "tree_path_names",
+        "autogen_path_names", "is_node_group",
+        "index_link", "solve_length", "index",
+        "interfaces", "index_str", "prev_index_str",
+        "current_interface", "next_interface"
+    )
     def __init__(self, schema_dummy, nodes, ui_node, signature=None, error_popups=False):
         self.all_nodes = nodes # this is the parsed tree from Mantis
         self.node = schema_dummy
@@ -24,16 +38,11 @@ class SchemaSolver:
             self.signature = signature
         else:
             self.signature = self.node.signature
-        self.schema_nodes={}
-        self.solved_nodes = {}
-        self.incoming_connections = {}
-        self.outgoing_connections = {}
-        self.constant_in = {}
-        self.constant_out = {}
-        self.array_input_connections = {}
-        self.array_output_connections = {}
-        self.nested_schemas = {}
-        self.autogenerated_nodes = {}
+        self.schema_nodes={}; self.solved_nodes = {}
+        self.incoming_connections = {}; self.outgoing_connections = {}
+        self.constant_in = {}; self.constant_out = {}
+        self.array_input_connections = {}; self.array_output_connections = {}
+        self.nested_schemas = {}; self.autogenerated_nodes = {}
         self.held_links = []
         self.tree_path_names  = [*self.node.signature] # same tree as the schema node
         self.autogen_path_names = ['SCHEMA_AUTOGENERATED', *self.node.signature[1:]]
@@ -51,12 +60,14 @@ class SchemaSolver:
         self.solve_length = self.node.evaluate_input("Schema Length")
         # I'm making this a property of the solver because the solver's data is modified as it solves each iteration
         self.index = 0
+        self.interfaces = []
 
         prWhite(f"\nExpanding schema {self.tree.name} in node {self.node.signature}"
                  f" with length {self.solve_length}.")
 
         self.init_schema_links()
-        self.set_index_strings()
+        self.generate_interface_nodes()
+        self.set_schema_per_interation_constants()
 
         # Sort the multi-input nodes in reverse order of ID, this ensures that they are
         #   read in the order they were created
@@ -86,15 +97,6 @@ class SchemaSolver:
                 mantis_node = SchemaConstOutput(signature=signature, base_tree=self.node.base_tree, parent_schema_node=self.node)
                 self.schema_nodes[signature] = mantis_node
 
-    # 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_index_strings(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)
-        if self.is_node_group:
-            self.index_str = ''
-            self.prev_index_str = ''
-
     def init_schema_links(self,):
         """ Sort and store the links to/from the Schema group node."""
         for item in self.tree.interface.items_tree:
@@ -142,7 +144,53 @@ class SchemaSolver:
                         if out_links := self.node.outputs[item.identifier].links:
                             self.array_output_connections[item.identifier] = out_links.copy()
 
-
+    # 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)
+        self.current_interface = self.interfaces[self.index]
+        self.next_interface = self.interfaces[self.index + 1] # this is guaranteed
+        if self.is_node_group:
+            self.index_str = ''; self.prev_index_str = ''
+
+    def generate_interface_nodes(self):
+        from ..internal_nodes import GroupInterface
+        # One input interface per iteration, and one output interface
+        items_tree = self.tree.interface.items_tree
+        for i in range(self.solve_length): # one input per
+            index_str = "."+str(i).zfill(4) # self.index_str
+            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)
+            # add the index and length socket constants
+            interface.parameters['Index'] = i
+            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
+                    if (mantis_link := self.constant_in.get(item.name)):
+                        mantis_link.from_node.outputs[mantis_link.from_socket].connect(interface, item.identifier)
+                    # for mantis_link in self.array_input_connections.get(item.name, []):
+                    #     mantis_link.from_node.outputs[mantis_link.from_socket].connect(interface, item.identifier)
+                    if (mantis_link := self.incoming_connections.get(item.name)):
+                        mantis_link.from_node.outputs[mantis_link.from_socket].connect(interface, item.identifier)
+            self.interfaces.append(interface)
+            # solved nodes will get added to the outer all_nodes later
+            self.solved_nodes[signature]=interface
+
+        # now do the output
+        signature = (*self.autogen_path_names, "OutputInterface")
+        interface = GroupInterface(signature=signature, base_tree=self.tree,
+                    in_out='OUTPUT', ui_node=None, items_tree=items_tree)
+        self.interfaces.append(interface)
+        self.solved_nodes[signature]=interface
 
     def is_node_deeper_nested(self, queried_node, compare_node):
         return len(compare_node.signature) < len(queried_node.signature)
@@ -206,20 +254,9 @@ class SchemaSolver:
 
         if (not can_remove_socket_for_autogen(to_node, ui_link.to_socket.name)) or \
                                     to_node.node_type in ['DUMMY', 'DUMMY_SCHEMA']:
-            from .readtree import autogen_node
-            unique_name = "".join([
-                ui_link.to_socket.node.name+self.index_str,
-                ui_link.from_socket.name, ui_link.from_socket.identifier,
-                "==>", ui_link.to_socket.name, ui_link.to_socket.identifier,],)
-            signature = ("MANTIS_AUTOGENERATED", *self.tree_path_names[1:-1], unique_name)
-            from_node = self.all_nodes.get(signature)
-            if not from_node:
-                from_node = autogen_node(self.node.base_tree, ui_link.from_socket,
-                                signature=signature, mContext=self.node.mContext)
-                from_node.parameters = {ui_link.from_socket.name:index}
-                frame_mantis_nodes[signature]=from_node; self.solved_nodes[signature]=from_node
-                self.all_nodes[signature]=from_node
-            _connection = from_node.outputs[ui_link.from_socket.name].connect(node=to_node, socket=ui_link.to_socket.identifier)
+            from_node = self.current_interface
+            connection = from_node.outputs[ui_link.from_socket.name].connect(node=to_node, socket=ui_link.to_socket.identifier)
+            connection.is_hierarchy = False # For now this is the easiest way to do this
             return
         # Since the index is already determined, it is safe to remove the socket and just keep the value.
         to_node.parameters[ui_link.to_socket.name] = index
@@ -570,7 +607,7 @@ class SchemaSolver:
                                         SchemaIncomingConnection,)
         from ..utilities.utilities import clear_reroutes, link_mantis_nodes
         from ..base_definitions import array_output_types
-        self.set_index_strings()
+        self.set_schema_per_interation_constants()
         frame_mantis_nodes = {}
 
         # Later we have to run bPrepare() on these guys, so we make the deque and fill it now.
@@ -804,7 +841,6 @@ class SchemaSolver:
         for index in range(self.solve_length):
             self.index = index
             frame_mantis_nodes = self.solve_iteration()
-            self.set_index_strings()
             for sig, mantis_node in frame_mantis_nodes.items():
                 if mantis_node.node_type == 'DUMMY_SCHEMA':
                     self.nested_schemas[sig] = mantis_node

+ 45 - 12
internal_nodes.py

@@ -49,9 +49,14 @@ 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
     interface = GroupInterface(
-        ( *group.signature, "InputInterface"),
-        group.base_tree, group.ui_node, 'INPUT',)
+        ( *group.signature, "InputInterface"), group.base_tree,
+            'INPUT', group.ui_node, items_tree=items_tree )
     all_nodes[interface.signature] = interface
     if group.inputs:
         if group_input := all_nodes.get(( *group.signature, "NodeGroupInput")):
@@ -60,12 +65,18 @@ def reroute_links_grp(group, all_nodes):
             raise RuntimeError("internal error: failed to enter a node group ")
 
 def reroute_links_grpout(group_output, all_nodes):
-    if (group := all_nodes.get( ( *group_output.signature[:-1],) )):
+    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
         interface = GroupInterface(
-            ( *group.signature, "OutputInterface"),
-            group.base_tree, group.ui_node, 'OUTPUT',)
+            ( *group_output_node.signature, "OutputInterface"),
+            group_output_node.base_tree, 'OUTPUT', ui_node=group_output_node.ui_node,
+            items_tree=items_tree,)
         all_nodes[interface.signature] = interface
-        grp_node_reroute_common(group_output, group, interface)
+        grp_node_reroute_common(group_output, group_output_node, interface)
     else:
         prOrange(f"WARN: unconnected outputs from a node group "
                  "(maybe you are running the tree from inside a node group?)")
@@ -146,14 +157,36 @@ class AutoGenNode(MantisNode):
 class GroupInterface(MantisNode):
     __slots__ = ("in_out")
     node_type = 'INTERFACE'
-    def __init__(self, signature, base_tree, ui_node, in_out):
+    def __init__(self, signature, base_tree, in_out, ui_node = None, items_tree = None):
+        # DO: refactor me to use the tree interface
         super().__init__(signature, base_tree)
         self.prepared, self.executed = True, True
         self.in_out = in_out
         # init the sockets based on in/out, then set up traversal
-        collection = ui_node.inputs if in_out == 'INPUT' else ui_node.outputs
-        for socket in collection:
-            self.inputs[socket.identifier] = NodeSocket(is_input=True, node=self, name=socket.identifier)
-            self.outputs[socket.identifier] = NodeSocket(is_input=False, node=self, name=socket.identifier)
-        for socket in self.inputs.keys(): self.set_traverse( [(socket, socket)] )
+    
+        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")
+        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")
+               
+        for i, item in enumerate(items_tree):
+            if item.item_type == 'PANEL': continue
+            is_connection = False; is_array = False
+            if  hasattr(item, 'is_connection'): # in older files and for some types this happens
+                is_connection = item.is_connection # BUG really every socket should be a Mantis interface type
+                is_array = item.is_array
+            if (self.in_out in {'INPUT', 'OUTPUT'} and item.in_out == self.in_out):
+                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)] )
+            elif self.in_out == 'SCHEMA_INPUT':
+                if is_array: continue  # I'll skip these for now
+                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)] )
+            elif self.in_out == 'SCHEMA_BETWEEN':
+                if is_connection: # we need these at every in-between because they are modified between
+                    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)] )
         self.execution_prepared=True