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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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.