Skip to content

Animation

pl.blender.animate(output, updater, frames, *, fps, ...) drives a Python per-frame loop. The updater(frame_index) callback mutates the PyVista scene in place, the bridge reconciles against the identity cache (mesh data blocks are refreshed, not rebuilt), Cycles renders the frame, and the resulting PNGs are muxed into the requested container.

Deformation

import numpy as np
import pyvista as pv

xs = np.arange(-10.0, 10.0, 0.25)
ys = np.arange(-10.0, 10.0, 0.25)
x, y = np.meshgrid(xs, ys)
radius = np.sqrt(x**2 + y**2)

grid = pv.StructuredGrid(x, y, np.sin(radius))
grid["height"] = grid.points[:, 2].copy()

plotter = pv.Plotter(off_screen=True, window_size=[640, 480])
plotter.add_mesh(grid, scalars="height", cmap="viridis", clim=(-1.0, 1.0))
plotter.camera_position = [(20.0, -20.0, 18.0), (0.0, 0.0, 0.0), (0.0, 0.0, 1.0)]

phases = np.linspace(0.0, 2.0 * np.pi, 30, endpoint=False)

def update(frame: int) -> None:
    z = np.sin(radius + phases[frame]).ravel()
    grid.points[:, 2] = z
    grid["height"] = z

plotter.blender.animate(
    "wave.mp4",
    updater=update,
    frames=range(30),
    fps=30,
    samples=32,
)
plotter.close()

The vertex positions are written in place via foreach_set on the existing bpy mesh — no per-frame allocation, no leaked data blocks. Topology must stay constant across frames; if mesh.points.shape changes the cache rebuilds from scratch and warns.

Full script: examples/animation/wave_animation.py.

Camera orbits

pl.blender.orbit_camera(...) returns a per-frame updater that rotates the camera around a focal point. The orbit radius and elevation are derived from the plotter's current camera, so frame 0 reproduces the starting pose and a full loop returns to it.

import pyvista as pv

airplane = pv.examples.load_airplane()
center = tuple(float(c) for c in airplane.center)

plotter = pv.Plotter(off_screen=True, window_size=[480, 360])
plotter.add_mesh(airplane, color="#9ec5e8", pbr=True, metallic=0.7, roughness=0.3)
plotter.camera_position = [
    (center[0] + 2500.0, center[1] - 2500.0, center[2] + 1200.0),
    center,
    (0.0, 0.0, 1.0),
]

orbit = plotter.blender.orbit_camera(focal_point=center, n_frames=48)
plotter.blender.animate(
    "orbit.gif",
    updater=orbit,
    frames=range(48),
    fps=24,
    samples=32,
)
plotter.close()

Full script: examples/animation/orbit_animation.py.

Output format

The extension picks the muxer:

Extension Backend Codec
.gif imageio's gif plugin (built-in)
.mp4 imageio-ffmpeg libx264
.mov, .mkv imageio-ffmpeg libx264
.webm imageio-ffmpeg libvpx-vp9

Anything else raises ValueError with the supported set.