52
$\begingroup$

I keep on getting context errors in the Blender Python API.

I don't understand the issue of the context in the API, how do I know which is the correct context?

Thanks!

$\endgroup$
3

2 Answers 2

67
$\begingroup$

The context members available depend on the area of blender which is currently being accessed.

See bpy.context

Many operators have a “poll” function which may check that the mouse is a valid area or that the object is in the correct mode (Edit Mode, Weight Paint etc). When an operator’s poll function fails within python, an exception is raised.

See Operator.poll()

More about using Operators and why you experience poll-fails (mouse not over right screen area or wrong mode / other pre-condition). Also see the Gotchas section of the API docs about that.

You can either:

  1. Run an operator via spacebar menu (see bottom of the section) in Blender 2.7x or using F3 as of Blender 2.8x from the proper Area (bl_options must not contain 'INTERNAL')
  2. Specify the right execution context for the operator call (this fixes the context error only for some!)
    (API docs, example)
  3. Use an override to pass the required context arguments
  4. Change the Area.type temporarily
  5. Use "low-level" API functions to achieve the same result without context worries

Override

Doesn't seem to work for this operator?! It adds a background but without name / texture.

Blender 3.2+

import bpy
for area in bpy.context.screen.areas:
    if area.type == 'VIEW_3D':
        override = bpy.context.copy()
        override["area"] = area
        with context.temp_override(**override):
            bpy.ops.view3d.background_image_add(name="BG", filepath=r"image.png")
        break

Blender prior to 3.2

import bpy
for area in bpy.context.screen.areas:
    if area.type == 'VIEW_3D':
        override = bpy.context.copy()
        override['area'] = area
        bpy.ops.view3d.background_image_add(override, name="BG", filepath=r"image.png")
        break

By the way:
You can pass an empty dict (like bpy.ops.example.operator({})), which usually prints warnings to the system console about the context members missing. You need to pass these members and repeat until it doesn't complain anymore. But beware of certain operators, which require scene bases - if you don't provide a reference to them, Blender will crash to desktop before you even find out they are needed.

Change Area.type

area = bpy.context.area
old_type = area.type
area.type = 'VIEW_3D'
bpy.ops.view3d.background_image_add(name="BG", filepath=r"image.png")
area.type = old_type

Low-level

