Update for API change: scene.cursor_location -> scene.cursor.location
[blender-addons.git] / depsgraph_debug.py
blob76f2529091bfafc24ac7bd01f7ce215b147be3a8
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 #####
19 import bpy
20 from bpy.types import (
21 Operator,
22 Panel,
24 from bpy.props import (StringProperty, )
26 bl_info = {
27 "name": "Dependency Graph Debug",
28 "author": "Sergey Sharybin",
29 "version": (0, 1),
30 "blender": (2, 80, 0),
31 "description": "Various dependency graph debugging tools",
32 "warning": "",
33 "wiki_url": "",
34 "tracker_url": "",
35 "category": "Development"}
38 def _get_depsgraph(context):
39 scene = context.scene
40 if bpy.app.version < (2, 80, 0,):
41 return scene.depsgraph
42 else:
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(
52 name="File Path",
53 description="Filepath used for saving the file",
54 maxlen=1024,
55 subtype='FILE_PATH',
58 def _getExtension(self, context):
59 return ""
61 @classmethod
62 def poll(cls, context):
63 depsgraph = _get_depsgraph(context)
64 return depsgraph is not None
66 def invoke(self, context, event):
67 import os
68 if not self.filepath:
69 blend_filepath = context.blend_data.filepath
70 if not blend_filepath:
71 blend_filepath = "deg"
72 else:
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):
82 return {'CANCELLED'}
83 return {'FINISHED'}
85 def performSave(self, context, depsgraph):
86 pass
89 class SCENE_OT_depsgraph_relations_graphviz(
90 Operator,
91 SCENE_OT_depsgraph_save_common,
93 bl_idname = "scene.depsgraph_relations_graphviz"
94 bl_label = "Save Depsgraph"
95 bl_description = "Save current scene's dependency graph to a graphviz file"
97 def _getExtension(self, context):
98 return ".dot"
100 def performSave(self, context, depsgraph):
101 import os
102 basename, extension = os.path.splitext(self.filepath)
103 depsgraph.debug_relations_graphviz(os.path.join(self.filepath, basename + ".dot"))
104 return True
107 class SCENE_OT_depsgraph_stats_gnuplot(
108 Operator,
109 SCENE_OT_depsgraph_save_common,
111 bl_idname = "scene.depsgraph_stats_gnuplot"
112 bl_label = "Save Depsgraph Stats"
113 bl_description = "Save current scene's evaluaiton stats to gnuplot file"
115 def _getExtension(self, context):
116 return ".plot"
118 def performSave(self, context, depsgraph):
119 depsgraph.debug_stats_gnuplot(self.filepath, "")
120 return True
123 ###############################################################################
124 # Visualize some depsgraph information as an image opening in image editor.
126 class SCENE_OT_depsgraph_image_common:
127 def _getOrCreateImageForAbsPath(self, filepath):
128 for image in bpy.data.images:
129 if image.filepath == filepath:
130 image.reload()
131 return image
132 return bpy.data.images.load(filepath, check_existing=True)
134 def _findBestImageEditor(self, context, image):
135 first_none_editor = None
136 for area in context.screen.areas:
137 if area.type != 'IMAGE_EDITOR':
138 continue
139 for space in area.spaces:
140 if space.type != 'IMAGE_EDITOR':
141 continue
142 if not space.image:
143 first_none_editor = space
144 else:
145 if space.image == image:
146 return space
147 return first_none_editor
149 def _createTempFile(self, suffix):
150 import os
151 import tempfile
152 fd, filepath = tempfile.mkstemp(suffix=suffix)
153 os.close(fd)
154 return filepath
156 def _openImageInEditor(self, context, image_filepath):
157 image = self._getOrCreateImageForAbsPath(image_filepath)
158 editor = self._findBestImageEditor(context, image)
159 if editor:
160 editor.image = image
162 def execute(self, context):
163 depsgraph = _get_depsgraph(context)
164 if not self.performSave(context, depsgraph):
165 return {'CANCELLED'}
166 return {'FINISHED'}
168 def performSave(self, context, depsgraph):
169 pass
172 class SCENE_OT_depsgraph_relations_image(Operator,
173 SCENE_OT_depsgraph_image_common):
174 bl_idname = "scene.depsgraph_relations_image"
175 bl_label = "Depsgraph as Image"
176 bl_description = "Create new image datablock from the dependency graph"
178 def performSave(self, context, depsgraph):
179 import os
180 import subprocess
181 # Create temporary file.
182 dot_filepath = self._createTempFile(suffix=".dot")
183 # Save dependency graph to graphviz file.
184 depsgraph.debug_relations_graphviz(dot_filepath)
185 # Convert graphviz to PNG image.
186 png_filepath = os.path.join(bpy.app.tempdir, "depsgraph.png")
187 command = ("dot", "-Tpng", dot_filepath, "-o", png_filepath)
188 try:
189 subprocess.run(command)
190 self._openImageInEditor(context, png_filepath)
191 except:
192 self.report({'ERROR'}, "Error invoking dot command")
193 return False
194 finally:
195 # Remove graphviz file.
196 os.remove(dot_filepath)
197 return True
200 class SCENE_OT_depsgraph_stats_image(Operator,
201 SCENE_OT_depsgraph_image_common):
202 bl_idname = "scene.depsgraph_stats_image"
203 bl_label = "Depsgraph Stats as Image"
204 bl_description = "Create new image datablock from the dependency graph " + \
205 "execution statistics"
207 def performSave(self, context, depsgraph):
208 import os
209 import subprocess
210 # Create temporary file.
211 plot_filepath = self._createTempFile(suffix=".plot")
212 png_filepath = os.path.join(bpy.app.tempdir, "depsgraph_stats.png")
213 # Save dependency graph stats to gnuplot file.
214 depsgraph.debug_stats_gnuplot(plot_filepath, png_filepath)
215 # Convert graphviz to PNG image.
216 command = ("gnuplot", plot_filepath)
217 try:
218 subprocess.run(command)
219 self._openImageInEditor(context, png_filepath)
220 except:
221 self.report({'ERROR'}, "Error invoking gnuplot command")
222 return False
223 finally:
224 # Remove graphviz file.
225 os.remove(plot_filepath)
226 return True
229 ###############################################################################
230 # Interface.
233 class SCENE_PT_depsgraph_common:
234 def draw(self, context):
235 layout = self.layout
236 col = layout.column()
237 # Everything related on relations and graph topology.
238 col.label(text="Relations:")
239 row = col.row()
240 row.operator("scene.depsgraph_relations_graphviz")
241 row.operator("scene.depsgraph_relations_image")
242 # Everything related on evaluaiton statistics.
243 col.label(text="Statistics:")
244 row = col.row()
245 row.operator("scene.depsgraph_stats_gnuplot")
246 row.operator("scene.depsgraph_stats_image")
249 class SCENE_PT_depsgraph(bpy.types.Panel, SCENE_PT_depsgraph_common):
250 bl_label = "Dependency Graph"
251 bl_space_type = "PROPERTIES"
252 bl_region_type = "WINDOW"
253 bl_context = "scene"
254 bl_options = {'DEFAULT_CLOSED'}
256 @classmethod
257 def poll(cls, context):
258 if bpy.app.version >= (2, 80, 0,):
259 return False
260 depsgraph = _get_depsgraph(context)
261 return depsgraph is not None
264 class RENDERLAYER_PT_depsgraph(bpy.types.Panel, SCENE_PT_depsgraph_common):
265 bl_label = "Dependency Graph"
266 bl_space_type = "PROPERTIES"
267 bl_region_type = "WINDOW"
268 bl_context = "view_layer"
269 bl_options = {'DEFAULT_CLOSED'}
271 @classmethod
272 def poll(cls, context):
273 if bpy.app.version < (2, 80, 0,):
274 return False
275 depsgraph = _get_depsgraph(context)
276 return depsgraph is not None
279 def register():
280 bpy.utils.register_class(SCENE_OT_depsgraph_relations_graphviz)
281 bpy.utils.register_class(SCENE_OT_depsgraph_relations_image)
282 bpy.utils.register_class(SCENE_OT_depsgraph_stats_gnuplot)
283 bpy.utils.register_class(SCENE_OT_depsgraph_stats_image)
284 bpy.utils.register_class(SCENE_PT_depsgraph)
285 bpy.utils.register_class(RENDERLAYER_PT_depsgraph)
288 def unregister():
289 bpy.utils.unregister_class(SCENE_OT_depsgraph_relations_graphviz)
290 bpy.utils.unregister_class(SCENE_OT_depsgraph_relations_image)
291 bpy.utils.unregister_class(SCENE_OT_depsgraph_stats_gnuplot)
292 bpy.utils.unregister_class(SCENE_OT_depsgraph_stats_image)
293 bpy.utils.unregister_class(SCENE_PT_depsgraph)
294 bpy.utils.unregister_class(RENDERLAYER_PT_depsgraph)
297 if __name__ == "__main__":
298 register()