1 # SPDX-FileCopyrightText: 2017-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
6 from bpy
.types
import (
10 from bpy
.props
import (StringProperty
, )
13 "name": "Dependency Graph Debug",
14 "author": "Sergey Sharybin",
16 "blender": (2, 80, 0),
17 "description": "Various dependency graph debugging tools",
18 "location": "Properties > View Layer > Dependency Graph",
22 "category": "Development",
26 def _get_depsgraph(context
):
28 if bpy
.app
.version
< (2, 80, 0,):
29 return scene
.depsgraph
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(
41 description
="Filepath used for saving the file",
46 def _getExtension(self
, context
):
50 def poll(cls
, context
):
51 depsgraph
= _get_depsgraph(context
)
52 return depsgraph
is not None
54 def invoke(self
, context
, event
):
57 blend_filepath
= context
.blend_data
.filepath
58 if not blend_filepath
:
59 blend_filepath
= "deg"
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
):
73 def performSave(self
, context
, depsgraph
):
77 class SCENE_OT_depsgraph_relations_graphviz(
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
):
88 def performSave(self
, context
, depsgraph
):
90 basename
, extension
= os
.path
.splitext(self
.filepath
)
91 depsgraph
.debug_relations_graphviz(os
.path
.join(self
.filepath
, basename
+ ".dot"))
95 class SCENE_OT_depsgraph_stats_gnuplot(
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
):
106 def performSave(self
, context
, depsgraph
):
107 depsgraph
.debug_stats_gnuplot(self
.filepath
, "")
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
:
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':
127 for space
in area
.spaces
:
128 if space
.type != 'IMAGE_EDITOR':
131 first_none_editor
= space
133 if space
.image
== image
:
135 return first_none_editor
137 def _createTempFile(self
, suffix
):
140 fd
, filepath
= tempfile
.mkstemp(suffix
=suffix
)
144 def _openImageInEditor(self
, context
, image_filepath
):
145 image
= self
._getOrCreateImageForAbsPath
(image_filepath
)
146 editor
= self
._findBestImageEditor
(context
, image
)
150 def execute(self
, context
):
151 depsgraph
= _get_depsgraph(context
)
152 if not self
.performSave(context
, depsgraph
):
156 def performSave(self
, context
, depsgraph
):
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
):
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
)
177 subprocess
.run(command
)
178 self
._openImageInEditor
(context
, png_filepath
)
180 self
.report({'ERROR'}, "Error invoking dot command")
183 # Remove graphviz file.
184 os
.remove(dot_filepath
)
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
):
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
)
206 subprocess
.run(command
)
207 self
._openImageInEditor
(context
, png_filepath
)
209 self
.report({'ERROR'}, "Error invoking gnuplot command")
212 # Remove graphviz file.
213 os
.remove(plot_filepath
)
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
):
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
)
235 subprocess
.run(command
)
236 webbrowser
.open_new_tab("file://" + os
.path
.abspath(svg_filepath
))
238 self
.report({'ERROR'}, "Error invoking dot command")
241 # Remove graphviz file.
242 os
.remove(dot_filepath
)
246 ###############################################################################
250 class SCENE_PT_depsgraph_common
:
251 def draw(self
, context
):
253 col
= layout
.column()
254 # Everything related on relations and graph topology.
255 col
.label(text
="Relations:")
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:")
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"
272 bl_options
= {'DEFAULT_CLOSED'}
275 def poll(cls
, context
):
276 if bpy
.app
.version
>= (2, 80, 0,):
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'}
290 def poll(cls
, context
):
291 if bpy
.app
.version
< (2, 80, 0,):
293 depsgraph
= _get_depsgraph(context
)
294 return depsgraph
is not None
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
)
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__":