schema_solve.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  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
  6. from .utilities import class_for_mantis_prototype_node
  7. from .base_definitions import SchemaNode, replace_types, custom_props_types
  8. from .node_container_common import fill_parameters, setup_custom_props_from_np
  9. # a class that solves Schema nodes
  10. from uuid import uuid4
  11. # currently this is fairly ugly and a lot of cooupling but it is at least within the class
  12. # basically tho the idea is to make solving happen one iteration at time so that I can have nested structures
  13. # ultimately it will be much less messy, because I can simplify it
  14. # the initializer __init__ does all the necessary initialization
  15. # then solve_iteration should both solve the iteration and do all the cleanup
  16. # so the interface should be able to solve_iteration on this node until it is done
  17. # actually.. that wasn't necesary. Maybe the current solution is even... better?
  18. # I donno. Maybe doing one iteration at a time would be a better way to do things.
  19. # Now that it works, I can very easily write a second class and replace this one
  20. #example UUID and index
  21. #9e6df689-3d71-425e-be6c-fe768e7417ec.0000
  22. # def strip_uuid(signature):
  23. # # prOrange("strip uuid",signature)
  24. # ret_sig=[]
  25. # for name in signature:
  26. # if name is None:
  27. # ret_sig.append(None)
  28. # continue # this is normal, first element
  29. # ret_sig.append(strip_uuid_string(name))
  30. # return tuple(ret_sig)
  31. # def strip_uuid_string(string):
  32. # import re
  33. # split = re.split("\.[a-z,A-Z,0-9]{8}-[a-z,A-Z,0-9]{4}-[a-z,A-Z,0-9]{4}-[a-z,A-Z,0-9]{4}-[a-z,A-Z,0-9]{12}\.[0-9]{4}", string)
  34. # prRed(string, split)
  35. # return split[0]
  36. # to get this working with groups.... ensure that group node sockets and such are all working (names, identifiers...)
  37. # current error seems to occur only when schema are nested
  38. # error is probably caused by the lack of auto-get nodes for Schema Group inputs that are not connected
  39. # but it may simply be caused by bad routing
  40. # both bugs should be fixed (if it is two bugs and not one)
  41. class SchemaSolver:
  42. def __init__(self, schema_dummy, nodes, prototype, signature=None):
  43. self.all_nodes = nodes # this is the parsed tree from Mantis
  44. self.node = schema_dummy
  45. # ugly.. but we are getting the Schema node's prototype, then its node tree
  46. from .utilities import get_node_prototype
  47. self.tree = prototype.node_tree# get_node_prototype(self.node.signature, self.node.base_tree).node_tree
  48. self.uuid = self.node.uuid
  49. self.schema_nodes={}
  50. self.solved_nodes = {}
  51. # self.out_nodes = {}
  52. self.incoming_connections = {}
  53. self.outgoing_connections = {}
  54. self.constant_in = {}
  55. self.constant_out = {}
  56. self.array_input_connections = {}
  57. self.array_output_connections = {}
  58. # prGreen(self.node.signature[:-1])
  59. # This singature/natural_signature thing is so, so bad and stupid and wrong... but it works so I won't change it
  60. if signature:
  61. self.natural_signature=signature
  62. # print (signature)
  63. else:
  64. self.natural_signature=self.node.signature
  65. self.tree_path_names = [*self.node.signature[:-1]] # same tree as the schema node
  66. self.autogen_path_names = ['SCHEMA_AUTOGENERATED', *self.node.signature[1:-1]]
  67. self.index_link = self.node.inputs['Schema Length'].links[0]
  68. # TODO UNBREAK ME FIXME NOW
  69. # identifiers are unfortunately not somethign I can set or predict in the items_tree
  70. # but I can set them in the node.
  71. # I either need to set them properly in the node so they always match
  72. # or: I need to ignore the names and get the identifiers correctly
  73. # self.node is a NC
  74. # node_identifier_map_in = []
  75. # node_identifier_map_out = []
  76. for item in self.tree.interface.items_tree:
  77. if item.item_type == 'PANEL': continue
  78. if item.parent and item.parent.name == 'Connection':
  79. if item.in_out == 'INPUT':
  80. if incoming_links := self.node.inputs[item.identifier].links:
  81. self.incoming_connections[item.name] = incoming_links[0]
  82. else:
  83. self.incoming_connections[item.name] = None # it isn't linked
  84. # print (self.node)
  85. # prRed("candidates...", self.node.inputs)
  86. # for k,v in self.node.inputs.items():
  87. # print (k,v)
  88. # print(self.node.outputs)
  89. # print (self.node.parameters)
  90. # raise RuntimeError(f"Cannot find incoming connection \"{item.identifier}\" .")
  91. else:
  92. if outgoing_links := self.node.outputs[item.identifier].links:
  93. self.outgoing_connections[item.name] = outgoing_links.copy()
  94. else:
  95. self.outgoing_connections[item.name] = []
  96. if item.parent and item.parent.name == 'Constant':
  97. if item.in_out == 'INPUT':
  98. if constant_in_links := self.node.inputs[item.identifier].links:
  99. self.constant_in[item.name] = constant_in_links[0]
  100. else:
  101. self.constant_in[item.name] = None
  102. else:
  103. if constant_out_links := self.node.outputs[item.identifier].links:
  104. self.constant_out[item.name] = constant_out_links.copy()
  105. else:
  106. self.constant_out[item.name] = []
  107. if item.parent and item.parent.name == 'Array':
  108. if item.in_out == 'INPUT':
  109. if item.identifier not in self.array_input_connections.keys():
  110. self.array_input_connections[item.identifier]=[]
  111. if in_links := self.node.inputs[item.identifier].links:
  112. self.array_input_connections[item.identifier]=in_links.copy()
  113. if item.in_out == 'OUTPUT':
  114. if item.identifier not in self.array_output_connections.keys():
  115. self.array_output_connections[item.identifier]=[]
  116. if out_links := self.node.outputs[item.identifier].links:
  117. self.array_output_connections[item.identifier] = out_links.copy()
  118. self.held_links = []
  119. # just define them for now... we redefine them properly later when they are needed. THis is messy.
  120. self.index_str = lambda : '.'+str(self.uuid)+'.'+str(0).zfill(4)
  121. self.prev_index_str = lambda : '.'+str(self.uuid)+'.'+str(0-1).zfill(4)
  122. self.nested_schemas={}
  123. self.autogenerated_nodes = {} # this is a bad ugly HACK, but I think I need to mark these and deal with them later
  124. # Create the Schema Nodes
  125. # prGreen(self.tree_path_names)
  126. for n in self.tree.nodes:
  127. if isinstance(n, SchemaNode):
  128. # first we need to fill the parameters of the schema nodes.
  129. # the node is actually in the Schema group so we include the schema_dummy name
  130. # and we use the bl_idname because I think all schema nodes should be single-instance
  131. signature = (*self.tree_path_names, self.node.signature[-1], n.bl_idname)
  132. # get_sig = [*self.tree_path_names, strip_uuid_string(self.node.signature[-1]), n.bl_idname]
  133. # get_sig[0] = None; get_sig = tuple(get_sig) # this is so dumb haha
  134. get_sig = (*self.natural_signature, n.bl_idname)
  135. if not (nc := self.all_nodes.get(get_sig)): raise RuntimeError(wrapRed(f"Not found: {get_sig}"))
  136. self.schema_nodes[signature] = nc
  137. # nc.signature = signature # I don't really intend for this value to be mutable... but... HACK
  138. # there is no need to mutate this. also I may need to reuse it later
  139. fill_parameters(nc, n)
  140. # print (nc)
  141. def solve(self, schema_length):
  142. import time
  143. start_time = time.time()
  144. # from .schema_containers import SchemaIndex
  145. # for nc in self.schema_nodes.values():
  146. # if isinstance(nc, SchemaIndex):
  147. # #HACK? I thought for sure this was being done elsewhere...
  148. # nc.parameters["Schema Length"]=schema_length
  149. # prRed(nc.parameters)
  150. frame_nc={}
  151. for index in range(schema_length):
  152. frame_nc = self.solve_iteration(index, schema_length)
  153. for sig, nc in frame_nc.items():
  154. if nc.node_type == 'DUMMY_SCHEMA':
  155. self.nested_schemas[sig] = nc
  156. # prRed (self.array_output_connections)
  157. # for k,v in self.array_output_connections.items():
  158. # prRed(k,v)
  159. self.finalize(frame_nc)
  160. # prRed (self.array_output_connections)
  161. # for k,v in self.array_output_connections.items():
  162. # prRed(k,v)
  163. return self.solved_nodes
  164. def solve_nested_schema(self, schema_nc):
  165. if schema_nc.prepared == False:
  166. all_nodes = self.all_nodes.copy()
  167. # for k,v in self.solved_nodes.items():
  168. # all_nodes[k]=v
  169. # from .utilities import get_node_prototype
  170. np = schema_nc.prototype
  171. # for n in self.node.base_tree.nodes:
  172. # print (n.name)
  173. # print (schema_nc.signature[-1])
  174. from .schema_solve import SchemaSolver
  175. length = schema_nc.evaluate_input("Schema Length")
  176. tree = np.node_tree
  177. prOrange(f"Expanding schema {tree.name} in node {schema_nc} with length {length}.")
  178. solver = SchemaSolver(schema_nc, all_nodes, np, schema_nc.natural_signature)
  179. solved_nodes = solver.solve(length)
  180. schema_nc.prepared = True
  181. for k,v in solved_nodes.items():
  182. self.solved_nodes[k]=v
  183. def finalize(self, frame_nc):
  184. from .schema_definitions import (SchemaOutgoingConnection,)
  185. for i in range(len(self.held_links)):
  186. link = self.held_links.pop()
  187. to_np = link.to_socket.node; from_np = link.from_socket.node
  188. if isinstance(to_np, SchemaOutgoingConnection):
  189. if link.to_socket.name in self.outgoing_connections.keys():
  190. if (outgoing_links := self.outgoing_connections[link.to_socket.name]) is None: continue
  191. for outgoing in outgoing_links:
  192. if outgoing:
  193. to_node = outgoing.to_node
  194. from_node =frame_nc.get( (*self.autogen_path_names, from_np.name+self.index_str()) )
  195. connection = from_node.outputs[link.from_socket.name].connect(node=to_node, socket=outgoing.to_socket)
  196. # we need to kill the link between the Schema itself and the next node and update the deps. Otherwise:confusing bugs.
  197. outgoing.die(); init_dependencies(to_node)
  198. # else: # the node just isn't connected out this socket.
  199. # # solve all unsolved nested schemas...
  200. for schema_sig, schema_nc in self.nested_schemas.items():
  201. self.solve_nested_schema(schema_nc)
  202. for n in self.autogenerated_nodes.values():
  203. init_connections(n)
  204. for c in n.connections:
  205. init_dependencies(c)
  206. all_outgoing_links = []
  207. for conn in self.outgoing_connections.values():
  208. for outgoing in conn:
  209. all_outgoing_links.append(outgoing)
  210. for conn in self.constant_out.values():
  211. for outgoing in conn:
  212. all_outgoing_links.append(outgoing)
  213. for conn in self.array_output_connections.values():
  214. for outgoing in conn:
  215. all_outgoing_links.append(outgoing)
  216. for outgoing in all_outgoing_links:
  217. to_node = outgoing.to_node
  218. for l in to_node.inputs[outgoing.to_socket].links:
  219. other = l.to_node
  220. other_input = l.to_socket
  221. if self.node == l.from_node:
  222. l.die()
  223. for inp in self.node.inputs.values():
  224. for l in inp.links:
  225. init_connections(l.from_node) # to force it to have hierarchy connections with the new nodes.
  226. def solve_iteration(self, index, schema_length):
  227. from .schema_definitions import (SchemaIndex,
  228. SchemaArrayInput,
  229. SchemaArrayInputGet,
  230. SchemaArrayOutput,
  231. SchemaConstInput,
  232. SchemaConstOutput,
  233. SchemaOutgoingConnection,
  234. SchemaIncomingConnection,)
  235. from bpy.types import (NodeFrame)
  236. from .utilities import clear_reroutes
  237. from .utilities import get_link_in_out, link_node_containers
  238. from .node_container_common import DummyLink
  239. # if index_nc:
  240. # index_nc.parameters['Index']=index
  241. self.index_str = lambda : '.'+str(self.uuid)+'.'+str(index).zfill(4)
  242. # index_str = str(index).zfill(4)
  243. self.prev_index_str = lambda : '.'+str(self.uuid)+'.'+str(index-1).zfill(4)
  244. frame_nc = {}
  245. # At this point, GENERATE all the nodes for the frame
  246. for n in self.tree.nodes:
  247. if isinstance(n, SchemaNode) or isinstance(n, NodeFrame):
  248. continue
  249. if n.bl_idname in ['NodeReroute']:
  250. continue
  251. # this is the N.C. which is a prototype of the NC we actually want to make...
  252. signature = (*self.autogen_path_names, n.name+self.index_str())
  253. # proto_nc = self.all_nodes.get((*self.tree_path_names, self.node.signature[-1], n.name))
  254. proto_nc = self.all_nodes.get((*self.natural_signature, n.name))
  255. # this proto_nc was generated inside the schema when we parsed the tree.
  256. if not proto_nc:
  257. raise RuntimeError(f"Node not found: {(*self.tree_path_names, self.node.signature[-1], n.name)}")
  258. # for Schema sub-nodes ... they need a prototype to init.
  259. if proto_nc.node_type in ['DUMMY', 'DUMMY_SCHEMA']:
  260. from .utilities import get_node_prototype
  261. np = get_node_prototype(proto_nc.signature, proto_nc.base_tree)
  262. # assert np is not None
  263. if proto_nc.node_type == 'DUMMY_SCHEMA':
  264. nat_sig = (*self.node.signature, np.name)
  265. nc = proto_nc.__class__(signature, proto_nc.base_tree, prototype=np, natural_signature=nat_sig)
  266. else:
  267. nc = proto_nc.__class__(signature, proto_nc.base_tree, prototype=np)
  268. else:
  269. # try:
  270. nc = proto_nc.__class__(signature, proto_nc.base_tree)
  271. # except AttributeError:
  272. # from .utilities import get_node_prototype
  273. # np = get_node_prototype(proto_nc.signature, proto_nc.base_tree)
  274. # nc = proto_nc.__class__(signature, proto_nc.base_tree, prototype=np)
  275. frame_nc[nc.signature] = nc
  276. #
  277. if nc.__class__.__name__ in custom_props_types:
  278. setup_custom_props_from_np(nc, n)
  279. fill_parameters(nc, n) # this is the best place to do this..
  280. # This is where we handle node connections BETWEEN frames
  281. for i in range(len(self.held_links)):
  282. link = self.held_links.pop()
  283. to_np = link.to_socket.node; from_np = link.from_socket.node
  284. if isinstance(to_np, SchemaOutgoingConnection):
  285. # incoming connection tells us where to take this.
  286. incoming_node = self.schema_nodes[*self.tree_path_names, self.node.signature[-1], 'SchemaIncomingConnection']
  287. for l in incoming_node.outputs[link.to_socket.name].links:
  288. to_node, to_socket = l.to_node, l.to_socket
  289. from_name = get_link_in_out(link)[0]
  290. from_node = self.solved_nodes.get( (*self.autogen_path_names, from_name+self.prev_index_str()) )
  291. #
  292. to_node = frame_nc.get( (*self.autogen_path_names, to_node.signature[-1]+self.index_str()) )
  293. # the to_node and to_socket don't really matter, the incoming connection will handle this part
  294. connection = from_node.outputs[link.from_socket.name].connect(node=to_node, socket=to_socket)
  295. if existing_link := self.incoming_connections[link.to_socket.name]:
  296. # if index == 0:
  297. if self.node.signature[-1] in existing_link.to_node.signature:
  298. # not sure this is helping
  299. existing_link.die()
  300. # could be better to make it a dummy link?
  301. self.incoming_connections[link.to_socket.name] = connection
  302. links = clear_reroutes(list(self.tree.links))
  303. # frame_nc_with_schema = frame_nc.copy()
  304. # for k,v in self.schema_nodes.items(): frame_nc_with_schema[k]=v
  305. # Now we handle links in the current frame, including those links between Schema nodes and "real" nodes
  306. # this gets really complicated :\
  307. awaiting_prep_stage = []
  308. for link in links:
  309. # at THIS POINT I should make a buncha dummy links to deal
  310. # with the schema node connections...
  311. to_np = link.to_socket.node; from_np = link.from_socket.node
  312. if isinstance(to_np, SchemaConstOutput) or isinstance(to_np, SchemaArrayOutput) or \
  313. isinstance(from_np, SchemaArrayInputGet):# or isinstance(from_np, SchemaArrayInput):
  314. awaiting_prep_stage.append(link)
  315. continue
  316. if isinstance(from_np, SchemaIndex):
  317. if link.from_socket.name == "Index":
  318. _from_name, to_name = get_link_in_out(link)
  319. to_node = frame_nc.get( (*self.autogen_path_names, to_name+self.index_str()) )
  320. if to_node.node_type in ['DUMMY', 'DUMMY_SCHEMA']:
  321. # prRed("This is causing the problem, right?")
  322. from .utilities import gen_nc_input_for_data
  323. nc_cls = gen_nc_input_for_data(link.from_socket)
  324. if (nc_cls): #HACK
  325. sig = ("MANTIS_AUTOGENERATED", *self.tree_path_names[1:], self.index_str(), link.from_socket.name, link.from_socket.identifier)
  326. nc_from = nc_cls(sig, self.node.base_tree)
  327. # ugly! maybe even a HACK!
  328. nc_from.inputs = {}
  329. from .node_container_common import NodeSocket
  330. nc_from.outputs = {link.from_socket.name:NodeSocket(name = link.from_socket.name, node=nc_from)}
  331. from .node_container_common import get_socket_value
  332. nc_from.parameters = {link.from_socket.name:index}
  333. frame_nc[sig]=nc_from
  334. from_node = nc_from
  335. # self.autogenerated_nodes[sig]=from_node
  336. self.solved_nodes[sig]=from_node
  337. connection = from_node.outputs[link.from_socket.name].connect(node=to_node, socket=link.to_socket.identifier)
  338. # prGreen(connection)
  339. continue
  340. # this actually seems to work. I could use Autogen nodes
  341. # but maybe this is actually better since it results in fewer nodes.
  342. # if this never causes any problems, then I will do it in other places
  343. # HACK Here Be Danger HACK
  344. to_node.parameters[link.to_socket.name] = index
  345. del to_node.inputs[link.to_socket.name]
  346. # so I have tried to implement this as connections and actually this is the best way because otherwise I have
  347. # to do something very similar on the input node.
  348. # HACK
  349. elif link.from_socket.name == "Schema Length":
  350. # # see, here I can just use the schema node
  351. _from_name, to_name = get_link_in_out(link)
  352. to_node = frame_nc.get( (*self.autogen_path_names, to_name+self.index_str()) )
  353. # this self.index_link is only used here?
  354. if (self.index_link.from_node):
  355. connection = self.index_link.from_node.outputs[self.index_link.from_socket].connect(node=to_node, socket=link.to_socket.name)
  356. # otherwise we can autogen an int input I guess...?
  357. else:
  358. raise RuntimeError("I was expecting there to be an incoming connection here for Schema Length")
  359. continue
  360. if isinstance(from_np, SchemaIncomingConnection):
  361. if link.from_socket.name in self.incoming_connections.keys():
  362. incoming = self.incoming_connections[link.from_socket.name]
  363. # if incoming is None:
  364. # print (link.from_socket.name)
  365. from_node = incoming.from_node
  366. _from_name, to_name = get_link_in_out(link)
  367. to_node = frame_nc.get( (*self.autogen_path_names, to_name+self.index_str()) )
  368. # try:
  369. connection = from_node.outputs[incoming.from_socket].connect(node=to_node, socket=link.to_socket.name)
  370. # prGreen(connection)
  371. # except KeyError as e:
  372. # prRed(f"busted: {from_node}:{incoming.from_socket} --> {to_node}:{link.to_socket.name},{link.to_socket.identifier}")
  373. # for output in from_node.outputs:
  374. # prOrange(output)
  375. # for sinput in from_node.inputs:
  376. # prPurple(sinput)
  377. # raise e
  378. init_connections(from_node)
  379. continue
  380. if isinstance(to_np, SchemaOutgoingConnection):
  381. self.held_links.append(link)
  382. continue
  383. if isinstance(from_np, SchemaConstInput):
  384. if link.from_socket.name in self.constant_in.keys():
  385. incoming = self.constant_in[link.from_socket.name]
  386. from_node = incoming.from_node
  387. to_name = get_link_in_out(link)[1]
  388. to_node = frame_nc.get( (*self.autogen_path_names, to_name+self.index_str()) )
  389. # print(from_node, incoming.from_socket, "==>", to_node, link.to_socket.identifier)
  390. # print (to_node.inputs)
  391. # for k,v in from_node.outputs.items():
  392. # print (k,v)
  393. # print (from_node.outputs[incoming.from_socket])
  394. to_socket=link.to_socket.name
  395. from .base_definitions import SchemaGroup
  396. if isinstance(to_np, SchemaGroup):
  397. to_socket=link.to_socket.identifier
  398. connection = from_node.outputs[incoming.from_socket].connect(node=to_node, socket=to_socket)
  399. init_connections(from_node)
  400. continue
  401. if isinstance(to_np, SchemaArrayInputGet):
  402. from_name, to_name = get_link_in_out(link)
  403. from_nc = frame_nc.get( (*self.autogen_path_names, from_name+self.index_str()))
  404. to_nc = self.schema_nodes.get((*self.tree_path_names, self.node.signature[-1], to_name))
  405. # this only needs to be done once.
  406. if index == 0:
  407. old_nc = self.all_nodes.get((*self.tree_path_names, self.node.signature[-1], from_name))
  408. # I am not sure about this!
  409. existing_link = old_nc.outputs[link.from_socket.name].links[0]
  410. existing_link.die()
  411. #
  412. connection = from_nc.outputs[link.from_socket.name].connect(node=to_nc, socket=link.to_socket.name)
  413. continue
  414. if isinstance(from_np, SchemaArrayInput):
  415. get_index = index
  416. try:
  417. incoming = self.array_input_connections[link.from_socket.identifier][get_index]
  418. except IndexError:
  419. if len(self.array_input_connections[link.from_socket.identifier]) > 0:
  420. incoming = self.array_input_connections[link.from_socket.identifier][0]
  421. # prOrange(incoming.from_node.node_type)
  422. if incoming.from_node.node_type not in ['DUMMY_SCHEMA']:
  423. raise RuntimeError(wrapRed("You need to make it so Mantis checks if there are enough Array inputs."))
  424. else: # do nothing
  425. continue
  426. else:
  427. raise RuntimeError(wrapRed("make it so Mantis checks if there are enough Array inputs!"))
  428. to_name = get_link_in_out(link)[1]
  429. to_node = frame_nc.get((*self.autogen_path_names, to_name+self.index_str()))
  430. connection = incoming.from_node.outputs[incoming.from_socket].connect(node=to_node, socket=link.to_socket.name)
  431. init_connections(incoming.from_node)
  432. continue
  433. link_path_names = self.tree_path_names[1:]
  434. # use this line to debug, usually I don't need it
  435. connection = link_node_containers(self.autogen_path_names, link, frame_nc, from_suffix=self.index_str(), to_suffix=self.index_str())
  436. for k,v in frame_nc.items():
  437. self.solved_nodes[k]=v
  438. init_dependencies(v) # it is hard to overstate how important this single line of code is
  439. # for node in v.hierarchy_dependencies:
  440. # init_connections(node)
  441. # done with the link handling.
  442. # Start Section: This is the place where we solve dependencies and continue
  443. # we need to sort the nodes in solved_nc and prepare whatever can be preepared.
  444. from collections import deque
  445. unprepared= deque()
  446. for nc in frame_nc.values():
  447. if nc.prepared == False:
  448. unprepared.append(nc)
  449. # this is potentially a little inneficient but it isn't a big deal.
  450. # since the only extra preparations I am doing is nodes that don't yet have outgoing connections
  451. # but I may add them in the next step anyways, then I need to prepare them! so this is simpler.
  452. # at this point it should be possible to sort unprepared to avoid the while loop
  453. # but I don't really care. this will work since we have guarenteed solved all the
  454. # schema_dummy's dependencies.... I hope. lol.
  455. while unprepared: # DANGER.
  456. # raise NotImplementedError
  457. # prRed("this part!")
  458. nc = unprepared.pop()
  459. # print(nc)
  460. if sum([dep.prepared for dep in nc.hierarchy_dependencies]) == len(nc.hierarchy_dependencies):
  461. nc.bPrepare()
  462. if nc.node_type == 'DUMMY_SCHEMA':
  463. self.solve_nested_schema(nc)
  464. else:
  465. # print (sum([dep.prepared for dep in nc.hierarchy_dependencies]), len(nc.hierarchy_dependencies))
  466. # for dep in nc.hierarchy_dependencies:
  467. # if not dep.prepared:
  468. # prOrange(dep)
  469. unprepared.appendleft(nc)
  470. for i in range(len(awaiting_prep_stage)): #why is this a for loop and not a while loop?? # FIXME?
  471. link = awaiting_prep_stage.pop()
  472. to_np = link.to_socket.node; from_np = link.from_socket.node
  473. if isinstance(to_np, SchemaConstOutput):
  474. to_node = self.schema_nodes.get((*self.tree_path_names, self.node.signature[-1], to_np.bl_idname))
  475. expose_when = to_node.evaluate_input('Expose when N==')
  476. if index == expose_when:
  477. for outgoing in self.constant_out[link.to_socket.name]:
  478. to_node = outgoing.to_node
  479. from_name = get_link_in_out(link)[0]
  480. from_node = frame_nc.get( (*self.autogen_path_names, from_name+self.index_str()) )
  481. connection = from_node.outputs[link.from_socket.name].connect(node=to_node, socket=outgoing.to_socket)
  482. if isinstance(to_np, SchemaArrayOutput): # if this duplicated code works, dedupe!
  483. to_node = self.schema_nodes.get((*self.tree_path_names, self.node.signature[-1], to_np.bl_idname))
  484. for outgoing in self.array_output_connections[link.to_socket.identifier]:
  485. # print (type(outgoing))
  486. from .schema_containers import SchemaIndex
  487. from_name = get_link_in_out(link)[0]
  488. from_node = frame_nc.get( (*self.autogen_path_names, from_name+self.index_str()) )
  489. if not from_node:
  490. from_node = self.schema_nodes.get( (*self.tree_path_names, self.node.signature[-1], from_np.bl_idname) )
  491. if not from_node:
  492. raise RuntimeError()
  493. to_node = outgoing.to_node
  494. if isinstance(from_node, SchemaIndex): # I think I need to dedup this stuff
  495. # print("INDEX")
  496. from .utilities import gen_nc_input_for_data
  497. nc_cls = gen_nc_input_for_data(link.from_socket)
  498. if (nc_cls): #HACK
  499. sig = ("MANTIS_AUTOGENERATED", *self.tree_path_names[1:], self.index_str(), link.from_socket.name, link.from_socket.identifier)
  500. nc_from = nc_cls(sig, self.node.base_tree)
  501. # ugly! maybe even a HACK!
  502. nc_from.inputs = {}
  503. from .node_container_common import NodeSocket
  504. nc_from.outputs = {link.from_socket.name:NodeSocket(name = link.from_socket.name, node=nc_from)}
  505. from .node_container_common import get_socket_value
  506. if link.from_socket.name in ['Index']:
  507. nc_from.parameters = {link.from_socket.name:index}
  508. else:
  509. nc_from.parameters = {link.from_socket.name:schema_length}
  510. frame_nc[sig]=nc_from
  511. from_node = nc_from
  512. self.solved_nodes[sig]=from_node
  513. # I have a feeling that something bad will happen if both of these conditions (above and below) are true
  514. if to_node.node_type == 'DUMMY_SCHEMA' and to_node.prepared:
  515. other_stem = ('SCHEMA_AUTOGENERATED', *to_node.signature[1:-1])
  516. from .utilities import get_node_prototype
  517. other_schema_np = get_node_prototype(to_node.signature, to_node.base_tree)
  518. other_schema_tree = other_schema_np.node_tree
  519. for n in other_schema_tree.nodes:
  520. if n.bl_idname not in ["SchemaArrayInput", "SchemaArrayInputGet"]:
  521. continue
  522. out = n.outputs[outgoing.to_socket]
  523. for l in out.links:
  524. other_index_str = lambda : '.'+str(to_node.uuid)+'.'+str(index).zfill(4)
  525. out_node = self.all_nodes.get((*other_stem, l.to_node.name+other_index_str()))
  526. connection = from_node.outputs[link.from_socket.name].connect(node=out_node, socket=l.to_socket.name)
  527. else:
  528. connection = from_node.outputs[link.from_socket.name].connect(node=to_node, socket=outgoing.to_socket)
  529. if isinstance(from_np, SchemaArrayInputGet): # or isinstance(from_np, SchemaArrayInput) or
  530. get_index = index
  531. if isinstance(from_np, SchemaArrayInputGet):
  532. from_node = self.schema_nodes.get((*self.tree_path_names, self.node.signature[-1], from_np.bl_idname))
  533. from .utilities import cap, wrap
  534. get_index = from_node.evaluate_input("Index", index)
  535. oob = from_node.evaluate_input("OoB Behaviour")
  536. # we must assume that the array has sent the correct number of links
  537. if oob == 'WRAP':
  538. get_index = wrap(get_index, len(self.array_input_connections[link.from_socket.identifier])-1, 0)
  539. if oob == 'HOLD':
  540. get_index = cap(get_index, len(self.array_input_connections[link.from_socket.identifier])-1)
  541. try:
  542. incoming = self.array_input_connections[link.from_socket.identifier][get_index]
  543. except IndexError:
  544. 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!"))
  545. to_name = get_link_in_out(link)[1]
  546. to_node = frame_nc.get((*self.autogen_path_names, to_name+self.index_str()))
  547. connection = incoming.from_node.outputs[incoming.from_socket].connect(node=to_node, socket=link.to_socket.name)
  548. init_connections(incoming.from_node)
  549. # end seciton
  550. return frame_nc
  551. # TODO: figure out why the tree is sorting wrong when using arrays!
  552. # despite a lot of ugly hacks, things are going well
  553. # current TODO:
  554. # - get nested schema working when nodes in the parent tree depend on them
  555. # - eventually I can clean all this up by re-designing the solver code to solve one iteration at a time instead of all at once
  556. # - get schema-in-group working
  557. # - groups need constants and arrays
  558. # maybe the simplest solution is for groups to be schema always
  559. # but I don't want to do that
  560. # anyways the next milestone is for the spine to be a group or schema output
  561. # note that schemas will send out arrays and such. So I don't necessarily want groups to have array in/out
  562. # instead, groups are simple
  563. # they can contain schema, but schema can't have arrays from the group interface
  564. # groups are good for large, abstract components
  565. # fundamentally they are similar but groups are cleaner since they don't require dependency solving
  566. # anyways... schema with nesting and groups is good enough for now
  567. # I don't need it to be stable. I just need to rig my human model with Mantis and iterate from there. Getting it to rig a human character will find and fix most bugs.