@chestnutlabs/gcode-model-renderer 0.17.0 → 0.19.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/dist/index.d.ts CHANGED
@@ -20,6 +20,6 @@ export type { ModelLoader, ModelLoadOptions, ModelSourceInput } from './loaders.
20
20
  export { computeCacheKey, defaultEnvId } from './cache-key.js';
21
21
  export { DEFAULT_LIMITS, resolveLimits, ModelParseError } from './limits.js';
22
22
  export type { ModelLimits, ResolvedLimits } from './limits.js';
23
- export { IDENTITY_MAT4 } from './scene-model.js';
24
- export type { ModelScene, ModelObject, ModelMaterial, ModelPlateSummary, MeshGeometry, ModelBounds, RGB, Mat4 } from './scene-model.js';
23
+ export { IDENTITY_MAT4, applyRenderScope } from './scene-model.js';
24
+ export type { ModelScene, ModelObject, ModelMaterial, ModelPlateSummary, MeshGeometry, ModelBounds, RenderScope, RGB, Mat4 } from './scene-model.js';
25
25
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC5E,YAAY,EAAE,oBAAoB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACnG,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,YAAY,EAAE,WAAW,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAC5G,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC3G,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,qBAAqB,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAChH,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACpF,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7E,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,YAAY,EACV,UAAU,EACV,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,WAAW,EACX,GAAG,EACH,IAAI,EACL,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC5E,YAAY,EAAE,oBAAoB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACnG,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,YAAY,EAAE,WAAW,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAC5G,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC3G,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,qBAAqB,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAChH,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACpF,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7E,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACnE,YAAY,EACV,UAAU,EACV,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,WAAW,EACX,WAAW,EACX,GAAG,EACH,IAAI,EACL,MAAM,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -14,4 +14,4 @@ export { parse3mf } from './three-mf.js';
14
14
  export { stlLoader, threeMfLoader, DEFAULT_MODEL_LOADERS, isModelScene, resolveModelScene } from './loaders.js';
15
15
  export { computeCacheKey, defaultEnvId } from './cache-key.js';
16
16
  export { DEFAULT_LIMITS, resolveLimits, ModelParseError } from './limits.js';
17
- export { IDENTITY_MAT4 } from './scene-model.js';
17
+ export { IDENTITY_MAT4, applyRenderScope } from './scene-model.js';
@@ -1,9 +1,9 @@
1
- import { type CameraMode, type CameraState, type CameraView, type GLRendererLike, type InteractiveStageOptions, type LoadProgress, type RenderTargetCanvas } from '@chestnutlabs/gcode-renderer-three';
1
+ import { type CameraMode, type CameraState, type CameraView, type CaptureOptions, type GLRendererLike, type InteractiveStageOptions, type LoadProgress, type RenderTargetCanvas } from '@chestnutlabs/gcode-renderer-three';
2
2
  import type { Confidence } from '@chestnutlabs/toolpath-core';
3
3
  import { type ModelLoader, type ModelSourceInput } from './loaders.js';
4
4
  import { type ModelLimits } from './limits.js';
5
5
  import type { ModelBackground, PresentationView } from './model-renderer.js';
