2023-11-07 12:04:14 +01:00
|
|
|
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"
|
2024-08-22 14:37:01 +02:00
|
|
|
bl_options = {'DEFAULT_CLOSED'}
|
2023-11-07 12:04:14 +01:00
|
|
|
|
|
|
|
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:
|
2024-11-04 12:26:29 +01:00
|
|
|
|
|
|
|
t = mod_eval.execution_time
|
|
|
|
|
2023-11-07 12:04:14 +01:00
|
|
|
if i == 0:
|
2024-11-04 12:26:29 +01:00
|
|
|
# Initialize modifier dict time list
|
2023-11-07 12:04:14 +01:00
|
|
|
tmp_dic[mod_eval.name] = []
|
|
|
|
|
2024-11-04 12:26:29 +01:00
|
|
|
## Do not store first iteration, often less precise timing
|
|
|
|
continue
|
|
|
|
|
2023-11-07 12:04:14 +01:00
|
|
|
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)
|
2024-11-04 12:26:29 +01:00
|
|
|
evaluations_count : bpy.props.IntProperty(name='Evaluations', default=5, min=2)
|
2023-11-07 12:04:14 +01:00
|
|
|
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()
|