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()
1 Like

Thanks for the ideas.

from vtkmodules.vtkCommonCore import vtkAnimationCue
from vtkmodules.vtkCommonDataModel import vtkAnimationScene
from vtkmodules.vtkFiltersSources import vtkSphereSource
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTrackballCamera
from vtkmodules.vtkRenderingCore import vtkPolyDataMapper, \
vtkActor, vtkRenderer, vtkRenderWindow, vtkRenderWindowInteractor
from vtkmodules.vtkRenderingVolumeOpenGL2 import vtkSmartVolumeMapper


class FixedAnimationCue(vtkAnimationCue):
    def __init__(self):
        self.AddObserver('AnimationCueTickEvent', self._TickHandler)

    def _TickHandler(self, obj, event) -> None:
        currenttime = obj.GetAnimationTime()
        deltatime = obj.GetDeltaTime()
        clocktime = obj.GetClockTime()
        self.TickInternal(currenttime, deltatime, clocktime)

    def TickInternal(self, currenttime: float, deltatime: float, clocktime: float) -> None:
        pass


class TestAni(FixedAnimationCue):
    def __init__(self):
        super(TestAni, self).__init__()
        self.renderWindow: vtkRenderWindow = None
        self.render: vtkRenderer = None
        self.sphere = None

    def TickInternal(self, currenttime: float, deltatime: float, clocktime: float) -> None:
        # print(currenttime, deltatime, clocktime)
        camera = self.render.GetActiveCamera()
        self.sphere.SetStartTheta(currenttime * 180)
        # camera.Azimuth(currenttime * 180)
        self.renderWindow.Render()


def showVtk():
    # create actor
    sphere = vtkSphereSource()
    mapper = vtkPolyDataMapper()
    mapper.SetInputConnection(sphere.GetOutputPort())
    actor = vtkActor()
    actor.SetMapper(mapper)
    # vtk init
    render = vtkRenderer()
    renderWin = vtkRenderWindow()
    renderWin.SetWindowName('vtk')
    renderWin.SetSize(1024, 768)
    renderWin.AddRenderer(render)

    render.AddActor(actor)

    render.ResetCamera()
    render.SetBackground(0.0, 0.0, 0.0)
    renderWin.Render()

    # init ani
    scene = vtkAnimationScene()
    scene.SetModeToSequence()
    scene.SetFrameRate(30)
    scene.SetStartTime(0)
    scene.SetEndTime(60)

    testCue = TestAni()
    testCue.render = render
    testCue.renderWindow = renderWin
    testCue.sphere = sphere
    testCue.SetTimeModeToNormalized()
    testCue.SetStartTime(0)
    testCue.SetEndTime(60)

    scene.AddCue(testCue)
    scene.Play()
    scene.Stop()

    iren = vtkRenderWindowInteractor()
    iren.SetRenderWindow(renderWin)

    style = vtkInteractorStyleTrackballCamera()
    iren.SetInteractorStyle(style)
    iren.Initialize()
    renderWin.Render()
    iren.Start()


if __name__ == '__main__':
    showVtk()