Task-focused snippets. All use the shared <GcodePreview> surface unless noted.
Clip to a layer range and scrub within it — no geometry rebuilds, just draw-range updates:
<GcodePreview source={file} layerRange={[0, 73]} scrub={0.5} />
<GcodePreview source={file} quality="tubes" colorMode="feature" cameraMode="orthographic" />
colorMode is capability-gated — the stack colors by a feature only when the dialect actually
disclosed it (see ToolpathIR & the capability model).
The imperative controls handle (the composable return / React ref / Svelte bind:this / element
instance) exposes preset orientations and a serializable camera snapshot:
const { controls } = preview; // or handleRef.current, etc.
controls.setView('iso'); // 'top' | 'bottom' | 'front' | 'back' | 'left' | 'right' | 'iso'
controls.frame(); // re-fit to the model bounds
controls.setCameraMode('orthographic'); // perspective ↔ ortho
// Persist "where the user was looking" and restore it later (e.g. a dashboard).
const view = controls.getCameraState(); // { position, target, zoom, cameraMode } | null
localStorage.setItem('view', JSON.stringify(view));
controls.setCameraState(view); // restores verbatim — no re-fit to the current model
setView snaps instantly (no animation) and preserves the active projection. getCameraState
returns null on the low-resource 2D renderer, which has no 3D pose — setView/setCameraState
there disclose via the renderer-unsupported event rather than fabricating one.
The same three are also declarative props on every adapter — a view prop (preset) and a
cameraState prop (restore) — paired with a camerachange/camera-change event that fires with
the new CameraState after a user orbits/pans/zooms. Together they form a two-way binding you can
persist:
<GcodePreview {source} {view} bind:cameraState on:camerachange={(e) => save(e.detail)} />
readyThe ready event (React onReady, Vue @ready, Svelte on:ready, element ready) carries
capabilities (the per-field known | inferred | approximated | unavailable map) and warnings
alongside { segments, layers, complete } — so a consumer can gate its own UI (e.g. only offer a
color-by-power control when capabilities.toolPower !== 'unavailable') without reaching for the raw
handle.
.gcode.3mf multi-plate.gcode.3mf containers can hold several sliced plates. Select one with parseOptions.plate:
<GcodePreview source={file} parseOptions={{ plate: 1 }} />
parseOptions.plate selects the plate at parse time. To switch plates after the first render,
start a fresh parse rather than only changing parseOptions — assign a new source, or call the
imperative controls.parse(bytes, { plate: 1 }). When the source is a Uint8Array, parsing
transfers (empties) its buffer, so re-parsing the same bytes needs a fresh copy — pass
bytes.slice(). (Re-parsing a now-empty buffer fails cleanly with E_POST_FAILED; it no longer
wedges the preview.)
A Bambu/Orca project 3MF can declare several plates. The model viewer reports them on ready
(info.plates.list, each with an id, optional name, and counts), and a render scope draws one plate alone.
Offer a selector only when the source declares plates:
import { useState } from 'react';
import { ModelViewer } from '@chestnutlabs/gcode-preview-react/model';
function PlateViewer({ source }) {
const [plates, setPlates] = useState([]);
const [plateId, setPlateId] = useState(null); // null = every plate
return (
<>
{plates.length > 0 && (
<select value={plateId ?? 'all'} onChange={(e) => setPlateId(e.target.value === 'all' ? null : Number(e.target.value))}>
<option value="all">All plates</option>
{plates.map((p) => (
<option key={p.id} value={p.id}>{p.name ?? `Plate ${p.id}`}</option>
))}
</select>
)}
<ModelViewer
source={source}
buildVolume={{ x: 256, y: 256, z: 256 }}
renderScope={plateId === null ? null : { plateId }}
onReady={(info) => setPlates(info.plates?.list ?? [])}
/>
</>
);
}
The same scope is controls.setRenderScope({ plateId }) on any adapter, and renderScope on renderModelStill.
Other scopes are { objectIds } and { instanceFilter }. Three things to know:
info.plates; { plateId } on such a source matches nothing and renders an empty scene, so gate the selector on info.plates.buildVolume, each selection also reports its own placement (for a project made for your bed, i.e. its printable area matches buildVolume: known for plate 1, inferred for a later plate whose parts sit one workspace cell over; otherwise approximated, centred on your plate); with every plate in scope there is no single bed, so placement is null and no plate is drawn. See Studio look and build plate.The Feature Lab's Prepare mode has this selector, on a synthetic two-plate project.
By default the adapters create a batteries worker (every dialect adapter + .gcode.3mf) via the
bundler-native new Worker(new URL(...)) pattern. Pass createWorker to supply your own — a slim
build, custom dialects, or a CSP-friendly setup:
<GcodePreview source={file} createWorker={() => new Worker(new URL('./my-worker.js', import.meta.url), { type: 'module' })} />
Feed a normalized ProgressObservation and the preview maps it onto the toolpath with honest,
tiered confidence — a precise marker when the source position is known, an uncertainty band when it
is approximate, and user scrub always winning. See
Live progress & motion model and the
progress signal contract.
<GcodePreview source={file} progress={observation} />
renderStill from @chestnutlabs/gcode-preview-core produces a single non-interactive image from
a Worker OffscreenCanvas, an Electron hidden window, or headless Chromium — no interactive viewer
required. See the
still-render reference.
Mount multiple <GcodePreview> instances — each owns its own worker and renderer. See the
multi-gcode note
for the current guidance and its trade-offs.