base_definitions.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. #Mantis Nodes Base
  2. import bpy
  3. from bpy.props import BoolProperty, StringProperty, EnumProperty, CollectionProperty, IntProperty, PointerProperty, BoolVectorProperty
  4. from . import ops_nodegroup
  5. from bpy.types import NodeTree, Node, PropertyGroup, Operator, UIList, Panel
  6. from .utilities import (prRed, prGreen, prPurple, prWhite,
  7. prOrange,
  8. wrapRed, wrapGreen, wrapPurple, wrapWhite,
  9. wrapOrange,)
  10. from .utilities import get_socket_maps, relink_socket_map, do_relink
  11. from bpy.app.handlers import persistent
  12. def TellClasses():
  13. #Why use a function to do this? Because I don't need every class to register.
  14. return [ MantisTree,
  15. SchemaTree,
  16. # MantisNode,
  17. # SchemaNode,
  18. MantisNodeGroup,
  19. SchemaGroup,
  20. MantisVisualizeTree,
  21. MantisVisualizeNode,
  22. ]
  23. class MantisTree(NodeTree):
  24. '''A custom node tree type that will show up in the editor type list'''
  25. bl_idname = 'MantisTree'
  26. bl_label = "Rigging Nodes"
  27. bl_icon = 'OUTLINER_OB_ARMATURE'
  28. tree_valid:BoolProperty(default=False)
  29. do_live_update:BoolProperty(default=True) # use this to disable updates for e.g. scripts
  30. num_links:IntProperty(default=-1)
  31. filepath:StringProperty(default="", subtype='FILE_PATH')
  32. is_executing:BoolProperty(default=False)
  33. is_exporting:BoolProperty(default=False)
  34. execution_id:StringProperty(default='')
  35. parsed_tree={}
  36. def interface_update(self, context):
  37. # no idea what this does
  38. print ("Update Interface function in MantisTree class")
  39. if bpy.app.version >= (3, 2): # in 3.1 this can lead to a crash
  40. @classmethod
  41. def valid_socket_type(cls, socket_type: str):
  42. # https://docs.blender.org/api/master/bpy.types.NodeTree.html#bpy.types.NodeTree.valid_socket_type
  43. from .socket_definitions import Tell_bl_idnames
  44. return socket_type in Tell_bl_idnames()
  45. # thank you, Sverchok
  46. def update_tree(self, context = None):
  47. if self.is_exporting:
  48. return
  49. # return
  50. self.is_executing = True
  51. from . import readtree
  52. prGreen("Validating Tree: %s" % self.name)
  53. try:
  54. self.parsed_tree = readtree.parse_tree(self)
  55. if context:
  56. self.display_update(context)
  57. self.is_executing = False
  58. self.tree_valid = True
  59. except GraphError as e:
  60. prRed("Failed to update node tree due to error.")
  61. self.tree_valid = False
  62. self.is_executing = False
  63. raise e
  64. def display_update(self, context):
  65. current_tree = bpy.context.space_data.path[-1].node_tree
  66. for node in current_tree.nodes:
  67. if hasattr(node, "display_update"):
  68. try:
  69. node.display_update(self.parsed_tree, context)
  70. except Exception as e:
  71. print("Node \"%s\" failed to update display with error: %s" %(wrapGreen(node.name), wrapRed(e)))
  72. # raise e
  73. # from .utilities import all_trees_in_tree
  74. # all_my_trees = all_trees_in_tree(self,)
  75. # for tree in all_my_trees:
  76. # I think using the current visible tree is actually fine
  77. if True:
  78. # HACK
  79. for l in current_tree.links:
  80. l.is_valid = True # I don't want Blender marking these for me. No warning is better than a wrong warning.
  81. # end HACK
  82. # in the future, fix it properly. Here is a start.
  83. else: # this is harder to fix than I thought!
  84. tree_path_names=[None,]
  85. for i, path_item in enumerate(bpy.context.space_data.path[:-1]):
  86. tree_path_names.append(path_item.node_tree.nodes.active.name)
  87. for l in current_tree.links: # We need to loop through actual links so we can filter them
  88. if l.is_valid == False:
  89. if nc_from := self.parsed_tree.get( (*tree_path_names, l.from_node.name)) is None:
  90. continue
  91. for o in nc_from.outputs.values():
  92. for mlink in o.links:
  93. if (mlink.to_socket == l.to_socket.name) and (mlink.to_node.signature[-1] == l.to_node.name):
  94. l.is_valid = not mlink.is_hierarchy
  95. # in reality, we need to trace the link UP and find a cycle - then find a link in the cycle that is non-hierarchy
  96. # the actual link that BLENDER marks as invalid may be a hierarchy link.
  97. # let's see if this works
  98. def execute_tree(self,context):
  99. if self.is_exporting:
  100. return
  101. # return
  102. prGreen("Executing Tree: %s" % self.name)
  103. self.is_executing = True
  104. from . import readtree
  105. try:
  106. readtree.execute_tree(self.parsed_tree, self, context)
  107. except RecursionError as e:
  108. prRed("Recursion error while parsing tree.")
  109. # prRed(e); node_tree.do_live_update = False
  110. # except Exception:
  111. # pass
  112. finally: # first time I have ever used a finally block in my life.
  113. self.is_executing = False
  114. # class SchemaPropertyGroup(bpy.types.PropertyGroup):
  115. class SchemaTree(NodeTree):
  116. '''A node tree representing a schema to generate a Mantis tree'''
  117. bl_idname = 'SchemaTree'
  118. bl_label = "Rigging Nodes Schema"
  119. bl_icon = 'RIGID_BODY_CONSTRAINT'
  120. tree_valid:BoolProperty(default=False)
  121. do_live_update:BoolProperty(default=True) # use this to disable updates for e.g. scripts
  122. is_executing:BoolProperty(default=False)
  123. num_links:IntProperty(default=-1)
  124. # filepath:StringProperty(default="", subtype='FILE_PATH')
  125. parsed_tree={}
  126. # def update(self):
  127. # for n in self.nodes:
  128. # if hasattr(n, "update"): n.update()
  129. # def update_tree(self, context):
  130. # prRed("update tree for Schema Tree!")
  131. # # self.tree_valid = True
  132. # # return
  133. # from . import readtree
  134. # prGreen("Validating Tree: %s" % self.name)
  135. # parsed_tree = readtree.parse_tree(self)
  136. # self.parsed_tree=parsed_tree
  137. # current_tree = bpy.context.space_data.path[-1].node_tree
  138. # self.tree_valid = True
  139. # prWhite("Number of Nodes: %s" % (len(self.parsed_tree)))
  140. # self.display_update(context)
  141. # def display_update(self, context):
  142. # prRed("display update for Schema Tree!")
  143. # return
  144. # current_tree = bpy.context.space_data.path[-1].node_tree
  145. # for node in current_tree.nodes:
  146. # if hasattr(node, "display_update"):
  147. # try:
  148. # node.display_update(self.parsed_tree, context)
  149. # except Exception as e:
  150. # print("Node \"%s\" failed to update display with error: %s" %(wrapGreen(node.name), wrapRed(e)))
  151. # # raise e
  152. # def execute_tree(self,context):
  153. # self.is_executing = True
  154. # prRed("executing Schema Tree!")
  155. # self.tree_valid = False
  156. # self.is_executing = False
  157. # return
  158. # prGreen("Executing Tree: %s" % self.name)
  159. # from . import readtree
  160. # try:
  161. # readtree.execute_tree(self.parsed_tree, self, context)
  162. # except RecursionError as e:
  163. # prRed("Recursion error while parsing tree.")
  164. # prRed(e); node_tree.do_live_update = False
  165. if bpy.app.version >= (3, 2): # in 3.1 this can lead to a crash
  166. @classmethod
  167. def valid_socket_type(cls, socket_type: str):
  168. # https://docs.blender.org/api/master/bpy.types.NodeTree.html#bpy.types.NodeTree.valid_socket_type
  169. from .socket_definitions import Tell_bl_idnames
  170. return socket_type in Tell_bl_idnames()
  171. # thank you, Sverchok
  172. class MantisNode:
  173. # num_links:IntProperty(default=-1) # is this used anywhere?
  174. # is_triggering_execute:BoolProperty(default=False)
  175. # do_display_update:BoolProperty(default=False)
  176. @classmethod
  177. def poll(cls, ntree):
  178. return (ntree.bl_idname in ['MantisTree', 'SchemaTree'])
  179. def insert_link(self, link):
  180. context = bpy.context
  181. if context.space_data:
  182. node_tree = context.space_data.path[0].node_tree
  183. from . import readtree
  184. if node_tree.do_live_update:
  185. # prOrange("Updating from insert_link callback")
  186. node_tree.update_tree(context)
  187. if (link.to_socket.is_linked == False):
  188. node_tree.num_links+=1
  189. elif (link.to_socket.is_multi_input):# and
  190. #len(link.to_socket.links) < link.to_socket.link_limit ):
  191. # the above doesn't work and I can't be bothered to fix it right now TODO
  192. node_tree.num_links+=1
  193. class SchemaNode:
  194. # is_triggering_execute:BoolProperty(default=False)
  195. # do_display_update:BoolProperty(default=False)
  196. @classmethod
  197. def poll(cls, ntree):
  198. return (ntree.bl_idname in ['SchemaTree'])
  199. class LinkNode(MantisNode):
  200. useTarget : BoolProperty(default=False)
  201. @classmethod
  202. def poll(cls, ntree):
  203. return (ntree.bl_idname in ['MantisTree', 'SchemaTree'])
  204. class xFormNode(MantisNode):
  205. @classmethod
  206. def poll(cls, ntree):
  207. return (ntree.bl_idname in ['MantisTree', 'SchemaTree'])
  208. class DeformerNode(MantisNode):
  209. @classmethod
  210. def poll(cls, ntree):
  211. return (ntree.bl_idname in ['MantisTree', 'SchemaTree'])
  212. # from bpy.types import NodeCustomGroup
  213. def poll_node_tree(self, object):
  214. if isinstance(object, MantisTree):
  215. return True
  216. return False
  217. # TODO this should check ID's instead of name
  218. def node_group_update(node):
  219. toggle_update = node.id_data.do_live_update
  220. node.id_data.do_live_update = False
  221. # prWhite (node.name, len(node.inputs), len(node.outputs))
  222. identifiers_in={socket.identifier:socket for socket in node.inputs}
  223. identifiers_out={socket.identifier:socket for socket in node.outputs}
  224. if node.node_tree is None:
  225. node.inputs.clear(); node.outputs.clear()
  226. node.id_data.do_live_update = toggle_update
  227. return
  228. found_in, found_out = [], []
  229. update_input, update_output = False, False
  230. for item in node.node_tree.interface.items_tree:
  231. if item.item_type != "SOCKET": continue
  232. if item.in_out == 'OUTPUT':
  233. if s:= identifiers_out.get(item.identifier): # if the requested output doesn't exist, update
  234. found_out.append(item.identifier)
  235. if update_output: continue # done here
  236. if s.bl_idname != item.socket_type: update_output = True; continue
  237. else: update_output = True; continue # prRed(f"Not found: {item.name}");
  238. else:
  239. if s:= identifiers_in.get(item.identifier): # if the requested input doesn't exist, update
  240. found_in.append(item.identifier)
  241. if update_input: continue # done here
  242. if s.bl_idname != item.socket_type: update_input = True; continue
  243. else: update_input = True; continue # prGreen(f"Not found: {item.name}");
  244. # Schema has an extra input for Length and for Extend.
  245. if node.bl_idname == 'MantisSchemaGroup':
  246. found_in.extend(['Schema Length', ''])
  247. # if we have too many elements, just get rid of the ones we don't need
  248. if len(node.inputs) > len(found_in):#
  249. for inp in node.inputs:
  250. if inp.identifier in found_in: continue
  251. node.inputs.remove(inp)
  252. if len(node.outputs) > len(found_out):
  253. for out in node.outputs:
  254. if out.identifier in found_out: continue
  255. node.outputs.remove(out)
  256. #
  257. if len(node.inputs) > 0 and (inp := node.inputs[-1]).bl_idname == 'WildcardSocket' and inp.is_linked:
  258. # prPurple("oink! I am a piggy!")
  259. update_input = True
  260. if len(node.outputs) > 0 and (out := node.outputs[-1]).bl_idname == 'WildcardSocket' and out.is_linked:
  261. # prPurple("woof! I am a doggy!")
  262. update_output = True
  263. #
  264. if not (update_input or update_output):
  265. node.id_data.do_live_update = toggle_update
  266. return
  267. if update_input or update_output:
  268. socket_map_in, socket_map_out = get_socket_maps(node)
  269. if update_input :
  270. if node.bl_idname == 'MantisSchemaGroup':
  271. schema_length=0
  272. if sl := node.inputs.get("Schema Length"):
  273. schema_length = sl.default_value
  274. # sometimes this isn't available yet # TODO not happy about this solution
  275. node.inputs.clear()
  276. if node.bl_idname == 'MantisSchemaGroup':
  277. node.inputs.new("IntSocket", "Schema Length", identifier='Schema Length')
  278. node.inputs['Schema Length'].default_value = schema_length
  279. if update_output: node.outputs.clear()
  280. for item in node.node_tree.interface.items_tree:
  281. if item.item_type != "SOCKET": continue
  282. if (item.in_out == 'INPUT' and update_input):
  283. relink_socket_map(node, node.inputs, socket_map_in, item)
  284. if (item.in_out == 'OUTPUT' and update_output):
  285. relink_socket_map(node, node.outputs, socket_map_out, item)
  286. # at this point there is no wildcard socket
  287. if '__extend__' in socket_map_in.keys():
  288. do_relink(node, None, socket_map_in, in_out='INPUT', parent_name='Constant' )
  289. node.id_data.do_live_update = toggle_update
  290. def node_tree_prop_update(self, context):
  291. if self.is_updating: # this looks dumb... but update() can be called from update() in a sort of accidental way.
  292. return
  293. # prGreen("updating me...")
  294. self.is_updating = True
  295. node_group_update(self)
  296. self.is_updating = False
  297. if self.bl_idname in ['MantisSchemaGroup'] and self.node_tree is not None:
  298. if len(self.inputs) == 0:
  299. self.inputs.new("IntSocket", "Schema Length", identifier='Schema Length')
  300. if self.inputs[-1].bl_idname != "WildcardSocket":
  301. self.inputs.new("WildcardSocket", "", identifier="__extend__")
  302. from bpy.types import NodeCustomGroup
  303. class MantisNodeGroup(Node, MantisNode):
  304. bl_idname = "MantisNodeGroup"
  305. bl_label = "Node Group"
  306. node_tree:PointerProperty(type=NodeTree, poll=poll_node_tree, update=node_tree_prop_update,)
  307. is_updating:BoolProperty(default=False)
  308. def update(self):
  309. if self.is_updating: # this looks dumb... but update() can be called from update() in a sort of accidental way.
  310. return
  311. self.is_updating = True
  312. node_group_update(self)
  313. self.is_updating = False
  314. def draw_buttons(self, context, layout):
  315. row = layout.row(align=True)
  316. row.prop(self, "node_tree", text="")
  317. row.operator("mantis.edit_group", text="", icon='NODETREE', emboss=True)
  318. class CircularDependencyError(Exception):
  319. pass
  320. class GraphError(Exception):
  321. pass
  322. def get_signature_from_edited_tree(node, context):
  323. sig_path=[None,]
  324. for item in context.space_data.path[:-1]:
  325. sig_path.append(item.node_tree.nodes.active.name)
  326. return tuple(sig_path+[node.name])
  327. def poll_node_tree_schema(self, object):
  328. if isinstance(object, SchemaTree):
  329. return True
  330. return False
  331. # def update_schema_length(self, context):
  332. # pass # for now
  333. # TODO tiny UI problem - inserting new links into the tree will not place them in the right place.
  334. #this is a schema node in a mantis tree... kinda confusing
  335. class SchemaGroup(Node, MantisNode):
  336. bl_idname = "MantisSchemaGroup"
  337. bl_label = "Node Schema"
  338. node_tree:PointerProperty(type=NodeTree, poll=poll_node_tree_schema, update=node_tree_prop_update,)
  339. # schema_length:IntProperty(default=5, update=update_schema_length)
  340. is_updating:BoolProperty(default=False)
  341. # incoming link
  342. # from-node = name
  343. # from socket = index or identifier or something
  344. # property is unset as soon as it is used
  345. # so the update function checks this property and handles the incoming link in an allowed place
  346. # and actually the handle-link function can work as a switch - when the property is unset, it allows new links
  347. # otherwise it unsets the property and returns.
  348. # def init(self, context):
  349. # self.inputs.new("IntSocket", "Schema Length")
  350. # self.inputs.new("WildcardSocket", "")
  351. def draw_buttons(self, context, layout):
  352. row = layout.row(align=True)
  353. row.prop(self, "node_tree", text="")
  354. row.operator("mantis.edit_group", text="", icon='NODETREE', emboss=True)
  355. # layout.prop(self, "schema_length", text="n=")
  356. # WHAT IF:
  357. # - the function that creates the input/output map returns to a property in the node
  358. # - then the node handles the update in its update function.
  359. def update(self):
  360. if self.is_updating: # this looks dumb... but update() can be called from update() in a sort of accidental way.
  361. return
  362. self.is_updating = True
  363. node_group_update(self)
  364. # kinda dumb but necessary since update doesn't always fix this
  365. if self.node_tree:
  366. if len(self.inputs) == 0:
  367. self.inputs.new("IntSocket", "Schema Length", identifier='Schema Length')
  368. if self.inputs[-1].bl_idname != "WildcardSocket":
  369. self.inputs.new("WildcardSocket", "", identifier="__extend__")
  370. self.is_updating = False
  371. # def insert_link(self, link):
  372. # if self.node_tree is None:
  373. # link.is_valid = False
  374. # return
  375. # sock_type = link.from_socket.bl_idname
  376. # for i, sock in enumerate(self.inputs):
  377. # if sock == link.to_socket: # dumb but whatever
  378. # identifier = link.to_socket.identifier
  379. # if sock.bl_idname not in ["WildcardSocket"]:
  380. # if sock.is_linked == True:
  381. # links = [ getattr(l, "from_socket") for l in sock.links ]
  382. # name = sock.name
  383. # # self.inputs.remove(sock)
  384. # # new_input = self.inputs.new(sock_type, name, identifier=identifier, use_multi_input=True); self.inputs.move(-1, i)
  385. # sock.display_shape = 'SQUARE_DOT'
  386. # interface_item = self.node_tree.interface.items_tree[name]
  387. # if not (interface_parent := self.node_tree.interface.items_tree.get('Array')):
  388. # interface_parent = self.node_tree.interface.new_panel(name='Array')
  389. # self.node_tree.interface.move_to_parent(interface_item, interface_parent, len(interface_parent.interface_items))
  390. # # sock.link_limit = self.schema_length TODO this will be very hard to get at this point
  391. # # self.id_data.links.new()
  392. # else: #if link.to_socket == self.inputs[-1]:
  393. # self.inputs.remove(sock)#self.inputs[-1])
  394. # #
  395. # name_stem = link.from_socket.bl_idname.replace('Socket',''); num=0
  396. # if hasattr(link.from_socket, "default_value"):
  397. # name_stem = type(link.from_socket.default_value).__name__
  398. # for n in self.inputs:
  399. # if name_stem in n.name: num+=1
  400. # name = name_stem + '.' + str(num).zfill(3)
  401. # #
  402. # new_input = self.inputs.new(sock_type, name, identifier=identifier, use_multi_input=False); self.inputs.move(-1, i+1)
  403. # new_input.link_limit = 1
  404. # # link.to_socket = new_input
  405. # # this seems to work
  406. # self.inputs.new("WildcardSocket", "")
  407. # if not (interface_parent := self.node_tree.interface.items_tree.get('Constant')):
  408. # interface_parent = self.node_tree.interface.new_panel(name='Constant')
  409. # self.node_tree.interface.new_socket(name=name,in_out='INPUT', socket_type=sock_type, parent=interface_parent)
  410. # return
  411. # TODO: investigate whether this is necessary
  412. # @classmethod
  413. # def poll(cls, ntree):
  414. # return (ntree.bl_idname in ['MantisTree'])
  415. # handlers!
  416. #annoyingly these have to be persistent
  417. @persistent
  418. def update_handler(scene):
  419. context=bpy.context
  420. if context.space_data:
  421. if not hasattr(context.space_data, "path"):
  422. return
  423. trees = [p.node_tree for p in context.space_data.path]
  424. if not trees: return
  425. if (node_tree := trees[0]).bl_idname in ['MantisTree']:
  426. if node_tree.do_live_update and not (node_tree.is_executing or node_tree.is_exporting):
  427. prev_links = node_tree.num_links
  428. node_tree.num_links = len(node_tree.links)
  429. if (prev_links == -1):
  430. return
  431. if prev_links != node_tree.num_links:
  432. node_tree.tree_valid = False
  433. if node_tree.tree_valid == False:
  434. node_tree.update_tree(context)
  435. @persistent
  436. def execute_handler(scene):
  437. context = bpy.context
  438. if context.space_data:
  439. if not hasattr(context.space_data, "path"):
  440. return
  441. trees = [p.node_tree for p in context.space_data.path]
  442. if not trees: return
  443. if (node_tree := trees[0]).bl_idname in ['MantisTree']:
  444. if node_tree.tree_valid and node_tree.do_live_update and not (node_tree.is_executing or node_tree.is_exporting):
  445. node_tree.execute_tree(context)
  446. node_tree.tree_valid = False
  447. # @persistent
  448. # def load_post_handler(scene):
  449. # print ("cuss and darn")
  450. # # import bpy
  451. # import sys
  452. # def wrapRed(skk): return "\033[91m{}\033[00m".format(skk)
  453. # def intercept(fn, *args):
  454. # print(wrapRed("Intercepting:"), fn)
  455. # sys.stdout.flush()
  456. # fn(*args)
  457. # print(wrapRed("... done"))
  458. # sys.stdout.flush()
  459. # for attr in dir(bpy.app.handlers):
  460. # if attr.startswith("_"):
  461. # continue
  462. # handler_list = getattr(bpy.app.handlers, attr)
  463. # if attr =='load_post_handler':
  464. # continue
  465. # if not isinstance(handler_list, list):
  466. # continue
  467. # if not handler_list:
  468. # continue
  469. # print("Intercept Setup:", attr)
  470. # handler_list[:] = [lambda *args: intercept(fn, *args) for fn in handler_list]
  471. # # import cProfile
  472. # from os import environ
  473. # do_profile=False
  474. # print (environ.get("DOPROFILE"))
  475. # if environ.get("DOPROFILE"):
  476. # do_profile=True
  477. # if do_profile:
  478. # # cProfile.runctx("tree.update_tree(context)", None, locals())
  479. # # cProfile.runctx("tree.execute_tree(context)", None, locals())
  480. # import hunter
  481. # hunter.trace(stdlib=False, action=hunter.CallPrinter(force_colors=False))
  482. # # def msgbus_callback(*args):
  483. # # # print("something changed!")
  484. # # print("Something changed!", args)
  485. # # owner = object()
  486. # subscribe_to = (bpy.types.Node, "location")
  487. # subscribe_to = (bpy.types.Node, "color")
  488. # subscribe_to = (bpy.types.Node, "dimensions")
  489. # subscribe_to = (bpy.types.Node, "height")
  490. # subscribe_to = (bpy.types.Node, "width")
  491. # subscribe_to = (bpy.types.Node, "inputs")
  492. # subscribe_to = (bpy.types.Node, "outputs")
  493. # subscribe_to = (bpy.types.Node, "select")
  494. # subscribe_to = (bpy.types.Node, "name")
  495. # subscribe_to = (bpy.types.NodeSocket, "name")
  496. # subscribe_to = (bpy.types.NodeSocket, "display_shape")
  497. # bpy.msgbus.subscribe_rna(
  498. # key=subscribe_to,
  499. # owner=owner,
  500. # args=(1, 2, 3),
  501. # notify=msgbus_callback,
  502. # )
  503. # print ("cuss and darn")
  504. # bpy.app.handlers.load_post.append(set_tree_invalid)
  505. bpy.app.handlers.depsgraph_update_pre.append(update_handler)
  506. bpy.app.handlers.depsgraph_update_post.append(execute_handler)
  507. # bpy.app.handlers.load_post.append(load_post_handler)
  508. # # import bpy
  509. # import sys
  510. # def wrapRed(skk): return "\033[91m{}\033[00m".format(skk)
  511. # def intercept(fn, *args):
  512. # print(wrapRed("Intercepting:"), fn)
  513. # sys.stdout.flush()
  514. # fn(*args)
  515. # print(wrapRed("... done"))
  516. # sys.stdout.flush()
  517. # for attr in dir(bpy.app.handlers):
  518. # if attr.startswith("_"):
  519. # continue
  520. # handler_list = getattr(bpy.app.handlers, attr)
  521. # if attr =='load_post_handler':
  522. # continue
  523. # if not isinstance(handler_list, list):
  524. # continue
  525. # if not handler_list:
  526. # continue
  527. # print("Intercept Setup:", attr)
  528. # handler_list[:] = [lambda *args: intercept(fn, *args) for fn in handler_list]
  529. class MantisVisualizeTree(NodeTree):
  530. '''A custom node tree type that will show up in the editor type list'''
  531. bl_idname = 'MantisVisualizeTree'
  532. bl_label = "mantis output"
  533. bl_icon = 'HIDE_OFF'
  534. class MantisVisualizeNode(Node):
  535. bl_idname = "MantisVisualizeNode"
  536. bl_label = "Node"
  537. @classmethod
  538. def poll(cls, ntree):
  539. return (ntree.bl_idname in ['MantisVisualizeTree'])
  540. def init(self, context):
  541. pass
  542. def gen_data(self, nc, np = None):
  543. self.use_custom_color = True
  544. if nc.node_type == 'XFORM':
  545. self.color = (1.0 ,0.5, 0.0)
  546. if nc.node_type == 'LINK':
  547. self.color = (0.4 ,0.2, 1.0)
  548. if nc.node_type == 'UTILITY':
  549. self.color = (0.2 ,0.2, 0.2)
  550. if nc.node_type == 'SCHEMA':
  551. self.color = (0.85 ,0.95, 0.9)
  552. if nc.node_type == 'DUMMY':
  553. self.color = (0.05 ,0.05, 0.15)
  554. self.name = ''.join(nc.signature[1:])
  555. if np:
  556. if np.label:
  557. self.label=np.label
  558. else:
  559. self.label=np.name
  560. for inp in nc.inputs:
  561. s = self.inputs.new('WildcardSocket', inp)
  562. try:
  563. if sock := np.inputs.get(inp):
  564. s.color = inp.color
  565. except AttributeError: #default bl_idname types like Float and Vector, no biggie
  566. pass
  567. except KeyError:
  568. pass
  569. for out in nc.outputs:
  570. s = self.outputs.new('WildcardSocket', out)
  571. try:
  572. if sock := np.outputs.get(out):
  573. s.color = out.color
  574. except AttributeError: #default bl_idname types like Float and Vector, no biggie
  575. pass
  576. except KeyError:
  577. pass
  578. self.location = np.location # will get overwritten by Grandalf later.
  579. else:
  580. self.label = nc.signature[-1] # which is be the unique name.
  581. for inp in nc.inputs:
  582. self.inputs.new('WildcardSocket', inp)
  583. for out in nc.outputs:
  584. self.outputs.new('WildcardSocket', out)
  585. # replace names with bl_idnames for reading the tree and solving schemas.
  586. replace_types = ["NodeGroupInput", "NodeGroupOutput", "SchemaIncomingConnection",
  587. "SchemaArrayInput", "SchemaConstInput", "SchemaConstOutput", "SchemaIndex",
  588. "SchemaOutgoingConnection", "SchemaConstantOutput", "SchemaArrayOutput",
  589. "SchemaArrayInputGet",]
  590. # anything that gets properties added in the graph... this is a clumsy approach but I need to watch for this
  591. # in schema generation and this is the easiest way to do it for now.
  592. custom_props_types = ["LinkArmature", "UtilityKeyframe", "UtilityFCurve", "UtilityDriver", "xFormBone"]
  593. from_name_filter = ["Driver",]
  594. to_name_filter = [
  595. "Custom Object xForm Override",
  596. "Custom Object",
  597. "Deform Bones",
  598. ]