Animate data over time#
MAPDL LS-DYNA FLUENT CFX
This tutorial demonstrates how to create 3D animations of data in time.
Download tutorial as Python script
Download tutorial as Jupyter notebook
To animate data across time, you must store the data in a FieldsContainer
with a time
label.
Get the result files#
First, import a results file. For this tutorial, you can use the one available in the Examples
module.
For more information about how to import your own result file in DPF, see
the Import Data tutorial section.
# Import the ``ansys.dpf.core`` module
from ansys.dpf import core as dpf
# Import the examples module
from ansys.dpf.core import examples
# Import the operators module
from ansys.dpf.core import operators as ops
# Define the result file path
result_file_path = examples.find_msup_transient()
# Create the model
model = dpf.Model(data_sources=result_file_path)
Define a time scoping#
To animate across time, you must define the time steps you are interested in.
This tutorial retrieves all the time steps available in TimeFreqSupport
, but you can also filter them.
For more information on how to define a scoping, see the Narrow down data
tutorial in the
Import Data tutorials section.
# Get a scoping of all time steps available
time_steps = model.metadata.time_freq_support.time_frequencies
Extract the results#
Extract the results to animate. In this tutorial, you extract the displacement and stress results.
Note
Only the elemental
, nodal
, or Faces
locations are supported for animations.
overall
and elemental_nodal
locations are not currently supported.
# Get the displacement fields (already on nodes) at all time steps
disp_fc = model.results.displacement(time_scoping=time_steps).eval()
print(disp_fc)
DPF Fields Container
with 20 field(s)
defined on labels: time
with:
- field 0 {time: 1} with Nodal location, 3 components and 393 entities.
- field 1 {time: 2} with Nodal location, 3 components and 393 entities.
- field 2 {time: 3} with Nodal location, 3 components and 393 entities.
- field 3 {time: 4} with Nodal location, 3 components and 393 entities.
- field 4 {time: 5} with Nodal location, 3 components and 393 entities.
- field 5 {time: 6} with Nodal location, 3 components and 393 entities.
- field 6 {time: 7} with Nodal location, 3 components and 393 entities.
- field 7 {time: 8} with Nodal location, 3 components and 393 entities.
- field 8 {time: 9} with Nodal location, 3 components and 393 entities.
- field 9 {time: 10} with Nodal location, 3 components and 393 entities.
- field 10 {time: 11} with Nodal location, 3 components and 393 entities.
- field 11 {time: 12} with Nodal location, 3 components and 393 entities.
- field 12 {time: 13} with Nodal location, 3 components and 393 entities.
- field 13 {time: 14} with Nodal location, 3 components and 393 entities.
- field 14 {time: 15} with Nodal location, 3 components and 393 entities.
- field 15 {time: 16} with Nodal location, 3 components and 393 entities.
- field 16 {time: 17} with Nodal location, 3 components and 393 entities.
- field 17 {time: 18} with Nodal location, 3 components and 393 entities.
- field 18 {time: 19} with Nodal location, 3 components and 393 entities.
- field 19 {time: 20} with Nodal location, 3 components and 393 entities.
# Get the stress fields on nodes at all time steps
# Request the stress on |Nodal| location as the default |ElementalNodal| location is not supported.
stress_fc = model.results.stress.on_location(location=dpf.locations.nodal).on_time_scoping(time_scoping=time_steps).eval()
print(stress_fc)
DPF Fields Container
with 20 field(s)
defined on labels: time
with:
- field 0 {time: 1} with Nodal location, 6 components and 393 entities.
- field 1 {time: 2} with Nodal location, 6 components and 393 entities.
- field 2 {time: 3} with Nodal location, 6 components and 393 entities.
- field 3 {time: 4} with Nodal location, 6 components and 393 entities.
- field 4 {time: 5} with Nodal location, 6 components and 393 entities.
- field 5 {time: 6} with Nodal location, 6 components and 393 entities.
- field 6 {time: 7} with Nodal location, 6 components and 393 entities.
- field 7 {time: 8} with Nodal location, 6 components and 393 entities.
- field 8 {time: 9} with Nodal location, 6 components and 393 entities.
- field 9 {time: 10} with Nodal location, 6 components and 393 entities.
- field 10 {time: 11} with Nodal location, 6 components and 393 entities.
- field 11 {time: 12} with Nodal location, 6 components and 393 entities.
- field 12 {time: 13} with Nodal location, 6 components and 393 entities.
- field 13 {time: 14} with Nodal location, 6 components and 393 entities.
- field 14 {time: 15} with Nodal location, 6 components and 393 entities.
- field 15 {time: 16} with Nodal location, 6 components and 393 entities.
- field 16 {time: 17} with Nodal location, 6 components and 393 entities.
- field 17 {time: 18} with Nodal location, 6 components and 393 entities.
- field 18 {time: 19} with Nodal location, 6 components and 393 entities.
- field 19 {time: 20} with Nodal location, 6 components and 393 entities.
Animate the results#
Animate the results with the FieldsContainer.animate()
method.
You can animate them on a deformed mesh (animate the color map and the mesh)
or on a static mesh (animate the color map only).
The default behavior of the FieldsContainer.animate()
method is to:
Display the norm of the data components;
Display data at the top layer for shells;
Display the deformed mesh when animating displacements;
Display the static mesh for other types of results;
Use a constant and uniform scale factor of 1.0 when deforming the mesh.
You can animate any result on a deformed geometry by providing displacement results in the deform_by parameter.
The geometry can be deformed by a Result
object, an Operator
(It must evaluate to a FieldsContainer
of same length as the one being animated), or a FieldsContainer
(also of same length as the one being animated).
Note
The behavior of the FieldsContainer.animate()
method is defined by a Workflow
it creates and feeds to an Animator
.
This Workflow
loops over a Field
of frame indices and for each frame generates a field of norm contours
to render, as well as a displacement field to deform the mesh if deform_by is provided.
For more information on plots on deformed meshes see: ref_plotting_data_on_deformed_mesh.
Animate the displacement results#
Use FieldsContainer.animate()
with the displacement results.
Animate the stress#
Use FieldsContainer.animate()
with the stress results.
Change the scale factor#
You can change the scale factor using:
A single number for a uniform constant scaling;
A list of numbers for a varying scaling (same length as the number of frames).
Uniform constant scaling#
# Define a uniform scale factor
uniform_scale_factor=10.
# Animate the displacements
disp_fc.animate(scale_factor=uniform_scale_factor)

