Skip to content

release: version packages (lockstep) - #412

Merged
sobechestnut-dev merged 1 commit into
devfrom
changeset-release/dev
Aug 27, 2026
Merged

sobechestnut-dev merged 1 commit into
devfrom
changeset-release/dev

Conversation

@github-actions

@github-actions github-actions Bot commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or setup this action to publish automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to dev, this PR will be updated.

Releases

@chestnutlabs/gcode-model-renderer@0.18.0

Minor Changes

  • #418 c950339 Thanks @sobechestnut-dev! - feat(renderer): interactive view capture() → Blob (DD-030 D1)

    The interactive viewer can now hand back what is on screen right now as an image Blob — for a
    user-selected thumbnail, a large-file thumbnail fallback, or a screenshot. New capture(opts?) where
    opts is { width?, height?, format?, quality?, background? } (all optional; defaults match the live
    view).

    Available on every interactive surface: GcodePreviewControls.capture() (so the Vue, React, Svelte, and
    Web-Component adapters all inherit it — the Web Component also exposes an imperative capture() method),
    and ModelViewer.capture() on the model-viewer handle. The toolpath ToolpathRenderer and the shared
    InteractiveStage carry the implementation.

    Mechanism (render-to-target). Capture renders the current scene + active camera into an off-screen
    WebGLRenderTarget at the requested size and reads it back, rather than flipping the interactive
    context's preserveDrawingBuffer (which would tax every interactive frame). That gives an arbitrary
    output size and an independent/transparent background without disturbing the live view, and reuses the
    headless still path's "single render, then read pixels" recipe. The thumbnail is framed at its own aspect
    so it isn't distorted; the live view is repainted afterward. The library returns the Blob and never
    triggers a download — the caller owns the pixels (same contract as renderStill).

    Honest. When the renderer cannot render-to-target (the 2D renderer, a stub GL / no WebGL) or the stage
    is disposed / its context is lost, capture() rejects with a typed CaptureUnsupportedError
    (code: 'E_CAPTURE_UNSUPPORTED') — never fabricated output. Purely additive (a new optional method on the
    renderer contract; no existing signature changed). Final increment of the DD-030 renderer/viewer
    interoperability batch (bed + per-plate scope + capture).

  • #417 5dc8079 Thanks @sobechestnut-dev! - feat(model-renderer): per-plate / object-subset render scope (DD-030 D2)

    A caller can now render just one plate (or any object/placement subset) of a multi-plate / multi-object
    source, instead of always the whole project — so a single-plate variant thumbnail looks like that plate.

    New RenderScope selector — { plateId }, { objectIds }, or { instanceFilter } — with a pure,
    three-free applyRenderScope(scene, scope) that returns a filtered ModelScene (dropping non-matching
    objects/placements) with bounds recomputed from the kept placements, so framing follows the subset.
    { plateId } is sugar over the placement-level plateIds (DD-025); it's generic (a plate is just one way
    to derive the subset) and vendor-neutral.

    Wired into both surfaces:

    • renderModelStill gains a renderScope? option; it also folds the scope into the still cacheKey so a
      plate-1 and a plate-2 thumbnail of the same source key distinctly (an opaque instanceFilter is marked
      non-cacheable).
    • createModelViewer gains an initial renderScope? and a setRenderScope(scope | null) handle method
      that rebuilds and reframes to the subset (null restores the whole source).

    Honest and additive: omitting renderScope renders the whole project exactly as before. A { plateId }
    that matches nothing (e.g. a source with no declared plate structure, capabilities.plates !== 'known')
    renders an empty scene by design — the honest result of selecting a plate that isn't declared;
    consumers gate { plateId } on plates: 'known'. The source's declared plates/capabilities are
    preserved (only what is rendered is narrowed). Second increment of the DD-030 renderer/viewer interop
    batch.

Patch Changes

  • Updated dependencies [0ebeadf, c950339]:
    • @chestnutlabs/gcode-renderer-three@0.18.0
    • @chestnutlabs/gcode-containers@0.18.0
    • @chestnutlabs/toolpath-core@0.18.0

@chestnutlabs/gcode-parser@0.18.0

