2
bpy.ops.object.bake('INVOKE_DEFAULT', type='DIFFUSE')
save_image()

Does not work because the bake uses 'INVOKE_DEFAULT' and thus save_image will be executed directly.

How to run save_image after the baking has finished?

Florian Ludewig
  • 253
  • 4
  • 16

2 Answers2

6

Although the question is old, can help more developers.
You can create a modal operator and check the state of the 'is_dirty' property of the image.

import bpy

class ModalTimerOperator(bpy.types.Operator): bl_idname = "wm.modal_timer_operator" bl_label = "Modal Timer Operator" _timer = None _img = None

def modal(self, context, event):
    if event.type in {'RIGHTMOUSE', 'ESC'}:
        self.cancel(context)
        return {'CANCELLED'}

    if event.type == 'TIMER':
        if self._img.is_dirty: # <--- Wait until the image is marked as dirty
            self.finish(context)
            return {'FINISHED'}

    return {'PASS_THROUGH'}

def execute(self, context: bpy.context):
    if context.scene.render.engine != 'CYCLES':
        context.scene.render.engine = 'CYCLES'

    if not context.active_object.select_get() or not context.active_object.type == 'MESH':
        self.report({'WARNING'}, "No valid selected objects")
        return {'FINISHED'}

    obj = context.active_object
    mats = obj.data.materials
    mats_len = len(mats)

    if mats_len == 0 or not mats[0]:
        mat_new = bpy.data.materials.new('BakeMaterial')
        if mats_len == 0:
            mats.append(mat_new)
        else:
            mats[0] = mat_new
        pass

    mat = mats[0]
    mat.use_nodes = True
    nodes = mat.node_tree.nodes
    tex_node = nodes.new('ShaderNodeTexImage')
    tex_node.name = 'BakeNode'
    tex_node.select = True
    nodes.active = tex_node
    self._img = bpy.data.images.new('BakeResult', 1024, 1024)
    tex_node.image = self._img

    self.report({'INFO'}, "Execute")
    result = bpy.ops.object.bake('INVOKE_DEFAULT', type='DIFFUSE')
    if result != {'RUNNING_MODAL'}: # <--- Important to check the result
        self.report({'WARNING'}, "Failed to start baking")
        return {'FINISHED'}

    wm = context.window_manager
    self._timer = wm.event_timer_add(0.5, window=context.window)
    wm.modal_handler_add(self)
    return {'RUNNING_MODAL'}

def cancel(self, context):
    self.report({'INFO'}, "Baking map cancelled")

def finish(self, context):
    wm = context.window_manager
    wm.event_timer_remove(self._timer)
    self.report({'INFO'}, "Baking map completed")
    #save_image() # <--- Call the function after baking

def register(): bpy.utils.register_class(ModalTimerOperator)

def unregister(): bpy.utils.unregister_class(ModalTimerOperator)

if name == "main": register() bpy.ops.wm.modal_timer_operator()

Carneiro
  • 81
  • 1
  • 4
0

Thank you. Great script. As a side note, once the image data is baked, is_dity returns true. If you want to bake that image over again, you need to pack the image once. In

self._img.pack()

, the value of is_dirty returns false. Now you can create a modus operandi that bakes images on top of each other.

When overlapping bakes, scene settings should also be reviewed.

bpy.context.scene.render.bake.use_clear = False
mml
  • 443
  • 2
  • 9