diff --git a/README.md b/README.md index 3acc5da..081b09e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ -# GIT_TEMPLATE -> Template for the others repository +# PROFILING BUDDY + +Profiling Panel in Modifier stack + +Forked from Simon Thommes [random-blender-scripts](https://gitlab.com/simonthommes/random-blender-scripts/-/tree/master/addons/profiling_buddy?ref_type=heads) -Git_template is a template repository that must be used to generate others repository *** @@ -34,7 +36,7 @@ Git_template is a template repository that must be used to generate others repos ``` 2. Create your own local directory in ```sh - git clone ssh://git@git.autourdeminuit.com:222/autour_de_minuit/git_template.git + git clone ssh://git@git.autourdeminuit.com:222/autour_de_minuit/profiling_buddy.git ``` diff --git a/__init__.py b/__init__.py new file mode 100755 index 0000000..c908758 --- /dev/null +++ b/__init__.py @@ -0,0 +1,22 @@ +import bpy +from . import modifier_profiling + +bl_info = { + "name": "Profiling Buddy", + "author": "Simon Thommes, Samuel Bernou", + "version": (0, 2), + "blender": (3, 5, 0), + "location": "Properties > Modifier > Profiling Buddy", + "description": "Adds panels to profile execution times.", + "category": "Workflow", +} + +modules = [modifier_profiling] + +def register(): + for m in modules: + m.register() + +def unregister(): + for m in modules: + m.unregister() diff --git a/modifier_profiling.py b/modifier_profiling.py new file mode 100755 index 0000000..22b0782 --- /dev/null +++ b/modifier_profiling.py @@ -0,0 +1,240 @@ +import bpy +from bpy.types import PropertyGroup, Operator +from bpy.props import (StringProperty, + FloatProperty, + PointerProperty, + CollectionProperty) + +class PB_Modifier_Profiling_Panel(bpy.types.Panel): + """ + """ + bl_label = "Modifier Profiling" + bl_idname = "SCENE_PT_modifier_profiling" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "modifier" + + def draw(self, context): + layout = self.layout + scene = context.scene + settings = scene.profiler_buddy_prop + + layout.prop(settings, 'evaluation_live') + + if settings.evaluation_live: + draw_modifier_times_live(self, context) + else: + draw_modifier_times(self, context) + col = layout.column() + # col.prop(settings, 'use_frame_set') + if settings.progress >= 0: + col.progress(factor=settings.progress / settings.evaluations_count, + text = f"Iteration {settings.progress}/{settings.evaluations_count}") + ## Show modifier sub-iteration with a load ring ? + # col.progress(factor=settings.subprogress / len(context.object.modifiers), + # type = 'RING') # , text = f"{settings.subprogress}/{len(context.object.modifiers)}" + + row = col.row() + row.prop(settings, 'evaluations_count') + row.operator('pb.evaluate', text='Evaluate') + + + +def time_to_string(t): + ''' Formats time in seconds to the nearest sensible unit. + ''' + units = {3600.: 'h', 60.: 'm', 1.: 's', .001: 'ms'} + for factor in units.keys(): + if t >= factor: + return f'{t/factor:.3g} {units[factor]}' + if t >= 1e-4: + return f'{t/factor:.3g} {units[factor]}' + else: + return f'<0.1 ms' + +## Dynamic draw + +def draw_modifier_times_live(self, context): + depsgraph = context.view_layer.depsgraph + + ob = context.object + ob_eval = ob.evaluated_get(depsgraph) + + box = self.layout.box() + times = [] + total = 0 + for mod_eval in ob_eval.modifiers: + t = mod_eval.execution_time + times += [t] + total += t + + col_fl = box.column_flow(columns=2) + col = col_fl.column(align=True) + for mod_eval in ob_eval.modifiers: + row = col.row() + row.enabled = mod_eval.show_viewport + row.label(text=f'{mod_eval.name}:') + + col = col_fl.column(align=True) + for i, t in enumerate(times): + row = col.row() + row.enabled = ob_eval.modifiers[i].show_viewport + row.alert = t >= 0.8*max(times) + row.label(text=time_to_string(t)) + + row = box.column_flow() + row.column().label(text=f'TOTAL:') + row.column().label(text=time_to_string(sum(times))) + +## Static draw from dict + +def draw_modifier_times(self, context): + box = self.layout.box() + + ob = context.object + wm = bpy.context.window_manager + mod_timer = wm.get('mod_timer') + if not mod_timer: + mod_timer = wm['mod_timer'] = {} + + if len(ob.modifiers) == 0: + return + + if mod_timer: + # box.label(text=f'Average out of {context.scene.profiler_buddy_prop.evaluations_count}') + times = [t for _m, t in mod_timer.items()] + col_fl = box.column_flow(columns=2) + col = col_fl.column(align=True) + for i, (mod_name, t) in enumerate(mod_timer.items()): + if i > len(ob.modifiers) - 1: + continue + row = col.row() + row.enabled = ob.modifiers[i].show_viewport + row.label(text=f'{mod_name}:') + + col = col_fl.column(align=True) + for i, (mod_name, t) in enumerate(mod_timer.items()): + if i > len(ob.modifiers) - 1: + continue + row = col.row() + row.enabled = ob.modifiers[i].show_viewport + row.alert = t >= 0.8*max(times) + row.label(text=time_to_string(t)) + + # col = col_fl.column(align=True) + # for i, (mod_name, t) in enumerate(mod_timer.items()): + # if i > len(ob.modifiers) - 1: + # continue + # row = col.row() + # row.enabled = ob.modifiers[i].show_viewport + # row.alert = t >= 0.8*max(times) + # row.label(text=f'{1000 / (t*1000):.0f} fps') + + row = box.column_flow() + + total_time = sum(times) + fps = f'{1000 / (total_time*1000):.1f} fps' if total_time > 0 else '-- fps' + # row.label(text = fps) + row.column().label(text=f'TOTAL: {fps}') + + row.column().label(text=time_to_string(total_time)) + + + else: + if context.scene.profiler_buddy_prop.progress < 0: + box.label(text='Click Evaluate To Profile Modifiers', icon='INFO') + + +class PB_OT_evalulate(Operator): + bl_idname = "pb.evaluate" + bl_label = "Evaluate Modifiers" + bl_description = "Evaluate modifier by a count " + bl_options = {"REGISTER"} # , "INTERNAL" + + def execute(self, context): + wm = bpy.context.window_manager + + settings = context.scene.profiler_buddy_prop + mod_timer = wm.get('mod_timer') + if not mod_timer: + mod_timer = wm['mod_timer'] = {} + else: + mod_timer = {} + + wm['mod_timer'].clear() # Clear dict + + tmp_dic = {} + ob = context.object + + wm.progress_begin(0, settings.evaluations_count) + for i in range(settings.evaluations_count): + settings.progress = i + bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) + # context.area.tag_redraw() + ob.update_tag() + # if settings.use_frame_set: + # context.scene.frame_current = context.scene.frame_current + + depsgraph = context.view_layer.depsgraph + depsgraph.update() + ob_eval = ob.evaluated_get(depsgraph) + + ## gather a list of all evaluated sessions + for mod_eval in ob_eval.modifiers: + if i == 0: + tmp_dic[mod_eval.name] = [] + + t = mod_eval.execution_time + # print(i, mod_eval.name, t) + # wm['mod_timer'][mod_eval.name] = t # keep last evaluated time + tmp_dic[mod_eval.name] += [t] + + wm.progress_update(i) + + wm.progress_end() + + settings.progress = -1 + + for k, v in tmp_dic.items(): + average = sum(v) / len(v) + # print(k, v, average) + ## debugging + # if settings.evaluations_count > 1: + # if v[0] == v[1]: + # print('same:') + # for subv in v: + # print(subv) + + wm['mod_timer'][k] = average + + return {"FINISHED"} + + +class PB_PG_modifiers_profiler(PropertyGroup): + evaluation_live : bpy.props.BoolProperty(name='Live Mode', default=True) + evaluations_count : bpy.props.IntProperty(name='Evaluations', default=5) + progress : bpy.props.IntProperty(name='Progress', default=-1) + # subprogress : bpy.props.IntProperty(name='ModifierProgress', default=-1) + # use_frame_set : bpy.props.BoolProperty(name='Set Frame At Each Iteration', default=True) + +classes = [ + PB_OT_evalulate, + PB_Modifier_Profiling_Panel, + ] + + +def register(): + bpy.utils.register_class(PB_PG_modifiers_profiler) + bpy.types.Scene.profiler_buddy_prop = PointerProperty(type=PB_PG_modifiers_profiler) + bpy.context.window_manager['mod_timer'] = {} + for c in classes: + bpy.utils.register_class(c) + +def unregister(): + for c in reversed(classes): + bpy.utils.unregister_class(c) + bpy.utils.unregister_class(PB_PG_modifiers_profiler) + del bpy.types.Scene.profiler_buddy_prop + +if __name__ == "__main__": + register() \ No newline at end of file