diff --git a/depsgraph_debug.py b/depsgraph_debug.py new file mode 100644 index 0000000000000000000000000000000000000000..9d919dcc91b86ed6ac2c63873f61e09b04ea0eca --- /dev/null +++ b/depsgraph_debug.py @@ -0,0 +1,197 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy +from bpy.types import ( + Operator, + Panel, + ) +from bpy.props import (StringProperty, ) + +bl_info = { + "name": "Dependency Graph Debug", + "author": "Sergey Sharybin", + "version": (0, 1), + "blender": (2, 79, 0), + "description": "Various dependency graph debugging tools", + "warning": "", + "wiki_url": "", + "tracker_url": "", + "category": "Development"} + + +def _get_depsgraph(context): + scene = context.scene + if bpy.app.version < (2, 80, 0,): + return scene.depsgraph + else: + scene_layer = scene.render_layers.active + return scene_layer.depsgraph + + +class SCENE_OT_depsgraph_graphviz(Operator): + bl_idname = "scene.depsgraph_graphviz" + bl_label = "Save Depsgraph" + bl_description = "Save current scene's dependency graph to a graphviz file" + + filepath = StringProperty( + name="File Path", + description="Filepath used for saving the file", + maxlen=1024, + subtype='FILE_PATH', + ) + + @classmethod + def poll(cls, context): + depsgraph = _get_depsgraph(context) + return depsgraph is not None + + def invoke(self, context, event): + import os + if not self.filepath: + blend_filepath = context.blend_data.filepath + if not blend_filepath: + blend_filepath = "deg" + else: + blend_filepath = os.path.splitext(blend_filepath)[0] + + self.filepath = blend_filepath + ".dot" + + context.window_manager.fileselect_add(self) + + return {'RUNNING_MODAL'} + + def execute(self, context): + depsgraph = _get_depsgraph(context) + depsgraph.debug_graphviz(self.filepath) + return {'FINISHED'} + + +class SCENE_OT_depsgraph_image(Operator): + bl_idname = "scene.depsgraph_image" + bl_label = "Depsgraph as Image" + bl_description = "Create new image datablock from the dependency graph" + + def _getOrCreateImageForAbsPath(self, filepath): + for image in bpy.data.images: + if image.filepath == filepath: + image.reload() + return image + return bpy.data.images.load(filepath, check_existing=True) + + def _findBestImageEditor(self, context, image): + first_none_editor = None + for area in context.screen.areas: + if area.type != 'IMAGE_EDITOR': + continue + for space in area.spaces: + if space.type != 'IMAGE_EDITOR': + continue + if not space.image: + first_none_editor = space + else: + if space.image == image: + return space + return first_none_editor + + def execute(self, context): + import os + import subprocess + import tempfile + # Create temporary file. + fd, dot_filepath = tempfile.mkstemp(suffix=".dot") + os.close(fd) + # Save dependency graph to graphviz file. + depsgraph = _get_depsgraph(context) + depsgraph.debug_graphviz(dot_filepath) + # Convert graphviz to PNG image. + png_filepath = os.path.join(bpy.app.tempdir, "depsgraph.png") + command = ("dot", "-Tpng", dot_filepath, "-o", png_filepath) + image = None + try: + subprocess.run(command) + # Open image in Blender. + image = self._getOrCreateImageForAbsPath(png_filepath) + except: + self.report({'ERROR'}, "Error invoking dot command") + return {'CANCELLED'} + finally: + # Remove graphviz file. + os.remove(dot_filepath) + editor = self._findBestImageEditor(context, image) + if editor: + editor.image = image + return {'FINISHED'} + + +class SCENE_PT_depsgraph(bpy.types.Panel): + bl_label = "Dependency Graph" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "scene" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + if bpy.app.version >= (2, 80, 0,): + return False + depsgraph = _get_depsgraph(context) + return depsgraph is not None + + def draw(self, context): + layout = self.layout + layout.operator("scene.depsgraph_graphviz") + layout.operator("scene.depsgraph_image") + + +class RENDERLAYER_PT_depsgraph(bpy.types.Panel): + bl_label = "Dependency Graph" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render_layer" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + if bpy.app.version < (2, 80, 0,): + return False + depsgraph = _get_depsgraph(context) + return depsgraph is not None + + def draw(self, context): + layout = self.layout + layout.operator("scene.depsgraph_graphviz") + layout.operator("scene.depsgraph_image") + + +def register(): + bpy.utils.register_class(SCENE_OT_depsgraph_graphviz) + bpy.utils.register_class(SCENE_OT_depsgraph_image) + bpy.utils.register_class(SCENE_PT_depsgraph) + bpy.utils.register_class(RENDERLAYER_PT_depsgraph) + + +def unregister(): + bpy.utils.unregister_class(SCENE_OT_depsgraph_graphviz) + bpy.utils.unregister_class(SCENE_OT_depsgraph_image) + bpy.utils.unregister_class(SCENE_PT_depsgraph) + bpy.utils.unregister_class(RENDERLAYER_PT_depsgraph) + + +if __name__ == "__main__": + register()