Bläddra i källkod

Refactor Schema Solve

This commit splits schema solve into more managble functions, removes some
unnecessary variables, renames things to be more readable, and simplifies the
handling of node signatures (although it still isn't perfect).

Further work needs to be done in ensuring the graph is prepared for the schema
solve itself, which may need more than just its length, and should catch and raise
errors if it finds a cycle.

This patch doesn't resolve any bugs as far as I am aware -- it is primarily a refactor.
Joseph Brandenburg 7 månader sedan
förälder
incheckning
1ecaf00c1d
3 ändrade filer med 434 tillägg och 571 borttagningar
  1. 1 0
      internal_containers.py
  2. 8 14
      readtree.py
  3. 425 557
      schema_solve.py

+ 1 - 0
internal_containers.py

@@ -24,6 +24,7 @@ class DummyNode(MantisNode):
                 self.outputs[sock.identifier] = NodeSocket(is_input = False, name = sock.identifier, node = self)
                 self.parameters[sock.identifier]=None
         # keep track of the "natural signature" of Schema nodes - so that they are unambiguous
+        self.natural_signature=self.signature
         if natural_signature:
             self.natural_signature=natural_signature
         # This is necessary for Schema to work if there are multiple Schema nodes using the same Schema tree.

+ 8 - 14
readtree.py

@@ -214,18 +214,6 @@ def data_from_tree(base_tree, tree_path, dummy_nodes, all_nc, all_schema):
 from .utilities import check_and_add_root, init_connections, init_dependencies, init_schema_dependencies
 
 
-def delete_nc(nc):
-    return
-    # this doesn't seem to work actually
-    for socket in nc.inputs.values():
-        for l in socket.links:
-            if l is not None:
-                l.__del__()
-    for socket in nc.outputs.values():
-        for l in socket.links:
-            if l is not None:
-                l.__del__()
-
 def is_signature_in_other_signature(sig_a, sig_b):
     # this is the easiest but not the best way to do this:
     # this function is hideous but it does not seem to have any significant effect on timing
@@ -257,7 +245,7 @@ def solve_schema_to_tree(nc, all_nc, roots=[]):
         inp.links.sort(key=lambda a : -a.multi_input_sort_id)
 
     solver = SchemaSolver(nc, all_nc, np)
-    solved_nodes = solver.solve(length)
+    solved_nodes = solver.solve()
     # prGreen(f"Finished solving schema {tree.name} in node {nc}.")
     prWhite(f"Schema declared {len(solved_nodes)} nodes.")
     nc.prepared = True
@@ -268,7 +256,6 @@ def solve_schema_to_tree(nc, all_nc, roots=[]):
         # delete all the schema's internal nodes. The links have already been deleted by the solver.
         if v.signature[0] not in ['MANTIS_AUTOGENERATED'] and is_signature_in_other_signature(nc.signature, k):
             # print (wrapOrange("Culling: ")+wrapRed(v))
-            delete_nc(v)
             del_me.append(k)
     for k in del_me:
         del all_nc[k]
@@ -499,6 +486,13 @@ def execution_error_cleanup(node, exception, switch_objects = [] ):
 
 #execute tree is really slow overall, but still completes 1000s of nodes in only 
 def execute_tree(nodes, base_tree, context, error_popups = False):
+    # for node in nodes.values():
+    #     if node.signature == (None, 'IK/FK Switch Spine', 'Copy Location'):
+    #         print("beans")
+    #         prRed (len(node.outputs["Output Relationship"].links))
+    #         for l in node.outputs["Output Relationship"].links:
+    #             print (l)
+    #         raise NotImplementedError 
     # return
     import bpy
     from time import time

+ 425 - 557
schema_solve.py

@@ -4,220 +4,461 @@ from .utilities import (prRed, prGreen, prPurple, prWhite,
                               wrapOrange,)
 from .utilities import init_connections, init_dependencies
 from .utilities import class_for_mantis_prototype_node
-from .base_definitions import SchemaUINode, replace_types, custom_props_types
+from .base_definitions import SchemaUINode, GraphError, replace_types, custom_props_types
 from .node_container_common import setup_custom_props_from_np
 # a class that solves Schema nodes
-from uuid import uuid4
-
-# currently this is fairly ugly and a lot of cooupling but it is at least within the class
-
-# basically tho the idea is to make solving happen one iteration at time so that I can have nested structures
-# ultimately it will be much less messy, because I can simplify it
-# the initializer __init__ does all the necessary initialization
-# then solve_iteration should both solve the iteration and do all the cleanup
-# so the interface should be able to solve_iteration on this node until it is done
-
-# actually.. that wasn't necesary. Maybe the current solution is even... better?
-# I donno. Maybe doing one iteration at a time would be a better way to do things.
-# Now that it works, I can very easily write a second class and replace this one
-
-#example UUID and index
-#9e6df689-3d71-425e-be6c-fe768e7417ec.0000
-
-# def strip_uuid(signature):
-#     # prOrange("strip uuid",signature)
-#     ret_sig=[]
-#     for name in signature:
-#         if name is None:
-#             ret_sig.append(None)
-#             continue # this is normal, first element
-#         ret_sig.append(strip_uuid_string(name))
-#     return tuple(ret_sig)
-
-# def strip_uuid_string(string):
-#     import re
-#     split = re.split("\.[a-z,A-Z,0-9]{8}-[a-z,A-Z,0-9]{4}-[a-z,A-Z,0-9]{4}-[a-z,A-Z,0-9]{4}-[a-z,A-Z,0-9]{12}\.[0-9]{4}", string)
-#     prRed(string, split)
-#     return split[0]
-        
-# to get this working with groups.... ensure that group node sockets and such are all working (names, identifiers...)
-# current error seems to occur only when schema are nested
-# error is probably caused by the lack of auto-get nodes for Schema Group inputs that are not connected
-# but it may simply be caused by bad routing
-# both bugs should be fixed (if it is two bugs and not one)
+
+
+
 
 class SchemaSolver:
     def __init__(self, schema_dummy, nodes, prototype, signature=None):
         self.all_nodes = nodes # this is the parsed tree from Mantis
         self.node = schema_dummy
-        # ugly.. but we are getting the Schema node's prototype, then its node tree
-        from .utilities import get_node_prototype
-        self.tree = prototype.node_tree# get_node_prototype(self.node.signature, self.node.base_tree).node_tree
+        self.tree = prototype.node_tree
         self.uuid = self.node.uuid
+        if signature:
+            self.signature = signature
+        else:
+            self.signature = self.node.signature
         self.schema_nodes={}
         self.solved_nodes = {}
-        # self.out_nodes = {}
-
         self.incoming_connections = {}
         self.outgoing_connections = {}
         self.constant_in = {}
         self.constant_out = {}
         self.array_input_connections = {}
         self.array_output_connections = {}
-
-        # prGreen(self.node.signature[:-1])
-
-        # This singature/natural_signature thing is so, so bad and stupid and wrong... but it works so I won't change it
-        if signature:
-            self.natural_signature=signature
-            # print (signature)
-        else:
-            self.natural_signature=self.node.signature
-        
-        self.tree_path_names  = [*self.node.signature[:-1]] # same tree as the schema node
-        self.autogen_path_names = ['SCHEMA_AUTOGENERATED', *self.node.signature[1:-1]]
+        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:]]
         self.index_link = self.node.inputs['Schema Length'].links[0]
