Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
## Documentation
- [ ] User- and maintainer-facing docs updated in this PR (docs are part of the feature)
- [ ] Support matrix / migration notes updated, or N/A
- [ ] **Release promotion (`dev` → `main`) only:** `npm run docs:release-check` is green, the
`docs/README` current-state narrative + history name this version, `RELEASE_NOTES_DRAFT.md`
is folded in and deleted, and screenshots/guides were reviewed (CLAUDE.md public-docs
completion check) — or N/A for a normal `dev` PR.
- [ ] **Release promotion (`dev` → `main`) only:** `npm run docs:release-check` is green — the
`docs/README` current-state narrative + history name this version, `RELEASE_NOTES_DRAFT.md` is
folded in and deleted, and the enforced **`RELEASE_REVIEW.md`** (Public Product + Docs + Visual
review) is present for this version with every disposition resolved (each changed package
`reviewed` / `no-change-needed` / `not-applicable`; Product / Docs / Visual markers `resolved`).
The gate blocks promotion until it is — or N/A for a normal `dev` PR.

## Impact
- Security: <untrusted-input / limits impact, or none>
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/docs-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ on:
- 'packages/**'
- 'typedoc.json'
- 'tools/docs/**'
- 'docs/manual/**'
- 'docs/media/**'
- '.github/workflows/docs-pages.yml'
workflow_dispatch:

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-publish.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# DD-008 D3 (#130): the ONLY path that publishes @chestnutlabs/* — a GitHub Release
# (tag vX.Y.Z) cut from `main`. Fresh install, every repository gate, then a
# dependency-ordered `npm publish` ×9 with npm provenance. Trusted publishing (OIDC)
# dependency-ordered `npm publish` ×14 with npm provenance. Trusted publishing (OIDC)
# is the intended auth; a granular NPM_TOKEN secret is the recorded fallback
# (DD-008 §4.3 — the @chestnutlabs scope is user-owned; org conversion deferred).
# No workstation publishing, ever.
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
dist
*.min.css
*.svelte
docs-site
23 changes: 16 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Project instructions for Claude Code and other agents working in this repository

A browser G-code / toolpath toolkit: parse `.gcode`, `.gcode.3mf`, and Prusa `.bgcode` off the main
thread into a neutral intermediate representation (`ToolpathIR`) and render it with Three.js or a
Canvas 2D fallback. It ships as 13 lockstep-versioned `@chestnutlabs/*` packages plus Vue, React,
Canvas 2D fallback. It ships as 14 lockstep-versioned `@chestnutlabs/*` packages plus Vue, React,
Svelte, and Web Component adapters over one shared engine. It began as a fork of
`xyz-tools/gcode-preview` — attribution and provenance are load-bearing (see `NOTICE.md`).

Expand Down Expand Up @@ -48,18 +48,27 @@ deliberate consideration is):

If visible behavior changed, regenerate the affected media with
[`tools/screenshots/`](tools/screenshots/README.md) rather than leaving a stale image in place.
Every user-facing feature is **visually documented somewhere** unless the visual coverage matrix
([`docs/VISUAL_FEATURE_COVERAGE.md`](docs/VISUAL_FEATURE_COVERAGE.md)) records why not — see the
visual standard in [`docs/USER_FACING_DOCS_STYLE.md`](docs/USER_FACING_DOCS_STYLE.md) §6/§8.

**Docs freshness is enforced at release time, not left as post-release cleanup.** The `version`
script auto-stamps the deterministic "vX.Y.Z is on npm" strings into the generated Version PR and
script auto-stamps the deterministic "vX.Y.Z is on npm" strings into the generated Version PR,
drops a `RELEASE_NOTES_DRAFT.md` to seed the `docs/README` "Current state" narrative + history
(fold it in, then delete it). The `Docs release gate` (`npm run docs:release-check`) then **blocks
the `dev` → `main` promotion** until every version surface, the current-state lead, and the
history list name the version being cut. Surfaces live in `tools/release/doc-surfaces.mjs`; the
flow is documented in [`docs/reference/release-process.md`](docs/reference/release-process.md).
(fold it in, then delete it), and generates `RELEASE_REVIEW.md` — a **per-release Public Product +
Documentation + Visual review** seeded from the changed-capability inventory (every package whose
`src/` changed since the previous tag). The `Docs release gate` (`npm run docs:release-check`) then
**blocks the `dev` → `main` promotion** until every version surface names the version being cut,
`RELEASE_NOTES_DRAFT.md` is gone, **and** `RELEASE_REVIEW.md` is present with every disposition
resolved (each changed package `reviewed` / `no-change-needed` / `not-applicable`, and the Product /
Docs / Visual markers `resolved`). This is enforcement, not a checkbox: reconcile the README, Pages
homepage, feature gallery, manual, demo/examples, screenshots, and the coverage matrix against the
inventory, then resolve the review. Surfaces live in `tools/release/doc-surfaces.mjs`; the flow is
documented in [`docs/reference/release-process.md`](docs/reference/release-process.md).