6
- import { type ModelBounds, type ModelPlateSummary } from './scene-model.js';
6
+ import { type ModelBounds, type ModelPlateSummary, type RenderScope } from './scene-model.js';
7
7
  export interface ModelViewerOptions {
8
8
  /** Loader registry (open `kind`). Default: `[stlLoader, threeMfLoader]`. */
9
9
  loaders?: readonly ModelLoader[];
@@ -21,6 +21,8 @@ export interface ModelViewerOptions {
21
21
  createRenderer?: (canvas: RenderTargetCanvas) => GLRendererLike;
22
22
  /** Injected orbit controls (tests). Default: three `OrbitControls` via the stage. */
23
23
  createControls?: NonNullable<InteractiveStageOptions['createControls']>;
24
+ /** Initial render scope (DD-030 D2) — e.g. `{ plateId: 3 }`. Omit to render the whole source. */
25
+ renderScope?: RenderScope;
24
26
  /**
25
27
  * Staged loading-progress callback (DD-024): typed `stage`/`done`/`total`/`unit` events (no human copy),
26
28
  * each tagged with the load `generation` so a consumer drops events from a superseded `setSource`. The
@@ -86,6 +88,17 @@ export interface ModelViewer {
86
88
  resize(width: number, height: number): void;
87
89
  /** Re-fit the camera to the current model bounds. */
88
90
  frame(): void;
91
+ /**
92
+ * Render only a subset of the current source (DD-030 D2): `{ plateId }`, `{ objectIds }`, or an
93
+ * `{ instanceFilter }`; `null` restores the whole source. Rebuilds and reframes to the subset. Gate a
94
+ * `{ plateId }` on the ready info's `plates` being present — an unmatched scope renders empty.
95
+ */
96
+ setRenderScope(scope: RenderScope | null): void;
97
+ /**
98
+ * Capture the current view as an image `Blob` (DD-030 D1) — for an operator-selected thumbnail or a
99
+ * large-file fallback. Rejects with `E_CAPTURE_UNSUPPORTED` when the renderer is unavailable.
100
+ */
101
+ capture(opts?: CaptureOptions): Promise<Blob>;
89
102
  onEvent(cb: (e: ModelViewerEvent) => void): () => void;
90
103
  dispose(): void;
91
104
  }
@@ -1 +1 @@
1
- {"version":3,"file":"model-viewer.d.ts","sourceRoot":"","sources":["../src/model-viewer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"model-viewer.d.ts","sourceRoot":"","sources":["../src/model-viewer.ts"],"names":[],"mappings":"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"}
@@ -13,11 +13,11 @@
13
13
  * Interactive is a browser concern — headless/offscreen stills stay `renderModelStill` (DD-018 §4.3).
14
14
  */
15
15
  import { Color, Scene } from 'three';
16
- import { InteractiveStage, createDefaultGLRenderer } from '@chestnutlabs/gcode-renderer-three';
16
+ import { CaptureUnsupportedError, InteractiveStage, createDefaultGLRenderer } from '@chestnutlabs/gcode-renderer-three';
17
17
  import { ModelContent } from './model-content.js';
18
18
  import { DEFAULT_MODEL_LOADERS, resolveModelScene } from './loaders.js';
19
19
  import { ModelParseError } from './limits.js';
20
- import { sceneInstanceCount } from './scene-model.js';
20
+ import { applyRenderScope, sceneInstanceCount } from './scene-model.js';
21
21
  function backgroundColor(bg) {
22
22
  return bg === 'transparent' ? null : new Color(bg);
23
23
  }
@@ -48,7 +48,11 @@ export function createModelViewer(canvas, options = {}) {
48
48
  const content = new ModelContent(scene);
49
49
  // Retained so context-loss recovery can rebuild meshes from the last-set scene.
50
50
  let currentScene = null;
51
+ // Active render scope (DD-030 D2); applied to `currentScene` on build/reframe/restore. Null = whole source.
52
+ let currentScope = options.renderScope ?? null;
51
53
  let disposed = false;
54
+ /** The scene to actually build/render = the full source narrowed by the active scope. */
55
+ const scopedScene = () => currentScene === null ? null : currentScope ? applyRenderScope(currentScene, currentScope) : currentScene;
52
56
  // Monotonic token for last-wins overlapping setSource (stale async results are discarded).
53
57
  let sourceToken = 0;
54
58
  let stage = null;
@@ -65,8 +69,9 @@ export function createModelViewer(canvas, options = {}) {
65
69
  onContextRestored: () => {
66
70
  // GPU resources are gone; rebuild meshes from the retained scene and reframe, mirroring the
67
71
  // toolpath renderer's recovery.
68
- if (currentScene !== null && stage !== null) {
69
- content.setScene(currentScene);
72
+ const shown = scopedScene();
73
+ if (shown !== null && stage !== null) {
74
+ content.setScene(shown);
70
75
  const f = content.framing;
71
76
  if (f !== null)
72
77
  stage.frameTo(f.center, f.radius);
@@ -114,10 +119,11 @@ export function createModelViewer(canvas, options = {}) {
114
119
  // Last-wins: a newer setSource (or a dispose) superseded this one — discard without mutating.
115
120
  if (my !== sourceToken || disposed || stage === null)
116
121
  return info;
117
- const total = parsed.objects.length;
118
- progress({ stage: 'building-geometry', done: 0, total, unit: 'objects' });
119
- content.setScene(parsed, (done) => progress({ stage: 'building-geometry', done, total, unit: 'objects' }));
120
122
  currentScene = parsed;
123
+ const shown = scopedScene(); // parsed just set → non-null
124
+ const total = shown.objects.length;
125
+ progress({ stage: 'building-geometry', done: 0, total, unit: 'objects' });
126
+ content.setScene(shown, (done) => progress({ stage: 'building-geometry', done, total, unit: 'objects' }));
121
127
  const f = content.framing;
122
128
  if (f !== null)
123
129
  stage.frameTo(f.center, f.radius);
@@ -160,6 +166,23 @@ export function createModelViewer(canvas, options = {}) {
160
166
  if (f !== null)
161
167
  stage?.frameTo(f.center, f.radius);
162
168
  },
169
+ setRenderScope(scope) {
170
+ currentScope = scope;
171
+ const shown = scopedScene();
172
+ if (shown === null || stage === null)
173
+ return; // no source yet — applied on the next setSource
174
+ content.setScene(shown);
175
+ const f = content.framing;
176
+ if (f !== null)
177
+ stage.frameTo(f.center, f.radius);
178
+ stage.render();
179
+ },
180
+ capture(opts) {
181
+ if (stage === null) {
182
+ return Promise.reject(new CaptureUnsupportedError('model viewer renderer unavailable'));
183
+ }
184
+ return stage.capture(opts);
185
+ },
163
186
  onEvent(cb) {
164
187
  listeners.add(cb);
165
188
  if (pending.length > 0) {
@@ -13,7 +13,7 @@
13
13
  import type { Confidence } from '@chestnutlabs/toolpath-core';
14
14
  import type { GLRendererLike, RenderTargetCanvas } from '@chestnutlabs/gcode-renderer-three';
15
15
  import { type ModelBackground, type PresentationView } from './model-renderer.js';
16
- import { type ModelPlateSummary, type ModelScene } from './scene-model.js';
16
+ import { type ModelPlateSummary, type ModelScene, type RenderScope } from './scene-model.js';
17
17
  import type { ModelLimits } from './limits.js';
18
18
  /**
19
19
  * A source model: STL bytes, 3MF bytes, or a pre-built `ModelScene`. This concrete union is the
@@ -45,6 +45,13 @@ export interface RenderModelStillOptions {
45
45
  filamentPalette?: readonly (string | undefined)[];
46
46
  /** Override the environment id folded into the cache key (default: the `three` revision). */
47
47
  envId?: string;
48
+ /**
49
+ * Render only a subset of a multi-plate / multi-object source (DD-030 D2) — e.g. `{ plateId: 3 }` to
50
+ * render just plate 3 of a project, framed to that plate. Omit to render the whole project (default).
51
+ * `{ plateId }` is meaningful only when the source declares plates (`plates: 'known'`); on a source
52
+ * without plate structure it matches nothing and renders empty (the honest result).
53
+ */
54
+ renderScope?: RenderScope;
48
55
  }
49
56
  export interface RenderModelStillResult {
50
57
  canvas: RenderTargetCanvas;
@@ -76,10 +83,5 @@ export interface RenderModelStillResult {
76
83
  /** Stable identity for caching: `hash(source) + options + envId`. */
77
84
  cacheKey: string;
78
85
  }
79
- /**
80
- * Render one presentation still. Async because the 3MF path unzips with the container reader
81
- * (`DecompressionStream`); the STL and pre-built-`ModelScene` paths resolve promptly. Mirrors the
82
- * async `renderStill` on the toolpath side.
83
- */
84
86
  export declare function renderModelStill(source: ModelSource, options: RenderModelStillOptions): Promise<RenderModelStillResult>;
85
87
  //# sourceMappingURL=render-model-still.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"render-model-still.d.ts","sourceRoot":"","sources":["../src/render-model-still.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAC7F,OAAO,EAAiB,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEjG,OAAO,EAAsB,KAAK,iBAAiB,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE/F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;GAIG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAAA;CAAE,GAChD,UAAU,CAAC;AAEf,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mGAAmG;IACnG,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,qCAAqC;IACrC,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,cAAc,CAAC;IAChE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;OAIG;IACH,eAAe,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;IAClD,6FAA6F;IAC7F,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,sGAAsG;IACtG,SAAS,EAAE,UAAU,CAAC;IACtB;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAkBD;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,sBAAsB,CAAC,CAoCjC"}
1
+ {"version":3,"file":"render-model-still.d.ts","sourceRoot":"","sources":["../src/render-model-still.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAC7F,OAAO,EAAiB,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEjG,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,UAAU,EACf,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;GAIG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAAA;CAAE,GAChD,UAAU,CAAC;AAEf,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mGAAmG;IACnG,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,qCAAqC;IACrC,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,cAAc,CAAC;IAChE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;OAIG;IACH,eAAe,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;IAClD,6FAA6F;IAC7F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,sGAAsG;IACtG,SAAS,EAAE,UAAU,CAAC;IACtB;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAkCD,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,sBAAsB,CAAC,CA+CjC"}
@@ -1,6 +1,6 @@
1
1
  import { ModelRenderer } from './model-renderer.js';
2
2
  import { isModelScene, resolveModelScene } from './loaders.js';
3
- import { sceneInstanceCount } from './scene-model.js';
3
+ import { applyRenderScope, sceneInstanceCount } from './scene-model.js';
4
4
  import { computeCacheKey } from './cache-key.js';
5
5
  async function toScene(source, limits, filamentPalette) {
6
6
  if (isModelScene(source)) {
@@ -18,13 +18,27 @@ async function toScene(source, limits, filamentPalette) {
18
18
  * (`DecompressionStream`); the STL and pre-built-`ModelScene` paths resolve promptly. Mirrors the
19
19
  * async `renderStill` on the toolpath side.
20
20
  */
21
+ /** Counter that makes an opaque `instanceFilter` scope's cache key unique (never a false cache hit). */
22
+ let scopeKeyCounter = 0;
23
+ /** A stable, serializable cache-key fragment for a render scope (DD-030 D2). */
24
+ function scopeCacheKey(scope) {
25
+ if (!scope)
26
+ return undefined;
27
+ if ('plateId' in scope)
28
+ return { p: scope.plateId };
29
+ if ('objectIds' in scope)
30
+ return { o: [...scope.objectIds].sort() };
31
+ return { fn: ++scopeKeyCounter }; // opaque predicate → non-cacheable, unique each call
32
+ }
21
33
  export async function renderModelStill(source, options) {
22
34
  const { canvas } = options;
23
35
  const width = options.width ?? canvas.width;
24
36
  const height = options.height ?? canvas.height;
25
37
  const background = options.background ?? 'transparent';
26
38
  const view = options.view ?? 'iso';
27
- const { scene, sourceBytes } = await toScene(source, options.limits, options.filamentPalette);
39
+ const { scene: fullScene, sourceBytes } = await toScene(source, options.limits, options.filamentPalette);
40
+ // DD-030 D2: narrow to the requested plate/object subset (framing follows the filtered bounds).
41
+ const scene = options.renderScope ? applyRenderScope(fullScene, options.renderScope) : fullScene;
28
42
  const renderer = new ModelRenderer({
29
43
  canvas,
30
44
  background,
@@ -35,7 +49,16 @@ export async function renderModelStill(source, options) {
35
49
  renderer.resize(width, height);
36
50
  renderer.setScene(scene);
37
51
  renderer.render();
38
- const optionsJson = JSON.stringify({ width, height, background, view, palette: options.filamentPalette });
52
+ const optionsJson = JSON.stringify({
53
+ width,
54
+ height,
55
+ background,
56
+ view,
57
+ palette: options.filamentPalette,
58
+ // Fold the scope so plate-1 and plate-2 thumbnails key distinctly. An opaque `instanceFilter` can't
59
+ // be serialized — such a still is marked non-cacheable (consumers should not reuse it by key).
60
+ scope: scopeCacheKey(options.renderScope)
61
+ });
39
62
  const cacheKey = computeCacheKey(sourceBytes, optionsJson, options.envId);
40
63
  return {
41
64
  canvas,
@@ -118,6 +118,29 @@ export interface ModelScene {
118
118
  plates: Confidence;
119
119
  };
120
120
  }
121
+ /**
122
+ * A render-scope selector (DD-030 D2): which objects/placements of a scene to render. Generic and
123
+ * vendor-neutral — a plate is one way to derive the subset. `{plateId}` is sugar over the placement's
124
+ * {@link ModelObject.plateIds} (meaningful only when `capabilities.plates === 'known'`; on a scene with
125
+ * no plate structure it matches nothing → an empty scene, the honest result of selecting a plate that
126
+ * isn't declared). `{objectIds}` and `{instanceFilter}` need no capability (ids are always known).
127
+ */
128
+ export type RenderScope = {
129
+ objectIds: string[];
130
+ } | {
131
+ plateId: number;
132
+ } | {
133
+ instanceFilter: (objectId: string, placementIndex: number) => boolean;
134
+ };
135
+ /**
136
+ * Return a filtered copy of `scene` containing only the objects/placements the scope selects, with
137
+ * `bounds` recomputed from the kept placements (so a consumer frames just the selected plate/subset).
138
+ * Pure and three-free. Objects with no kept placement are dropped; an empty result (nothing matched) is
139
+ * a valid, honest scene — a `{plateId}` that matches nothing renders empty by design (gate on
140
+ * `capabilities.plates === 'known'`). `scene.plates`/`capabilities` are preserved (the source's declared
141
+ * structure is unchanged; only what is *rendered* is narrowed).
142
+ */
143
+ export declare function applyRenderScope(scene: ModelScene, scope: RenderScope): ModelScene;
121
144
  /** The identity transform (column-major), for single-object sources. */
122
145
  export declare const IDENTITY_MAT4: Mat4;
123
146
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"scene-model.d.ts","sourceRoot":"","sources":["../src/scene-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,0BAA0B;AAC1B,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAE3C,iGAAiG;AACjG,MAAM,MAAM,IAAI,GAAG,SAAS,MAAM,EAAE,CAAC;AAErC,4FAA4F;AAC5F,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,GAAG,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,wGAAwG;AACxG,MAAM,WAAW,YAAY;IAC3B,kDAAkD;IAClD,SAAS,EAAE,YAAY,CAAC;IACxB,kFAAkF;IAClF,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,iEAAiE;IACjE,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,gGAAgG;AAChG,MAAM,WAAW,aAAa;IAC5B,gFAAgF;IAChF,KAAK,CAAC,EAAE,GAAG,CAAC;CACb;AAED,wCAAwC;AACxC,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,QAAQ,EAAE,YAAY,CAAC;IACvB,iGAAiG;IACjG,SAAS,EAAE,IAAI,CAAC;IAChB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC;IACnB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,uGAAuG;IACvG,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oHAAoH;IACpH,WAAW,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,uGAAuG;AACvG,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,MAAM,EAAE,WAAW,CAAC;IACpB;;;;;OAKG;IACH,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD,0EAA0E;IAC1E,YAAY,EAAE;QACZ,iFAAiF;QACjF,SAAS,EAAE,UAAU,CAAC;QACtB,sFAAsF;QACtF,UAAU,EAAE,UAAU,CAAC;QACvB,+DAA+D;QAC/D,WAAW,EAAE,UAAU,CAAC;QACxB;4FACoF;QACpF,SAAS,EAAE,UAAU,CAAC;QACtB;gFACwE;QACxE,MAAM,EAAE,UAAU,CAAC;KACpB,CAAC;CACH;AAED,wEAAwE;AACxE,eAAO,MAAM,aAAa,EAAE,IAAuD,CAAC;AAEpF;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAI5D"}
1
+ {"version":3,"file":"scene-model.d.ts","sourceRoot":"","sources":["../src/scene-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,0BAA0B;AAC1B,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAE3C,iGAAiG;AACjG,MAAM,MAAM,IAAI,GAAG,SAAS,MAAM,EAAE,CAAC;AAErC,4FAA4F;AAC5F,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,GAAG,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,wGAAwG;AACxG,MAAM,WAAW,YAAY;IAC3B,kDAAkD;IAClD,SAAS,EAAE,YAAY,CAAC;IACxB,kFAAkF;IAClF,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,iEAAiE;IACjE,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,gGAAgG;AAChG,MAAM,WAAW,aAAa;IAC5B,gFAAgF;IAChF,KAAK,CAAC,EAAE,GAAG,CAAC;CACb;AAED,wCAAwC;AACxC,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,QAAQ,EAAE,YAAY,CAAC;IACvB,iGAAiG;IACjG,SAAS,EAAE,IAAI,CAAC;IAChB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC;IACnB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,uGAAuG;IACvG,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oHAAoH;IACpH,WAAW,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,uGAAuG;AACvG,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,MAAM,EAAE,WAAW,CAAC;IACpB;;;;;OAKG;IACH,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD,0EAA0E;IAC1E,YAAY,EAAE;QACZ,iFAAiF;QACjF,SAAS,EAAE,UAAU,CAAC;QACtB,sFAAsF;QACtF,UAAU,EAAE,UAAU,CAAC;QACvB,+DAA+D;QAC/D,WAAW,EAAE,UAAU,CAAC;QACxB;4FACoF;QACpF,SAAS,EAAE,UAAU,CAAC;QACtB;gFACwE;QACxE,MAAM,EAAE,UAAU,CAAC;KACpB,CAAC;CACH;AAED;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,GACvB;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GACnB;IAAE,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,KAAK,OAAO,CAAA;CAAE,CAAC;AA6C9E;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,GAAG,UAAU,CAyBlF;AAED,wEAAwE;AACxE,eAAO,MAAM,aAAa,EAAE,IAAuD,CAAC;AAEpF;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAI5D"}
@@ -1,3 +1,87 @@
1
+ /** Local axis-aligned bounds of a geometry's positions. */
2
+ function geometryAABB(positions) {
3
+ const min = [Infinity, Infinity, Infinity];
4
+ const max = [-Infinity, -Infinity, -Infinity];
5
+ for (let i = 0; i + 2 < positions.length; i += 3) {
6
+ for (let a = 0; a < 3; a++) {
7
+ const v = positions[i + a];
8
+ if (v < min[a])
9
+ min[a] = v;
10
+ if (v > max[a])
11
+ max[a] = v;
12
+ }
13
+ }
14
+ return { min, max };
15
+ }
16
+ /** Transform a point by a column-major (three `Matrix4.elements`) affine Mat4. */
17
+ function applyMat4(m, x, y, z) {
18
+ return [
19
+ m[0] * x + m[4] * y + m[8] * z + m[12],
20
+ m[1] * x + m[5] * y + m[9] * z + m[13],
21
+ m[2] * x + m[6] * y + m[10] * z + m[14]
22
+ ];
23
+ }
24
+ /** Union of the world AABBs of the given placements of an object's geometry, into `acc`. */
25
+ function accumulatePlacementBounds(geom, placements, acc) {
26
+ const { min: lmin, max: lmax } = geometryAABB(geom.positions);
27
+ if (lmin[0] > lmax[0])
28
+ return; // empty geometry
29
+ const mut = acc;
30
+ for (const m of placements) {
31
+ for (let cx = 0; cx < 2; cx++) {
32
+ for (let cy = 0; cy < 2; cy++) {
33
+ for (let cz = 0; cz < 2; cz++) {
34
+ const w = applyMat4(m, cx ? lmax[0] : lmin[0], cy ? lmax[1] : lmin[1], cz ? lmax[2] : lmin[2]);
35
+ for (let a = 0; a < 3; a++) {
36
+ if (w[a] < mut.min[a])
37
+ mut.min[a] = w[a];
38
+ if (w[a] > mut.max[a])
39
+ mut.max[a] = w[a];
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ }
46
+ /**
47
+ * Return a filtered copy of `scene` containing only the objects/placements the scope selects, with
48
+ * `bounds` recomputed from the kept placements (so a consumer frames just the selected plate/subset).
49
+ * Pure and three-free. Objects with no kept placement are dropped; an empty result (nothing matched) is
50
+ * a valid, honest scene — a `{plateId}` that matches nothing renders empty by design (gate on
51
+ * `capabilities.plates === 'known'`). `scene.plates`/`capabilities` are preserved (the source's declared
52
+ * structure is unchanged; only what is *rendered* is narrowed).
53
+ */
54
+ export function applyRenderScope(scene, scope) {
55
+ const matches = (o, i) => {
56
+ if ('objectIds' in scope)
57
+ return scope.objectIds.includes(o.id);
58
+ if ('plateId' in scope)
59
+ return o.plateIds?.[i] === scope.plateId;
60
+ return scope.instanceFilter(o.id, i);
61
+ };
62
+ const objects = [];
63
+ const bounds = { min: [Infinity, Infinity, Infinity], max: [-Infinity, -Infinity, -Infinity] };
64
+ for (const o of scene.objects) {
65
+ const placements = o.instances ?? [o.transform];
66
+ const kept = [];
67
+ for (let i = 0; i < placements.length; i++)
68
+ if (matches(o, i))
69
+ kept.push(i);
70
+ if (kept.length === 0)
71
+ continue;
72
+ const keptPlacements = kept.map((i) => placements[i]);
73
+ const next = {
74
+ ...o,
75
+ transform: keptPlacements[0],
76
+ instances: keptPlacements.length > 1 ? keptPlacements.slice() : undefined,
77
+ plateIds: o.plateIds ? kept.map((i) => o.plateIds[i]) : undefined
78
+ };
79
+ objects.push(next);
80
+ accumulatePlacementBounds(o.geometry, keptPlacements, bounds);
81
+ }
82
+ const finalBounds = objects.length === 0 ? { min: [0, 0, 0], max: [0, 0, 0] } : bounds;
83
+ return { ...scene, objects, bounds: finalBounds };
84
+ }
1
85
  /** The identity transform (column-major), for single-object sources. */
2
86
  export const IDENTITY_MAT4 = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
3
87
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chestnutlabs/gcode-model-renderer",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "description": "Three.js presentation renderer for source models (STL/3MF) — clean thumbnails/cards, distinct from toolpath inspection (DD-018).",
5
5
  "keywords": [
6
6
  "3d-printing",
@@ -51,9 +51,9 @@
51
51
  "test": "vitest run"
52
52
  },
53
53
  "dependencies": {
54
- "@chestnutlabs/gcode-containers": "0.17.0",
55
- "@chestnutlabs/gcode-renderer-three": "0.17.0",
56
- "@chestnutlabs/toolpath-core": "0.17.0"
54
+ "@chestnutlabs/gcode-containers": "0.19.0",
55
+ "@chestnutlabs/gcode-renderer-three": "0.19.0",
56
+ "@chestnutlabs/toolpath-core": "0.19.0"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@types/three": "0.178.0"