node_container_common.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800
  1. from .utilities import (prRed, prGreen, prPurple, prWhite,
  2. prOrange,
  3. wrapRed, wrapGreen, wrapPurple, wrapWhite,
  4. wrapOrange,)
  5. from .base_definitions import GraphError, CircularDependencyError
  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. # KEEP THIS
  152. # don't modify this.... until the other one actually works.
  153. # def original_get_depth_lines(root):
  154. # path, seek, nc_path = [0,], root, [root,]
  155. # lines, nc_paths = {}, {}
  156. # nc_len = len(root.hierarchy_connections)-1
  157. # curheight=0
  158. # while (path[0] <= nc_len):
  159. # nc_path.append(nc_path[-1].hierarchy_connections[path[-1]])
  160. # if (not (node_lines := lines.get(nc_path[-1].signature, None))):
  161. # node_lines = lines[nc_path[-1].signature] = set()
  162. # if (not (node_paths := nc_paths.get(nc_path[-1].signature, None))):
  163. # node_paths = nc_paths[nc_path[-1].signature] = set()
  164. # node_lines.add(tuple(path)); node_paths.add(tuple(nc_path))
  165. # if not hasattr(nc_path[-1], "hierarchy_connections"):
  166. # # TODO even though I am going to completely rewrite this function, I need to fix this
  167. # prRed(f"{nc_path[-1]} has some sort of stupid hierarchy problem. Ignoring...")
  168. # nc_path[-1].hierarchy_connections = []; nc_path[-1].connected_to = 0
  169. # if nc_path[-1].hierarchy_connections:
  170. # path.append(0); curheight+=1
  171. # else:
  172. # path[curheight] = path[curheight] + 1
  173. # nc_path.pop()
  174. # connected_nodes = nc_path[-1].hierarchy_connections
  175. # if ( path[-1] <= len(connected_nodes)-1 ):
  176. # seek = connected_nodes[path[-1]]
  177. # elif curheight > 0:
  178. # while(len(path) > 1):
  179. # path.pop(); curheight -= 1; path[curheight]+=1; nc_path.pop()
  180. # if ( (len(nc_path)>1) and path[-1] < len(nc_path[-1].hierarchy_connections) ):
  181. # break
  182. # return lines, nc_paths
  183. def num_hierarchy_connections(nc):
  184. num=0
  185. for out in nc.outputs:
  186. for link in out.links:
  187. if link.is_hierarchy: num+=1
  188. return num
  189. def list_hierarchy_connections(nc):
  190. return len(nc.hierarchy_connections)-1
  191. hc=[]
  192. for out in nc.outputs:
  193. for link in out.links:
  194. if link.is_hierarchy: hc.append(link.to_node)
  195. return num
  196. # what this is doing is giving a list of Output-Index that is the path to the given node, from a given root.
  197. # HOW TO REWRITE...
  198. # we simply do the same thing, but we look at the outputs, not the old hierarchy-connections
  199. # we can do the same tree-search but we simply ignore an output if it is not hierarchy.
  200. # the existing code isn't complicated, it's just hard to read. So this new code should be easier to read, too.
  201. def get_depth_lines(root):
  202. from .base_definitions import GraphError
  203. path, nc_path = [0,], [root,]
  204. lines, nc_paths = {}, {}
  205. nc_len = len(root.hierarchy_connections)-1
  206. curheight=0
  207. while (path[0] <= nc_len):
  208. # this doesn't seem to make this any slower. It is good to check it.
  209. if nc_path[-1] in nc_path[:-1]:
  210. raise GraphError(wrapRed(f"Infinite loop detected while depth sorting for root {root}."))
  211. #
  212. nc_path.append(nc_path[-1].hierarchy_connections[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 nc_path[-1].hierarchy_connections: # 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(nc_path[-1].hierarchy_connections)-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(nc_path[-1].hierarchy_connections) ):
  230. break
  231. return lines, nc_paths
  232. # 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.
  233. def get_prepared_depth_lines(root,):
  234. # import pstats, io, cProfile
  235. # from pstats import SortKey
  236. # with cProfile.Profile() as pr:
  237. path, nc_path = [0,], [root,]
  238. lines, nc_paths = {}, {}
  239. nc_len = len(prepared_connections(root, ))-1
  240. curheight=0
  241. while (path[0] <= nc_len):
  242. if nc_path[-1] in nc_path[:-1]:
  243. raise GraphError(wrapRed(f"Infinite loop detected while depth sorting for root {root}."))
  244. nc_path.append(prepared_connections(nc_path[-1], )[path[-1]])
  245. if (not (node_lines := lines.get(nc_path[-1].signature, None))):
  246. node_lines = lines[nc_path[-1].signature] = set()
  247. if (not (node_paths := nc_paths.get(nc_path[-1].signature, None))):
  248. node_paths = nc_paths[nc_path[-1].signature] = set()
  249. node_lines.add(tuple(path)); node_paths.add(tuple(nc_path))
  250. if prepared_connections(nc_path[-1], ): # if there is at least one element
  251. path.append(0); curheight+=1
  252. else: # at this point, nc_path is one longer than path because path is a segment between two nodes
  253. # or more siimply, because nc_path has the root in it and path starts with the first node
  254. path[curheight] = path[curheight] + 1
  255. nc_path.pop() # so we go back and horizontal
  256. if path[-1] <= len(prepared_connections(nc_path[-1], ))-1:
  257. pass # and continue if we can
  258. elif curheight > 0: # otherwise we keep going back
  259. while(len(path) > 1):
  260. path.pop(); curheight -= 1; path[curheight]+=1; nc_path.pop()
  261. if (len(nc_path)>1) and path[-1] < len(prepared_connections(nc_path[-1], ) ):
  262. break
  263. # from the Python docs at https://docs.python.org/3/library/profile.html#module-cProfile
  264. # s = io.StringIO()
  265. # sortby = SortKey.TIME
  266. # ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
  267. # ps.print_stats()
  268. # print(s.getvalue())
  269. return lines, nc_paths
  270. def prepared_connections(nc):
  271. if nc.prepared:
  272. return nc.hierarchy_connections
  273. else:
  274. ret = []
  275. for hc in nc.hierarchy_connections:
  276. if hc.prepared:
  277. ret.append(hc)
  278. return ret
  279. # return [hc for hc in nc.hierarchy_connections if hc.prepared]
  280. def node_depth(lines):
  281. maxlen = 0
  282. for line in lines:
  283. if ( (l := len(line) ) > maxlen):
  284. maxlen = l
  285. return maxlen
  286. #TODO rewrite this so it'll work with new nc_path thing
  287. # not a high priority bc this was debugging code for something that
  288. # works and has since ben refactored to work better
  289. def printable_path(nc, path, no_wrap = False):
  290. string = ""; cur_nc = nc
  291. #DO: find out if the copy is necessary
  292. path = path.copy(); path.reverse()
  293. dummy = lambda a : a
  294. while path:
  295. wrap = dummy
  296. if not no_wrap:
  297. wrap=wrapWhite
  298. if (cur_nc.node_type == 'DRIVER'):
  299. wrap = wrapPurple
  300. elif (cur_nc.node_type == 'XFORM'):
  301. wrap = wrapOrange
  302. elif (cur_nc.node_type == 'LINK'):
  303. wrap = wrapGreen
  304. string += wrap(cur_nc.__repr__()) + " -> "
  305. try:
  306. cur_nc = get_from_path(cur_nc, [path.pop()] )
  307. except IndexError:
  308. string = string[:-4]
  309. return string
  310. string = string[:-4]
  311. return string
  312. # why is this not printing groups in brackets?
  313. def get_parent(node_container, type = 'XFORM'):
  314. # type variable for selecting whether to get either
  315. # the parent xForm or the inheritance node
  316. node_line, socket = trace_single_line(node_container, "Relationship")
  317. parent_nc = None
  318. for i in range(len(node_line)):
  319. # check each of the possible parent types.
  320. if ( (node_line[ i ].__class__.__name__ == 'LinkInherit') ):
  321. try: # it's the next one
  322. if (type == 'XFORM'):
  323. return node_line[ i + 1 ]
  324. else: # type = 'LINK'
  325. return node_line[ i ]
  326. except IndexError: # if there is no next one...
  327. return None # then there's no parent!
  328. return None
  329. # TODO!
  330. #
  331. # make this do shorthand parenting - if no parent, then use World
  332. # if the parent node is skipped, use the previous node (an xForm)
  333. # with default settings.
  334. # it is OK to generate a new, "fake" node container for this!
  335. def get_target_and_subtarget(node_container, linkOb, input_name = "Target"):
  336. from bpy.types import PoseBone, Object, SplineIKConstraint, ArmatureModifier
  337. subtarget = ''; target = node_container.evaluate_input(input_name)
  338. if target:
  339. if (isinstance(target.bGetObject(), PoseBone)):
  340. subtarget = target.bGetObject().name
  341. target = target.bGetParentArmature()
  342. elif (isinstance(target.bGetObject(), Object) ):
  343. target = target.bGetObject()
  344. else:
  345. raise RuntimeError("Cannot interpret linkOb target!")
  346. if (isinstance(linkOb, SplineIKConstraint)):
  347. if target and target.type not in ["CURVE"]:
  348. raise GraphError(wrapRed("Error: %s requires a Curve input, not %s" %
  349. (node_container, type(target))))
  350. # don't get a subtarget
  351. linkOb.target = target
  352. elif (isinstance(linkOb, ArmatureModifier)):
  353. linkOb.object = target
  354. elif (input_name == 'Target'): # this is sloppy, but it werks
  355. linkOb.target, linkOb.subtarget = target, subtarget
  356. elif (input_name == 'Pole Target'):
  357. linkOb.pole_target, linkOb.pole_subtarget = target, subtarget
  358. else: # this is really just for Armature Constraint targets...
  359. linkOb.target, linkOb.subtarget = target, subtarget
  360. def setup_custom_props(nc):
  361. from .utilities import get_node_prototype
  362. if nc.signature[0] == 'SCHEMA_AUTOGENERATED':
  363. from .base_definitions import custom_props_types
  364. if nc.__class__.__name__ not in custom_props_types:
  365. # prRed(f"Reminder: figure out how to deal with custom property setting for Schema Node {nc}")
  366. raise RuntimeError(wrapRed(f"Custom Properties not set up for node {nc}"))
  367. return
  368. else:
  369. np = get_node_prototype(nc.signature, nc.base_tree)
  370. if np:
  371. setup_custom_props_from_np(nc, np)
  372. else:
  373. prRed("Failed to setup custom properties for: nc")
  374. def setup_custom_props_from_np(nc, np):
  375. for inp in np.inputs:
  376. if inp.identifier == "__extend__": continue
  377. if not (inp.name in nc.inputs.keys()) :
  378. socket = NodeSocket(is_input = True, name = inp.name, node = nc,)
  379. nc.inputs[inp.name] = socket
  380. nc.parameters[inp.name] = None
  381. for attr_name in ["min", "max", "soft_min", "soft_max", "description"]:
  382. try:
  383. setattr(socket, attr_name, getattr(inp, attr_name))
  384. except AttributeError:
  385. pass
  386. for out in np.outputs:
  387. if out.identifier == "__extend__": continue
  388. if not (out.name in nc.outputs.keys()) :
  389. nc.outputs[out.name] = NodeSocket(is_input = False, name = out.name, node = nc,)
  390. def prepare_parameters(nc):
  391. # some nodes add new parameters at runtime, e.g. Drivers
  392. # so we need to take that stuff from the node_containers that have
  393. # been executed prior to this node.
  394. for s_name, sock in nc.inputs.items():
  395. if not (sock.is_linked):
  396. continue
  397. if (sock.name in sock.links[0].from_node.parameters.keys()):
  398. nc.parameters[s_name] = sock.links[0].from_node.parameters[sock.name]
  399. # should work, this is ugly.
  400. # TODO: this should handle sub-properties better
  401. def evaluate_sockets(nc, c, props_sockets):
  402. # this is neccesary because some things use dict properties for dynamic properties and setattr doesn't work
  403. def safe_setattr(ob, att_name, val):
  404. if ob.__class__.__name__ in ["NodesModifier"]:
  405. ob[att_name]=val
  406. elif c.__class__.__name__ in ["Key"]:
  407. if not val: val=0
  408. ob.key_blocks[att_name].value=val
  409. elif "]." in att_name:
  410. # it is of the form prop[int].prop2
  411. prop=att_name.split('[')[0]
  412. prop1=att_name.split('.')[1]
  413. index = int(att_name.split('[')[1][0])
  414. setattr(getattr(c, prop)[index], prop1, val)
  415. else:
  416. try:
  417. setattr(ob, att_name, val)
  418. except Exception as e:
  419. prRed(ob, att_name, val); raise e
  420. # HACK I think I should do this in __init__
  421. if not hasattr(nc, "drivers"):
  422. nc.drivers = {}
  423. # end HACK
  424. for prop, (sock, default) in props_sockets.items():
  425. # c = nc.bObject
  426. # annoyingly, sometimes the socket is an array
  427. index = None
  428. if isinstance(sock, tuple):
  429. index = sock[1]; sock = sock[0]
  430. if (check_for_driver(nc, sock, index)):
  431. sock = (sock, index)
  432. original_prop = prop
  433. # TODO: deduplicate this terrible hack
  434. if ("." in prop) and not c.__class__.__name__ in ["Key"]: # this is a property of a property...
  435. sub_props = [c]
  436. while ("." in prop):
  437. split_prop = prop.split(".")
  438. prop = split_prop[1]
  439. sub_prop = (split_prop[0])
  440. if ("[" in sub_prop):
  441. sub_prop, index = sub_prop.split("[")
  442. index = int(index[0])
  443. sub_props.append(getattr(sub_props[-1], sub_prop)[index])
  444. else:
  445. sub_props.append(getattr(sub_props[-1], sub_prop))
  446. safe_setattr(sub_props[-1], prop, default)
  447. # this is really stupid
  448. else:
  449. safe_setattr(c, prop, default)
  450. if nc.node_type in ['LINK',]:
  451. printname = wrapOrange(nc.GetxForm().bGetObject().name)
  452. elif nc.node_type in ['XFORM',]:
  453. printname = wrapOrange(nc.bGetObject().name)
  454. else:
  455. printname = wrapOrange(nc)
  456. print("Adding driver %s to %s in %s" % (wrapPurple(original_prop), wrapWhite(nc.signature[-1]), printname))
  457. if c.__class__.__name__ in ["NodesModifier"]:
  458. nc.drivers[sock] = "[\""+original_prop+"\"]" # lol. It is a dict element not a "true" property
  459. elif c.__class__.__name__ in ["Key"]:
  460. nc.drivers[sock] = "key_blocks[\""+original_prop+"\"].value"
  461. else:
  462. nc.drivers[sock] = original_prop
  463. else: # here we can do error checking for the socket if needed
  464. if (index is not None):
  465. safe_setattr(c, prop, nc.evaluate_input(sock)[index])
  466. else: # 'mute' is better than 'enabled'
  467. # UGLY HACK # because it is available in older
  468. if (prop == 'mute'): # Blenders.
  469. safe_setattr(c, prop, not nc.evaluate_input(sock))
  470. else:
  471. try:
  472. # prRed(c.name, nc, prop, nc.evaluate_input(sock) )
  473. # print( nc.evaluate_input(sock))
  474. # value_eval = nc.evaluate_input(sock)
  475. # just wanna see if we are dealing with some collection
  476. # check hasattr in case it is one of those ["such-and-such"] props, and ignore those
  477. if hasattr(c, prop) and (not isinstance(getattr(c, prop), str)) and hasattr(getattr(c, prop), "__getitem__"):
  478. # prGreen("Doing the thing")
  479. for val_index, value in enumerate(nc.evaluate_input(sock)):
  480. # assume this will work, both because val should have the right number of elements, and because this should be the right data type.
  481. from .drivers import MantisDriver
  482. if isinstance(value, MantisDriver):
  483. getattr(c,prop)[val_index] = default[val_index]
  484. print("Adding driver %s to %s in %s" % (wrapPurple(prop), wrapWhite(nc.signature[-1]), nc))
  485. try:
  486. nc.drivers[sock].append((prop, val_index))
  487. except:
  488. nc.drivers[sock] = [(prop, val_index)]
  489. else:
  490. getattr(c,prop)[val_index] = value
  491. else:
  492. # prOrange("Skipping the Thing", getattr(c, prop))
  493. safe_setattr(c, prop, nc.evaluate_input(sock))
  494. except Exception as e:
  495. prRed(c, nc, prop, sock, nc.evaluate_input(sock))
  496. raise e
  497. def finish_driver(nc, driver_item, prop):
  498. # prWhite(nc, prop)
  499. index = driver_item[1]; driver_sock = driver_item[0]
  500. driver_trace = trace_single_line(nc, driver_sock)
  501. driver_provider, driver_socket = driver_trace[0][-1], driver_trace[1]
  502. if index is not None:
  503. driver = driver_provider.parameters[driver_socket.name][index].copy()
  504. # this is harmless and necessary for the weird ones where the property is a vector too
  505. driver["ind"] = index
  506. else:
  507. driver = driver_provider.parameters[driver_socket.name].copy()
  508. if driver:
  509. # todo: deduplicate this terrible hack
  510. c = None # no idea what this c and sub_prop thing is, HACK?
  511. if hasattr(nc, "bObject"):
  512. c = nc.bObject # STUPID # stupid and bad HACK here too
  513. if ("." in prop) and nc.__class__.__name__ != "DeformerMorphTargetDeform": # this is a property of a property...
  514. sub_props = [c]
  515. while ("." in prop):
  516. split_prop = prop.split(".")
  517. prop = split_prop[1]
  518. sub_prop = (split_prop[0])
  519. if ("[" in sub_prop):
  520. sub_prop, index = sub_prop.split("[")
  521. index = int(index[0])
  522. sub_props.append(getattr(sub_props[-1], sub_prop)[index])
  523. else:
  524. sub_props.append(getattr(sub_props[-1], sub_prop))
  525. driver["owner"] = sub_props[-1]
  526. elif nc.node_type in ['XFORM',] and nc.__class__.__name__ in ['xFormBone']:
  527. # TODO: I really shouldn't have to hardcode this. Look into better solutions.
  528. if prop in ['hide', 'show_wire']: # we need to get the bone, not the pose bone.
  529. bone_col = nc.bGetParentArmature().data.bones
  530. else:
  531. bone_col = nc.bGetParentArmature().pose.bones
  532. driver["owner"] = bone_col[nc.bObject] # we use "unsafe" brackets instead of get() because we want to see any errors that occur
  533. else:
  534. driver["owner"] = nc.bObject
  535. # prPurple("Successfully created driver for %s" % prop)
  536. driver["prop"] = prop
  537. return driver
  538. # prWhite(driver)
  539. else:
  540. prOrange("Provider", driver_provider)
  541. prGreen("socket", driver_socket)
  542. print (index)
  543. prPurple(driver_provider.parameters[driver_socket.name])
  544. prRed("Failed to create driver for %s" % prop)
  545. return None
  546. def finish_drivers(nc):
  547. # gonna make this into a common function...
  548. drivers = []
  549. if not hasattr(nc, "drivers"):
  550. # prGreen(f"No Drivers to construct for {nc}")
  551. return # HACK
  552. for driver_item, prop in nc.drivers.items():
  553. if isinstance(prop, list):
  554. for sub_item in prop:
  555. drivers.append(finish_driver(nc, (driver_item, sub_item[1]), sub_item[0]))
  556. else:
  557. drivers.append(finish_driver(nc, driver_item, prop))
  558. from .drivers import CreateDrivers
  559. CreateDrivers(drivers)
  560. from .base_definitions import from_name_filter, to_name_filter
  561. def detect_hierarchy_link(from_node, from_socket, to_node, to_socket,):
  562. if to_node.node_type in ['DUMMY_SCHEMA', 'SCHEMA']:
  563. return False
  564. if (from_socket in from_name_filter) or (to_socket in to_name_filter):
  565. return False
  566. # if from_node.__class__.__name__ in ["UtilityCombineVector", "UtilityCombineThreeBool"]:
  567. # return False
  568. return True
  569. #Dummy classes for logic with node containers, they are not meant to do
  570. # each and every little thing the "real" Blender classes do.
  571. class NodeLink:
  572. from_node = None
  573. from_socket = None
  574. to_node = None
  575. to_socket = None
  576. def __init__(self, from_node, from_socket, to_node, to_socket, multi_input_sort_id=0):
  577. if from_node.signature == to_node.signature:
  578. raise RuntimeError("Cannot connect a node to itself.")
  579. self.from_node = from_node
  580. self.from_socket = from_socket
  581. self.to_node = to_node
  582. self.to_socket = to_socket
  583. self.from_node.outputs[self.from_socket].links.append(self)
  584. # it is the responsibility of the node that uses these links to sort them correctly based on the sort_id
  585. self.multi_input_sort_id = multi_input_sort_id
  586. self.to_node.inputs[self.to_socket].links.append(self)
  587. self.is_hierarchy = detect_hierarchy_link(from_node, from_socket, to_node, to_socket,)
  588. self.is_alive = True
  589. def __repr__(self):
  590. return self.from_node.outputs[self.from_socket].__repr__() + " --> " + self.to_node.inputs[self.to_socket].__repr__()
  591. # link_string = # if I need to colorize output for debugging.
  592. # if self.is_hierarchy:
  593. # return wrapOrange(link_string)
  594. # else:
  595. # return wrapWhite(link_string)
  596. def die(self):
  597. self.is_alive = False
  598. self.to_node.inputs[self.to_socket].flush_links()
  599. self.from_node.outputs[self.from_socket].flush_links()
  600. def insert_node(self, middle_node, middle_node_in, middle_node_out, re_init_hierarchy = True):
  601. to_node = self.to_node
  602. to_socket = self.to_socket
  603. self.to_node = middle_node
  604. self.to_socket = middle_node_in
  605. middle_node.outputs[middle_node_out].connect(to_node, to_socket)
  606. if re_init_hierarchy:
  607. from .utilities import init_connections, init_dependencies
  608. init_connections(self.from_node)
  609. init_connections(middle_node)
  610. init_dependencies(middle_node)
  611. init_dependencies(to_node)
  612. class NodeSocket:
  613. @property # this is a read-only property.
  614. def is_linked(self):
  615. return bool(self.links)
  616. def __init__(self, is_input = False,
  617. node = None, name = None,
  618. traverse_target = None):
  619. self.can_traverse = False # to/from the other side of the parent node
  620. self.traverse_target = None
  621. self.node = node
  622. self.name = name
  623. self.is_input = is_input
  624. self.links = []
  625. if (traverse_target):
  626. self.can_traverse = True
  627. def connect(self, node, socket, sort_id=0):
  628. if (self.is_input):
  629. to_node = self.node; from_node = node
  630. to_socket = self.name; from_socket = socket
  631. else:
  632. from_node = self.node; to_node = node
  633. from_socket = self.name; to_socket = socket
  634. for l in from_node.outputs[from_socket].links:
  635. if l.to_node==to_node and l.to_socket==to_socket:
  636. return None
  637. new_link = NodeLink(
  638. from_node,
  639. from_socket,
  640. to_node,
  641. to_socket,
  642. sort_id)
  643. return new_link
  644. def set_traverse_target(self, traverse_target):
  645. self.traverse_target = traverse_target
  646. self.can_traverse = True
  647. def flush_links(self):
  648. self.links = [l for l in self.links if l.is_alive]
  649. @property
  650. def is_connected(self):
  651. return len(self.links)>0
  652. def __repr__(self):
  653. return self.node.__repr__() + "::" + self.name
  654. # do I need this and the link class above?
  655. class DummyLink:
  656. #gonna use this for faking links to keep the interface consistent
  657. def __init__(self, from_socket, to_socket, nc_from=None, nc_to=None, original_from=None, multi_input_sort_id=0):
  658. self.from_socket = from_socket
  659. self.to_socket = to_socket
  660. self.nc_from = nc_from
  661. self.nc_to = nc_to
  662. self.multi_input_sort_id = multi_input_sort_id
  663. # self.from_node = from_socket.node
  664. # self.to_node = to_socket.node
  665. if (original_from):
  666. self.original_from = original_from
  667. else:
  668. self.original_from = self.from_socket
  669. def __repr__(self):
  670. return(self.nc_from.__repr__()+":"+self.from_socket.name + " -> " + self.nc_to.__repr__()+":"+self.to_socket.name)
  671. # Here we setup some properties that we want every class to have
  672. # I am not using inheritance because I don't like it, I think it adds a bunch of weird complexity
  673. # This, on the otherhand, is basically just extending the class definitions I have with some boilerplate
  674. # and that means I can avoid writing the boilerplate, get the benefits of class inheritance, and none of the bad
  675. #
  676. # I don't want these classes to share a superclass, because I want them to be nothing more than a way to
  677. # package some methods and data together, so my node tree can be like a linked list using basic types like dict
  678. # essentially, I don't have any use for a superclass, but I do need these properties and attributes.
  679. # and now, instead of declaring it, inheriting it, and shadowing it elsewhere, I simply do not declare it unless I need it
  680. #
  681. # I've looked in to using an interface or abstract metaclass but all of those seem more complicated than doing this.
  682. def setup_container(some_class):
  683. # NOTE: DO NOT use default properties except for None, we don't want to share data between classes.
  684. # @property
  685. # def dependencies(self):
  686. # c = []
  687. # for i in nc.inputs.values():
  688. # for l in i.links:
  689. # if l.is_hierarchy:
  690. # c.append(l.from_node)
  691. # def default_prepare(self, bContext=None):
  692. # for dep in self.dependencies:
  693. # def del_me(self):
  694. # from itertools import chain
  695. # links = []
  696. # for socket in chain(self.inputs, self.outputs):
  697. # for i in len(socket.links):
  698. # links.append(l)
  699. # for l in links:
  700. # del l
  701. # # just make sure to clean up links.
  702. def flush_links(self):
  703. for inp in self.inputs.values():
  704. inp.flush_links()
  705. for out in self.outputs.values():
  706. out.flush_links()
  707. functions = {
  708. # "dependencies": dependencies,
  709. # "num_dependencies": property(lambda self: len(self.dependencies)),
  710. # "connections": some-property, # this one is potentially better as a property tho
  711. # "hierarchy_connections": some-property, # I tried making this a getter property but it was a bit slower.
  712. # importantly: these below are not properties.
  713. "evaluate_input" : lambda self, input_name, index=0 : evaluate_input(self, input_name, index),
  714. "fill_parameters" : lambda self : fill_parameters(self),
  715. 'flush_links': flush_links,
  716. # then I am not sure I want these because it is really easy to check
  717. # and it may be helpful to know if a node doesn't have one of these properties.
  718. "bPrepare" : lambda self, bContext=None : None,
  719. "bExecute" : lambda self, bContext=None : None,
  720. "bFinalize" : lambda self, bContext=None : None,
  721. # "__del__" : del_me,
  722. }
  723. for n, f in functions.items():
  724. if not hasattr(some_class, n):
  725. setattr(some_class, n, f)
  726. some_class.__repr__ = lambda self : self.signature.__repr__()