+        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
+        # So
+        self.index = 0
 
-        # TODO UNBREAK ME FIXME NOW
-        # identifiers are unfortunately not somethign I can set or predict in the items_tree
-        # but I can set them in the node.
-        # I either need to set them properly in the node so they always match
-        # or: I need to ignore the names and get the identifiers correctly
-        # self.node is a NC
-
-        # node_identifier_map_in = []
-        # node_identifier_map_out = []
+        self.init_schema_links()
+        self.set_index_strings()
 
+        for ui_node in self.tree.nodes:
+            if isinstance(ui_node, SchemaUINode):
+                # first we need to fill the parameters of the schema nodes.
+                # we use the bl_idname because all schema nodes should be single-instance 
+                signature = (*self.tree_path_names, ui_node.bl_idname)
+                # We use the solver's signature here because it represents the "original" signature of the schema UI group node
+                # since this schema solver may be in a nested schema, and its node's signature may have uuid/index attached.
+                get_sig = (*self.signature, ui_node.bl_idname) 
+                if not (mantis_node := self.all_nodes.get(get_sig)): raise RuntimeError(wrapRed(f"Not found: {get_sig}"))
+                self.schema_nodes[signature] = mantis_node
+                mantis_node.fill_parameters(ui_node)
+    
+    def set_index_strings(self):
+        self.index_str = lambda : '.'+str(self.uuid)+'.'+str(self.index).zfill(4)
+        self.prev_index_str = lambda : '.'+str(self.uuid)+'.'+str(self.index-1).zfill(4)
+    
+    def init_schema_links(self,):
+        """ Sort and store the links to/from the Schema group node."""
         for item in self.tree.interface.items_tree:
             if item.item_type == 'PANEL': continue
-            if item.parent and item.parent.name == 'Connection':
-                if item.in_out == 'INPUT':
-                    if incoming_links := self.node.inputs[item.identifier].links:
-                        self.incoming_connections[item.name] = incoming_links[0]
-                    else:
-                        self.incoming_connections[item.name] = None # it isn't linked
-                        # print (self.node)
-                        # prRed("candidates...", self.node.inputs)
-                        # for k,v in self.node.inputs.items():
-                        #     print (k,v)
-                        # print(self.node.outputs)
-                        # print (self.node.parameters)
-                        # raise RuntimeError(f"Cannot find incoming connection \"{item.identifier}\" .")
-                else:
-                    if outgoing_links := self.node.outputs[item.identifier].links:
-                        self.outgoing_connections[item.name] = outgoing_links.copy()
-                    else:
-                        self.outgoing_connections[item.name] = []
-            if item.parent and item.parent.name == 'Constant':
-                if item.in_out == 'INPUT':
-                    if constant_in_links := self.node.inputs[item.identifier].links:
-                        self.constant_in[item.name] = constant_in_links[0]
-                    else:
-                        self.constant_in[item.name] = None
+            if not item.parent:
+                raise GraphError("ERROR: Schema tree has inputs that are not in categories. This is not supported")
+            match item.parent.name:
+                case 'Connection':
+                    if item.in_out == 'INPUT':
+                        if incoming_links := self.node.inputs[item.identifier].links:
+                            self.incoming_connections[item.name] = incoming_links[0]
+                        else:
+                            self.incoming_connections[item.name] = None 
+                    else: # OUTPUT
+                        if outgoing_links := self.node.outputs[item.identifier].links:
+                            self.outgoing_connections[item.name] = outgoing_links.copy()
+                        else:
+                            self.outgoing_connections[item.name] = []
+                case 'Constant':
+                    if item.in_out == 'INPUT':
+                        if constant_in_links := self.node.inputs[item.identifier].links:
+                            self.constant_in[item.name] = constant_in_links[0]
+                        else:
+                            self.constant_in[item.name] = None
+                    else: # OUTPUT
+                        if constant_out_links := self.node.outputs[item.identifier].links:
+                            self.constant_out[item.name] = constant_out_links.copy()
+                        else:
+                            self.constant_out[item.name] = []
+                case 'Array':
+                    if item.in_out == 'INPUT':
+                        if item.identifier not in self.array_input_connections.keys():
+                            self.array_input_connections[item.identifier]=[]
+                        if in_links := self.node.inputs[item.identifier].links:
+                            self.array_input_connections[item.identifier]=in_links.copy()
+                    else: # OUTPUT
+                        if item.identifier not in self.array_output_connections.keys():
+                            self.array_output_connections[item.identifier]=[]
+                        if out_links := self.node.outputs[item.identifier].links:
+                            self.array_output_connections[item.identifier] = out_links.copy()
+    
+
+    
+
+    def gen_solve_iteration_mantis_nodes(self, frame_mantis_nodes, unprepared):
+        for prototype_ui_node in self.tree.nodes:
+            if isinstance(prototype_ui_node, SchemaUINode):
+                continue  # IGNORE the schema interface nodes, we already made them in __init__()
+                # they are reused for each iteration.
+            elif prototype_ui_node.bl_idname in ['NodeFrame', 'NodeReroute']:
+                continue  # IGNORE stuff that is purely UI - frames, reroutes.
+            signature = (*self.autogen_path_names, prototype_ui_node.name+self.index_str())
+            prototype_mantis_node = self.all_nodes[(*self.signature, prototype_ui_node.name)]
+            # the prototype_mantis_node was generated inside the schema when we parsed the tree.
+            # it is the prototype of the mantis node which we make for this iteration
+            # for Schema sub-nodes ... they need a prototype to init.
+            if prototype_mantis_node.node_type in ['DUMMY', 'DUMMY_SCHEMA']:
+                from .utilities import get_node_prototype
+                ui_node = get_node_prototype(prototype_mantis_node.signature, prototype_mantis_node.base_tree)
+                if prototype_mantis_node.node_type == 'DUMMY_SCHEMA':
+                    mantis_node = prototype_mantis_node.__class__(
+                        signature, prototype_mantis_node.base_tree, prototype=ui_node,
+                        natural_signature = (*self.node.signature, ui_node.name) )
                 else:
