How to redefine a context mid script?

hello this is a really simple case, im creating a new window, i just want to do some operator in this window but unfortunately the context is still the old one.

bpy.context.area.ui_type = 'VIEW_3D'
bpy.ops.screen.area_dupli('INVOKE_DEFAULT')
bpy.ops.view3d.view_camera() #i want to do this operator in the new window
bpy.context.space_data.lock_camera = True #i want to do this boolean change in the new window

i know that this code below

bpy.context.window_manager.windows[-1].screen.areas[0]

represent the new window, but i donā€™t know how how to use it properly in this situation

anyone have a solution ? i find all the context change and override documentation very week, both in the api doc, blender artist forum, and the stack. :frowning:

Copy the context and rewrite some of its elements. 3d view operators often just need the area object updated in order to work.

import bpy
context = bpy.context

def run():
    bpy.ops.screen.area_dupli('INVOKE_DEFAULT')
    new_window = context.window_manager.windows[-1]

    area = new_window.screen.areas[-1]
    area.type = 'VIEW_3D'


    C_dict = bpy.context.copy()
    C_dict.update(area=area)

    bpy.ops.view3d.view_camera(C_dict)

    sv3d = area.spaces.active
    sv3d.lock_camera = True

if __name__ == '__main__':
    run()

3 Likes

thanks, im sure that this simple context change example will help a lot of people

why adding this simple line wonā€™t work ?

bpy.ops.view3d.properties()

or

bpy.ops.view3d.properties(C_dict)

?

not logic, why bpy.ops.view3d.view_camera(C_dict) work but not another operator ?

Because bpy.ops.view3d.properties() has been deprecated.

Use space_data.show_region_ui = True for the N-panel.

import bpy

bpy.ops.screen.area_dupli('INVOKE_DEFAULT')
new_window = bpy.context.window_manager.windows[-1]


area = new_window.screen.areas[-1]
space_data = bpy.context.space_data ####context define

area.type = 'VIEW_3D'

C_dict = bpy.context.copy()
C_dict.update(area=area)
C_dict.update(space_data=space_data) #### context update?

bpy.ops.view3d.view_camera(C_dict)

sv3d = area.spaces.active
sv3d.lock_camera = True

bpy.ops.view3d.view_center_camera(C_dict)

space_data.show_region_ui = True ### context is updated ? 

i really donā€™t get it :sweat_smile:

Ps:
Are you avaible as a python teacher/coatch ? i want to improve my scripting abilities

space_data just means the contextual space type for a given editor. For 3d views itā€™s called SpaceView3D. In your case, itā€™s shortened to sv3d. :stuck_out_tongue:

sv3d.show_region_ui = True

No, Iā€™m only available here sometimes, and when itā€™s full moon!

1 Like

Okay Okay

sv3d.show_region_ui = True work fine
thanks master

1 Like

Update the C_dict so it contains the correct region from the new window.

        C_dict.update(region=area.regions[-1])

Example:

import bpy

class SimpleOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.screen.area_dupli('INVOKE_DEFAULT')
        new_window = bpy.context.window_manager.windows[-1]

        area = new_window.screen.areas[-1]
        space_data = area.spaces.active

        area.type = 'VIEW_3D'

        C_dict = bpy.context.copy()
        C_dict.update(area=area)
        C_dict.update(region=area.regions[-1])

        bpy.ops.view3d.view_camera(C_dict)

        sv3d = area.spaces.active
        sv3d.lock_camera = True

        bpy.ops.view3d.view_center_camera(C_dict)

        space_data.show_region_ui = True ### context is updated ? 
        return {'FINISHED'}


if __name__ == "__main__":
    bpy.utils.register_class(SimpleOperator)
1 Like

is there a direct way to update every context potentially needed or we need to specify each time in the C_dict.update function ?

It depends on the area type and the operator used. Some operators require extensive context members, some require only one.

You could write a function that generates a context dict based on a window and its area type. This one is specifically made for 3d view, but should mostly work in other area types.

import bpy

def gen_C_dict(context, win, area_type='VIEW_3D'):

    C_dict = context.copy()

    for area in win.screen.areas:
        if area.type == area_type:
            for region in area.regions:
                if region.type == 'WINDOW':
                    break
            for space in area.spaces:
                if space.type == area_type:
                    region_data = None
                    if area_type == 'VIEW_3D':
                        region_data = space.region_3d
                    break
            break
    
    C_dict.update(
        area=area,
        region=region,
        region_data=region_data,
        screen=win.screen,
        space_data=space)

    return C_dict

