visualize.py 9.3 KB

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