-                    if constant_out_links := self.node.outputs[item.identifier].links:
-                        self.constant_out[item.name] = constant_out_links.copy()
+                    mantis_node = prototype_mantis_node.__class__(signature, prototype_mantis_node.base_tree, prototype=ui_node)
+            else:
+                mantis_node = prototype_mantis_node.__class__(signature, prototype_mantis_node.base_tree)
+            frame_mantis_nodes[mantis_node.signature] = mantis_node
+            if mantis_node.prepared == False:
+                unprepared.append(mantis_node)
+            if mantis_node.__class__.__name__ in custom_props_types:
+                setup_custom_props_from_np(mantis_node, prototype_ui_node)
+            mantis_node.fill_parameters(prototype_ui_node)
+
+
+    def handle_link_from_index_input(self, index, frame_mantis_nodes, ui_link):
+        from .utilities import get_link_in_out
+        _from_name, to_name = get_link_in_out(ui_link)
+        to_node = frame_mantis_nodes[ (*self.autogen_path_names, to_name+self.index_str()) ]
+        if to_node.node_type in ['DUMMY', 'DUMMY_SCHEMA']:
+            from .utilities import gen_nc_input_for_data
+            nc_cls = gen_nc_input_for_data(ui_link.from_socket)
+            if (nc_cls): #HACK
+                sig = ("MANTIS_AUTOGENERATED", *self.tree_path_names[1:-1], self.index_str(), ui_link.from_socket.name, ui_link.from_socket.identifier)
+                nc_from = nc_cls(sig, self.node.base_tree)
+                # ugly! maybe even a HACK!
+                nc_from.inputs = {}
+                from .base_definitions import NodeSocket
+                nc_from.outputs = {ui_link.from_socket.name:NodeSocket(name = ui_link.from_socket.name, node=nc_from)}
+                from .base_definitions import get_socket_value
+                nc_from.parameters = {ui_link.from_socket.name:index}
+                frame_mantis_nodes[sig]=nc_from
+                from_node = nc_from
+                self.solved_nodes[sig]=from_node
+                _connection = from_node.outputs[ui_link.from_socket.name].connect(node=to_node, socket=ui_link.to_socket.identifier)
+            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
+        del to_node.inputs[ui_link.to_socket.name]
+    
+    def handle_link_from_schema_length_input(self, frame_mantis_nodes, ui_link):
+        from .utilities import get_link_in_out
+        # see, here I can just use the schema node
+        _from_name, to_name = get_link_in_out(ui_link)
+        to_node = frame_mantis_nodes[(*self.autogen_path_names, to_name+self.index_str())]
+        # this self.index_link is only used here?
+        if (self.index_link.from_node):
+            connection = self.index_link.from_node.outputs[self.index_link.from_socket].connect(node=to_node, socket=ui_link.to_socket.name)
+        # otherwise we can autogen an int input I guess...?
+        else:
+            raise RuntimeError("I was expecting there to be an incoming connection here for Schema Length")
+
+    def handle_link_from_incoming_connection_input(self, frame_mantis_nodes, ui_link):
+        from .utilities import get_link_in_out
+        incoming = self.incoming_connections[ui_link.from_socket.name]
+        from_node = incoming.from_node
+        _from_name, to_name = get_link_in_out(ui_link)
+        to_node = frame_mantis_nodes[ (*self.autogen_path_names, to_name+self.index_str()) ]
+        connection = from_node.outputs[incoming.from_socket].connect(node=to_node, socket=ui_link.to_socket.name)
+        init_connections(from_node)
+
+    def handle_link_from_constant_input(self, frame_mantis_nodes, ui_link, to_ui_node):
+        from .utilities import get_link_in_out
+        incoming = self.constant_in[ui_link.from_socket.name]
+        from_node = incoming.from_node
+        to_name = get_link_in_out(ui_link)[1]
+        to_node = frame_mantis_nodes[(*self.autogen_path_names, to_name+self.index_str())]
+        to_socket=ui_link.to_socket.name
+        from .base_definitions import SchemaGroup
+        if isinstance(to_ui_node, SchemaGroup):
+            to_socket=ui_link.to_socket.identifier
+        connection = from_node.outputs[incoming.from_socket].connect(node=to_node, socket=to_socket)
+        init_connections(from_node)
+    
+    def handle_link_to_array_input_get(self, frame_mantis_nodes, ui_link, index):
+        from .utilities import get_link_in_out
+        from_name, to_name = get_link_in_out(ui_link)
+        from_nc = frame_mantis_nodes[(*self.autogen_path_names, from_name+self.index_str())]
+        to_nc = self.schema_nodes[(*self.tree_path_names, to_name)]
+        # this only needs to be done once:
+        if index == 0: # BUG? HACK? TODO find out what is going on here.
+            # Kill the link between the schema node group and the node connecting to it
+            old_nc = self.all_nodes[(*self.tree_path_names, from_name)]
+            # I am not sure about this!
+            existing_link = old_nc.outputs[ui_link.from_socket.name].links[0]
+            existing_link.die()
+        #
+        connection = from_nc.outputs[ui_link.from_socket.name].connect(node=to_nc, socket=ui_link.to_socket.name)
+
+    def handle_link_from_array_input(self, frame_mantis_nodes, ui_link, index):
+        from .utilities import get_link_in_out
+        get_index = index
+        try:
+            incoming = self.array_input_connections[ui_link.from_socket.identifier][get_index]
+        except IndexError:
+            if len(self.array_input_connections[ui_link.from_socket.identifier]) > 0:
+                incoming = self.array_input_connections[ui_link.from_socket.identifier][0]
+                # prOrange(incoming.from_node.node_type)
+                if incoming.from_node.node_type not in ['DUMMY_SCHEMA']:
+                    raise NotImplementedError(wrapRed("dev: make it so Mantis checks if there are enough Array inputs."))
+                else: # do nothing
+                    return
+            else:
+                raise RuntimeError(wrapRed("make it so Mantis checks if there are enough Array inputs!"))
+        to_name = get_link_in_out(ui_link)[1]
+        to_node = frame_mantis_nodes[(*self.autogen_path_names, to_name+self.index_str())]
+        connection = incoming.from_node.outputs[incoming.from_socket].connect(node=to_node, socket=ui_link.to_socket.name)
+        init_connections(incoming.from_node)
+
+    def handle_link_to_constant_output(self, frame_mantis_nodes, index, ui_link,  to_ui_node):
+        from .utilities import get_link_in_out
+        to_node = self.schema_nodes[(*self.tree_path_names, to_ui_node.bl_idname)]
+        expose_when = to_node.evaluate_input('Expose when N==')
+        if index == expose_when:
+            for outgoing in self.constant_out[ui_link.to_socket.name]:
+                to_node = outgoing.to_node
+                from_name = get_link_in_out(ui_link)[0]
+                from_node = frame_mantis_nodes[(*self.autogen_path_names, from_name+self.index_str()) ]
+                connection = from_node.outputs[ui_link.from_socket.name].connect(node=to_node, socket=outgoing.to_socket)
+
+    # WTF is even happening here?? TODO BUG HACK
+    def handle_link_to_array_output(self, frame_mantis_nodes, index, ui_link, to_ui_node, from_ui_node):# if this duplicated code works, dedupe!
+        from .utilities import get_link_in_out
+        to_node = self.schema_nodes[(*self.tree_path_names, to_ui_node.bl_idname)] # get it by [], we want a KeyError if this fails
+        for outgoing in self.array_output_connections[ui_link.to_socket.identifier]:
+            # print (type(outgoing))
+            from .schema_containers import SchemaIndex
+            from_name = get_link_in_out(ui_link)[0]
+            from_node = frame_mantis_nodes[ (*self.autogen_path_names, from_name+self.index_str()) ]
+            if not from_node:
+                from_node = self.schema_nodes[(*self.tree_path_names, from_ui_node.bl_idname)]
+            to_node = outgoing.to_node
+
+            if isinstance(from_node, SchemaIndex): # I think I need to dedup this stuff
+                # print("INDEX")
+                from .utilities import gen_nc_input_for_data
+                nc_cls = gen_nc_input_for_data(ui_link.from_socket)
+                if (nc_cls): #HACK
+                    sig = ("MANTIS_AUTOGENERATED", *self.tree_path_names[1:-1], self.index_str(), ui_link.from_socket.name, ui_link.from_socket.identifier)
+                    nc_from = nc_cls(sig, self.node.base_tree)
+                    # ugly! maybe even a HACK!
+                    nc_from.inputs = {}
+                    from .node_container_common import NodeSocket
+                    nc_from.outputs = {ui_link.from_socket.name:NodeSocket(name = ui_link.from_socket.name, node=nc_from)}
+                    from .node_container_common import get_socket_value
+                    if ui_link.from_socket.name in ['Index']:
+                        nc_from.parameters = {ui_link.from_socket.name:index}
                     else:
