pythonblender

How to properly register and unregister 'bpy.app.timers.' function in blender


bpy.app.timers.register(my_func) is not properly registered

we get error while trying bpy.app.timers.unregister(my_func)

    bpy.app.timers.unregister(clock)
ValueError: Error: function is not registered

i want to run timer to update view3d

ba_props.py

class BastromProps(PropertyGroup):
    ...
    clock_interval: IntProperty(
        name="",
        description="update clock every x seconds",
        default=1,
    )

def register():
    bpy.utils.register_class(BastromProps)
    bpy.types.Scene.bapp = PointerProperty(type=BastromProps)
    ...

ba_ui.py

class EVENT_PT_two(Panel):
    bl_idname = "EVENT_PT_one"
    ...

    def draw(self, context):
        layout = self.layout
        bapp = context.scene.bapp

        if bapp.run_clock_1_:
            clock_icon = "PREVIEW_RANGE"
        else:
            clock_icon = "RECOVER_LAST"

        split = layout.split(factor=0.6, align=True)
        ...
        split.operator("event.two_clock", text="", icon=clock_icon)

ba_ops.py

class EVENT_OT_one_clock(Operator):
    # event one 'run clock' button was pressed

    def execute(self, context):
        bapp = bpy.data.scenes["zodiac"].bapp

        # update chart every x seconds
        def update_interval():
            # get local / computer utc timezone-aware datetime
            local_utc_now = datetime.utcnow().replace(tzinfo=zi("UTC"))
            ...
            return bapp.clock_interval

        # toggle 'run clock' mode
        if bapp.run_clock_1_:
            bpy.app.timers.register(update_interval)
            bapp.run_clock_1_ = False
        else:
            bpy.app.timers.unregister(update_interval)
            bapp.run_clock_1_ = True

        if bpy.app.timers.is_registered(update_interval):
            print(
                f"event.one_clock: timers.is_registered: {bpy.app.timers.is_registered}"
            )
            ...

           return {"FINISHED"}

above code gives error:

    bpy.app.timers.unregister(update_interval)
ValueError: Error: function is not registered

i tried few results from web, ie:

moved update_interval(self) outside of execute(...) function, then:

clock = self.update_interval()

if bapp.run_clock_1_:
    bpy.app.timers.register(clock)
    bapp.run_clock_1_ = False
else:
    bpy.app.timers.unregister(clock)
    bapp.run_clock_1_ = True

still giving error

how can we fix the code to run properly ?

(adding some more details otherwise i can not post the question)


Solution

  • Yes you're right that you have to move the update_interval function outside of execute.

    If update_interval is defined inside execute, it would create a new instance of update_interval each time the operator is run. So when you try to call bpy.app.timers.unregister(update_interval), it will always fail since it's a different instance.


    Then if you want to use the clock_interval property inside of the update_interval function, you can either:

    1. Get it from the scene in the function
    def update_interval():
        interval = bpy.context.scene.bapp.clock_interval
        <...update logic...>
        return interval
    
    1. Pass it into the function from the operator
    import functools
    
    update_func = None
    
    def update_interval(interval):
        <...update logic...>
        return interval
    
    
    class EVENT_OT_one_clock(Operator):
        def execute(self, context):
            bapp = bpy.context.scene.bapp
    
            global update_func
    
            if bapp.run_clock_1_:
                # store function in a variable so it can be unregistered later
                update_func = functools.partial(update_interval, bapp.clock_interval)
                bpy.app.timers.register(update_func)
    
            <...other operator code...>
    

    Or if you want the update_interval function to be part of the class like in your example:

    clock = self.update_interval()

    You'll need to use functools.partial so it registers properly:

    self.update_func = functools.partial(self.update_interval)
    bpy.app.timers.register(self.update_func)
    

    You should also make sure to always check if the timer is registered before trying to unregister it so you can handle errors:

    if bpy.app.timers.is_registered(update_func):
        bpy.app.timers.unregister(update_func)
    else:
        print("error")
    

    Also, to change a scene property, you'll need to use the original object and not the copy. In your code, run_clock_1_ will always be false.

    # this won't work
    bapp = bpy.context.scene.bapp
    bapp.run_clock_1_ = False
    
    # this works
    bpy.context.scene.bapp.run_clock_1_ = False