visualize.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. """ Optional file providing a tool to visualize Mantis Graphs, for debugging and development"""
  2. from bpy.types import Node, NodeTree, Operator
  3. from bpy.props import StringProperty
  4. from .utilities import (prRed, prGreen, prPurple, prWhite,
  5. prOrange,
  6. wrapRed, wrapGreen, wrapPurple, wrapWhite,
  7. wrapOrange,)
  8. class MantisVisualizeTree(NodeTree):
  9. '''A custom node tree type that will show up in the editor type list'''
  10. bl_idname = 'MantisVisualizeTree'
  11. bl_label = "mantis output"
  12. bl_icon = 'HIDE_OFF'
  13. class MantisVisualizeNode(Node):
  14. bl_idname = "MantisVisualizeNode"
  15. bl_label = "Node"
  16. signature : StringProperty(default = '')
  17. @classmethod
  18. def poll(cls, ntree):
  19. return (ntree.bl_idname in ['MantisVisualizeTree'])
  20. def init(self, context):
  21. pass
  22. def draw_label(self):
  23. label=''
  24. exploded = self.signature.separate('|')
  25. for elem in exploded:
  26. label+=elem+', '
  27. label = label[:-2] # cut the last comma
  28. return label
  29. def gen_data(self, mantis_node, mode='DEBUG_CONNECTIONS'):
  30. from .utilities import get_node_prototype
  31. if mantis_node.node_type in ['SCHEMA', 'DUMMY']:
  32. np=None
  33. elif mantis_node.ui_signature is None:
  34. np=None
  35. else:
  36. np=get_node_prototype(mantis_node.ui_signature, mantis_node.base_tree)
  37. self.use_custom_color = True
  38. match mantis_node.node_type:
  39. case 'XFORM': self.color = (1.0 ,0.5, 0.0)
  40. case 'LINK': self.color = (0.4 ,0.2, 1.0)
  41. case 'UTILITY': self.color = (0.2 ,0.2, 0.2)
  42. case 'DRIVER': self.color = (0.7, 0.05, 0.8)
  43. case 'DUMMY_SCHEMA': self.color = (0.85 ,0.95, 0.9)
  44. case 'DUMMY': self.color = (0.05 ,0.05, 0.15)
  45. self.name = '.'.join(mantis_node.signature[1:]) # this gets trunc'd
  46. self.signature = '|'.join(mantis_node.signature[1:])
  47. if np:
  48. if np.label:
  49. self.label=np.label
  50. else:
  51. self.label=np.name
  52. for inp in mantis_node.inputs:
  53. match mode:
  54. case "DEBUG_CONNECTIONS":
  55. if not inp.is_connected:
  56. continue
  57. multi = False
  58. if len(inp.links) > 1: multi = True
  59. s = self.inputs.new('WildcardSocket', inp.name, use_multi_input=multi)
  60. s.link_limit = 4000
  61. try:
  62. if sock := np.inputs.get(inp.name):
  63. s.color = sock.color_simple
  64. except AttributeError: #default bl_idname types like Float and Vector, no biggie
  65. pass
  66. except KeyError:
  67. pass
  68. for out in mantis_node.outputs:
  69. match mode:
  70. case "DEBUG_CONNECTIONS":
  71. if not out.is_connected:
  72. continue
  73. s = self.outputs.new('WildcardSocket', out.name)
  74. try:
  75. if sock := np.outputs.get(out.name):
  76. s.color = sock.color_simple
  77. except AttributeError: #default bl_idname types like Float and Vector, no biggie
  78. pass
  79. except KeyError:
  80. pass
  81. self.location = np.location # will get overwritten by Grandalf later.
  82. else:
  83. self.label = mantis_node.signature[-1] # which is be the unique name.
  84. for inp in mantis_node.inputs:
  85. match mode:
  86. case "DEBUG_CONNECTIONS":
  87. if not inp.is_connected:
  88. continue
  89. multi = False
  90. if len(inp.links) > 1: multi = True
  91. s = self.inputs.new('WildcardSocket', inp.name)
  92. s.link_limit = 4000
  93. for out in mantis_node.outputs:
  94. match mode:
  95. case "DEBUG_CONNECTIONS":
  96. if not out.is_connected:
  97. continue
  98. self.outputs.new('WildcardSocket', out.name)
  99. def gen_vis_node( mantis_node,
  100. vis_tree,
  101. links,
  102. omit_simple=True,
  103. ):
  104. from .base_definitions import array_output_types
  105. # if mantis_node.node_type == 'UTILITY' and \
  106. # mantis_node.execution_prepared == True:
  107. # return
  108. base_tree= mantis_node.base_tree
  109. vis = vis_tree.nodes.new('MantisVisualizeNode')
  110. vis.gen_data(mantis_node)
  111. for inp in mantis_node.inputs.values():
  112. for l in inp.links:
  113. if l.from_node in mantis_node.hierarchy_dependencies:
  114. links.add(l)
  115. for out in mantis_node.outputs.values():
  116. for l in out.links:
  117. if l.to_node in mantis_node.hierarchy_connections:
  118. links.add(l)
  119. return vis
  120. def visualize_tree(m_nodes, base_tree, context):
  121. # first create a MantisVisualizeTree
  122. from .readtree import check_and_add_root
  123. from .utilities import trace_all_nodes_from_root
  124. import bpy
  125. base_tree.is_executing=True
  126. import cProfile
  127. import pstats, io
  128. from pstats import SortKey
  129. with cProfile.Profile() as pr:
  130. try:
  131. trace_from_roots = False
  132. all_links = set()
  133. mantis_nodes=set()
  134. nodes={}
  135. if trace_from_roots:
  136. roots=[]
  137. for n in m_nodes.values():
  138. check_and_add_root(n, roots)
  139. for r in roots:
  140. trace_all_nodes_from_root(r, mantis_nodes)
  141. if len(mantis_nodes) == 0:
  142. print ("No nodes to visualize")
  143. return
  144. else:
  145. mantis_nodes = list(base_tree.parsed_tree.values())
  146. vis_tree = bpy.data.node_groups.new(base_tree.name+'_visualized', type='MantisVisualizeTree')
  147. for m in mantis_nodes:
  148. nodes[m.signature]=gen_vis_node(m, vis_tree,all_links)
  149. # useful for debugging: check the connections for nodes that are
  150. # not in the parsed tree or available from trace_all_nodes_from_root.
  151. for l in all_links:
  152. # if l.to_node.node_type in ['DUMMY_SCHEMA', 'DUMMY'] or \
  153. # l.from_node.node_type in ['DUMMY_SCHEMA', 'DUMMY']:
  154. # pass
  155. from_node=nodes.get(l.from_node.signature)
  156. to_node=nodes.get(l.to_node.signature)
  157. from_socket, to_socket = None, None
  158. if from_node and to_node:
  159. from_socket = from_node.outputs.get(l.from_socket)
  160. to_socket = to_node.inputs.get(l.to_socket)
  161. if from_socket and to_socket:
  162. try:
  163. vis_tree.links.new(
  164. input=from_socket,
  165. output=to_socket,
  166. )
  167. except Exception as e:
  168. print (type(e)); print(e)
  169. raise e
  170. # at this point not all links are in the tree yet!
  171. def has_links (n):
  172. for input in n.inputs:
  173. if input.is_linked:
  174. return True
  175. for output in n.outputs:
  176. if output.is_linked:
  177. return True
  178. return False
  179. no_links=[]
  180. for n in vis_tree.nodes:
  181. if not has_links(n):
  182. no_links.append(n)
  183. while (no_links):
  184. n = no_links.pop()
  185. vis_tree.nodes.remove(n)
  186. # def side_len(n):
  187. # from math import floor
  188. # side = floor(n**(1/2)) + 1
  189. # return side
  190. # side=side_len(len(no_links))
  191. # break_me = True
  192. # for i in range(side):
  193. # for j in range(side):
  194. # index = side*i+j
  195. # try:
  196. # n = no_links[index]
  197. # n.location.x = i*200
  198. # n.location.y = j*200
  199. # except IndexError:
  200. # break_me = True # it's too big, that's OK the square is definitely bigger
  201. # break
  202. # if break_me:
  203. # break
  204. # from .utilities import SugiyamaGraph
  205. # SugiyamaGraph(vis_tree, 1) # this can take a really long time
  206. finally:
  207. s = io.StringIO()
  208. sortby = SortKey.TIME
  209. ps = pstats.Stats(pr, stream=s).strip_dirs().sort_stats(sortby)
  210. ps.print_stats(40) # print the top 20
  211. print(s.getvalue())
  212. base_tree.prevent_next_exec=True
  213. base_tree.is_executing=False
  214. from .ops_nodegroup import mantis_tree_poll_op
  215. class MantisVisualizeOutput(Operator):
  216. """Mantis Visualize Output Operator"""
  217. bl_idname = "mantis.visualize_output"
  218. bl_label = "Visualize Output"
  219. @classmethod
  220. def poll(cls, context):
  221. return (mantis_tree_poll_op(context))
  222. def execute(self, context):
  223. from time import time
  224. from .utilities import wrapGreen, prGreen
  225. tree=context.space_data.path[0].node_tree
  226. tree.update_tree(context)
  227. prGreen(f"Visualize Tree: {tree.name}")
  228. nodes = tree.parsed_tree
  229. visualize_tree(nodes, tree, context)
  230. return {"FINISHED"}