base_definitions.py 28 KB

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