Starting a render from a callback gives error “Missing 'window' in context”? I want to start a render from a callback, but I also need the render preview to update while it renders.

If I call bpy.ops.render.render() then there is no preview.

If I call bpy.ops.render.render("INVOKE_DEFAULT") then the preview gets updated normally.

However when I call bpy.ops.render.render("INVOKE_DEFAULT") from a callback, then I get the error "RuntimeError: Operator bpy.ops.render.render.poll() Missing 'window' in context".

Here's an example script that will generate the error:

import bpy

def in_1_seconds():
    bpy.ops.render.render("INVOKE_DEFAULT") #RuntimeError: Operator bpy.ops.render.render.poll() Missing 'window' in context

bpy.app.timers.register(in_1_seconds, first_interval=1)


I can not find a way around, I have tried all:

(‘INVOKE_DEFAULT’, ‘INVOKE_REGION_WIN’, ‘INVOKE_REGION_CHANNELS’, ‘INVOKE_REGION_PREVIEW’, ‘INVOKE_AREA’, ‘INVOKE_SCREEN’, ‘EXEC_DEFAULT’, ‘EXEC_REGION_WIN’, ‘EXEC_REGION_CHANNELS’, ‘EXEC_REGION_PREVIEW’, ‘EXEC_AREA’, ‘EXEC_SCREEN’)

The "INVOKE" types cause the error to be displayed while the "EXEC" types cause Blender to completely freeze until rendering finishes.

I need a way to start a render from Python exactly like pressing "F12" with live preview. Any ideas how to do achieve this from callbacks?

[EDIT]

This is not a duplicate of "Context is incorrect when calling from a timer"
Because after trying to impliment the answer from that post I get the exact same error and/or no render preview.

For example this does not work:

import time, bpy, functools
from bpy import context

def render(od):
    timestr = time.strftime("%Y%m%d-%H%M%S")
    print(timestr)
    # uncomment below line for error...
    bpy.ops.render.render("INVOKE_DEFAULT")
    return 5.0

def loop(idx):
    print("LOOP")
    window = context.window_manager.windows[idx]
    screen = window.screen
    views_3d = sorted(
            [a for a in screen.areas if a.type == 'VIEW_3D'],
            key=lambda a: (a.width * a.height))
    if views_3d:
        a = views_3d[0]
        # override
        o = {"window" : window,
             "screen" : screen,
             "area" : a,
             "space_data": a.spaces.active,
             "region" : a.regions[-1]
         }
        return render(o)
    return 2.0

#f = functools.partial(loop, bpy.context.screen.name)

if __name__ == '__main__':
    idx = bpy.context.window_manager.windows[:].index(bpy.context.window)
    bpy.app.timers.register(functools.partial(loop, idx), first_interval=5.0)


[EDIT]

More specifically I would like to start a render after the previous render completes:

The following script simply starts rendering, and when the render_complete callback is fired it increments the current frame then renders the next image (cycle repeats until endframe reached):

import bpy

endframe = 10 #bpy.context.scene.frame_end

def complete(*args, **kwargs):
    if bpy.context.scene.frame_current &lt;= endframe:
        bpy.context.scene.frame_current += 1
        #bpy.ops.render.render("INVOKE_DEFAULT") #I cause errors!!!   
        bpy.ops.render.render()  

bpy.app.handlers.render_complete.append(complete)

bpy.ops.render.render("INVOKE_DEFAULT")


In the above script only the very first render shows the preview updating during the render, while subsequent renders only show the preview update on completion of that frame. I would like all frames to show a updating preview as the frame is being rendered but I am completely stuck and don't know how to override context so I can use "INVOKE_DEFAULT"??
Create an override dictionary.


Pass the context dictionary 

bpy.ops.render.render(od, "INVOKE_DEFAULT")
Made a complete copy of context.  This is somewhat dangerous since an object reference to a removed object may be accessed which will core dump blender.  I have a feeling the context members required to override hinges on the render display mode selected. with new window scene.render.display_mode = 'WINDOW' mode, it appears only the context.window is needed.


Have put in a count to stop after 5 renders to test.


Test run, code slightly altered (highlighted) to render frames 50, 40, 30, 20 and 10. In image editor display mode, renders to the image editor (if no open images) or opens one in the largest area

import time, bpy, functools

count = 5
def render(od):
    global count
    count -= 1
    timestr = time.strftime("%Y%m%d-%H%M%S")
    print(timestr)
    # I get no error in the line below
    bpy.ops.render.render(od, "INVOKE_DEFAULT")

    return 2 if count else None

def loop(c):
    print("LOOP", count)

    # override
    c["scene"].frame_set(10 * count)
    return render(c)


if __name__ == '__main__':
    f = functools.partial(loop, bpy.context.copy())

    bpy.app.timers.register(f, first_interval=5.0)