1 # SPDX-License-Identifier: GPL-2.0-or-later
4 from bpy
.types
import (
8 from bpy
.props
import (StringProperty
, )
11 "name": "Dependency Graph Debug",
12 "author": "Sergey Sharybin",
14 "blender": (2, 80, 0),
15 "description": "Various dependency graph debugging tools",
16 "location": "Properties > View Layer > Dependency Graph",
20 "category": "Development",
24 def _get_depsgraph(context
):
26 if bpy
.app
.version
< (2, 80, 0,):
27 return scene
.depsgraph
29 view_layer
= context
.view_layer
30 return view_layer
.depsgraph
33 ###############################################################################
34 # Save data from depsgraph to a specified file.
36 class SCENE_OT_depsgraph_save_common
:
37 filepath
: StringProperty(
39 description
="Filepath used for saving the file",
44 def _getExtension(self
, context
):
48 def poll(cls
, context
):
49 depsgraph
= _get_depsgraph(context
)
50 return depsgraph
is not None
52 def invoke(self
, context
, event
):
55 blend_filepath
= context
.blend_data
.filepath
56 if not blend_filepath
:
57 blend_filepath
= "deg"
59 blend_filepath
= os
.path
.splitext(blend_filepath
)[0]
61 self
.filepath
= blend_filepath
+ self
._getExtension
(context
)
62 context
.window_manager
.fileselect_add(self
)
63 return {'RUNNING_MODAL'}
65 def execute(self
, context
):
66 depsgraph
= _get_depsgraph(context
)
67 if not self
.performSave(context
, depsgraph
):
71 def performSave(self
, context
, depsgraph
):
75 class SCENE_OT_depsgraph_relations_graphviz(
77 SCENE_OT_depsgraph_save_common
,
79 bl_idname
= "scene.depsgraph_relations_graphviz"
80 bl_label
= "Save Depsgraph"
81 bl_description
= "Save current scene's dependency graph to a graphviz file"
83 def _getExtension(self
, context
):
86 def performSave(self
, context
, depsgraph
):
88 basename
, extension
= os
.path
.splitext(self
.filepath
)
89 depsgraph
.debug_relations_graphviz(os
.path
.join(self
.filepath
, basename
+ ".dot"))
93 class SCENE_OT_depsgraph_stats_gnuplot(
95 SCENE_OT_depsgraph_save_common
,
97 bl_idname
= "scene.depsgraph_stats_gnuplot"
98 bl_label
= "Save Depsgraph Stats"
99 bl_description
= "Save current scene's evaluaiton stats to gnuplot file"
101 def _getExtension(self
, context
):
104 def performSave(self
, context
, depsgraph
):
105 depsgraph
.debug_stats_gnuplot(self
.filepath
, "")
109 ###############################################################################
110 # Visualize some depsgraph information as an image opening in image editor.
112 class SCENE_OT_depsgraph_image_common
:
113 def _getOrCreateImageForAbsPath(self
, filepath
):
114 for image
in bpy
.data
.images
:
115 if image
.filepath
== filepath
:
118 return bpy
.data
.images
.load(filepath
, check_existing
=True)
120 def _findBestImageEditor(self
, context
, image
):
121 first_none_editor
= None
122 for area
in context
.screen
.areas
:
123 if area
.type != 'IMAGE_EDITOR':
125 for space
in area
.spaces
:
126 if space
.type != 'IMAGE_EDITOR':
129 first_none_editor
= space
131 if space
.image
== image
:
133 return first_none_editor
135 def _createTempFile(self
, suffix
):
138 fd
, filepath
= tempfile
.mkstemp(suffix
=suffix
)
142 def _openImageInEditor(self
, context
, image_filepath
):
143 image
= self
._getOrCreateImageForAbsPath
(image_filepath
)
144 editor
= self
._findBestImageEditor
(context
, image
)
148 def execute(self
, context
):
149 depsgraph
= _get_depsgraph(context
)
150 if not self
.performSave(context
, depsgraph
):
154 def performSave(self
, context
, depsgraph
):
158 class SCENE_OT_depsgraph_relations_image(Operator
,
159 SCENE_OT_depsgraph_image_common
):
160 bl_idname
= "scene.depsgraph_relations_image"
161 bl_label
= "Depsgraph as Image"
162 bl_description
= "Create new image datablock from the dependency graph"
164 def performSave(self
, context
, depsgraph
):
167 # Create temporary file.
168 dot_filepath
= self
._createTempFile
(suffix
=".dot")
169 # Save dependency graph to graphviz file.
170 depsgraph
.debug_relations_graphviz(dot_filepath
)
171 # Convert graphviz to PNG image.
172 png_filepath
= os
.path
.join(bpy
.app
.tempdir
, "depsgraph.png")
173 command
= ("dot", "-Tpng", dot_filepath
, "-o", png_filepath
)
175 subprocess
.run(command
)
176 self
._openImageInEditor
(context
, png_filepath
)
178 self
.report({'ERROR'}, "Error invoking dot command")
181 # Remove graphviz file.
182 os
.remove(dot_filepath
)
186 class SCENE_OT_depsgraph_stats_image(Operator
,
187 SCENE_OT_depsgraph_image_common
):
188 bl_idname
= "scene.depsgraph_stats_image"
189 bl_label
= "Depsgraph Stats as Image"
190 bl_description
= "Create new image datablock from the dependency graph " + \
191 "execution statistics"
193 def performSave(self
, context
, depsgraph
):
196 # Create temporary file.
197 plot_filepath
= self
._createTempFile
(suffix
=".plot")
198 png_filepath
= os
.path
.join(bpy
.app
.tempdir
, "depsgraph_stats.png")
199 # Save dependency graph stats to gnuplot file.
200 depsgraph
.debug_stats_gnuplot(plot_filepath
, png_filepath
)
201 # Convert graphviz to PNG image.
202 command
= ("gnuplot", plot_filepath
)
204 subprocess
.run(command
)
205 self
._openImageInEditor
(context
, png_filepath
)
207 self
.report({'ERROR'}, "Error invoking gnuplot command")
210 # Remove graphviz file.
211 os
.remove(plot_filepath
)
215 class SCENE_OT_depsgraph_relations_svg(Operator
,
216 SCENE_OT_depsgraph_image_common
):
217 bl_idname
= "scene.depsgraph_relations_svg"
218 bl_label
= "Depsgraph as SVG in Browser"
219 bl_description
= "Create an SVG image from the dependency graph and open it in the web browser"
221 def performSave(self
, context
, depsgraph
):
225 # Create temporary file.
226 dot_filepath
= self
._createTempFile
(suffix
=".dot")
227 # Save dependency graph to graphviz file.
228 depsgraph
.debug_relations_graphviz(dot_filepath
)
229 # Convert graphviz to SVG image.
230 svg_filepath
= os
.path
.join(bpy
.app
.tempdir
, "depsgraph.svg")
231 command
= ("dot", "-Tsvg", dot_filepath
, "-o", svg_filepath
)
233 subprocess
.run(command
)
234 webbrowser
.open_new_tab("file://" + os
.path
.abspath(svg_filepath
))
236 self
.report({'ERROR'}, "Error invoking dot command")
239 # Remove graphviz file.
240 os
.remove(dot_filepath
)
244 ###############################################################################
248 class SCENE_PT_depsgraph_common
:
249 def draw(self
, context
):
251 col
= layout
.column()
252 # Everything related on relations and graph topology.
253 col
.label(text
="Relations:")
255 row
.operator("scene.depsgraph_relations_graphviz")
256 row
.operator("scene.depsgraph_relations_image")
257 col
.operator("scene.depsgraph_relations_svg")
258 # Everything related on evaluaiton statistics.
259 col
.label(text
="Statistics:")
261 row
.operator("scene.depsgraph_stats_gnuplot")
262 row
.operator("scene.depsgraph_stats_image")
265 class SCENE_PT_depsgraph(bpy
.types
.Panel
, SCENE_PT_depsgraph_common
):
266 bl_label
= "Dependency Graph"
267 bl_space_type
= "PROPERTIES"
268 bl_region_type
= "WINDOW"
270 bl_options
= {'DEFAULT_CLOSED'}
273 def poll(cls
, context
):
274 if bpy
.app
.version
>= (2, 80, 0,):
276 depsgraph
= _get_depsgraph(context
)
277 return depsgraph
is not None
280 class RENDERLAYER_PT_depsgraph(bpy
.types
.Panel
, SCENE_PT_depsgraph_common
):
281 bl_label
= "Dependency Graph"
282 bl_space_type
= "PROPERTIES"
283 bl_region_type
= "WINDOW"
284 bl_context
= "view_layer"
285 bl_options
= {'DEFAULT_CLOSED'}
288 def poll(cls
, context
):
289 if bpy
.app
.version
< (2, 80, 0,):
291 depsgraph
= _get_depsgraph(context
)
292 return depsgraph
is not None
296 bpy
.utils
.register_class(SCENE_OT_depsgraph_relations_graphviz
)
297 bpy
.utils
.register_class(SCENE_OT_depsgraph_relations_image
)
298 bpy
.utils
.register_class(SCENE_OT_depsgraph_relations_svg
)
299 bpy
.utils
.register_class(SCENE_OT_depsgraph_stats_gnuplot
)
300 bpy
.utils
.register_class(SCENE_OT_depsgraph_stats_image
)
301 bpy
.utils
.register_class(SCENE_PT_depsgraph
)
302 bpy
.utils
.register_class(RENDERLAYER_PT_depsgraph
)
306 bpy
.utils
.unregister_class(SCENE_OT_depsgraph_relations_graphviz
)
307 bpy
.utils
.unregister_class(SCENE_OT_depsgraph_relations_image
)
308 bpy
.utils
.unregister_class(SCENE_OT_depsgraph_relations_svg
)
309 bpy
.utils
.unregister_class(SCENE_OT_depsgraph_stats_gnuplot
)
310 bpy
.utils
.unregister_class(SCENE_OT_depsgraph_stats_image
)
311 bpy
.utils
.unregister_class(SCENE_PT_depsgraph
)
312 bpy
.utils
.unregister_class(RENDERLAYER_PT_depsgraph
)
315 if __name__
== "__main__":