Minor Changes

  • #410 5144890 Thanks @sobechestnut-dev! - feat(parser): RS274NGC parameters + expressions (DD-017 Phase 1, #189 phase 7)

    The parser now understands the RS274NGC programming layer that FDM slicers never use but CAM /
    LinuxCNC output routinely does: numbered/named/global parameters ([#100](https://github.com/ChestnutLabs/gcode-preview/issues/100), #<name>, #<_global>),
    assignment (#n = <expr>), and bracket expressions ([ … ]) usable wherever a numeric word value is
    expected (X[[#1](https://github.com/ChestnutLabs/gcode-preview/issues/1)+2], F[#feed], Z#<depth>). Previously these were silently dropped, so parametric
    programs rendered honest-but-empty; they now resolve to real geometry.

    A new self-contained module (rs274.ts) provides a pure recursive-descent evaluator — the RS274NGC
    operator + function set with correct precedence (** > */MOD > +- > comparisons > logical),
    degree-based trig, LinuxCNC MOD/EQ semantics, FIX/FUP/ROUND, indirect ##n and computed
    #[expr] references, and a read-only system-parameter allow-list ([#5420](https://github.com/ChestnutLabs/gcode-preview/issues/5420)–[#5422](https://github.com/ChestnutLabs/gcode-preview/issues/5422) = current position).
    It is bounded and safe: a depth guard on nested brackets, no eval/Function, and a capped
    parameter store, so a hostile program can waste only bounded CPU. Malformed input never throws out of
    the parse — the offending word is dropped with a specific disclosure (rs274-bad-expression,
    rs274-uninitialized-param, rs274-unsupported-sysparam) and parsing continues.

    Honest and additive: a new parametricProgram capability (known when executed, unavailable
    otherwise). Engaged only on lines that use #/[ — every FDM and simple-CNC line takes the untouched
    lexer and is byte-identical (native goldens regenerated for the additive capability key only; the
    detection gate adds ~2.7% to a large FDM parse, geometry unchanged).

    Scope: Phase 1 (parameters + expressions) only. O-word control flow (if/while/sub/call) is a
    later phase and is not yet supported — such constructs are ignored (and will be disclosed) for now.

  • #413 bcb4b78 Thanks @sobechestnut-dev! - feat(parser): RS274NGC O-word control flow (DD-017 Phase 2, #189 phase 7)

    Building on Phase 1's parameters + expressions, the parser now executes the RS274NGC O-word control
    structures that CAM / LinuxCNC programs use to generate geometry: if/elseif/else/endif,
    while/endwhile, do/while, repeat/endrepeat, and break/continue. A bolt-circle written as
    a while loop over a computed angle, a pocket cut by a repeat, an if that selects a tool path —
    these previously ran once (or not at all) because the parser is forward-only; they now resolve to the
    full, correct toolpath.

    A new module (rs274-flow.ts) is the program-buffered interpreter of DD-017 D1: engaged only when a
    program actually contains an O-word control line (a cheap detection scan), it buffers the program, builds
    a block tree, and executes it — feeding every non-control line back through the same engine so
    geometry/params/expressions stay on one code path. Loops evaluate their conditions/counts against the
    same shared parameter store, so #<i> = [#<i> + 1] inside a loop body does what you expect.

    Bounded and safe (this is now a small interpreter). A new maxProgramIterations limit (default
    1_000_000) bounds total loop work — charged per loop pass and per statement executed inside a
    loop body, so a large body cannot multiply the real work past the cap — and stops a runaway o while [1]
    with a partial result and a rs274-iteration-limit disclosure. Structural nesting is capped; loops are
    ordinary iteration, not recursion, so an adversarial program can waste only bounded CPU — it cannot hang,
    blow the stack, or OOM the worker. There is no eval and no I/O.

    Honest and additive. Clean flow keeps parametricProgram: 'known'; a degraded run (a hit iteration
    cap, an unbalanced or unsupported O-word, or a malformed condition) reports 'approximated' and always
    emits a specific disclosure — never silently wrong. Subroutines (sub/call/return) are a later phase
    and are explicitly disclosed as unsupported and not executed inline. Non-parametric input never
    enters the interpreter and stays byte-identical (both golden suites unchanged). Streaming input, which
    cannot re-run a line range from a partial stream, discloses and runs linearly.

  • #415 dc89056 Thanks @sobechestnut-dev! - feat(parser): RS274NGC subroutines (DD-017 Phase 3, #189 phase 7)

    Completes the RS274NGC programming layer: the parser now executes in-file O-word subroutines —
    o<id> sub/endsub, o<id> call [args], and o<id> return. A call binds its arguments to [#1](https://github.com/ChestnutLabs/gcode-preview/issues/1)..[#30](https://github.com/ChestnutLabs/gcode-preview/issues/30)
    in a fresh call frame, runs the named sub's body, and returns; return exits early. Subroutines may be
    forward-referenced (a call before the sub that defines it) and may recurse. This is how CAM/LinuxCNC
    programs factor repeated geometry (a "stamp" drilled at many positions, a parametric feature reused
    across a part).

    Correct scoping (recursion-safe). Numbered parameters [#1](https://github.com/ChestnutLabs/gcode-preview/issues/1)..[#30](https://github.com/ChestnutLabs/gcode-preview/issues/30) and non-underscore named parameters
    (#<local>) are LOCAL to each call frame; [#31](https://github.com/ChestnutLabs/gcode-preview/issues/31)+ and underscore-named (#<_global>) are shared globals —
    matching LinuxCNC. A recursive sub's [#1](https://github.com/ChestnutLabs/gcode-preview/issues/1) therefore cannot clobber its caller's, and a sub's locals do not
    leak out. The main program runs in the base scope, so a program that uses no subroutines behaves exactly
    as before (byte-identical; both golden suites unchanged).

    Bounded and safe. A new maxCallDepth limit (default 50) caps subroutine recursion — an unbounded
    or mutually-recursive call is disclosed (rs274-call-depth) and skipped, never a stack overflow. A
    runtime execution-depth backstop additionally bounds the combined structural-nesting + call-depth JS
    recursion. A sub called inside a loop still charges its body work against maxProgramIterations, so a huge
    sub body cannot multiply work past the cap. break/continue/return are frame-local — they never jump
    across a call boundary.

    Honest. Clean subroutine execution keeps parametricProgram: 'known'; a degraded run (unknown sub,
    recursion-limit hit, duplicate definition, misplaced return) reports 'approximated' with a specific
    disclosure — never silently wrong. Subroutine return VALUES are not yet propagated to the caller (a
    documented Phase-3 limitation; return acts as an early exit). External subroutine files (M98,
    o<name> call resolving to a separate .ngc) remain an explicit non-goal (no I/O in the parser).

Patch Changes

  • Updated dependencies []:
    • @chestnutlabs/gcode-bgcode@0.18.0
    • @chestnutlabs/gcode-containers@0.18.0
    • @chestnutlabs/gcode-dialects@0.18.0
    • @chestnutlabs/toolpath-core@0.18.0

@chestnutlabs/gcode-preview-core@0.18.0

Minor Changes

  • #418 c950339 Thanks @sobechestnut-dev! - feat(renderer): interactive view capture() → Blob (DD-030 D1)

    The interactive viewer can now hand back what is on screen right now as an image Blob — for a
    user-selected thumbnail, a large-file thumbnail fallback, or a screenshot. New capture(opts?) where
    opts is { width?, height?, format?, quality?, background? } (all optional; defaults match the live
    view).

    Available on every interactive surface: GcodePreviewControls.capture() (so the Vue, React, Svelte, and
    Web-Component adapters all inherit it — the Web Component also exposes an imperative capture() method),
    and ModelViewer.capture() on the model-viewer handle. The toolpath ToolpathRenderer and the shared
    InteractiveStage carry the implementation.

    Mechanism (render-to-target). Capture renders the current scene + active camera into an off-screen
    WebGLRenderTarget at the requested size and reads it back, rather than flipping the interactive
    context's preserveDrawingBuffer (which would tax every interactive frame). That gives an arbitrary
    output size and an independent/transparent background without disturbing the live view, and reuses the
    headless still path's "single render, then read pixels" recipe. The thumbnail is framed at its own aspect
    so it isn't distorted; the live view is repainted afterward. The library returns the Blob and never
    triggers a download — the caller owns the pixels (same contract as renderStill).

    Honest. When the renderer cannot render-to-target (the 2D renderer, a stub GL / no WebGL) or the stage
    is disposed / its context is lost, capture() rejects with a typed CaptureUnsupportedError
    (code: 'E_CAPTURE_UNSUPPORTED') — never fabricated output. Purely additive (a new optional method on the
    renderer contract; no existing signature changed). Final increment of the DD-030 renderer/viewer
    interoperability batch (bed + per-plate scope + capture).

Patch Changes

  • Updated dependencies [5144890, bcb4b78, dc89056, 0ebeadf, c950339]:
    • @chestnutlabs/gcode-parser@0.18.0
    • @chestnutlabs/gcode-renderer-three@0.18.0
    • @chestnutlabs/gcode-renderer-2d@0.18.0
    • @chestnutlabs/toolpath-core@0.18.0

@chestnutlabs/gcode-preview-element@0.18.0

Minor Changes

  • #418 c950339 Thanks @sobechestnut-dev! - feat(renderer): interactive view capture() → Blob (DD-030 D1)

    The interactive viewer can now hand back what is on screen right now as an image Blob — for a
    user-selected thumbnail, a large-file thumbnail fallback, or a screenshot. New capture(opts?) where
    opts is { width?, height?, format?, quality?, background? } (all optional; defaults match the live
    view).

    Available on every interactive surface: GcodePreviewControls.capture() (so the Vue, React, Svelte, and
    Web-Component adapters all inherit it — the Web Component also exposes an imperative capture() method),
    and ModelViewer.capture() on the model-viewer handle. The toolpath ToolpathRenderer and the shared
    InteractiveStage carry the implementation.

    Mechanism (render-to-target). Capture renders the current scene + active camera into an off-screen
    WebGLRenderTarget at the requested size and reads it back, rather than flipping the interactive
    context's preserveDrawingBuffer (which would tax every interactive frame). That gives an arbitrary
    output size and an independent/transparent background without disturbing the live view, and reuses the
    headless still path's "single render, then read pixels" recipe. The thumbnail is framed at its own aspect
    so it isn't distorted; the live view is repainted afterward. The library returns the Blob and never
    triggers a download — the caller owns the pixels (same contract as renderStill).

    Honest. When the renderer cannot render-to-target (the 2D renderer, a stub GL / no WebGL) or the stage
    is disposed / its context is lost, capture() rejects with a typed CaptureUnsupportedError
    (code: 'E_CAPTURE_UNSUPPORTED') — never fabricated output. Purely additive (a new optional method on the
    renderer contract; no existing signature changed). Final increment of the DD-030 renderer/viewer
    interoperability batch (bed + per-plate scope + capture).

Patch Changes

  • Updated dependencies [5144890, bcb4b78, dc89056, 0ebeadf, c950339]:
    • @chestnutlabs/gcode-parser@0.18.0
    • @chestnutlabs/gcode-renderer-three@0.18.0
    • @chestnutlabs/gcode-preview-core@0.18.0
    • @chestnutlabs/toolpath-core@0.18.0

@chestnutlabs/gcode-renderer-three@0.18.0

Minor Changes

  • #416 0ebeadf Thanks @sobechestnut-dev! - feat(renderer): non-rectangular build-bed geometry (DD-030 D3)

    The build volume can now draw an honest non-rectangular bed — delta, round, or irregular — instead of
    only a rectangle. BuildVolumeDef gains an optional shape?: BedShape
    ({kind:'rect'} | {kind:'circular', center, diameter} | {kind:'polygon', points}): a circle is
    polygonized, the outline is filled and drawn, and the floor grid is clipped to the outline so the
    printable area reads correctly (a round bed is no longer a square with a circle floating over it). The
    mesh escape hatch for a fully custom bed mesh is reserved for a later phase.

    machineToVolume() now maps a discovered MachineGeometry.bed of kind circular/polygon onto that
    shape, so a discovered round/delta bed renders as its true outline instead of being collapsed to its
    bounding rectangle (a visible improvement for delta/round printers). Callers supply the shape (from a
    machine profile, a config, wherever); the renderer just draws the polygon — no profile parsing in the
    library, no vendor semantics baked in.

    Additive and safe: a bed with no shape (or {kind:'rect'}) takes the original rectangular path and is
    byte-identical — the rectangular grid, plate, cage, excluded-region outlines, and origin tripod are
    unchanged. The volume cage stays a bounding box (the bed outline carries the shape). First increment of
    the DD-030 renderer/viewer interoperability batch.

  • #418 c950339 Thanks @sobechestnut-dev! - feat(renderer): interactive view capture() → Blob (DD-030 D1)

    The interactive viewer can now hand back what is on screen right now as an image Blob — for a
    user-selected thumbnail, a large-file thumbnail fallback, or a screenshot. New capture(opts?) where
    opts is { width?, height?, format?, quality?, background? } (all optional; defaults match the live
    view).

    Available on every interactive surface: GcodePreviewControls.capture() (so the Vue, React, Svelte, and
    Web-Component adapters all inherit it — the Web Component also exposes an imperative capture() method),
    and ModelViewer.capture() on the model-viewer handle. The toolpath ToolpathRenderer and the shared
    InteractiveStage carry the implementation.

    Mechanism (render-to-target). Capture renders the current scene + active camera into an off-screen
    WebGLRenderTarget at the requested size and reads it back, rather than flipping the interactive
    context's preserveDrawingBuffer (which would tax every interactive frame). That gives an arbitrary
    output size and an independent/transparent background without disturbing the live view, and reuses the
    headless still path's "single render, then read pixels" recipe. The thumbnail is framed at its own aspect
    so it isn't distorted; the live view is repainted afterward. The library returns the Blob and never
    triggers a download — the caller owns the pixels (same contract as renderStill).

    Honest. When the renderer cannot render-to-target (the 2D renderer, a stub GL / no WebGL) or the stage
    is disposed / its context is lost, capture() rejects with a typed CaptureUnsupportedError
    (code: 'E_CAPTURE_UNSUPPORTED') — never fabricated output. Purely additive (a new optional method on the
    renderer contract; no existing signature changed). Final increment of the DD-030 renderer/viewer
    interoperability batch (bed + per-plate scope + capture).

Patch Changes

  • Updated dependencies []:
    • @chestnutlabs/gcode-colors@0.18.0
    • @chestnutlabs/toolpath-core@0.18.0

@chestnutlabs/gcode-bgcode@0.18.0

Patch Changes

  • Updated dependencies []:
    • @chestnutlabs/gcode-containers@0.18.0
    • @chestnutlabs/toolpath-core@0.18.0

@chestnutlabs/gcode-colors@0.18.0

Patch Changes

  • Updated dependencies []:
    • @chestnutlabs/toolpath-core@0.18.0

@chestnutlabs/gcode-containers@0.18.0

Patch Changes

  • Updated dependencies []:
    • @chestnutlabs/toolpath-core@0.18.0

@chestnutlabs/gcode-dialects@0.18.0

Patch Changes

  • Updated dependencies []:
    • @chestnutlabs/toolpath-core@0.18.0

@chestnutlabs/gcode-preview-react@0.18.0

Patch Changes

  • Updated dependencies [5144890, bcb4b78, dc89056, 0ebeadf, c950339]:
    • @chestnutlabs/gcode-parser@0.18.0
    • @chestnutlabs/gcode-renderer-three@0.18.0
    • @chestnutlabs/gcode-preview-core@0.18.0
    • @chestnutlabs/toolpath-core@0.18.0

@chestnutlabs/gcode-preview-svelte@0.18.0

Patch Changes

  • Updated dependencies [5144890, bcb4b78, dc89056, 0ebeadf, c950339]:
    • @chestnutlabs/gcode-parser@0.18.0
    • @chestnutlabs/gcode-renderer-three@0.18.0
    • @chestnutlabs/gcode-preview-core@0.18.0
    • @chestnutlabs/toolpath-core@0.18.0

@chestnutlabs/gcode-preview-vue@0.18.0

Patch Changes

  • Updated dependencies [5144890, bcb4b78, dc89056, 0ebeadf, c950339]:
    • @chestnutlabs/gcode-parser@0.18.0
    • @chestnutlabs/gcode-renderer-three@0.18.0
    • @chestnutlabs/gcode-preview-core@0.18.0
    • @chestnutlabs/toolpath-core@0.18.0

@chestnutlabs/gcode-renderer-2d@0.18.0

Patch Changes

  • Updated dependencies []:
    • @chestnutlabs/gcode-colors@0.18.0
    • @chestnutlabs/toolpath-core@0.18.0

@chestnutlabs/toolpath-core@0.18.0

@github-actions
github-actions Bot force-pushed the changeset-release/dev branch from 7ad1a33 to 9ba0e78 Compare August 27, 2026 16:01
@github-actions
github-actions Bot force-pushed the changeset-release/dev branch 6 times, most recently from 5bc2ca8 to e5ab43e Compare August 27, 2026 18:23
@github-actions
github-actions Bot force-pushed the changeset-release/dev branch from e5ab43e to 7995cfd Compare August 27, 2026 20:42
@sobechestnut-dev
sobechestnut-dev merged commit c5d0bd4 into dev Aug 27, 2026
4 checks passed
@sobechestnut-dev sobechestnut-dev mentioned this pull request Aug 27, 2026
4 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant