Guide / Chapter 21

Headless Rendering

Headless mode renders a saved session to a PNG, a movie or a glTF scene from the command line, with no window, and reports progress and exit codes that scripts can rely on. It is also the render pipeline behind the Animate editor: Render runs this mode in a child process and reads its progress lines (see Animations). It loads a .pyv session, evaluates the nodes feeding one Plotter and writes the output; a glTF export also writes a JSON styling sidecar. No window opens and no Qt application is created, so it works from scripts, over SSH and on machines without a display.

Invoking it

The arguments are the same however PVA is installed; only the launcher differs: the executable inside the macOS bundle (/Applications/PVA.app/Contents/MacOS/PVA), the Linux AppImage itself (or AppRun inside an extracted bundle), or python -m _pva.run in a development environment. Below, pva stands for whichever applies.

pva --headless SESSION.pyv --screenshot OUT.png
pva --headless SESSION.pyv --animate [NAME_OR_YAML]
pva --headless SESSION.pyv --export-scene OUT.gltf

--headless anywhere in the arguments selects headless mode; without it PVA starts normally and opens a bare file path as a session. Exactly one of the three modes is required.

FlagUseDefaultDescription
--version VERSION optional ==SUPPRESS==
--headless SESSION required none Path to the .pyv session to render. Presence of this flag selects headless mode; no window opens and no Qt application is created.
--screenshot OUT.png one of --screenshot / --animate / --export-scene none Render one frame of the main Plotter (or the Plotter chosen with --plotter) to this PNG file. Mutually exclusive with --animate and --export-scene. The default size is 1280x720.
--animate [NAME_OR_YAML] one of --screenshot / --animate / --export-scene none Render an animation and encode it with ffmpeg. The optional argument is a saved animation name, or the path to a YAML file holding an animation entry. With no argument, the session must hold exactly one saved animation. Mutually exclusive with --screenshot and --export-scene.
--export-scene OUT.gltf one of --screenshot / --animate / --export-scene none Render one frame of the main Plotter (or the one chosen with --plotter) and write it as a JSON glTF scene, plus a <name>.scene.json sidecar with styling, scalar bars, background and camera. Volumes are left out. Mutually exclusive with --screenshot and --animate.
--zoom F optional 1 Camera zoom factor applied after the camera is set, for --screenshot and --export-scene. Must be positive; values above 1 zoom in. Ignored by --animate.
--out OUT optional none Movie output path for --animate. Overrides the animation’s own Output path; when neither is set, the movie is <name>.mp4 in the screenshots folder.
--plotter PLOTTER optional none Node id of the Plotter to render. Defaults to the main Plotter, or to the Plotter stored on the animation. Also used by --export-scene.
--size WxH optional none Frame size as WIDTHxHEIGHT, for example 1280x720. Both dimensions must be positive. For --animate it overrides the animation’s Size. For --export-scene it sets the render window size recorded in the sidecar.
--jobs JOBS optional 0 Ceiling on parallel frame workers for --animate. 0 uses the Settings value capture.jobs, and when that is 0 as well, half the CPU count. The worker count is also capped by available memory.
--frames-dir FRAMES_DIR optional none Folder for rendered frames. Stale frame-*.png files in it are cleared first; other files are left alone. The folder is kept after the render.
--keep-frames optional off Keep the rendered frames after encoding, in <movie stem>-frames beside the movie unless --frames-dir is given.
--frames-only optional off Render frames only and skip encoding; ffmpeg is never looked up. Frames are always kept, and the final PVA_DONE line names the frames folder.

Screenshot and export-scene modes

pva --headless scene.pyv --screenshot wide.png --size 1920x1080 --plotter 12
pva --headless scene.pyv --export-scene scene.gltf --zoom 2

One frame of one Plotter is rendered. --plotter ID names it by node id, otherwise the session’s main Plotter is used, and with neither the run fails. The default size is 1280x720. The demand roots are every visible Plot and every active Camera connected directly to the Plotter’s scene port; hidden nodes and nodes connected only to other Plotters are not drawn. The saved Plotter theme and settings load first, so the render matches the live viewport, and the Capture setting Transparent background applies. A file that cannot be read prints a one-line message on standard error and defaults are used. --zoom F scales the camera zoom after the camera is set. On success the process prints PVA_DONE OUT.

