How to get progres info from ops.object.bake()

I have created a texture bake tool that adds an extra Texture node to all materials of a set of selected objects. In order to not block the Blender user interface while the bake tool is running i am using the baker as follows:

bpy.ops.object.bake(context, 'INVOKE_DEFAULT', ...)

i also embedded the call to the object.bake inside a modal operator and i added a panel to the properties bar where i want to show the bake state for all affected objects/materials:

However the bake tool seems to run silently in the background without telling any information about its current progress. So my bake tool is completely blind and can not figure out when the object.bake is finished. and it also can not figure out which Object it currently processes. and finally it does not know when to finish the modal operation.

I tried this as well:

`bpy.app.handlers.render_complete.append(cleanup_after_bake)`

But the cleanup_after_bake() function is only called when i start a render with F12, so this does also not solve my issue.

I hope that i just do not know how to get the information. However in the case that object.bake operator does not send out progress information of any kind, then isn’t that actually a strongly needed feature after all?.

There is no way to get the granular progress as you suggest (without significant changes in the code base). There is already a progress bar when baking, and that should be reliable enough.

If you want control over the baking process I recommend baking one object at a time. This is what the code does internally anyways

It still would be very helpful if at least the end of bake is comunicated. It could tag the objects for example. But i think its even better if the baker sends an Event when it is done. Then we can capture this event and modal operators which call object.bake() now get informed when they can terminate cleanly.

I wrote this some time ago as a proof of concept. Added some more comments on how it works and a progress report.

Essentially we create a macro, then add bakes to the macro and set their properties etc. At the end of a queue, we put in an operator that says the bakes are done.

All this happens inside a modal, because for some reason successive bakes won’t start unless there’s an event callback.

import bpy, _bpy

class WM_OT_bake_report(bpy.types.Operator):
    bl_idname = "wm.bake_report"
    bl_label = "Bake Report"
    bl_options = {'INTERNAL'}

    queue_position: bpy.props.IntProperty()
    queue_size: bpy.props.IntProperty()

    def execute(self, context):
        p = self.queue_position
        s = self.queue_size
        self.report({'INFO'}, f"{round(100 * p / s)}%")
        return {'FINISHED'}

class WM_OT_set_finished(bpy.types.Operator):
    bl_idname = "wm.bake_set_finished"
    bl_label = "Bake Set Finished"
    bl_options = {'INTERNAL'}

    def execute(self, context):
        # this is the last operator to run in the bake queue
        # signal the modal operator to end
        WM_OT_bake_modal._ready = True
        return {'FINISHED'}

def get_macro():
    class OBJECT_OT_bake_macro(bpy.types.Macro):
        bl_idname = "object.bake_macro"
        bl_label = "Bake Macro"
        bl_options = {'INTERNAL'}

    # unregister any previous macro
    if hasattr(bpy.types, "OBJECT_OT_bake_macro"):
        bpy.utils.unregister_class(bpy.types.OBJECT_OT_bake_macro)

    bpy.utils.register_class(OBJECT_OT_bake_macro)
    return OBJECT_OT_bake_macro

# bake operator
class WM_OT_bake_modal(bpy.types.Operator):
    bl_idname = "wm.bake_modal"
    bl_label = "Bake Modal"

    def modal(self, context, event):
        
        # keep checking the attribute
        if getattr(__class__, '_ready', False):
            __class__._ready = False
            context.window_manager.event_timer_remove(self.timer)
            self.report({'INFO'}, "Finished")
            print("Done")
            return {'FINISHED'}
        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        
        # macro is a container that lets
        # us define any number of sub-operators
        macro = get_macro()

        # we can add sub-operators to a list for easy access
        bake_queue = []

        # let's do 16 bakes
        num_bakes = 16
        for i in range(num_bakes):
            bake = _bpy.ops.macro_define(macro, 'OBJECT_OT_bake')
            bake_queue.append(bake)

            # add a progress report operator
            report = _bpy.ops.macro_define(macro, 'WM_OT_bake_report')
            report.properties.queue_size = num_bakes
            report.properties.queue_position = i

        # set some bake properties
        bake_queue[0].properties.margin = 24

        # define a last operator that tells the modal to end
        _bpy.ops.macro_define(macro, 'WM_OT_bake_set_finished')

        # the macro container is ready. remember
        # to use 'INVOKE_DEFAULT' to keep the ui responsive
        bpy.ops.object.bake_macro('INVOKE_DEFAULT')

        __class__._ready = False
        wm = context.window_manager
        # a timer is needed to jumpstart each successive macro
        self.timer = wm.event_timer_add(0.1, window=context.window)
        wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

def register():
    bpy.utils.register_class(WM_OT_bake_report)
    bpy.utils.register_class(WM_OT_set_finished)
    bpy.utils.register_class(WM_OT_bake_modal)

if __name__ == '__main__':
    register()