schema_solve.py 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. from .utilities import (prRed, prGreen, prPurple, prWhite,
  2. prOrange,
  3. wrapRed, wrapGreen, wrapPurple, wrapWhite,
  4. wrapOrange,)
  5. from .utilities import init_connections, init_dependencies, get_link_in_out
  6. from .base_definitions import SchemaUINode, custom_props_types, MantisNodeGroup, replace_types
  7. from .node_container_common import setup_custom_props_from_np
  8. # a class that solves Schema nodes
  9. from bpy.types import NodeGroupInput, NodeGroupOutput
  10. class SchemaSolver:
  11. def __init__(self, schema_dummy, nodes, prototype, signature=None,):
  12. self.all_nodes = nodes # this is the parsed tree from Mantis
  13. self.node = schema_dummy
  14. self.tree = prototype.node_tree
  15. self.uuid = self.node.uuid
  16. if signature:
  17. self.signature = signature
  18. else:
  19. self.signature = self.node.signature
  20. self.schema_nodes={}
  21. self.solved_nodes = {}
  22. self.incoming_connections = {}
  23. self.outgoing_connections = {}
  24. self.constant_in = {}
  25. self.constant_out = {}
  26. self.array_input_connections = {}
  27. self.array_output_connections = {}
  28. self.nested_schemas = {}
  29. self.autogenerated_nodes = {}
  30. self.held_links = []
  31. self.tree_path_names = [*self.node.signature] # same tree as the schema node
  32. self.autogen_path_names = ['SCHEMA_AUTOGENERATED', *self.node.signature[1:]]
  33. self.is_node_group = False
  34. if self.node.prototype.bl_idname == "MantisNodeGroup":
  35. self.is_node_group = True
  36. if self.node.inputs['Schema Length'].links:
  37. self.index_link = self.node.inputs['Schema Length'].links[0]
  38. else:
  39. self.index_link = None
  40. # this should never happen, but it's OK to check.
  41. if self.node.inputs["Schema Length"].is_linked:
  42. if (other := self.node.inputs['Schema Length'].links[0].from_node).prepared == False:
  43. raise RuntimeError(f"Schema Length cannot be determined for {self.node} because {other} is not prepared.")
  44. self.solve_length = self.node.evaluate_input("Schema Length")
  45. # I'm making this a property of the solver because the solver's data is modified as it solves each iteration
  46. self.index = 0
  47. self.init_schema_links()
  48. self.set_index_strings()
  49. # Sort the multi-input nodes in reverse order of ID, this ensures that they are
  50. # read in the order they were created
  51. for inp in self.node.inputs.values():
  52. inp.links.sort(key=lambda a : -a.multi_input_sort_id)
  53. for ui_node in self.tree.nodes:
  54. # first we need to fill the parameters of the schema nodes.
  55. # we use the bl_idname because all schema nodes should be single-instance
  56. signature = (*self.tree_path_names, ui_node.bl_idname)
  57. if isinstance(ui_node, SchemaUINode):
  58. # We use the schema node's "natural signature" here because it represents
  59. # the "original" signature of the schema UI group node since this schema
  60. # solver may be in a nested schema, and its node's signature may have
  61. # uuid/index attached.
  62. get_sig = (*self.node.ui_signature, ui_node.bl_idname)
  63. if not (mantis_node := self.all_nodes.get(get_sig)): raise RuntimeError(wrapRed(f"Not found: {get_sig}"))
  64. self.schema_nodes[signature] = mantis_node
  65. mantis_node.fill_parameters(ui_node)
  66. # HACK to make Group Nodes work
  67. if ui_node.bl_idname == "NodeGroupInput":
  68. from .schema_containers import SchemaConstInput
  69. mantis_node = SchemaConstInput(signature=signature, base_tree=self.node.base_tree, parent_schema_node=self.node)
  70. self.schema_nodes[signature] = mantis_node
  71. mantis_node.fill_parameters(ui_node)
  72. if ui_node.bl_idname == "NodeGroupOutput":
  73. from .schema_containers import SchemaConstOutput
  74. mantis_node = SchemaConstOutput(signature=signature, base_tree=self.node.base_tree, parent_schema_node=self.node)
  75. self.schema_nodes[signature] = mantis_node
  76. mantis_node.fill_parameters(ui_node)
  77. def set_index_strings(self):
  78. self.index_str = lambda : '.'+str(self.uuid)+'.'+str(self.index).zfill(4)
  79. self.prev_index_str = lambda : '.'+str(self.uuid)+'.'+str(self.index-1).zfill(4)
  80. if self.is_node_group:
  81. self.index_str=lambda : ''
  82. self.prev_index_str=lambda : ''
  83. def init_schema_links(self,):
  84. """ Sort and store the links to/from the Schema group node."""
  85. for item in self.tree.interface.items_tree:
  86. if item.item_type == 'PANEL': continue
  87. parent_name='Constant'
  88. if item.parent.name != '': # in an "prphan" item this is left blank , it is not None or an AttributeError.
  89. parent_name = item.parent.name
  90. match parent_name:
  91. case 'Connection':
  92. if item.in_out == 'INPUT':
  93. if incoming_links := self.node.inputs[item.identifier].links:
  94. self.incoming_connections[item.name] = incoming_links[0]
  95. else:
  96. self.incoming_connections[item.name] = None
  97. else: # OUTPUT
  98. if outgoing_links := self.node.outputs[item.identifier].links:
  99. self.outgoing_connections[item.name] = outgoing_links.copy()
  100. else:
  101. self.outgoing_connections[item.name] = []
  102. case 'Constant':
  103. if item.in_out == 'INPUT':
  104. if constant_in_links := self.node.inputs[item.identifier].links:
  105. self.constant_in[item.name] = constant_in_links[0]
  106. else:
  107. self.constant_in[item.name] = None
  108. else: # OUTPUT
  109. if constant_out_links := self.node.outputs[item.identifier].links:
  110. self.constant_out[item.name] = constant_out_links.copy()
  111. else:
  112. self.constant_out[item.name] = []
  113. case 'Array':
  114. if item.in_out == 'INPUT':
  115. if item.identifier not in self.array_input_connections.keys():
  116. self.array_input_connections[item.identifier]=[]
  117. if in_links := self.node.inputs[item.identifier].links:
  118. self.array_input_connections[item.identifier]=in_links.copy()
  119. # I am tempted to put a check here, but it is sufficient to
  120. # rely on hierarchy links ensuring the arrays are prepared.
  121. # and testing here for un-prepared arrays will mess up schema
  122. # relationships in non-hierarchy situations.
  123. # Still, I'd like to have an easier time catching these problems.
  124. else: # OUTPUT
  125. if item.identifier not in self.array_output_connections.keys():
  126. self.array_output_connections[item.identifier]=[]
  127. if out_links := self.node.outputs[item.identifier].links:
  128. self.array_output_connections[item.identifier] = out_links.copy()
  129. def gen_solve_iteration_mantis_nodes(self, frame_mantis_nodes, unprepared):
  130. for prototype_ui_node in self.tree.nodes:
  131. mantis_node_name = prototype_ui_node.name
  132. index_str = self.index_str()
  133. mContext=self.node.mContext
  134. if isinstance(prototype_ui_node, SchemaUINode):
  135. continue # IGNORE the schema interface nodes, we already made them in __init__()
  136. # they are reused for each iteration.
  137. elif prototype_ui_node.bl_idname in ['NodeFrame', 'NodeReroute']:
  138. continue # IGNORE stuff that is purely UI - frames, reroutes.
  139. elif prototype_ui_node.bl_idname in ['NodeGroupInput', 'NodeGroupOutput']:
  140. continue # we converted these to Schema Nodes because they represent a Group input.
  141. signature = (*self.autogen_path_names, mantis_node_name+index_str)
  142. ui_signature=(*self.signature, mantis_node_name)
  143. prototype_mantis_node = self.all_nodes[ui_signature]
  144. # the prototype_mantis_node was generated inside the schema when we parsed the tree.
  145. # it is the prototype of the mantis node which we make for this iteration
  146. # for Schema sub-nodes ... they need a prototype to init.
  147. if prototype_mantis_node.node_type in ['DUMMY', 'DUMMY_SCHEMA']:
  148. # We stored the prototype ui_node when creating the Mantis node.
  149. ui_node = prototype_mantis_node.prototype
  150. # if prototype_mantis_node is a group or schema: TODO changes are needed elsewhere to make this easier to read. LEGIBILITY
  151. if ui_node.bl_idname in ["MantisNodeGroup", "MantisSchemaGroup"]:
  152. mantis_node = prototype_mantis_node.__class__(
  153. signature, prototype_mantis_node.base_tree, prototype=ui_node,
  154. ui_signature = prototype_mantis_node.signature)
  155. # now let's copy the links from the prototype node
  156. if ui_node.bl_idname in ["MantisNodeGroup"]:
  157. mantis_node.prepared = False
  158. mantis_node.node_type = 'DUMMY_SCHEMA' # we promote it to a schema for now
  159. mantis_node.inputs.init_sockets(['Schema Length']) # add a Schema Length socket
  160. mantis_node.parameters['Schema Length'] = 1 # set the length to 1 since it is a single group instance
  161. # we'll make the autogenerated nodes for constant inputs. It doesn't matter that there is technically
  162. # a prototype available for each one -- these are cheap and I want this to be easy.
  163. from .readtree import make_connections_to_ng_dummy
  164. make_connections_to_ng_dummy(self.node.base_tree, self.autogen_path_names, frame_mantis_nodes, self.all_nodes, mantis_node)
  165. else:
  166. mantis_node = prototype_mantis_node.__class__(signature, prototype_mantis_node.base_tree, prototype=ui_node)
  167. else:
  168. mantis_node = prototype_mantis_node.__class__(signature, prototype_mantis_node.base_tree)
  169. frame_mantis_nodes[mantis_node.signature] = mantis_node
  170. mantis_node.ui_signature=ui_signature # set the natural signature to ensure we can access from the UI
  171. if mantis_node.prepared == False:
  172. unprepared.append(mantis_node)
  173. if mantis_node.__class__.__name__ in custom_props_types:
  174. setup_custom_props_from_np(mantis_node, prototype_ui_node)
  175. mantis_node.fill_parameters(prototype_ui_node)
  176. # be sure to pass on the Mantis Context to them
  177. mantis_node.mContext=mContext
  178. def handle_link_from_index_input(self, index, frame_mantis_nodes, ui_link):
  179. _from_name, to_name = get_link_in_out(ui_link)
  180. to_node = frame_mantis_nodes[ (*self.autogen_path_names, to_name+self.index_str()) ]
  181. if to_node.node_type in ['DUMMY', 'DUMMY_SCHEMA']:
  182. from .utilities import gen_nc_input_for_data
  183. nc_cls = gen_nc_input_for_data(ui_link.from_socket)
  184. if (nc_cls): #HACK
  185. unique_name = "".join([
  186. ui_link.to_socket.node.name+self.index_str(),
  187. ui_link.from_socket.name,
  188. ui_link.from_socket.identifier,
  189. "==>",
  190. ui_link.to_socket.name,
  191. ui_link.to_socket.identifier,
  192. ])
  193. sig = ("MANTIS_AUTOGENERATED", *self.tree_path_names[1:-1], unique_name)
  194. nc_from = frame_mantis_nodes.get(sig)
  195. if not nc_from:
  196. nc_from = nc_cls(sig, self.node.base_tree)
  197. # ugly! maybe even a HACK!
  198. nc_from.inputs = {}
  199. from .base_definitions import NodeSocket
  200. nc_from.outputs = {ui_link.from_socket.name:NodeSocket(name = ui_link.from_socket.name, node=nc_from)}
  201. nc_from.parameters = {ui_link.from_socket.name:index}
  202. frame_mantis_nodes[sig]=nc_from
  203. from_node = nc_from
  204. self.solved_nodes[sig]=from_node
  205. _connection = from_node.outputs[ui_link.from_socket.name].connect(node=to_node, socket=ui_link.to_socket.identifier)
  206. return
  207. # Since the index is already determined, it is safe to remove the socket and just keep the value.
  208. to_node.parameters[ui_link.to_socket.name] = index
  209. del to_node.inputs[ui_link.to_socket.name]
  210. def handle_link_from_schema_length_input(self, frame_mantis_nodes, ui_link):
  211. # see, here I can just use the schema node
  212. _from_name, to_name = get_link_in_out(ui_link)
  213. if to_name in replace_types:
  214. to_node = self.schema_nodes[(*self.autogen_path_names, to_name)]
  215. else:
  216. to_node = frame_mantis_nodes[(*self.autogen_path_names, to_name+self.index_str())]
  217. # this self.index_link is only used here?
  218. if self.index_link is None:
  219. # this should be impossible because the Schema gets an auto-generated Int input.
  220. raise NotImplementedError("This code should be unreachable. Please report this as a bug!")
  221. if (self.index_link.from_node):
  222. connection = self.index_link.from_node.outputs[self.index_link.from_socket].connect(node=to_node, socket=ui_link.to_socket.name)
  223. # otherwise we can autogen an int input I guess...?
  224. else:
  225. raise RuntimeError("I was expecting there to be an incoming connection here for Schema Length")
  226. def handle_link_from_incoming_connection_input(self, frame_mantis_nodes, ui_link):
  227. incoming = self.incoming_connections[ui_link.from_socket.name]
  228. if incoming is not None:
  229. from_node = incoming.from_node
  230. _from_name, to_name = get_link_in_out(ui_link)
  231. to_node = frame_mantis_nodes[ (*self.autogen_path_names, to_name+self.index_str()) ]
  232. socket_name=ui_link.to_socket.name
  233. if to_node.node_type in [ 'DUMMY_SCHEMA' ]:
  234. socket_name=ui_link.to_socket.identifier
  235. connection = from_node.outputs[incoming.from_socket].connect(node=to_node, socket=socket_name)
  236. init_connections(from_node)
  237. def handle_link_to_outgoing_connection_output(self, frame_mantis_nodes, ui_link,):
  238. mantis_incoming_node = self.schema_nodes[*self.tree_path_names, 'SchemaIncomingConnection']
  239. for mantis_link in mantis_incoming_node.outputs[ui_link.to_socket.name].links:
  240. to_mantis_node, to_mantis_socket = mantis_link.to_node, mantis_link.to_socket
  241. from_name = get_link_in_out(ui_link)[0]
  242. from_mantis_node = self.solved_nodes[ (*self.autogen_path_names, from_name+self.prev_index_str()) ]
  243. to_mantis_node = frame_mantis_nodes[ (*self.autogen_path_names, to_mantis_node.signature[-1]+self.index_str()) ]
  244. from_socket_name = ui_link.from_socket.name
  245. if from_mantis_node.node_type in ['DUMMY_SCHEMA']:
  246. from_socket_name = ui_link.from_socket.identifier
  247. connection = from_mantis_node.outputs[from_socket_name].connect(node=to_mantis_node, socket=to_mantis_socket)
  248. # We want to delete the links from the tree into the schema node.
  249. # TODO: this is not robust enough and I do not feel sure this is doing the right thing.
  250. if existing_link := self.incoming_connections[ui_link.to_socket.name]:
  251. if existing_link.to_node == self.node:
  252. print ("INFO: Deleting...", existing_link)
  253. if self.node.signature[-1] in existing_link.to_node.signature:
  254. existing_link.die()
  255. # BUG may exist here.
  256. self.incoming_connections[ui_link.to_socket.name] = connection
  257. def handle_link_from_constant_input(self, frame_mantis_nodes, ui_link, to_ui_node):
  258. incoming = self.constant_in[ui_link.from_socket.name]
  259. from_node = incoming.from_node
  260. to_name = get_link_in_out(ui_link)[1]
  261. to_node = frame_mantis_nodes[(*self.autogen_path_names, to_name+self.index_str())]
  262. to_socket=ui_link.to_socket.name
  263. from .base_definitions import MantisNodeGroup, SchemaGroup
  264. if isinstance(to_ui_node, (SchemaGroup, MantisNodeGroup)):
  265. to_socket=ui_link.to_socket.identifier
  266. connection = from_node.outputs[incoming.from_socket].connect(node=to_node, socket=to_socket)
  267. init_connections(from_node)
  268. def handle_link_to_array_input_get(self, frame_mantis_nodes, ui_link, index):
  269. from_name, to_name = get_link_in_out(ui_link)
  270. from_nc = frame_mantis_nodes[(*self.autogen_path_names, from_name+self.index_str())]
  271. to_nc = self.schema_nodes[(*self.tree_path_names, to_name)]
  272. # this only needs to be done once:
  273. if index == 0: # BUG? HACK? TODO find out what is going on here.
  274. # Kill the link between the schema node group and the node connecting to it
  275. old_nc = self.all_nodes[(*self.tree_path_names, from_name)]
  276. # I am not sure about this!
  277. existing_link = old_nc.outputs[ui_link.from_socket.name].links[0]
  278. existing_link.die()
  279. #
  280. connection = from_nc.outputs[ui_link.from_socket.name].connect(node=to_nc, socket=ui_link.to_socket.name)
  281. def handle_link_from_array_input(self, frame_mantis_nodes, ui_link, index):
  282. get_index = index
  283. try:
  284. incoming = self.array_input_connections[ui_link.from_socket.identifier][get_index]
  285. except IndexError:
  286. if len(self.array_input_connections[ui_link.from_socket.identifier]) > 0:
  287. incoming = self.array_input_connections[ui_link.from_socket.identifier][0]
  288. # prOrange(incoming.from_node.node_type)
  289. if incoming.from_node.node_type not in ['DUMMY_SCHEMA']:
  290. raise NotImplementedError(wrapRed("dev: make it so Mantis checks if there are enough Array inputs."))
  291. else: # do nothing
  292. return
  293. else:
  294. raise RuntimeError(wrapRed("make it so Mantis checks if there are enough Array inputs!"))
  295. to_name = get_link_in_out(ui_link)[1]
  296. to_node = frame_mantis_nodes[(*self.autogen_path_names, to_name+self.index_str())]
  297. connection = incoming.from_node.outputs[incoming.from_socket].connect(node=to_node, socket=ui_link.to_socket.name)
  298. init_connections(incoming.from_node)
  299. def handle_link_from_array_input_all(self, frame_mantis_nodes, ui_link):
  300. all_links = self.array_input_connections[ui_link.from_socket.identifier]
  301. to_name = get_link_in_out(ui_link)[1]
  302. to_node = frame_mantis_nodes[(*self.autogen_path_names, to_name+self.index_str())]
  303. # connection = incoming.from_node.outputs[incoming.from_socket].connect(node=to_node, socket=ui_link.to_socket.name)
  304. for l in all_links:
  305. # we need to copy the link with the new from-node info
  306. from .base_definitions import NodeLink
  307. to_socket_name=ui_link.to_socket.name
  308. if to_node.node_type in ['DUMMY_SCHEMA']:
  309. to_socket_name=ui_link.to_socket.identifier
  310. connection = NodeLink(l.from_node, l.from_socket, to_node, to_socket_name, l.multi_input_sort_id)
  311. to_node.flush_links()
  312. def handle_link_to_constant_output(self, frame_mantis_nodes, index, ui_link, to_ui_node):
  313. to_node = self.schema_nodes[(*self.tree_path_names, to_ui_node.bl_idname)]
  314. expose_when = to_node.evaluate_input('Expose when N==')
  315. # HACK here to force it to work with ordinary node groups, which don't seem to set this value correctly.
  316. if to_ui_node.bl_idname == "NodeGroupOutput":
  317. expose_when = index # just set it directly since it is getting set to None somewhere (I should find out where tho)
  318. # end HACK
  319. if index == expose_when:
  320. for outgoing in self.constant_out[ui_link.to_socket.name]:
  321. to_node = outgoing.to_node
  322. from_name = get_link_in_out(ui_link)[0]
  323. from_node = frame_mantis_nodes[(*self.autogen_path_names, from_name+self.index_str()) ]
  324. connection = from_node.outputs[ui_link.from_socket.name].connect(node=to_node, socket=outgoing.to_socket)
  325. # WTF is even happening here?? TODO BUG HACK
  326. 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!
  327. to_node = self.schema_nodes[(*self.tree_path_names, to_ui_node.bl_idname)] # get it by [], we want a KeyError if this fails
  328. for outgoing in self.array_output_connections[ui_link.to_socket.identifier]:
  329. # print (type(outgoing))
  330. from .schema_containers import SchemaIndex
  331. from_name = get_link_in_out(ui_link)[0]
  332. from_node = frame_mantis_nodes[ (*self.autogen_path_names, from_name+self.index_str()) ]
  333. if not from_node:
  334. from_node = self.schema_nodes[(*self.tree_path_names, from_ui_node.bl_idname)]
  335. to_node = outgoing.to_node
  336. if isinstance(from_node, SchemaIndex): # I think I need to dedup this stuff
  337. # print("INDEX")
  338. from .utilities import gen_nc_input_for_data
  339. nc_cls = gen_nc_input_for_data(ui_link.from_socket)
  340. if (nc_cls): #HACK
  341. sig = ("MANTIS_AUTOGENERATED", *self.tree_path_names[1:-1], self.index_str(), ui_link.from_socket.name, ui_link.from_socket.identifier)
  342. nc_from = nc_cls(sig, self.node.base_tree)
  343. # ugly! maybe even a HACK!
  344. nc_from.inputs = {}
  345. from .node_container_common import NodeSocket
  346. nc_from.outputs = {ui_link.from_socket.name:NodeSocket(name = ui_link.from_socket.name, node=nc_from)}
  347. from .node_container_common import get_socket_value
  348. if ui_link.from_socket.name in ['Index']:
  349. nc_from.parameters = {ui_link.from_socket.name:index}
  350. else:
  351. nc_from.parameters = {ui_link.from_socket.name:self.solve_length}
  352. frame_mantis_nodes[sig]=nc_from
  353. from_node = nc_from
  354. self.solved_nodes[sig]=from_node
  355. # I have a feeling that something bad will happen if both of these conditions (above and below) are true
  356. if to_node.node_type == 'DUMMY_SCHEMA' and to_node.prepared:
  357. other_stem = ('SCHEMA_AUTOGENERATED', *to_node.signature[1:])
  358. from .utilities import get_node_prototype
  359. other_schema_np = get_node_prototype(to_node.signature, to_node.base_tree)
  360. other_schema_tree = other_schema_np.node_tree
  361. for n in other_schema_tree.nodes:
  362. if n.bl_idname not in ["SchemaArrayInput", "SchemaArrayInputGet"]:
  363. continue
  364. out = n.outputs[outgoing.to_socket]
  365. for l in out.links:
  366. other_index_str = lambda : '.'+str(to_node.uuid)+'.'+str(index).zfill(4)
  367. # get it by [], we want a KeyError if this fails
  368. try:
  369. out_node = self.all_nodes[(*other_stem, l.to_node.name+other_index_str())]
  370. except KeyError as e:
  371. for n in self.all_nodes:
  372. if len(n) > len(other_stem)+1: break
  373. for elem in other_stem:
  374. if elem not in n: break
  375. else:
  376. print(n)
  377. raise e
  378. connection = from_node.outputs[ui_link.from_socket.name].connect(node=out_node, socket=l.to_socket.name)
  379. else:
  380. connection = from_node.outputs[ui_link.from_socket.name].connect(node=to_node, socket=outgoing.to_socket)
  381. def handle_link_from_array_input_get(self, frame_mantis_nodes, index, ui_link, from_ui_node ):
  382. get_index = index
  383. from_node = self.schema_nodes[(*self.tree_path_names, from_ui_node.bl_idname)]
  384. from .utilities import cap, wrap
  385. get_index = from_node.evaluate_input("Index", index)
  386. oob = from_node.evaluate_input("OoB Behaviour")
  387. # we must assume that the array has sent the correct number of links
  388. if oob == 'WRAP':
  389. get_index = wrap(get_index, len(self.array_input_connections[ui_link.from_socket.identifier])-1, 0)
  390. if oob == 'HOLD':
  391. get_index = cap(get_index, len(self.array_input_connections[ui_link.from_socket.identifier])-1)
  392. try:
  393. incoming = self.array_input_connections[ui_link.from_socket.identifier][get_index]
  394. except IndexError:
  395. 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!"))
  396. to_name = get_link_in_out(ui_link)[1]
  397. to_node = frame_mantis_nodes[(*self.autogen_path_names, to_name+self.index_str())]
  398. connection = incoming.from_node.outputs[incoming.from_socket].connect(node=to_node, socket=ui_link.to_socket.name)
  399. init_connections(incoming.from_node)
  400. def test_is_sub_schema(self, other):
  401. for i in range(len(other.ui_signature)-1): # -1, we don't want to check this node, obviously
  402. if self.node.ui_signature[:i+1]:
  403. return False
  404. return True
  405. def prepare_nodes(self, unprepared):
  406. # At this point, we've already run a pretty exhaustive preperation phase to prep the schema's dependencies
  407. # So we should not need to add any new dependencies unless there is a bug elsewhere.
  408. # and in fact, I could skip this in some cases, and should investigate if profiling reveals a slowdown here.
  409. forbidden=set()
  410. # forbid some nodes - they aren't necessary to solve the schema & cause problems.
  411. from .readtree import execution_error_cleanup
  412. while unprepared:
  413. nc = unprepared.pop()
  414. if sum([dep.prepared for dep in nc.hierarchy_dependencies]) == len(nc.hierarchy_dependencies):
  415. try:
  416. nc.bPrepare()
  417. except Exception as e:
  418. execution_error_cleanup(nc, e)
  419. if nc.node_type == 'DUMMY_SCHEMA':
  420. self.solve_nested_schema(nc)
  421. elif nc.node_type == 'DUMMY_SCHEMA' and not self.test_is_sub_schema(nc):
  422. forbidden.add(nc)
  423. continue # do NOT add this as a dependency.
  424. else: # Keeping this for-loop as a fallback, it should never add dependencies though
  425. can_add_me = True
  426. for dep in nc.hierarchy_dependencies:
  427. if not dep.prepared and dep not in unprepared:
  428. if dep in forbidden:
  429. can_add_me=False
  430. forbidden.add(nc) # forbid the parent, too
  431. continue
  432. prOrange(f"Adding dependency... {dep}")
  433. unprepared.appendleft(dep)
  434. if can_add_me:
  435. unprepared.appendleft(nc) # just rotate them until they are ready.
  436. def solve_iteration(self):
  437. """ Solve an iteration of the schema.
  438. - 1 Create the Mantis Node instances that represent this iteration of the schema
  439. - 2 Connect the links from the entrypoint or previous iteration.
  440. - 3 Connect the constant and array links, and any link between nodes entirely within the tree
  441. - 4 Prepare the nodes that modify data (in case of e.g. array get index or nested schema length input)
  442. - 5 Connect the final prepared nodes
  443. and return the nodes that were created in this schema iteration (frame).
  444. This function also adds to held_links to pass data between iterations.
  445. """
  446. from .schema_definitions import (SchemaIndex,
  447. SchemaArrayInput,
  448. SchemaArrayInputGet,
  449. SchemaArrayInputAll,
  450. SchemaArrayOutput,
  451. SchemaConstInput,
  452. SchemaConstOutput,
  453. SchemaOutgoingConnection,
  454. SchemaIncomingConnection,)
  455. from .utilities import clear_reroutes, link_node_containers
  456. self.set_index_strings()
  457. frame_mantis_nodes = {}
  458. # Later we have to run bPrepare() on these guys, so we make the deque and fill it now.
  459. from collections import deque
  460. unprepared= deque()
  461. self.gen_solve_iteration_mantis_nodes(frame_mantis_nodes, unprepared)
  462. # This is where we handle node connections BETWEEN frames
  463. while(self.held_links):
  464. ui_link = self.held_links.pop()
  465. to_ui_node = ui_link.to_socket.node; from_ui_node = ui_link.from_socket.node
  466. if isinstance(to_ui_node, SchemaOutgoingConnection):
  467. self.handle_link_to_outgoing_connection_output(frame_mantis_nodes, ui_link)
  468. # Get the rerouted links from the graph. We don't really need to do this every iteration.
  469. # TODO: use profiling to determine if this is slow; if so: copy & reuse the data, refactor the pop()'s out.
  470. ui_links = clear_reroutes(list(self.tree.links))
  471. # Now we handle ui_links in the current frame, including those ui_links between Schema nodes and "real" nodes
  472. awaiting_prep_stage = []
  473. for ui_link in ui_links:
  474. to_ui_node = ui_link.to_socket.node; from_ui_node = ui_link.from_socket.node
  475. if isinstance(from_ui_node, SchemaIndex):
  476. if ui_link.from_socket.name == "Index":
  477. self.handle_link_from_index_input(self.index, frame_mantis_nodes, ui_link)
  478. elif ui_link.from_socket.name == "Schema Length":
  479. self.handle_link_from_schema_length_input(frame_mantis_nodes, ui_link)
  480. continue
  481. if isinstance(from_ui_node, SchemaIncomingConnection):
  482. if ui_link.from_socket.name in self.incoming_connections.keys():
  483. self.handle_link_from_incoming_connection_input(frame_mantis_nodes, ui_link)
  484. continue
  485. if isinstance(from_ui_node, (SchemaConstInput, NodeGroupInput)):
  486. if ui_link.from_socket.name in self.constant_in.keys():
  487. self.handle_link_from_constant_input( frame_mantis_nodes, ui_link, to_ui_node)
  488. continue
  489. if isinstance(to_ui_node, SchemaArrayInputGet):
  490. self.handle_link_to_array_input_get( frame_mantis_nodes, ui_link, self.index)
  491. continue
  492. if isinstance(from_ui_node, SchemaArrayInput):
  493. self.handle_link_from_array_input(frame_mantis_nodes, ui_link, self.index)
  494. continue
  495. if isinstance(from_ui_node, SchemaArrayInputAll):
  496. self.handle_link_from_array_input_all(frame_mantis_nodes, ui_link)
  497. continue
  498. # HOLD these links to the next iteration:
  499. if isinstance(to_ui_node, SchemaOutgoingConnection):
  500. self.held_links.append(ui_link)
  501. continue
  502. # HOLD these links until prep is done a little later
  503. if isinstance(to_ui_node, (SchemaConstOutput, NodeGroupOutput)) or isinstance(to_ui_node, SchemaArrayOutput) or \
  504. isinstance(from_ui_node, SchemaArrayInputGet):
  505. awaiting_prep_stage.append(ui_link)
  506. continue
  507. # for any of the special cases, we hit a 'continue' block. So this connection is not special, and is made here.
  508. connection = link_node_containers(self.autogen_path_names, ui_link, frame_mantis_nodes, from_suffix=self.index_str(), to_suffix=self.index_str())
  509. for signature, node in frame_mantis_nodes.items():
  510. self.solved_nodes[signature]=node
  511. if node.node_type == "DUMMY_SCHEMA":
  512. # make sure to add the nodes to the group's sockets if the user set them directly
  513. from .readtree import make_connections_to_ng_dummy
  514. make_connections_to_ng_dummy(
  515. self.node.base_tree,
  516. self.autogen_path_names,
  517. {}, # just pass an empty dict, this argument is not needed in this context
  518. self.all_nodes,
  519. node)
  520. from .utilities import init_schema_dependencies
  521. init_schema_dependencies(node, self.all_nodes)
  522. else:
  523. init_dependencies(node) # it is hard to overstate how important this single line of code is
  524. self.prepare_nodes(unprepared)
  525. while(awaiting_prep_stage):
  526. ui_link = awaiting_prep_stage.pop()
  527. to_ui_node = ui_link.to_socket.node; from_ui_node = ui_link.from_socket.node
  528. if isinstance(to_ui_node, (SchemaConstOutput, NodeGroupOutput)):
  529. self.handle_link_to_constant_output(frame_mantis_nodes, self.index, ui_link, to_ui_node)
  530. if isinstance(to_ui_node, SchemaArrayOutput):
  531. self.handle_link_to_array_output(frame_mantis_nodes, self.index, ui_link, to_ui_node, from_ui_node)
  532. if isinstance(from_ui_node, SchemaArrayInputGet):
  533. self.handle_link_from_array_input_get(frame_mantis_nodes, self.index, ui_link, from_ui_node )
  534. # end seciton
  535. return frame_mantis_nodes
  536. def solve_nested_schema(self, schema_nc):
  537. """ Solves all schema node groups found in this Schema. This is a recursive function, which will
  538. solve all levels of nested schema - since this function is called by solver.solve().
  539. """
  540. solver=None
  541. if schema_nc.prepared == False:
  542. all_nodes = self.all_nodes.copy()
  543. ui_node = schema_nc.prototype
  544. length = schema_nc.evaluate_input("Schema Length")
  545. tree = ui_node.node_tree
  546. if schema_nc.prototype.bl_idname == "MantisNodeGroup":
  547. prOrange(f"Expanding Node Group {tree.name} in node {schema_nc}.")
  548. else:
  549. prOrange(f"Expanding schema {tree.name} in node {schema_nc} with length {length}.")
  550. solver = SchemaSolver(schema_nc, all_nodes, ui_node, schema_nc.ui_signature)
  551. solved_nodes = solver.solve()
  552. schema_nc.prepared = True
  553. for k,v in solved_nodes.items():
  554. self.solved_nodes[k]=v
  555. return solver
  556. def finalize(self, frame_nc):
  557. from .schema_definitions import (SchemaOutgoingConnection,)
  558. for i in range(len(self.held_links)):
  559. link = self.held_links.pop()
  560. to_np = link.to_socket.node; from_np = link.from_socket.node
  561. if isinstance(to_np, SchemaOutgoingConnection):
  562. if link.to_socket.name in self.outgoing_connections.keys():
  563. if (outgoing_links := self.outgoing_connections[link.to_socket.name]) is None: continue
  564. for outgoing in outgoing_links:
  565. if outgoing:
  566. to_node = outgoing.to_node
  567. from_node =frame_nc[(*self.autogen_path_names, from_np.name+self.index_str()) ]
  568. from_socket_name = link.from_socket.name
  569. if from_node.node_type in ['DUMMY_SCHEMA']:
  570. from_socket_name = link.from_socket.identifier
  571. connection = from_node.outputs[from_socket_name].connect(node=to_node, socket=outgoing.to_socket)
  572. # we need to kill the link between the Schema itself and the next node and update the deps. Otherwise:confusing bugs.
  573. outgoing.die(); init_dependencies(to_node)
  574. # else: # the node just isn't connected out this socket.
  575. # # solve all unsolved nested schemas...
  576. for schema_sig, schema_nc in self.nested_schemas.items():
  577. self.solve_nested_schema(schema_nc)
  578. for n in self.autogenerated_nodes.values():
  579. init_connections(n)
  580. for c in n.connections:
  581. init_dependencies(c)
  582. all_outgoing_links = []
  583. for conn in self.outgoing_connections.values():
  584. for outgoing in conn:
  585. all_outgoing_links.append(outgoing)
  586. for conn in self.constant_out.values():
  587. for outgoing in conn:
  588. all_outgoing_links.append(outgoing)
  589. for conn in self.array_output_connections.values():
  590. for outgoing in conn:
  591. all_outgoing_links.append(outgoing)
  592. for outgoing in all_outgoing_links:
  593. to_node = outgoing.to_node
  594. for l in to_node.inputs[outgoing.to_socket].links:
  595. if self.node == l.from_node:
  596. l.die()
  597. for inp in self.node.inputs.values():
  598. for l in inp.links:
  599. init_connections(l.from_node) # to force it to have hierarchy connections with the new nodes.
  600. def solve(self):
  601. if self.solve_length < 1:
  602. from .base_definitions import GraphError
  603. for o in self.node.outputs:
  604. if o.is_linked:
  605. raise GraphError(f"ERROR: Schema {self} has a length"
  606. " of 0 but other nodes depend on it.")
  607. print (f"WARN: Schema {self} has a length of 0 or less and will not expand.")
  608. return {} # just don't do anything - it's OK to have a noop schema if it doesn't have dependencies.
  609. for index in range(self.solve_length):
  610. self.index = index
  611. frame_mantis_nodes = self.solve_iteration()
  612. for sig, nc in frame_mantis_nodes.items():
  613. if nc.node_type == 'DUMMY_SCHEMA':
  614. self.nested_schemas[sig] = nc
  615. self.finalize(frame_mantis_nodes)
  616. self.node.solver = self
  617. self.node.prepared = True
  618. return self.solved_nodes