Node Wrangler: Add more specific poll methods
[blender-addons.git] / depsgraph_debug.py
blob60ce7471234a8c3af82648dc9dc6def00cbda08d
1 # SPDX-FileCopyrightText: 2017-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 import bpy
6 from bpy.types import (
7 Operator,
8 Panel,
10 from bpy.props import (StringProperty, )
12 bl_info = {
13 "name": "Dependency Graph Debug",
14 "author": "Sergey Sharybin",
15 "version": (0, 1),
16 "blender": (2, 80, 0),
17 "description": "Various dependency graph debugging tools",
18 "location": "Properties > View Layer > Dependency Graph",
19 "warning": "",
20 "doc_url": "",
21 "tracker_url": "",
22 "category": "Development",
26 def _get_depsgraph(context):
27 scene = context.scene
28 if bpy.app.version < (2, 80, 0,):
29 return scene.depsgraph
30 else:
31 view_layer = context.view_layer
32 return view_layer.depsgraph
35 ###############################################################################
36 # Save data from depsgraph to a specified file.
38 class SCENE_OT_depsgraph_save_common:
39 filepath: StringProperty(
40 name="File Path",
41 description="Filepath used for saving the file",
42 maxlen=1024,
43 subtype='FILE_PATH',
46 def _getExtension(self, context):
47 return ""
49 @classmethod
50 def poll(cls, context):
51 depsgraph = _get_depsgraph(context)
52 return depsgraph is not None
54 def invoke(self, context, event):
55 import os
56 if not self.filepath:
57 blend_filepath = context.blend_data.filepath
58 if not blend_filepath:
59 blend_filepath = "deg"
60 else:
61 blend_filepath = os.path.splitext(blend_filepath)[0]
63 self.filepath = blend_filepath + self._getExtension(context)
64 context.window_manager.fileselect_add(self)
65 return {'RUNNING_MODAL'}
67 def execute(self, context):
68 depsgraph = _get_depsgraph(context)
69 if not self.performSave(context, depsgraph):
70 return {'CANCELLED'}
71 return {'FINISHED'}
73 def performSave(self, context, depsgraph):
74 pass
77 class SCENE_OT_depsgraph_relations_graphviz(
78 Operator,
79 SCENE_OT_depsgraph_save_common,
81 bl_idname = "scene.depsgraph_relations_graphviz"
82 bl_label = "Save Depsgraph"
83 bl_description = "Save current scene's dependency graph to a graphviz file"
85 def _getExtension(self, context):
86 return ".dot"
88 def performSave(self, context, depsgraph):
89 import os
90 basename, extension = os.path.splitext(self.filepath)
91 depsgraph.debug_relations_graphviz(os.path.join(self.filepath, basename + ".dot"))
92 return True
95 class SCENE_OT_depsgraph_stats_gnuplot(
96 Operator,
97 SCENE_OT_depsgraph_save_common,
99 bl_idname = "scene.depsgraph_stats_gnuplot"
100 bl_label = "Save Depsgraph Stats"
101 bl_description = "Save current scene's evaluaiton stats to gnuplot file"
103 def _getExtension(self, context):
104 return ".plot"
106 def performSave(self, context, depsgraph):
107 depsgraph.debug_stats_gnuplot(self.filepath, "")
108 return True
111 ###############################################################################
112 # Visualize some depsgraph information as an image opening in image editor.
114 class SCENE_OT_depsgraph_image_common:
115 def _getOrCreateImageForAbsPath(self, filepath):
116 for image in bpy.data.images:
117 if image.filepath == filepath:
118 image.reload()
119 return image
120 return bpy.data.images.load(filepath, check_existing=True)
122 def _findBestImageEditor(self, context, image):
123 first_none_editor = None
124 for area in context.screen.areas:
125 if area.type != 'IMAGE_EDITOR':
126 continue
127 for space in area.spaces:
128 if space.type != 'IMAGE_EDITOR':
129 continue
130 if not space.image:
131 first_none_editor = space
132 else:
133 if space.image == image:
134 return space
135 return first_none_editor
137 def _createTempFile(self, suffix):
138 import os
139 import tempfile
140 fd, filepath = tempfile.mkstemp(suffix=suffix)
141 os.close(fd)
142 return filepath
144 def _openImageInEditor(self, context, image_filepath):
145 image = self._getOrCreateImageForAbsPath(image_filepath)
146 editor = self._findBestImageEditor(context, image)
147 if editor:
148 editor.image = image
150 def execute(self, context):
151 depsgraph = _get_depsgraph(context)
152 if not self.performSave(context, depsgraph):
153 return {'CANCELLED'}
154 return {'FINISHED'}
156 def performSave(self, context, depsgraph):
157 pass
160 class SCENE_OT_depsgraph_relations_image(Operator,
161 SCENE_OT_depsgraph_image_common):
162 bl_idname = "scene.depsgraph_relations_image"
163 bl_label = "Depsgraph as Image"
164 bl_description = "Create new image datablock from the dependency graph"
166 def performSave(self, context, depsgraph):
167 import os
168 import subprocess
169 # Create temporary file.
170 dot_filepath = self._createTempFile(suffix=".dot")
171 # Save dependency graph to graphviz file.
172 depsgraph.debug_relations_graphviz(dot_filepath)
173 # Convert graphviz to PNG image.
174 png_filepath = os.path.join(bpy.app.tempdir, "depsgraph.png")
175 command = ("dot", "-Tpng", dot_filepath, "-o", png_filepath)
176 try:
177 subprocess.run(command)
178 self._openImageInEditor(context, png_filepath)
179 except:
180 self.report({'ERROR'}, "Error invoking dot command")
181 return False
182 finally:
183 # Remove graphviz file.
184 os.remove(dot_filepath)
185 return True
188 class SCENE_OT_depsgraph_stats_image(Operator,
189 SCENE_OT_depsgraph_image_common):
190 bl_idname = "scene.depsgraph_stats_image"
191 bl_label = "Depsgraph Stats as Image"
192 bl_description = "Create new image datablock from the dependency graph " + \
193 "execution statistics"
195 def performSave(self, context, depsgraph):
196 import os
197 import subprocess
198 # Create temporary file.
199 plot_filepath = self._createTempFile(suffix=".plot")
200 png_filepath = os.path.join(bpy.app.tempdir, "depsgraph_stats.png")
201 # Save dependency graph stats to gnuplot file.
202 depsgraph.debug_stats_gnuplot(plot_filepath, png_filepath)
203 # Convert graphviz to PNG image.
204 command = ("gnuplot", plot_filepath)
205 try:
206 subprocess.run(command)
207 self._openImageInEditor(context, png_filepath)
208 except:
209 self.report({'ERROR'}, "Error invoking gnuplot command")
210 return False
211 finally:
212 # Remove graphviz file.
213 os.remove(plot_filepath)
214 return True
217 class SCENE_OT_depsgraph_relations_svg(Operator,
218 SCENE_OT_depsgraph_image_common):
219 bl_idname = "scene.depsgraph_relations_svg"
220 bl_label = "Depsgraph as SVG in Browser"
221 bl_description = "Create an SVG image from the dependency graph and open it in the web browser"
223 def performSave(self, context, depsgraph):
224 import os
225 import subprocess
226 import webbrowser
227 # Create temporary file.
228 dot_filepath = self._createTempFile(suffix=".dot")
229 # Save dependency graph to graphviz file.
230 depsgraph.debug_relations_graphviz(dot_filepath)
231 # Convert graphviz to SVG image.
232 svg_filepath = os.path.join(bpy.app.tempdir, "depsgraph.svg")
233 command = ("dot", "-Tsvg", dot_filepath, "-o", svg_filepath)
234 try:
235 subprocess.run(command)
236 webbrowser.open_new_tab("file://" + os.path.abspath(svg_filepath))
237 except:
238 self.report({'ERROR'}, "Error invoking dot command")
239 return False
240 finally:
241 # Remove graphviz file.
242 os.remove(dot_filepath)
243 return True
246 ###############################################################################
247 # Interface.
250 class SCENE_PT_depsgraph_common:
251 def draw(self, context):
252 layout = self.layout
253 col = layout.column()
254 # Everything related on relations and graph topology.
255 col.label(text="Relations:")
256 row = col.row()
257 row.operator("scene.depsgraph_relations_graphviz")
258 row.operator("scene.depsgraph_relations_image")
259 col.operator("scene.depsgraph_relations_svg")
260 # Everything related on evaluaiton statistics.
261 col.label(text="Statistics:")
262 row = col.row()
263 row.operator("scene.depsgraph_stats_gnuplot")
264 row.operator("scene.depsgraph_stats_image")
267 class SCENE_PT_depsgraph(bpy.types.Panel, SCENE_PT_depsgraph_common):
268 bl_label = "Dependency Graph"
269 bl_space_type = "PROPERTIES"
270 bl_region_type = "WINDOW"
271 bl_context = "scene"
272 bl_options = {'DEFAULT_CLOSED'}
274 @classmethod
275 def poll(cls, context):
276 if bpy.app.version >= (2, 80, 0,):
277 return False
278 depsgraph = _get_depsgraph(context)
279 return depsgraph is not None
282 class RENDERLAYER_PT_depsgraph(bpy.types.Panel, SCENE_PT_depsgraph_common):
283 bl_label = "Dependency Graph"
284 bl_space_type = "PROPERTIES"
285 bl_region_type = "WINDOW"
286 bl_context = "view_layer"
287 bl_options = {'DEFAULT_CLOSED'}
289 @classmethod
290 def poll(cls, context):
291 if bpy.app.version < (2, 80, 0,):
292 return False
293 depsgraph = _get_depsgraph(context)
294 return depsgraph is not None
297 def register():
298 bpy.utils.register_class(SCENE_OT_depsgraph_relations_graphviz)
299 bpy.utils.register_class(SCENE_OT_depsgraph_relations_image)
300 bpy.utils.register_class(SCENE_OT_depsgraph_relations_svg)
301 bpy.utils.register_class(SCENE_OT_depsgraph_stats_gnuplot)
302 bpy.utils.register_class(SCENE_OT_depsgraph_stats_image)
303 bpy.utils.register_class(SCENE_PT_depsgraph)
304 bpy.utils.register_class(RENDERLAYER_PT_depsgraph)
307 def unregister():
308 bpy.utils.unregister_class(SCENE_OT_depsgraph_relations_graphviz)
309 bpy.utils.unregister_class(SCENE_OT_depsgraph_relations_image)
310 bpy.utils.unregister_class(SCENE_OT_depsgraph_relations_svg)
311 bpy.utils.unregister_class(SCENE_OT_depsgraph_stats_gnuplot)
312 bpy.utils.unregister_class(SCENE_OT_depsgraph_stats_image)
313 bpy.utils.unregister_class(SCENE_PT_depsgraph)
314 bpy.utils.unregister_class(RENDERLAYER_PT_depsgraph)
317 if __name__ == "__main__":
318 register()