Embedding <Visualizer />
Common use cases: embedding a snapshot viewer next to a robot’s data, rendering test fixtures in a custom dashboard, or shipping the visualizer in a larger debugging surface.
Getting started
Section titled “Getting started”Prerequisites
Section titled “Prerequisites”A Svelte 5 / SvelteKit (or Astro+Svelte, Vite+Svelte, etc.) project. The package is svelte-typed and built for runes; you cannot consume it from Svelte 4.
Install
Section titled “Install”The package declares a list of peerDependencies (Svelte, Threlte, Three.js, the Viam SDK, prime-core, several Zag.js components, etc.). pnpm will print warnings for any that aren’t already in your project — install whatever it asks for.
Peers that only a plugin needs are marked optional, so you install them only if you mount that plugin: @dimforge/rapier3d-compat and @threlte/rapier for MoveFrame, @zag-js/tree-view and svelte-virtuallists for <WorldTree />, @zag-js/tabs for <Settings />, @threlte/xr for <XR />, earcut for <SelectionTool />, and the Connect RPC client for <DrawService />.
Configure your bundler
Section titled “Configure your bundler”The visualizer’s scene imports two things a stock Vite config won’t handle: an HDR
environment map and GLSL shader sources. Both need a line in your config, or the build fails
parsing them as JavaScript (Unexpected identifier 'highp' is the shader one).
vite-plugin-glsl is a build-time dependency of your app — install it alongside the package.
Disable SSR for the page
Section titled “Disable SSR for the page”Threlte renders WebGL, which needs the browser. Disable SSR on any page that mounts <Visualizer />. In SvelteKit:
Mounting <Visualizer />
Section titled “Mounting <Visualizer />”<Visualizer /> is the entire visualizer rendered as a single Svelte component. It expects an explicitly-sized parent because it stretches to fill its container:
That’s enough to get an empty scene: the camera and its controls, the grid, the entity renderer, and the portal targets plugins render into. From there, you can:
- Pass
partIDto bind it to a specific Viam machine part. - Pass
cameraPoseto set an initial camera position and look-at target. - Provide a
childrensnippet to render your own Threlte primitives or the<Snapshot />component inside the scene.
What’s in the box, and what isn’t
Section titled “What’s in the box, and what isn’t”<Visualizer /> is the scene, not the app. It renders:
- the
<Canvas>, inrenderMode="on-demand" - the camera, camera controls, and the reset / orthographic buttons
- the grid, lighting, and environment
- the entity renderer — everything spawned into the ECS world, including
<PCD /> - selection and hover affordances
- the portal targets that plugins render their UI into
Everything else is a plugin you opt into — including the workspace modes: with no mode plugin mounted the mode is none and the visualizer is a bare renderer, with no toggle, no details cards, and no live-query pausing. The visualizer’s own chrome ships as six plugins:
| Plugin | Adds |
|---|---|
<Monitor /> | Monitor mode: the mode button and details cards |
<WorldTree /> | The scene hierarchy panel |
<Settings /> | The settings cog and its tabbed popover |
<BuildFrames /> | Build mode: gizmos, snapping, frame editing |
<FileDrop /> | Drag-and-drop loading of snapshots, PCD and PLY |
<FramePov /> | Per-frame point-of-view panels |
To reproduce the standalone app, mount all six plus the feature plugins you want, keeping
Monitor before BuildFrames before MoveFrame — mode plugins’ mount order sets both the
toggle’s button order and the fallback priority. To embed a focused viewer, mount none of them —
or just the one or two that earn their space.
A focused point-cloud viewer
Section titled “A focused point-cloud viewer”The smallest useful embed: a scene, a point cloud you already have the bytes for, and a ruler.
<PCD /> parses in a worker and spawns an entity the visualizer’s renderer picks up, so nothing
else is needed — no partID, and no network calls of its own.
Rebinding data reparses and respawns the entity, so this works for a polling feed as well as a
one-shot capture.
Passing props and children
Section titled “Passing props and children”Rendering snapshots
Section titled “Rendering snapshots”A snapshot is a serialized scene — every transform, drawing, and camera setting captured into one protobuf payload. Snapshots render purely client-side; no draw server, no live machine, no network calls.
Use the <Snapshot /> component as a child of <Visualizer />:
Put the snapshot in your app’s public/ (or static/) directory and fetch it at runtime. This is the pattern the playground uses.
Fetch and decode at runtime:
JSON snapshots are bigger but easier to debug:
You can re-bind a different snapshot at any time — <Snapshot /> watches the prop and respawns the scene entities when the value changes.
The playground renders this snapshot by default. To experiment with it, download visualization_snapshot.json, edit a few values, and drag the modified file onto the playground — the scene re-renders with your changes. The drop zone accepts .json, .pb, and .pb.gz snapshot files, but they must be named with a visualization_snapshot prefix (see src/lib/plugins/FileDrop/file-names.ts) for the loader to recognize them.
Producing snapshots
Section titled “Producing snapshots”Snapshots are produced from Go using the draw package. The most common pattern: build up a *draw.Snapshot, then call MarshalBinary() (or MarshalJSON() for human-readable output) and ship the bytes wherever your frontend can fetch them.
A minimal producer:
See the draw API reference for the full set of Snapshot.Draw* methods.
Using only the standalone components
Section titled “Using only the standalone components”A few components are exported via the /lib subpath and work without a <Visualizer /> parent — useful if you just need a piece of the visualizer (e.g. an axes helper) inside your own Threlte canvas:
<Snapshot /> is also exported from /lib, but unlike <AxesHelper /> it requires the context that <Visualizer /> provides — use it as a child of <Visualizer />, not standalone.