schema_solve.py 47 KB

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