node_container_common.py 14 KB

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