node_common.py 17 KB

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