[SOLVED] Is it possible to update bpy.context.scene after creating a new scene?

I’m using latest 2.91 build. I create a new scene with new_scene = bpy.data.scenes["New Scene"].copy(). After this I want to set context.scene to the new scene. I have tried bpy.context.window.scene = new_scene but it didn’t affect bpy.context.scene variable (and since it is read-only, I cannot set it directly).

I also tried bpy.ops.scene.new(type="LINK_COPY") and it works but only in Python Console or if I put it in startup code of my addon. It then sets bpy.context.scene to the new scene. But if I call it after the user clicks a button, it sets only bpy.context.window.scene but bpy.context.scene still refers to the old scene. Very strange.

To summurize, this is what works as expected (but I cannot use this since I do not want to create new scene on addon initialization):

import bpy
bpy.ops.scene.new(type="LINK_COPY") # Assuming current scene is "Scene", this creates "Scene.001"
print(bpy.context.window.scene.name) # Output: Scene.001
print(bpy.context.scene.name) # Output: Scene.001

The above code still works as expected in execute() function of operator_simple.py template, but this is expected since it is calling bpy.ops.object.simple_operator() right after registration (calling bpy.ops.object.simple_operator() with bpy.ops.scene.new(type="LINK_COPY") inside it from Python Console also works as expected).

This what does not work as expected:

new_scene = bpy.data.scenes[scene_original.name].copy()
bpy.context.window.scene = new_scene
print(bpy.context.window.scene.name) # Output: Scene.001
print(bpy.context.scene.name) # Output: Scene (still refers to the old scene)

And bpy.ops.scene.new() does not work as expected either if called after the user clicks a button. Here is minimalistic example to reproduce the issue based on ui_panel_simple.py template:

import bpy
class SCENE_OT_create_linked_copy(bpy.types.Operator):
    bl_idname = "scene.create_linked_copy"
    bl_label = "Create linked copy"
    bl_description = "Create linked copy of current scene."
    
    def execute(self, context):
        bpy.ops.scene.new(type="LINK_COPY") # Assuming current scene is "Scene", this creates "Scene.001"
        print(context.window.scene.name) # Output: Scene.001
        print(context.scene.name) # Output: Scene (still refers to the old scene)
        return {"FINISHED"}

class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Scene properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "SCENE_PT_hello"
    bl_space_type = "PROPERTIES"
    bl_region_type = "WINDOW"
    bl_context = "scene"

    def draw(self, context):
        layout = self.layout
        row = layout.row()
        row.operator("scene.create_linked_copy")

def register():
    bpy.utils.register_class(SCENE_OT_create_linked_copy)
    bpy.utils.register_class(HelloWorldPanel)
def unregister():
    bpy.utils.unregister_class(SCENE_OT_create_linked_copy)
    bpy.utils.unregister_class(HelloWorldPanel)
if __name__ == "__main__":
    register()

Technically updating bpy.context.scene is possible but it is not clear how to do this in general case. This is may have something to do with the fact that I’m trying to use bpy.ops.scene.new() and its behavior changes somehow depending on from what part of the GUI it was called. But is there a way to do this without using ops? Or trying to avoid using context is the only way?

I found no other solution except to try to avoid some context properties which depend on context.scene. In case somebody encounters the same issue, I briefly explain below how I solved this.

For example, if I want to access context.scene.collection (or any other scene property), I have two options:

Use context.window.scene.collection instead. This is the most straightforward solution assuming context.window.scene was previously set to the correct scene.

Or use bpy.data.scenes[new_scene.name].collection. This may work even better if I have to manage multiple scenes, otherwise the first solution is more readable.

But it is not just context.scene but many other properties of the context may be wrong after creating and switching to the new scene programatically (at least this is true when the code triggered by pressing a button).

For example, after creating new scene I cannot use context.layer_collection anymore since it seems to refer to the old scene. But fortunately context.window.view_layer.layer_collection works as expected. To add a new view layer I have to use new_view_layer = context.window.scene.view_layers.new() and then I can set context.window.view_layer = new_view_layer if necessary to make it active. And so on.

In some cases I had to specify the scene explicitly, for example after creating CompositorNodeRLayers node it uses the old scene by default so I have to set it to the new scene.

Another example is bpy.ops.render.render() and other bpy.ops functions which by default use context.scene. To workaround that it is necessary to override the scene value:
bpy.ops.render.render({"scene": context.window.scene}, "INVOKE_DEFAULT", use_viewport=True)

After these and many other similar changes I was able to finally convert my script to a working add-on.

1 Like