node_container_common.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. from .utilities import (prRed, prGreen, prPurple, prWhite,
  2. prOrange,
  3. wrapRed, wrapGreen, wrapPurple, wrapWhite,
  4. wrapOrange,)
  5. from .base_definitions import GraphError
  6. # BE VERY CAREFUL
  7. # the x_containers files import * from this file
  8. # so all the top-level imports are carried over
  9. def get_socket_value(node_socket):
  10. # if node_socket.bl_idname in ['RelationshipSocket', 'xFormSocket']:
  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. # TODO: unify the fill_paramaters for auto-gen nodes
  18. def fill_parameters(nc, np = None):
  19. from .utilities import get_node_prototype
  20. if not np:
  21. if ( (nc.signature[0] in ["MANTIS_AUTOGENERATED", "SCHEMA_AUTOGENERATED" ]) or
  22. (nc.signature[-1] in ["NodeGroupOutput", "NodeGroupInput"]) ): # I think this is harmless
  23. return None
  24. else:
  25. np = get_node_prototype(nc.signature, nc.base_tree)
  26. if not np:
  27. raise RuntimeError(wrapRed("No node prototype found for... %s" % ( [nc.base_tree] + list(nc.signature[1:]) ) ) )
  28. for key in nc.parameters.keys():
  29. node_socket = np.inputs.get(key)
  30. # if (nc.signature[0] is "MANTIS_AUTOGENERATED"):
  31. # node_socket = None
  32. # for node_socket in np.inputs:
  33. # if node_socket.identifier == nc.signature[-1]:
  34. # break
  35. if nc.parameters[key] is not None:
  36. continue # will be filled by the node itself
  37. if not node_socket:
  38. #maybe the node socket has no name
  39. if ( ( len(np.inputs) == 0) and ( len(np.outputs) == 1) ):
  40. # this is a simple input node.
  41. node_socket = np.outputs[0]
  42. elif key == 'Name': # for Links we just use the Node Label, or if there is no label, the name.
  43. nc.parameters[key] = np.label if np.label else np.name
  44. continue
  45. else:
  46. pass
  47. if node_socket:
  48. if node_socket.bl_idname in ['RelationshipSocket', 'xFormSocket']:
  49. continue
  50. elif hasattr(node_socket, "default_value"):
  51. if (value := get_socket_value(node_socket)) is not None:
  52. nc.parameters[key] = value
  53. else:
  54. raise RuntimeError(wrapRed("No value found for " + nc.__repr__() + " when filling out node parameters for " + np.name + "::"+node_socket.name))
  55. else:
  56. pass
  57. # if key in ['Use Target Z']:
  58. # prRed (nc, key, nc.parameters[key])
  59. def evaluate_input(node_container, input_name, index=0):
  60. if not (node_container.inputs.get(input_name)):
  61. # just return the parameter, there is no socket associated
  62. # prOrange("No input: %s, %s" %(node_container, input_name))
  63. return node_container.parameters.get(input_name)
  64. trace = trace_single_line(node_container, input_name, index)
  65. # this should give a key error if there is a problem
  66. # it is NOT handled here because it should NOT happen
  67. try:
  68. prop = trace[0][-1].parameters[trace[1].name] #[0] = nodes [-1] = last node, read its parameters
  69. except Exception as e:
  70. prRed (trace[1].name, trace[0][-1], "prepared" if trace[0][-1].prepared else "NO","executed" if trace[0][-1].executed else "NO")
  71. raise e
  72. return prop # this should not be necessary...but dicts will be dicts
  73. def check_for_driver(node_container, input_name, index = None):
  74. prop = evaluate_input(node_container, input_name)
  75. if (index is not None):
  76. prop = prop[index]
  77. return (prop.__class__.__name__ == 'MantisDriver')
  78. def trace_node_lines(node_container):
  79. """ Tells the depth of a node within the node tree. """
  80. node_lines = []
  81. if hasattr(node_container, "inputs"):
  82. for key, socket in node_container.inputs.items():
  83. # Recrusive search through the tree.
  84. # * checc each relevant input socket in the node
  85. # * for EACH input, find the node it's connected to
  86. # * repeat from here until you get all the lines
  87. if ( ( key in ["Relationship", "Parent", "Input Relationship", "Target"])
  88. and (socket.is_connected) ):
  89. # it is necesary to check the key because of Link nodes,
  90. # which don't really traverse like normal.
  91. # TODO: see if I can refactor this to make it traverse
  92. other = socket.from_node
  93. if (other):
  94. other_lines = trace_node_lines(other)
  95. if not other_lines:
  96. node_lines.append([other])
  97. for line in other_lines:
  98. node_lines.append( [other] + line )
  99. return node_lines
  100. # TODO: modify this to work with multi-input nodes
  101. def trace_single_line(node_container, input_name, link_index=0):
  102. # DO: refactor this for new link class
  103. """Traces a line to its input."""
  104. nodes = [node_container]
  105. # Trace a single line
  106. if (socket := node_container.inputs.get(input_name) ):
  107. while (socket.is_linked):
  108. link = socket.links[link_index]; link_index = 0
  109. if (socket := link.from_node.outputs.get(link.from_socket)):
  110. nodes.append(socket.node)
  111. if socket.can_traverse:
  112. socket = socket.traverse_target
  113. else: # this is an output.
  114. break
  115. else:
  116. break
  117. return nodes, socket
  118. # this is same as the other, just flip from/to and in/out
  119. def trace_single_line_up(node_container, output_name,):
  120. """I use this to get the xForm from a link node."""
  121. nodes = [node_container]
  122. if hasattr(node_container, "outputs"):
  123. # Trace a single line
  124. if (socket := node_container.outputs.get(output_name) ):
  125. while (socket.is_linked):
  126. # This is bad, but it's efficient for nodes that only expect
  127. # one path along the given line
  128. link = socket.links[0] # TODO: find out if this is wise.
  129. other = link.to_node.inputs.get(link.to_socket)
  130. if (other):
  131. socket = other
  132. if socket.can_traverse:
  133. socket = socket.traverse_target
  134. nodes.append(socket.node)
  135. else: # this is an input.
  136. nodes.append(socket.node)
  137. break
  138. else:
  139. break
  140. return nodes, socket
  141. def trace_all_lines_up(nc, output_name):
  142. copy_items = {}
  143. for item in dir(nc):
  144. if "__" not in item:
  145. copy_items[item]=getattr(nc, item)
  146. # we want to copy it, BUT:
  147. copy_items["outputs"]:{output_name:nc.outputs[output_name]}
  148. # override outputs with just the one we care about.
  149. check_me = type('', (object,), copy_items)
  150. return get_depth_lines(check_me)[1]
  151. def num_hierarchy_connections(nc):
  152. num=0
  153. for out in nc.outputs:
  154. for link in out.links:
  155. if link.is_hierarchy: num+=1
  156. return num
  157. def list_hierarchy_connections(nc):
  158. return len(nc.hierarchy_connections)-1
  159. hc=[]
  160. for out in nc.outputs:
  161. for link in out.links:
  162. if link.is_hierarchy: hc.append(link.to_node)
  163. return num
  164. # what this is doing is giving a list of Output-Index that is the path to the given node, from a given root.
  165. # HOW TO REWRITE...
  166. # we simply do the same thing, but we look at the outputs, not the old hierarchy-connections
  167. # we can do the same tree-search but we simply ignore an output if it is not hierarchy.
  168. # the existing code isn't complicated, it's just hard to read. So this new code should be easier to read, too.
  169. def get_depth_lines(root):
  170. from .base_definitions import GraphError
  171. path, nc_path = [0,], [root,]
  172. lines, nc_paths = {}, {}
  173. nc_len = len(root.hierarchy_connections)-1
  174. curheight=0
  175. while (path[0] <= nc_len):
  176. # this doesn't seem to make this any slower. It is good to check it.
  177. if nc_path[-1] in nc_path[:-1]:
  178. raise GraphError(wrapRed(f"Infinite loop detected while depth sorting for root {root}."))
  179. #
  180. nc_path.append(nc_path[-1].hierarchy_connections[path[-1]])
  181. if (not (node_lines := lines.get(nc_path[-1].signature, None))):
  182. node_lines = lines[nc_path[-1].signature] = set()
  183. if (not (node_paths := nc_paths.get(nc_path[-1].signature, None))):
  184. node_paths = nc_paths[nc_path[-1].signature] = set()
  185. node_lines.add(tuple(path)); node_paths.add(tuple(nc_path))
  186. if nc_path[-1].hierarchy_connections: # if there is at least one element
  187. path.append(0); curheight+=1
  188. else: # at this point, nc_path is one longer than path because path is a segment between two nodes
  189. # or more siimply, because nc_path has the root in it and path starts with the first node
  190. path[curheight] = path[curheight] + 1
  191. nc_path.pop() # so we go back and horizontal
  192. if ( path[-1] <= len(nc_path[-1].hierarchy_connections)-1 ):
  193. pass # and continue if we can
  194. elif curheight > 0: # otherwise we keep going back
  195. while(len(path) > 1):
  196. path.pop(); curheight -= 1; path[curheight]+=1; nc_path.pop()
  197. if ( (len(nc_path)>1) and path[-1] < len(nc_path[-1].hierarchy_connections) ):
  198. break
  199. return lines, nc_paths
  200. # 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.
  201. def get_prepared_depth_lines(root,):
  202. # import pstats, io, cProfile
  203. # from pstats import SortKey
  204. # with cProfile.Profile() as pr:
  205. path, nc_path = [0,], [root,]
  206. lines, nc_paths = {}, {}
  207. nc_len = len(prepared_connections(root, ))-1
  208. curheight=0
  209. while (path[0] <= nc_len):
  210. if nc_path[-1] in nc_path[:-1]:
  211. raise GraphError(wrapRed(f"Infinite loop detected while depth sorting for root {root}."))
  212. nc_path.append(prepared_connections(nc_path[-1], )[path[-1]])
  213. if (not (node_lines := lines.get(nc_path[-1].signature, None))):
  214. node_lines = lines[nc_path[-1].signature] = set()
  215. if (not (node_paths := nc_paths.get(nc_path[-1].signature, None))):
  216. node_paths = nc_paths[nc_path[-1].signature] = set()
  217. node_lines.add(tuple(path)); node_paths.add(tuple(nc_path))
  218. if prepared_connections(nc_path[-1], ): # if there is at least one element
  219. path.append(0); curheight+=1
  220. else: # at this point, nc_path is one longer than path because path is a segment between two nodes
  221. # or more siimply, because nc_path has the root in it and path starts with the first node
  222. path[curheight] = path[curheight] + 1
  223. nc_path.pop() # so we go back and horizontal
  224. if path[-1] <= len(prepared_connections(nc_path[-1], ))-1:
  225. pass # and continue if we can
  226. elif curheight > 0: # otherwise we keep going back
  227. while(len(path) > 1):
  228. path.pop(); curheight -= 1; path[curheight]+=1; nc_path.pop()
  229. if (len(nc_path)>1) and path[-1] < len(prepared_connections(nc_path[-1], ) ):
  230. break
  231. # from the Python docs at https://docs.python.org/3/library/profile.html#module-cProfile
  232. # s = io.StringIO()
  233. # sortby = SortKey.TIME
  234. # ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
  235. # ps.print_stats()
  236. # print(s.getvalue())
  237. return lines, nc_paths
  238. def prepared_connections(nc):
  239. if nc.prepared:
  240. return nc.hierarchy_connections
  241. else:
  242. ret = []
  243. for hc in nc.hierarchy_connections:
  244. if hc.prepared:
  245. ret.append(hc)
  246. return ret
  247. # return [hc for hc in nc.hierarchy_connections if hc.prepared]
  248. def node_depth(lines):
  249. maxlen = 0
  250. for line in lines:
  251. if ( (l := len(line) ) > maxlen):
  252. maxlen = l
  253. return maxlen
  254. #TODO rewrite this so it'll work with new nc_path thing
  255. # not a high priority bc this was debugging code for something that
  256. # works and has since ben refactored to work better
  257. def printable_path(nc, path, no_wrap = False):
  258. string = ""; cur_nc = nc
  259. #DO: find out if the copy is necessary
  260. path = path.copy(); path.reverse()
  261. dummy = lambda a : a
  262. while path:
  263. wrap = dummy
  264. if not no_wrap:
  265. wrap=wrapWhite
  266. if (cur_nc.node_type == 'DRIVER'):
  267. wrap = wrapPurple
  268. elif (cur_nc.node_type == 'XFORM'):
  269. wrap = wrapOrange
  270. elif (cur_nc.node_type == 'LINK'):
  271. wrap = wrapGreen
  272. string += wrap(cur_nc.__repr__()) + " -> "
  273. try:
  274. cur_nc = get_from_path(cur_nc, [path.pop()] )
  275. except IndexError:
  276. string = string[:-4]
  277. return string
  278. string = string[:-4]
  279. return string
  280. # why is this not printing groups in brackets?
  281. def get_parent(node_container, type = 'XFORM'):
  282. # type variable for selecting whether to get either
  283. # the parent xForm or the inheritance node
  284. node_line, socket = trace_single_line(node_container, "Relationship")
  285. parent_nc = None
  286. for i in range(len(node_line)):
  287. # check each of the possible parent types.
  288. if ( (node_line[ i ].__class__.__name__ == 'LinkInherit') ):
  289. try: # it's the next one
  290. if (type == 'XFORM'):
  291. return node_line[ i + 1 ]
  292. else: # type = 'LINK'
  293. return node_line[ i ]
  294. except IndexError: # if there is no next one...
  295. return None # then there's no parent!
  296. return None
  297. # TODO!
  298. #
  299. # make this do shorthand parenting - if no parent, then use World
  300. # if the parent node is skipped, use the previous node (an xForm)
  301. # with default settings.
  302. # it is OK to generate a new, "fake" node container for this!
  303. def get_target_and_subtarget(node_container, linkOb, input_name = "Target"):
  304. from bpy.types import PoseBone, Object, SplineIKConstraint, ArmatureModifier, HookModifier
  305. subtarget = ''; target = node_container.evaluate_input(input_name)
  306. if target:
  307. if not hasattr(target, "bGetObject"):
  308. prRed(f"No {input_name} target found for {linkOb.name} in {node_container} because there is no connected node, or node is wrong type")
  309. return
  310. if (isinstance(target.bGetObject(), PoseBone)):
  311. subtarget = target.bGetObject().name
  312. target = target.bGetParentArmature()
  313. elif (isinstance(target.bGetObject(), Object) ):
  314. target = target.bGetObject()
  315. else:
  316. raise RuntimeError("Cannot interpret linkOb target!")
  317. if (isinstance(linkOb, SplineIKConstraint)):
  318. if target and target.type not in ["CURVE"]:
  319. raise GraphError(wrapRed("Error: %s requires a Curve input, not %s" %
  320. (node_container, type(target))))
  321. linkOb.target = target# don't get a subtarget
  322. if (input_name == 'Pole Target'):
  323. linkOb.pole_target, linkOb.pole_subtarget = target, subtarget
  324. else:
  325. if hasattr(linkOb, "target"):
  326. linkOb.target = target
  327. if hasattr(linkOb, "object"):
  328. linkOb.object = target
  329. if hasattr(linkOb, "subtarget"):
  330. linkOb.subtarget = subtarget
  331. def setup_custom_props(nc):
  332. from .utilities import get_node_prototype
  333. if nc.signature[0] == 'SCHEMA_AUTOGENERATED':
  334. from .base_definitions import custom_props_types
  335. if nc.__class__.__name__ not in custom_props_types:
  336. # prRed(f"Reminder: figure out how to deal with custom property setting for Schema Node {nc}")
  337. raise RuntimeError(wrapRed(f"Custom Properties not set up for node {nc}"))
  338. return
  339. else:
  340. np = get_node_prototype(nc.signature, nc.base_tree)
  341. if np:
  342. setup_custom_props_from_np(nc, np)
  343. else:
  344. prRed("Failed to setup custom properties for: nc")
  345. def setup_custom_props_from_np(nc, np):
  346. for inp in np.inputs:
  347. if inp.identifier == "__extend__": continue
  348. if not (inp.name in nc.inputs.keys()) :
  349. socket = NodeSocket(is_input = True, name = inp.name, node = nc,)
  350. nc.inputs[inp.name] = socket
  351. nc.parameters[inp.name] = None
  352. for attr_name in ["min", "max", "soft_min", "soft_max", "description"]:
  353. try:
  354. setattr(socket, attr_name, getattr(inp, attr_name))
  355. except AttributeError:
  356. pass
  357. for out in np.outputs:
  358. if out.identifier == "__extend__": continue
  359. if not (out.name in nc.outputs.keys()) :
  360. nc.outputs[out.name] = NodeSocket(is_input = False, name = out.name, node = nc,)
  361. def prepare_parameters(nc):
  362. # some nodes add new parameters at runtime, e.g. Drivers
  363. # so we need to take that stuff from the node_containers that have
  364. # been executed prior to this node.
  365. for s_name, sock in nc.inputs.items():
  366. if not (sock.is_linked):
  367. continue
  368. if (sock.name in sock.links[0].from_node.parameters.keys()):
  369. nc.parameters[s_name] = sock.links[0].from_node.parameters[sock.name]
  370. # should work, this is ugly.
  371. # TODO: this should handle sub-properties better
  372. def evaluate_sockets(nc, c, props_sockets):
  373. # this is neccesary because some things use dict properties for dynamic properties and setattr doesn't work
  374. def safe_setattr(ob, att_name, val):
  375. if ob.__class__.__name__ in ["NodesModifier"]:
  376. ob[att_name]=val
  377. elif c.__class__.__name__ in ["Key"]:
  378. if not val: val=0
  379. ob.key_blocks[att_name].value=val
  380. elif "]." in att_name:
  381. # it is of the form prop[int].prop2
  382. prop=att_name.split('[')[0]
  383. prop1=att_name.split('.')[1]
  384. index = int(att_name.split('[')[1][0])
  385. setattr(getattr(c, prop)[index], prop1, val)
  386. else:
  387. try:
  388. setattr(ob, att_name, val)
  389. except Exception as e:
  390. prRed(ob, att_name, val); raise e
  391. # HACK I think I should do this in __init__
  392. if not hasattr(nc, "drivers"):
  393. nc.drivers = {}
  394. # end HACK
  395. for prop, (sock, default) in props_sockets.items():
  396. # c = nc.bObject
  397. # annoyingly, sometimes the socket is an array
  398. index = None
  399. if isinstance(sock, tuple):
  400. index = sock[1]; sock = sock[0]
  401. if (check_for_driver(nc, sock, index)):
  402. sock = (sock, index)
  403. original_prop = prop
  404. # TODO: deduplicate this terrible hack
  405. if ("." in prop) and not c.__class__.__name__ in ["Key"]: # this is a property of a property...
  406. sub_props = [c]
  407. while ("." in prop):
  408. split_prop = prop.split(".")
  409. prop = split_prop[1]
  410. sub_prop = (split_prop[0])
  411. if ("[" in sub_prop):
  412. sub_prop, index = sub_prop.split("[")
  413. index = int(index[0])
  414. sub_props.append(getattr(sub_props[-1], sub_prop)[index])
  415. else:
  416. sub_props.append(getattr(sub_props[-1], sub_prop))
  417. safe_setattr(sub_props[-1], prop, default)
  418. # this is really stupid
  419. else:
  420. safe_setattr(c, prop, default)
  421. if nc.node_type in ['LINK',]:
  422. printname = wrapOrange(nc.GetxForm().bGetObject().name)
  423. elif nc.node_type in ['XFORM',]:
  424. printname = wrapOrange(nc.bGetObject().name)
  425. else:
  426. printname = wrapOrange(nc)
  427. print("Adding driver %s to %s in %s" % (wrapPurple(original_prop), wrapWhite(nc.signature[-1]), printname))
  428. if c.__class__.__name__ in ["NodesModifier"]:
  429. nc.drivers[sock] = "[\""+original_prop+"\"]" # lol. It is a dict element not a "true" property
  430. elif c.__class__.__name__ in ["Key"]:
  431. nc.drivers[sock] = "key_blocks[\""+original_prop+"\"].value"
  432. else:
  433. nc.drivers[sock] = original_prop
  434. else: # here we can do error checking for the socket if needed
  435. if (index is not None):
  436. safe_setattr(c, prop, nc.evaluate_input(sock)[index])
  437. else: # 'mute' is better than 'enabled'
  438. # UGLY HACK # because it is available in older
  439. if (prop == 'mute'): # Blenders.
  440. safe_setattr(c, prop, not bool(nc.evaluate_input(sock)))
  441. elif (prop == 'hide'): # this will not cast it for me, annoying.
  442. safe_setattr(c, prop, bool(nc.evaluate_input(sock)))
  443. else:
  444. try:
  445. # prRed(c.name, nc, prop, nc.evaluate_input(sock) )
  446. # print( nc.evaluate_input(sock))
  447. # value_eval = nc.evaluate_input(sock)
  448. # just wanna see if we are dealing with some collection
  449. # check hasattr in case it is one of those ["such-and-such"] props, and ignore those
  450. if hasattr(c, prop) and (not isinstance(getattr(c, prop), str)) and hasattr(getattr(c, prop), "__getitem__"):
  451. # prGreen("Doing the thing")
  452. for val_index, value in enumerate(nc.evaluate_input(sock)):
  453. # assume this will work, both because val should have the right number of elements, and because this should be the right data type.
  454. from .drivers import MantisDriver
  455. if isinstance(value, MantisDriver):
  456. getattr(c,prop)[val_index] = default[val_index]
  457. print("Adding driver %s to %s in %s" % (wrapPurple(prop), wrapWhite(nc.signature[-1]), nc))
  458. try:
  459. nc.drivers[sock].append((prop, val_index))
  460. except:
  461. nc.drivers[sock] = [(prop, val_index)]
  462. else:
  463. getattr(c,prop)[val_index] = value
  464. else:
  465. # prOrange("Skipping the Thing", getattr(c, prop))
  466. safe_setattr(c, prop, nc.evaluate_input(sock))
  467. except Exception as e:
  468. prRed(c, nc, prop, sock, nc.evaluate_input(sock))
  469. raise e
  470. def finish_driver(nc, driver_item, prop):
  471. # prWhite(nc, prop)
  472. index = driver_item[1]; driver_sock = driver_item[0]
  473. driver_trace = trace_single_line(nc, driver_sock)
  474. driver_provider, driver_socket = driver_trace[0][-1], driver_trace[1]
  475. if index is not None:
  476. driver = driver_provider.parameters[driver_socket.name][index].copy()
  477. # this is harmless and necessary for the weird ones where the property is a vector too
  478. driver["ind"] = index
  479. else:
  480. driver = driver_provider.parameters[driver_socket.name].copy()
  481. if driver:
  482. # todo: deduplicate this terrible hack
  483. c = None # no idea what this c and sub_prop thing is, HACK?
  484. if hasattr(nc, "bObject"):
  485. c = nc.bObject # STUPID # stupid and bad HACK here too
  486. if ("." in prop) and nc.__class__.__name__ != "DeformerMorphTargetDeform": # this is a property of a property...
  487. sub_props = [c]
  488. while ("." in prop):
  489. split_prop = prop.split(".")
  490. prop = split_prop[1]
  491. sub_prop = (split_prop[0])
  492. if ("[" in sub_prop):
  493. sub_prop, index = sub_prop.split("[")
  494. index = int(index[0])
  495. sub_props.append(getattr(sub_props[-1], sub_prop)[index])
  496. else:
  497. sub_props.append(getattr(sub_props[-1], sub_prop))
  498. driver["owner"] = sub_props[-1]
  499. elif nc.node_type in ['XFORM',] and nc.__class__.__name__ in ['xFormBone']:
  500. # TODO: I really shouldn't have to hardcode this. Look into better solutions.
  501. if prop in ['hide', 'show_wire']: # we need to get the bone, not the pose bone.
  502. bone_col = nc.bGetParentArmature().data.bones
  503. else:
  504. bone_col = nc.bGetParentArmature().pose.bones
  505. driver["owner"] = bone_col[nc.bObject] # we use "unsafe" brackets instead of get() because we want to see any errors that occur
  506. else:
  507. driver["owner"] = nc.bObject
  508. # prPurple("Successfully created driver for %s" % prop)
  509. driver["prop"] = prop
  510. return driver
  511. # prWhite(driver)
  512. else:
  513. prOrange("Provider", driver_provider)
  514. prGreen("socket", driver_socket)
  515. print (index)
  516. prPurple(driver_provider.parameters[driver_socket.name])
  517. prRed("Failed to create driver for %s" % prop)
  518. return None
  519. def finish_drivers(nc):
  520. # gonna make this into a common function...
  521. drivers = []
  522. if not hasattr(nc, "drivers"):
  523. # prGreen(f"No Drivers to construct for {nc}")
  524. return # HACK
  525. for driver_item, prop in nc.drivers.items():
  526. if isinstance(prop, list):
  527. for sub_item in prop:
  528. drivers.append(finish_driver(nc, (driver_item, sub_item[1]), sub_item[0]))
  529. else:
  530. drivers.append(finish_driver(nc, driver_item, prop))
  531. from .drivers import CreateDrivers
  532. CreateDrivers(drivers)
  533. from .base_definitions import from_name_filter, to_name_filter
  534. def detect_hierarchy_link(from_node, from_socket, to_node, to_socket,):
  535. if to_node.node_type in ['DUMMY_SCHEMA', 'SCHEMA']:
  536. return False
  537. if (from_socket in from_name_filter) or (to_socket in to_name_filter):
  538. return False
  539. # if from_node.__class__.__name__ in ["UtilityCombineVector", "UtilityCombineThreeBool"]:
  540. # return False
  541. return True
  542. #Dummy classes for logic with node containers, they are not meant to do
  543. # each and every little thing the "real" Blender classes do.
  544. class NodeLink:
  545. from_node = None
  546. from_socket = None
  547. to_node = None
  548. to_socket = None
  549. def __init__(self, from_node, from_socket, to_node, to_socket, multi_input_sort_id=0):
  550. if from_node.signature == to_node.signature:
  551. raise RuntimeError("Cannot connect a node to itself.")
  552. self.from_node = from_node
  553. self.from_socket = from_socket
  554. self.to_node = to_node
  555. self.to_socket = to_socket
  556. self.from_node.outputs[self.from_socket].links.append(self)
  557. # it is the responsibility of the node that uses these links to sort them correctly based on the sort_id
  558. self.multi_input_sort_id = multi_input_sort_id
  559. self.to_node.inputs[self.to_socket].links.append(self)
  560. self.is_hierarchy = detect_hierarchy_link(from_node, from_socket, to_node, to_socket,)
  561. self.is_alive = True
  562. def __repr__(self):
  563. return self.from_node.outputs[self.from_socket].__repr__() + " --> " + self.to_node.inputs[self.to_socket].__repr__()
  564. # link_string = # if I need to colorize output for debugging.
  565. # if self.is_hierarchy:
  566. # return wrapOrange(link_string)
  567. # else:
  568. # return wrapWhite(link_string)
  569. def die(self):
  570. self.is_alive = False
  571. self.to_node.inputs[self.to_socket].flush_links()
  572. self.from_node.outputs[self.from_socket].flush_links()
  573. def insert_node(self, middle_node, middle_node_in, middle_node_out, re_init_hierarchy = True):
  574. to_node = self.to_node
  575. to_socket = self.to_socket
  576. self.to_node = middle_node
  577. self.to_socket = middle_node_in
  578. middle_node.outputs[middle_node_out].connect(to_node, to_socket)
  579. if re_init_hierarchy:
  580. from .utilities import init_connections, init_dependencies
  581. init_connections(self.from_node)
  582. init_connections(middle_node)
  583. init_dependencies(middle_node)
  584. init_dependencies(to_node)
  585. class NodeSocket:
  586. @property # this is a read-only property.
  587. def is_linked(self):
  588. return bool(self.links)
  589. def __init__(self, is_input = False,
  590. node = None, name = None,
  591. traverse_target = None):
  592. self.can_traverse = False # to/from the other side of the parent node
  593. self.traverse_target = None
  594. self.node = node
  595. self.name = name
  596. self.is_input = is_input
  597. self.links = []
  598. if (traverse_target):
  599. self.can_traverse = True
  600. def connect(self, node, socket, sort_id=0):
  601. if (self.is_input):
  602. to_node = self.node; from_node = node
  603. to_socket = self.name; from_socket = socket
  604. else:
  605. from_node = self.node; to_node = node
  606. from_socket = self.name; to_socket = socket
  607. for l in from_node.outputs[from_socket].links:
  608. if l.to_node==to_node and l.to_socket==to_socket:
  609. return None
  610. new_link = NodeLink(
  611. from_node,
  612. from_socket,
  613. to_node,
  614. to_socket,
  615. sort_id)
  616. return new_link
  617. def set_traverse_target(self, traverse_target):
  618. self.traverse_target = traverse_target
  619. self.can_traverse = True
  620. def flush_links(self):
  621. self.links = [l for l in self.links if l.is_alive]
  622. @property
  623. def is_connected(self):
  624. return len(self.links)>0
  625. def __repr__(self):
  626. return self.node.__repr__() + "::" + self.name
  627. # do I need this and the link class above?
  628. class DummyLink:
  629. #gonna use this for faking links to keep the interface consistent
  630. def __init__(self, from_socket, to_socket, nc_from=None, nc_to=None, original_from=None, multi_input_sort_id=0):
  631. self.from_socket = from_socket
  632. self.to_socket = to_socket
  633. self.nc_from = nc_from
  634. self.nc_to = nc_to
  635. self.multi_input_sort_id = multi_input_sort_id
  636. # self.from_node = from_socket.node
  637. # self.to_node = to_socket.node
  638. if (original_from):
  639. self.original_from = original_from
  640. else:
  641. self.original_from = self.from_socket
  642. def __repr__(self):
  643. return(self.nc_from.__repr__()+":"+self.from_socket.name + " -> " + self.nc_to.__repr__()+":"+self.to_socket.name)
  644. # Here we setup some properties that we want every class to have
  645. # I am not using inheritance because I don't like it, I think it adds a bunch of weird complexity
  646. # This, on the otherhand, is basically just extending the class definitions I have with some boilerplate
  647. # and that means I can avoid writing the boilerplate, get the benefits of class inheritance, and none of the bad
  648. #
  649. # I don't want these classes to share a superclass, because I want them to be nothing more than a way to
  650. # package some methods and data together, so my node tree can be like a linked list using basic types like dict
  651. # essentially, I don't have any use for a superclass, but I do need these properties and attributes.
  652. # and now, instead of declaring it, inheriting it, and shadowing it elsewhere, I simply do not declare it unless I need it
  653. #
  654. # I've looked in to using an interface or abstract metaclass but all of those seem more complicated than doing this.
  655. def setup_container(some_class):
  656. # NOTE: DO NOT use default properties except for None, we don't want to share data between classes.
  657. def flush_links(self):
  658. for inp in self.inputs.values():
  659. inp.flush_links()
  660. for out in self.outputs.values():
  661. out.flush_links()
  662. functions = {
  663. # importantly: these below are not properties.
  664. "evaluate_input" : lambda self, input_name, index=0 : evaluate_input(self, input_name, index),
  665. "fill_parameters" : lambda self : fill_parameters(self),
  666. 'flush_links': flush_links,
  667. "bPrepare" : lambda self, bContext=None : None,
  668. "bExecute" : lambda self, bContext=None : None,
  669. "bFinalize" : lambda self, bContext=None : None,
  670. }
  671. for n, f in functions.items():
  672. if not hasattr(some_class, n):
  673. setattr(some_class, n, f)
  674. some_class.__repr__ = lambda self : self.signature.__repr__()