Three.js toolpath renderer consuming ToolpathIR for the Chestnut Labs
G-code Preview stack (design: DD-004, live-progress overlay: DD-006).
Peer dependency:
three(supported range^0.178.0) — install it alongside this package.

setBuildVolume, mismatch events, consumer-wins precedence)setShowRetractions) — warm = retract, cool = unretract,
clipped by the same layer/scrub window; capability-honest (shown only when the IR carries events)
import { ToolpathRenderer } from '@chestnutlabs/gcode-renderer-three';
const renderer = new ToolpathRenderer({ canvas });
renderer.setIR(ir); // builds progressively; 'buildComplete' event when done
renderer.setLayerRange(0, 42);
renderer.setProgress(mapped); // DD-006 MappedProgress overlay
studioTheme('dark' | 'light') returns a Theme (gradient backdrop, solid build plate with grid and outline,
contact shadow) and STUDIO_CAMERA_FOV is 32. Both are optional: with no theme the renderer is unchanged.
import { ToolpathRenderer, studioTheme, STUDIO_CAMERA_FOV } from '@chestnutlabs/gcode-renderer-three';
const renderer = new ToolpathRenderer({ canvas, theme: studioTheme('dark'), cameraFov: STUDIO_CAMERA_FOV });
renderer.setBuildVolumeCage(false); // the preset is measured with the cage off
cameraFov accepts 10-120 degrees and throws RangeError outside it. See the
Studio look and build plate guide.
ToolpathRenderer (and createModelViewer) take pixelRatio: a number in (0, 4], or 'device' for
min(devicePixelRatio, 2), followed live. renderer.setPixelRatio(ratio) changes it later. Anything else throws
RangeError. Unset, the renderer keeps its own ratio and never calls the GL's setPixelRatio at rest. interactionQuality
lowers the ratio while you orbit and settles back on yours. The type is PixelRatioOption.
const renderer = new ToolpathRenderer({ canvas, pixelRatio: 'device' });
An unsized capture() is the resting drawing-buffer size when unset and the canvas's CSS size when set, so with the
built-in renderer setting a ratio never changes the default capture size (a custom createRenderer already at
another ratio returns its own drawing-buffer size when unset); pass width/height for a crisper image. Ratio 2 shades four times the
fragments of ratio 1.
A segment with an Infinity or NaN endpoint coordinate is not drawn. The camera frame still comes from the IR's bounds, so with the default frameContent: 'all' the
camera frames the build volume (or the preview bounds) instead of the remaining toolpath; under 'object' it
tries the model, then the object bounds, then the build volume. The 2D renderer skips on x/y only.
buildChunks counts skipped segments in skippedNonFinite; core and the framework adapters do not surface that
count yet (tracked with #518). createBuildVolume and
setBuildVolume throw RangeError for a volume that cannot be drawn: a non-finite size, origin or height, a grid
pitch that is not a positive number, more than 10 000 grid lines on one axis, or an origin so large that the pitch
no longer advances. A bed discovered in the file (a MachineGeometry) never throws: setBuildVolume keeps the
current volume and emits an error event with code machine-geometry-invalid. A decimation option must be a
whole number of at least 1, or 'auto'; anything else throws RangeError.
On headless or software-rendered GL (CI, a VM, a remote desktop) a large file can exhaust memory long before it is
slow. Nothing in the renderer adapts to this for you yet (resolveCapability and capabilityHint are
experimental and diagnostic-only), so read the context yourself and pick tighter limits:
import { detectRenderCapability } from '@chestnutlabs/gcode-renderer-three';
const gl = document.createElement('canvas').getContext('webgl2'); // a scratch canvas, not the one you render to
const { capability } = gl ? detectRenderCapability(gl) : { capability: 'unknown' };
const constrained = capability !== 'hardware'; // 'unknown' (extension blocked) is treated as constrained
// pass to the parse step (adapters: `parseOptions`) and, on a direct renderer, to `tubeByteBudget`
const parseOptions = constrained ? { limits: { maxSegments: 2_000_000 } } : {};
const tubeByteBudget = constrained ? 100_000_000 : undefined;
detectRenderCapability never throws; it returns { capability: 'unknown', rawRenderer: null } when the browser hides the
renderer string. The two numbers above are starting points to tune for your hardware, not recommendations
from measurement.
Most applications should consume this through @chestnutlabs/gcode-preview-core
or a framework adapter, which own the parse→render lifecycle.
Part of Chestnut Labs G-code Preview · MIT