-                        self.constant_out[item.name] = []
-            if item.parent and item.parent.name == 'Array':
-                if item.in_out == 'INPUT':
-                    if item.identifier not in self.array_input_connections.keys():
-                        self.array_input_connections[item.identifier]=[]
-                    if in_links := self.node.inputs[item.identifier].links:
-                        self.array_input_connections[item.identifier]=in_links.copy()
-                if item.in_out == 'OUTPUT':
-                    if item.identifier not in self.array_output_connections.keys():
-                        self.array_output_connections[item.identifier]=[]
-                    if out_links := self.node.outputs[item.identifier].links:
-                        self.array_output_connections[item.identifier] = out_links.copy()
+                        nc_from.parameters = {ui_link.from_socket.name:self.solve_length}
+                    frame_mantis_nodes[sig]=nc_from
+                    from_node = nc_from
+                    self.solved_nodes[sig]=from_node
+
+            # I have a feeling that something bad will happen if both of these conditions (above and below) are true
+            if to_node.node_type == 'DUMMY_SCHEMA' and to_node.prepared:
+                other_stem = ('SCHEMA_AUTOGENERATED', *to_node.signature[1:])
+                from .utilities import get_node_prototype
+                other_schema_np = get_node_prototype(to_node.signature, to_node.base_tree)
+                other_schema_tree = other_schema_np.node_tree
+                for n in other_schema_tree.nodes:
+                    if n.bl_idname not in ["SchemaArrayInput", "SchemaArrayInputGet"]:
+                        continue
+                    out = n.outputs[outgoing.to_socket]
+                    for l in out.links:
+                        other_index_str = lambda : '.'+str(to_node.uuid)+'.'+str(index).zfill(4)
+                        # get it by [], we want a KeyError if this fails
+                        try:
+                            out_node = self.all_nodes[(*other_stem, l.to_node.name+other_index_str())]
+                        except KeyError as e:
+                            for n in self.all_nodes:
+                                if len(n) > len(other_stem)+1: break
+                                for elem in other_stem:
+                                    if elem not in n: break
+                                else:
+                                    print(n)
+                            raise e
+                        connection = from_node.outputs[ui_link.from_socket.name].connect(node=out_node, socket=l.to_socket.name)
+            else:
+                connection = from_node.outputs[ui_link.from_socket.name].connect(node=to_node, socket=outgoing.to_socket)
+
+    def handle_link_from_array_input_get(self, frame_mantis_nodes, index, ui_link, from_ui_node ):
+        from .utilities import get_link_in_out
+        get_index = index
+        from_node = self.schema_nodes[(*self.tree_path_names, from_ui_node.bl_idname)]
+        from .utilities import cap, wrap
+        get_index = from_node.evaluate_input("Index", index)
+        oob = from_node.evaluate_input("OoB Behaviour")
+        # we must assume that the array has sent the correct number of links
+        if oob == 'WRAP':
+            get_index = wrap(get_index, len(self.array_input_connections[ui_link.from_socket.identifier])-1, 0)
+        if oob == 'HOLD':
+            get_index = cap(get_index, len(self.array_input_connections[ui_link.from_socket.identifier])-1)
+        try:
+            incoming = self.array_input_connections[ui_link.from_socket.identifier][get_index]
+        except IndexError:
+            raise RuntimeError(wrapRed("Dummy! You need to make it so Mantis checks if there are enough Array inputs! It should probably have a Get Index!"))
+        to_name = get_link_in_out(ui_link)[1]
+        to_node = frame_mantis_nodes[(*self.autogen_path_names, to_name+self.index_str())]
+        connection = incoming.from_node.outputs[incoming.from_socket].connect(node=to_node, socket=ui_link.to_socket.name)
+        init_connections(incoming.from_node)
+                    
 
-        self.held_links = []
 
-        # just define them for now... we redefine them properly later when they are needed. THis is messy.
-        self.index_str = lambda : '.'+str(self.uuid)+'.'+str(0).zfill(4)
-        self.prev_index_str = lambda : '.'+str(self.uuid)+'.'+str(0-1).zfill(4)
+    def solve_iteration(self):
+        """ Solve an iteration of the schema.
+             - 1 Create the Mantis Node instances that represent this iteration of the schema
+             - 2 Connect the links from the entrypoint or previous iteration.
+             - 3 Connect the constant and array links, and any link between nodes entirely within the tree
+             - 4 Prepare the nodes that modify data (in case of e.g. array get index or nested schema length input)
+             - 5 Connect the final prepared nodes
+             and return the nodes that were created in this schema iteration (frame).
+             This function also adds to held_links to pass data between iterations.
+        """
 
-        self.nested_schemas={}
+        from .schema_definitions import (SchemaIndex,
+                                        SchemaArrayInput,
+                                        SchemaArrayInputGet,
+                                        SchemaArrayOutput,
+                                        SchemaConstInput,
+                                        SchemaConstOutput,
+                                        SchemaOutgoingConnection,
+                                        SchemaIncomingConnection,)
+        
+        from .utilities import clear_reroutes
+        from .utilities import get_link_in_out, link_node_containers
 
-        self.autogenerated_nodes = {} # this is a bad ugly HACK, but I think I need to mark these and deal with them later
+        self.set_index_strings()
+        frame_mantis_nodes = {}
 
-        # Create the Schema Nodes
-        # prGreen(self.tree_path_names)
+        # Later we have to run bPrepare() on these guys, so we make the deque and fill it now.
+        from collections import deque
+        unprepared= deque()
+        self.gen_solve_iteration_mantis_nodes(frame_mantis_nodes, unprepared)
 
