How to frame an object after loading a scene via a script (context error)?

Hi

I am trying to load a scene with Python then I want to zoom into a certain object. Loading and all that works, but expectedly I am getting a context error when I fire bpy.ops.view3d.view_all(center=False) right after loading the file.

I tried couple ways to override the context but it does not work. Does anyone know of a working way of overriding the context in such situation? Maybe there is another way to zoom to an object after loading a scene via a script?


Traceback (most recent call last):
  __init__.py", line 299, in update_previews
    #                     bpy.ops.view3d.view_all(center=False)
  File "C:\blender28\2.80\scripts\modules\bpy\ops.py", line 200, in __call__
    ret = op_call(self.idname_py(), None, kw)
RuntimeError: Operator bpy.ops.view3d.view_all.poll() expected a view3d region

Try this:

areas  = [area for area in bpy.context.screen.areas if area.type == 'VIEW_3D']

if areas:
    regions = [region for region in areas[0].regions if region.type == 'WINDOW']

    if regions:
        override = {'area': areas[0],
                    'region': regions[0]}        

        bpy.ops.view3d.view_all(override, center=False)

From where are you executing your operator?

1 Like

Thanks I will try it

The addon is run in View3d, but once the addon is triggered, it loads another scene (which replaces the current scene)

This is the current order of things which throws the context error. This setup has been giving me nothing but context errors, I had to cut down on some features due to this issue of not figuring or setting the right context.

bpy.ops.wm.open_mainfile(filepath=str(p))
bpy.ops.view3d.view_all(center=False)

@MACHIN3

I tried your recommendation and it still does not work :frowning:

Read blend: C:\Users\USER\AppData\Local\Temp\Mesh.blend
Traceback (most recent call last):
  File "C:\Users\USER\AppData\Roaming\Blender Foundation\Blender\2.80\scripts\addons\ADDON\__init__.py", line 293, in update_previews
    areas  = [area for area in bpy.context.screen.areas if area.type == 'VIEW_3D']
AttributeError: 'NoneType' object has no attribute 'areas'
File "C:\Users\USER\AppData\Roaming\Blender Foundation\Blender\2.80\scripts\addons\ADDON\__init__.py", line 241, in update_previews

Context might be restricted or just not available at the time it’s executing. In those cases better start from bpy.data.screens (or worst case bpy.data.window_managers), or simply wait for the context to be available with a recursive Timer object.

FWIW, you are not loading another scene. You are loading a new blend file. There’s a difference.

You can’t run a script continuously while loading different blend files. With the new blend file, execution of the script run in the previous file stops. The context will be gone, since the previously loaded file is gone. That’s my understanding at least. I have never tried what @kaio describes.

Maybe decscribe in more detail what you are trying to do? There may be a different way.

@MACHIN3

You are right, it is a totally new scene. That is what I meant but I did not word it properly. Do you think that it could be timing issue regarding loading the scene and running a command right after it? Kaio’s recommendation makes me think that the scene load maybe is not finalized by the time view_all is fired?

@kaio

Your recommendation is intriguing. Do you have any basic implementation of how that would look like in a simple operator that does load a scene? I get the timer idea but you recommend a recursive approach, which I am not able to grasp how I can do design it as an operator.

thanks

If you’re loading a blend-file it’s probably best to add a callback to load_post in bpy.app.handlers since it’s made specifically for waiting until things are ready. Handlers work like lists which you can append a callback function to and it’ll call the function.

For a brute force method just for example’s sake:

import bpy
from threading import Timer
context = bpy.context

def recursive(count=0):
    if context is not None:
        if hasattr(context, "screen"):
            if hasattr(context.screen, "areas"):
                for area in context.screen.areas:
                    if area.type == 'VIEW_3D':
                        break
                for region in area.regions:
                    if region.type == 'WINDOW':
                        break
                view_all_fn(area, region)
                return

    if count < 20:
        count +=1
        Timer(0.1, recursive, (count,)).start()

def view_all_fn(area, region):
    override = {'area': area,'region': region}
    bpy.ops.view3d.view_all(override, 'INVOKE_DEFAULT')
    return

if __name__ == '__main__':
    recursive()

Timers run in separate threads so they can’t return anything useful, which also makes the resulting code rather inelegant, but sometimes the end justifies the means. In this example I’ve set it to only run 20 times. If it needs more than that then it probably isn’t a good solution.

1 Like