Skip to content

CLI Utilities

Cache Management

SimView caches scene files fetched from remote hosts (see Opening a file on a remote host) under $XDG_CACHE_HOME/.simview_cache (~/.cache/.simview_cache by default). You can clear it using the following command:

simview clear

Inspecting a scene file

To print a quick structural summary of a scene JSON file (body/terrain/state breakdown, plus consistency warnings) without opening the viewer:

simview info scene.json          # human-readable text
simview info scene.json --json   # machine-readable JSON (for scripts/agents)
simview info scene.json --full   # don't cap lists (bodies, missing frames) at 20

Besides structure, the summary carries value statistics per batch: each scalar's min/mean/max (and percentiles in --json), each body's world-space position bounds, and, for bodies with contacts, how many frames have a contact. It also lists episodes and the polyline count. Works on gzip-compressed files automatically, and does not require the authoring extra.

Reading state values (simview sample)

To print the actual numbers at a few frames (world-space position and wxyz orientation of every body, any stored velocity/angular velocity/force/torque, the contact count, and every scalar) without opening the viewer:

simview sample scene.json                         # 11 evenly spaced frames, first and last included
simview sample scene.json --t 0.5 --t 1.0         # the frames nearest these times
simview sample scene.json --frames 0:2000:200     # a Python slice of frame indices ('-1' = last)
simview sample scene.json --body box --batch GT   # one body, one batch (by name or index; --batch repeats)
simview sample scene.json --json                  # single-line JSON; --csv: one row per (frame, body, batch)

Poses go through the same parent-chain resolution as simview diff, so a wheel reports where it is in the world, not its offset from the chassis.

Querying terrain data

To read raw numeric terrain values (height, plus any named property the terrain was authored with) at a single point or over an area, without opening the viewer:

simview terrain scene.json --point 1.5 -2.0        # bilinear-interpolated value(s) at (x, y)
simview terrain scene.json --area                  # whole terrain extent
simview terrain scene.json --area -5 5 -5 0        # xmin xmax ymin ymax sub-box
simview terrain scene.json --area --json           # machine-readable JSON (for scripts/agents)
simview terrain scene.json --area --csv            # CSV, one row per layer
simview terrain scene.json --area --mask "known>0.5"  # statistics over cells passing a test only
simview terrain scene.json --area --full --csv     # every grid cell (x, y, layers...)

An --area query prints the sampled extent and per-layer statistics (count, non-finite count, min, 5th/50th/95th percentile, mean, max, std). Add --full for the grid itself — the whole grid of a large terrain is megabytes of numbers. --mask 'LAYER OP VALUE' (OP one of > >= < <= == !=) restricts the statistics, not the grid, to the cells where that layer passes, e.g. only the cells a mapping run actually observed.

Add --layer NAME to restrict to one layer: height, all (the default — every layer present), or any property the terrain was authored with (e.g. friction, stiffness, or a custom name). Add --batch N to pick a batch (only matters when the terrain isn't a singleton), and --stride N to subsample an --area query. --batch (and --batches below) takes a batch name too (--batch post-adaptation). Like simview info, this works on gzip-compressed files and doesn't require the authoring extra.

Pass --batches A B instead of --batch N to compare two batches directly (e.g. a ground-truth terrain vs. a recovered friction/stiffness estimate) instead of querying a single one — each layer then reports both batches' statistics and statistics of their delta (b - a: mean, RMSE, mean/p95/max of the absolute delta). A --mask is evaluated on batch A. With --full, each layer also carries the value_a, value_b and delta grids:

simview terrain scene.json --area --batches GT pred --json   # ground truth vs. estimate, whole extent

Sampling terrain along a body's trajectory

Pass --along-body BODY instead of --point/--area to sample terrain value(s) at each state's (x, y) position of a given body, instead of a fixed point or area — useful for checking what terrain properties a robot actually drove over. For example, a DRIFT-style scene with batches [GT, baseline, pre-adaptation, post-adaptation], each batch with its own terrain and its own recorded trajectory of a body named box:

simview terrain scene.json --along-body box --batch 3 --json   # friction/stiffness under box's path, post-adaptation batch

Add --batches A B to compare two batches instead: both batches' terrains are sampled along batch A's trajectory (batch A is the reference path, typically ground truth), so the reported delta reflects a difference in terrain properties under the path, not the two batches' trajectories diverging from each other — use simview diff to measure that separately.

simview terrain scene.json --along-body box --batches 0 3 --json   # GT terrain vs. post-adaptation terrain, sampled along GT's path

BODY is matched the same way as simview diff's --body (full label, or any single name inside a rigidly-grouped body). Add --every N to subsample frames, and --t0/--t1 SECONDS to keep only part of the run.

Comparing two batches' trajectories

To check how far apart two batches' trajectories are — e.g. ground truth vs. a model's prediction, or baseline vs. post-adaptation — without opening the viewer:

simview diff scene.json --batches 0 1                 # every body, human-readable text
simview diff scene.json --batches 0 1 --json           # machine-readable JSON (for scripts/agents)
simview diff scene.json --batches 0 1 --csv            # CSV, one row per (body, frame)
simview diff scene.json --batches 0 1 --body Box       # restrict to one body
simview diff scene.json                                # a 2-batch scene needs no --batches
simview diff scene.json --batches GT post-adaptation   # batches by name
simview diff gt.json pred.json                         # two single-batch files
simview diff run_a.json#GT run_b.json#1                # picked batches of two files

Several inputs (or one with a #<batches> selection) are merged first exactly as simview view merges them: later files are matched to the first file's timeline by nearest time.

For each body, this reports per-frame position error (meters) and orientation error (degrees, quaternion angular distance) between the two batches, plus mean/max/final summaries. Add --per-axis to also report the signed per-axis components (err_x/err_y/err_z = batch A minus batch B), matching the viewer's Error Metrics "Per-axis" toggle. Add --every N to subsample frames, --t0/--t1 SECONDS to compare only a time window, and --pos-threshold METERS/--rot-threshold-deg DEGREES to report the first frame where a batch's trajectory diverges past a given tolerance. --align-first-frame expresses each batch's trajectory relative to its own pose at the first compared frame (R0⁻¹(p − p0), R0⁻¹R), so two rollouts that start from differently placed poses are compared by their motion only. Like simview info/simview terrain, this works on gzip-compressed files and doesn't require the authoring extra.

Poses are compared in world space. A parented body's stored transform is relative to its parent, so simview diff resolves the parent chain first — the same thing the viewer's Error Metrics panel does, so the two report the same numbers for the same scene. This also means rigidly-attached bodies (a constant localTransform, never written into the states) can be diffed: name them with --body like any other body.

Add --fail-on-exceed to make simview diff machine-checkable in a script or CI job: it requires at least one of --pos-threshold/--rot-threshold-deg, and exits non-zero if any diffed body's trajectory exceeds it (after printing the normal output, so you still get the report either way). A frame whose pose is NaN in either batch (e.g. a body never initialised there) has a NaN error: the summary's mean/min/max skip such frames (nan_count says how many) and the frame counts as exceeding any threshold:

simview diff scene.json --batches 0 1 --pos-threshold 0.1 --fail-on-exceed
echo $?   # 0 = within threshold, 1 = usage/parse error, 2 = threshold exceeded
Exit code Meaning
0 Every diffed body stayed within the given threshold(s).
1 Usage or parse error (bad arguments, unreadable file, etc.).
2 At least one diffed body's trajectory exceeded a threshold.

Visualization of exported simulations

To visualize a simulation defined in a JSON file, run the following command, replacing [path_to_json_file] with the actual path to your JSON data:

simview [path_to_json_file]

This is the simview view subcommand, which is the default when the first argument isn't another subcommand. Gzip-compressed files (e.g. scene.json.gz) are detected automatically and decompressed transparently — no separate flag needed.

Useful flags:

simview scene.json --host 0.0.0.0 --port 8080  # bind to a specific host/port
simview scene.json --no-browser                # don't auto-open a browser tab
simview --version                               # print the installed version

Note that the server has no authentication: binding to a non-loopback host (as above) exposes the scene data to everyone who can reach that address, which the server logs a warning about at startup. Do it only on networks you trust. Other machines must open the viewer by address (e.g. http://10.0.0.5:8080), not by hostname: requests naming any host other than localhost or an IP address are refused, which blocks DNS-rebinding attacks from web pages.

Opening a file on a remote host

Anywhere the CLI takes an input file, you can give an scp-style host:path spec instead of a local path, and SimView will fetch it over SSH for you:

simview rci:~/projects/DRIFT/results/comparison.json

host is passed straight to ssh, so ~/.ssh/config aliases, ProxyJump, agent keys and password prompts all work exactly as they do for ssh and scp. Nothing needs to be installed on the remote — just a shell, and gzip if you want the transfer compressed. user@host:path, [::1]:path and ssh://host/path are accepted too.

This works for every input, not just the viewer, including mixing local and remote files in one merged scene:

simview info rci:~/results/comparison.json
simview diff rci:~/results/comparison.json --batches 0 1
simview local_rerun.json rci:~/results/real_world.json   # merged into one scene

Transfers are compressed. A plain-JSON scene is gzipped on the remote before it goes over the wire and unpacked on arrival, which typically cuts the transfer by 5-10x on scene data this redundant; a file that's already *.gz is streamed as-is. The saving is reported when the fetch finishes:

Fetching rci:~/results/comparison.json (312.4MB)...
Fetched 41.2MB over the wire (312.4MB on disk, 7.6x smaller). Cached at ...

Files are cached and only re-fetched when they change. The cached copy is byte-identical to the remote file and carries its modification time, so each run costs one cheap ssh stat call to check:

Using cached copy of rci:~/results/comparison.json (remote unchanged).

Useful flags:

simview rci:~/results/scene.json --refresh   # re-fetch even if the cache is current
simview rci:~/results/scene.json --offline   # use the cache, don't contact the host
simview clear                                # drop the cache entirely

Remote specs are for inputs only — --output and --save-merged must be local paths. A local file whose name happens to contain a colon always wins over the remote reading, so simview weird:name.json still opens the file sitting next to you.

Vendoring the viewer for a static site (simview static-viewer)

To host the viewer as plain files (GitHub Pages, any static file server) next to scenes written with scene.save_static(), copy the page and its assets once:

simview static-viewer site/simview      # writes site/simview/index.html + static/

Then open site/simview/index.html#data=../scenes/run42 — data= is resolved relative to the page, so bundles can live anywhere on the site. See Embedding for the hash keys (ui=0, play=1, t=...).

Headless rendering (simview render)

To save a single PNG screenshot of a scene without opening a browser — e.g. from a SLURM job or CI runner with no display — use simview render:

simview render scene.json --output frame.png

This starts the server in the background, drives a headless Chromium browser via Playwright, waits for the scene to load, and saves one screenshot. It requires the optional render extra plus a one-time browser download:

pip install "simview[render]"   # or: uv sync --extra render
playwright install chromium

Useful flags:

simview render scene.json --output frame.png --width 1920 --height 1080  # custom resolution (default 1280x720)
simview render scene.json --output frame.png --view <hash>                # restore a "Copy view link" hash first
simview render scene.json --output frame.png --host 0.0.0.0 --port 8080   # bind the background server explicitly

--view accepts the hash produced by the viewer's "Copy view link" button (with or without the leading #), so a screenshot can reproduce a specific camera angle, playback time, focused batch, and visualization toggles instead of the viewer's default startup state.

Without a hash, these shortcut flags build one (they can't be combined with --view):

simview render scene.json --output top.png --camera top --terrain-color friction  # straight down on the whole terrain
simview render scene.json --output f.png --t 0.5 --t 1.0 --t 1.5 --track box     # f_t0.5.png, f_t1.png, f_t1.5.png
simview render scene.json --output f.png --batch post-adaptation                 # focus one batch

Several --t values are captured in one browser session, so each extra frame costs about half a second instead of a full page load.

All commands exit quietly when their output is cut short by a pipe (e.g. simview terrain ... | head).

Comparing multiple runs (e.g. real-world vs. simulated)

Pass multiple JSON files to merge them into a single scene, each file's batches appended as extra batches in the viewer:

simview real_world.json simulated.json

The files must describe the same physical setup (identical bodies and terrain grid) — that's what makes the batches comparable. They don't need to share a timeline: the first file's timestamps become the merged timeline, and every other file is resampled onto it by nearest timestamp (no interpolation), so put the recording you care most about matching frame-for-frame first. See Visualization Controls for a way to quantify the difference between two merged batches.

Each merged file's batches are auto-named after its filename (e.g. real_world, simulated), shown in the Batch Legend. You can rename them from there — renames are saved next to the input file(s) and reloaded automatically the next time you open the same file(s). You can also set initial batch names yourself by including a batchNames array directly in the JSON's model object (see JSON Format Specification); renames from the UI take precedence over this once saved.

Merging only some of a file's batches

If several files each carry their own copy of the same ground-truth batch, merging them whole duplicates that ground truth once per file. Append #<batches> to a file to contribute only some of its batches:

simview gt.json method_a.json#1 method_b.json#1

That merges every batch of gt.json plus batch 1 of each method file — three batches, one ground truth. The selector accepts, comma-separated:

Selector Takes
#1 batch 1
#0,2 batches 0 and 2, in that order
#1-3 batches 1, 2 and 3 (inclusive range)
#-1 the last batch
#ours the batch named ours in that file's batchNames

Selected batches keep their source index in the merged batch names (batch 2 of run.json is run[2]), so a merged scene stays traceable back to what it was built from. Selecting a batch twice, or one that doesn't exist, is an error rather than a silent surprise. A file literally named odd#name.json still opens as itself — an existing local file always wins over the selector reading, exactly as it does for remote host:path specs.

A selection also works with a single file, when you want to view just some of its batches:

simview run.json#0,2

To merge files without launching the viewer, e.g. to inspect or re-share the merged scene, pass --save-merged:

simview real_world.json simulated.json --save-merged combined.json.gz
simview gt.json method_a.json#1 --save-merged combined.json.gz   # with a batch selection

The output is gzipped if the path ends in .gz.