-        for n in self.tree.nodes:
-            if isinstance(n, SchemaUINode):
-                # first we need to fill the parameters of the schema nodes.
-                # the node is actually in the Schema group so we include the schema_dummy name
-                # and we use the bl_idname because I think all schema nodes should be single-instance
-                signature = (*self.tree_path_names, self.node.signature[-1], n.bl_idname)
-                # get_sig = [*self.tree_path_names, strip_uuid_string(self.node.signature[-1]), n.bl_idname]
-                # get_sig[0] = None; get_sig = tuple(get_sig) # this is so dumb haha
-                get_sig = (*self.natural_signature, n.bl_idname)
-                
-                if not (nc := self.all_nodes.get(get_sig)): raise RuntimeError(wrapRed(f"Not found: {get_sig}"))
-                self.schema_nodes[signature] = nc
-                # nc.signature = signature # I don't really intend for this value to be mutable... but... HACK
-                # there is no need to mutate this. also I may need to reuse it later
-                nc.fill_parameters(n)
-                # print (nc)
+        # This is where we handle node connections BETWEEN frames
+        while(self.held_links):
+            ui_link = self.held_links.pop()
+            to_ui_node = ui_link.to_socket.node; from_ui_node = ui_link.from_socket.node
+            if isinstance(to_ui_node, SchemaOutgoingConnection):
+                mantis_incoming_node = self.schema_nodes[*self.tree_path_names,  'SchemaIncomingConnection']
+                for mantis_link in mantis_incoming_node.outputs[ui_link.to_socket.name].links:
+                    to_mantis_node, to_mantis_socket = mantis_link.to_node, mantis_link.to_socket
+                    from_name = get_link_in_out(ui_link)[0]
+                    from_mantis_node = self.solved_nodes[ (*self.autogen_path_names, from_name+self.prev_index_str()) ]
+                    to_mantis_node = frame_mantis_nodes[ (*self.autogen_path_names, to_mantis_node.signature[-1]+self.index_str()) ]
+                    connection = from_mantis_node.outputs[ui_link.from_socket.name].connect(node=to_mantis_node, socket=to_mantis_socket)
+                    # We want to delete the links from the tree into the schema node.
+                    # TODO: this is not robust enough and I do not feel sure this is doing the right thing.
+                    if existing_link := self.incoming_connections[ui_link.to_socket.name]:
+                        if existing_link.to_node == self.node:
+                            print ("Deleting...", existing_link)
+                            if self.node.signature[-1] in existing_link.to_node.signature:
+                                existing_link.die()
+                    # BUG may exist here.
+                    self.incoming_connections[ui_link.to_socket.name] = connection
+
+        # Get the rerouted links from the graph. We don't really need to do this every iteration.
+        # TODO: use profiling to determine if this is slow; if so: copy & reuse the data, refactor the pop()'s out.
+        ui_links = clear_reroutes(list(self.tree.links))
+
+        # Now we handle ui_links in the current frame, including those ui_links between Schema nodes and "real" nodes
+        awaiting_prep_stage = []
+        for ui_link in ui_links:
+            to_ui_node = ui_link.to_socket.node; from_ui_node = ui_link.from_socket.node
+            if isinstance(from_ui_node, SchemaIndex):
+                if ui_link.from_socket.name == "Index":
+                    self.handle_link_from_index_input(self.index, frame_mantis_nodes, ui_link)
+                elif ui_link.from_socket.name == "Schema Length":
+                    self.handle_link_from_schema_length_input(frame_mantis_nodes, ui_link)
+                continue
+            if isinstance(from_ui_node, SchemaIncomingConnection):
+                if ui_link.from_socket.name in self.incoming_connections.keys():
+                    self.handle_link_from_incoming_connection_input(frame_mantis_nodes, ui_link)
+                continue
+            if isinstance(from_ui_node, SchemaConstInput):
+                if ui_link.from_socket.name in self.constant_in.keys():
+                    self.handle_link_from_constant_input( frame_mantis_nodes, ui_link, to_ui_node)
+                continue 
+            if isinstance(to_ui_node, SchemaArrayInputGet):
+                self.handle_link_to_array_input_get( frame_mantis_nodes, ui_link, self.index)
+                continue
+            if isinstance(from_ui_node, SchemaArrayInput):
+                self.handle_link_from_array_input(frame_mantis_nodes, ui_link, self.index)
+                continue
+            # HOLD these links to the next iteration:
+            if isinstance(to_ui_node, SchemaOutgoingConnection):
+                self.held_links.append(ui_link)
+                continue 
+            # HOLD these links until prep is done a little later
+            if isinstance(to_ui_node, SchemaConstOutput) or isinstance(to_ui_node, SchemaArrayOutput) or \
+                isinstance(from_ui_node, SchemaArrayInputGet):
+                awaiting_prep_stage.append(ui_link)
+                continue
+            # for any of the special cases, we hit a 'continue' block. So this connection is not special, and is made here.
+            connection = link_node_containers(self.autogen_path_names, ui_link, frame_mantis_nodes, from_suffix=self.index_str(), to_suffix=self.index_str())
+        for k,v in frame_mantis_nodes.items():
+            self.solved_nodes[k]=v
+            init_dependencies(v) # it is hard to overstate how important this single line of code is
         
-    
-    def solve(self, schema_length):
-        import time
-        start_time = time.time()
-
-        # from .schema_containers import SchemaIndex
-        # for nc in self.schema_nodes.values():
-        #     if isinstance(nc, SchemaIndex):
-        #         #HACK? I thought for sure this was being done elsewhere...
-        #         nc.parameters["Schema Length"]=schema_length
-        #         prRed(nc.parameters)
-        frame_nc={}
-
-        for index in range(schema_length):
-            frame_nc = self.solve_iteration(index, schema_length)
-            for sig, nc in frame_nc.items():
+        # This while-loop is a little scary, but in my testing it has never been a problem.
+        # At this point, we've already run a pretty exhaustive preperation phase to prep the schema's dependencies
+        # So at this point, if this while loop hangs it is because of an error elsewhere.
+        while unprepared:
+            nc = unprepared.pop()
+            if sum([dep.prepared for dep in nc.hierarchy_dependencies]) == len(nc.hierarchy_dependencies):
+                nc.bPrepare()
                 if nc.node_type == 'DUMMY_SCHEMA':
-                    self.nested_schemas[sig] = nc
+                    self.solve_nested_schema(nc)
+            else:
+                unprepared.appendleft(nc) # just rotate them until they are ready.
         
-        # prRed (self.array_output_connections)
-        # for k,v in self.array_output_connections.items():
-        #     prRed(k,v)
-
-        self.finalize(frame_nc)
-
-        # prRed (self.array_output_connections)
-        # for k,v in self.array_output_connections.items():
-        #     prRed(k,v)
-
-
-        return self.solved_nodes
-
+        while(awaiting_prep_stage):
+            ui_link = awaiting_prep_stage.pop()
+            to_ui_node = ui_link.to_socket.node; from_ui_node = ui_link.from_socket.node
+            if isinstance(to_ui_node, SchemaConstOutput):
+                self.handle_link_to_constant_output(frame_mantis_nodes, self.index, ui_link,  to_ui_node)
+            if isinstance(to_ui_node, SchemaArrayOutput):
+                self.handle_link_to_array_output(frame_mantis_nodes, self.index, ui_link, to_ui_node, from_ui_node)
+            if isinstance(from_ui_node, SchemaArrayInputGet):
+                self.handle_link_from_array_input_get(frame_mantis_nodes, self.index, ui_link, from_ui_node )
+            # end seciton
+        return frame_mantis_nodes
+    
     def solve_nested_schema(self, schema_nc):
+        """ Solves all schema node groups found in this Schema. This is a recursive function, which will
+            solve all levels of nested schema - since this function is called by solver.solve().
+        """
         if schema_nc.prepared == False:
             all_nodes = self.all_nodes.copy()
-            # for k,v in self.solved_nodes.items():
-            #     all_nodes[k]=v
-
-            # from .utilities import get_node_prototype
-
-            np = schema_nc.prototype
-            # for n in self.node.base_tree.nodes:
-            #     print (n.name)
-            # print (schema_nc.signature[-1])
-            from .schema_solve import SchemaSolver
+            ui_node = schema_nc.prototype
             length = schema_nc.evaluate_input("Schema Length")
-
-            tree = np.node_tree
+            tree = ui_node.node_tree
             prOrange(f"Expanding schema {tree.name} in node {schema_nc} with length {length}.")
-
-            solver = SchemaSolver(schema_nc, all_nodes, np, schema_nc.natural_signature)
-            solved_nodes = solver.solve(length)
+            solver = SchemaSolver(schema_nc, all_nodes, ui_node, schema_nc.natural_signature)
+            solved_nodes = solver.solve()
             schema_nc.prepared = True
-
             for k,v in solved_nodes.items():
                 self.solved_nodes[k]=v
 
+
     def finalize(self, frame_nc):
         from .schema_definitions import (SchemaOutgoingConnection,)
         for i in range(len(self.held_links)):
@@ -229,7 +470,7 @@ class SchemaSolver:
                     for outgoing in outgoing_links:
                         if outgoing:
                             to_node = outgoing.to_node
-                            from_node =frame_nc.get( (*self.autogen_path_names, from_np.name+self.index_str()) )
+                            from_node =frame_nc[(*self.autogen_path_names, from_np.name+self.index_str()) ]
                             connection = from_node.outputs[link.from_socket.name].connect(node=to_node, socket=outgoing.to_socket)
                             # we need to kill the link between the Schema itself and the next node and update the deps. Otherwise:confusing bugs.
                             outgoing.die(); init_dependencies(to_node)
@@ -260,8 +501,6 @@ class SchemaSolver:
         for outgoing in all_outgoing_links:
             to_node = outgoing.to_node
             for l in to_node.inputs[outgoing.to_socket].links:
-                other = l.to_node
-                other_input = l.to_socket
                 if self.node == l.from_node:
                     l.die()
         
@@ -270,385 +509,14 @@ class SchemaSolver:
                 init_connections(l.from_node) # to force it to have hierarchy connections with the new nodes.
                     
         
