7
$\begingroup$

I have an operator, “my_operator”, that is called from a panel, as follows:

class CATEGORY_PT_test_panel(bpy.types.Panel):
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_label = "Test"
bl_context = "objectmode"
bl_category = "Test"

def draw(self, context):
    layout = self.layout
    scene = context.scene

    row = layout.row()
    row.operator('object.my_operator', text='Activate')

This works fine, but I need to override the context when calling the operator; I need it to run in the context of the Image Editor. The only way I have found that works is to create a dummy operator (that is called from the panel) and in the dummy call “my_operator” as follows:

class OBJECT_OT_dummy(bpy.types.Operator):
"""Dummy operator workaround used for override."""     

bl_idname = "object.dummy"     
bl_label = "Dummy"         
bl_options = {'REGISTER'}

def execute(self, context):
    screen = context.screen
    override = bpy.context.copy()

    # Update the context 
    for area in screen.areas:
        if area.type == 'IMAGE_EDITOR':
            for region in area.regions:
                if region.type == 'WINDOW':
                    override = {'region': region, 'area': area}

    bpy.ops.object.my_operator(override, 'INVOKE_DEFAULT')
    return {'FINISHED'}

Is there some better way to do this…? “my_operator” is modal for doing some stuff when the user clicks on the image in the Image Editor. I’m using Blender 2.8.

$\endgroup$
1

1 Answer 1

3
$\begingroup$

Context overrides are sometimes necessary depending on the functionality of your operator but should be avoided if possible, in my opinion. I can think of these options to avoid context overrides:

  1. If your operator performs a very complicated task spanning over multiple context areas inside Blender, think about splitting the operators functionality into multiple operators that require only one context
  2. If you already know that your operator is performing a certain task that requires a specific context, make sure to call the operator from within that context. In your case, it sounds like you are performing Image related tasks. Calling the operator from the Image Editor Window itself, can solve your issue. This can be done by adding your operator to existing menus or a custom panel of the Image Editors sidebar
import bpy

from bpy.types import Operator, Panel


class A_OP_ContextualOperator(bpy.types.Operator):
    bl_idname = "image.contextual_operator"
    bl_label = "Contextual Operator"
    bl_description = "Operator called with appropriate context"
    bl_options = {'REGISTER', 'UNDO'}

    # check for appropriate context in case this operator is searched using F3-Search
    @classmethod
    def poll(cls, context):
        return context.space_data.type == 'IMAGE_EDITOR'

    def execute(self, context):
        print(f"Calling contextual operator - Current Context is: {context.space_data.type}")
        return {'FINISHED'}

class A_PT_CustomImageEditor_Panel(Panel):
    # custom panel added to the Image Editor Sidebar
    bl_space_type = 'IMAGE_EDITOR'
    bl_region_type = "UI"
    bl_label = "Custom Panel"
    bl_category = "Custom Tab"
    bl_options = {"DEFAULT_CLOSED"}

    def draw(self, context):
        layout = self.layout
        layout.operator("image.contextual_operator")

def custom_menu_entries(self, context) -> None:
    # custom draw method to add entries to existing menus
    self.layout.separator()
    self.layout.operator("image.contextual_operator")

classes = (A_OP_ContextualOperator, A_PT_CustomImageEditor_Panel)

def register():
    for c in classes:
        bpy.utils.register_class(c)

    # add operator to Image Menu in Image Editor
    bpy.types.IMAGE_MT_image.append(custom_menu_entries)
    # add operator to View Menu in Image Editor
    bpy.types.IMAGE_MT_view.append(custom_menu_entries)

if __name__ == "__main__":
    register()

If you still need context overrides however, as of Blender 3.2 there is an easier way to do this: context.temp_override()

An operator with an "Image Editor" context override called from the 3D-View sidebar could look like this:

import bpy

from bpy.types import Operator, Panel

class A_OP_ContextOverrideOperator(Operator):
    bl_idname = "image.contextoverride_operator"
    bl_label = "Context Override Operator"
    bl_description = "Operator called with context override"
    bl_options = {'REGISTER', 'UNDO'}

    # verify appropriate context is available in current workspace and save reference in context_area variable
    context_area: bpy.types.Area = None
    @classmethod
    def poll(cls, context):
        cls.context_area = None
        for area in context.screen.areas:
            if area.type == 'IMAGE_EDITOR':
                cls.context_area = area
                break

        if not cls.context_area:
            cls.poll_message_set("Need Image Editor window in workspace")
            return False

        return cls.context_area.type == 'IMAGE_EDITOR'

    def execute(self, context):
        print("Calling context override operator...")
        print(f"\tOperator called with context: {context.space_data.type}")
        with context.temp_override(area=self.context_area):
            # put Image Editor related code  here
            print(f"\tContext after override: {context.space_data.type}")

        return {'FINISHED'}

class A_PT_CustomView3D_Panel(Panel):
    # custom panel added to the 3D View Sidebar
    bl_space_type = 'VIEW_3D'
    bl_region_type = "UI"
    bl_label = "Custom Panel"
    bl_category = "Custom Tab"
    bl_options = {"DEFAULT_CLOSED"}

    def draw(self, context):
        layout = self.layout
        layout.operator("image.contextoverride_operator")

classes = (A_OP_ContextOverrideOperator, A_PT_CustomView3D_Panel)

def register():
    for c in classes:
        bpy.utils.register_class(c)

if __name__ == "__main__":
    register()

Please note that the operator is coded to only work, as long as an Image Editor Window is open in the workspace. Depending on your operators or other Blender operators functionality, this may not be necessary.

$\endgroup$

You must log in to answer this question.

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