if __name__ == '__main__':
    # gen_C_dict arguments
    context = bpy.context
    win = context.window  # just an example. use new window instead

    C_dict = gen_C_dict(context, win, area_type='VIEW_3D')
    bpy.ops.view3d.view_camera(C_dict)

Usage:

  1. Create your new window
  2. Set the desired area of the new window
  3. Generate an override by passing context, window and area type to gen_C_dict
  4. Use the new C_dict in the operator.
1 Like

do you think its possible to do some operations when we close the window ?

There are no callbacks for window management so you would have to implement a monitor. You can use a pass-through modal operator or a timer that checks context.window_manager.windows at an interval, but itā€™s a hacky approach.

This example runs a timer every 100 ms and checks windows by their hash() value using a set(). When new windows are created, the new set is compared against the old. It has negligible impact on performance if you donā€™t go much lower than 0.1, but Iā€™ve never used something like this in the past myself.

Usage: Run in text editor, open / close windows and watch the console for output.

import bpy
context = bpy.context
wm = context.window_manager

def hash_windows():
    return {hash(w) for w in wm.windows}

def winmon():
    new_hash = hash_windows()
    diff = windows.difference(new_hash)

    if diff:
        for hsh in diff:
            print(f"window with hash {hsh} closed")
    else:
        sym = windows.symmetric_difference(new_hash)
        if sym:
            for hsh in sym:
                print(f"window with hash {hsh} opened")

    windows.clear()
    windows.update(hash_windows())

    return 0.1

if __name__ == '__main__':
    windows = hash_windows()
    bpy.app.timers.register(winmon)

thanks for your answer, im quite afraid of running a script every 100ms,it look unstable isnā€™t it ?

speaking of our context thread, i still have some misundestanding, why does some operator are still noe affected by this double change of context ? some operator like

bpy.ops.view3d.view_axis(C_dict,type='TOP')
bpy.ops.view3d.walk(C_dict)
bpy.ops.view3d.view_all(C_dict, center=False)

still wont work, and i donā€™t understand why, it has been ā€œdeprecatedā€ for sure, but thereā€™s no way to call them then ?

Not really. Itā€™s common to leave modal operators running in the background depending on the amount and type of add-ons you run. An operator polling every 100ms isnā€™t bad, but it depends on the complexity of the code it runs. You pretty much get python sets and area looping for free.

Updated example that supports the operators you mention.

import bpy
context = bpy.context

# supply window and area type
def gen_C_dict(context, window, area_type='VIEW_3D'):
    C_dict = {}

    for area in window.screen.areas:
        if area.type == area_type:
            for region in area.regions:
                if region.type == 'WINDOW':
                    print("found region")
                    break
            for space in area.spaces:
                if space.type == area_type:
                    region_data = None
                    if area_type == 'VIEW_3D':
                        region_data = space.region_3d
                    break
            break

    C_dict.update(
        window=window,
        area=area,
        region=region,
        region_data=region_data,
        screen=window.screen,
        space_data=space)

    return C_dict

# spawn your window
bpy.ops.screen.area_dupli('INVOKE_DEFAULT')
new_window = context.window_manager.windows[-1]
new_window.screen.areas[-1].type = 'VIEW_3D'

# generate cdict based on new window
C_dict = gen_C_dict(context, new_window, area_type='VIEW_3D')

if __name__ == '__main__':


    # toggle between these:
    bpy.ops.view3d.view_camera(C_dict, 'INVOKE_DEFAULT')
#    bpy.ops.view3d.walk(C_dict, 'INVOKE_DEFAULT')
#    bpy.ops.view3d.view_axis(C_dict, 'INVOKE_DEFAULT', type='TOP')
1 Like

how did you knew which context to redefine from just an operator code ? i didnā€™t find anything about what you just did on the API did i miss something ? and why do suddenly ā€œā€˜INVOKE_DEFAULTā€™ā€ is inside the code ? :sweat_smile:

isnā€™t that problematic when blender is freezing or rendering ?

Just an educated guess. You can also take a peek at the C definition of an operator by searching for its rna identifier in the blender repository. There you can see what context arguments it requires.

3d view operators need ā€˜INVOKE_DEFAULTā€™ to be able to perform camera transitioning. ā€˜INVOKE_DEFAULTā€™ is implied by default in the keymap, however when an operator is called outside a keymap (eg. inside a script using bpy.ops we need to explicitly pass the argument)

Modal handlers are persistent until they are explicitly removed by the operator itself or blender exit. Any script execution, even the user interface may be blocked during rendering. Usually they just resume afterwards.