-
-    def solve_iteration(self, index, schema_length):
-
-        from .schema_definitions import (SchemaIndex,
-                                        SchemaArrayInput,
-                                        SchemaArrayInputGet,
-                                        SchemaArrayOutput,
-                                        SchemaConstInput,
-                                        SchemaConstOutput,
-                                        SchemaOutgoingConnection,
-                                        SchemaIncomingConnection,)
-        from bpy.types import (NodeFrame)
-        
-        from .utilities import clear_reroutes
-        from .utilities import get_link_in_out, link_node_containers
-
-        # if index_nc:
-        #     index_nc.parameters['Index']=index
-
-        self.index_str = lambda : '.'+str(self.uuid)+'.'+str(index).zfill(4)
-        # index_str = str(index).zfill(4)
-        self.prev_index_str = lambda : '.'+str(self.uuid)+'.'+str(index-1).zfill(4)
-        frame_nc = {}
-        # At this point, GENERATE all the nodes for the frame
-        for n in self.tree.nodes:
-            if isinstance(n, SchemaUINode) or isinstance(n, NodeFrame):
-                continue
-            if n.bl_idname in ['NodeReroute']:
-                continue
-            # this is the N.C. which is a prototype of the NC we actually want to make...
-            signature = (*self.autogen_path_names, n.name+self.index_str())
-
-            # proto_nc = self.all_nodes.get((*self.tree_path_names, self.node.signature[-1], n.name))
-            proto_nc = self.all_nodes.get((*self.natural_signature, n.name))
-            # this proto_nc was generated inside the schema when we parsed the tree.
-            if not proto_nc:
-                raise RuntimeError(f"Node not found: {(*self.tree_path_names, self.node.signature[-1], n.name)}")
-            # for Schema sub-nodes ... they need a prototype to init.
-            if proto_nc.node_type in ['DUMMY', 'DUMMY_SCHEMA']:
-                from .utilities import get_node_prototype
-                np = get_node_prototype(proto_nc.signature, proto_nc.base_tree)
-                # assert np is not None
-                if proto_nc.node_type == 'DUMMY_SCHEMA':
-                    nat_sig = (*self.node.signature, np.name)
-                    nc = proto_nc.__class__(signature, proto_nc.base_tree, prototype=np, natural_signature=nat_sig)
-                else:
-                    nc = proto_nc.__class__(signature, proto_nc.base_tree, prototype=np)
-            else:
-            # try:
-                nc = proto_nc.__class__(signature, proto_nc.base_tree)
-            # except AttributeError:
-            #     from .utilities import get_node_prototype
-            #     np = get_node_prototype(proto_nc.signature, proto_nc.base_tree)
-            #     nc = proto_nc.__class__(signature, proto_nc.base_tree, prototype=np)
-
-            frame_nc[nc.signature] = nc
-            #
-            if nc.__class__.__name__ in custom_props_types:
-                setup_custom_props_from_np(nc, n)
-            nc.fill_parameters(n) # this is the best place to do this..
-
-
-        # This is where we handle node connections BETWEEN frames
-        for i in range(len(self.held_links)):
-            link = self.held_links.pop()
-            to_np = link.to_socket.node; from_np = link.from_socket.node
-            if isinstance(to_np, SchemaOutgoingConnection):
-                # incoming connection tells us where to take this.
-                incoming_node = self.schema_nodes[*self.tree_path_names, self.node.signature[-1], 'SchemaIncomingConnection']
-                for l in incoming_node.outputs[link.to_socket.name].links:
-                    to_node, to_socket = l.to_node, l.to_socket
-                    from_name = get_link_in_out(link)[0]
-                    from_node = self.solved_nodes.get( (*self.autogen_path_names, from_name+self.prev_index_str()) )
-                    #
-                    to_node = frame_nc.get( (*self.autogen_path_names, to_node.signature[-1]+self.index_str()) )
-                    # the to_node and to_socket don't really matter, the incoming connection will handle this part
-                    
-                    connection = from_node.outputs[link.from_socket.name].connect(node=to_node, socket=to_socket)
-                    if existing_link := self.incoming_connections[link.to_socket.name]:
-                        # if index == 0:
-                        if self.node.signature[-1] in existing_link.to_node.signature:
-                        # not sure this is helping
-                            existing_link.die()
-                        # could be better to make it a dummy link?
-                    self.incoming_connections[link.to_socket.name] = connection
-
-        links = clear_reroutes(list(self.tree.links))
-
-
-        # frame_nc_with_schema = frame_nc.copy()
-        # for k,v in self.schema_nodes.items(): frame_nc_with_schema[k]=v
-
-        # Now we handle links in the current frame, including those links between Schema nodes and "real" nodes
-        # this gets really complicated :\
-        awaiting_prep_stage = []
-        for link in links:
-        # at THIS POINT I should make a buncha dummy links to deal
-        #   with the schema node connections...
-            to_np = link.to_socket.node; from_np = link.from_socket.node
-            if isinstance(to_np, SchemaConstOutput) or isinstance(to_np, SchemaArrayOutput) or \
-                isinstance(from_np, SchemaArrayInputGet):# or isinstance(from_np, SchemaArrayInput):
-                awaiting_prep_stage.append(link)
-                continue
-            if isinstance(from_np, SchemaIndex):
-                if link.from_socket.name == "Index":
-                    _from_name, to_name = get_link_in_out(link)
-                    to_node = frame_nc.get( (*self.autogen_path_names, to_name+self.index_str()) )
-
-                    if to_node.node_type in ['DUMMY', 'DUMMY_SCHEMA']:
-                        # prRed("This is causing the problem, right?")
-                        from .utilities import gen_nc_input_for_data
-                        nc_cls = gen_nc_input_for_data(link.from_socket)
-                        if (nc_cls): #HACK
-                            sig = ("MANTIS_AUTOGENERATED", *self.tree_path_names[1:], self.index_str(), link.from_socket.name, link.from_socket.identifier)
-                            nc_from = nc_cls(sig, self.node.base_tree)
-                            # ugly! maybe even a HACK!
-                            nc_from.inputs = {}
-                            from .base_definitions import NodeSocket
-                            nc_from.outputs = {link.from_socket.name:NodeSocket(name = link.from_socket.name, node=nc_from)}
-                            from .base_definitions import get_socket_value
-                            nc_from.parameters = {link.from_socket.name:index}
-                            frame_nc[sig]=nc_from
-                            from_node = nc_from
-                            # self.autogenerated_nodes[sig]=from_node
-                            self.solved_nodes[sig]=from_node
-
-                            connection = from_node.outputs[link.from_socket.name].connect(node=to_node, socket=link.to_socket.identifier)
-                            # prGreen(connection)
-                        continue
-
-                    # this actually seems to work. I could use Autogen nodes
-                    # but maybe this is actually better since it results in fewer nodes.
-                    # if this never causes any problems, then I will do it in other places
-                    # HACK Here Be Danger HACK
-                    to_node.parameters[link.to_socket.name] = index
-                    del to_node.inputs[link.to_socket.name]
-                    # so I have tried to implement this as connections and actually this is the best way because otherwise I have
-                    #  to do something very similar on the input node.
-                    # HACK
-                elif link.from_socket.name == "Schema Length":
-                    # # see, here I can just use the schema node
-                    _from_name, to_name = get_link_in_out(link)
-                    to_node = frame_nc.get( (*self.autogen_path_names, to_name+self.index_str()) )
-                    # this self.index_link is only used here?
-                    if (self.index_link.from_node):
-                        connection = self.index_link.from_node.outputs[self.index_link.from_socket].connect(node=to_node, socket=link.to_socket.name)
-                    # otherwise we can autogen an int input I guess...?
-                    else:
-                        raise RuntimeError("I was expecting there to be an incoming connection here for Schema Length")
-                continue
-            if isinstance(from_np, SchemaIncomingConnection):
-                if link.from_socket.name in self.incoming_connections.keys():
-                    incoming = self.incoming_connections[link.from_socket.name]
-                    # if incoming is None:
-                    #     print (link.from_socket.name)
-                    from_node = incoming.from_node
-                    _from_name, to_name = get_link_in_out(link)
-                    to_node = frame_nc.get( (*self.autogen_path_names, to_name+self.index_str()) )
-                    # try:
-                    connection = from_node.outputs[incoming.from_socket].connect(node=to_node, socket=link.to_socket.name)
-                    # prGreen(connection)
-                    # except KeyError as e:
-                    #     prRed(f"busted: {from_node}:{incoming.from_socket} --> {to_node}:{link.to_socket.name},{link.to_socket.identifier}")
-                        # for output in from_node.outputs:
-                        #     prOrange(output)
-                        # for sinput in from_node.inputs:
-                        #     prPurple(sinput)
-                        # raise e
-                    init_connections(from_node)
-                continue
-            if isinstance(to_np, SchemaOutgoingConnection):
-                self.held_links.append(link)
-                continue 
-            if isinstance(from_np, SchemaConstInput):
-                if link.from_socket.name in self.constant_in.keys():
-                    incoming = self.constant_in[link.from_socket.name]
-                    from_node = incoming.from_node
-                    to_name = get_link_in_out(link)[1]
-                    to_node = frame_nc.get( (*self.autogen_path_names, to_name+self.index_str()) )
-
-                    # print(from_node, incoming.from_socket, "==>", to_node, link.to_socket.identifier)
-                    # print (to_node.inputs)
-                    # for k,v in from_node.outputs.items():
-                    #     print (k,v)
-                    # print (from_node.outputs[incoming.from_socket])
-                    to_socket=link.to_socket.name
-                    from .base_definitions import SchemaGroup
-                    if isinstance(to_np, SchemaGroup):
-                        to_socket=link.to_socket.identifier
-                    connection = from_node.outputs[incoming.from_socket].connect(node=to_node, socket=to_socket)
-                    init_connections(from_node)
-                continue 
-            if isinstance(to_np, SchemaArrayInputGet):
-                from_name, to_name = get_link_in_out(link)
-                from_nc = frame_nc.get( (*self.autogen_path_names, from_name+self.index_str()))
-                to_nc = self.schema_nodes.get((*self.tree_path_names, self.node.signature[-1], to_name))
-                # this only needs to be done once.
-                if index == 0:
-                    old_nc = self.all_nodes.get((*self.tree_path_names, self.node.signature[-1], from_name))
-                    # I am not sure about this!
-                    existing_link = old_nc.outputs[link.from_socket.name].links[0]
-                    existing_link.die()
-                #
-                connection = from_nc.outputs[link.from_socket.name].connect(node=to_nc, socket=link.to_socket.name)
-                continue
-            if isinstance(from_np, SchemaArrayInput):
-                get_index = index
-                try:
-                    incoming = self.array_input_connections[link.from_socket.identifier][get_index]
-                except IndexError:
-                    if len(self.array_input_connections[link.from_socket.identifier]) > 0:
-                        incoming = self.array_input_connections[link.from_socket.identifier][0]
-                        # prOrange(incoming.from_node.node_type)
-                        if incoming.from_node.node_type not in ['DUMMY_SCHEMA']:
-                            raise RuntimeError(wrapRed("You need to make it so Mantis checks if there are enough Array inputs."))
-                        else: # do nothing
-                            continue
-                    else:
-                        raise RuntimeError(wrapRed("make it so Mantis checks if there are enough Array inputs!"))
-                to_name = get_link_in_out(link)[1]
-                to_node = frame_nc.get((*self.autogen_path_names, to_name+self.index_str()))
-                connection = incoming.from_node.outputs[incoming.from_socket].connect(node=to_node, socket=link.to_socket.name)
-                init_connections(incoming.from_node)
-                continue
-            link_path_names = self.tree_path_names[1:]
-            # use this line to debug, usually I don't need it
-            connection = link_node_containers(self.autogen_path_names, link, frame_nc, from_suffix=self.index_str(), to_suffix=self.index_str())
-        for k,v in frame_nc.items():
-            self.solved_nodes[k]=v
-            init_dependencies(v) # it is hard to overstate how important this single line of code is
-            # for node in v.hierarchy_dependencies:
-            #     init_connections(node)
-
-        # done with the link handling.
-        
-
-        # Start Section: This is the place where we solve dependencies and continue
-        # we need to sort the nodes in solved_nc and prepare whatever can be preepared.
-        from collections import deque
-        unprepared= deque()
-        for nc in frame_nc.values():
-            if nc.prepared == False:
-                unprepared.append(nc)
-                # this is potentially a little inneficient but it isn't a big deal.
-                # since the only extra preparations I am doing is nodes that don't yet have outgoing connections
-                # but I may add them in the next step anyways, then I need to prepare them! so this is simpler.
-        # at this point it should be possible to sort unprepared to avoid the while loop
-        # but I don't really care. this will work since we have guarenteed solved all the
-        #  schema_dummy's dependencies.... I hope. lol.
-
-        while unprepared: # DANGER.
-            # raise NotImplementedError
-            # prRed("this part!")
-            nc = unprepared.pop()
-            # print(nc)
-            if sum([dep.prepared for dep in nc.hierarchy_dependencies]) == len(nc.hierarchy_dependencies):
-                nc.bPrepare()
+    def solve(self):
+        for index in range(self.solve_length):
+            self.index = index
+            frame_mantis_nodes = self.solve_iteration()
+            for sig, nc in frame_mantis_nodes.items():
                 if nc.node_type == 'DUMMY_SCHEMA':