Similar to the override, you need a 3D View instance, in this case its space data (because this is the place where backgrounds reside). The following code takes the first 3D View of the current screen layout (if there's one), adds a new background and sets the image to an image datablock:

import bpy

filepath = r"C:\path\to\image.png"

img = bpy.data.images.load(filepath)

for area in bpy.context.screen.areas:
    if area.type == 'VIEW_3D':
        space_data = area.spaces.active
        bg = space_data.background_images.new()
        bg.image = img
        break
$\endgroup$
3
  • 1
    $\begingroup$ What do you mean by "scene bases"? docs.blender.org/api/blender_python_api_master/…? Would you mind providing an example? $\endgroup$
    – JakeD
    Feb 10, 2017 at 22:38
  • $\begingroup$ @JakeD im wondering the same what does he mean by scene bases? $\endgroup$ Aug 1, 2022 at 15:00
  • 2
    $\begingroup$ @HarryMcKenzie Up to v2.79, there was a layer system, and an implementation detail were "SceneBases". A Base "is a wrapper for referencing Objects in a Scene" that references an object, stores a layer number, a selection flag, etc. (see github.com/blender/blender/blob/…). The Python API hid this quite well but some operators required you to provide object_bases in context overrides or Blender would crash. $\endgroup$
    – CodeManX
    Aug 18, 2022 at 18:14
9
$\begingroup$

Here's to add to @CodeManX's answer. If you get a runtime error that looks like this:

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

which is specifically in your case:

RuntimeError: Operator bpy.ops.view3d.background_image_add.poll() failed, context is incorrect

You can use the following script to override context. Make sure to use the correct Area Type which is assigned to area_type in this script and make sure this area is visible or open in your screen. For example, if you call a bpy.ops function that directly manipulates the Dope Sheet, you have to make sure this editor is actually open and set area_type = 'DOPESHEET'.

Since you are calling bpy.ops.view3d.background_image_add() which directly manipulates the 3d Viewport, you have to make sure it is actually open and set area_type = 'VIEW_3D'. It is easy to find out the enum for the area type; just switch the editor type and you will see the type in the Info Editor which is open by default in the lower left corner of the Scripting Tab

enter image description here

Legacy Override (version < 3.2)

Take note that in the legacy context override you have to pass the override dictionary object as first parameter to the bpy.ops function.

import bpy

area_type = 'VIEW_3D' # change this to use the correct Area Type context you want to process in
areas  = [area for area in bpy.context.window.screen.areas if area.type == area_type]

if len(areas) <= 0:
    raise Exception(f"Make sure an Area of type {area_type} is open or visible in your screen!")

override = {
    'window': bpy.context.window,
    'screen': bpy.context.window.screen,
    'area': areas[0],
    'region': [region for region in areas[0].regions if region.type == 'WINDOW'][0],
}

bpy.ops.view3d.background_image_add(override)

Temp Override (version 3.2 and beyond)

Use bpy.context.temp_override for blender 3.2 and beyond.

import bpy

area_type = 'VIEW_3D' # change this to use the correct Area Type context you want to process in
areas  = [area for area in bpy.context.window.screen.areas if area.type == area_type]

if len(areas) <= 0:
    raise Exception(f"Make sure an Area of type {area_type} is open or visible in your screen!")

with bpy.context.temp_override(
    window=bpy.context.window,
    area=areas[0],
    region=[region for region in areas[0].regions if region.type == 'WINDOW'][0],
    screen=bpy.context.window.screen
):

    bpy.ops.view3d.background_image_add()

Override for Any Blender Version

Here's a script I've prepared that I found very useful that you can utilize for any Blender version no matter if the target context area is visible or not. You simply create an instance of this ContextScriptExecuter class and pass the Area.type (area_type i.e. VIEW_3D, IMAGE_EDITOR, NODE_EDITOR, etc...) and an optional Area.ui_type (ui_type for specific NODE_EDITOR area context i.e. ShaderNodeTree, GeometryNodeTree, CompositorNodeTree, etc...) and pass your script that requires context to the script parameter.

import bpy

class ContextExecuterOverride:
    def __init__(self, window, screen, area, region):
        self.window, self.screen, self.area, self.region = window, screen, area, region
        self.legacy = not hasattr(bpy.context, "temp_override")
        if self.legacy:
            self.context = bpy.context.copy()
            self.context['window'] = window
            self.context['screen'] = screen
            self.context['area'] = area
            self.context['region'] = region
        else:
            self.context = bpy.context.temp_override(window=window, screen=screen, area=area, region=region)

    def __enter__(self):
        if not self.legacy:
            self.context.__enter__()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if not self.legacy:
            self.context.__exit__(self, exc_type, exc_value, traceback)
        return self

class ContextScriptExecuter():

    def __init__(self, area_type, ui_type=None, script=None):
        self.area_type = area_type
        self.ui_type = ui_type if ui_type else area_type
        self.script = script

    def script_content(self, override):
        self.script(override)

    def execute_script(self):
        window = bpy.context.window
        screen = window.screen
        areas = [area for area in screen.areas if area.type == self.area_type]
        area = areas[0] if len(areas) else screen.areas[0]
        prev_ui_type = area.ui_type
        area.ui_type = self.ui_type
        regions = [region for region in area.regions if region.type == 'WINDOW']
        region = regions[0] if len(regions) else None
        with ContextExecuterOverride(window=window, screen=screen, area=area, region=region) as override:
            self.script_content(override)
        area.ui_type = prev_ui_type

Here's a sample inline usage of a lambda function passed to the script parameter:

ContextScriptExecuter(
    area_type='VIEW_3D',
    script=lambda override: (
        bpy.ops.view3d.view_axis(override.context, type='TOP')
        if override.legacy
        else bpy.ops.view3d.view_axis(type='TOP')
    )
).execute_script()

And here's another example with a defined class method passed by function name to the script parameter. This script assumes you have an object already actively selected with at least one material.

class MyClass:

    def my_context_script(self, override):
        override.area.spaces.active.node_tree = bpy.context.active_object.active_material.node_tree
        if override.legacy:
            bpy.ops.node.add_node(override.context, use_transform=True, type='ShaderNodeVertexColor')
            node_tree = bpy.context.active_object.active_material.node_tree
            active_node = node_tree.nodes[-1]
        else:
            bpy.ops.node.add_node(use_transform=True, type='ShaderNodeVertexColor')
            active_node = bpy.context.active_node
        active_node.location = (0, 0)

    def exec(self):
        ContextScriptExecuter(
            area_type='NODE_EDITOR',
            ui_type='ShaderNodeTree',
            script=self.my_context_script
        ).execute_script()

MyClass().exec()

You can also create a subclass and override the script_content function and define your content there.

Warning on bpy.ops

Please note that you should always try to avoid bpy.ops functions whenever possible, that is, avoid them when there is an alternative solution because an overuse of these functions can potentially cause performance issues.

$\endgroup$
4
  • $\begingroup$ you can also switch the editor type while running your script using bpy.context.area.ui_type = area_type $\endgroup$ Aug 3, 2022 at 8:55
  • 1
    $\begingroup$ FWIW think use_temp_override() can be reduced to if bpy.app.version >= (3, 2, 0): or if hasattr(bpy.context, "temp_override"): $\endgroup$
    – Gorgious
    Dec 21, 2022 at 8:18
  • 1
    $\begingroup$ @Gorgious hey if hasattr(bpy.context, "temp_override"): is even better as we can get rid of the hard-coded version values. thanks for sharing! $\endgroup$ Dec 21, 2022 at 8:21
  • 1
    $\begingroup$ Yep. There is still a very very small chance that it would fail if someone monkeypatched this as a member of bpy.context or added it via layout.context_pointer_set but I think it's fait to assume no one would do that :) $\endgroup$
    – Gorgious
    Dec 21, 2022 at 8:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .