= Animating Data with ParaView Python Scripting = Some information on how to write Python scripts to animate your data with ParaView can be found here:\\ https://www.paraview.org/Wiki/ParaView/Python_Scripting\\ https://kitware.github.io/paraview-docs/latest/python/paraview.simple.html\\ https://kitware.github.io/paraview-docs/latest/python/_modules/paraview/servermanager.html On this page, we would like to share our own experience with you. DISCLAIMER: our knowledge about all this is limited and maybe even incorrect. So take all this with a grain of salt. == ParaView Gui: Animation View == As usual, when you want to write a Python script for !ParaView from scratch, and have no idea where to start, it makes sense to open the !ParaView GUI, start a Python trace, and prototype the desired setup in the GUI interactively. When you want to generate an animation, you have to deal with the Animation View, obviously. [[Image(ParaView_AnimationView.jpg, margin=10)]]\\ Unfortunately, !ParaView is not willing to show you all settings in the trace. Some are just skipped, e.g. settings regarding the "TimeKeeper1 - Time" are NOT reflected in the trace. For this reason it makes sense to take a more closer look at the mechanisms and Python classes working behind the scene. == Basic Concept == The basic concept of how to animate "something", in this case parameters (so called properties) of the !ParaView scene, is quite easy to understand. The central element of any animation is a timeline with a start and end time and a mechanism to determine a number of descrete timesteps (frames). in ParaView all this is implemented in the Python class paraview.simple.!AnimationScene. Within the time interval defined in the timeline, properties of the scene can be changed. In the !ParaView GUI this concept is reflected by so called "tracks" (or "cues") in the Animation View. Basically, each track defines the temporal change of one (or more) properties of the scene. In !ParaView-Python, different kinds (classes) of tracks/cues are available. We will deal with four of them, which are implemented in the following Python classes: * paraview.simple.!KeyFrameAnimationCue: basic cue to animate general properties of objects (readers, sources, filters) in the render pipeline * paraview.simple.!CameraAnimationCue: cue to change camera parameters (e.g. position, focal point, up direction, view angle, parallel scale) * paraview.simple.!PythonAnimationCue: cue to execute a Python script at a certain point in time * paraview.simple.!TimeAnimationCue: cue to determine what data timesteps are loaded depending on the animation clock time In general, each cue is connected to one or more properties of a !ParaView object and has a number of keyframes attached to it, each defining certain values valid for a specific point in time. Therefore the key elements :) of a keyframe are !KeyTime and !KeyValues. The value(s) of the referred property is set to the keyvalues at the specified keytime. The most types of keyframes have the ability to interpolate keyvalues from one keyframe to the next (se below). In the next paragraphs we are going to explain the basic mechanisms of the relevat Python classes and we give examples of typical use cases. == !AnimationScene == The !AnimationScene is responsible to steer the animation clock time. For this purpose, the !AnimationScene has a !StartTime, an !EndTime, and a mechanism to calculate the discrete points in time between !StartTime and !EndTime. These descrete timesteps are determined by the Mode (called !PlayMode in Python) of the !AnimationScene. Three different !PlayModes are available: * 'Sequence': the timesteps are defined by the property !NumberOfFrames * 'Snap To !TimeSteps': only useful if you have time dependent data. The descrete timesteps are identical to the timesteps of the data. The !AnimationScene has knowledge about the available data timesteps by the help of a seperate object, the !TimeKeeper. * '!RealTime': given a specified duration, the timesteps are calculated on the fly (while rendering) in order to guarantee that the animated sequence will take this duration. This mode is not useful for typical Python scripting, though. Important properties of !AnimationScene: * !StartTime: the start time of the animation * !EndTime: the end time of the animation * !PlayMode: either 'Sequence', 'Snap To !TimeSteps' or '!RealTime' * !NumberOfFrames: number of frames, only used when !PlayMode is set to 'Sequence' * Duration: duration of the animation in seconds, only used when !PlayMode is set to '!RealTime' * !AnimationTime: the actual animation clock time. Can be get or set. * !TimeKeeper: the !TimeKeeper-object * Cues: List of attached cues (=tracks) Important methods of !AnimationScene: * !GoToFirst(): goto first frame * !GoToLast(): goto last frame * !GoToNext(): goto next frame * !GoToPrevious(): goto previous frame * Play(): renders all timesteps one after another in a window * Stop(): stops the rendering === !AnimationScene Use Cases === Get the animation scene and play a sequence of 100 timesteps: {{{ #!python from paraview.simple import * renderView = GetActiveViewOrCreate('RenderView') animationScene = GetAnimationScene() animationScene.StartTime = 0 animationScene.EndTime = 1 animationScene.NumberOfFrames = 100 animationScene.PlayMode = 'Sequence' animationScene.GoToFirst() i = 0 while True: imageName = "image_%04d.jpg" % (i) i = i + 1 renderView.Update() SaveScreenshot(imageName, renderView, ImageResolution=[1920, 1080]) if animationScene.AnimationTime == animationScene.EndTime: break anmationScene.GoToNext() }}} Get the animation scene and play all available timesteps of time dependent data: {{{ #!python from paraview.simple import * renderView = GetActiveViewOrCreate('RenderView') animationScene = GetAnimationScene() animationScene.PlayMode = 'Snap To TimeSteps' animationScene.GoToFirst() i = 0 while True: imageName = "image_%04d.jpg" % (i) i = i + 1 renderView.Update() SaveScreenshot(imageName, renderView, ImageResolution=[1920, 1080]) if animationScene.AnimationTime == animationScene.EndTime: break anmationScene.GoToNext() }}} == Key Frames == Before we start our explantion of cues, we give an overview about keyframes, as keyframes are an integral part of cues. Keyframes define what value(s) a cue passes to the property of the connected pipeline object at a give point in time. Without keyframes, a cue is more or less useless (with the exception of the !TimeAnimationCue, see below). A cue stores the list of attached keyframes in its !KeyFrame property. The two basic keyframe classes are: * paraview.simple.!KeyFrame * paraview.simple.!BooleanKeyFrame These two keyframes have only two intersting properties: !KeyTime and !KeyValues (which is a Python-list of values). Keyframes used to interpolate values are: * paraview.simple.!RampKeyFrame: linear interpolation * paraview.simple.!SinusoidKeyFrame: sinusoidal interpolation * paraview.simple.!ExponentialKeyFrame: exponential interpolation * paraview.simple.!CompositeKeyFrame: composite of four types of keyframes. 'Interpolation' property can be set to 'Boolean', 'Ramp', 'Exponential', or 'Sinusoid' The keyframe for interpolating camera parameters is: * paraview.simple.!CameraKeyFrame == !KeyFrameAnimationCue == As already mentioned, a !KeyFrameAnimationCue connects the property of a pipeline object to the cue. This connection is defined via the attributes !AnimatedProxy (proxy of the pipeline object) and !AnimatedPropertyName (name of the connected property), though typically you do not have to set these attributes on your own. Instead they are set when constructing a !KeyFrameAnimationCue by calling !GetAnimationTrack(...) (see use case below). Similar to the !AnimationScene, every cue also has the properties !StartTime and !EndTime. Typically these properties do not have to be identical to the corresponding ones of !AnimationScene. When !TimeMode is set to 'Normalize' (which is the default), the start and end time of the animation scene is linearly interpolated to the interval [!StartTime, !EndTime]. Default values are !StartTime=0 and !EndTime=1. === !KeyFrameAnimationCue Use Case === Create three different animation cues for three different properties of a sphere: {{{ #!python from paraview.simple import * sphere = Sphere() Show(sphere) track1 = GetAnimationTrack("Visibility") # property of active source track2 = GetAnimationTrack("Center", 0, sphere) track3 = GetAnimationTrack(sphere.GetProperty("Radius")) # set keyframes, in this example only for track1: kf0 = CompositeKeyFrame() kf0.Interpolation = 'Ramp' # At time = 0, value = 0 kf0.KeyTime = 0 kf0.KeyValues = [0] kf1 = CompositeKeyFrame() # At time = 1.0, value = 200 kf1.KeyTime = 1.0 kf1.KeyValues = [200] # attach keyframes to track1 track1.KeyFrames = [kf0, kf1] }}} == !TimeAnimationCue == This type of cue is very special somehow: while all other kind of cues are absolutely useless without attached keyframes, this one can fullfill a special task without any: it can directly hand over the animation clock time (coming from !AnimationScene) to the property of a connected object. The typical use case within !ParaView is as follows: the !AnimationScene sets the value of the !TimeAnimationCue directly to its clock time. The !TimeAnimationCue passes this value to the Time property of the !TimeKeeper object, which in turn requests all data objects to load just this time step of the data. This way the clock time of the animation scene can be kept in sync with the data timsteps. Nevertheless you can override this behaviour by attaching keyframes to this cue and setting "!UseAnimationTime=0". This way you can control what data timesteps are loaded/rendered independent from the animation clock time. === !TimeAnimationCue Use Cases === Whatever the !AnimationScene clock time is, set the time of the timetrack to 100. This setup can be useful e.g. to generate an animated camera flight of one single data timestep (100 in this example). {{{ #!python from paraview.simple import * timeTrack = GetTimeTrack() timeTrack.StartTime = 0 timeTrack.EndTime = 1 timeTrack.UseAnimationTime = 0 keyFrame = CompositeKeyFrame() keyFrame.Interpolation = "Ramp" keyFrame.KeyValues=[100] # set time step to 100 keyFrame.KeyTime = 0 timeTrack.KeyFrames=[keyFrame] }}} Reset the timetrack to use the !AnimationScene clock time again: {{{ #!python timeTrack.UseAnimationTime = 1 }}} == !CameraAnimationCue == The !CameraAnimationCue controls severals parameters of the camera, like position, focal point, up vector and view angle. It's main purpose is to generate camera flights by controlling the position and focal point of the camera depending on the animation time. The behaviour of the camera can be set to three different modes by the property Mode: Mode: * 'Interpolate Camera': each keyframe of the !CameraAnimationCue stores one camera position. These positions are interpolated in the animation. * 'Path-based': the first keyframe of the !CameraAnimationCue stores a list of camera positions (!PositionPathPoints) and focal points (!FocalPathPoints). These values are interpolated in the animation. The pathes may be closed to generate seamless camera rides (!ClosedPositionPath, !ClosedFocalPath). * 'Follow-data': The !CameraAnimationCue is connected to a visible pipeline object and the focal point of the camera is adjusted to point at the center of this (potentially moving) object. === !CameraAnimationCue Use Cases === {{{ #!python renderView = GetActiveView() cameraTrack = GetCameraTrack(view=renderView) cameraTrack.Mode = 'Interpolate Camera' To be continued }}} == !PythonAnimationCue == Also a Python script can be used in an animation cue. In the Python script three functions can be implemented: * start_cue: is executed once at the beginning of the animation * tick: is executed every time step * end_cue: is executed once at the end of the animation Here is an example of such a script. In this example the value of the timestep is translated into a date string (e.g. 2018-01-01) and displayed in the scene via a text source with name "Text1". {{{ #!python from paraview.simple import * from datetime import date from datetime import timedelta def start_cue(self): pass def tick(self): view = GetActiveView() time = int(view.ViewTime) dat = date(2000,1,1) + timedelta(days=time) tx = FindSource("Text1") tx.Text = repr(dat.year) + "-" + '{:02d}'.format(dat.month) + "-" + '{:02d}'.format(dat.day) def end_cue(self): pass }}} Here is an example on how to create and use a Python cue in a Python script: {{{ #!python ############################# function call on each animation step PythonAnimationCue1 = PythonAnimationCue() PythonAnimationCue1.Script= """ def start_cue(self): pass def tick(self): # source = FindSource("out_") # source.UpdatePipeline(GetAnimationScene().TimeKeeper.Time) # tube1 = FindSource("tube1") # tube1.UpdatePipeline(self.GetAnimationTime()) # glyph1 = FindSource("glyph1") # glyph1.UpdatePipeline(self.GetAnimationTime()) animationScene1 = GetAnimationScene() time = animationScene1.TimeKeeper.Time i = int(time) print(time, i) # skip if already finished if i > 250: renderView1=GetActiveView() # renderView1.ViewTime = i # animationScene1.AnimationTime = i # get new filename outname=outpath + 'out_' + str(i).zfill(5) + '.x3d' print(' -> ' + outname) # write X3D scene x3dExporter=exporters.X3DExporter(FileName=outname) print(' x3d created') x3dExporter.SetView(renderView1) print(' x3d configured') x3dExporter.Write() print(' x3d written') def end_cue(self): pass """ ############################## get animation scene animationScene1 = GetAnimationScene() animationScene1.Cues.append(PythonAnimationCue1) animationScene1.PlayMode = 'Sequence' animationScene1.StartTime = 0 animationScene1.NumberOfFrames = len(vtkfiles) - 0 animationScene1.Play() }}}