To clear up some misunderstandings:
There are no get and set methods to modify the context, and mostly not for other purposes either. It's simply not the Python way of coding. ob.attr
is your getter, and ob.attr = 123
the setter. Magic methods can intercept get and set operations to customize object behavior, but that's advanced stuff.
There's also a programmatic way to get and set any attribute, but you only ever need it if the attribute name is dynamic or not always present: hasattr(ob, "attr")
, getattr(ob, "attr")
, setattr(ob, "attr", 123)
.
All immediate members of bpy.context
are read-only. You can't modify anything here. You can do something like bpy.context.window.screen = bpy.data.screens["Default"]
however. screen
is not a direct context attribute and editable. On the other hand, bpy.context.screen
is read-only. bpy.context.window_manager.windows[#].screen
is editable again.
To run an operator in a custom context means, that you construct a Python dictionary with context member names as keys, and datablock references as values. You do not modify the actual context:
ob = bpy.context.scene.object_bases["Cube"]
ctx = {"selected_bases": [ob]} # dict to store custom context
bpy.ops.object.delete(ctx) # run operator with custom context
Above code deletes the object Cube
, even if completely different objects (or rather object bases) are selected without changing the selection temporarily.
It also prints a bunch of PyContext warnings to console, because the delete operator expects a couple more context members. We did not provide them in above code, we only set selected_bases
. A nice, although limited trick is to run an operator with an empty context to figure out (some of the) context members:
bpy.ops.object.delete({})
It's sometimes easier to derive the context from the actual context, and only modify the necessary dictionary keys:
ctx = bpy.context.copy()
ctx["selected_bases"] = [bpy.context.scene.object_bases["Cube"]]
bpy.ops.object.delete(ctx)
Above code should not cause any PyContext warnings.
We can apply this to the screenshot operator now:
context = bpy.context
for area in context.screen.areas:
if area.type == 'VIEW_3D':
ctx = {
"window": context.window, # current window, could also copy context
"area": area, # our 3D View (the first found only actually)
"region": None # just to suppress PyContext warning, doesn't seem to have any effect
}
bpy.ops.screen.screenshot(ctx, filepath="...", full=False)
break # limit to first 3D View (optional)
Don't ever rely on the area index, it's not a stable property.
If you want an screenshot of a certain region (exactly as seen on screen), you can do the following:
import bpy
import bgl
context = bpy.context
def screenshot_region(region):
x = region.x
y = region.y
width = region.width
height = region.height
buf = bgl.Buffer(bgl.GL_FLOAT, width * height * 4)
bgl.glReadPixels(x, y, width, height, bgl.GL_RGBA, bgl.GL_FLOAT, buf)
img = bpy.data.images.new("Screenshot", width, height, alpha=True)
img.pixels[:] = buf
for area in context.screen.areas:
if area.type == 'VIEW_3D':
for region in area.regions:
if region.type == 'WINDOW':
screenshot_region(region)
break
It creates an image datablock with the screenshot. You may wanna save it using Image.save_as()
, which will use the scene's render settings. Or use struct.pack()
to create some sort of bitmap file directly.
If you want to take a series of screenshots, you could do the following:
scene = context.scene
frame_orig = scene.frame_current
for frame in range(scene.frame_start, scene.frame_end + 1):
scene.frame_set(frame)
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
screenshot_region(region)
scene.frame_set(frame_orig)
Also see Can I redraw during the script?.
Here's a more sophisticated example:
import bpy
import bgl
context = bpy.context
scene = context.scene
def screenshot_region(region):
x = region.x
y = region.y
width = region.width
height = region.height
view_settings(area.spaces.active)
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
buf = bgl.Buffer(bgl.GL_FLOAT, width * height * 4)
bgl.glReadPixels(x, y, width, height, bgl.GL_RGBA, bgl.GL_FLOAT, buf)
img = bpy.data.images.new("Screenshot", width, height, alpha=True)
img.pixels[:] = buf
view_settings(area.spaces.active, True)
def view_settings(view3d, revert=False):
pref = context.user_preferences.view
if not revert:
view_settings.ob_info = pref.show_object_info
view_settings.view_name = pref.show_view_name
view_settings.mini_axis = pref.show_mini_axis
#view_settings.only_render = view3d.show_only_render
view_settings.manipulator = view3d.show_manipulator
pref.show_object_info = False
pref.show_view_name = False
pref.show_mini_axis = False
#view3d.show_only_render = False
view3d.show_manipulator = False
else:
pref.show_object_info = view_settings.ob_info
pref.show_view_name = view_settings.view_name
pref.show_mini_axis = view_settings.mini_axis
#view3d.show_only_render = view_settings.only_render
view3d.show_manipulator = view_settings.manipulator
for area in context.screen.areas:
if area.type == 'VIEW_3D':
for region in area.regions:
if region.type == 'WINDOW':
for frame in range(scene.frame_start, scene.frame_end + 1):
scene.frame_set(frame)
screenshot_region(region)
break
Related reads:
bpy.context.window
but when I do dir() on that, the only setter I find seems to be.cursor_set()
which is not what I need. Where can I find or look up information like "the identity of the current active window can be read or set using the following attribute...". Where is this stuff documented in a way that is easy to look-up? Thanks! $\endgroup$[area.type for area in windows[0].screen.areas if "3" in area.type]
returns['VIEW 3D']
. But I really don't see how to make it "the active area". $\endgroup$