@chestnutlabs/gcode-preview-react 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,12 +26,19 @@ function Viewer({ file }: { file: File | null }) {
26
26
  }
27
27
  ```
28
28
 
29
- That is a complete viewer (StrictMode-safe). The full surface is optional props with sensible
30
- defaults — `source`, `parseOptions`, `buildVolume` (consumer-wins bed precedence), `quality`,
31
- `colorMode`, `layerRange`, `scrub`, `showTravel`, `progress` (DD-006 observation), `createWorker`
32
- — with matching callbacks (`onReady`, `onParseError`, `onBuildComplete`, `onQualityFallback`,
29
+ That is a complete viewer (StrictMode-safe). The full surface is ~24 optional props with sensible
30
+ defaults — `source`, `parseOptions`, `buildVolume` (consumer-wins bed precedence), `quality` /
31
+ `qualityMode`, `colorMode`, `theme`, `cameraMode` / `view` / `frameContent`, `layerRange`, `scrub`,
32
+ `showTravel` / `showRetractions` / `showWipe`, `hiddenFeatureRoles` (hide feature roles like Skirt or
33
+ Brim; gate on `capabilities.featureRoles`), `progress` (DD-006 observation), `createWorker` — with
34
+ matching callbacks (`onReady`, `onCameraChange`, `onParseError`, `onParseCancelled`,
35
+ `onParseProgress`, `onStage`, `onBuildComplete`, `onQualityFallback`,
33
36
  `onMachineGeometryMismatch`/`Discovered`, `onProgressPresentationChanged`, `onDisclosure`,
34
- `onError`). The full handle is reachable via `ref`.
37
+ `onError`). The full handle is reachable via `ref`; its `controls` reach the imperative surface —
38
+ `getRenderStats()` (render diagnostics), `pickSegment(ndcX, ndcY, threshold?)` (source-mapping /
39
+ picking), `isColorModeAvailable(mode)`, and `capture(opts?)` (image `Blob`, also `handle.capture`).
40
+ The handle `state` carries capability-aware UI data (`availableColorModes`, `hasRetractions`,
41
+ `hasColorChanges`).
35
42
 
36
43
  ## Headless hook (build your own controls)
37
44
 
@@ -54,7 +61,45 @@ The batteries default (all dialect adapters + `.gcode.3mf`) needs zero setup und
54
61
  `@chestnutlabs/gcode-preview-vue` README for the shared worker documentation — the options are
55
62
  identical across adapters by design.
56
63
 
57
- ## Example
64
+ ## Model viewing (Prepare side)
58
65
 
59
- `tools/example-react` in the repository is a complete Vite + React app (corpus load, layer/scrub,
60
- simulated live progress) running under `<StrictMode>`.
66
+ The `/model` subpath ships a `<ModelViewer>` — the **Prepare**-side counterpart to `<GcodePreview>`.
67
+ Where `<GcodePreview>` draws the toolpath (how a print runs), `<ModelViewer>` draws the **source
68
+ model** (an `.stl` / `.3mf` mesh) — the object before slicing.
69
+
70
+ ```tsx
71
+ import { ModelViewer } from '@chestnutlabs/gcode-preview-react/model';
72
+
73
+ function ModelPreview({ bytes }: { bytes: Uint8Array }) {
74
+ return (
75
+ <div style={{ height: '70vh' }}>
76
+ <ModelViewer
77
+ source={{ kind: '3mf', bytes }}
78
+ onReady={(info) => console.log(info.objectCount, info.materials)}
79
+ />
80
+ </div>
81
+ );
82
+ }
83
+ ```
84
+
85
+ `onReady(info)` carries the honest material-colour tier: `info.materials` is `'known'` /
86
+ `'approximated'` when the file declared colours the render used, and `'unavailable'` when it declared
87
+ none (a neutral default was drawn, never faked). The declarative props, callbacks, and the
88
+ `useModelViewer` hook mirror the toolpath component. See
89
+ [`docs/manual/adapters.md`](../../docs/manual/adapters.md) "Two viewers: Preview and Prepare" for the
90
+ cross-adapter tour, and `tools/example-react/model.html` for a runnable minimal page.
91
+
92
+ ## Examples
93
+
94
+ `tools/example-react` in the repository is a Vite app with two tiers, both driving this published
95
+ package (no raw renderer or parser imports):
96
+
97
+ - **`minimal.html`** — the smallest real integration: `<GcodePreview source>` plus a fixture picker
98
+ and a layer slider. One short file to copy when getting started.
99
+ - **`showcase.html`** — the full declarative surface: capability-gated color modes (a mode greys out
100
+ with a plain-language reason when the file can't support it), declarative `hiddenFeatureRoles`,
101
+ camera, and `getRenderStats()`/`pickSegment()` diagnostics through the `ref` handle. Uses the
102
+ `useGcodePreview` hook for render-subscribed reactive state.
103
+
104
+ Run it with `npm install --prefix tools/example-react && npm run dev --prefix tools/example-react`
105
+ (port 5201).
@@ -1,5 +1,5 @@
1
1
  import type { BuildVolumeDef, CameraMode, CameraState, CameraView, ColorMode, ProgressivePreview, PreparationStage, QualityMode, QualityPolicy, Theme, TubeOptions } from '@chestnutlabs/gcode-renderer-three';
2
- import type { MachineGeometry, ProgressObservation } from '@chestnutlabs/toolpath-core';
2
+ import type { FeatureRoleValue, MachineGeometry, ProgressObservation } from '@chestnutlabs/toolpath-core';
3
3
  import type { WireParseOptions, WorkerLike } from '@chestnutlabs/gcode-parser';
4
4
  import { type GcodePreviewHandle, type RendererMode, type UseGcodePreviewOptions } from './use-gcode-preview.js';
5
5
  export type GcodePreviewSource = Uint8Array | ArrayBuffer | File | null;
@@ -48,6 +48,9 @@ export interface GcodePreviewProps {
48
48
  showRetractions?: boolean;
49
49
  /** #306/#6: show the build-volume wireframe cage (independent of the bed/plate). Default true. */
50
50
  showVolumeCage?: boolean;
51
+ /** DD-031 G3: feature roles to hide (e.g. `[FeatureRole.Skirt, FeatureRole.Brim]`). Declarative form
52
+ * of `controls.setFeatureRoleVisible`; gate on `capabilities.featureRoles`. */
53
+ hiddenFeatureRoles?: FeatureRoleValue[];
51
54
  /** DD-006 live progress observation; null hides the overlay. */
52
55
  progress?: ProgressObservation | null;
53
56
  /** Worker factory escape hatch (DD-005 slim/custom entries; D2). */
@@ -1 +1 @@
1
- {"version":3,"file":"gcode-preview-component.d.ts","sourceRoot":"","sources":["../src/gcode-preview-component.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gcode-preview-component.d.ts","sourceRoot":"","sources":["../src/gcode-preview-component.ts"],"names":[],"mappings":"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"}
@@ -36,6 +36,8 @@ function GcodePreviewImpl(props, ref) {
36
36
  // Callbacks live in a ref so the event subscription is stable across renders.
37
37
  const cbRef = useRef(props);
38
38
  cbRef.current = props;
39
+ // Tracks which roles THIS prop last hid, so we diff prev→next and don't disturb roles hidden elsewhere.
40
+ const prevHiddenRef = useRef([]);
39
41
  useEffect(() => {
40
42
  return preview.onEvent((e) => {
41
43
  const p = cbRef.current;
@@ -90,7 +92,7 @@ function GcodePreviewImpl(props, ref) {
90
92
  });
91
93
  }, []);
92
94
  // ---- prop wiring: each prop is a thin call into the hook (D1 shell rule) ----
93
- const { source, layerRange, scrub, scrubTime, showTravel, showWipe, showRetractions, showVolumeCage, colorMode, quality, qualityMode, progressivePreview, cameraMode, frameContent, interactionQuality, view, cameraState, theme, buildVolume, progress } = props;
95
+ const { source, layerRange, scrub, scrubTime, showTravel, showWipe, showRetractions, showVolumeCage, colorMode, quality, qualityMode, progressivePreview, cameraMode, frameContent, interactionQuality, view, cameraState, theme, buildVolume, hiddenFeatureRoles, progress } = props;
94
96
  useEffect(() => {
95
97
  if (source !== null && source !== undefined)
96
98
  void preview.parse(source, cbRef.current.parseOptions);
@@ -163,6 +165,18 @@ function GcodePreviewImpl(props, ref) {
163
165
  if (buildVolume !== undefined && 'bed' in buildVolume)
164
166
  preview.controls.setBuildVolume(buildVolume);
165
167
  }, [buildVolume]);
168
+ useEffect(() => {
169
+ // Feature-role visibility (DD-031 G3): diff prev→next so we only toggle roles this prop owns.
170
+ const next = hiddenFeatureRoles ?? [];
171
+ const prev = prevHiddenRef.current;
172
+ for (const role of prev)
173
+ if (!next.includes(role))
174
+ preview.controls.setFeatureRoleVisible(role, true);
175
+ for (const role of next)
176
+ if (!prev.includes(role))
177
+ preview.controls.setFeatureRoleVisible(role, false);
178
+ prevHiddenRef.current = [...next];
179
+ }, [hiddenFeatureRoles]);
166
180
  useEffect(() => {
167
181
  if (progress === null || progress === undefined)
168
182
  preview.clearProgress();
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @chestnutlabs/gcode-preview-react/model — the React integration for interactive **source-model**
3
+ * viewing (STL / 3MF), the Prepare-side counterpart to the toolpath `<GcodePreview>` (DD-031).
4
+ * `useModelViewer` hook + `<ModelViewer>` component, both thin reactivity bridges over
5
+ * `createModelPreviewController` — never a separate viewer. Shared contracts pass through from the
6
+ * controller (the D1 drift firewall).
7
+ */
8
+ export { useModelViewer } from './use-model-viewer.js';
9
+ export type { ModelViewerHandle, UseModelViewerOptions, ModelPreviewControls, ModelPreviewState, ModelViewerEvent, ModelReadyInfo } from './use-model-viewer.js';
10
+ export { ModelViewer } from './model-viewer-component.js';
11
+ export type { ModelViewerProps, ModelViewerSource } from './model-viewer-component.js';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/model/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @chestnutlabs/gcode-preview-react/model — the React integration for interactive **source-model**
3
+ * viewing (STL / 3MF), the Prepare-side counterpart to the toolpath `<GcodePreview>` (DD-031).
4
+ * `useModelViewer` hook + `<ModelViewer>` component, both thin reactivity bridges over
5
+ * `createModelPreviewController` — never a separate viewer. Shared contracts pass through from the
6
+ * controller (the D1 drift firewall).
7
+ */
8
+ export { useModelViewer } from './use-model-viewer.js';
9
+ export { ModelViewer } from './model-viewer-component.js';
@@ -0,0 +1,44 @@
1
+ import type { CameraMode, CameraState, CameraView, GLRendererLike, InteractiveStageOptions, LoadProgress, RenderTargetCanvas } from '@chestnutlabs/gcode-renderer-three';
2
+ import type { ModelBackground, ModelLimits, ModelLoader, ModelReadyInfo, ModelSourceInput, PresentationView, RenderScope } from '@chestnutlabs/gcode-model-renderer';
3
+ import { type ModelViewerHandle } from './use-model-viewer.js';
4
+ export type ModelViewerSource = ModelSourceInput | null;
5
+ export interface ModelViewerProps {
6
+ /** The one prop needed in practice: the source model. Changing it re-parses. */
7
+ source?: ModelViewerSource;
8
+ /** Snap to a preset orientation (shared vocabulary with the toolpath camera). */
9
+ view?: PresentationView | CameraView;
10
+ /** Restore a saved camera pose. Pair with `onCameraChange` for a two-way binding. */
11
+ cameraState?: CameraState | null;
12
+ /** `'transparent'` (default) | CSS colour | `0xRRGGBB`. */
13
+ background?: ModelBackground;
14
+ /** DD-020 interaction-aware quality; default `'auto'`. */
15
+ interactionQuality?: 'off' | 'auto';
16
+ /** Initial camera projection; default `'perspective'`. */
17
+ cameraMode?: CameraMode;
18
+ /** Render only a subset: `{ plateId }` / `{ objectIds }` / `{ instanceFilter }`; null = whole source. */
19
+ renderScope?: RenderScope | null;
20
+ /** Source-model triangle / byte caps. */
21
+ limits?: ModelLimits;
22
+ /** 3MF `paint_color` palette override (hex per 0-based slot). */
23
+ filamentPalette?: readonly (string | undefined)[];
24
+ /** Loader registry (open `kind`). Default: STL + 3MF. */
25
+ loaders?: readonly ModelLoader[];
26
+ /** Advanced/test: inject GL / orbit controls. */
27
+ createRenderer?: (canvas: RenderTargetCanvas) => GLRendererLike;
28
+ createControls?: NonNullable<InteractiveStageOptions['createControls']>;
29
+ onReady?: (info: ModelReadyInfo) => void;
30
+ onCameraChange?: (state: CameraState) => void;
31
+ onError?: (e: {
32
+ code: string;
33
+ message: string;
34
+ }) => void;
35
+ onRendererUnsupported?: (e: {
36
+ feature: string;
37
+ message: string;
38
+ }) => void;
39
+ onContextLost?: () => void;
40
+ onContextRestored?: () => void;
41
+ onProgress?: (progress: LoadProgress) => void;
42
+ }
43
+ export declare const ModelViewer: import("react").ForwardRefExoticComponent<ModelViewerProps & import("react").RefAttributes<ModelViewerHandle>>;
44
+ //# sourceMappingURL=model-viewer-component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-viewer-component.d.ts","sourceRoot":"","sources":["../../src/model/model-viewer-component.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,UAAU,EACV,cAAc,EACd,uBAAuB,EACvB,YAAY,EACZ,kBAAkB,EACnB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EACV,eAAe,EACf,WAAW,EACX,WAAW,EACX,cAAc,EACd,gBAAgB,EAEhB,gBAAgB,EAChB,WAAW,EACZ,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAkB,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE/E,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,GAAG,IAAI,CAAC;AAExD,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,iFAAiF;IACjF,IAAI,CAAC,EAAE,gBAAgB,GAAG,UAAU,CAAC;IACrC,qFAAqF;IACrF,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,2DAA2D;IAC3D,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,0DAA0D;IAC1D,kBAAkB,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACpC,0DAA0D;IAC1D,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,yGAAyG;IACzG,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,yCAAyC;IACzC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,iEAAiE;IACjE,eAAe,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;IAClD,yDAAyD;IACzD,OAAO,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IACjC,iDAAiD;IACjD,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,cAAc,CAAC;IAChE,cAAc,CAAC,EAAE,WAAW,CAAC,uBAAuB,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACxE,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACzC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAC9C,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACzD,qBAAqB,CAAC,EAAE,CAAC,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC1E,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;CAC/C;AA8FD,eAAO,MAAM,WAAW,gHAA8B,CAAC"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * <ModelViewer> — the ready-to-use React component for interactive source-model viewing (STL / 3MF),
3
+ * the Prepare-side counterpart to <GcodePreview> (DD-031). A SHELL over `useModelViewer`: every prop
4
+ * maps to a controls call, every callback re-fires a controller event, and the full handle is reachable
5
+ * via `ref`. `<ModelViewer source={file} />` alone is a working viewer.
6
+ *
7
+ * createElement (no JSX) on purpose — the template is a single full-size <canvas>.
8
+ */
9
+ import { createElement, forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
10
+ import { useModelViewer } from './use-model-viewer.js';
11
+ function ModelViewerImpl(props, ref) {
12
+ const { source = null, view, cameraState, background, interactionQuality, cameraMode, renderScope, limits, filamentPalette, loaders, createRenderer, createControls } = props;
13
+ // Construction-time options (stable for the controller's lifetime).
14
+ const viewer = useModelViewer({
15
+ ...(background !== undefined ? { background } : {}),
16
+ ...(interactionQuality !== undefined ? { interactionQuality } : {}),
17
+ ...(cameraMode !== undefined ? { cameraMode } : {}),
18
+ ...(limits !== undefined ? { limits } : {}),
19
+ ...(filamentPalette !== undefined ? { filamentPalette } : {}),
20
+ ...(loaders !== undefined ? { loaders } : {}),
21
+ ...(renderScope != null ? { renderScope } : {}),
22
+ ...(createRenderer !== undefined ? { createRenderer } : {}),
23
+ ...(createControls !== undefined ? { createControls } : {}),
24
+ onProgress: (p) => cbRef.current.onProgress?.(p)
25
+ });
26
+ useImperativeHandle(ref, () => viewer, [viewer]);
27
+ // Latest callbacks without re-subscribing the event bridge.
28
+ const cbRef = useRef(props);
29
+ cbRef.current = props;
30
+ useEffect(() => {
31
+ return viewer.onEvent((e) => {
32
+ const cb = cbRef.current;
33
+ switch (e.type) {
34
+ case 'ready':
35
+ cb.onReady?.(e.info);
36
+ break;
37
+ case 'camera-changed':
38
+ cb.onCameraChange?.(e.state);
39
+ break;
40
+ case 'error':
41
+ cb.onError?.({ code: e.code, message: e.message });
42
+ break;
43
+ case 'renderer-unsupported':
44
+ cb.onRendererUnsupported?.({ feature: e.feature, message: e.message });
45
+ break;
46
+ case 'context-lost':
47
+ cb.onContextLost?.();
48
+ break;
49
+ case 'context-restored':
50
+ cb.onContextRestored?.();
51
+ break;
52
+ default:
53
+ break;
54
+ }
55
+ });
56
+ }, [viewer]);
57
+ // ---- runtime prop wiring (each a thin controls call) ----
58
+ useEffect(() => {
59
+ if (source !== null && source !== undefined)
60
+ void viewer.controls.setSource(source);
61
+ }, [viewer, source]);
62
+ useEffect(() => {
63
+ if (view !== undefined)
64
+ viewer.controls.setView(view);
65
+ }, [viewer, view]);
66
+ useEffect(() => {
67
+ if (cameraState !== undefined && cameraState !== null)
68
+ viewer.controls.setCameraState(cameraState);
69
+ }, [viewer, cameraState]);
70
+ useEffect(() => {
71
+ if (background !== undefined)
72
+ viewer.controls.setBackground(background);
73
+ }, [viewer, background]);
74
+ useEffect(() => {
75
+ if (interactionQuality !== undefined)
76
+ viewer.controls.setInteractionQuality(interactionQuality);
77
+ }, [viewer, interactionQuality]);
78
+ useEffect(() => {
79
+ if (renderScope !== undefined)
80
+ viewer.controls.setRenderScope(renderScope);
81
+ }, [viewer, renderScope]);
82
+ return createElement('canvas', {
83
+ ref: viewer.canvasRef,
84
+ style: { width: '100%', height: '100%', display: 'block', touchAction: 'none' },
85
+ tabIndex: 0,
86
+ 'aria-label': '3D source-model preview'
87
+ });
88
+ }
89
+ export const ModelViewer = forwardRef(ModelViewerImpl);
@@ -0,0 +1,22 @@
1
+ import { type ModelPreviewControllerOptions, type ModelPreviewControls, type ModelPreviewState, type ModelViewer, type ModelViewerEvent, type ModelReadyInfo } from '@chestnutlabs/gcode-model-renderer';
2
+ import type { CaptureOptions } from '@chestnutlabs/gcode-renderer-three';
3
+ export type { ModelPreviewControls, ModelPreviewState, ModelViewerEvent, ModelReadyInfo };
4
+ export type UseModelViewerOptions = ModelPreviewControllerOptions;
5
+ export interface ModelViewerHandle {
6
+ /** Attach as `<canvas ref={viewer.canvasRef} />`; null detaches safely. */
7
+ canvasRef: (el: HTMLCanvasElement | null) => void;
8
+ /** Parse → build → frame → `ready`. Also reachable as `controls.setSource`. */
9
+ setSource: ModelPreviewControls['setSource'];
10
+ /** Capture the current view as an image `Blob` (also `controls.capture`). */
11
+ capture(opts?: CaptureOptions): Promise<Blob>;
12
+ /** Render-subscribed state snapshot (via useSyncExternalStore). */
13
+ state: ModelPreviewState;
14
+ controls: ModelPreviewControls;
15
+ raw: {
16
+ viewer: () => ModelViewer | null;
17
+ };
18
+ onEvent(cb: (e: ModelViewerEvent) => void): () => void;
19
+ dispose(): void;
20
+ }
21
+ export declare function useModelViewer(options?: UseModelViewerOptions): ModelViewerHandle;
22
+ //# sourceMappingURL=use-model-viewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-model-viewer.d.ts","sourceRoot":"","sources":["../../src/model/use-model-viewer.ts"],"names":[],"mappings":"AAQA,OAAO,EAGL,KAAK,6BAA6B,EAClC,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACpB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,cAAc,EAAE,CAAC;AAC1F,MAAM,MAAM,qBAAqB,GAAG,6BAA6B,CAAC;AAElE,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,SAAS,EAAE,CAAC,EAAE,EAAE,iBAAiB,GAAG,IAAI,KAAK,IAAI,CAAC;IAClD,+EAA+E;IAC/E,SAAS,EAAE,oBAAoB,CAAC,WAAW,CAAC,CAAC;IAC7C,6EAA6E;IAC7E,OAAO,CAAC,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,mEAAmE;IACnE,KAAK,EAAE,iBAAiB,CAAC;IACzB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,GAAG,EAAE;QAAE,MAAM,EAAE,MAAM,WAAW,GAAG,IAAI,CAAA;KAAE,CAAC;IAC1C,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACvD,OAAO,IAAI,IAAI,CAAC;CACjB;AAQD,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,iBAAiB,CA+CrF"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * `useModelViewer` — the React hook for interactive **source-model** viewing (the Prepare side),
3
+ * the analogue of `useGcodePreview` for the toolpath/Preview side (DD-031). A thin reactivity bridge
4
+ * over the framework-neutral `createModelPreviewController` from `@chestnutlabs/gcode-model-renderer`
5
+ * — never a separate viewer implementation (the D1 drift firewall). Shared contracts are re-exported
6
+ * from the controller.
7
+ */
8
+ import { useCallback, useMemo, useRef, useSyncExternalStore } from 'react';
9
+ import { createModelPreviewController } from '@chestnutlabs/gcode-model-renderer';
10
+ export function useModelViewer(options = {}) {
11
+ const optionsRef = useRef(options);
12
+ optionsRef.current = options;
13
+ const slot = useRef({ controller: null, disposed: false, canvas: null });
14
+ const ensure = useCallback(() => {
15
+ const s = slot.current;
16
+ if (s.controller === null || s.disposed) {
17
+ s.controller = createModelPreviewController(optionsRef.current);
18
+ s.disposed = false;
19
+ if (s.canvas !== null)
20
+ s.controller.bindCanvas(s.canvas);
21
+ }
22
+ return s.controller;
23
+ }, []);
24
+ const subscribe = useCallback((cb) => ensure().onStateChange(() => cb()), [ensure]);
25
+ const getSnapshot = useCallback(() => ensure().getState(), [ensure]);
26
+ const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
27
+ const stateRef = useRef(state);
28
+ stateRef.current = state;
29
+ return useMemo(() => ({
30
+ canvasRef: (el) => {
31
+ slot.current.canvas = el;
32
+ ensure().bindCanvas(el);
33
+ },
34
+ setSource: (input) => ensure().controls.setSource(input),
35
+ capture: (opts) => ensure().controls.capture(opts),
36
+ get state() {
37
+ return stateRef.current;
38
+ },
39
+ get controls() {
40
+ return ensure().controls;
41
+ },
42
+ get raw() {
43
+ return ensure().raw;
44
+ },
45
+ onEvent: (cb) => ensure().onEvent(cb),
46
+ dispose: () => {
47
+ slot.current.controller?.dispose();
48
+ slot.current.disposed = true;
49
+ }
50
+ }), [ensure]);
51
+ }
@@ -1,6 +1,6 @@
1
1
  import type { GcodeParseSession, WireParseOptions } from '@chestnutlabs/gcode-parser';
2
2
  import type { MappedProgress, ProgressObservation } from '@chestnutlabs/toolpath-core';
3
- import { type GcodePreviewControls, type GcodePreviewState, type ParseOutcome, type PreviewControllerOptions, type PreviewEvent, type PreviewRenderer } from '@chestnutlabs/gcode-preview-core';
3
+ import { type CaptureOptions, type GcodePreviewControls, type GcodePreviewState, type ParseOutcome, type PreviewControllerOptions, type PreviewEvent, type PreviewRenderer } from '@chestnutlabs/gcode-preview-core';
4
4
  export type { GcodePreviewControls, GcodePreviewState, ParseOutcome, PreviewEvent, PreviewRenderer, RendererMode } from '@chestnutlabs/gcode-preview-core';
5
5
  /** React-facing options — identical to the controller's (the alias is the public name). */
6
6
  export type UseGcodePreviewOptions = PreviewControllerOptions;
@@ -12,6 +12,9 @@ export interface GcodePreviewHandle {
12
12
  observeProgress(obs: ProgressObservation): MappedProgress | null;
13
13
  tickProgress(nowMs: number): MappedProgress | null;
14
14
  clearProgress(): void;
15
+ /** Capture the current view as an image `Blob` (DD-030 D1, DD-031 G5) — handle-level parity with the
16
+ * Web Component's `capture()`. Also reachable as `controls.capture`. Rejects on the 2D renderer. */
17
+ capture(opts?: CaptureOptions): Promise<Blob>;
15
18
  /** Current state snapshot — render-subscribed via useSyncExternalStore. */
16
19
  state: GcodePreviewState;
17
20
  controls: GcodePreviewControls;
@@ -1 +1 @@
1
- {"version":3,"file":"use-gcode-preview.d.ts","sourceRoot":"","sources":["../src/use-gcode-preview.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvF,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAEjB,KAAK,wBAAwB,EAC7B,KAAK,YAAY,EACjB,KAAK,eAAe,EACrB,MAAM,kCAAkC,CAAC;AAG1C,YAAY,EACV,oBAAoB,EACpB,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,YAAY,EACb,MAAM,kCAAkC,CAAC;AAE1C,2FAA2F;AAC3F,MAAM,MAAM,sBAAsB,GAAG,wBAAwB,CAAC;AAE9D,MAAM,WAAW,kBAAkB;IACjC,4EAA4E;IAC5E,SAAS,EAAE,CAAC,EAAE,EAAE,iBAAiB,GAAG,IAAI,KAAK,IAAI,CAAC;IAClD,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAC9F,MAAM,IAAI,IAAI,CAAC;IACf,eAAe,CAAC,GAAG,EAAE,mBAAmB,GAAG,cAAc,GAAG,IAAI,CAAC;IACjE,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAAC;IACnD,aAAa,IAAI,IAAI,CAAC;IACtB,2EAA2E;IAC3E,KAAK,EAAE,iBAAiB,CAAC;IACzB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,GAAG,EAAE;QAAE,OAAO,EAAE,iBAAiB,CAAC;QAAC,QAAQ,EAAE,MAAM,eAAe,GAAG,IAAI,CAAA;KAAE,CAAC;IAC5E,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACnD,OAAO,IAAI,IAAI,CAAC;CACjB;AAUD,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,kBAAkB,CAiExF"}
1
+ {"version":3,"file":"use-gcode-preview.d.ts","sourceRoot":"","sources":["../src/use-gcode-preview.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvF,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAEjB,KAAK,wBAAwB,EAC7B,KAAK,YAAY,EACjB,KAAK,eAAe,EACrB,MAAM,kCAAkC,CAAC;AAG1C,YAAY,EACV,oBAAoB,EACpB,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,YAAY,EACb,MAAM,kCAAkC,CAAC;AAE1C,2FAA2F;AAC3F,MAAM,MAAM,sBAAsB,GAAG,wBAAwB,CAAC;AAE9D,MAAM,WAAW,kBAAkB;IACjC,4EAA4E;IAC5E,SAAS,EAAE,CAAC,EAAE,EAAE,iBAAiB,GAAG,IAAI,KAAK,IAAI,CAAC;IAClD,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAC9F,MAAM,IAAI,IAAI,CAAC;IACf,eAAe,CAAC,GAAG,EAAE,mBAAmB,GAAG,cAAc,GAAG,IAAI,CAAC;IACjE,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAAC;IACnD,aAAa,IAAI,IAAI,CAAC;IACtB;yGACqG;IACrG,OAAO,CAAC,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,2EAA2E;IAC3E,KAAK,EAAE,iBAAiB,CAAC;IACzB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,GAAG,EAAE;QAAE,OAAO,EAAE,iBAAiB,CAAC;QAAC,QAAQ,EAAE,MAAM,eAAe,GAAG,IAAI,CAAA;KAAE,CAAC;IAC5E,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACnD,OAAO,IAAI,IAAI,CAAC;CACjB;AAUD,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,kBAAkB,CAkExF"}
@@ -56,6 +56,7 @@ export function useGcodePreview(options = {}) {
56
56
  observeProgress: (obs) => slot.current.controller?.observeProgress(obs) ?? null,
57
57
  tickProgress: (nowMs) => slot.current.controller?.tickProgress(nowMs) ?? null,
58
58
  clearProgress: () => slot.current.controller?.clearProgress(),
59
+ capture: (opts) => ensure().controls.capture(opts),
59
60
  get state() {
60
61
  return stateRef.current;
61
62
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chestnutlabs/gcode-preview-react",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "description": "Thin React integration for the Chestnut Labs G-code viewer (DD-007 D1 amendment): useGcodePreview hook + GcodePreview component as a reactivity bridge over @chestnutlabs/gcode-preview-core.",
5
5
  "keywords": [
6
6
  "gcode",
@@ -35,6 +35,10 @@
35
35
  ".": {
36
36
  "types": "./dist/index.d.ts",
37
37
  "import": "./dist/index.js"
38
+ },
39
+ "./model": {
40
+ "types": "./dist/model/index.d.ts",
41
+ "import": "./dist/model/index.js"
38
42
  }
39
43
  },
40
44
  "files": [
@@ -49,10 +53,11 @@
49
53
  "test": "vitest run"
50
54
  },
51
55
  "dependencies": {
52
- "@chestnutlabs/gcode-parser": "0.18.0",
53
- "@chestnutlabs/gcode-preview-core": "0.18.0",
54
- "@chestnutlabs/gcode-renderer-three": "0.18.0",
55
- "@chestnutlabs/toolpath-core": "0.18.0"
56
+ "@chestnutlabs/gcode-model-renderer": "0.20.0",
57
+ "@chestnutlabs/gcode-parser": "0.20.0",
58
+ "@chestnutlabs/gcode-preview-core": "0.20.0",
59
+ "@chestnutlabs/gcode-renderer-three": "0.20.0",
60
+ "@chestnutlabs/toolpath-core": "0.20.0"
56
61
  },
57
62
  "peerDependencies": {
58
63
  "react": "^18.0.0 || ^19.0.0"