Varying scaling#
# Define a varying scale factor
varying_scale_factor = [float(i) for i in range(len(disp_fc))]
# Animate the displacements
disp_fc.animate(scale_factor=varying_scale_factor)

Save the animation#
You can save the animation using the save_as
argument with a target file path with the desired format as the extension key.
Accepted extensions are:
.gif
;.avi
;.mp4
For more information see pyvista.Plotter.open_movie
.
# Animate the stress results and save it
stress_fc.animate(deform_by=disp_fc, save_as="animate_stress.gif")
Control the camera#
Control the camera with the cpos
argument.
A camera position is a combination of:
A position;
A focal point (the target);
A upwards vector.
It results in a list of format:
camera_position= [[pos_x, pos_y, pos_z], # position
[fp_x, fp_y, fp_z], # focal point
[up_x, up_y, up_z]] # upwards vector
The FieldsContainer.animate()
method accepts a single camera position or a list of camera positions for each frame.
Note
A tip for defining a camera position is to do a first interactive plot of the data
with argument return_cpos=True
, position the camera as desired in the view, and retrieve
the output of the plotting command.
Fixed camera#
# Define the camera position
cam_pos = [[0., 2.0, 0.6], [0.05, 0.005, 0.5], [0.0, 0.0, 1.0]]
# Animate the stress with a custom fixed camera position
stress_fc.animate(cpos=cam_pos)

Moving camera#
import copy
# Define the list of camera positions
cpos_list = [cam_pos]
# Incrementally increase the x coordinate of the camera by 0.1 for each frame
for i in range(1, len(disp_fc)):
new_pos = copy.deepcopy(cpos_list[i-1])
new_pos[0][0] += 0.1
cpos_list.append(new_pos)
# Animate the stress with a moving camera
stress_fc.animate(cpos=cpos_list)

Additional options#
You can use additional PyVista arguments of pyvista.Plotter.open_movie
), such as:
Show or hide the coordinate system axis with
show_axes=True
orshow_axes=False
;Render off-screen for batch animation creation with
off_screen=True
;Change the frame-rate with
framerate
;Change the image quality with
quality
.