visualize.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  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. if mantis_node.execution_prepared:
  46. self.color = (0.02, 0.98, 0.02) # GREEN!
  47. # if mantis_node.execution_debug_tag:
  48. # self.color = (0.02 ,0.02, 0.02)
  49. self.name = '.'.join(mantis_node.signature[1:]) # this gets trunc'd
  50. self.signature = '|'.join(mantis_node.signature[1:])
  51. if np:
  52. if np.label:
  53. self.label=np.label
  54. else:
  55. self.label=np.name
  56. for inp in mantis_node.inputs:
  57. match mode:
  58. case "DEBUG_CONNECTIONS":
  59. if not inp.is_connected:
  60. continue
  61. s = self.inputs.new('WildcardSocket', inp.name)
  62. try:
  63. if sock := np.inputs.get(inp.name):
  64. s.color = sock.color_simple
  65. except AttributeError: #default bl_idname types like Float and Vector, no biggie
  66. pass
  67. except KeyError:
  68. pass
  69. for out in mantis_node.outputs:
  70. match mode:
  71. case "DEBUG_CONNECTIONS":
  72. if not out.is_connected:
  73. continue
  74. s = self.outputs.new('WildcardSocket', out.name)
  75. try:
  76. if sock := np.outputs.get(out.name):
  77. s.color = sock.color_simple
  78. except AttributeError: #default bl_idname types like Float and Vector, no biggie
  79. pass
  80. except KeyError:
  81. pass
  82. self.location = np.location # will get overwritten by Grandalf later.
  83. else:
  84. self.label = mantis_node.signature[-1] # which is be the unique name.
  85. for inp in mantis_node.inputs:
  86. match mode:
  87. case "DEBUG_CONNECTIONS":
  88. if not inp.is_connected:
  89. continue
  90. self.inputs.new('WildcardSocket', inp.name)
  91. for out in mantis_node.outputs:
  92. match mode:
  93. case "DEBUG_CONNECTIONS":
  94. if not out.is_connected:
  95. continue
  96. self.outputs.new('WildcardSocket', out.name)
  97. def gen_vis_node( mantis_node,
  98. vis_tree,
  99. links,
  100. omit_simple=True,
  101. ):
  102. from .base_definitions import array_output_types
  103. if mantis_node.node_type == 'UTILITY' and \
  104. mantis_node.execution_prepared == True:
  105. return
  106. base_tree= mantis_node.base_tree
  107. vis = vis_tree.nodes.new('MantisVisualizeNode')
  108. vis.gen_data(mantis_node)
  109. for inp in mantis_node.inputs.values():
  110. for l in inp.links:
  111. if l.from_node in mantis_node.hierarchy_dependencies:
  112. links.add(l)
  113. for out in mantis_node.outputs.values():
  114. for l in out.links:
  115. if l.to_node in mantis_node.hierarchy_connections:
  116. links.add(l)
  117. return vis
  118. def visualize_tree(m_nodes, base_tree, context):
  119. # first create a MantisVisualizeTree
  120. from .readtree import check_and_add_root
  121. from .utilities import trace_all_nodes_from_root
  122. import bpy
  123. base_tree.is_executing=True
  124. import cProfile
  125. import pstats, io
  126. from pstats import SortKey
  127. with cProfile.Profile() as pr:
  128. try:
  129. trace_from_roots = True
  130. all_links = set()
  131. mantis_nodes=set()
  132. nodes={}
  133. if trace_from_roots:
  134. roots=[]
  135. for n in m_nodes.values():
  136. check_and_add_root(n, roots)
  137. for r in roots:
  138. trace_all_nodes_from_root(r, mantis_nodes)
  139. if len(mantis_nodes) == 0:
  140. print ("No nodes to visualize")
  141. return
  142. else:
  143. mantis_nodes = list(base_tree.parsed_tree.values())
  144. vis_tree = bpy.data.node_groups.new(base_tree.name+'_visualized', type='MantisVisualizeTree')
  145. for m in mantis_nodes:
  146. nodes[m.signature]=gen_vis_node(m, vis_tree,all_links)
  147. # useful for debugging: check the connections for nodes that are
  148. # not in the parsed tree or available from trace_all_nodes_from_root.
  149. for l in all_links:
  150. if l.to_node.node_type in ['DUMMY_SCHEMA', 'DUMMY'] or \
  151. l.from_node.node_type in ['DUMMY_SCHEMA', 'DUMMY']:
  152. pass
  153. from_node=nodes.get(l.from_node.signature)
  154. to_node=nodes.get(l.to_node.signature)
  155. from_socket, to_socket = None, None
  156. if from_node and to_node:
  157. from_socket = from_node.outputs.get(l.from_socket)
  158. to_socket = to_node.inputs.get(l.to_socket)
  159. if from_socket and to_socket:
  160. try:
  161. vis_tree.links.new(
  162. input=from_socket,
  163. output=to_socket,
  164. )
  165. except Exception as e:
  166. print (type(e)); print(e)
  167. raise e
  168. # at this point not all links are in the tree yet!
  169. def has_links (n):
  170. for input in n.inputs:
  171. if input.is_linked:
  172. return True
  173. for output in n.outputs:
  174. if output.is_linked:
  175. return True
  176. return False
  177. no_links=[]
  178. for n in vis_tree.nodes:
  179. if not has_links(n):
  180. no_links.append(n)
  181. while (no_links):
  182. n = no_links.pop()
  183. vis_tree.nodes.remove(n)
  184. # def side_len(n):
  185. # from math import floor
  186. # side = floor(n**(1/2)) + 1
  187. # return side
  188. # side=side_len(len(no_links))
  189. # break_me = True
  190. # for i in range(side):
  191. # for j in range(side):
  192. # index = side*i+j
  193. # try:
  194. # n = no_links[index]
  195. # n.location.x = i*200
  196. # n.location.y = j*200
  197. # except IndexError:
  198. # break_me = True # it's too big, that's OK the square is definitely bigger
  199. # break
  200. # if break_me:
  201. # break
  202. # from .utilities import SugiyamaGraph
  203. # SugiyamaGraph(vis_tree, 1) # this can take a really long time
  204. finally:
  205. s = io.StringIO()
  206. sortby = SortKey.TIME
  207. ps = pstats.Stats(pr, stream=s).strip_dirs().sort_stats(sortby)
  208. ps.print_stats(40) # print the top 20
  209. print(s.getvalue())
  210. base_tree.prevent_next_exec=True
  211. base_tree.is_executing=False
  212. from .ops_nodegroup import mantis_tree_poll_op
  213. class MantisVisualizeOutput(Operator):
  214. """Mantis Visualize Output Operator"""
  215. bl_idname = "mantis.visualize_output"
  216. bl_label = "Visualize Output"
  217. @classmethod
  218. def poll(cls, context):
  219. return (mantis_tree_poll_op(context))
  220. def execute(self, context):
  221. from time import time
  222. from .utilities import wrapGreen, prGreen
  223. tree=context.space_data.path[0].node_tree
  224. tree.update_tree(context)
  225. prGreen(f"Visualize Tree: {tree.name}")
  226. nodes = tree.parsed_tree
  227. visualize_tree(nodes, tree, context)
  228. return {"FINISHED"}