readtree.py 30 KB

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