Page MenuHome

Python: Support for calling operators in timers.
Needs RevisionPublic

Authored by Jacques Lucke (JacquesLucke) on Thu, May 21, 10:11 PM.

Details

Summary

This patch makes it possible to call operators from within timers reliably.

The implemented solution is a bit more generic than that, but that's the main purpose for me.
This might not seem like a big deal, but it is the main limitation that stops me from building more
sophisticated tools in my vs code extension for Blender addon development.

I know that other developers ran into the same limitation as well. I'm not aware of any other reliable
solution to the problem.

This patch adds a new api method called Region.exec_operator(operator_name) -> OperatorProperties.
It does not call the operator immediately, but schedules it. Then, Blender will execute it in its main
event loop later on. The operator is executed within the region the exec_operator method
was called on.

You can test the basic functionality of the patch by executing the following in the console:
C.region.exec_operator("mesh.primitive_plane_add").location = (1, 2, 3).
This will create a new plane.

When used from within a script, parameters can be passed to the operator as follows.
This matches the convention we use when creating buttons in the ui.

props = region.exec_operator(operator_name)
props.prop1 = ...
props.prop2 = ...

This example will toggle full screen for the 3d view and then open a new file.
This is just to showcase how it generally works, not really a practical use case.

import bpy

def callback():
    for area in bpy.data.window_managers[0].windows[0].screen.areas:
        if area.type == "VIEW_3D":
            for region in area.regions:
                if region.type == "WINDOW":
                    region.exec_operator("screen.screen_full_area")
                    
def load_other_file():
    region = bpy.data.window_managers[0].windows[0].screen.areas[0].regions[0]
    region.exec_operator("wm.read_homefile")
                    
bpy.app.timers.register(callback, first_interval=1)
bpy.app.timers.register(callback, first_interval=2)
bpy.app.timers.register(load_other_file, first_interval=3)

Here is another example that adds spheres over time. It also seems to work nice with undo.
Every added sphere adds an undo step.

import bpy
    
counter = 0
def add_object():
    global counter
    counter += 1
    region = bpy.data.window_managers[0].windows[0].screen.areas[0].regions[0]
    props = region.exec_operator("mesh.primitive_uv_sphere_add")
    props.location = (counter, 0, 0)
    
bpy.app.timers.register(add_object, first_interval=1)
bpy.app.timers.register(add_object, first_interval=2)
bpy.app.timers.register(add_object, first_interval=3)

I know that the bpy.data.window_managers[0].windows[0].screen.areas[0].regions[0] thing is ugly.
However, I think for now it is good to force the developer to pick a region, even if it is not strictly
necessary in all operators.

I also tested this patch with my vs code extension. It allowed me to execute Python scripts
written in vs code directly in Blender in the right context, without having to "touch" the
Blender window.

Diff Detail

Repository
rB Blender
Branch
scheduled_operators (branched from master)
Build Status
Buildable 8156
Build 8156: arc lint + arc unit

Event Timeline

Jacques Lucke (JacquesLucke) requested review of this revision.Thu, May 21, 10:11 PM
Jacques Lucke (JacquesLucke) created this revision.
Jacques Lucke (JacquesLucke) edited the summary of this revision. (Show Details)
Jacques Lucke (JacquesLucke) retitled this revision from Schedule Operator Calls to Python: Support for calling operators in timers..Thu, May 21, 10:55 PM
Jacques Lucke (JacquesLucke) edited the summary of this revision. (Show Details)
Jacques Lucke (JacquesLucke) edited the summary of this revision. (Show Details)
Jacques Lucke (JacquesLucke) edited the summary of this revision. (Show Details)
Brecht Van Lommel (brecht) requested changes to this revision.Fri, May 22, 2:42 PM

Can you explain why this is necessary? What makes it impossible to execute operators now in timers?

Delayed execution of operators means you can't easily do more complex operations where you might call operators but also change properties or call API functions in a particular order. In general this can also lead to subtle failures if whatever the data the operator works on was changed or deleted by the time the operator is actually executed.

We also already have a mechanism for providing window/area/region context to an operator. Maybe we need a more convenient or discoverable mechanism for this. But it's not obvious from the name at all that region.exec_operator will do a delayed execution.

This revision now requires changes to proceed.Fri, May 22, 2:42 PM