How to use multiple timers and multiple callbacks simultaneously?

Hello. I’m writing a 3D image block viewer in Python with pyvtk.
I’m adding some short animations triggered by keyboard.

And there, I encounter a situation that multiple repeating timers could fire simultaneously, and the corresponding callbacks can not distinguish between the timers. i.e. the callbacks will be called despite of the timer ID.

To be more precise, suppose we have two callbacks (cb1, cb2) and added them as observors:

    cb1 = vtkTimerCallback(2, 'Timer1')
    renderWindowInteractor.AddObserver('TimerEvent', cb1.execute)
    cb1.timerId = renderWindowInteractor.CreateRepeatingTimer(250)

    cb2 = vtkTimerCallback(3, 'Timer2')
    renderWindowInteractor.AddObserver('TimerEvent', cb2.execute)
    cb2.timerId = renderWindowInteractor.CreateRepeatingTimer(350)

Then when either of the timers is ringing, both cb1.execute and cb2.execute will be called, which is not desired.
The function I want to achieve is each RepeatingTimer calls its callback only.

I have tried .GetTimerEventId() (so that we could compared with self.timerId), but it shows always 0.

Any idea?

Demo for convenience:

#!/usr/bin/env python

import time

# noinspection PyUnresolvedReferences
import vtkmodules.vtkInteractionStyle
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkFiltersSources import vtkSphereSource
from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkPolyDataMapper,
    vtkRenderWindow,
    vtkRenderWindowInteractor,
    vtkRenderer
)

class vtkTimerCallback():
    def __init__(self, steps, name):
        self.steps = steps
        self.name  = name
        self.timerId = None
        self.t0 = time.time()

    def execute(self, obj, event):
        self.steps -= 1
        print(event, ' ', self.name, ' Step:', \
              self.steps, '  t = %.3f'%(time.time() - self.t0))
        #print(obj)
        obj.GetRenderWindow().Render()  # call this to avoid segfault
        if (self.steps <= 0) and self.timerId:
            obj.DestroyTimer(self.timerId)
            print(self.name, 'Destroyed. id =', self.timerId)
            self.timerId = None

if __name__ == '__main__':
    sphereSource = vtkSphereSource()
    mapper = vtkPolyDataMapper()
    mapper.SetInputConnection(sphereSource.GetOutputPort())
    actor = vtkActor()
    actor.SetMapper(mapper)

    # Setup a renderer, render window, and interactor
    renderer = vtkRenderer()
    renderWindow = vtkRenderWindow()
    renderWindow.AddRenderer(renderer)

    renderWindowInteractor = vtkRenderWindowInteractor()
    renderWindowInteractor.SetRenderWindow(renderWindow)

    renderer.AddActor(actor)

    renderWindow.Render()
    renderer.GetActiveCamera().Zoom(0.8)
    renderWindow.Render()

    # Initialize must be called prior to creating timer events.
    renderWindowInteractor.Initialize()

    # Sign up to receive TimerEvent
    cb1 = vtkTimerCallback(3, 'Timer1')
    renderWindowInteractor.AddObserver('TimerEvent', cb1.execute)
    cb1.timerId = renderWindowInteractor.CreateRepeatingTimer(250)

    cb2 = vtkTimerCallback(4, 'Timer2')
    renderWindowInteractor.AddObserver('TimerEvent', cb2.execute)
    cb2.timerId = renderWindowInteractor.CreateRepeatingTimer(350)

    # start the interaction and timer
    renderWindow.Render()
    renderWindowInteractor.Start()

I’m running into the same issue right now trying to implement something like this for PyVista. We can create repeating timers and add an observer to the render window interactor, but when the timer is called, the ID of the timer triggering the TimerEvent is simply not available. All the methods to get the timer ID appear to work with CreateTimerEvent, and not with the TimerEvent.

The usual pattern for receiving data with a VTK event in Python looks like this.
The decorator is needed because, in C++, VTK events use void* to pass extra data.

from vtkmodules.vtkCommonCore import VTK_INT
from vtkmodules.util.misc import calldata_type

class callback:
    @calldata_type(VTK_INT)
    def execute(self, obj, event, timerId):
        print(timerId)

Have you tried this, and if so, is it working for TimerEvents?

1 Like

Thanks Gobbi, indeed that is the solution for TimerEvents, I’ve learned that after almost a year.

For later comer, here is the related doc:
Python Wrappers - Observer Callbacks - Call Data

To 100% confirm the existence and type of payload for observer callbacks, one might need the source code :smiling_face_with_tear:, search the event vtkCommand::TimerEvent, such as shown here vtkInteractorStyle:

Note these lines:

int timerId = (calldata ? *(reinterpret_cast<int*>(calldata)) : 1);
if (self->HandleObservers && self->HasObserver(vtkCommand::TimerEvent))
{
  self->InvokeEvent(vtkCommand::TimerEvent, &timerId);
}

The InvokeEvent tells us how the observer is been called – here with extra payload timerId of type int.