node_common.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  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. # Utility to print links nicely for end user.
  12. def print_link_list(links):
  13. ret_str = ""
  14. for link in links:
  15. ret_str+= link.__repr__() + '\n'
  16. return ret_str
  17. # I've moved this to a separate function because the trace functions
  18. # are kind of hard to understand and the cycle check breaks the flow
  19. # of the code.
  20. def detect_cycle_in_trace(link, link_circle):
  21. if link.is_hierarchy:
  22. if link in link_circle:
  23. raise GraphError( "There is a cycle in the following links:\n" +
  24. print_link_list(link_circle))
  25. link_circle.append(link)
  26. #TODO: refactor the socket definitions so this becomes unnecessary.
  27. def get_socket_value(node_socket):
  28. value = None
  29. if hasattr(node_socket, "default_value"):
  30. value = node_socket.default_value
  31. if node_socket.bl_idname == 'MatrixSocket':
  32. value = node_socket.TellValue()
  33. return value
  34. # NOTE this is a dupe of trace_single_line, and it must be used
  35. # at any time before the tree is parsed (to check for cycles)
  36. # I don't want or need to pay the performance cost to check this everywhere
  37. def trace_single_line_safe(mantis_node, input_name, link_index=0):
  38. # DO: refactor this for new link class
  39. """Traces a line to its input."""
  40. nodes = [mantis_node]
  41. # a test for cyclic connections
  42. link_circle = []
  43. # Trace a single line
  44. if (socket := mantis_node.inputs.get(input_name) ):
  45. while (socket.is_linked):
  46. link = socket.links[link_index]; link_index = 0
  47. # should throw an error if it is in the set
  48. if detect_cycle_in_trace(link, link_circle):
  49. return
  50. if (socket := link.from_node.outputs.get(link.from_socket)):
  51. nodes.append(socket.node)
  52. if socket.can_traverse:
  53. socket = socket.traverse_target
  54. else: # this is an output.
  55. break
  56. else:
  57. break
  58. return nodes, socket
  59. def trace_single_line(mantis_node, input_name, link_index=0):
  60. # DO: refactor this for new link class
  61. """Traces a line to its input."""
  62. nodes = [mantis_node]
  63. # a test for cyclic connections
  64. if (socket := mantis_node.inputs.get(input_name) ):
  65. while (socket.is_linked):
  66. link = socket.links[link_index]; link_index = 0
  67. if (socket := link.from_node.outputs.get(link.from_socket)):
  68. nodes.append(socket.node)
  69. if socket.can_traverse:
  70. socket = socket.traverse_target
  71. else: # this is an output.
  72. break
  73. else:
  74. break
  75. return nodes, socket
  76. def trace_single_line_up(mantis_node, output_name,):
  77. """I use this to get the xForm from a link node."""
  78. nodes = [mantis_node]
  79. if hasattr(mantis_node, "outputs"):
  80. # Trace a single line
  81. if (socket := mantis_node.outputs.get(output_name) ):
  82. while (socket.is_linked):
  83. # This is bad, but it's efficient for nodes that only expect
  84. # one path along the given line
  85. link = socket.links[0] # TODO: find out if this is wise.
  86. other = link.to_node.inputs.get(link.to_socket)
  87. if (other):
  88. socket = other
  89. if socket.can_traverse:
  90. socket = socket.traverse_target
  91. nodes.append(socket.node)
  92. else: # this is an input.
  93. nodes.append(socket.node)
  94. break
  95. else:
  96. break
  97. return nodes, socket
  98. def trace_line_up_branching(mantis_node : MantisNode, output_name : str,
  99. break_condition : Callable = lambda mantis_node : False):
  100. """ Returns all leaf nodes at the ends of branching lines from an output."""
  101. leaf_nodes = []
  102. if hasattr(mantis_node, "outputs"):
  103. # Trace a single line
  104. if (socket := mantis_node.outputs.get(output_name) ):
  105. check_sockets={socket}
  106. while (check_sockets):
  107. # This is bad, but it's efficient for nodes that only expect
  108. # one path along the given line
  109. socket = check_sockets.pop()
  110. for link in socket.links:
  111. other = link.to_node.inputs.get(link.to_socket)
  112. if (other):
  113. socket = other
  114. if break_condition(socket.node):
  115. leaf_nodes.append(socket.node)
  116. elif socket.is_input and socket.can_traverse:
  117. check_sockets.add(socket.traverse_target)
  118. else: # this is an input.
  119. leaf_nodes.append(socket.node)
  120. return leaf_nodes
  121. # this function exists to walk back to the first ancestor node of XFORM or LINK without
  122. # breaking the traverse behaviour which I still need for various reasons.
  123. def get_parent_xForm_info(mantis_node, socket_name = 'Relationship'):
  124. from ..mantis_dataclasses import xForm_info
  125. # we re-implement the trace logic here for performance.
  126. # because this kind of connection has traversal (for Links to find their children), we must
  127. # simply seek back to the first available xForm or Link and get its xForm info.\
  128. parent_node = None
  129. if (socket := mantis_node.inputs.get(socket_name) ):
  130. while (socket.is_linked):
  131. link = socket.links[0]
  132. if (socket := link.from_node.outputs.get(link.from_socket)):
  133. if socket.node.node_type in ['LINK', 'XFORM']:
  134. parent_node = socket.node; break
  135. if socket.can_traverse:
  136. socket = socket.traverse_target
  137. else: # this is an output.
  138. break
  139. else:
  140. break
  141. parent_xForm_info = None
  142. if parent_node:
  143. if parent_node.node_type == 'XFORM':
  144. parent_xForm_info = parent_node.parameters['xForm Out']
  145. else:
  146. if 'Output Relationship' in parent_node.parameters.keys():
  147. parent_xForm_info = parent_node.parameters['Output Relationship']
  148. else: # TODO refactor Inherit Node to have the same output as the other link nodes.
  149. parent_xForm_info = parent_node.parameters['Inheritance']
  150. if parent_xForm_info is None: parent_xForm_info = xForm_info() # default to empty
  151. return parent_xForm_info
  152. def setup_custom_property_inputs_outputs(mantis_node):
  153. from .utilities import get_ui_node
  154. if mantis_node.signature[0] == 'SCHEMA_AUTOGENERATED':
  155. from ..base_definitions import custom_props_types
  156. if mantis_node.__class__.__name__ not in custom_props_types:
  157. raise RuntimeError(wrapRed(f"Custom Properties not allowed for class {mantis_node.__class__.__name__}"))
  158. return
  159. else:
  160. ui_node = get_ui_node(mantis_node.ui_signature, mantis_node.base_tree)
  161. if ui_node:
  162. setup_custom_props_from_np(mantis_node, ui_node)
  163. else:
  164. prRed(f"Failed to setup custom properties for: {mantis_node.ui_signature}")
  165. def setup_custom_props_from_np(mantis_node, ui_node):
  166. for inp in ui_node.inputs:
  167. if inp.identifier == "__extend__": continue
  168. if not (inp.name in mantis_node.inputs.keys()) :
  169. socket = NodeSocket(is_input = True, name = inp.name, node = mantis_node,)
  170. mantis_node.inputs[inp.name] = socket
  171. mantis_node.parameters[inp.name] = None
  172. for attr_name in ["min", "max", "soft_min", "soft_max", "description"]:
  173. try:
  174. setattr(socket, attr_name, getattr(inp, attr_name))
  175. except AttributeError:
  176. pass
  177. for out in ui_node.outputs:
  178. if out.identifier == "__extend__": continue
  179. if not (out.name in mantis_node.outputs.keys()) :
  180. mantis_node.outputs[out.name] = NodeSocket(is_input = False, name = out.name, node = mantis_node,)
  181. def prepare_parameters(mantis_node):
  182. # some nodes add new parameters at runtime, e.g. Drivers
  183. # so we need to take that stuff from the mantis_nodes that have
  184. # been executed prior to this node.
  185. for s_name, sock in mantis_node.inputs.items():
  186. if not (sock.is_linked):
  187. continue
  188. if (sock.name in sock.links[0].from_node.parameters.keys()):
  189. mantis_node.parameters[s_name] = sock.links[0].from_node.parameters[sock.name]
  190. # should work, this is ugly.
  191. def check_for_driver(mantis_node, input_name, value_index = None, array_index = 0, combo_property=False):
  192. prop = mantis_node.evaluate_input(input_name, array_index)
  193. if (value_index is not None):
  194. try:
  195. prop = prop[value_index]
  196. except KeyError: # We'll try to use the driver for all three channels.
  197. pass
  198. # TODO an Index or Key error here should have a reasonable error message
  199. return (prop.__class__.__name__ == 'MantisDriver')
  200. def print_driver_item(mantis_node, b_object, original_prop):
  201. if mantis_node.node_type in ['LINK',]:
  202. printname = wrapOrange(b_object.id_data.name)
  203. elif mantis_node.node_type in ['XFORM',]:
  204. printname = wrapOrange(mantis_node.bGetObject().name)
  205. else:
  206. printname = wrapOrange(mantis_node)
  207. print("Adding driver %s to %s in %s" % (wrapPurple(original_prop), wrapWhite(mantis_node.signature[-1]), printname))
  208. # HACK This is the most hideous voodoo black magic fucntion in the entire program
  209. # but everything depends on it and it works
  210. def evaluate_sockets(mantis_node, b_object, props_sockets,):
  211. # this is neccesary because some things use dict properties for dynamic properties and setattr doesn't work
  212. def safe_setattr(ob, att_name, val):
  213. if ob.__class__.__name__ in ["NodesModifier"]:
  214. ob[att_name]=val
  215. elif b_object.__class__.__name__ in ["Key"]:
  216. if not val: val=0
  217. ob.key_blocks[att_name].value=val
  218. elif "]." in att_name:
  219. # it is of the form prop[int].prop2
  220. prop=att_name.split('[')[0]
  221. prop1=att_name.split('.')[1]
  222. get_index = int(att_name.split('[')[1][0])
  223. setattr(getattr(b_object, prop)[get_index], prop1, val)
  224. else:
  225. try:
  226. setattr(ob, att_name, val)
  227. except Exception as e:
  228. print ("ob, att_name, val")
  229. prRed(ob, att_name, val)
  230. raise e
  231. for prop, filler_item in props_sockets.items():
  232. if isinstance(filler_item, tuple):
  233. raise NotImplementedError(mantis_node)
  234. sock = filler_item.socket_name
  235. default = filler_item.default_value
  236. value_index = filler_item.value_index
  237. array_index = filler_item.array_index
  238. combo_property = filler_item.combo_property
  239. driver_key = (sock, value_index, array_index)
  240. if (check_for_driver(mantis_node, sock, value_index, array_index, combo_property)):
  241. original_prop = prop
  242. # TODO: deduplicate this terrible hack
  243. if ("." in prop) and not b_object.__class__.__name__ in ["Key"]: # this is a property of a property...
  244. sub_props = [b_object]
  245. while ("." in prop):
  246. split_prop = prop.split(".")
  247. prop = split_prop[1]
  248. sub_prop = (split_prop[0])
  249. if ("[" in sub_prop):
  250. sub_prop, sub_index = sub_prop.split("[")
  251. sub_prop_index = int(sub_index[0])
  252. sub_props.append(getattr(sub_props[-1], sub_prop)[sub_prop_index])
  253. else:
  254. sub_props.append(getattr(sub_props[-1], sub_prop))
  255. safe_setattr(sub_props[-1], prop, default)
  256. # this is really stupid
  257. else:
  258. safe_setattr(b_object, prop, default)
  259. print_driver_item( mantis_node, b_object, original_prop)
  260. if b_object.__class__.__name__ in ["NodesModifier"]:
  261. mantis_node.drivers[driver_key] = "[\""+original_prop+"\"]" # lol. It is a dict element not a "true" property
  262. elif b_object.__class__.__name__ in ["Key"]:
  263. mantis_node.drivers[driver_key] = "key_blocks[\""+original_prop+"\"].value"
  264. else:
  265. mantis_node.drivers[driver_key] = original_prop
  266. else: # here we can do error checking for the socket if needed
  267. if (value_index is not None):
  268. safe_setattr(b_object, prop, mantis_node.evaluate_input(sock)[value_index])
  269. else: # 'mute' is better than 'enabled'
  270. # UGLY HACK # because it is available in older
  271. if (prop == 'mute'): # Blenders.
  272. safe_setattr(b_object, prop, not bool(mantis_node.evaluate_input(sock)))
  273. elif (prop == 'hide'): # this will not cast it for me, annoying.
  274. safe_setattr(b_object, prop, bool(mantis_node.evaluate_input(sock)))
  275. else:
  276. try:
  277. # just wanna see if we are dealing with some collection
  278. # check hasattr in case it is one of those ["such-and-such"] props, and ignore those
  279. if hasattr(b_object, prop) and (not isinstance(getattr(b_object, prop), str)) and hasattr(getattr(b_object, prop), "__getitem__"):
  280. # prGreen("Doing the thing")
  281. for val_index, value in enumerate(mantis_node.evaluate_input(sock)):
  282. # assume this will work, both because val should have the right number of elements, and because this should be the right data type.
  283. from .drivers import MantisDriver
  284. if isinstance(value, MantisDriver):
  285. getattr(b_object,prop)[val_index] = default[val_index]
  286. print("Adding driver %s to %s in %s" % (wrapPurple(prop), wrapWhite(mantis_node.signature[-1]), mantis_node))
  287. try:
  288. mantis_node.drivers[sock].append((prop, val_index))
  289. except:
  290. mantis_node.drivers[sock] = [(prop, val_index)]
  291. else:
  292. getattr(b_object,prop)[val_index] = value
  293. else:
  294. safe_setattr(b_object, prop, mantis_node.evaluate_input(sock))
  295. except Exception as e:
  296. prRed(b_object, mantis_node, prop, sock, mantis_node.evaluate_input(sock))
  297. raise e
  298. def finish_driver(mantis_node, b_object, driver_item, prop):
  299. try:
  300. driver_sock = driver_item[0]; index = driver_item[1]; array_index =driver_item[2]
  301. except Exception as e:
  302. print (wrapGreen(mantis_node), wrapOrange(b_object), wrapRed(driver_item), wrapWhite(prop),)
  303. raise e
  304. driver_trace = trace_single_line(mantis_node, driver_sock, array_index)
  305. driver_provider, driver_socket = driver_trace[0][-1], driver_trace[1]
  306. combo_property = False
  307. # try to detect combo properties
  308. if mantis_node.socket_templates:
  309. for st in mantis_node.socket_templates:
  310. if st.name == driver_sock:
  311. if isinstance(st.blender_property, tuple):
  312. combo_property = True
  313. break
  314. if index is not None:
  315. try:
  316. driver = driver_provider.parameters[driver_socket.name][index].copy()
  317. except KeyError as e: # try single driver, it should still work fine.
  318. if combo_property:
  319. driver = driver_provider.parameters[driver_socket.name].copy()
  320. else:
  321. raise e
  322. # this is harmless and necessary for the weird ones where the property is a vector too
  323. if not combo_property:
  324. driver["ind"] = index
  325. else:
  326. driver = driver_provider.parameters[driver_socket.name].copy()
  327. if driver:
  328. socket_template = None
  329. if mantis_node.socket_templates:
  330. for socket_template in mantis_node.socket_templates:
  331. if socket_template.name == driver_sock: break
  332. if socket_template:
  333. if socket_template.is_animatable == False:
  334. raise RuntimeError(f"Driver is not allowed for {driver_sock} in node {mantis_node}")
  335. if ("." in prop) and mantis_node.__class__.__name__ != "DeformerMorphTargetDeform": # this is a property of a property...
  336. sub_props = [b_object]
  337. while ("." in prop):
  338. split_prop = prop.split(".")
  339. prop = split_prop[1]
  340. sub_prop = (split_prop[0])
  341. if ("[" in sub_prop):
  342. sub_prop, index = sub_prop.split("[")
  343. index = int(index[0])
  344. sub_props.append(getattr(sub_props[-1], sub_prop)[index])
  345. else:
  346. sub_props.append(getattr(sub_props[-1], sub_prop))
  347. driver["owner"] = sub_props[-1]
  348. elif mantis_node.node_type in ['XFORM',] and mantis_node.__class__.__name__ in ['xFormBone']:
  349. # TODO: I really shouldn't have to hardcode this. Look into better solutions.
  350. # NOTE: VERSIONING in Blender 5.0 we have to use pose bone for this, too.
  351. # default to pose bone, it's usually what we want
  352. bone_col = mantis_node.bGetParentArmature().pose.bones
  353. from ..xForm_socket_templates import attributes_shared_by_bone_and_pose_bone
  354. if socket_template.prop_owner_group == 'bone':
  355. bone_col = mantis_node.bGetParentArmature().data.bones
  356. # this is a little bit clumsy. TODO clean up here
  357. if prop in attributes_shared_by_bone_and_pose_bone:
  358. # we want to use the pose bone for this...only if it has a driver
  359. bone_col = mantis_node.bGetParentArmature().pose.bones
  360. driver["owner"] = bone_col[b_object] # we use "unsafe" brackets instead of get() because we want to see any errors that occur
  361. # HACK having special cases here is indicitave of a deeper problem that should be refactored
  362. elif mantis_node.__class__.__name__ in ['xFormCurvePin'] and \
  363. prop in ['offset_factor', 'forward_axis', 'up_axis']:
  364. driver["owner"] = b_object.constraints['Curve Pin']
  365. else:
  366. driver["owner"] = b_object
  367. driver["prop"] = prop
  368. return driver
  369. else:
  370. prOrange("Provider", driver_provider)
  371. prGreen("socket", driver_socket)
  372. print (index)
  373. prPurple(driver_provider.parameters[driver_socket.name])
  374. prRed("Failed to create driver for %s" % prop)
  375. return None
  376. def finish_drivers(mantis_node):
  377. drivers = []
  378. if not hasattr(mantis_node, "drivers"):
  379. return # HACK
  380. for driver_item, prop in mantis_node.drivers.items():
  381. b_objects = [mantis_node.bObject]
  382. if mantis_node.node_type == 'LINK':
  383. b_objects = mantis_node.bObject # this is already a list
  384. for b_object in b_objects:
  385. # Mutating the driver_item here is the WORST THING I HAVE EVER SEEN
  386. # HACK UGLY BUG TERRIBLE BAD CODE
  387. if isinstance(prop, list):
  388. for sub_item in prop:
  389. drivers.append(finish_driver(mantis_node, b_object, (driver_item, sub_item[1], 0), sub_item[0]))
  390. else:
  391. drivers.append(finish_driver(mantis_node, b_object, (driver_item, sub_item[1], 0), sub_item[0]))
  392. else:
  393. drivers.append(finish_driver(mantis_node, b_object, driver_item, prop))
  394. from .drivers import CreateDrivers
  395. CreateDrivers(drivers)