node_container_common.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. from mantis.utilities import (prRed, prGreen, prPurple, prWhite,
  2. prOrange,
  3. wrapRed, wrapGreen, wrapPurple, wrapWhite,
  4. wrapOrange,)
  5. from mantis.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. return None
  12. elif hasattr(node_socket, "default_value"):
  13. from .utilities import to_mathutils_value
  14. default_value_type = type(node_socket.default_value)
  15. math_val = to_mathutils_value(node_socket)
  16. if math_val:
  17. return math_val
  18. # maybe we can use it directly.. ?
  19. elif ( (default_value_type == str) or (default_value_type == bool) or
  20. (default_value_type == float) or (default_value_type == int) ):
  21. return node_socket.default_value
  22. return None
  23. # TODO: unify the fill_paramaters for auto-gen nodes
  24. def fill_parameters(nc):
  25. from .utilities import get_node_prototype
  26. if (nc.signature[0] is "MANTIS_AUTOGENERATED"):
  27. return None
  28. else:
  29. np = get_node_prototype(nc.signature, nc.base_tree)
  30. if ( not np ):
  31. raise RuntimeError(wrapRed("No node prototype found for... %s" % ( [nc.base_tree] + list(nc.signature[1:]) ) ) )
  32. for key in nc.parameters.keys():
  33. node_socket = np.inputs.get(key)
  34. # if (nc.signature[0] is "MANTIS_AUTOGENERATED"):
  35. # node_socket = None
  36. # for node_socket in np.inputs:
  37. # if node_socket.identifier == nc.signature[-1]:
  38. # break
  39. if nc.parameters[key] is not None:
  40. continue # will be filled by the node itself
  41. if not node_socket:
  42. #maybe the node socket has no name
  43. if ( ( len(np.inputs) == 0) and ( len(np.outputs) == 1) ):
  44. # this is a simple input node.
  45. node_socket = np.outputs[0]
  46. elif key == 'Name': # for Links we just use the Node Label
  47. nc.parameters[key] = np.label
  48. continue
  49. else:
  50. pass
  51. if node_socket:
  52. if node_socket.bl_idname in ['RelationshipSocket', 'xFormSocket']:
  53. continue
  54. elif hasattr(node_socket, "default_value"):
  55. if (value := get_socket_value(node_socket)) is not None:
  56. nc.parameters[key] = value
  57. else:
  58. raise RuntimeError(wrapRed("No value found for " + nc.__repr__() + " when filling out node parameters for " + np.name + "::"+node_socket.name))
  59. else:
  60. pass
  61. def evaluate_input(node_container, input_name):
  62. if not (node_container.inputs.get(input_name)):
  63. # just return the parameter, there is no socket associated
  64. return node_container.parameters.get(input_name)
  65. trace = trace_single_line(node_container, input_name)
  66. # this should give a key error if there is a problem
  67. # it is NOT handled here because it should NOT happen
  68. prop = trace[0][-1].parameters[trace[1].name]
  69. return prop
  70. def check_for_driver(node_container, input_name, index = None):
  71. prop = evaluate_input(node_container, input_name)
  72. if (index is not None):
  73. prop = prop[index]
  74. # This should work, even for None.
  75. return (prop.__class__.__name__ == 'MantisDriver')
  76. def trace_node_lines(node_container):
  77. """ Tells the depth of a node within the node tree. """
  78. node_lines = []
  79. if hasattr(node_container, "inputs"):
  80. for key, socket in node_container.inputs.items():
  81. # Recrusive search through the tree.
  82. # * checc each relevant input socket in the node
  83. # * for EACH input, find the node it's connected to
  84. # * repeat from here until you get all the lines
  85. if ( ( key in ["Relationship", "Parent", "Input Relationship", "Target"])
  86. and (socket.is_connected) ):
  87. # it is necesary to check the key because of Link nodes,
  88. # which don't really traverse like normal.
  89. # TODO: see if I can refactor this to make it traverse
  90. other = socket.from_node
  91. if (other):
  92. other_lines = trace_node_lines(other)
  93. if not other_lines:
  94. node_lines.append([other])
  95. for line in other_lines:
  96. node_lines.append( [other] + line )
  97. return node_lines
  98. # TODO: modify this to work with multi-input nodes
  99. def trace_single_line(node_container, input_name):
  100. # DO: refactor this for new link class
  101. """Traces a line to its input."""
  102. nodes = [node_container]
  103. if hasattr(node_container, "inputs"):
  104. # Trace a single line
  105. if (socket := node_container.inputs.get(input_name) ):
  106. while (socket.is_linked):
  107. link = socket.links[0] # inputs can only get one link.
  108. other = link.from_node.outputs.get(link.from_socket)
  109. if (other):
  110. socket = other
  111. if socket.can_traverse:
  112. socket = socket.traverse_target
  113. nodes.append(socket.node)
  114. else: # this is an output.
  115. nodes.append(socket.node)
  116. break
  117. else:
  118. break
  119. return nodes, socket
  120. # this is same as the other, just flip from/to and in/out
  121. def trace_single_line_up(node_container, output_name,):
  122. """ Tells the depth of a node within the node tree. """
  123. nodes = [node_container]
  124. if hasattr(node_container, "outputs"):
  125. # Trace a single line
  126. if (socket := node_container.outputs.get(output_name) ):
  127. while (socket.is_linked):
  128. # This is bad, but it's efficient for nodes that only expect
  129. # one path along the given line
  130. link = socket.links[0]
  131. other = link.to_node.inputs.get(link.to_socket)
  132. if (other):
  133. socket = other
  134. if socket.can_traverse:
  135. socket = socket.traverse_target
  136. nodes.append(socket.node)
  137. else: # this is an input.
  138. nodes.append(socket.node)
  139. break
  140. else:
  141. break
  142. return nodes, socket
  143. def trace_all_lines_up(nc, output_name):
  144. copy_items = {}
  145. for item in dir(nc):
  146. if "__" not in item:
  147. copy_items[item]=getattr(nc, item)
  148. # we want to copy it, BUT:
  149. copy_items["outputs"]:{output_name:nc.outputs[output_name]}
  150. # override outputs with just the one we care about.
  151. check_me = type('', (object,), copy_items)
  152. return get_depth_lines(check_me)[1]
  153. def 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 nc_path[-1].hierarchy_connections:
  166. path.append(0); curheight+=1
  167. else:
  168. path[curheight] = path[curheight] + 1
  169. nc_path.pop()
  170. connected_nodes = nc_path[-1].hierarchy_connections
  171. if ( path[-1] <= len(connected_nodes)-1 ):
  172. seek = connected_nodes[path[-1]]
  173. elif curheight > 0:
  174. while(len(path) > 1):
  175. path.pop(); curheight -= 1; path[curheight]+=1; nc_path.pop()
  176. if ( (len(nc_path)>1) and path[-1] < len(nc_path[-1].hierarchy_connections) ):
  177. break
  178. return lines, nc_paths
  179. def node_depth(lines):
  180. maxlen = 0
  181. for line in lines:
  182. if ( (l := len(line) ) > maxlen):
  183. maxlen = l
  184. return maxlen
  185. #TODO rewrite this so it'll work with new nc_path thing
  186. # not a high priority bc this was debugging code for something that
  187. # works and has since ben refactored to work better
  188. def printable_path(nc, path, no_wrap = False):
  189. string = ""; cur_nc = nc
  190. #DO: find out if the copy is necessary
  191. path = path.copy(); path.reverse()
  192. dummy = lambda a : a
  193. while path:
  194. wrap = dummy
  195. if not no_wrap:
  196. wrap=wrapWhite
  197. if (cur_nc.node_type == 'DRIVER'):
  198. wrap = wrapPurple
  199. elif (cur_nc.node_type == 'XFORM'):
  200. wrap = wrapOrange
  201. elif (cur_nc.node_type == 'LINK'):
  202. wrap = wrapGreen
  203. string += wrap(cur_nc.__repr__()) + " -> "
  204. try:
  205. cur_nc = get_from_path(cur_nc, [path.pop()] )
  206. except IndexError:
  207. string = string[:-4]
  208. return string
  209. string = string[:-4]
  210. return string
  211. # why is this not printing groups in brackets?
  212. def get_parent(node_container, type = 'XFORM'):
  213. # type variable for selecting whether to get either
  214. # the parent xForm or the inheritance node
  215. node_line, socket = trace_single_line(node_container, "Relationship")
  216. parent_nc = None
  217. for i in range(len(node_line)):
  218. # check each of the possible parent types.
  219. if ( (node_line[ i ].__class__.__name__ == 'LinkInherit') ):
  220. try: # it's the next one
  221. if (type == 'XFORM'):
  222. return node_line[ i + 1 ]
  223. else: # type = 'LINK'
  224. return node_line[ i ]
  225. except IndexError: # if there is no next one...
  226. return None # then there's no parent!
  227. return None
  228. # TODO!
  229. #
  230. # make this do shorthand parenting - if no parent, then use World
  231. # if the parent node is skipped, use the previous node (an xForm)
  232. # with default settings.
  233. # it is OK to generate a new, "fake" node container for this!
  234. def get_target_and_subtarget(node_container, linkOb, input_name = "Target"):
  235. from bpy.types import PoseBone, Object, SplineIKConstraint, ArmatureModifier
  236. subtarget = ''; target = node_container.evaluate_input(input_name)
  237. if target:
  238. if (isinstance(target.bGetObject(), PoseBone)):
  239. subtarget = target.bGetObject().name
  240. target = target.bGetParentArmature()
  241. elif (isinstance(target.bGetObject(), Object) ):
  242. target = target.bGetObject()
  243. else:
  244. raise RuntimeError("Cannot interpret linkOb target!")
  245. if (isinstance(linkOb, SplineIKConstraint)):
  246. if target and target.type not in ["CURVE"]:
  247. raise GraphError(wrapRed("Error: %s requires a Curve input, not %s" %
  248. (node_container, type(target))))
  249. # don't get a subtarget
  250. linkOb.target = target
  251. elif (isinstance(linkOb, ArmatureModifier)):
  252. linkOb.object = target
  253. elif (input_name == 'Target'): # this is sloppy, but it werks
  254. linkOb.target, linkOb.subtarget = target, subtarget
  255. elif (input_name == 'Pole Target'):
  256. linkOb.pole_target, linkOb.pole_subtarget = target, subtarget
  257. else: # this is really just for Armature Constraint targets...
  258. linkOb.target, linkOb.subtarget = target, subtarget
  259. def setup_custom_props(nc):
  260. from .utilities import get_node_prototype
  261. if np := get_node_prototype(nc.signature, nc.base_tree):
  262. for inp in np.inputs:
  263. if not (inp.name in nc.inputs.keys()) :
  264. nc.inputs[inp.name] = NodeSocket(is_input = True, name = inp.name, node = nc,)
  265. nc.parameters[inp.name] = None
  266. for out in np.outputs:
  267. if not (out.name in nc.outputs.keys()) :
  268. nc.outputs[out.name] = NodeSocket(is_input = False, name = out.name, node = nc,)
  269. #nc.parameters[out.name] = None
  270. # probably not something I want?
  271. # I don't think this supports in and out by the same name oof
  272. else:
  273. prRed(nc)
  274. def prepare_parameters(nc):
  275. # some nodes add new parameters at runtime, e.g. Drivers
  276. # so we need to take that stuff from the node_containers that have
  277. # been executed prior to this node.
  278. for s_name, sock in nc.inputs.items():
  279. if not (sock.is_linked):
  280. continue
  281. if (sock.name in sock.links[0].from_node.parameters.keys()):
  282. nc.parameters[s_name] = sock.links[0].from_node.parameters[sock.name]
  283. # should work, this is ugly.
  284. # TODO: this should handle sub-properties better
  285. def evaluate_sockets(nc, c, props_sockets):
  286. # HACK I think I should do this in __init__
  287. if not hasattr(nc, "drivers"):
  288. nc.drivers = {}
  289. # end HACK
  290. for prop, (sock, default) in props_sockets.items():
  291. # c = nc.bObject
  292. # annoyingly, sometimes the socket is an array
  293. index = None
  294. if isinstance(sock, tuple):
  295. index = sock[1]; sock = sock[0]
  296. if (check_for_driver(nc, sock, index)):
  297. sock = (sock, index)
  298. original_prop = prop
  299. # TODO: deduplicate this terrible hack
  300. if ("." in prop): # this is a property of a property...
  301. sub_props = [c]
  302. while ("." in prop):
  303. split_prop = prop.split(".")
  304. prop = split_prop[1]
  305. sub_prop = (split_prop[0])
  306. if ("[" in sub_prop):
  307. sub_prop, index = sub_prop.split("[")
  308. index = int(index[0])
  309. sub_props.append(getattr(sub_props[-1], sub_prop)[index])
  310. else:
  311. sub_props.append(getattr(sub_props[-1], sub_prop))
  312. setattr(sub_props[-1], prop, default)
  313. # this is really stupid
  314. else:
  315. setattr(c, prop, default)
  316. print("Adding driver %s to %s in %s" % (wrapPurple(original_prop), wrapWhite(nc.signature[-1]), wrapOrange(nc.GetxForm().bGetObject().name)))
  317. nc.drivers[sock] = original_prop
  318. else: # here we can do error checking for the socket if needed
  319. if (index is not None):
  320. setattr(c, prop, nc.evaluate_input(sock)[index])
  321. else: # 'mute' is better than 'enabled'
  322. # UGLY HACK # because it is available in older
  323. if (prop == 'mute'): # Blenders.
  324. setattr(c, prop, not nc.evaluate_input(sock))
  325. else:
  326. setattr(c, prop, nc.evaluate_input(sock))
  327. def finish_drivers(self):
  328. # gonna make this into a common function...
  329. drivers = []
  330. if not hasattr(self, "drivers"):
  331. return # HACK
  332. for driver, prop in self.drivers.items():
  333. #annoyingly, the driver may be a vector-driver...
  334. index = driver[1]; driver = driver[0]
  335. driver_trace = trace_single_line(self, driver)
  336. driver_provider, driver_socket = driver_trace[0][-1], driver_trace[1]
  337. if index is not None:
  338. driver = driver_provider.parameters[driver_socket.name][index].copy()
  339. else:
  340. driver = driver_provider.parameters[driver_socket.name].copy()
  341. if driver:
  342. # todo: deduplicate this terrible hack
  343. c = self.bObject # STUPID
  344. if ("." in prop): # this is a property of a property...
  345. sub_props = [c]
  346. while ("." in prop):
  347. split_prop = prop.split(".")
  348. prop = split_prop[1]
  349. sub_prop = (split_prop[0])
  350. if ("[" in sub_prop):
  351. sub_prop, index = sub_prop.split("[")
  352. index = int(index[0])
  353. sub_props.append(getattr(sub_props[-1], sub_prop)[index])
  354. else:
  355. sub_props.append(getattr(sub_props[-1], sub_prop))
  356. driver["owner"] = sub_props[-1]
  357. else:
  358. driver["owner"] = self.bObject
  359. # prPurple("Successfully created driver for %s" % prop)
  360. driver["prop"] = prop
  361. drivers.append(driver)
  362. else:
  363. prRed("Failed to create driver for %s" % prop)
  364. from mantis.drivers import CreateDrivers
  365. CreateDrivers(drivers)
  366. #Dummy classes for logic with node containers, they are not meant to do
  367. # each and every little thing the "real" Blender classes do.
  368. class NodeLink:
  369. from_node = None
  370. from_socket = None
  371. to_node = None
  372. to_socket = None
  373. def __init__(self, from_node, from_socket, to_node, to_socket):
  374. self.from_node = from_node
  375. self.from_socket = from_socket
  376. self.to_node = to_node
  377. self.to_socket = to_socket
  378. self.from_node.outputs[self.from_socket].links.append(self)
  379. self.to_node.inputs[self.to_socket].links.append(self)
  380. def __repr__(self):
  381. return self.from_node.outputs[self.from_socket].__repr__() + " --> " + self.to_node.inputs[self.to_socket].__repr__()
  382. class NodeSocket:
  383. @property # this is a read-only property.
  384. def is_linked(self):
  385. return len(self.links) > 0
  386. def __init__(self, is_input = False,
  387. node = None, name = None,
  388. traverse_target = None):
  389. self.can_traverse = False # to/from the other side of the parent node
  390. self.traverse_target = None # a reference to another Socket
  391. # will set in a sec
  392. self.node = node
  393. self.name = name
  394. self.is_input = is_input
  395. self.links = []
  396. if (traverse_target):
  397. set_traverse_target(traverse_target)
  398. def connect(self, node, socket):
  399. if (self.is_input):
  400. to_node = self.node; from_node = node
  401. to_socket = self.name; from_socket = socket
  402. else:
  403. from_node = self.node; to_node = node
  404. from_socket = self.name; to_socket = socket
  405. new_link = NodeLink(
  406. from_node,
  407. from_socket,
  408. to_node,
  409. to_socket)
  410. # if (from_node.signature[-2] in ["Chiral Identifier"] and
  411. # from_node.signature[-1] in ['Input_4']):
  412. # print(wrapRed("Connecting %s" % new_link),)
  413. # if (from_node.signature[-2] in ["Chiral Identifier"] and
  414. # from_node.signature[-1] in ['Input_3',]):
  415. # print(wrapRed("Connecting %s" % new_link),)
  416. return new_link
  417. def set_traverse_target(self, traverse_target):
  418. self.traverse_target = traverse_target
  419. self.can_traverse = True
  420. @property
  421. def is_connected(self):
  422. return len(self.links)>0
  423. def __repr__(self):
  424. return self.node.__repr__() + "::" + self.name