object_print3d_utils: replace f-strings by str.format() for I18n
[blender-addons.git] / depsgraph_debug.py
blob7c8784a290d26ce4e9ce54139e7a91d1bf39bdae
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 import bpy
4 from bpy.types import (
5 Operator,
6 Panel,
8 from bpy.props import (StringProperty, )
10 bl_info = {
11 "name": "Dependency Graph Debug",
12 "author": "Sergey Sharybin",
13 "version": (0, 1),
14 "blender": (2, 80, 0),
15 "description": "Various dependency graph debugging tools",
16 "warning": "",
17 "doc_url": "",
18 "tracker_url": "",
19 "category": "Development",
23 def _get_depsgraph(context):
24 scene = context.scene
25 if bpy.app.version < (2, 80, 0,):
26 return scene.depsgraph
27 else:
28 view_layer = context.view_layer
29 return view_layer.depsgraph
32 ###############################################################################
33 # Save data from depsgraph to a specified file.
35 class SCENE_OT_depsgraph_save_common:
36 filepath: StringProperty(
37 name="File Path",
38 description="Filepath used for saving the file",
39 maxlen=1024,
40 subtype='FILE_PATH',
43 def _getExtension(self, context):
44 return ""
46 @classmethod
47 def poll(cls, context):
48 depsgraph = _get_depsgraph(context)
49 return depsgraph is not None
51 def invoke(self, context, event):
52 import os
53 if not self.filepath:
54 blend_filepath = context.blend_data.filepath
55 if not blend_filepath:
56 blend_filepath = "deg"
57 else:
58 blend_filepath = os.path.splitext(blend_filepath)[0]
60 self.filepath = blend_filepath + self._getExtension(context)
61 context.window_manager.fileselect_add(self)
62 return {'RUNNING_MODAL'}
64 def execute(self, context):
65 depsgraph = _get_depsgraph(context)
66 if not self.performSave(context, depsgraph):
67 return {'CANCELLED'}
68 return {'FINISHED'}
70 def performSave(self, context, depsgraph):
71 pass
74 class SCENE_OT_depsgraph_relations_graphviz(
75 Operator,
76 SCENE_OT_depsgraph_save_common,
78 bl_idname = "scene.depsgraph_relations_graphviz"
79 bl_label = "Save Depsgraph"
80 bl_description = "Save current scene's dependency graph to a graphviz file"
82 def _getExtension(self, context):
83 return ".dot"
85 def performSave(self, context, depsgraph):
86 import os
87 basename, extension = os.path.splitext(self.filepath)
88 depsgraph.debug_relations_graphviz(os.path.join(self.filepath, basename + ".dot"))
89 return True
92 class SCENE_OT_depsgraph_stats_gnuplot(
93 Operator,
94 SCENE_OT_depsgraph_save_common,
96 bl_idname = "scene.depsgraph_stats_gnuplot"
97 bl_label = "Save Depsgraph Stats"
98 bl_description = "Save current scene's evaluaiton stats to gnuplot file"
100 def _getExtension(self, context):
101 return ".plot"
103 def performSave(self, context, depsgraph):
104 depsgraph.debug_stats_gnuplot(self.filepath, "")
105 return True
108 ###############################################################################
109 # Visualize some depsgraph information as an image opening in image editor.
111 class SCENE_OT_depsgraph_image_common:
112 def _getOrCreateImageForAbsPath(self, filepath):
113 for image in bpy.data.images:
114 if image.filepath == filepath:
115 image.reload()
116 return image
117 return bpy.data.images.load(filepath, check_existing=True)
119 def _findBestImageEditor(self, context, image):
120 first_none_editor = None
121 for area in context.screen.areas:
122 if area.type != 'IMAGE_EDITOR':
123 continue
124 for space in area.spaces:
125 if space.type != 'IMAGE_EDITOR':
126 continue
127 if not space.image:
128 first_none_editor = space
129 else:
130 if space.image == image:
131 return space
132 return first_none_editor
134 def _createTempFile(self, suffix):
135 import os
136 import tempfile
137 fd, filepath = tempfile.mkstemp(suffix=suffix)
138 os.close(fd)
139 return filepath
141 def _openImageInEditor(self, context, image_filepath):
142 image = self._getOrCreateImageForAbsPath(image_filepath)
143 editor = self._findBestImageEditor(context, image)
144 if editor:
145 editor.image = image
147 def execute(self, context):
148 depsgraph = _get_depsgraph(context)
149 if not self.performSave(context, depsgraph):
150 return {'CANCELLED'}
151 return {'FINISHED'}
153 def performSave(self, context, depsgraph):
154 pass
157 class SCENE_OT_depsgraph_relations_image(Operator,
158 SCENE_OT_depsgraph_image_common):
159 bl_idname = "scene.depsgraph_relations_image"
160 bl_label = "Depsgraph as Image"
161 bl_description = "Create new image datablock from the dependency graph"
163 def performSave(self, context, depsgraph):
164 import os
165 import subprocess
166 # Create temporary file.
167 dot_filepath = self._createTempFile(suffix=".dot")
168 # Save dependency graph to graphviz file.
169 depsgraph.debug_relations_graphviz(dot_filepath)
170 # Convert graphviz to PNG image.
171 png_filepath = os.path.join(bpy.app.tempdir, "depsgraph.png")
172 command = ("dot", "-Tpng", dot_filepath, "-o", png_filepath)
173 try:
174 subprocess.run(command)
175 self._openImageInEditor(context, png_filepath)
176 except:
177 self.report({'ERROR'}, "Error invoking dot command")
178 return False
179 finally:
180 # Remove graphviz file.
181 os.remove(dot_filepath)
182 return True
185 class SCENE_OT_depsgraph_stats_image(Operator,
186 SCENE_OT_depsgraph_image_common):
187 bl_idname = "scene.depsgraph_stats_image"
188 bl_label = "Depsgraph Stats as Image"
189 bl_description = "Create new image datablock from the dependency graph " + \
190 "execution statistics"
192 def performSave(self, context, depsgraph):
193 import os
194 import subprocess
195 # Create temporary file.
196 plot_filepath = self._createTempFile(suffix=".plot")
197 png_filepath = os.path.join(bpy.app.tempdir, "depsgraph_stats.png")
198 # Save dependency graph stats to gnuplot file.
199 depsgraph.debug_stats_gnuplot(plot_filepath, png_filepath)
200 # Convert graphviz to PNG image.
201 command = ("gnuplot", plot_filepath)
202 try:
203 subprocess.run(command)
204 self._openImageInEditor(context, png_filepath)
205 except:
206 self.report({'ERROR'}, "Error invoking gnuplot command")
207 return False
208 finally:
209 # Remove graphviz file.
210 os.remove(plot_filepath)
211 return True
214 class SCENE_OT_depsgraph_relations_svg(Operator,
215 SCENE_OT_depsgraph_image_common):
216 bl_idname = "scene.depsgraph_relations_svg"
217 bl_label = "Depsgraph as SVG in Browser"
218 bl_description = "Create an SVG image from the dependency graph and open it in the web browser"
220 def performSave(self, context, depsgraph):
221 import os
222 import subprocess
223 import webbrowser
224 # Create temporary file.
225 dot_filepath = self._createTempFile(suffix=".dot")
226 # Save dependency graph to graphviz file.
227 depsgraph.debug_relations_graphviz(dot_filepath)
228 # Convert graphviz to SVG image.
229 svg_filepath = os.path.join(bpy.app.tempdir, "depsgraph.svg")
230 command = ("dot", "-Tsvg", dot_filepath, "-o", svg_filepath)
231 try:
232 subprocess.run(command)
233 webbrowser.open_new_tab("file://" + os.path.abspath(svg_filepath))
234 except:
235 self.report({'ERROR'}, "Error invoking dot command")
236 return False
237 finally:
238 # Remove graphviz file.
239 os.remove(dot_filepath)
240 return True
243 ###############################################################################
244 # Interface.
247 class SCENE_PT_depsgraph_common:
248 def draw(self, context):
249 layout = self.layout
250 col = layout.column()
251 # Everything related on relations and graph topology.
252 col.label(text="Relations:")
253 row = col.row()
254 row.operator("scene.depsgraph_relations_graphviz")
255 row.operator("scene.depsgraph_relations_image")
256 col.operator("scene.depsgraph_relations_svg")
257 # Everything related on evaluaiton statistics.
258 col.label(text="Statistics:")
259 row = col.row()
260 row.operator("scene.depsgraph_stats_gnuplot")
261 row.operator("scene.depsgraph_stats_image")
264 class SCENE_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 = "scene"
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 class RENDERLAYER_PT_depsgraph(bpy.types.Panel, SCENE_PT_depsgraph_common):
280 bl_label = "Dependency Graph"
281 bl_space_type = "PROPERTIES"
282 bl_region_type = "WINDOW"
283 bl_context = "view_layer"
284 bl_options = {'DEFAULT_CLOSED'}
286 @classmethod
287 def poll(cls, context):
288 if bpy.app.version < (2, 80, 0,):
289 return False
290 depsgraph = _get_depsgraph(context)
291 return depsgraph is not None
294 def register():
295 bpy.utils.register_class(SCENE_OT_depsgraph_relations_graphviz)
296 bpy.utils.register_class(SCENE_OT_depsgraph_relations_image)
297 bpy.utils.register_class(SCENE_OT_depsgraph_relations_svg)
298 bpy.utils.register_class(SCENE_OT_depsgraph_stats_gnuplot)
299 bpy.utils.register_class(SCENE_OT_depsgraph_stats_image)
300 bpy.utils.register_class(SCENE_PT_depsgraph)
301 bpy.utils.register_class(RENDERLAYER_PT_depsgraph)
304 def unregister():
305 bpy.utils.unregister_class(SCENE_OT_depsgraph_relations_graphviz)
306 bpy.utils.unregister_class(SCENE_OT_depsgraph_relations_image)
307 bpy.utils.unregister_class(SCENE_OT_depsgraph_relations_svg)
308 bpy.utils.unregister_class(SCENE_OT_depsgraph_stats_gnuplot)
309 bpy.utils.unregister_class(SCENE_OT_depsgraph_stats_image)
310 bpy.utils.unregister_class(SCENE_PT_depsgraph)
311 bpy.utils.unregister_class(RENDERLAYER_PT_depsgraph)
314 if __name__ == "__main__":
315 register()