-                    self.solve_nested_schema(nc)
-
-            else:
-                # print (sum([dep.prepared for dep in nc.hierarchy_dependencies]), len(nc.hierarchy_dependencies))
-                # for dep in nc.hierarchy_dependencies:
-                #     if not dep.prepared:
-                #         prOrange(dep)
-                unprepared.appendleft(nc)
-        
-        for i in range(len(awaiting_prep_stage)): #why is this a for loop and not a while loop?? # FIXME?
-            link = awaiting_prep_stage.pop()
-            to_np = link.to_socket.node; from_np = link.from_socket.node
-            if isinstance(to_np, SchemaConstOutput):
-                to_node = self.schema_nodes.get((*self.tree_path_names, self.node.signature[-1], to_np.bl_idname))
-                expose_when = to_node.evaluate_input('Expose when N==')
-                if index == expose_when:
-                    for outgoing in self.constant_out[link.to_socket.name]:
-                        to_node = outgoing.to_node
-                        from_name = get_link_in_out(link)[0]
-                        from_node = frame_nc.get( (*self.autogen_path_names, from_name+self.index_str()) )
-                        connection = from_node.outputs[link.from_socket.name].connect(node=to_node, socket=outgoing.to_socket)
-            if isinstance(to_np, SchemaArrayOutput): # if this duplicated code works, dedupe!
-                to_node = self.schema_nodes.get((*self.tree_path_names, self.node.signature[-1], to_np.bl_idname))
-                for outgoing in self.array_output_connections[link.to_socket.identifier]:
-                    # print (type(outgoing))
-                    from .schema_containers import SchemaIndex
-                    from_name = get_link_in_out(link)[0]
-                    from_node = frame_nc.get( (*self.autogen_path_names, from_name+self.index_str()) )
-                    if not from_node:
-                        from_node = self.schema_nodes.get( (*self.tree_path_names, self.node.signature[-1], from_np.bl_idname) )
-                    if not from_node:
-                        raise RuntimeError()
-                    to_node = outgoing.to_node
-
-                    if isinstance(from_node, SchemaIndex): # I think I need to dedup this stuff
-                        # print("INDEX")
-                        from .utilities import gen_nc_input_for_data
-                        nc_cls = gen_nc_input_for_data(link.from_socket)
-                        if (nc_cls): #HACK
-                            sig = ("MANTIS_AUTOGENERATED", *self.tree_path_names[1:], self.index_str(), link.from_socket.name, link.from_socket.identifier)
-                            nc_from = nc_cls(sig, self.node.base_tree)
-                            # ugly! maybe even a HACK!
-                            nc_from.inputs = {}
-                            from .node_container_common import NodeSocket
-                            nc_from.outputs = {link.from_socket.name:NodeSocket(name = link.from_socket.name, node=nc_from)}
-                            from .node_container_common import get_socket_value
-                            if link.from_socket.name in ['Index']:
-                                nc_from.parameters = {link.from_socket.name:index}
-                            else:
-                                nc_from.parameters = {link.from_socket.name:schema_length}
-                            frame_nc[sig]=nc_from
-                            from_node = nc_from
-                            self.solved_nodes[sig]=from_node
-
-                    # I have a feeling that something bad will happen if both of these conditions (above and below) are true
-                    if to_node.node_type == 'DUMMY_SCHEMA' and to_node.prepared:
-                        other_stem = ('SCHEMA_AUTOGENERATED', *to_node.signature[1:-1])
-                        from .utilities import get_node_prototype
-                        other_schema_np = get_node_prototype(to_node.signature, to_node.base_tree)
-                        other_schema_tree = other_schema_np.node_tree
-                        for n in other_schema_tree.nodes:
-                            if n.bl_idname not in ["SchemaArrayInput", "SchemaArrayInputGet"]:
-                                continue
-                            out = n.outputs[outgoing.to_socket]
-                            for l in out.links:
-                                other_index_str = lambda : '.'+str(to_node.uuid)+'.'+str(index).zfill(4)
-                                out_node = self.all_nodes.get((*other_stem, l.to_node.name+other_index_str()))
-                                connection = from_node.outputs[link.from_socket.name].connect(node=out_node, socket=l.to_socket.name)
-                    else:
-                        connection = from_node.outputs[link.from_socket.name].connect(node=to_node, socket=outgoing.to_socket)
-                    
-                    
-            if isinstance(from_np, SchemaArrayInputGet): # or isinstance(from_np, SchemaArrayInput) or 
-                get_index = index
-                if isinstance(from_np, SchemaArrayInputGet):
-                    from_node = self.schema_nodes.get((*self.tree_path_names, self.node.signature[-1], from_np.bl_idname))
-                    from .utilities import cap, wrap
-                    get_index = from_node.evaluate_input("Index", index)
-                    oob = from_node.evaluate_input("OoB Behaviour")
-                    # we must assume that the array has sent the correct number of links
-                    if oob == 'WRAP':
-                        get_index = wrap(get_index, len(self.array_input_connections[link.from_socket.identifier])-1, 0)
-                    if oob == 'HOLD':
-                        get_index = cap(get_index, len(self.array_input_connections[link.from_socket.identifier])-1)
-                try:
-                    incoming = self.array_input_connections[link.from_socket.identifier][get_index]
-                except IndexError:
-                    raise RuntimeError(wrapRed("Dummy! You need to make it so Mantis checks if there are enough Array inputs! It should probably have a Get Index!"))
-                to_name = get_link_in_out(link)[1]
-                to_node = frame_nc.get((*self.autogen_path_names, to_name+self.index_str()))
-                connection = incoming.from_node.outputs[incoming.from_socket].connect(node=to_node, socket=link.to_socket.name)
-                init_connections(incoming.from_node)
-            # end seciton
-        return frame_nc
-
-
-            
-
-# TODO: figure out why the tree is sorting wrong when using arrays!
-
-
-
-
-
-# despite a lot of ugly hacks, things are going well
-# current TODO:
-#  - get nested schema working when nodes in the parent tree depend on them
-#  - eventually I can clean all this up by re-designing the solver code to solve one iteration at a time instead of all at once
-
-#  - get schema-in-group working
-#  - groups need constants and arrays 
-# maybe the simplest solution is for groups to be schema always
-# but I don't want to do that
-
-# anyways the next milestone is for the spine to be a group or schema output
-
-# note that schemas will send out arrays and such. So I don't necessarily want groups to have array in/out
-# instead, groups are simple
-# they can contain schema, but schema can't have arrays from the group interface
-# groups are good for large, abstract components
-# fundamentally they are similar but groups are cleaner since they don't require dependency solving
-
-# anyways... schema with nesting and groups is good enough for now
-# I don't need it to be stable. I just need to rig my human model with Mantis and iterate from there. Getting it to rig a human character will find and fix most bugs.
+                    self.nested_schemas[sig] = nc
+        self.finalize(frame_mantis_nodes)
+        self.node.solver = self
+        return self.solved_nodes
+