How to get a Python version of the User Guide vtkAnimationScene example running?

I’ve converted the C++ animation example from the User guide to Python but I can’t quite get it to run correctly. When the code hits scene.Play(), the render window displays the test model (I can’t interact with it), the CPU pegs to 100% on one core, and the TickInternal method on the AnimationCue never gets called.

Can anyone advise what’s going on here? (Python 3.10.4, VTK 9.2)

# noinspection PyUnresolvedReferences
import vtkmodules.vtkInteractionStyle
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkRenderingCore import vtkRenderer, vtkRenderWindow    
from vtkmodules.vtkCommonDataModel import vtkAnimationScene
from vtkmodules.vtkFiltersSources import vtkSphereSource
from vtkmodules.vtkRenderingCore import vtkPolyDataMapper, vtkActor, vtkRenderWindowInteractor
from vtkmodules.vtkCommonCore import vtkAnimationCue

find_module=False
if find_module is True:
    from vtkmodules.all import *
    for c in [vtkRenderer,vtkRenderWindow, vtkSphereSource, vtkPolyDataMapper,vtkAnimationCue]:
        print(f"from {c.__module__} import {c.__name__}")
    exit()

class vtkCustomAnimationCue(vtkAnimationCue):
    def __init__(self, renwin, sphere):
        super().__init__()
        self.renwin = renwin
        self.sphere = sphere

    def TickInternal(self, currenttime, deltatime, clocktime):
        print('TickInternal')
        new_st = currenttime * 180
        self.sphere.SetStartTheta(new_st)
        self.renwin.Render()


ren1=vtkRenderer()
renWin=vtkRenderWindow()
renWin.SetMultiSamples(0)
renWin.AddRenderer(ren1)

#iren = vtkRenderWindowInteractor()
#iren.SetRenderWindow(renWin)

sphere = vtkSphereSource()
mapper = vtkPolyDataMapper()
mapper.SetInputConnection(sphere.GetOutputPort())
actor = vtkActor()
actor.SetMapper(mapper)
ren1.AddActor(actor)
ren1.ResetCamera()
renWin.Render()

scene = vtkAnimationScene()
scene.SetModeToSequence()
scene.SetFrameRate(1)
scene.SetStartTime(0)
scene.SetEndTime(5)
scene.SetLoop(1)

cue1 = vtkCustomAnimationCue(renWin, sphere)
cue1.SetTimeModeToNormalized()
cue1.SetStartTime(0)
cue1.SetEndTime(1.0)
scene.AddCue(cue1)
scene.Play()
scene.Stop()

#iren.Start()

OK - I figured this out. The answer was in the Python wrapper docs"

“It is possible to subclass a VTK class from within Python, but this is of limited use because the C++ virtual methods are not hooked to the Python methods”

And sure enough, when I look at the code for vtkAnimationCue I find that TickInternal is a virtual method…

So the approach I’m using above is just never going to work. So instead of having my animator in a custom vtkAnimationCue class I need to put it into a class/method that I can then use as an Observer on vtkAnimationCue to trigger the update… Which is a little more indirect, but not really a problem.

Just for completeness, here’s the running vtkAnimationScene example in Python using vtkCallbackCommand.

Next step - figure out how to support interaction with the window while the animation is running…

# noinspection PyUnresolvedReferences
import vtkmodules.vtkInteractionStyle
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkRenderingCore import vtkRenderer, vtkRenderWindow    
from vtkmodules.vtkCommonDataModel import vtkAnimationScene
from vtkmodules.vtkFiltersSources import vtkSphereSource
from vtkmodules.vtkRenderingCore import vtkPolyDataMapper, vtkActor, vtkRenderWindowInteractor
from vtkmodules.vtkCommonCore import vtkAnimationCue, vtkCommand, vtkCallbackCommand, VTK_INT, VTK_OBJECT

find_module=False
if find_module is True:
    from vtkmodules.all import *
    for c in [vtkCallbackCommand]:
        print(f"from {c.__module__} import {c.__name__}")
    exit()

class SphereAnimator(vtkCallbackCommand):
    def __init__(self, renwin, sphere):
        self.renwin = renwin
        self.sphere = sphere
        vtkCallbackCommand.__init__(self)

    def __call__(self, obj, event):
        new_st = obj.GetAnimationTime() * 180
        self.sphere.SetStartTheta(new_st)
        self.renwin.Render()

ren=vtkRenderer()
renWin=vtkRenderWindow()
renWin.SetMultiSamples(0)
renWin.AddRenderer(ren)

iren = vtkRenderWindowInteractor()
iren.SetRenderWindow(renWin)

sphere = vtkSphereSource()
mapper = vtkPolyDataMapper()
mapper.SetInputConnection(sphere.GetOutputPort())
actor = vtkActor()
actor.SetMapper(mapper)
ren.AddActor(actor)
ren.ResetCamera()
renWin.Render()

scene = vtkAnimationScene()
scene.SetModeToSequence()
scene.SetFrameRate(30)
scene.SetStartTime(0)
scene.SetEndTime(10)

cue = vtkAnimationCue()
cue.SetTimeModeToNormalized()
cue.SetStartTime(0)
cue.SetEndTime(1.0)

sphere_animator=SphereAnimator(renWin, sphere)
cue.AddObserver('AnimationCueTickEvent', sphere_animator)

scene.AddCue(cue)
scene.Play()
scene.Stop()

iren.Start()