schema_solve.py 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. from ..utilities.utilities import (prRed, prGreen, prPurple, prWhite,
  2. prOrange,
  3. wrapRed, wrapGreen, wrapPurple, wrapWhite,
  4. wrapOrange,)
  5. from ..utilities.utilities import init_connections, init_dependencies, get_link_in_out
  6. from ..base_definitions import (SchemaUINode, custom_props_types, \
  7. MantisNodeGroup, SchemaGroup, replace_types, GraphError, links_sort_key)
  8. from ..utilities.node_common import setup_custom_props_from_np
  9. # a class that solves Schema nodes
  10. from bpy.types import NodeGroupInput, NodeGroupOutput, NodeSocket
  11. from .readtree import execution_error_cleanup
  12. class SchemaSolver:
  13. __slots__ = (
  14. "all_nodes", "node", "solved", "tree",
  15. "uuid", "error_popups", "signature",
  16. "schema_nodes", "solved_nodes",
  17. "incoming_connections", "outgoing_connections",
  18. "constant_in", "constant_out",
  19. "array_input_connections", "array_output_connections",
  20. "nested_schemas", "autogenerated_nodes",
  21. "held_links", "tree_path_names",
  22. "autogen_path_names", "is_node_group",
  23. "index_link", "solve_length", "index",
  24. "interfaces", "index_str", "prev_index_str",
  25. "current_interface", "next_interface"
  26. )
  27. def __init__(self, schema_dummy, nodes, ui_node, signature=None, error_popups=False):
  28. self.all_nodes = nodes # this is the parsed tree from Mantis
  29. self.node = schema_dummy
  30. self.node.solver = self
  31. self.solved = False
  32. self.tree = ui_node.node_tree
  33. self.uuid = self.node.uuid
  34. self.error_popups = error_popups
  35. if signature:
  36. self.signature = signature
  37. else:
  38. self.signature = self.node.signature
  39. self.schema_nodes={}; self.solved_nodes = {}
  40. self.incoming_connections = {}; self.outgoing_connections = {}
  41. self.constant_in = {}; self.constant_out = {}
  42. self.array_input_connections = {}; self.array_output_connections = {}
  43. self.nested_schemas = {}; self.autogenerated_nodes = {}
  44. self.held_links = []
  45. self.tree_path_names = [*self.node.signature] # same tree as the schema node
  46. self.autogen_path_names = ['SCHEMA_AUTOGENERATED', *self.node.signature[1:]]
  47. self.is_node_group = False
  48. if self.node.ui_node.bl_idname == "MantisNodeGroup":
  49. self.is_node_group = True
  50. if self.node.inputs['Schema Length'].links:
  51. self.index_link = self.node.inputs['Schema Length'].links[0]
  52. else:
  53. self.index_link = None
  54. # this should never happen, but it's OK to check.
  55. if self.node.inputs["Schema Length"].is_linked:
  56. if (other := self.node.inputs['Schema Length'].links[0].from_node).prepared == False:
  57. raise RuntimeError(f"Schema Length cannot be determined for {self.node} because {other} is not prepared.")
  58. self.solve_length = self.node.evaluate_input("Schema Length")
  59. # The user may be using an overlay variable to set the schema length
  60. from .overlays import node_scan_overlays
  61. if isinstance(self.solve_length, str):
  62. self.solve_length = node_scan_overlays(self.node, 'Schema Length')
  63. if self.solve_length == 0: return
  64. elif self.solve_length < 0:
  65. raise GraphError(f"Schema Length of less than 0 is not allowed in {self.node}")
  66. # I'm making this a property of the solver because the solver's data is modified as it solves each iteration
  67. self.index = 0
  68. self.interfaces = []
  69. prWhite(f"\nExpanding schema {self.tree.name} in node {self.node.signature}"
  70. f" with length {self.solve_length}.")
  71. self.init_schema_links()
  72. self.generate_interface_nodes()
  73. self.set_schema_per_interation_constants()
  74. # Sort the multi-input nodes in reverse order of ID, this ensures that they are
  75. # read in the order they were created
  76. for inp in self.node.inputs.values():
  77. inp.links.sort(key=links_sort_key)
  78. for ui_node in self.tree.nodes:
  79. # Make the mantis nodes for the special interface types
  80. # we use the bl_idname because all schema nodes should be single-instance
  81. signature = (*self.tree_path_names, ui_node.bl_idname)
  82. if isinstance(ui_node, (SchemaUINode, NodeGroupInput, NodeGroupOutput)):
  83. # We use the schema node's "natural signature" here because it represents
  84. # the "original" signature of the schema UI group node since this schema
  85. # solver may be in a nested schema, and its node's signature may have
  86. # uuid/index attached.
  87. get_sig = (*self.node.ui_signature, ui_node.bl_idname)
  88. if not (mantis_node := self.all_nodes.get(get_sig)):
  89. raise RuntimeError(wrapRed(f"Not found: {get_sig}"))
  90. self.schema_nodes[signature] = mantis_node
  91. # HACK to make Group Nodes work
  92. if ui_node.bl_idname == "NodeGroupInput":
  93. from ..schema_nodes import SchemaConstInput
  94. mantis_node = SchemaConstInput(signature=signature, base_tree=self.node.base_tree, parent_schema_node=self.node)
  95. self.schema_nodes[signature] = mantis_node
  96. if ui_node.bl_idname == "NodeGroupOutput":
  97. from ..schema_nodes import SchemaConstOutput
  98. mantis_node = SchemaConstOutput(signature=signature, base_tree=self.node.base_tree, parent_schema_node=self.node)
  99. self.schema_nodes[signature] = mantis_node
  100. def init_schema_links(self,):
  101. """ Sort and store the links to/from the Schema group node."""
  102. for item in self.tree.interface.items_tree:
  103. if item.item_type == 'PANEL': continue
  104. from ..utilities.utilities import read_schema_type
  105. parent_name = read_schema_type(item)
  106. # just gonna try and make the
  107. match parent_name:
  108. case 'Connection':
  109. if item.in_out == 'INPUT':
  110. if incoming_links := self.node.inputs[item.identifier].links:
  111. self.incoming_connections[item.name] = incoming_links[0]
  112. else:
  113. self.incoming_connections[item.name] = None
  114. else: # OUTPUT
  115. if outgoing_links := self.node.outputs[item.identifier].links:
  116. self.outgoing_connections[item.name] = outgoing_links.copy()
  117. else:
  118. self.outgoing_connections[item.name] = []
  119. case 'Constant':
  120. if item.in_out == 'INPUT':
  121. if constant_in_links := self.node.inputs[item.identifier].links:
  122. self.constant_in[item.name] = constant_in_links[0]
  123. else:
  124. self.constant_in[item.name] = None
  125. else: # OUTPUT
  126. if constant_out_links := self.node.outputs[item.identifier].links:
  127. self.constant_out[item.name] = constant_out_links.copy()
  128. else:
  129. self.constant_out[item.name] = []
  130. case 'Array':
  131. if item.in_out == 'INPUT':
  132. if item.identifier not in self.array_input_connections.keys():
  133. self.array_input_connections[item.identifier]=[]
  134. if in_links := self.node.inputs[item.identifier].links:
  135. self.array_input_connections[item.identifier]=in_links.copy()
  136. # I am tempted to put a check here, but it is sufficient to
  137. # rely on hierarchy links ensuring the arrays are prepared.
  138. # and testing here for un-prepared arrays will mess up schema
  139. # relationships in non-hierarchy situations.
  140. # Still, I'd like to have an easier time catching these problems.
  141. else: # OUTPUT
  142. if item.identifier not in self.array_output_connections.keys():
  143. self.array_output_connections[item.identifier]=[]
  144. if out_links := self.node.outputs[item.identifier].links:
  145. self.array_output_connections[item.identifier] = out_links.copy()
  146. # Using lambdas here causes a lot of function calls, should be optimized out
  147. # since after all this only needs to change when the index updates
  148. def set_schema_per_interation_constants(self):
  149. # NOTE: I removed the UUID that has always been here
  150. # I can't figure out why I needed it and as far as I can tell there
  151. # really is no way for this to have name clashes. Tests work OK, too.
  152. self.index_str = '.'+str(self.index).zfill(4)
  153. self.prev_index_str = '.'+str(self.index-1).zfill(4)
  154. self.current_interface = self.interfaces[self.index]
  155. self.next_interface = self.interfaces[self.index + 1] # this is guaranteed
  156. if self.is_node_group:
  157. self.index_str = ''; self.prev_index_str = ''
  158. def generate_interface_nodes(self):
  159. from ..internal_nodes import GroupInterface
  160. # One input interface per iteration, and one output interface
  161. items_tree = self.tree.interface.items_tree
  162. mContext=self.node.mContext
  163. for i in range(self.solve_length): # one input per
  164. index_str = '.'+str(i).zfill(4)
  165. name = 'InputInterface'+index_str
  166. if i == 0: in_out = 'SCHEMA_INPUT'
  167. else: in_out = 'SCHEMA_BETWEEN'
  168. signature = (None, *self.autogen_path_names[1:], name)
  169. if not (interface := self.all_nodes.get( signature)):
  170. interface = GroupInterface(signature=signature, base_tree=self.tree, in_out=in_out,
  171. mContext=mContext, ui_node=self.node.ui_node, items_tree=items_tree)
  172. interface.generated_at_schema_index = self.node.generated_at_schema_index + [i]
  173. # add the index and length socket constants. Only the first one needs Length
  174. # (it's best to avoid having multiple places to read single-source values.)
  175. interface.parameters['Index'] = i
  176. # This has already been fully resolved, we do not need to relink anything.
  177. if i == 0: interface.parameters['Schema Length'] = self.solve_length
  178. # the GroupInterface will initialize with the right sockets, but we still have to link them
  179. if in_out == 'SCHEMA_INPUT': # Take the links into the schema and plug them here
  180. for item in items_tree:
  181. if item.item_type == 'PANEL': continue
  182. if (mantis_link := self.constant_in.get(item.name)):
  183. mantis_link.from_node.outputs[mantis_link.from_socket].connect(interface, item.identifier)
  184. # for mantis_link in self.array_input_connections.get(item.name, []):
  185. # mantis_link.from_node.outputs[mantis_link.from_socket].connect(interface, item.identifier)
  186. if (mantis_link := self.incoming_connections.get(item.name)):
  187. mantis_link.from_node.outputs[mantis_link.from_socket].connect(interface, item.identifier)
  188. self.interfaces.append(interface)
  189. # solved nodes will get added to the outer all_nodes later
  190. self.solved_nodes[signature]=interface
  191. # now do the output
  192. signature = (None, *self.autogen_path_names[1:], "OutputInterface" + index_str)
  193. interface = GroupInterface(signature=signature, base_tree=self.tree, in_out='OUTPUT',
  194. mContext=mContext, ui_node=self.node.ui_node, items_tree=items_tree)
  195. self.interfaces.append(interface)
  196. interface.generated_at_schema_index = i
  197. self.solved_nodes[signature]=interface
  198. # strip the AUTOGEN/SCHEMA tag if it is there
  199. def is_node_deeper_nested(self, queried_node, compare_node):
  200. return len(compare_node.signature) < len(queried_node.signature)
  201. def gen_solve_iteration_mantis_nodes(self, frame_mantis_nodes, unprepared):
  202. for ui_node in self.tree.nodes:
  203. mantis_node_name = ui_node.name
  204. index_str = self.index_str
  205. mContext=self.node.mContext
  206. if isinstance(ui_node, SchemaUINode):
  207. continue # IGNORE the schema interface nodes, we already made them in __init__()
  208. # they are reused for each iteration.
  209. elif ui_node.bl_idname in ['NodeFrame', 'NodeReroute']:
  210. continue # IGNORE stuff that is purely UI - frames, reroutes.
  211. elif ui_node.bl_idname in ['NodeGroupInput', 'NodeGroupOutput']:
  212. continue # we converted these to Schema Nodes because they represent a Group input.
  213. signature = (*self.autogen_path_names, mantis_node_name+index_str)
  214. ui_signature=(*self.signature, mantis_node_name)
  215. prototype_mantis_node = self.all_nodes[ui_signature]
  216. # the prototype_mantis_node was generated inside the schema when we parsed the tree.
  217. # it is the ui_node of the mantis node which we make for this iteration
  218. # for Schema sub-nodes ... they need a ui_node to init.
  219. if prototype_mantis_node.node_type in ['DUMMY', 'DUMMY_SCHEMA']:
  220. # We stored the ui_node ui_node when creating the Mantis node.
  221. ui_node = prototype_mantis_node.ui_node
  222. # if prototype_mantis_node is a group or schema: TODO changes are needed elsewhere to make this easier to read. LEGIBILITY
  223. if ui_node.bl_idname in ["MantisNodeGroup", "MantisSchemaGroup"]:
  224. mantis_node = prototype_mantis_node.__class__(
  225. signature, prototype_mantis_node.base_tree, ui_node=ui_node,
  226. ui_signature = prototype_mantis_node.signature)
  227. # now let's copy the links from the ui_node node
  228. if ui_node.bl_idname in ["MantisNodeGroup"]:
  229. mantis_node.prepared = False
  230. mantis_node.node_type = 'DUMMY_SCHEMA' # we promote it to a schema for now
  231. from ..base_definitions import NodeSocket
  232. mantis_node.inputs['Schema Length'] = NodeSocket(is_input=True, node=mantis_node, name='Schema Length')
  233. mantis_node.parameters['Schema Length'] = 1 # set the length to 1 since it is a single group instance
  234. # we'll make the autogenerated nodes for constant inputs. It doesn't matter that there is technically
  235. # a ui_node available for each one -- these are cheap and I want this to be easy.
  236. from .readtree import make_connections_to_ng_dummy
  237. make_connections_to_ng_dummy(self.node.base_tree, self.autogen_path_names, frame_mantis_nodes, self.all_nodes, mantis_node)
  238. else:
  239. mantis_node = prototype_mantis_node.__class__(signature, prototype_mantis_node.base_tree, ui_node=ui_node)
  240. else:
  241. mantis_node = prototype_mantis_node.__class__(signature, prototype_mantis_node.base_tree)
  242. frame_mantis_nodes[mantis_node.signature] = mantis_node
  243. self.all_nodes[mantis_node.signature] = mantis_node
  244. mantis_node.ui_signature=ui_signature # set the natural signature to ensure we can access from the UI
  245. if mantis_node.prepared == False:
  246. unprepared.append(mantis_node)
  247. if mantis_node.__class__.__name__ in custom_props_types:
  248. setup_custom_props_from_np(mantis_node, ui_node)
  249. # be sure to pass on the Mantis Context to them.
  250. mantis_node.mContext=mContext
  251. mantis_node.generated_at_schema_index = self.node.generated_at_schema_index + [self.index]
  252. mantis_node.fill_parameters(ui_node)
  253. assert mantis_node.mContext is not None
  254. def handle_link_from_index_input(self, index, frame_mantis_nodes, ui_link):
  255. from ..base_definitions import can_remove_socket_for_autogen
  256. _from_name, to_name = get_link_in_out(ui_link)
  257. to_node = frame_mantis_nodes[ (*self.autogen_path_names, to_name+self.index_str) ]
  258. if (not can_remove_socket_for_autogen(to_node, ui_link.to_socket.name)) or \
  259. to_node.node_type in ['DUMMY', 'DUMMY_SCHEMA']:
  260. from_node = self.current_interface
  261. connection = from_node.outputs[ui_link.from_socket.name].connect(node=to_node, socket=ui_link.to_socket.identifier)
  262. connection.is_hierarchy = False # For now this is the easiest way to do this
  263. return
  264. # Since the index is already determined, it is safe to remove the socket and just keep the value.
  265. to_node.parameters[ui_link.to_socket.name] = index
  266. del to_node.inputs[ui_link.to_socket.name]
  267. def handle_link_from_schema_length_input(self, frame_mantis_nodes, ui_link):
  268. # see, here I can just use the schema node
  269. _from_name, to_name = get_link_in_out(ui_link)
  270. if to_name in replace_types:
  271. to_node = self.schema_nodes[(*self.autogen_path_names, to_name)]
  272. else:
  273. to_node = frame_mantis_nodes[(*self.autogen_path_names, to_name+self.index_str)]
  274. # this self.index_link is only used here?
  275. if self.index_link is None:
  276. # this should be impossible because the Schema gets an auto-generated Int input.
  277. raise NotImplementedError(" 241 This code should be unreachable. Please report this as a bug!")
  278. if (self.index_link.from_node):
  279. connection = self.index_link.from_node.outputs[self.index_link.from_socket].connect(node=to_node, socket=ui_link.to_socket.name)
  280. # otherwise we can autogen an int input I guess...?
  281. else:
  282. raise RuntimeError("247 I This code should be unreachable. Please report this as a bug!")
  283. def handle_link_from_incoming_connection_input(self, frame_mantis_nodes, ui_link):
  284. incoming = self.incoming_connections[ui_link.from_socket.name]
  285. if incoming is not None:
  286. from_node = incoming.from_node
  287. _from_name, to_name = get_link_in_out(ui_link)
  288. to_node = frame_mantis_nodes[ (*self.autogen_path_names, to_name+self.index_str) ]
  289. socket_name=ui_link.to_socket.name
  290. if to_node.node_type in [ 'DUMMY_SCHEMA' ]:
  291. socket_name=ui_link.to_socket.identifier
  292. connection = from_node.outputs[incoming.from_socket].connect(node=to_node, socket=socket_name)
  293. init_connections(from_node)
  294. def handle_link_to_outgoing_connection_output(self, frame_mantis_nodes, ui_link,):
  295. mantis_incoming_node = self.schema_nodes[*self.tree_path_names, 'SchemaIncomingConnection']
  296. for mantis_link in mantis_incoming_node.outputs[ui_link.to_socket.name].links:
  297. to_mantis_node, to_socket_name = mantis_link.to_node, mantis_link.to_socket
  298. from_name = get_link_in_out(ui_link)[0]
  299. from_mantis_node = self.solved_nodes[ (*self.autogen_path_names, from_name+self.prev_index_str) ]
  300. to_mantis_node_signature = ( *self.autogen_path_names,
  301. to_mantis_node.signature[-1] + self.index_str )
  302. # we need to detect if the next node is in a group.
  303. # REMEMBER: at this point, nested groups haven't been solved yet.
  304. if to_mantis_node_signature not in frame_mantis_nodes.keys() and \
  305. self.is_node_deeper_nested(to_mantis_node, from_mantis_node):
  306. to_mantis_node = frame_mantis_nodes[ (
  307. *self.autogen_path_names, # the SCHEMA_AUTOGENERATED string
  308. to_mantis_node.signature[-2]+ self.index_str ) ]
  309. # this connection was forbidden before, right? so this should be a safe assumption.
  310. assert to_mantis_node.node_type == 'DUMMY_SCHEMA', "I expected this to be a group/schema"
  311. # we need to get the identifier of the named socket now.
  312. to_socket_name = to_mantis_node.ui_node.inputs[to_socket_name].identifier
  313. else:
  314. to_mantis_node = frame_mantis_nodes[ to_mantis_node_signature ] # add the index string
  315. from_socket_name = ui_link.from_socket.name
  316. if from_mantis_node.node_type in ['DUMMY_SCHEMA']:
  317. from_socket_name = ui_link.from_socket.identifier
  318. connection = from_mantis_node.outputs[from_socket_name].connect(node=to_mantis_node, socket=to_socket_name)
  319. # We want to delete the links from the tree into the schema node.
  320. # TODO: this is not robust enough and I do not feel sure this is doing the right thing.
  321. if existing_link := self.incoming_connections[ui_link.to_socket.name]:
  322. if existing_link.to_node == self.node:
  323. print ("INFO: Deleting...", existing_link)
  324. if self.node.signature[-1] in existing_link.to_node.signature:
  325. existing_link.die()
  326. # BUG may exist here.
  327. self.incoming_connections[ui_link.to_socket.name] = connection
  328. def handle_link_from_constant_input(self, frame_mantis_nodes, ui_link, to_ui_node):
  329. incoming = self.constant_in[ui_link.from_socket.name]
  330. if incoming is None:
  331. raise GraphError (f"{self.node} is missing a required input: {ui_link.from_socket.name}")
  332. from_node = incoming.from_node
  333. to_name = get_link_in_out(ui_link)[1]
  334. to_node = frame_mantis_nodes[(*self.autogen_path_names, to_name+self.index_str)]
  335. to_socket=ui_link.to_socket.name
  336. from ..base_definitions import MantisNodeGroup, SchemaGroup
  337. if isinstance(to_ui_node, (SchemaGroup, MantisNodeGroup)):
  338. to_socket=ui_link.to_socket.identifier
  339. connection = from_node.outputs[incoming.from_socket].connect(node=to_node, socket=to_socket)
  340. init_connections(from_node)
  341. def handle_link_from_array_input_get(self, frame_mantis_nodes, ui_link ):
  342. from_ui_node = ui_link.from_socket.node
  343. from_node = self.schema_nodes[(*self.node.ui_signature, from_ui_node.bl_idname)]
  344. from collections import deque
  345. unprepared = deque(from_node.hierarchy_dependencies)
  346. self.prepare_nodes(unprepared)
  347. from ..utilities.utilities import cap, wrap
  348. get_index = from_node.evaluate_input("Index", self.index) # get the most recent link
  349. # getting the link at self.index just saves the trouble of killing the old links
  350. # that are left because this node is reused in each iteration of the schema
  351. assert(get_index is not None), f"Cannot get index in {from_node}"
  352. oob = from_node.evaluate_input("OoB Behaviour")
  353. array_length = len(self.array_input_connections[ui_link.from_socket.identifier])-1
  354. if oob == 'WRAP':
  355. get_index = wrap(0, array_length+1, get_index)
  356. if oob == 'HOLD':
  357. get_index = cap(get_index, array_length)
  358. try:
  359. incoming = self.array_input_connections[ui_link.from_socket.identifier][get_index]
  360. except IndexError:
  361. raise RuntimeError(f"The array index {get_index} is out of bounds. Size: "
  362. f"{len(self.array_input_connections[ui_link.from_socket.identifier])}")
  363. to_name = get_link_in_out(ui_link)[1]
  364. to_node = frame_mantis_nodes[(*self.autogen_path_names, to_name+self.index_str)]
  365. from_socket = incoming.from_node.outputs[incoming.from_socket]
  366. connection = from_socket.connect(to_node, ui_link.to_socket.name)
  367. # TODO: kill links to the array get that are no longer needed
  368. # right now I inefficiently waste cpu-time solving extra nodes
  369. # because I took the lazy way out
  370. def handle_link_to_array_input_get(self, frame_mantis_nodes, ui_link):
  371. from_name, to_name = get_link_in_out(ui_link)
  372. from_mantis_node = frame_mantis_nodes[(*self.autogen_path_names, from_name+self.index_str)]
  373. to_mantis_node = self.schema_nodes[(*self.tree_path_names, to_name)]
  374. # this only needs to be done once:
  375. if self.index == 0: # BUG? HACK? TODO find out what is going on here.
  376. # Kill the link between the schema node group and the node connecting to it
  377. old_mantis_node = self.all_nodes[(*self.tree_path_names, from_name)]
  378. # I am not sure about this!
  379. existing_link = old_mantis_node.outputs[ui_link.from_socket.name].links[0]
  380. existing_link.die()
  381. #
  382. connection = from_mantis_node.outputs[ui_link.from_socket.name].connect(node=to_mantis_node, socket=ui_link.to_socket.name)
  383. connection.is_hierarchy = True # we have to mark this because links to Schema are not usually hierarchy (?)
  384. to_mantis_node.hierarchy_dependencies.append(from_mantis_node); from_mantis_node.hierarchy_connections.append(to_mantis_node)
  385. # TODO: review the wisdom of this default.
  386. def handle_link_from_array_input(self, frame_mantis_nodes, ui_link, index):
  387. get_index = index
  388. try:
  389. incoming = self.array_input_connections[ui_link.from_socket.identifier][get_index]
  390. except IndexError:
  391. if len(self.array_input_connections[ui_link.from_socket.identifier]) > 0:
  392. incoming = self.array_input_connections[ui_link.from_socket.identifier][0]
  393. # prOrange(incoming.from_node.node_type)
  394. if incoming.from_node.node_type not in ['DUMMY_SCHEMA']:
  395. raise NotImplementedError(wrapRed("dev: make it so Mantis checks if there are enough Array inputs."))
  396. else: # do nothing
  397. return
  398. else:
  399. raise RuntimeError(wrapRed("make it so Mantis checks if there are enough Array inputs!"))
  400. to_name = get_link_in_out(ui_link)[1]
  401. to_node = frame_mantis_nodes[(*self.autogen_path_names, to_name+self.index_str)]
  402. connection = incoming.from_node.outputs[incoming.from_socket].connect(node=to_node, socket=ui_link.to_socket.name)
  403. init_connections(incoming.from_node)
  404. def handle_link_from_array_input_all(self, frame_mantis_nodes, ui_link):
  405. all_links = self.array_input_connections[ui_link.from_socket.identifier]
  406. to_name = get_link_in_out(ui_link)[1]
  407. to_node = frame_mantis_nodes[(*self.autogen_path_names, to_name+self.index_str)]
  408. # connection = incoming.from_node.outputs[incoming.from_socket].connect(node=to_node, socket=ui_link.to_socket.name)
  409. for l in all_links:
  410. # we need to copy the link with the new from-node info
  411. from ..base_definitions import NodeLink
  412. to_socket_name=ui_link.to_socket.name
  413. if to_node.node_type in ['DUMMY_SCHEMA']:
  414. to_socket_name=ui_link.to_socket.identifier
  415. connection = NodeLink(l.from_node, l.from_socket, to_node, to_socket_name,
  416. l.multi_input_sort_id, l.sub_sort_id)
  417. to_node.flush_links()
  418. def handle_link_to_constant_output(self, frame_mantis_nodes, index, ui_link, to_ui_node):
  419. to_node = self.schema_nodes[(*self.tree_path_names, to_ui_node.bl_idname)]
  420. expose_when = to_node.evaluate_input('Expose when N==')
  421. # this will be None for nested Groups that have ordinary Group Input and Group Output nodes.
  422. # and in that case we just do it no matter what
  423. if expose_when is not None and expose_when > self.solve_length-1:
  424. raise GraphError(f"The Schema has a constant output at index {expose_when}"
  425. f" but it terminates at index {self.solve_length-1}. "
  426. f"The Schema must have a length of at least {expose_when+1}.")
  427. from_name = get_link_in_out(ui_link)[0]
  428. #use it directly if it is a mantis node; this happens when the previous node was a Schema
  429. if isinstance( ui_link.from_socket, NodeSocket): # normal
  430. from_node = frame_mantis_nodes[(*self.autogen_path_names, from_name+self.index_str) ]
  431. else: # it's a mantis socket, set manually while looping though prepped links
  432. from_node = ui_link.from_socket.node
  433. from_socket_name = ui_link.from_socket.name
  434. if from_node.node_type == 'DUMMY_SCHEMA':
  435. if isinstance( ui_link.from_socket, NodeSocket): # normal
  436. from_socket_name = ui_link.from_socket.identifier
  437. else:
  438. from_socket_name = ui_link.from_socket.name
  439. # HACK here to force it to work with ordinary node groups, which don't seem to set this value correctly.
  440. if to_ui_node.bl_idname == "NodeGroupOutput":
  441. expose_when = index # the expose-when value is None because the GroupOutput doesn't have the socket.
  442. # end HACK
  443. if index == expose_when:
  444. for outgoing in self.constant_out[ui_link.to_socket.name]:
  445. to_node = outgoing.to_node
  446. connection = from_node.outputs[from_socket_name].connect(node=to_node, socket=outgoing.to_socket)
  447. def handle_link_from_subschema_to_output(self, frame_mantis_nodes, ui_link, to_ui_node):
  448. # wire the schema node itself to the output so it will push the connections when solving
  449. to_node = self.schema_nodes[(*self.tree_path_names, to_ui_node.bl_idname)]
  450. from_name = get_link_in_out(ui_link)[0]
  451. from_node = frame_mantis_nodes[ (*self.autogen_path_names, from_name+self.index_str) ]
  452. connection = from_node.outputs[ui_link.from_socket.identifier].connect(node=to_node, socket=ui_link.to_socket.name)
  453. def handle_link_from_array_type_to_array_out(self, original_ui_link, dummy_link):
  454. # this is so annoyingly specific lol
  455. from_node = dummy_link.from_mantis_node; from_socket_name=dummy_link.from_socket.name
  456. for outgoing in self.array_output_connections[original_ui_link.to_socket.identifier]:
  457. to_node = outgoing.to_node
  458. connection = from_node.outputs[from_socket_name].connect(node=to_node, socket=outgoing.to_socket)
  459. # WTF is even happening here?? TODO BUG HACK
  460. 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!
  461. to_node = self.schema_nodes[(*self.tree_path_names, to_ui_node.bl_idname)] # get it by [], we want a KeyError if this fails
  462. from_name = get_link_in_out(ui_link)[0]
  463. #use it directly if it is a mantis node; this happens when the previous node was a Schema
  464. if isinstance( ui_link.from_socket, NodeSocket): # normal
  465. from_node = frame_mantis_nodes[(*self.autogen_path_names, from_name+self.index_str) ]
  466. else: # it's a mantis socket, set manually while looping though prepped links
  467. from_node = ui_link.from_socket.node
  468. from_socket_name = ui_link.from_socket.name
  469. for outgoing in self.array_output_connections[ui_link.to_socket.identifier]:
  470. if not from_node:
  471. from_node = self.schema_nodes[(*self.tree_path_names, from_ui_node.bl_idname)]
  472. to_node = outgoing.to_node
  473. from ..schema_nodes import SchemaIndex
  474. if isinstance(from_node, SchemaIndex):
  475. signature = ("MANTIS_AUTOGENERATED", *self.tree_path_names[1:-1], self.index_str,
  476. ui_link.from_socket.name, ui_link.from_socket.identifier)
  477. from_node = self.all_nodes.get(signature)
  478. if not from_node:
  479. from .readtree import autogen_node
  480. from_node = autogen_node(self.node.base_tree, ui_link.from_socket,
  481. signature, self.node.mContext)
  482. if ui_link.from_socket.name in ['Index']:
  483. from_node.parameters = {ui_link.from_socket.name:index}
  484. else:
  485. from_node.parameters = {ui_link.from_socket.name:self.solve_length}
  486. frame_mantis_nodes[signature]=from_node; self.solved_nodes[signature]=from_node
  487. self.all_nodes[signature]=from_node
  488. elif from_node.node_type == 'DUMMY_SCHEMA':
  489. if isinstance( ui_link.from_socket, NodeSocket): # normal
  490. from_socket_name = ui_link.from_socket.identifier
  491. else:
  492. from_socket_name = ui_link.from_socket.name
  493. if to_node.node_type == 'DUMMY_SCHEMA' and to_node.prepared:
  494. other_stem = ('SCHEMA_AUTOGENERATED', *to_node.signature[1:])
  495. other_schema_np = to_node.ui_node
  496. other_schema_tree = other_schema_np.node_tree
  497. for n in other_schema_tree.nodes:
  498. if n.bl_idname not in ["SchemaArrayInput", "SchemaArrayInputGet", "SchemaArrayInputAll"]:
  499. continue
  500. out = n.outputs[outgoing.to_socket]
  501. for l in out.links:
  502. other_index_str = lambda : '.'+str(to_node.uuid)+'.'+str(index).zfill(4)
  503. other_sig = (*other_stem, l.to_node.name+other_index_str())
  504. out_node = self.all_nodes.get(other_sig)
  505. if not out_node: # if it is a sub-schema, the other node is in the parent
  506. out_node = to_node.solver.all_nodes.get(other_sig)
  507. if not out_node:
  508. raise KeyError(f"Cannot find node: {other_sig}")
  509. connection = from_node.outputs[from_socket_name].connect(node=out_node, socket=l.to_socket.name)
  510. else:
  511. connection = from_node.outputs[from_socket_name].connect(node=to_node, socket=outgoing.to_socket)
  512. def spoof_link_for_subschema_to_output_edge_case(self, ui_link, ):
  513. to_ui_node = ui_link.to_socket.node
  514. # ui_link is the link between the Schema & the output but we have mantis_links
  515. # connected up correctly. between the output and the schema's generated nodes.
  516. # so seek BACK from the output node and grab the from-node that is connected to
  517. # the link. then modify the ui_link to point to that node.
  518. from ..base_definitions import DummyLink
  519. if not isinstance(ui_link, DummyLink): # make it a Dummy so i can modify it
  520. ui_link = DummyLink(ui_link.from_socket, ui_link.to_socket,
  521. multi_input_sort_id=ui_link.multi_input_sort_id)
  522. to_node = self.schema_nodes[(*self.tree_path_names, to_ui_node.bl_idname)]
  523. for inp in to_node.inputs:
  524. if inp.name == ui_link.to_socket.name: # the most recent one is from
  525. l = inp.links[-1]; break # <---- this index of the outer schema
  526. ui_link.from_mantis_node = l.from_node; ui_link.from_socket = l.from_node.outputs[l.from_socket]
  527. return ui_link
  528. def test_is_sub_schema(self, other):
  529. for i in range(len(other.ui_signature)-1): # -1, we don't want to check this node, obviously
  530. if self.node.ui_signature[:i+1]:
  531. return False
  532. return True
  533. def prepare_nodes(self, unprepared):
  534. # At this point, we've already run a pretty exhaustive preperation phase to prep the schema's dependencies
  535. # So we should not need to add any new dependencies unless there is a bug elsewhere.
  536. # and in fact, I could skip this in some cases, and should investigate if profiling reveals a slowdown here.
  537. forbidden=set()
  538. e = None
  539. # forbid some nodes - they aren't necessary to solve the schema & cause problems.
  540. while unprepared:
  541. mantis_node = unprepared.pop()
  542. if mantis_node.node_type == 'DUMMY_SCHEMA' and not self.test_is_sub_schema(mantis_node):
  543. forbidden.add(mantis_node) # do NOT add this as a dependency.
  544. if mantis_node in forbidden: continue # trying to resolve dependencies for.
  545. if sum([dep.prepared for dep in mantis_node.hierarchy_dependencies]) == len(mantis_node.hierarchy_dependencies):
  546. try:
  547. mantis_node.bPrepare()
  548. if mantis_node.node_type == 'DUMMY_SCHEMA':
  549. self.solve_nested_schema(mantis_node)
  550. except Exception as e:
  551. raise execution_error_cleanup(mantis_node, e, show_error = False)
  552. break
  553. if not mantis_node.prepared:
  554. raise RuntimeError( f"{mantis_node} has failed to prepare."
  555. " Please report this as a bug in mantis." )
  556. else: # Keeping this for-loop as a fallback, it should never add dependencies though
  557. can_add_me = True
  558. for dep in mantis_node.hierarchy_dependencies:
  559. if not dep.prepared and dep not in unprepared:
  560. if dep in forbidden:
  561. can_add_me=False
  562. forbidden.add(mantis_node) # forbid the parent, too
  563. continue
  564. unprepared.appendleft(dep)
  565. if can_add_me:
  566. unprepared.appendleft(mantis_node) # just rotate them until they are ready.
  567. def solve_iteration(self):
  568. """ Solve an iteration of the schema.
  569. - 1 Create the Mantis Node instances that represent this iteration of the schema
  570. - 2 Connect the links from the entrypoint or previous iteration.
  571. - 3 Connect the constant and array links, and any link between nodes entirely within the tree
  572. - 4 Prepare the nodes that modify data (in case of e.g. array get index or nested schema length input)
  573. - 5 Connect the final prepared nodes
  574. and return the nodes that were created in this schema iteration (frame).
  575. This function also adds to held_links to pass data between iterations.
  576. """
  577. from ..schema_nodes_ui import (SchemaIndex,
  578. SchemaArrayInput,
  579. SchemaArrayInputGet,
  580. SchemaArrayInputAll,
  581. SchemaArrayOutput,
  582. SchemaConstInput,
  583. SchemaConstOutput,
  584. SchemaOutgoingConnection,
  585. SchemaIncomingConnection,)
  586. from ..utilities.utilities import clear_reroutes, link_mantis_nodes
  587. from ..base_definitions import array_output_types
  588. self.set_schema_per_interation_constants()
  589. frame_mantis_nodes = {}
  590. # Later we have to run bPrepare() on these guys, so we make the deque and fill it now.
  591. from collections import deque
  592. unprepared= deque()
  593. self.gen_solve_iteration_mantis_nodes(frame_mantis_nodes, unprepared)
  594. # This is where we handle node connections BETWEEN frames
  595. while(self.held_links):
  596. ui_link = self.held_links.pop()
  597. to_ui_node = ui_link.to_socket.node; from_ui_node = ui_link.from_socket.node
  598. if isinstance(to_ui_node, SchemaOutgoingConnection):
  599. self.handle_link_to_outgoing_connection_output(frame_mantis_nodes, ui_link)
  600. # Get the rerouted links from the graph. We don't really need to do this every iteration.
  601. # TODO: use profiling to determine if this is slow; if so: copy & reuse the data, refactor the pop()'s out.
  602. ui_links = clear_reroutes(list(self.tree.links))
  603. # Now we handle ui_links in the current frame, including those ui_links between Schema nodes and "real" nodes
  604. links_to_output = []
  605. array_input_get_link = []
  606. for ui_link in ui_links:
  607. to_ui_node = ui_link.to_socket.node; from_ui_node = ui_link.from_socket.node
  608. if isinstance(from_ui_node, SchemaIndex):
  609. if ui_link.from_socket.name == "Index":
  610. self.handle_link_from_index_input(self.index, frame_mantis_nodes, ui_link)
  611. elif ui_link.from_socket.name == "Schema Length":
  612. self.handle_link_from_schema_length_input(frame_mantis_nodes, ui_link)
  613. continue
  614. if isinstance(from_ui_node, SchemaIncomingConnection):
  615. if ui_link.from_socket.name in self.incoming_connections.keys():
  616. self.handle_link_from_incoming_connection_input(frame_mantis_nodes, ui_link)
  617. continue
  618. if isinstance(from_ui_node, (SchemaConstInput, NodeGroupInput)):
  619. if ui_link.from_socket.name in self.constant_in.keys():
  620. self.handle_link_from_constant_input( frame_mantis_nodes, ui_link, to_ui_node)
  621. continue
  622. if isinstance(to_ui_node, SchemaArrayInputGet):
  623. self.handle_link_to_array_input_get( frame_mantis_nodes, ui_link)
  624. continue
  625. if isinstance(from_ui_node, SchemaArrayInput):
  626. self.handle_link_from_array_input(frame_mantis_nodes, ui_link, self.index)
  627. continue
  628. if isinstance(from_ui_node, SchemaArrayInputAll):
  629. self.handle_link_from_array_input_all(frame_mantis_nodes, ui_link)
  630. continue
  631. # HOLD these links to the next iteration:
  632. if isinstance(to_ui_node, SchemaOutgoingConnection):
  633. if isinstance(from_ui_node, (MantisNodeGroup, SchemaGroup)):
  634. self.handle_link_from_subschema_to_output(frame_mantis_nodes, ui_link, to_ui_node)
  635. self.held_links.append(ui_link) # is this wise? Why am I doing this?
  636. continue
  637. # HOLD these links until prep is done a little later
  638. if isinstance(to_ui_node, (SchemaConstOutput, NodeGroupOutput)) or isinstance(to_ui_node, SchemaArrayOutput):
  639. if isinstance(from_ui_node, (MantisNodeGroup, SchemaGroup)):
  640. self.handle_link_from_subschema_to_output(frame_mantis_nodes, ui_link, to_ui_node)
  641. # both links are desirable to create, so don't continue here
  642. if from_ui_node.bl_idname in array_output_types:
  643. self.handle_link_from_subschema_to_output(frame_mantis_nodes, ui_link, to_ui_node)
  644. # this one wires links around - we need to finish connecting it to the output
  645. # before we can prepare it. Otherwise, it will send a link from *itself* instead of
  646. # rewiring one of its *inputs* to the next node.
  647. # We have to prep it, and then deal with the links between it and the array. pain.
  648. links_to_output.append(ui_link)
  649. continue
  650. if isinstance(from_ui_node, SchemaArrayInputGet):
  651. array_input_get_link.append(ui_link)
  652. continue
  653. # for any of the special cases, we hit a 'continue' block. So this connection is not special, and is made here.
  654. connection = link_mantis_nodes(self.autogen_path_names, ui_link,
  655. frame_mantis_nodes, from_suffix=self.index_str,
  656. to_suffix=self.index_str)
  657. for signature, node in frame_mantis_nodes.items():
  658. self.solved_nodes[signature]=node
  659. if node.node_type == "DUMMY_SCHEMA":
  660. # make sure to add the nodes to the group's sockets if the user set them directly
  661. from .readtree import make_connections_to_ng_dummy
  662. make_connections_to_ng_dummy(
  663. self.node.base_tree,
  664. self.autogen_path_names,
  665. {}, # just pass an empty dict, this argument is not needed in this context
  666. self.all_nodes,
  667. node)
  668. from ..utilities.utilities import init_schema_dependencies
  669. init_schema_dependencies(node, self.all_nodes)
  670. else:
  671. init_dependencies(node) # it is hard to overstate how important this single line of code is
  672. # We have to prepare the nodes leading to Schema Length
  673. unprepared=deque()
  674. for node in frame_mantis_nodes.values():
  675. if node.node_type == 'DUMMY_SCHEMA' and (schema_len_in := node.inputs.get("Schema Length")):
  676. for l in schema_len_in.links:
  677. unprepared.append(l.from_node)
  678. self.prepare_nodes(unprepared)
  679. # We have to prepare the nodes leading to Array Input Get
  680. for ui_link in array_input_get_link:
  681. from_name = get_link_in_out(ui_link)[0]
  682. # because this both provides and receives deps, it must be solved first.
  683. from_node = self.schema_nodes.get( (*self.node.ui_signature, ui_link.from_node.bl_idname) )
  684. self.handle_link_from_array_input_get(frame_mantis_nodes, ui_link )
  685. # Finally, we have to prepare nodes leading to outputs.
  686. for i in range(len(links_to_output)):
  687. unprepared=deque()
  688. ui_link = links_to_output[i]
  689. to_ui_node = ui_link.to_socket.node; from_ui_node = ui_link.from_socket.node
  690. # ugly workaround here in a very painful edge case...
  691. if isinstance(from_ui_node, (MantisNodeGroup, SchemaGroup)) or\
  692. from_ui_node.bl_idname in array_output_types:
  693. ui_link=self.spoof_link_for_subschema_to_output_edge_case(ui_link)
  694. links_to_output[i] = ui_link
  695. from_name = get_link_in_out(ui_link)[0]
  696. signature = (*self.autogen_path_names, from_name+self.index_str)
  697. #use it directly if it is a mantis node; this happens when the previous node was a Schema
  698. if hasattr(ui_link, "from_node") and (from_node := self.schema_nodes.get( (*self.node.ui_signature, ui_link.from_node.bl_idname))):
  699. unprepared.append(from_node)
  700. unprepared.extend(from_node.hierarchy_dependencies)
  701. elif from_node := frame_mantis_nodes.get(signature):
  702. unprepared.append(from_node)
  703. unprepared.extend(from_node.hierarchy_dependencies)
  704. else:
  705. raise RuntimeError(" 671 there has been an error parsing the tree. Please report this as a bug.")
  706. self.prepare_nodes(unprepared) # prepare only the dependencies we need for this link
  707. # and handle the output by the specific type
  708. if isinstance(to_ui_node, (SchemaConstOutput, NodeGroupOutput)):
  709. self.handle_link_to_constant_output(frame_mantis_nodes, self.index, ui_link, to_ui_node)
  710. if isinstance(to_ui_node, SchemaArrayOutput):
  711. if from_node.bl_idname in array_output_types:
  712. from ..base_definitions import DummyLink
  713. for l in from_node.rerouted:
  714. new_link = DummyLink(
  715. from_socket=l.from_node.outputs[l.from_socket],
  716. to_socket=l.to_node.inputs[l.to_socket],
  717. from_mantis_node=l.from_node, to_mantis_node=l.to_node,
  718. multi_input_sort_id=l.multi_input_sort_id
  719. )
  720. self.handle_link_from_array_type_to_array_out(ui_link, new_link)
  721. else:
  722. self.handle_link_to_array_output(frame_mantis_nodes, self.index, ui_link, to_ui_node, from_ui_node)
  723. return frame_mantis_nodes
  724. def solve_nested_schema(self, schema_mantis_node):
  725. """ Solves all schema node groups found in this Schema. This is a recursive function, which will
  726. solve all levels of nested schema - since this function is called by solver.solve().
  727. """
  728. solver=None
  729. if schema_mantis_node.prepared == False:
  730. all_nodes = self.all_nodes.copy()
  731. ui_node = schema_mantis_node.ui_node
  732. length = schema_mantis_node.evaluate_input("Schema Length")
  733. tree = ui_node.node_tree
  734. if schema_mantis_node.ui_node.bl_idname == "MantisNodeGroup":
  735. prOrange(f"Expanding Node Group {tree.name} in node {schema_mantis_node}.")
  736. else:
  737. prOrange(f"Expanding schema {tree.name} in node {schema_mantis_node} with length {length}.")
  738. solver = SchemaSolver(schema_mantis_node, all_nodes, ui_node, schema_mantis_node.ui_signature, error_popups=self.error_popups)
  739. solved_nodes = solver.solve()
  740. schema_mantis_node.prepared = True
  741. for k,v in solved_nodes.items():
  742. self.solved_nodes[k]=v
  743. return solver
  744. def finalize(self, mantis_nodes_in_frame):
  745. from ..schema_nodes_ui import (SchemaOutgoingConnection,)
  746. for i in range(len(self.held_links)):
  747. link = self.held_links.pop()
  748. to_np = link.to_socket.node; from_np = link.from_socket.node
  749. if isinstance(to_np, SchemaOutgoingConnection):
  750. if link.to_socket.name in self.outgoing_connections.keys():
  751. if (outgoing_links := self.outgoing_connections[link.to_socket.name]) is None: continue
  752. for outgoing in outgoing_links:
  753. if outgoing:
  754. to_node = outgoing.to_node
  755. from_node =mantis_nodes_in_frame[(*self.autogen_path_names, from_np.name+self.index_str) ]
  756. from_socket_name = link.from_socket.name
  757. if from_node.node_type in ['DUMMY_SCHEMA']:
  758. from_socket_name = link.from_socket.identifier
  759. connection = from_node.outputs[from_socket_name].connect(node=to_node, socket=outgoing.to_socket)
  760. # we need to kill the link between the Schema itself and the next node and update the deps. Otherwise:confusing bugs.
  761. outgoing.die(); init_dependencies(to_node)
  762. # else: # the node just isn't connected out this socket.
  763. # # solve all unsolved nested schemas...
  764. for schema_sig, schema_mantis_node in self.nested_schemas.items():
  765. self.solve_nested_schema(schema_mantis_node)
  766. for n in self.autogenerated_nodes.values():
  767. init_connections(n)
  768. for c in n.connections:
  769. init_dependencies(c)
  770. all_outgoing_links = []
  771. for conn in self.outgoing_connections.values():
  772. for outgoing in conn:
  773. all_outgoing_links.append(outgoing)
  774. for conn in self.constant_out.values():
  775. for outgoing in conn:
  776. all_outgoing_links.append(outgoing)
  777. for conn in self.array_output_connections.values():
  778. for outgoing in conn:
  779. all_outgoing_links.append(outgoing)
  780. for outgoing in all_outgoing_links:
  781. to_node = outgoing.to_node
  782. for l in to_node.inputs[outgoing.to_socket].links:
  783. if self.node == l.from_node:
  784. l.die()
  785. for inp in self.node.inputs.values():
  786. for l in inp.links:
  787. init_connections(l.from_node) # to force it to have hierarchy connections with the new nodes.
  788. def solve(self):
  789. if self.solve_length < 1:
  790. from ..base_definitions import GraphError
  791. for o in self.node.outputs:
  792. if o.is_linked:
  793. raise GraphError(f"ERROR: Schema {self} has a length"
  794. " of 0 but other nodes depend on it.")
  795. print (f"WARN: Schema {self} has a length of 0 or less and will not expand.")
  796. return {} # just don't do anything - it's OK to have a noop schema if it doesn't have dependencies.
  797. for index in range(self.solve_length):
  798. self.index = index
  799. frame_mantis_nodes = self.solve_iteration()
  800. for sig, mantis_node in frame_mantis_nodes.items():
  801. if mantis_node.node_type == 'DUMMY_SCHEMA':
  802. self.nested_schemas[sig] = mantis_node
  803. self.finalize(frame_mantis_nodes)
  804. self.solved = True
  805. self.node.prepared = True
  806. prGreen(f"Schema declared {len(self.solved_nodes)} nodes.\n")
  807. return self.solved_nodes