readtree_compare.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. from .utilities import prRed, prGreen, prPurple, prWhite, prOrange, \
  2. wrapRed, wrapGreen, wrapPurple, wrapWhite, wrapOrange
  3. # what in the cuss is this horrible abomination??
  4. def class_for_mantis_prototype_node(prototype_node):
  5. """ This is a class which returns a class to instantiate for
  6. the given prototype node."""
  7. #from .node_container_classes import TellClasses
  8. from mantis import xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers
  9. classes = {}
  10. for module in [xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers]:
  11. for cls in module.TellClasses():
  12. classes[cls.__name__] = cls
  13. # I could probably do a string.replace() here
  14. # But I actually think this is a bad idea since I might not
  15. # want to use this name convention in the future
  16. # this is easy enough for now, may refactor.
  17. #
  18. # kek, turns out it was completely friggin' inconsistent already
  19. if prototype_node.bl_idname == 'xFormRootNode':
  20. return classes["xFormRoot"]
  21. elif prototype_node.bl_idname == 'xFormArmatureNode':
  22. return classes["xFormArmature"]
  23. elif prototype_node.bl_idname == 'xFormBoneNode':
  24. return classes["xFormBone"]
  25. elif prototype_node.bl_idname == 'xFormGeometryObject':
  26. return classes["xFormGeometryObject"]
  27. elif prototype_node.bl_idname == 'linkInherit':
  28. return classes["LinkInherit"]
  29. # BAD need to fix the above, bl_idname is not consistent
  30. # Copy's
  31. elif prototype_node.bl_idname == 'LinkCopyLocation':
  32. return classes["LinkCopyLocation"] # also bad
  33. elif prototype_node.bl_idname == 'LinkCopyRotation':
  34. return classes["LinkCopyRotation"]
  35. elif prototype_node.bl_idname == 'LinkCopyScale':
  36. return classes["LinkCopyScale"]
  37. elif prototype_node.bl_idname == 'LinkCopyTransforms':
  38. return classes["LinkCopyTransforms"]
  39. # Limits
  40. elif prototype_node.bl_idname == 'LinkLimitLocation':
  41. return classes["LinkLimitLocation"]
  42. elif prototype_node.bl_idname == 'LinkLimitRotation':
  43. return classes["LinkLimitRotation"]
  44. elif prototype_node.bl_idname == 'LinkLimitScale':
  45. return classes["LinkLimitScale"]
  46. elif prototype_node.bl_idname == 'LinkLimitDistance':
  47. return classes["LinkLimitDistance"]
  48. # tracking
  49. elif prototype_node.bl_idname == 'LinkStretchTo':
  50. return classes["LinkStretchTo"]
  51. elif prototype_node.bl_idname == 'LinkDampedTrack':
  52. return classes["LinkDampedTrack"]
  53. elif prototype_node.bl_idname == 'LinkLockedTrack':
  54. return classes["LinkLockedTrack"]
  55. elif prototype_node.bl_idname == 'LinkTrackTo':
  56. return classes["LinkTrackTo"]
  57. # misc
  58. elif prototype_node.bl_idname == 'LinkInheritConstraint':
  59. return classes["LinkInheritConstraint"]
  60. # IK
  61. elif prototype_node.bl_idname == 'LinkInverseKinematics':
  62. return classes["LinkInverseKinematics"]
  63. elif prototype_node.bl_idname == 'LinkSplineIK':
  64. return classes["LinkSplineIK"]
  65. # utilities
  66. elif prototype_node.bl_idname == 'InputFloatNode':
  67. return classes["InputFloat"]
  68. elif prototype_node.bl_idname == 'InputVectorNode':
  69. return classes["InputVector"]
  70. elif prototype_node.bl_idname == 'InputBooleanNode':
  71. return classes["InputBoolean"]
  72. elif prototype_node.bl_idname == 'InputBooleanThreeTupleNode':
  73. return classes["InputBooleanThreeTuple"]
  74. elif prototype_node.bl_idname == 'InputRotationOrderNode':
  75. return classes["InputRotationOrder"]
  76. elif prototype_node.bl_idname == 'InputTransformSpaceNode':
  77. return classes["InputTransformSpace"]
  78. elif prototype_node.bl_idname == 'InputStringNode':
  79. return classes["InputString"]
  80. elif prototype_node.bl_idname == 'InputQuaternionNode':
  81. return classes["InputQuaternion"]
  82. elif prototype_node.bl_idname == 'InputQuaternionNodeAA':
  83. return classes["InputQuaternionAA"]
  84. elif prototype_node.bl_idname == 'InputMatrixNode':
  85. return classes["InputMatrix"]
  86. elif prototype_node.bl_idname == 'MetaRigMatrixNode':
  87. return classes["InputMatrix"]
  88. elif prototype_node.bl_idname == 'InputLayerMaskNode':
  89. return classes["InputLayerMask"]
  90. # geometry
  91. elif prototype_node.bl_idname == 'GeometryCirclePrimitive':
  92. return classes["CirclePrimitive"]
  93. # Deformers:
  94. elif prototype_node.bl_idname == 'DeformerArmature':
  95. return classes["DeformerArmature"]
  96. # every node before this point is not guarenteed to follow the pattern
  97. # but every node not checked above does follow the pattern.
  98. try:
  99. return classes[ prototype_node.bl_idname ]
  100. except KeyError:
  101. pass
  102. if prototype_node.bl_idname in [
  103. "NodeReroute",
  104. "NodeGroupInput",
  105. "NodeGroupOutput",
  106. "MantisNodeGroup",
  107. "NodeFrame",
  108. ]:
  109. return None
  110. prRed(prototype_node.bl_idname)
  111. raise RuntimeError("Failed to create node container for: %s" % prototype_node.bl_idname)
  112. return None
  113. # This is really, really stupid HACK
  114. def gen_nc_input_for_data(socket):
  115. # Class List #TODO deduplicate
  116. from mantis import xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers
  117. classes = {}
  118. for module in [xForm_containers, link_containers, misc_containers, primitives_containers, deformer_containers]:
  119. for cls in module.TellClasses():
  120. classes[cls.__name__] = cls
  121. #
  122. socket_class_map = {
  123. "MatrixSocket" : classes["InputMatrix"],
  124. "xFormSocket" : None,
  125. "xFormMultiSocket" : None,
  126. "RelationshipSocket" : classes["xFormRoot"], # world in
  127. "DeformerSocket" : classes["xFormRoot"], # world in
  128. "GeometrySocket" : classes["InputExistingGeometryData"],
  129. "EnableSocket" : classes["InputBoolean"],
  130. "HideSocket" : classes["InputBoolean"],
  131. #
  132. "DriverSocket" : None,
  133. "DriverVariableSocket" : None,
  134. "FCurveSocket" : None,
  135. "KeyframeSocket" : None,
  136. "LayerMaskInputSocket" : classes["InputLayerMask"],
  137. "LayerMaskSocket" : classes["InputLayerMask"],
  138. #
  139. "xFormParameterSocket" : None,
  140. "ParameterBoolSocket" : classes["InputBoolean"],
  141. "ParameterIntSocket" : classes["InputFloat"], #TODO: make an Int node for this
  142. "ParameterFloatSocket" : classes["InputFloat"],
  143. "ParameterVectorSocket" : classes["InputVector"],
  144. "ParameterStringSocket" : classes["InputString"],
  145. #
  146. "TransformSpaceSocket" : classes["InputTransformSpace"],
  147. "BooleanSocket" : classes["InputBoolean"],
  148. "BooleanThreeTupleSocket" : classes["InputBooleanThreeTuple"],
  149. "RotationOrderSocket" : classes["InputRotationOrder"],
  150. "QuaternionSocket" : classes["InputQuaternion"],
  151. "QuaternionSocketAA" : classes["InputQuaternionAA"],
  152. "IntSocket" : classes["InputFloat"],
  153. "StringSocket" : classes["InputString"],
  154. #
  155. "BoolUpdateParentNode" : classes["InputBoolean"],
  156. "IKChainLengthSocket" : classes["InputFloat"],
  157. "EnumInheritScale" : classes["InputString"],
  158. "EnumRotationMix" : classes["InputString"],
  159. "EnumRotationMixCopyTransforms" : classes["InputString"],
  160. "EnumMaintainVolumeStretchTo" : classes["InputString"],
  161. "EnumRotationStretchTo" : classes["InputString"],
  162. "EnumTrackAxis" : classes["InputString"],
  163. "EnumUpAxis" : classes["InputString"],
  164. "EnumLockAxis" : classes["InputString"],
  165. "EnumLimitMode" : classes["InputString"],
  166. "EnumYScaleMode" : classes["InputString"],
  167. "EnumXZScaleMode" : classes["InputString"],
  168. # Deformers
  169. "EnumSkinning" : classes["InputString"],
  170. #
  171. "FloatSocket" : classes["InputFloat"],
  172. "FloatFactorSocket" : classes["InputFloat"],
  173. "FloatPositiveSocket" : classes["InputFloat"],
  174. "FloatAngleSocket" : classes["InputFloat"],
  175. "VectorSocket" : classes["InputVector"],
  176. "VectorEulerSocket" : classes["InputVector"],
  177. "VectorTranslationSocket" : classes["InputVector"],
  178. "VectorScaleSocket" : classes["InputVector"],
  179. # Drivers
  180. "EnumDriverVariableType" : classes["InputString"],
  181. "EnumDriverVariableEvaluationSpace" : classes["InputString"],
  182. "EnumDriverRotationMode" : classes["InputString"],
  183. "EnumDriverType" : classes["InputString"],
  184. }
  185. return socket_class_map.get(socket.bl_idname, None)
  186. class DummyLink:
  187. #gonna use this for faking links to keep the interface consistent
  188. def __init__(self, from_socket, to_socket, nc_from=None, nc_to=None, original_from=None):
  189. self.from_socket = from_socket
  190. self.to_socket = to_socket
  191. self.nc_from = nc_from
  192. self.nc_to = nc_to
  193. if (original_from):
  194. self.original_from = original_from
  195. else:
  196. self.original_from = self.from_socket
  197. def __repr__(self):
  198. return(self.nc_from.__repr__()+":"+self.from_socket.name + " -> " + self.nc_to.__repr__()+":"+self.to_socket.name)
  199. # We'll treat this as a "dangling" link if either socket is unset...
  200. # # May or may not use this for my Dummy Links to help me connect things
  201. # # I think I can avoid it tho
  202. # class DummyNode:
  203. # def __init__(self, signature, base_tree, prototype):
  204. # self.signature = signature
  205. # self.base_tree = base_tree
  206. # self.prototype = prototype
  207. # This really might be useful in sorting the tree, too.
  208. def socket_seek(start_link, links):
  209. link = start_link
  210. while(link.from_socket):
  211. for newlink in links:
  212. if link.from_socket.node.inputs:
  213. if newlink.to_socket == link.from_socket.node.inputs[0]:
  214. link=newlink; break
  215. else:
  216. break
  217. return link.from_socket
  218. def clear_reroutes(links):
  219. kept_links, rerouted_starts = [], []
  220. rerouted = []
  221. all_links = links.copy()
  222. while(all_links):
  223. link = all_links.pop()
  224. to_cls = link.to_socket.node.bl_idname
  225. from_cls = link.from_socket.node.bl_idname
  226. reroute_classes = ["NodeReroute"]
  227. if (to_cls in reroute_classes and
  228. from_cls in reroute_classes):
  229. rerouted.append(link)
  230. elif (to_cls in reroute_classes and not
  231. from_cls in reroute_classes):
  232. rerouted.append(link)
  233. elif (from_cls in reroute_classes and not
  234. to_cls in reroute_classes):
  235. rerouted_starts.append(link)
  236. else:
  237. kept_links.append(link)
  238. for start in rerouted_starts:
  239. from_socket = socket_seek(start, rerouted)
  240. new_link = DummyLink(from_socket=from_socket, to_socket=start.to_socket, nc_from=None, nc_to=None)
  241. kept_links.append(new_link)
  242. return kept_links
  243. def data_from_tree(base_tree, tree_path = [None], held_links = {}, all_nc = {}):
  244. # prGreen("Starting! Base Tree: %s, held nodes: %d, held links: %d, nc's: %d" % (base_tree.name, len(held_nodes), len(held_links), len(all_nc)))
  245. # prPurple(tree_path)
  246. nc_dict = {}
  247. tree_path_names = [tree.name for tree in tree_path if hasattr(tree, "name")]
  248. all_child_ng = []
  249. tree = base_tree
  250. if tree_path[-1]:
  251. tree = tree_path[-1].node_tree
  252. # Start by looking through the nodes and making nc's where possible
  253. # store the groups, we'll pricess them soon.
  254. for np in tree.nodes:
  255. if (nc_cls := class_for_mantis_prototype_node(np)):
  256. nc = nc_cls( sig := tuple([None] + tree_path_names + [np.name]) , base_tree)
  257. nc_dict[sig] = nc; all_nc[sig] = nc
  258. if hasattr(np, "node_tree"):
  259. all_child_ng.append(np)
  260. # Then deal with the links in the current tree and the held_links.
  261. kept_links, incoming, outgoing = [], [], []
  262. all_links = clear_reroutes(list(tree.links))
  263. while(all_links):
  264. link = all_links.pop()
  265. to_cls = link.to_socket.node.bl_idname
  266. from_cls = link.from_socket.node.bl_idname
  267. if (from_cls in ["NodeGroupInput"]):
  268. incoming.append(link)
  269. elif (to_cls in ["NodeGroupOutput"]):
  270. nc_from = nc_dict.get( tuple([None]+tree_path_names+[link.from_socket.node.name]) )
  271. to_s = link.to_socket.identifier
  272. # Let's try and connect it now; go UP:
  273. nc_to, new_link = None, None
  274. if len(tree_path)==1:
  275. prRed("Warning: There is a GroupOutput node in the Base Tree.")
  276. kept_links.append(link)
  277. continue
  278. elif tree_path[-2] is None:
  279. up_tree=base_tree
  280. else:
  281. up_tree=tree_path[-2].node_tree
  282. for up_node in up_tree.nodes:
  283. for out in up_node.outputs:
  284. if not hasattr(up_node, "node_tree"):
  285. continue
  286. if not out.is_linked:
  287. continue
  288. for up_link in out.links:
  289. if up_link.from_socket.identifier == to_s:
  290. new_link = DummyLink(from_socket=up_link.from_socket, to_socket=up_link.to_socket, nc_from=nc_from, nc_to=None)
  291. link_sig = tuple([None]+tree_path_names[:-1]+[up_link.to_socket.node.name, link.from_socket.name])
  292. held_links[link_sig] = new_link
  293. nc_from, nc_to = None, None # clear them
  294. elif (from_cls in ["MantisNodeGroup"]):
  295. outgoing.append(link)
  296. else:
  297. kept_links.append(link)
  298. # Make the connections:
  299. for link in kept_links:
  300. nc_from = nc_dict.get( tuple([None] + tree_path_names + [link.from_socket.node.name]) )
  301. nc_to = nc_dict.get( tuple([None] + tree_path_names + [link.to_socket.node.name]) )
  302. if (nc_from and nc_to):
  303. from_s, to_s = link.from_socket.name, link.to_socket.name
  304. connection = nc_from.outputs[from_s].connect(node=nc_to, socket=to_s)
  305. nc_from = None; nc_to = None #clear them, since we use the same variable names again
  306. # At this point we're also gonna deal with held links and held nodes
  307. hold_further = {}
  308. del_me = set() # I donno why but there can be dupes.
  309. for link_sig, held in held_links.items():
  310. found, connected = False, False
  311. nc_from, from_s = held.nc_from, held.original_from.name
  312. watch = (nc_from.signature[-1] == 'Parent') and (tree_path_names[-1] in ["right", "left"])
  313. for link in incoming:
  314. if watch:
  315. prWhite(link_sig)
  316. if ((link.from_socket.identifier != link_sig[-1]) or
  317. (link.from_socket.name != link_sig[-2])):
  318. continue # This ain't it
  319. if (link.from_socket.identifier == link_sig[-1]):
  320. if (link.to_socket.node.bl_idname in [ "MantisNodeGroup" ]):
  321. del_me.add(link_sig)
  322. link_sig = tuple(list(link_sig[:-2]) + [link.to_socket.name, link.to_socket.identifier])
  323. hold_further[link_sig] = DummyLink(from_socket = held.from_socket, to_socket = link.to_socket, nc_from=nc_from, nc_to = None, original_from=held.original_from)
  324. prGreen("Holding further %s" % held.original_from.name)
  325. continue # just continue to hold it
  326. found = True
  327. # TO-Node:
  328. to_s = link.to_socket.name
  329. sig_to = tuple([None] + tree_path_names + [link.to_socket.node.name])
  330. nc_to = nc_dict.get( sig_to )
  331. if (nc_to and nc_from):
  332. if watch:
  333. prGreen("Connecting: %s%s -> %s%s" % (nc_from, from_s, nc_to, to_s) )
  334. connection = nc_from.outputs[from_s].connect(node=nc_to, socket=to_s)
  335. connected = True
  336. elif watch:
  337. prRed("Not Connecting: %s%s -> %s%s" % (nc_from, from_s, nc_to, to_s) )
  338. if (connected) != found:
  339. print(wrapRed("Not Connected: ") ,link_sig, held)
  340. # it's fairly annoying that I can't do this while I go.
  341. # for k in del_me:
  342. # del held_links[k]
  343. for k,v in hold_further.items():
  344. held_links[k] = v
  345. for ng in all_child_ng:
  346. for inp in ng.inputs:
  347. if not inp.is_linked:
  348. nc_cls = gen_nc_input_for_data(inp)
  349. if (nc_cls):
  350. sig = ("MANTIS_AUTOGENERATED", *tree_path_names, inp.node.name, inp.identifier)
  351. nc = nc_cls(sig, tree)
  352. # HACK HACK HACK
  353. for k, v in nc.outputs.items():
  354. v.name = inp.name; break
  355. from mantis.node_container_common import NodeSocket
  356. nc.outputs[inp.name] = NodeSocket(name = inp.name, node=nc)
  357. # del nc.outputs[k]; del nc.parameters[k]
  358. nc.parameters[inp.name]=inp.default_value
  359. # HACK HACK HACK
  360. nc_dict[sig] = nc; all_nc[sig] = nc
  361. dummy = DummyLink(from_socket = inp, to_socket = inp, nc_from=nc, nc_to=None)
  362. link_sig = tuple([None] + tree_path_names +[ng.name, inp.name, inp.identifier])
  363. held_links[link_sig]=dummy
  364. else: # We need to hold the incoming connections
  365. for link in inp.links: #Usually there will only be 1
  366. from_socket = link.from_socket
  367. if (link.from_socket.node.bl_idname == "NodeGroupInput"):
  368. # shouldn't there be a held link for this?
  369. continue
  370. if (link.from_socket.node.bl_idname == "NodeReroute"):
  371. from_socket = socket_seek(link, list(tree.links))
  372. sig = tuple( [None] + tree_path_names +[from_socket.node.name])
  373. # print(sig)
  374. nc_from = nc_dict.get(sig)
  375. # This is kind of stupid
  376. if from_socket.node.bl_idname in "NodeGroupInput":
  377. nc_from = held_links.get( tuple([None] + tree_path_names + [link.from_socket.name, inp.identifier]) )
  378. if not (nc_from):
  379. prRed( [None] + tree_path_names + [link.from_socket.name, inp.identifier])
  380. for signature, link in held_links.items():
  381. print ( wrapGreen(signature), wrapWhite(link))
  382. nc_from = nc_from.nc_from
  383. if (nc_from):
  384. dummy = DummyLink(from_socket = from_socket, to_socket = inp, nc_from=nc_from, nc_to=None, original_from=from_socket )
  385. # The link sig should take us back to the group node.
  386. link_sig = tuple( [None] + tree_path_names + [ng.name, inp.name, inp.identifier])
  387. held_links[link_sig]=dummy
  388. prGreen("Adding %s" % from_socket)
  389. else:
  390. prRed("no nc?")
  391. prOrange(sig)
  392. # Recurse!
  393. # data_from_tree(base_tree, tree_path+[ng], grps, solved_trees, solved_tree_links, held_nodes, held_links, all_nc)
  394. data_from_tree(base_tree, tree_path+[ng], held_links, all_nc)
  395. for link_sig, held in held_links.items():
  396. from_cls = held.from_socket.node.bl_idname
  397. to_cls = held.to_socket.node.bl_idname
  398. if (from_cls in ["MantisNodeGroup"] and not
  399. to_cls in ["MantisNodeGroup"]):
  400. nc_from = held.nc_from
  401. for link in outgoing:
  402. if link.from_socket.node.name == nc_from.signature[-2]:
  403. to_sig = tuple([None] + tree_path_names + [held.to_socket.node.name])
  404. nc_to = nc_dict.get( to_sig )
  405. if (nc_from and nc_to):
  406. from_s, to_s = link_sig[-1], held.to_socket.name
  407. connection = nc_from.outputs[from_s].connect(node=nc_to, socket=to_s)
  408. held_nodes = {}; held_links = {} # NO IDEA why I have to do this
  409. # return None, grps, all_links, solved_trees, solved_tree_links, all_nc
  410. return all_nc
  411. from itertools import chain
  412. def parse_tree(base_tree):
  413. all_nc = data_from_tree(base_tree, tree_path = [None], held_links = {}, all_nc = {})
  414. all_nc = list(all_nc.values()).copy()
  415. kept_nc = {}
  416. while (all_nc):
  417. nc = all_nc.pop()
  418. # total_links=0
  419. # for sock in chain( nc.inputs.values(), nc.outputs.values()):
  420. # total_links+=len(sock.links)
  421. # if total_links > 0:
  422. nc.fill_parameters()
  423. # ugly, but it solves the problem easily:
  424. establish_node_connections(nc)
  425. kept_nc[nc.signature]=nc
  426. return kept_nc
  427. from_name_filter = ["Driver", ]
  428. to_name_filter = [
  429. "Custom Object xForm Override",
  430. "Custom Object",
  431. "Deform Bones"
  432. ]
  433. def establish_node_connections(nc):
  434. # This is ugly bc it adds parameters to an object
  435. # but it's kinda necesary to do it after the fact; and it
  436. # wouldn't be ugly if I just initialized the parameter elsewhere
  437. connections, hierarchy_connections = [], []
  438. for socket in nc.outputs.values():
  439. for link in socket.links:
  440. connections.append(link.to_node)
  441. # this may catch custom properties... too bad.
  442. if link.from_socket in from_name_filter:
  443. continue
  444. if link.to_socket in to_name_filter:
  445. continue
  446. hierarchy_connections.append(link.to_node)
  447. nc.connected_to = connections
  448. nc.hierarchy_connections = hierarchy_connections
  449. def sort_tree_into_layers(nodes, context):
  450. from time import time
  451. from mantis.node_container_common import (get_depth_lines,
  452. node_depth)
  453. from mantis.utilities import prGreen, prOrange, prRed, prPurple
  454. # All this function needs to do is sort out the hierarchy and
  455. # get things working in order of their dependencies.
  456. prPurple ("Number of nodes: ", len(nodes))
  457. roots, drivers = [], []
  458. start = time()
  459. for n in nodes.values():
  460. if n.node_type == 'DRIVER': drivers.append(n)
  461. # ugly but necesary to ensure that drivers are always connected.
  462. if not (hasattr(n, 'inputs')) or ( len(n.inputs) == 0):
  463. roots.append(n)
  464. elif (hasattr(n, 'inputs')):
  465. none_connected = True
  466. for inp in n.inputs.values():
  467. if inp.is_linked: none_connected = False
  468. if none_connected: roots.append(n)
  469. layers, nodes_heights = {}, {}
  470. for root in roots:
  471. nodes_heights[root.signature] = 0
  472. #Possible improvement: unify roots if they represent the same data
  473. all_sorted_nodes = []
  474. for root in roots:
  475. # if len(root.hierarchy_connections) == 0:
  476. # if (len(root.connected_to) == 0):
  477. # prRed("No connections: ", root)
  478. # continue
  479. depth_lines = get_depth_lines(root)[0]
  480. for n in nodes.values():
  481. if n.signature not in (depth_lines.keys()):
  482. continue #belongs to a different root
  483. d = nodes_heights.get(n.signature, 0)
  484. if (new_d := node_depth(depth_lines[n.signature])) > d:
  485. d = new_d
  486. nodes_heights[n.signature] = d
  487. for k, v in nodes_heights.items():
  488. if (layer := layers.get(v, None)):
  489. layer.append(nodes[k]) # add it to the existing layer
  490. else: layers[v] = [nodes[k]] # or make a new layer with the node
  491. all_sorted_nodes.append(nodes[k]) # add it to the sorted list
  492. for n in nodes.values():
  493. if n not in all_sorted_nodes:
  494. for drv in drivers:
  495. if n in drv.connected_to:
  496. depth = nodes_heights[drv.signature] + 1
  497. nodes_heights[n.signature] = depth
  498. # don't try to push downstream deps up bc this
  499. # is a driver and it will be done in the
  500. # finalize pass anyway
  501. if (layer := layers.get(depth, None)):
  502. layer.append(n)
  503. else: layers[v] = [n]
  504. else:
  505. prRed(n)
  506. for inp in n.inputs.values():
  507. print (len(inp.links))
  508. raise RuntimeError(wrapRed("Failed to depth-sort nodes (because of a driver-combine node?)"))
  509. #
  510. prGreen("Sorting depth for %d nodes finished in %s seconds" %
  511. (len(nodes), time() - start))
  512. if (False): # True to print the layers
  513. for i in range(len(layers)):
  514. try:
  515. print(i, layers[i])
  516. except KeyError: # empty layer?
  517. print (i)
  518. return layers
  519. def execute_tree(nodes, base_tree, context):
  520. import bpy
  521. from time import time
  522. from mantis.node_container_common import GraphError
  523. start_time = time()
  524. # input_from_grp_nodes(parsed_tree, base_tree, nodes)
  525. # bpy.ops.wm.quit_blender()
  526. layers = sort_tree_into_layers(nodes, context)
  527. start_execution_time = time()
  528. # Execute the first pass (xForm, Utility) #
  529. for i in range(len(layers)):
  530. for node in layers[i]:
  531. if (node.node_type in ['XFORM', 'UTILITY']):
  532. try:
  533. node.bExecute(context)
  534. except Exception as e:
  535. prRed("Execution failed at %s" % node); raise e
  536. # Switch to Pose Mode #
  537. active = None
  538. switch_me = []
  539. for n in nodes.values():
  540. # if it is a armature, switch modes
  541. # total hack #kinda dumb
  542. if ((hasattr(n, "bGetObject")) and (n.__class__.__name__ == "xFormArmature" )):
  543. try:
  544. ob = n.bGetObject()
  545. except KeyError: # for bones
  546. ob = None
  547. # TODO this will be a problem if and when I add mesh/curve stuff
  548. if (hasattr(ob, 'mode') and ob.mode == 'EDIT'):
  549. switch_me.append(ob)
  550. active = ob # need to have an active ob, not None, to switch modes.
  551. # we override selected_objects to prevent anyone else from mode-switching
  552. # TODO it's possible but unlikely that the user will try to run a
  553. # graph with no armature nodes in it.
  554. if (active):
  555. bpy.ops.object.mode_set({'active_object':active, 'selected_objects':switch_me}, mode='POSE')
  556. # Execute second pass (Link, Driver) #
  557. for i in range(len(layers)):
  558. for n in layers[i]:
  559. # Now do the Link & Driver nodes during the second pass.
  560. if (n.node_type in ['LINK', 'DRIVER']):
  561. try:
  562. n.bExecute(context)
  563. except GraphError:
  564. pass
  565. except Exception as e:
  566. print (n); raise e
  567. # Finalize #
  568. for i in range(len(layers)):
  569. for node in layers[i]:
  570. if (hasattr(node, "bFinalize")):
  571. node.bFinalize(context)
  572. prGreen("Executed Tree in %s seconds" % (time() - start_execution_time))
  573. prGreen("Finished executing tree in %f seconds" % (time() - start_time))