An export-scene run writes the visible scene as a JSON glTF with embedded data, plus a sidecar with the suffix replaced by .scene.json. The sidecar records each object’s styling (line width, point size, opacity, tubes or spheres, the solid color or colormap samples), the scalar bars, the Plotter’s background, frame and axes setting, the camera in the viewport’s own coordinates and the window size. Scalar bars and the axes widget stay out of the glTF; volume renderings and objects with no cells cannot be exported and are listed under skipped.

Animate mode

pva --headless scene.pyv --animate "Orbit" --out orbit.mp4 --jobs 4
pva --headless scene.pyv --animate "Orbit" --frames-only --frames-dir /data/orbit-frames

The optional argument after --animate selects the animation. With none, the session must hold exactly one saved animation; a name selects that animation; a path to a YAML file supplies an entry in the shape the Animate editor stores, validated against the session but not added to it. An animation with no drivers, or one naming nodes or parameters the session lacks, is an error. The animation’s Plotter is used when it names one, otherwise the main Plotter.

Output and frames

The movie path is --out, else the animation’s Output path, else <name>.mp4 in the screenshots folder. --size overrides the animation’s size. Frames are PNG files named frame-<digits>.png; where they go depends on the options:

ConditionFrames folderAfter the run
--frames-dir DIRDIRKept; stale frame-*.png cleared first, other files left alone.
--keep-frames, the Output choice Movie and frames, or --frames-only, no --frames-dir<movie stem>-frames beside the movieKept; stale frames cleared first.
None of the aboveA temporary scratch folderRemoved on success and on cancel.

--frames-only (or the Output choice Frames only) skips encoding, and ffmpeg is never looked up. Otherwise ffmpeg assembles the movie after the last frame using the animation’s codec, quality and FPS (see ffmpeg lookup). If ffmpeg is missing or fails, the run ends with a PVA_ERROR naming the folder that holds the kept frames, so no rendering work is lost.

Parallel rendering

The first frame is rendered in the main process; it settles the shared camera pose and measures the memory one frame needs. The remaining frames are split into chunks and rendered by worker processes, each reloading the session independently.

The worker count never exceeds a ceiling taken from the first of --jobs N (if non-zero), the Animation render workers setting (capture.jobs) and half the machine’s CPU count. It is also capped so the workers fit in 80 percent of the memory currently available at the measured per-frame footprint, and by the number of frames left. At least one worker always runs. Reduce the count for sessions with Tracer nodes, since each worker evaluates its own copy of the graph, including its own tracing subprocess. The decision is printed as a PVA_JOBS line before any worker starts.

Camera and render order

An animate render takes its camera from the first available of: an active Camera node connected to the Plotter, the saved camera view the animation names, the animation’s recorded camera, the pose and letterbox saved for the Plotter in the session, and an automatic fit. When the animation pins a camera (a named view or a recorded pose), every frame uses it unchanged; otherwise the first frame’s settled pose is shared with all workers, so every frame is framed the same way. To animate the camera, drive a Camera node’s parameters; the node re-applies on every frame. A saved view the animation names but the session lacks falls back to the recorded camera with a warning on standard error.

Progress protocol

Headless mode reports on standard output, one line per message. Every line begins with one of these prefixes followed by a space; a caller should ignore anything else.

LineMeaning
PVA_PROGRESS <done> <total>Frames finished out of the total, printed after the first frame and as each chunk completes.
PVA_JOBS <jobs> <footprint_mb> <available_mb>The worker decision, printed once before any worker starts and only when more than one frame must be rendered.
PVA_FRAMES <dir>The folder holding kept frames, printed just before the final PVA_DONE of an encoded render that kept them.
PVA_DONE <path>Success. The path is the PNG, the glTF file, the movie or, for a frames-only render, the frames folder.
PVA_ERROR <message>Failure. The message is the rest of the line.

A typical animate run prints:

PVA_PROGRESS 1 120
PVA_JOBS 4 612 14310
PVA_PROGRESS 31 120
PVA_PROGRESS 120 120
PVA_FRAMES /data/orbit-frames
PVA_DONE /data/orbit.mp4

Check for PVA_ERROR before the exit code; its message is more specific. Other messages go to standard error.

Exit codes

CodeMeaning
0Success.
1The run failed with a reported error (also printed as PVA_ERROR): an unreadable session, no Plotter, an unknown animation, no drivers, an ffmpeg failure or an unwritable file.
2The arguments are invalid: not exactly one mode, a malformed --size, a non-positive --zoom or any other argument error. The message goes to standard error.
130SIGINT or SIGTERM interrupted the run. Workers are terminated first and a temporary frames folder is removed.

Settings read by headless mode are described in Settings, and folder locations in Files and Folders.