Prefer get_rna_type() since it doesn't leak memory
[blender-addons.git] / depsgraph_debug.py
blob438d48858c49dc61bb3d1133e0b1e7f5f65081d3
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, 79, 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(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):
96 return ".dot"
98 def performSave(self, context, depsgraph):
99 basename, extension = os.path.splitext(self.filepath)
100 depsgraph.debug_relations_graphviz(self.filepath, absename + ".png")
101 return True
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):
111 return ".plot"
113 def performSave(self, context, depsgraph):
114 depsgraph.debug_stats_gnuplot(self.filepath, "")
115 return True
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:
125 image.reload()
126 return image
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':
133 continue
134 for space in area.spaces:
135 if space.type != 'IMAGE_EDITOR':
136 continue
137 if not space.image:
138 first_none_editor = space
139 else:
140 if space.image == image:
141 return space
142 return first_none_editor
144 def _createTempFile(self, suffix):
145 import os
146 import tempfile
147 fd, filepath = tempfile.mkstemp(suffix=suffix)
148 os.close(fd)
149 return filepath
151 def _openImageInEditor(self, context, image_filepath):
152 image = self._getOrCreateImageForAbsPath(image_filepath)
153 editor = self._findBestImageEditor(context, image)
154 if editor:
155 editor.image = image
157 def execute(self, context):
158 depsgraph = _get_depsgraph(context)
159 if not self.performSave(context, depsgraph):
160 return {'CANCELLED'}
161 return {'FINISHED'}
163 def performSave(self, context, depsgraph):
164 pass
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):
174 import os
175 import subprocess
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)
183 try:
184 subprocess.run(command)
185 self._openImageInEditor(context, png_filepath)
186 except:
187 self.report({'ERROR'}, "Error invoking dot command")
188 return False
189 finally:
190 # Remove graphviz file.
191 os.remove(dot_filepath)
192 return True
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):
203 import os
204 import subprocess
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)
212 try:
213 subprocess.run(command)
214 self._openImageInEditor(context, png_filepath)
215 except:
216 self.report({'ERROR'}, "Error invoking gnuplot command")
217 return False
218 finally:
219 # Remove graphviz file.
220 os.remove(plot_filepath)
221 return True
224 ###############################################################################
225 # Interface.
228 class SCENE_PT_depsgraph_common:
229 def draw(self, context):
230 layout = self.layout
231 col = layout.column()
232 # Everything related on relations and graph topology.
233 col.label(text="Relations:")
234 row = col.row()
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:")
239 row = col.row()
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"
248 bl_context = "scene"
249 bl_options = {'DEFAULT_CLOSED'}
251 @classmethod
252 def poll(cls, context):
253 if bpy.app.version >= (2, 80, 0,):
254 return False
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'}
266 @classmethod
267 def poll(cls, context):
268 if bpy.app.version < (2, 80, 0,):
269 return False
270 depsgraph = _get_depsgraph(context)
271 return depsgraph is not None
274 def register():
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)
283 def unregister():
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__":
293 register()