1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
20 from bpy
.types
import (
24 from bpy
.props
import (StringProperty
, )
27 "name": "Dependency Graph Debug",
28 "author": "Sergey Sharybin",
30 "blender": (2, 79, 0),
31 "description": "Various dependency graph debugging tools",
35 "category": "Development"}
38 def _get_depsgraph(context
):
40 if bpy
.app
.version
< (2, 80, 0,):
41 return scene
.depsgraph
43 view_layer
= context
.view_layer
44 return view_layer
.depsgraph
47 ###############################################################################
48 # Save data from depsgraph to a specified file.
50 class SCENE_OT_depsgraph_save_common
:
51 filepath
= StringProperty(
53 description
="Filepath used for saving the file",
58 def _getExtension(self
, context
):
62 def poll(cls
, context
):
63 depsgraph
= _get_depsgraph(context
)
64 return depsgraph
is not None
66 def invoke(self
, context
, event
):
69 blend_filepath
= context
.blend_data
.filepath
70 if not blend_filepath
:
71 blend_filepath
= "deg"
73 blend_filepath
= os
.path
.splitext(blend_filepath
)[0]
75 self
.filepath
= blend_filepath
+ self
._getExtension
(context
)
76 context
.window_manager
.fileselect_add(self
)
77 return {'RUNNING_MODAL'}
79 def execute(self
, context
):
80 depsgraph
= _get_depsgraph(context
)
81 if not self
.performSave(context
, depsgraph
):
85 def performSave(self
, context
, depsgraph
):
89 class SCENE_OT_depsgraph_relations_graphviz(Operator
,
90 SCENE_OT_depsgraph_save_common
):
91 bl_idname
= "scene.depsgraph_relations_graphviz"
92 bl_label
= "Save Depsgraph"
93 bl_description
= "Save current scene's dependency graph to a graphviz file"
95 def _getExtension(self
, context
):
98 def performSave(self
, context
, depsgraph
):
99 basename
, extension
= os
.path
.splitext(self
.filepath
)
100 depsgraph
.debug_relations_graphviz(self
.filepath
, absename
+ ".png")
104 class SCENE_OT_depsgraph_stats_gnuplot(Operator
,
105 SCENE_OT_depsgraph_save_common
):
106 bl_idname
= "scene.depsgraph_stats_gnuplot"
107 bl_label
= "Save Depsgraph Stats"
108 bl_description
= "Save current scene's evaluaiton stats to gnuplot file"
110 def _getExtension(self
, context
):
113 def performSave(self
, context
, depsgraph
):
114 depsgraph
.debug_stats_gnuplot(self
.filepath
, "")
118 ###############################################################################
119 # Visualize some depsgraph information as an image opening in image editor.
121 class SCENE_OT_depsgraph_image_common
:
122 def _getOrCreateImageForAbsPath(self
, filepath
):
123 for image
in bpy
.data
.images
:
124 if image
.filepath
== filepath
:
127 return bpy
.data
.images
.load(filepath
, check_existing
=True)
129 def _findBestImageEditor(self
, context
, image
):
130 first_none_editor
= None
131 for area
in context
.screen
.areas
:
132 if area
.type != 'IMAGE_EDITOR':
134 for space
in area
.spaces
:
135 if space
.type != 'IMAGE_EDITOR':
138 first_none_editor
= space
140 if space
.image
== image
:
142 return first_none_editor
144 def _createTempFile(self
, suffix
):
147 fd
, filepath
= tempfile
.mkstemp(suffix
=suffix
)
151 def _openImageInEditor(self
, context
, image_filepath
):
152 image
= self
._getOrCreateImageForAbsPath
(image_filepath
)
153 editor
= self
._findBestImageEditor
(context
, image
)
157 def execute(self
, context
):
158 depsgraph
= _get_depsgraph(context
)
159 if not self
.performSave(context
, depsgraph
):
163 def performSave(self
, context
, depsgraph
):
167 class SCENE_OT_depsgraph_relations_image(Operator
,
168 SCENE_OT_depsgraph_image_common
):
169 bl_idname
= "scene.depsgraph_relations_image"
170 bl_label
= "Depsgraph as Image"
171 bl_description
= "Create new image datablock from the dependency graph"
173 def performSave(self
, context
, depsgraph
):
176 # Create temporary file.
177 dot_filepath
= self
._createTempFile
(suffix
=".dot")
178 # Save dependency graph to graphviz file.
179 depsgraph
.debug_relations_graphviz(dot_filepath
)
180 # Convert graphviz to PNG image.
181 png_filepath
= os
.path
.join(bpy
.app
.tempdir
, "depsgraph.png")
182 command
= ("dot", "-Tpng", dot_filepath
, "-o", png_filepath
)
184 subprocess
.run(command
)
185 self
._openImageInEditor
(context
, png_filepath
)
187 self
.report({'ERROR'}, "Error invoking dot command")
190 # Remove graphviz file.
191 os
.remove(dot_filepath
)
195 class SCENE_OT_depsgraph_stats_image(Operator
,
196 SCENE_OT_depsgraph_image_common
):
197 bl_idname
= "scene.depsgraph_stats_image"
198 bl_label
= "Depsgraph Stats as Image"
199 bl_description
= "Create new image datablock from the dependency graph " + \
200 "execution statistics"
202 def performSave(self
, context
, depsgraph
):
205 # Create temporary file.
206 plot_filepath
= self
._createTempFile
(suffix
=".plot")
207 png_filepath
= os
.path
.join(bpy
.app
.tempdir
, "depsgraph_stats.png")
208 # Save dependency graph stats to gnuplot file.
209 depsgraph
.debug_stats_gnuplot(plot_filepath
, png_filepath
)
210 # Convert graphviz to PNG image.
211 command
= ("gnuplot", plot_filepath
)
213 subprocess
.run(command
)
214 self
._openImageInEditor
(context
, png_filepath
)
216 self
.report({'ERROR'}, "Error invoking gnuplot command")
219 # Remove graphviz file.
220 os
.remove(plot_filepath
)
224 ###############################################################################
228 class SCENE_PT_depsgraph_common
:
229 def draw(self
, context
):
231 col
= layout
.column()
232 # Everything related on relations and graph topology.
233 col
.label(text
="Relations:")
235 row
.operator("scene.depsgraph_relations_graphviz")
236 row
.operator("scene.depsgraph_relations_image")
237 # Everything related on evaluaiton statistics.
238 col
.label(text
="Statistics:")
240 row
.operator("scene.depsgraph_stats_gnuplot")
241 row
.operator("scene.depsgraph_stats_image")
244 class SCENE_PT_depsgraph(bpy
.types
.Panel
, SCENE_PT_depsgraph_common
):
245 bl_label
= "Dependency Graph"
246 bl_space_type
= "PROPERTIES"
247 bl_region_type
= "WINDOW"
249 bl_options
= {'DEFAULT_CLOSED'}
252 def poll(cls
, context
):
253 if bpy
.app
.version
>= (2, 80, 0,):
255 depsgraph
= _get_depsgraph(context
)
256 return depsgraph
is not None
259 class RENDERLAYER_PT_depsgraph(bpy
.types
.Panel
, SCENE_PT_depsgraph_common
):
260 bl_label
= "Dependency Graph"
261 bl_space_type
= "PROPERTIES"
262 bl_region_type
= "WINDOW"
263 bl_context
= "view_layer"
264 bl_options
= {'DEFAULT_CLOSED'}
267 def poll(cls
, context
):
268 if bpy
.app
.version
< (2, 80, 0,):
270 depsgraph
= _get_depsgraph(context
)
271 return depsgraph
is not None
275 bpy
.utils
.register_class(SCENE_OT_depsgraph_relations_graphviz
)
276 bpy
.utils
.register_class(SCENE_OT_depsgraph_relations_image
)
277 bpy
.utils
.register_class(SCENE_OT_depsgraph_stats_gnuplot
)
278 bpy
.utils
.register_class(SCENE_OT_depsgraph_stats_image
)
279 bpy
.utils
.register_class(SCENE_PT_depsgraph
)
280 bpy
.utils
.register_class(RENDERLAYER_PT_depsgraph
)
284 bpy
.utils
.unregister_class(SCENE_OT_depsgraph_relations_graphviz
)
285 bpy
.utils
.unregister_class(SCENE_OT_depsgraph_relations_image
)
286 bpy
.utils
.unregister_class(SCENE_OT_depsgraph_stats_gnuplot
)
287 bpy
.utils
.unregister_class(SCENE_OT_depsgraph_stats_image
)
288 bpy
.utils
.unregister_class(SCENE_PT_depsgraph
)
289 bpy
.utils
.unregister_class(RENDERLAYER_PT_depsgraph
)
292 if __name__
== "__main__":