Search code examples
pythondata-visualizationvtkpyvista

Save screen capture in pyvista including interaction / rotation


I'm looking for a way to effectively capture the frame buffer during interaction in pyvista so that I can produce a video afterwards of the model moving around on the screen.

The problem I've encountered is that when I click the screen to interact with the viewer/plotter, no frames are written while the mouse button is pressed and the model is moving to its next position. This results in 'jerky' movements in the video.

Is there a way around this behavior to effectively do something like a screen capture of the plotter window, even when being manipulated? Maybe through direct access to the frame buffer or something similar?

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import pyvista as pv
import numpy as np
from pyvista import examples

import matplotlib as mpl
import matplotlib.pyplot as plt

px = int(round(1920*0.4))
py = int(round(1000*0.4))

mesh = examples.download_st_helens().warp_by_scalar()

p = pv.Plotter()
p.set_background(color='k')
cmap = mpl.cm.get_cmap('viridis')
p.add_mesh(mesh, lighting=True, texture=False, cmap=cmap, smooth_shading=True)
p.show_grid()
p.show(window_size=[px,py], auto_close=False, interactive_update=True)
p.render()
p.open_movie('anim.mp4',framerate=60)

i=0
while (i<100):
    i+=1
    p.write_frame()
    print(i)

p.close()

enter image description here


Solution

  • I have added an example in pure VTK, where a cube is rotated and a smooth video is captured with the rotation. Afterwards, the interactor is started and the user can interact with the scene afterwards.

    import os
    import vtk
    import numpy as np
    
    def vtkRotationMovie(renderWindow, filename='c:/test.avi'):
      global degrees
      degrees = 0
    
      windowToImageFilter = vtk.vtkWindowToImageFilter()
      windowToImageFilter.SetInput(renderWindow)
      windowToImageFilter.SetInputBufferTypeToRGB()
      windowToImageFilter.ReadFrontBufferOff()
      windowToImageFilter.Update()
    
      if os.name == 'nt':
        writer = vtk.vtkAVIWriter()
      else:
        writer = vtk.vtkOggTheoraWriter()
      writer.SetInputConnection(windowToImageFilter.GetOutputPort())
      writer.SetRate(10) # Not needed for Ogg
    
      try:
        os.remove(filename)
      except OSError:
        pass
      writer.SetFileName(filename)
      writer.Start()
    
      timerId = renderWindow.GetInteractor().CreateRepeatingTimer(50)
      def cb(interactor, event):
        global degrees
        step = 5
        if (degrees > 359):
          interactor.DestroyTimer(timerId)
          writer.End()
          return
        interactor.GetRenderWindow().Render()
        cam = interactor.GetRenderWindow().GetRenderers().GetFirstRenderer().GetActiveCamera()
        cam.Azimuth(step)
        cam.OrthogonalizeViewUp()
        windowToImageFilter.Modified()
        writer.Write()
        degrees = degrees + step
      renderWindow.GetInteractor().AddObserver('TimerEvent', cb)
      renderWindow.GetInteractor().Start()
    
    # create a rendering window and renderer
    ren = vtk.vtkRenderer()
    renWin = vtk.vtkRenderWindow()
    renWin.AddRenderer(ren)
    
    # create a renderwindowinteractor
    iren = vtk.vtkRenderWindowInteractor()
    iren.SetRenderWindow(renWin)
    
    # create cube
    cube = vtk.vtkCubeSource()
    
    # mapper
    cubeMapper = vtk.vtkPolyDataMapper()
    cubeMapper.SetInputConnection(cube.GetOutputPort())
    
    # actor
    cubeActor = vtk.vtkActor()
    cubeActor.SetMapper(cubeMapper)
    
    # assign actor to the renderer
    ren.AddActor(cubeActor)
    ren.SetBackground(.3,.2,.1)
    # enable user interface interactor
    iren.Initialize()
    renWin.Render()
    
    vtkRotationMovie(renWin, filename='./test.avi')
    
    iren.Start()