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 <= 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"??