## Repo orientation

- `packages/*` — the 13 published packages (foundation → parse → color → render → adapters). Build
- `packages/*` — the 14 published packages (foundation → parse → color → render → adapters). Build
in dependency order; each package builds itself (`npm run build -w @chestnutlabs/<pkg>`).
- `docs/` — user manual (`manual/`), consumer references (`reference/`, `compatibility/`), and the
maintainer planning/design/research set.
Expand Down
60 changes: 58 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ live printer progress. Parsing runs in a Web Worker, so a 250 MB file never free
[![docs](https://img.shields.io/badge/docs-manual%20%2B%20API-2ea44f)](https://chestnutlabs.github.io/gcode-preview/)
![node](https://img.shields.io/badge/node-%E2%89%A522-339933)

![3DBenchy rendered as extrusion tubes with per-feature coloring — perimeters, infill, and skirt in distinct colors — on a dark build plate](docs/media/viewer-benchy-tubes.png)
![3DBenchy rendered as extrusion tubes with per-feature coloring — perimeters, infill, and skirt in distinct colors — on a neutral grey build plate](docs/media/viewer-benchy-tubes.png)

---

Expand Down Expand Up @@ -100,6 +100,18 @@ signal deserves**:
giving up final-frame fidelity. The hard GPU/vertex-budget fallback still applies underneath.
- **Degradation is disclosed** — over a large-file threshold the renderer decimates and *tells you*
the exact reduction factor rather than dropping detail silently.
- **Any build surface, not just a rectangle** — rectangular, **circular / delta**, and **polygonal**
beds render with the outline and grid following the real shape (`buildVolume.shape`).
- **Capture / export** — `capture()` returns the current view as a `Blob` (PNG/JPEG/WebP) from the
interactive viewer *or* the headless still, including a **transparent background** for compositing
onto cards — the same primitive behind server thumbnails and "save image".

<table>
<tr>
<td width="50%"><img src="docs/media/bed-circular.png" alt="A print rendered on a round, delta-style build plate — the bed outline and grid are circular, with the model centered on it"></td>
<td><b>Non-rectangular beds.</b> A circular (delta) or polygonal build surface draws its true outline and clips the grid to the shape, instead of forcing a rectangle. Pass a <code>shape</code> on the build volume; rectangular stays the default.</td>
</tr>
</table>

<table>
<tr>
Expand Down Expand Up @@ -132,6 +144,31 @@ a controller's semantic claims are reported `inferred` until confirmed on real h
— today **GRBL/LightBurn laser is hardware-validated**; GRBL-mill and LinuxCNC are experimental.
Geometry always parses regardless of tier.

#### Parametric programs (RS274NGC)

Some G-code doesn't spell out its geometry move by move — it **computes** it. A LinuxCNC / CAM
program can set variables, evaluate expressions as coordinates, loop, branch, and call subroutines,
so a bolt circle is a `while` loop over an angle instead of twelve explicit moves. The parser runs
that programming layer, so these files resolve to the **real toolpath** instead of rendering empty.

![A CNC toolpath of eight drilled holes on a circle plus a centre hole and a square frame, all computed by a parametric program, cut moves in pink and rapid moves in blue](docs/media/parametric-bolt-circle.png)

```gcode
o200 while [#<i> LT #<holes>]
#<a> = [#<i> * 360 / #<holes>]
G0 X[#<cx> + #<r> * COS[#<a>]] Y[#<cy> + #<r> * SIN[#<a>]] ; computed hole position
o100 call ; drill (subroutine)
#<i> = [#<i> + 1]
o200 endwhile
```

Parameters, expressions, `if` / `while` / `do` / `repeat` control flow, and in-file subroutines all
run — **bounded** (a hostile file can't hang the parser: `maxProgramIterations` / `maxCallDepth`) and
**honest** (a clean run reports `parametricProgram: 'known'`; a limit or unsupported construct drops
to `approximated` with a specific warning). It engages only on files that use `#` / `[` / O-words;
every FDM file is byte-for-byte unchanged. See
[Parametric programs](docs/manual/concept-parametric-programs.md).

### Framework integration

Four adapters over one engine, with matching options, events, and TypeScript types — enforced by a
Expand All @@ -148,6 +185,17 @@ shared behavioral test suite that runs against all four in CI:

- **Off-thread parsing** — a `GcodeParseSession` runs the parser in a Web Worker with streaming
input, progressive previews for large files, resource limits, and cancellation.
- **Parallel geometry build** — a geometry worker pool builds extrusion tubes off the main thread
(byte-identical to serial), bounded by a memory budget and degrading gracefully (pool → serial →
lines) with every step disclosed. Big plates stay responsive.
- **Preparation is staged, the reveal is clean** — a `stage` event walks
`parsing → classifying → building-geometry → preparing-gpu → ready` with a real build percentage,
and `progressivePreview: 'hold'` suppresses the half-built scene so the model appears in one clean
reveal instead of visibly growing.
- **Render diagnostics** — `getRenderStats()` reports what actually happened: backend and WebGL
version, hardware-vs-software GPU (unmasked renderer/vendor), geometry mode, rendered segment and
draw-call counts, whether decimation applied, worker count, and a timing breakdown — a ready-made
"what am I running on / why is this slow" panel, never fabricated.
- **Batteries-included worker** — the adapters wire up a worker with every dialect and `.gcode.3mf`
support via the bundler-native `new Worker(new URL(...))` pattern (Vite works out of the box); a
`createWorker` hook is the escape hatch for slim builds, custom dialects, or strict-CSP hosts.
Expand Down Expand Up @@ -201,6 +249,12 @@ multicolor) with camera presets and the same serializable camera state, over the
orbit controls. See the
[model-renderer README](packages/gcode-model-renderer/README.md#interactive-viewer).

**Multi-plate, multi-object, or just a subset.** A 3MF project can hold several plates and many
objects. Parse or render **one plate at a time** (`parseOptions.plate`), and a **render scope**
(`{ plateId }`, `{ objectIds }`, or an instance filter) narrows a still or the viewer to exactly the
plate or objects you want — a single plate's thumbnail, or one part isolated from the rest — with a
stable cache key per scope.

## Quick start

Install an adapter **plus `three`** (the renderer declares `three` as a peer dependency, range
Expand Down Expand Up @@ -319,7 +373,9 @@ contain. Details: [ToolpathIR & the capability model](docs/manual/concept-ir-cap

## Documentation

- **[Manual & getting started](https://chestnutlabs.github.io/gcode-preview/)** and the
- **[Manual & getting started](https://chestnutlabs.github.io/gcode-preview/)**, the
**[visual feature gallery](https://chestnutlabs.github.io/gcode-preview/documents/Feature_gallery.html)**
(what it does, shown rather than described), and the
**[API reference](https://chestnutlabs.github.io/gcode-preview/api/)** (generated from source).
- Concepts: [workers & performance](docs/manual/concept-workers.md) ·
[IR & capabilities](docs/manual/concept-ir-capabilities.md) ·
Expand Down
53 changes: 53 additions & 0 deletions RELEASE_REVIEW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<!--
RELEASE_REVIEW.md — Public Product + Docs + Visual review for the version being cut.
Auto-seeded by tools/release/stamp-release-docs.mjs (inside `npm run version`), then
RESOLVED by hand before promotion. The release gate (`npm run docs:release-check`) blocks
the dev -> main promotion until this file:
* declares the version being cut on the "Review version:" line;
* carries NO `Status: pending` row — every changed package must be one of
reviewed / no-change-needed / not-applicable;
* marks all three global dispositions "resolved":
"Product review: resolved", "Docs review: resolved", "Visual review: resolved".
Keep the greppable tokens ("Review version:", "Status:", "<X> review:") intact — the gate
parses them literally. Delete rows only for packages that did not change.
-->

# Release review — v0.18.0

**Review version:** v0.18.0
**Changed-capability inventory diffed against:** `v0.17.0`

This artifact records the per-release **Public Product + Documentation + Visual** reconciliation:
before the `dev` -> `main` promotion, compare the changed capabilities below against the README,
the Pages homepage, the feature gallery, the manual, the demo and examples, the screenshots, and
the coverage matrix ([`docs/VISUAL_FEATURE_COVERAGE.md`](docs/VISUAL_FEATURE_COVERAGE.md)); then set
each disposition. Guidance: CLAUDE.md "Public-docs completion check" and
[`docs/reference/release-process.md`](docs/reference/release-process.md).

> **This review (documentation-systematization pass).** v0.18.0 was already published to npm; this
> pass reconciled its public surface retroactively and stood up the enforcement machinery. The two
> shipped feature arcs — DD-017 RS274NGC parametric programs (parser) and DD-030 renderer/viewer
> interop (non-rectangular bed, per-plate render scope, `capture() → Blob`) — are covered by the
> manual (`concept-parametric-programs.md`, `concept-ir-capabilities.md`, `adapters.md`) and the
> package CHANGELOGs. No user-facing surface misrepresents the shipped build.

## Changed packages (`src/` since the previous release)

| Package | Changed src files | Changelog summary | Disposition |
|---|--:|---|---|
| `@chestnutlabs/gcode-model-renderer` | 5 | feat(renderer): interactive view capture() → Blob + per-plate render scope + non-rect bed (DD-030) | Status: reviewed |
| `@chestnutlabs/gcode-parser` | 6 | feat(parser): RS274NGC parameters + expressions + O-word flow + subroutines (DD-017 P1–3) | Status: reviewed |
| `@chestnutlabs/gcode-preview-core` | 3 | feat: capture()/render-scope plumbed through GcodePreviewControls + staged progress (DD-030) | Status: reviewed |
| `@chestnutlabs/gcode-preview-element` | 1 | feat: Web Component inherits imperative capture() (DD-030 D1) | Status: reviewed |
| `@chestnutlabs/gcode-renderer-three` | 8 | feat(renderer): non-rectangular build-bed geometry + frameBounds precedence (DD-030 D3) | Status: reviewed |

Set each **Disposition** to one of `Status: reviewed` / `Status: no-change-needed` /
`Status: not-applicable` (never `Status: pending`).

## Global dispositions

- **Product review:** resolved — README, Pages homepage, and the DD-017/DD-030 capability descriptions accurately describe the v0.18.0 product; no fabricated capability or validation claim.
- **Docs review:** resolved — the manual (parametric-programs + IR-capabilities + adapters), package READMEs, and quick-start match the shipped API; the parametric-programs concept page is now registered in Pages nav and link-checked.
- **Visual review:** resolved — DD-017 is a parser/semantic capability (no new on-screen surface beyond parsed geometry, disclosed via capabilities); DD-030's non-rect bed / per-plate scope / capture map to existing renderer imagery, tracked in `docs/VISUAL_FEATURE_COVERAGE.md`. No changed capability is left visually undocumented without a matrix entry.

_Resolve each marker above by replacing `pending` with `resolved` once reconciled._
36 changes: 35 additions & 1 deletion docs/USER_FACING_DOCS_STYLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,35 @@ confident, visual, technically credible, and human**.

## 6. Visuals

Screenshots carry the product. Rules:
Screenshots carry the product.

**Default rule:** every user-facing feature is **visually documented somewhere** — unless the
coverage matrix ([`docs/VISUAL_FEATURE_COVERAGE.md`](VISUAL_FEATURE_COVERAGE.md)) records *why not*
(the feature is non-visual, is redundant with an existing shot, or is inherently temporal and
covered by a clip). "We didn't get to it" is not a recorded reason; an explicit matrix entry is.
The per-release Public Product + Docs + **Visual** review (`RELEASE_REVIEW.md`, enforced by
`npm run docs:release-check`) is where this is reconciled before promotion.

**Two visual classes** — know which one you are producing:

- **Showcase** — the hero images that sell the product (README top, Pages homepage). They pick the
most compelling model + framing and demonstrate the product as a whole.
- **Feature-proof** — the minimum image that *proves a specific capability exists and works*
(a color mode, a non-rectangular bed, a clip plane, a capability disclosure). It is judged by the
§8 recognition test, not by how pretty it is. Every feature the matrix tracks needs one, even when
it will never be a hero shot.

Rules:

- **Real software only.** Every image is a render from the current build (see
[`tools/screenshots/`](../tools/screenshots/README.md)). Never mock, composite, or hand-paint a
screenshot, and never show UI the software doesn't have.
- **Documentation capture background.** Feature-proof and grid captures use the canonical
**mid-grey, slicer/CAD-style documentation background** — a named shared capture setting in
[`tools/screenshots/`](../tools/screenshots/README.md), *not* a library default. The neutral grey
keeps toolpath colors, feature coloring, and geometry legible and consistent across a set. It is a
documentation convention only: consumers keep full control of the rendered background, and nothing
here changes the library's own defaults.
- **Put a strong visual near the top** of the root README, above the fold. It should demonstrate
the product, not decorate the page.
- **Consistency:** hold viewport size, framing, crop, sample models, filenames, and UI state steady
Expand Down Expand Up @@ -133,6 +157,16 @@ consideration is):
If a visible behavior changed, regenerate the affected media with the capture harness rather than
leaving a stale image in place.

**The recognition test (for feature-proof visuals).** A feature-proof image passes only if:

> Would a developer who knows the product recognize *this specific feature* from the image alone,
> with the caption removed?

If the capability is not legible without the words underneath it, the shot is not proving anything —
reframe it, pick a model that exercises the feature, or add the on-screen state that makes it
visible. Track the outcome in the coverage matrix ([`docs/VISUAL_FEATURE_COVERAGE.md`](VISUAL_FEATURE_COVERAGE.md));
a feature with no passing image needs either a new capture or a recorded reason it has none.

## 9. Links and legal

- Link to deeper docs instead of inlining every detail: compatibility matrix, progress contract,
Expand Down
Loading
Loading