# Context is incorrect when calling from a timer

Eevee in Blender 2.8 is so awesome that just saving the viewport (view > Viewport Render Image) looks really good.

I'd like to save the viewport periodically from a timer script using the operation bpy.ops.render.opengl()

Here's some code that gets close:

# viewport_loop.py

import time, bpy, functools

def render():
timestr = time.strftime("%Y%m%d-%H%M%S")
print(timestr)
# uncomment below line for error...
# bpy.ops.render.opengl(animation=False, sequencer=False, write_still=True, view_context=True)

def loop(data):
render()
return 2.0

if __name__ == '__main__':
bpy.app.timers.register(functools.partial(loop, ""), first_interval=5.0)



I'm calling the script from the command line on osx

blender ./file.blend --python ./viewport_loop.py


When the render line is uncommented I'm getting the error:

RuntimeError: Operator bpy.ops.render.opengl.poll() failed, context is incorrect

bpy.ops.render.opengl() does work, but not in this timer (or in a thread incidentally).

Any thoughts, is there any way to override the context for this operation?

Perhaps around the 'view_context' parameter of bpy.ops.render.opengl() because I've seen in other answers that a context needs to be overridden. For example the following, which enlarges the viewport...

for window in bpy.context.window_manager.windows:
screen = window.screen
for area in screen.areas:
if area.type == 'VIEW_3D':
override = {'window': window, 'screen': screen, 'area': area}
bpy.ops.screen.screen_full_area(override)
break


Override the context.

With the timers running partial functions in their own thread, cannot rely on having many context members available. The usual suspects window, screen, scene, object will be None.

One exception is the window manager.

Test script, starts the timer with the index of the context window in the window managers windows collection.

From the window, the screen, area, space and region can be added to a context override dictionary to pass to the operator.

Test script, renders the largest 3d view open in the current screen.

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.opengl(od, animation=False, sequencer=False, write_still=True, view_context=True)
return 5.0

def loop(idx):
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)


Note: The big issue here re polling is not all context is available to the timer thread.

for k, v in bpy.context.copy.items():
if v is None:
print(k)


in loop method above will demonstrate which context members are not available (set to None).

and hence would recommend using a modal timer operator (wired to use context), that you could turn on and off from a button

Related

How to run a python script at regular intervals?

Need help moving a set of cubes from one end to the other on x-axis with python scripting

• Oh I see! So you can actually pass a context override into that bpy.ops.render.opengl(od...), that's awesome, thanks for the help. Apr 2, 2019 at 17:00