node_container_common.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. from .utilities import (prRed, prGreen, prPurple, prWhite,
  2. prOrange,
  3. wrapRed, wrapGreen, wrapPurple, wrapWhite,
  4. wrapOrange,)
  5. from .base_definitions import GraphError, NodeSocket
  6. # BE VERY CAREFUL
  7. # the x_containers files import * from this file
  8. # so all the top-level imports are carried over
  9. #TODO: refactor the socket definitions so this becomes unnecessary.
  10. def get_socket_value(node_socket):
  11. value = None
  12. if hasattr(node_socket, "default_value"):
  13. value = node_socket.default_value
  14. if node_socket.bl_idname == 'MatrixSocket':
  15. value = node_socket.TellValue()
  16. return value
  17. def check_for_driver(node_container, input_name, index = None):
  18. prop = node_container.evaluate_input(input_name)
  19. if (index is not None):
  20. prop = prop[index]
  21. return (prop.__class__.__name__ == 'MantisDriver')
  22. def trace_node_lines(node_container):
  23. """ Tells the depth of a node within the node tree. """
  24. node_lines = []
  25. if hasattr(node_container, "inputs"):
  26. for key, socket in node_container.inputs.items():
  27. # Recrusive search through the tree.
  28. # * checc each relevant input socket in the node
  29. # * for EACH input, find the node it's connected to
  30. # * repeat from here until you get all the lines
  31. if ( ( key in ["Relationship", "Parent", "Input Relationship", "Target"])
  32. and (socket.is_connected) ):
  33. # it is necesary to check the key because of Link nodes,
  34. # which don't really traverse like normal.
  35. # TODO: see if I can refactor this to make it traverse
  36. other = socket.from_node
  37. if (other):
  38. other_lines = trace_node_lines(other)
  39. if not other_lines:
  40. node_lines.append([other])
  41. for line in other_lines:
  42. node_lines.append( [other] + line )
  43. return node_lines
  44. # TODO: modify this to work with multi-input nodes
  45. def trace_single_line(node_container, input_name, link_index=0):
  46. # DO: refactor this for new link class
  47. """Traces a line to its input."""
  48. nodes = [node_container]
  49. # Trace a single line
  50. if (socket := node_container.inputs.get(input_name) ):
  51. while (socket.is_linked):
  52. link = socket.links[link_index]; link_index = 0
  53. if (socket := link.from_node.outputs.get(link.from_socket)):
  54. nodes.append(socket.node)
  55. if socket.can_traverse:
  56. socket = socket.traverse_target
  57. else: # this is an output.
  58. break
  59. else:
  60. break
  61. return nodes, socket
  62. # this is same as the other, just flip from/to and in/out
  63. def trace_single_line_up(node_container, output_name,):
  64. """I use this to get the xForm from a link node."""
  65. nodes = [node_container]
  66. if hasattr(node_container, "outputs"):
  67. # Trace a single line
  68. if (socket := node_container.outputs.get(output_name) ):
  69. while (socket.is_linked):
  70. # This is bad, but it's efficient for nodes that only expect
  71. # one path along the given line
  72. link = socket.links[0] # TODO: find out if this is wise.
  73. other = link.to_node.inputs.get(link.to_socket)
  74. if (other):
  75. socket = other
  76. if socket.can_traverse:
  77. socket = socket.traverse_target
  78. nodes.append(socket.node)
  79. else: # this is an input.
  80. nodes.append(socket.node)
  81. break
  82. else:
  83. break
  84. return nodes, socket
  85. def trace_all_lines_up(nc, output_name):
  86. copy_items = {}
  87. for item in dir(nc):
  88. if "__" not in item:
  89. copy_items[item]=getattr(nc, item)
  90. # we want to copy it, BUT:
  91. copy_items["outputs"]:{output_name:nc.outputs[output_name]}
  92. # override outputs with just the one we care about.
  93. check_me = type('', (object,), copy_items)
  94. return get_depth_lines(check_me)[1]
  95. def num_hierarchy_connections(nc):
  96. num=0
  97. for out in nc.outputs:
  98. for link in out.links:
  99. if link.is_hierarchy: num+=1
  100. return num
  101. def list_hierarchy_connections(nc):
  102. return len(nc.hierarchy_connections)-1
  103. hc=[]
  104. for out in nc.outputs:
  105. for link in out.links:
  106. if link.is_hierarchy: hc.append(link.to_node)
  107. return num
  108. # what this is doing is giving a list of Output-Index that is the path to the given node, from a given root.
  109. # HOW TO REWRITE...
  110. # we simply do the same thing, but we look at the outputs, not the old hierarchy-connections
  111. # we can do the same tree-search but we simply ignore an output if it is not hierarchy.
  112. # the existing code isn't complicated, it's just hard to read. So this new code should be easier to read, too.
  113. def get_depth_lines(root):
  114. from .base_definitions import GraphError
  115. path, nc_path = [0,], [root,]
  116. lines, nc_paths = {}, {}
  117. nc_len = len(root.hierarchy_connections)-1
  118. curheight=0
  119. while (path[0] <= nc_len):
  120. # this doesn't seem to make this any slower. It is good to check it.
  121. if nc_path[-1] in nc_path[:-1]:
  122. raise GraphError(wrapRed(f"Infinite loop detected while depth sorting for root {root}."))
  123. #
  124. nc_path.append(nc_path[-1].hierarchy_connections[path[-1]])
  125. if (not (node_lines := lines.get(nc_path[-1].signature, None))):
  126. node_lines = lines[nc_path[-1].signature] = set()
  127. if (not (node_paths := nc_paths.get(nc_path[-1].signature, None))):
  128. node_paths = nc_paths[nc_path[-1].signature] = set()
  129. node_lines.add(tuple(path)); node_paths.add(tuple(nc_path))
  130. if nc_path[-1].hierarchy_connections: # if there is at least one element
  131. path.append(0); curheight+=1
  132. else: # at this point, nc_path is one longer than path because path is a segment between two nodes
  133. # or more siimply, because nc_path has the root in it and path starts with the first node
  134. path[curheight] = path[curheight] + 1
  135. nc_path.pop() # so we go back and horizontal
  136. if ( path[-1] <= len(nc_path[-1].hierarchy_connections)-1 ):
  137. pass # and continue if we can
  138. elif curheight > 0: # otherwise we keep going back
  139. while(len(path) > 1):
  140. path.pop(); curheight -= 1; path[curheight]+=1; nc_path.pop()
  141. if ( (len(nc_path)>1) and path[-1] < len(nc_path[-1].hierarchy_connections) ):
  142. break
  143. return lines, nc_paths
  144. # same but because the checks end up costing a fair amount of time, I don't want to use this one unless I need to.
  145. def get_prepared_depth_lines(root,):
  146. # import pstats, io, cProfile
  147. # from pstats import SortKey
  148. # with cProfile.Profile() as pr:
  149. path, nc_path = [0,], [root,]
  150. lines, nc_paths = {}, {}
  151. nc_len = len(prepared_connections(root, ))-1
  152. curheight=0
  153. while (path[0] <= nc_len):
  154. if nc_path[-1] in nc_path[:-1]:
  155. raise GraphError(wrapRed(f"Infinite loop detected while depth sorting for root {root}."))
  156. nc_path.append(prepared_connections(nc_path[-1], )[path[-1]])
  157. if (not (node_lines := lines.get(nc_path[-1].signature, None))):
  158. node_lines = lines[nc_path[-1].signature] = set()
  159. if (not (node_paths := nc_paths.get(nc_path[-1].signature, None))):
  160. node_paths = nc_paths[nc_path[-1].signature] = set()
  161. node_lines.add(tuple(path)); node_paths.add(tuple(nc_path))
  162. if prepared_connections(nc_path[-1], ): # if there is at least one element
  163. path.append(0); curheight+=1
  164. else: # at this point, nc_path is one longer than path because path is a segment between two nodes
  165. # or more siimply, because nc_path has the root in it and path starts with the first node
  166. path[curheight] = path[curheight] + 1
  167. nc_path.pop() # so we go back and horizontal
  168. if path[-1] <= len(prepared_connections(nc_path[-1], ))-1:
  169. pass # and continue if we can
  170. elif curheight > 0: # otherwise we keep going back
  171. while(len(path) > 1):
  172. path.pop(); curheight -= 1; path[curheight]+=1; nc_path.pop()
  173. if (len(nc_path)>1) and path[-1] < len(prepared_connections(nc_path[-1], ) ):
  174. break
  175. # from the Python docs at https://docs.python.org/3/library/profile.html#module-cProfile
  176. # s = io.StringIO()
  177. # sortby = SortKey.TIME
  178. # ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
  179. # ps.print_stats()
  180. # print(s.getvalue())
  181. return lines, nc_paths
  182. def prepared_connections(nc):
  183. if nc.prepared:
  184. return nc.hierarchy_connections
  185. else:
  186. ret = []
  187. for hc in nc.hierarchy_connections:
  188. if hc.prepared:
  189. ret.append(hc)
  190. return ret
  191. # return [hc for hc in nc.hierarchy_connections if hc.prepared]
  192. def node_depth(lines):
  193. maxlen = 0
  194. for line in lines:
  195. if ( (l := len(line) ) > maxlen):
  196. maxlen = l
  197. return maxlen
  198. #TODO rewrite this so it'll work with new nc_path thing
  199. # not a high priority bc this was debugging code for something that
  200. # works and has since ben refactored to work better
  201. def printable_path(nc, path, no_wrap = False):
  202. string = ""; cur_nc = nc
  203. #DO: find out if the copy is necessary
  204. path = path.copy(); path.reverse()
  205. dummy = lambda a : a
  206. while path:
  207. wrap = dummy
  208. if not no_wrap:
  209. wrap=wrapWhite
  210. if (cur_nc.node_type == 'DRIVER'):
  211. wrap = wrapPurple
  212. elif (cur_nc.node_type == 'XFORM'):
  213. wrap = wrapOrange
  214. elif (cur_nc.node_type == 'LINK'):
  215. wrap = wrapGreen
  216. string += wrap(cur_nc.__repr__()) + " -> "
  217. try:
  218. cur_nc = get_from_path(cur_nc, [path.pop()] )
  219. except IndexError:
  220. string = string[:-4]
  221. return string
  222. string = string[:-4]
  223. return string
  224. # why is this not printing groups in brackets?
  225. def get_parent(node_container, type = 'XFORM'):
  226. # type variable for selecting whether to get either
  227. # the parent xForm or the inheritance node
  228. node_line, socket = trace_single_line(node_container, "Relationship")
  229. parent_nc = None
  230. for i in range(len(node_line)):
  231. # check each of the possible parent types.
  232. if ( (node_line[ i ].__class__.__name__ == 'LinkInherit') ):
  233. try: # it's the next one
  234. if (type == 'XFORM'):
  235. return node_line[ i + 1 ]
  236. else: # type = 'LINK'
  237. return node_line[ i ]
  238. except IndexError: # if there is no next one...
  239. return None # then there's no parent!
  240. return None
  241. # TODO!
  242. #
  243. # make this do shorthand parenting - if no parent, then use World
  244. # if the parent node is skipped, use the previous node (an xForm)
  245. # with default settings.
  246. # it is OK to generate a new, "fake" node container for this!
  247. def get_target_and_subtarget(node_container, linkOb, input_name = "Target"):
  248. from bpy.types import PoseBone, Object, SplineIKConstraint, ArmatureModifier, HookModifier
  249. subtarget = ''; target = node_container.evaluate_input(input_name)
  250. if target:
  251. if not hasattr(target, "bGetObject"):
  252. prRed(f"No {input_name} target found for {linkOb.name} in {node_container} because there is no connected node, or node is wrong type")
  253. return
  254. if (isinstance(target.bGetObject(), PoseBone)):
  255. subtarget = target.bGetObject().name
  256. target = target.bGetParentArmature()
  257. elif (isinstance(target.bGetObject(), Object) ):
  258. target = target.bGetObject()
  259. else:
  260. raise RuntimeError("Cannot interpret linkOb target!")
  261. if (isinstance(linkOb, SplineIKConstraint)):
  262. if target and target.type not in ["CURVE"]:
  263. raise GraphError(wrapRed("Error: %s requires a Curve input, not %s" %
  264. (node_container, type(target))))
  265. linkOb.target = target# don't get a subtarget
  266. if (input_name == 'Pole Target'):
  267. linkOb.pole_target, linkOb.pole_subtarget = target, subtarget
  268. else:
  269. if hasattr(linkOb, "target"):
  270. linkOb.target = target
  271. if hasattr(linkOb, "object"):
  272. linkOb.object = target
  273. if hasattr(linkOb, "subtarget"):
  274. linkOb.subtarget = subtarget
  275. def setup_custom_props(nc):
  276. from .utilities import get_node_prototype
  277. if nc.signature[0] == 'SCHEMA_AUTOGENERATED':
  278. from .base_definitions import custom_props_types
  279. if nc.__class__.__name__ not in custom_props_types:
  280. # prRed(f"Reminder: figure out how to deal with custom property setting for Schema Node {nc}")
  281. raise RuntimeError(wrapRed(f"Custom Properties not set up for node {nc}"))
  282. return
  283. else:
  284. np = get_node_prototype(nc.signature, nc.base_tree)
  285. if np:
  286. setup_custom_props_from_np(nc, np)
  287. else:
  288. prRed("Failed to setup custom properties for: nc")
  289. def setup_custom_props_from_np(nc, np):
  290. for inp in np.inputs:
  291. if inp.identifier == "__extend__": continue
  292. if not (inp.name in nc.inputs.keys()) :
  293. socket = NodeSocket(is_input = True, name = inp.name, node = nc,)
  294. nc.inputs[inp.name] = socket
  295. nc.parameters[inp.name] = None
  296. for attr_name in ["min", "max", "soft_min", "soft_max", "description"]:
  297. try:
  298. setattr(socket, attr_name, getattr(inp, attr_name))
  299. except AttributeError:
  300. pass
  301. for out in np.outputs:
  302. if out.identifier == "__extend__": continue
  303. if not (out.name in nc.outputs.keys()) :
  304. nc.outputs[out.name] = NodeSocket(is_input = False, name = out.name, node = nc,)
  305. def prepare_parameters(nc):
  306. # some nodes add new parameters at runtime, e.g. Drivers
  307. # so we need to take that stuff from the node_containers that have
  308. # been executed prior to this node.
  309. for s_name, sock in nc.inputs.items():
  310. if not (sock.is_linked):
  311. continue
  312. if (sock.name in sock.links[0].from_node.parameters.keys()):
  313. nc.parameters[s_name] = sock.links[0].from_node.parameters[sock.name]
  314. # should work, this is ugly.
  315. # TODO: this should handle sub-properties better
  316. def evaluate_sockets(nc, c, props_sockets):
  317. # this is neccesary because some things use dict properties for dynamic properties and setattr doesn't work
  318. def safe_setattr(ob, att_name, val):
  319. if ob.__class__.__name__ in ["NodesModifier"]:
  320. ob[att_name]=val
  321. elif c.__class__.__name__ in ["Key"]:
  322. if not val: val=0
  323. ob.key_blocks[att_name].value=val
  324. elif "]." in att_name:
  325. # it is of the form prop[int].prop2
  326. prop=att_name.split('[')[0]
  327. prop1=att_name.split('.')[1]
  328. index = int(att_name.split('[')[1][0])
  329. setattr(getattr(c, prop)[index], prop1, val)
  330. else:
  331. try:
  332. setattr(ob, att_name, val)
  333. except Exception as e:
  334. prRed(ob, att_name, val); raise e
  335. # HACK I think I should do this in __init__
  336. if not hasattr(nc, "drivers"):
  337. nc.drivers = {}
  338. # end HACK
  339. for prop, (sock, default) in props_sockets.items():
  340. # c = nc.bObject
  341. # annoyingly, sometimes the socket is an array
  342. index = None
  343. if isinstance(sock, tuple):
  344. index = sock[1]; sock = sock[0]
  345. if (check_for_driver(nc, sock, index)):
  346. sock = (sock, index)
  347. original_prop = prop
  348. # TODO: deduplicate this terrible hack
  349. if ("." in prop) and not c.__class__.__name__ in ["Key"]: # this is a property of a property...
  350. sub_props = [c]
  351. while ("." in prop):
  352. split_prop = prop.split(".")
  353. prop = split_prop[1]
  354. sub_prop = (split_prop[0])
  355. if ("[" in sub_prop):
  356. sub_prop, index = sub_prop.split("[")
  357. index = int(index[0])
  358. sub_props.append(getattr(sub_props[-1], sub_prop)[index])
  359. else:
  360. sub_props.append(getattr(sub_props[-1], sub_prop))
  361. safe_setattr(sub_props[-1], prop, default)
  362. # this is really stupid
  363. else:
  364. safe_setattr(c, prop, default)
  365. if nc.node_type in ['LINK',]:
  366. printname = wrapOrange(nc.GetxForm().bGetObject().name)
  367. elif nc.node_type in ['XFORM',]:
  368. printname = wrapOrange(nc.bGetObject().name)
  369. else:
  370. printname = wrapOrange(nc)
  371. print("Adding driver %s to %s in %s" % (wrapPurple(original_prop), wrapWhite(nc.signature[-1]), printname))
  372. if c.__class__.__name__ in ["NodesModifier"]:
  373. nc.drivers[sock] = "[\""+original_prop+"\"]" # lol. It is a dict element not a "true" property
  374. elif c.__class__.__name__ in ["Key"]:
  375. nc.drivers[sock] = "key_blocks[\""+original_prop+"\"].value"
  376. else:
  377. nc.drivers[sock] = original_prop
  378. else: # here we can do error checking for the socket if needed
  379. if (index is not None):
  380. safe_setattr(c, prop, nc.evaluate_input(sock)[index])
  381. else: # 'mute' is better than 'enabled'
  382. # UGLY HACK # because it is available in older
  383. if (prop == 'mute'): # Blenders.
  384. safe_setattr(c, prop, not bool(nc.evaluate_input(sock)))
  385. elif (prop == 'hide'): # this will not cast it for me, annoying.
  386. safe_setattr(c, prop, bool(nc.evaluate_input(sock)))
  387. else:
  388. try:
  389. # prRed(c.name, nc, prop, nc.evaluate_input(sock) )
  390. # print( nc.evaluate_input(sock))
  391. # value_eval = nc.evaluate_input(sock)
  392. # just wanna see if we are dealing with some collection
  393. # check hasattr in case it is one of those ["such-and-such"] props, and ignore those
  394. if hasattr(c, prop) and (not isinstance(getattr(c, prop), str)) and hasattr(getattr(c, prop), "__getitem__"):
  395. # prGreen("Doing the thing")
  396. for val_index, value in enumerate(nc.evaluate_input(sock)):
  397. # assume this will work, both because val should have the right number of elements, and because this should be the right data type.
  398. from .drivers import MantisDriver
  399. if isinstance(value, MantisDriver):
  400. getattr(c,prop)[val_index] = default[val_index]
  401. print("Adding driver %s to %s in %s" % (wrapPurple(prop), wrapWhite(nc.signature[-1]), nc))
  402. try:
  403. nc.drivers[sock].append((prop, val_index))
  404. except:
  405. nc.drivers[sock] = [(prop, val_index)]
  406. else:
  407. getattr(c,prop)[val_index] = value
  408. else:
  409. # prOrange("Skipping the Thing", getattr(c, prop))
  410. safe_setattr(c, prop, nc.evaluate_input(sock))
  411. except Exception as e:
  412. prRed(c, nc, prop, sock, nc.evaluate_input(sock))
  413. raise e
  414. def finish_driver(nc, driver_item, prop):
  415. # prWhite(nc, prop)
  416. index = driver_item[1]; driver_sock = driver_item[0]
  417. driver_trace = trace_single_line(nc, driver_sock)
  418. driver_provider, driver_socket = driver_trace[0][-1], driver_trace[1]
  419. if index is not None:
  420. driver = driver_provider.parameters[driver_socket.name][index].copy()
  421. # this is harmless and necessary for the weird ones where the property is a vector too
  422. driver["ind"] = index
  423. else:
  424. driver = driver_provider.parameters[driver_socket.name].copy()
  425. if driver:
  426. # todo: deduplicate this terrible hack
  427. c = None # no idea what this c and sub_prop thing is, HACK?
  428. if hasattr(nc, "bObject"):
  429. c = nc.bObject # STUPID # stupid and bad HACK here too
  430. if ("." in prop) and nc.__class__.__name__ != "DeformerMorphTargetDeform": # this is a property of a property...
  431. sub_props = [c]
  432. while ("." in prop):
  433. split_prop = prop.split(".")
  434. prop = split_prop[1]
  435. sub_prop = (split_prop[0])
  436. if ("[" in sub_prop):
  437. sub_prop, index = sub_prop.split("[")
  438. index = int(index[0])
  439. sub_props.append(getattr(sub_props[-1], sub_prop)[index])
  440. else:
  441. sub_props.append(getattr(sub_props[-1], sub_prop))
  442. driver["owner"] = sub_props[-1]
  443. elif nc.node_type in ['XFORM',] and nc.__class__.__name__ in ['xFormBone']:
  444. # TODO: I really shouldn't have to hardcode this. Look into better solutions.
  445. if prop in ['hide', 'show_wire']: # we need to get the bone, not the pose bone.
  446. bone_col = nc.bGetParentArmature().data.bones
  447. else:
  448. bone_col = nc.bGetParentArmature().pose.bones
  449. driver["owner"] = bone_col[nc.bObject] # we use "unsafe" brackets instead of get() because we want to see any errors that occur
  450. else:
  451. driver["owner"] = nc.bObject
  452. # prPurple("Successfully created driver for %s" % prop)
  453. driver["prop"] = prop
  454. return driver
  455. # prWhite(driver)
  456. else:
  457. prOrange("Provider", driver_provider)
  458. prGreen("socket", driver_socket)
  459. print (index)
  460. prPurple(driver_provider.parameters[driver_socket.name])
  461. prRed("Failed to create driver for %s" % prop)
  462. return None
  463. def finish_drivers(nc):
  464. # gonna make this into a common function...
  465. drivers = []
  466. if not hasattr(nc, "drivers"):
  467. # prGreen(f"No Drivers to construct for {nc}")
  468. return # HACK
  469. for driver_item, prop in nc.drivers.items():
  470. if isinstance(prop, list):
  471. for sub_item in prop:
  472. drivers.append(finish_driver(nc, (driver_item, sub_item[1]), sub_item[0]))
  473. else:
  474. drivers.append(finish_driver(nc, driver_item, prop))
  475. from .drivers import CreateDrivers
  476. CreateDrivers(drivers)
  477. from .base_definitions import from_name_filter, to_name_filter
  478. def detect_hierarchy_link(from_node, from_socket, to_node, to_socket,):
  479. if to_node.node_type in ['DUMMY_SCHEMA', 'SCHEMA']:
  480. return False
  481. if (from_socket in from_name_filter) or (to_socket in to_name_filter):
  482. return False
  483. # if from_node.__class__.__name__ in ["UtilityCombineVector", "UtilityCombineThreeBool"]:
  484. # return False
  485. return True
  486. #Dummy classes for logic with node containers, they are not meant to do
  487. # each and every little thing the "real" Blender classes do.