Search code examples
pythonblenderbpy

Execute script after Blender is fully loaded


How do I automatically execute a python script after Blender has fully loaded?

Context

My script generates a scene based on a seed. I want to creat a couple thousand images but since Blender leaks memory after a hundred generations or so everything becomes significantly slower and eventually crashes. I want to migitate the problem by creating only x images per session and completely restart Blender after each session.

Problem

If I load the blend file manually and click the play button in the script editor, everything works as expected. When I try to call the script after startup, it crashes in add_curve_spirals.pyline 184, since context.space_data is None.

Since manually starting the script works fine, the problem is, that Blender is in some sort of wrong state. Starting it with or without GUI (--background) does not affect this.

Failed solutions

  • blender myfile.blend --python myscript.py executes the script before the context is fully ready and thus produces the error.
  • Using a handler to delay execution (bpy.app.handlers.load_post) calls my script after completely loading the file but still the context is not ready and it produces the error.
  • Setting the script in Blender to auto execute on startup (Text/Register) also produces the error.
  • Using sockets, as suggested here, to send command to Blender at a later time. The server script, that is waiting for incomming commands, blocks Blender during startup and prevents it from fully loading, hence the effect is the same as executing the script directly.
  • Using timed events (bpy.app.timers.register(render_fun, first_interval=10).

These are all the ways that I found to automatically execute a script. In every case the script seems to be executed too early / in the wrong state and all fail in the same way.

I want to stress that the script is not the issue here. Even if I could work around the particular line, many similar problems might follow and I don't want to rewrite my whole script. So what is the best way to automatically invoke it in the right state?


Solution

  • It turns out, that the problem was the execution context. This became clear after invoking the timed event manually, e.g. after the scene was loaded completely the timed event was still executed in a wrong context.

    Since the crash happened in the add_curve_spirals addon, the solution was to provide a context override the the operator invokation. The rest of my script was not equally sensitive to the context and worked just fine.

    It was not clear to me, how exactly I should override the context, but this works for now (collected from other parts of the internet, so I don't understand all details):

    def get_context():
        # create a context that works when blender is executed from the command line.
        idx = bpy.context.window_manager.windows[:].index(bpy.context.window)
        window = bpy.context.window_manager.windows[idx]
        screen = window.screen
        views_3d = sorted(
                [a for a in screen.areas if a.type == 'VIEW_3D'],
                key=lambda a: (a.width * a.height))
        
        a = views_3d[0]
        # override
        o = {"window" : window,
             "screen" : screen,
             "area" : a,
             "space_data": a.spaces.active,
             "region" : a.regions[-1]
        }
        return o
    

    Final invocation: bpy.ops.curve.spirals(get_context(), spiral_type='ARCH', radius = radius, turns = turns, dif_z = dif_z, ...