node_container_common.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  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. # TODO: modify this to work with multi-input nodes
  18. def trace_single_line(node_container, input_name, link_index=0):
  19. # DO: refactor this for new link class
  20. """Traces a line to its input."""
  21. nodes = [node_container]
  22. # Trace a single line
  23. if (socket := node_container.inputs.get(input_name) ):
  24. while (socket.is_linked):
  25. link = socket.links[link_index]; link_index = 0
  26. if (socket := link.from_node.outputs.get(link.from_socket)):
  27. nodes.append(socket.node)
  28. if socket.can_traverse:
  29. socket = socket.traverse_target
  30. else: # this is an output.
  31. break
  32. else:
  33. break
  34. return nodes, socket
  35. # this is same as the other, just flip from/to and in/out
  36. def trace_single_line_up(node_container, output_name,):
  37. """I use this to get the xForm from a link node."""
  38. nodes = [node_container]
  39. if hasattr(node_container, "outputs"):
  40. # Trace a single line
  41. if (socket := node_container.outputs.get(output_name) ):
  42. while (socket.is_linked):
  43. # This is bad, but it's efficient for nodes that only expect
  44. # one path along the given line
  45. link = socket.links[0] # TODO: find out if this is wise.
  46. other = link.to_node.inputs.get(link.to_socket)
  47. if (other):
  48. socket = other
  49. if socket.can_traverse:
  50. socket = socket.traverse_target
  51. nodes.append(socket.node)
  52. else: # this is an input.
  53. nodes.append(socket.node)
  54. break
  55. else:
  56. break
  57. return nodes, socket
  58. def get_target_and_subtarget(node_container, linkOb, input_name = "Target"):
  59. from bpy.types import PoseBone, Object, SplineIKConstraint, ArmatureModifier, HookModifier
  60. subtarget = ''; target = node_container.evaluate_input(input_name)
  61. if target:
  62. if not hasattr(target, "bGetObject"):
  63. prRed(f"No {input_name} target found for {linkOb.name} in {node_container} because there is no connected node, or node is wrong type")
  64. return
  65. if (isinstance(target.bGetObject(), PoseBone)):
  66. subtarget = target.bGetObject().name
  67. target = target.bGetParentArmature()
  68. elif (isinstance(target.bGetObject(), Object) ):
  69. target = target.bGetObject()
  70. else:
  71. raise RuntimeError("Cannot interpret linkOb target!")
  72. if (isinstance(linkOb, SplineIKConstraint)):
  73. if target and target.type not in ["CURVE"]:
  74. raise GraphError(wrapRed("Error: %s requires a Curve input, not %s" %
  75. (node_container, type(target))))
  76. linkOb.target = target# don't get a subtarget
  77. if (input_name == 'Pole Target'):
  78. linkOb.pole_target, linkOb.pole_subtarget = target, subtarget
  79. else:
  80. if hasattr(linkOb, "target"):
  81. linkOb.target = target
  82. if hasattr(linkOb, "object"):
  83. linkOb.object = target
  84. if hasattr(linkOb, "subtarget"):
  85. linkOb.subtarget = subtarget
  86. def setup_custom_props(nc):
  87. from .utilities import get_node_prototype
  88. if nc.signature[0] == 'SCHEMA_AUTOGENERATED':
  89. from .base_definitions import custom_props_types
  90. if nc.__class__.__name__ not in custom_props_types:
  91. # prRed(f"Reminder: figure out how to deal with custom property setting for Schema Node {nc}")
  92. raise RuntimeError(wrapRed(f"Custom Properties not set up for node {nc}"))
  93. return
  94. else:
  95. np = get_node_prototype(nc.signature, nc.base_tree)
  96. if np:
  97. setup_custom_props_from_np(nc, np)
  98. else:
  99. prRed("Failed to setup custom properties for: nc")
  100. def setup_custom_props_from_np(nc, np):
  101. for inp in np.inputs:
  102. if inp.identifier == "__extend__": continue
  103. if not (inp.name in nc.inputs.keys()) :
  104. socket = NodeSocket(is_input = True, name = inp.name, node = nc,)
  105. nc.inputs[inp.name] = socket
  106. nc.parameters[inp.name] = None
  107. for attr_name in ["min", "max", "soft_min", "soft_max", "description"]:
  108. try:
  109. setattr(socket, attr_name, getattr(inp, attr_name))
  110. except AttributeError:
  111. pass
  112. for out in np.outputs:
  113. if out.identifier == "__extend__": continue
  114. if not (out.name in nc.outputs.keys()) :
  115. nc.outputs[out.name] = NodeSocket(is_input = False, name = out.name, node = nc,)
  116. def prepare_parameters(nc):
  117. # some nodes add new parameters at runtime, e.g. Drivers
  118. # so we need to take that stuff from the node_containers that have
  119. # been executed prior to this node.
  120. for s_name, sock in nc.inputs.items():
  121. if not (sock.is_linked):
  122. continue
  123. if (sock.name in sock.links[0].from_node.parameters.keys()):
  124. nc.parameters[s_name] = sock.links[0].from_node.parameters[sock.name]
  125. # should work, this is ugly.
  126. def check_for_driver(node_container, input_name, index = None):
  127. prop = node_container.evaluate_input(input_name)
  128. if (index is not None):
  129. prop = prop[index]
  130. return (prop.__class__.__name__ == 'MantisDriver')
  131. # TODO: this should handle sub-properties better
  132. def evaluate_sockets(nc, c, props_sockets):
  133. # this is neccesary because some things use dict properties for dynamic properties and setattr doesn't work
  134. def safe_setattr(ob, att_name, val):
  135. if ob.__class__.__name__ in ["NodesModifier"]:
  136. ob[att_name]=val
  137. elif c.__class__.__name__ in ["Key"]:
  138. if not val: val=0
  139. ob.key_blocks[att_name].value=val
  140. elif "]." in att_name:
  141. # it is of the form prop[int].prop2
  142. prop=att_name.split('[')[0]
  143. prop1=att_name.split('.')[1]
  144. index = int(att_name.split('[')[1][0])
  145. setattr(getattr(c, prop)[index], prop1, val)
  146. else:
  147. try:
  148. setattr(ob, att_name, val)
  149. except Exception as e:
  150. prRed(ob, att_name, val); raise e
  151. # HACK I think I should do this in __init__
  152. if not hasattr(nc, "drivers"):
  153. nc.drivers = {}
  154. # end HACK
  155. for prop, (sock, default) in props_sockets.items():
  156. # c = nc.bObject
  157. # annoyingly, sometimes the socket is an array
  158. index = None
  159. if isinstance(sock, tuple):
  160. index = sock[1]; sock = sock[0]
  161. if (check_for_driver(nc, sock, index)):
  162. sock = (sock, index)
  163. original_prop = prop
  164. # TODO: deduplicate this terrible hack
  165. if ("." in prop) and not c.__class__.__name__ in ["Key"]: # this is a property of a property...
  166. sub_props = [c]
  167. while ("." in prop):
  168. split_prop = prop.split(".")
  169. prop = split_prop[1]
  170. sub_prop = (split_prop[0])
  171. if ("[" in sub_prop):
  172. sub_prop, index = sub_prop.split("[")
  173. index = int(index[0])
  174. sub_props.append(getattr(sub_props[-1], sub_prop)[index])
  175. else:
  176. sub_props.append(getattr(sub_props[-1], sub_prop))
  177. safe_setattr(sub_props[-1], prop, default)
  178. # this is really stupid
  179. else:
  180. safe_setattr(c, prop, default)
  181. if nc.node_type in ['LINK',]:
  182. printname = wrapOrange(nc.GetxForm().bGetObject().name)
  183. elif nc.node_type in ['XFORM',]:
  184. printname = wrapOrange(nc.bGetObject().name)
  185. else:
  186. printname = wrapOrange(nc)
  187. print("Adding driver %s to %s in %s" % (wrapPurple(original_prop), wrapWhite(nc.signature[-1]), printname))
  188. if c.__class__.__name__ in ["NodesModifier"]:
  189. nc.drivers[sock] = "[\""+original_prop+"\"]" # lol. It is a dict element not a "true" property
  190. elif c.__class__.__name__ in ["Key"]:
  191. nc.drivers[sock] = "key_blocks[\""+original_prop+"\"].value"
  192. else:
  193. nc.drivers[sock] = original_prop
  194. else: # here we can do error checking for the socket if needed
  195. if (index is not None):
  196. safe_setattr(c, prop, nc.evaluate_input(sock)[index])
  197. else: # 'mute' is better than 'enabled'
  198. # UGLY HACK # because it is available in older
  199. if (prop == 'mute'): # Blenders.
  200. safe_setattr(c, prop, not bool(nc.evaluate_input(sock)))
  201. elif (prop == 'hide'): # this will not cast it for me, annoying.
  202. safe_setattr(c, prop, bool(nc.evaluate_input(sock)))
  203. else:
  204. try:
  205. # prRed(c.name, nc, prop, nc.evaluate_input(sock) )
  206. # print( nc.evaluate_input(sock))
  207. # value_eval = nc.evaluate_input(sock)
  208. # just wanna see if we are dealing with some collection
  209. # check hasattr in case it is one of those ["such-and-such"] props, and ignore those
  210. if hasattr(c, prop) and (not isinstance(getattr(c, prop), str)) and hasattr(getattr(c, prop), "__getitem__"):
  211. # prGreen("Doing the thing")
  212. for val_index, value in enumerate(nc.evaluate_input(sock)):
  213. # assume this will work, both because val should have the right number of elements, and because this should be the right data type.
  214. from .drivers import MantisDriver
  215. if isinstance(value, MantisDriver):
  216. getattr(c,prop)[val_index] = default[val_index]
  217. print("Adding driver %s to %s in %s" % (wrapPurple(prop), wrapWhite(nc.signature[-1]), nc))
  218. try:
  219. nc.drivers[sock].append((prop, val_index))
  220. except:
  221. nc.drivers[sock] = [(prop, val_index)]
  222. else:
  223. getattr(c,prop)[val_index] = value
  224. else:
  225. # prOrange("Skipping the Thing", getattr(c, prop))
  226. safe_setattr(c, prop, nc.evaluate_input(sock))
  227. except Exception as e:
  228. prRed(c, nc, prop, sock, nc.evaluate_input(sock))
  229. raise e
  230. def finish_driver(nc, driver_item, prop):
  231. # prWhite(nc, prop)
  232. index = driver_item[1]; driver_sock = driver_item[0]
  233. driver_trace = trace_single_line(nc, driver_sock)
  234. driver_provider, driver_socket = driver_trace[0][-1], driver_trace[1]
  235. if index is not None:
  236. driver = driver_provider.parameters[driver_socket.name][index].copy()
  237. # this is harmless and necessary for the weird ones where the property is a vector too
  238. driver["ind"] = index
  239. else:
  240. driver = driver_provider.parameters[driver_socket.name].copy()
  241. if driver:
  242. # todo: deduplicate this terrible hack
  243. c = None # no idea what this c and sub_prop thing is, HACK?
  244. if hasattr(nc, "bObject"):
  245. c = nc.bObject # STUPID # stupid and bad HACK here too
  246. if ("." in prop) and nc.__class__.__name__ != "DeformerMorphTargetDeform": # this is a property of a property...
  247. sub_props = [c]
  248. while ("." in prop):
  249. split_prop = prop.split(".")
  250. prop = split_prop[1]
  251. sub_prop = (split_prop[0])
  252. if ("[" in sub_prop):
  253. sub_prop, index = sub_prop.split("[")
  254. index = int(index[0])
  255. sub_props.append(getattr(sub_props[-1], sub_prop)[index])
  256. else:
  257. sub_props.append(getattr(sub_props[-1], sub_prop))
  258. driver["owner"] = sub_props[-1]
  259. elif nc.node_type in ['XFORM',] and nc.__class__.__name__ in ['xFormBone']:
  260. # TODO: I really shouldn't have to hardcode this. Look into better solutions.
  261. if prop in ['hide', 'show_wire']: # we need to get the bone, not the pose bone.
  262. bone_col = nc.bGetParentArmature().data.bones
  263. else:
  264. bone_col = nc.bGetParentArmature().pose.bones
  265. driver["owner"] = bone_col[nc.bObject] # we use "unsafe" brackets instead of get() because we want to see any errors that occur
  266. else:
  267. driver["owner"] = nc.bObject
  268. # prPurple("Successfully created driver for %s" % prop)
  269. driver["prop"] = prop
  270. return driver
  271. # prWhite(driver)
  272. else:
  273. prOrange("Provider", driver_provider)
  274. prGreen("socket", driver_socket)
  275. print (index)
  276. prPurple(driver_provider.parameters[driver_socket.name])
  277. prRed("Failed to create driver for %s" % prop)
  278. return None
  279. def finish_drivers(nc):
  280. # gonna make this into a common function...
  281. drivers = []
  282. if not hasattr(nc, "drivers"):
  283. # prGreen(f"No Drivers to construct for {nc}")
  284. return # HACK
  285. for driver_item, prop in nc.drivers.items():
  286. if isinstance(prop, list):
  287. for sub_item in prop:
  288. drivers.append(finish_driver(nc, (driver_item, sub_item[1]), sub_item[0]))
  289. else:
  290. drivers.append(finish_driver(nc, driver_item, prop))
  291. from .drivers import CreateDrivers
  292. CreateDrivers(drivers)