npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@chestnutlabs/gcode-renderer-three

v0.24.0

Published

Three.js toolpath renderer consuming ToolpathIR (DD-004). Phases 1-2: geometry builders + scene/lifecycle.

Readme

@chestnutlabs/gcode-renderer-three

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.

3DBenchy rendered as tubes with feature coloring

  • Layer-chunked line geometry with decimation + disclosure; tube geometry with automatic quality fallback
  • Layer-range clipping and segment-level scrub via draw ranges (no rebuilds)
  • Per-file build plates (setBuildVolume, mismatch events, consumer-wins precedence)
  • DD-006 live-progress overlay: completed cut + translucent remaining-path ghost + always-on-top marker (byte-exact) or uncertainty band (approximate); user scrub always wins
  • Opt-in retraction/deretraction markers (setShowRetractions) — warm = retract, cool = unretract, clipped by the same layer/scrub window; capability-honest (shown only when the IR carries events)

Retraction (orange) and unretraction (cyan) markers on object boundaries

  • Time-budgeted build ticks (rAF with a timeout backstop — works when rAF is suspended or absent), WebGL context-loss rebuild, injectable GL backend and frame scheduler (testable & headless-shaped)
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

Studio look (opt-in)

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, and so does lensScale(from, to). See the Studio look and build plate guide.

Theme values that throw

A theme value the renderer can't draw throws a RangeError that names the field, from the theme option and from setTheme(). Before, it drew something else without telling you. The defaults and both studioTheme() presets pass.

| Field | Accepted | | --- | --- | | hemisphereIntensity, directionalIntensity | a finite number >= 0 | | materialPreset | 'matte' or 'glossy' | | bedSurface.mode | 'none' or 'solid' (required when you pass bedSurface) | | bedSurface.opacity | a number in [0, 1] | | bedSurface.thickness | a finite number >= 0 | | grid, major-grid, outline and contact-shadow opacities | a number in [0, 1] | | gridMajor.every | a finite number above 0 |

A misspelled materialPreset or bedSurface.mode used to fall back to matte, or to no plate. It now throws, and the message lists the accepted values. If your theme comes from user settings or untyped JSON, check these two fields first. createContactShadow(footprint, opacity) throws for an opacity outside [0, 1]. Colour strings are not checked yet.

The model viewer (@chestnutlabs/gcode-model-renderer) checks the same theme the same way.

Pixel ratio (opt-in)

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.

Bad coordinates and bad build volumes

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, and the renderer's buildComplete event carries the count. GcodePreview and the framework adapters report it in their disclosure text, for example 4 segments with a non-finite coordinate were not drawn. 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. The tube and budget options are checked the same way, and undefined keeps the default: tube.radialSegments is a whole number from 3 to 64, tube.lineWidth and tube.lineHeight are finite and above 0, tube.maxVertices is a whole number of at least 1, tubeByteBudget and geometryMemoryBudgetBytes are finite and above 0, geometryConcurrency is 'auto', 'off' or a whole number of at least 1, and qualityMode (and setQualityMode()) takes 'full', 'adaptive' or 'fast'.

Tighter limits on software GL

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.

The three-free /lite entry

@chestnutlabs/gcode-renderer-three/lite holds the pieces that never load three.js: CaptureUnsupportedError, studioTheme, STUDIO_CAMERA_FOV, the option checks below, and the theme, camera and capture types. You normally don't need it: the main entry exports the same things. It exists for packages that load the renderer on demand, like @chestnutlabs/gcode-preview-core, so a 2D-only app loads no three.js. If the main entry could not load (offline, or a stale deploy) or could not start (no WebGL), core reports E_RENDERER_LOAD on its error event, with reason: 'load' or reason: 'start'; for 'start' (usually no WebGL), use renderer: { mode: '2d' }. Tests that wait a fixed number of ticks for the 3D renderer to bind can preload it once: await import('@chestnutlabs/gcode-renderer-three').

The option checks are the renderer's own rules, so you can check a value before any renderer exists (a setting a user typed, for example). Each throws RangeError with the same message the renderer throws:

| Check | Accepts | | --- | --- | | checkTheme(theme) | see theme values that throw | | checkBuildVolume(def) | a volume the grid can draw: finite sizes, at most MAX_GRID_LINES_PER_AXIS (10 000) lines per axis | | checkCameraMode(mode) | CAMERA_MODES: 'perspective', 'orthographic' | | checkCameraView(view) | CAMERA_VIEWS: 'top', 'bottom', 'front', 'back', 'left', 'right', 'iso' | | checkCameraState(state) | finite position / target, zoom > 0, a valid cameraMode | | checkCameraFov(fov) | 10 to 120 degrees | | checkPixelRatio(ratio) | 'device', or a number in (0, 4] | | checkQualityMode(mode) | QUALITY_POLICIES: 'full', 'adaptive', 'fast' | | checkProgressivePreview(mode) | PROGRESSIVE_PREVIEW_MODES: 'auto', 'lines', 'hold', 'off' | | checkFrameContent(mode) | FRAME_CONTENT_MODES: 'object', 'all' | | checkInteractionQuality(mode) | INTERACTION_QUALITY_MODES: 'off', 'auto' | | checkRendererMode(mode) | RENDERER_MODES: '2d', '3d' |

The upper-case lists are readonly arrays, so you can filter input without a try/catch: CAMERA_MODES.includes(value).

Part of Chestnut Labs G-code Preview · MIT