@chestnutlabs/gcode-model-renderer 0.12.0 → 0.13.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 +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/model-content.d.ts +6 -2
- package/dist/model-content.d.ts.map +1 -1
- package/dist/model-content.js +10 -4
- package/dist/model-viewer.d.ts +17 -2
- package/dist/model-viewer.d.ts.map +1 -1
- package/dist/model-viewer.js +17 -1
- package/dist/render-model-still.d.ts +10 -1
- package/dist/render-model-still.d.ts.map +1 -1
- package/dist/render-model-still.js +1 -0
- package/dist/scene-model.d.ts +38 -0
- package/dist/scene-model.d.ts.map +1 -1
- package/dist/stl.d.ts.map +1 -1
- package/dist/stl.js +2 -1
- package/dist/three-mf.d.ts +29 -0
- package/dist/three-mf.d.ts.map +1 -1
- package/dist/three-mf.js +161 -6
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -21,5 +21,5 @@ 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
23
|
export { IDENTITY_MAT4 } from './scene-model.js';
|
|
24
|
-
export type { ModelScene, ModelObject, ModelMaterial, MeshGeometry, ModelBounds, RGB, Mat4 } from './scene-model.js';
|
|
24
|
+
export type { ModelScene, ModelObject, ModelMaterial, ModelPlateSummary, MeshGeometry, ModelBounds, RGB, Mat4 } from './scene-model.js';
|
|
25
25
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/model-content.d.ts
CHANGED
|
@@ -28,8 +28,12 @@ export declare class ModelContent {
|
|
|
28
28
|
private readonly disposables;
|
|
29
29
|
private framedModel;
|
|
30
30
|
constructor(scene: Scene);
|
|
31
|
-
/**
|
|
32
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Build meshes for the scene and record its framing. Replaces (and disposes) any previous meshes.
|
|
33
|
+
* `onObject(done)` (DD-024) is invoked after each object's mesh is built, with the running count, for
|
|
34
|
+
* real `building-geometry` progress.
|
|
35
|
+
*/
|
|
36
|
+
setScene(scene: ModelScene, onObject?: (done: number) => void): void;
|
|
33
37
|
/** The framing of the last-set scene (`null` before the first {@link setScene}). */
|
|
34
38
|
get framing(): ModelFraming | null;
|
|
35
39
|
private buildMesh;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-content.d.ts","sourceRoot":"","sources":["../src/model-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAWL,KAAK,EACL,OAAO,EACR,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAe,UAAU,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAKrE,yGAAyG;AACzG,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,qBAAa,YAAY;IAKX,OAAO,CAAC,QAAQ,CAAC,KAAK;IAJlC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,OAAO,CAAC,WAAW,CAA6B;gBAEnB,KAAK,EAAE,KAAK;IAezC
|
|
1
|
+
{"version":3,"file":"model-content.d.ts","sourceRoot":"","sources":["../src/model-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAWL,KAAK,EACL,OAAO,EACR,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAe,UAAU,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAKrE,yGAAyG;AACzG,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,qBAAa,YAAY;IAKX,OAAO,CAAC,QAAQ,CAAC,KAAK;IAJlC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAe;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,OAAO,CAAC,WAAW,CAA6B;gBAEnB,KAAK,EAAE,KAAK;IAezC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAcpE,oFAAoF;IACpF,IAAI,OAAO,IAAI,YAAY,GAAG,IAAI,CAEjC;IAED,OAAO,CAAC,SAAS;IAoCjB,OAAO,CAAC,WAAW;IAMnB,kGAAkG;IAClG,OAAO,IAAI,IAAI;CAGhB;AAED,2FAA2F;AAC3F,eAAO,MAAM,sBAAsB,EAAE,GAAmB,CAAC"}
|
package/dist/model-content.js
CHANGED
|
@@ -37,11 +37,17 @@ export class ModelContent {
|
|
|
37
37
|
fill.position.set(-0.7, 0.3, -0.5);
|
|
38
38
|
this.scene.add(fill);
|
|
39
39
|
}
|
|
40
|
-
/**
|
|
41
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Build meshes for the scene and record its framing. Replaces (and disposes) any previous meshes.
|
|
42
|
+
* `onObject(done)` (DD-024) is invoked after each object's mesh is built, with the running count, for
|
|
43
|
+
* real `building-geometry` progress.
|
|
44
|
+
*/
|
|
45
|
+
setScene(scene, onObject) {
|
|
42
46
|
this.clearMeshes();
|
|
43
|
-
for (
|
|
44
|
-
this.root.add(this.buildMesh(
|
|
47
|
+
for (let i = 0; i < scene.objects.length; i++) {
|
|
48
|
+
this.root.add(this.buildMesh(scene.objects[i]));
|
|
49
|
+
onObject?.(i + 1);
|
|
50
|
+
}
|
|
45
51
|
const [minX, minY, minZ] = scene.bounds.min;
|
|
46
52
|
const [maxX, maxY, maxZ] = scene.bounds.max;
|
|
47
53
|
const center = new Vector3((minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2);
|
package/dist/model-viewer.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { type CameraMode, type CameraState, type CameraView, type GLRendererLike, type InteractiveStageOptions, type RenderTargetCanvas } from '@chestnutlabs/gcode-renderer-three';
|
|
1
|
+
import { type CameraMode, type CameraState, type CameraView, 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 } from './scene-model.js';
|
|
6
|
+
import { type ModelBounds, type ModelPlateSummary } 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,12 @@ 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
|
+
/**
|
|
25
|
+
* Staged loading-progress callback (DD-024): typed `stage`/`done`/`total`/`unit` events (no human copy),
|
|
26
|
+
* each tagged with the load `generation` so a consumer drops events from a superseded `setSource`. The
|
|
27
|
+
* consumer owns all wording/i18n. Events from a superseded load are not emitted.
|
|
28
|
+
*/
|
|
29
|
+
onProgress?: (progress: LoadProgress) => void;
|
|
24
30
|
}
|
|
25
31
|
/** What the source actually carried, surfaced on `ready` (never fabricated — DD-001). */
|
|
26
32
|
export interface ModelReadyInfo {
|
|
@@ -38,6 +44,15 @@ export interface ModelReadyInfo {
|
|
|
38
44
|
* (DD-022 Phase 2); reserved so the field is stable.
|
|
39
45
|
*/
|
|
40
46
|
decimationApplied: number;
|
|
47
|
+
/**
|
|
48
|
+
* Declared plate structure (DD-025), present only when the source explicitly declares plates
|
|
49
|
+
* (`capabilities.plates === 'known'`). A consumer builds a plate selector from `plates.list`; absent for a
|
|
50
|
+
* single/plate-less source. `active` is the source's declared active plate id when present.
|
|
51
|
+
*/
|
|
52
|
+
plates?: {
|
|
53
|
+
list: ModelPlateSummary[];
|
|
54
|
+
active?: number;
|
|
55
|
+
};
|
|
41
56
|
}
|
|
42
57
|
export type ModelViewerEvent = {
|
|
43
58
|
type: 'ready';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-viewer.d.ts","sourceRoot":"","sources":["../src/model-viewer.ts"],"names":[],"mappings":"AAeA,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACxB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,EAA4C,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACjH,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAsB,KAAK,WAAW,EAAmB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"model-viewer.d.ts","sourceRoot":"","sources":["../src/model-viewer.ts"],"names":[],"mappings":"AAeA,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACxB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,EAA4C,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACjH,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAsB,KAAK,WAAW,EAAE,KAAK,iBAAiB,EAAmB,MAAM,kBAAkB,CAAC;AAEjH,MAAM,WAAW,kBAAkB;IACjC,4EAA4E;IAC5E,OAAO,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IACjC,oEAAoE;IACpE,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,oFAAoF;IACpF,kBAAkB,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACpC,0DAA0D;IAC1D,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,gEAAgE;IAChE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,2GAA2G;IAC3G,eAAe,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;IAClD,2FAA2F;IAC3F,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,cAAc,CAAC;IAChE,qFAAqF;IACrF,cAAc,CAAC,EAAE,WAAW,CAAC,uBAAuB,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACxE;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;CAC/C;AAED,yFAAyF;AACzF,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,UAAU,CAAC;IACtB,MAAM,EAAE,WAAW,CAAC;IACpB;;;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;CAEzD;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClE;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,GACxB;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,CAAC;AAEjC,MAAM,WAAW,WAAW;IAC1B,4FAA4F;IAC5F,SAAS,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAC5D,sEAAsE;IACtE,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,UAAU,GAAG,IAAI,CAAC;IACnD,gGAAgG;IAChG,cAAc,IAAI,WAAW,GAAG,IAAI,CAAC;IACrC,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IACzC,aAAa,CAAC,EAAE,EAAE,eAAe,GAAG,IAAI,CAAC;IACzC,qBAAqB,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI,CAAC;IAClD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,qDAAqD;IACrD,KAAK,IAAI,IAAI,CAAC;IACd,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACvD,OAAO,IAAI,IAAI,CAAC;CACjB;AAMD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,EAAE,OAAO,GAAE,kBAAuB,GAAG,WAAW,CAoJ3G"}
|
package/dist/model-viewer.js
CHANGED
|
@@ -84,12 +84,23 @@ export function createModelViewer(canvas, options = {}) {
|
|
|
84
84
|
}
|
|
85
85
|
async function setSource(input) {
|
|
86
86
|
const my = ++sourceToken;
|
|
87
|
+
// Progress is generation-scoped (DD-024 §4 D5): only emit while THIS load is current, so a superseded
|
|
88
|
+
// load can never advance the next load's progress. Typed fields only — no human copy.
|
|
89
|
+
const onProgress = options.onProgress;
|
|
90
|
+
const progress = (p) => {
|
|
91
|
+
if (onProgress !== undefined && my === sourceToken && !disposed)
|
|
92
|
+
onProgress({ ...p, generation: my });
|
|
93
|
+
};
|
|
87
94
|
if (stage === null || disposed) {
|
|
88
95
|
const message = disposed ? 'model viewer is disposed' : 'WebGL renderer unavailable';
|
|
89
96
|
emit({ type: 'error', code: 'E_MODEL_RENDERER_UNAVAILABLE', message });
|
|
90
97
|
throw new ModelParseError('E_MODEL_RENDERER_UNAVAILABLE', message);
|
|
91
98
|
}
|
|
92
99
|
try {
|
|
100
|
+
// Parse/resolve is a single opaque async step here (unzip + XML parse); its total is unknown ⇒ honest
|
|
101
|
+
// indeterminate, not a fabricated bar. Finer parse-stage counts are a later phase (threading into the
|
|
102
|
+
// loader/parser); Phase A closes the biggest gap — the model renderer had NO progress at all.
|
|
103
|
+
progress({ stage: 'parsing', indeterminate: true });
|
|
93
104
|
const parsed = await resolveModelScene(input, loaders, limits, filamentPalette ? { filamentPalette } : undefined);
|
|
94
105
|
const info = {
|
|
95
106
|
objectCount: parsed.objects.length,
|
|
@@ -98,14 +109,19 @@ export function createModelViewer(canvas, options = {}) {
|
|
|
98
109
|
instancedCount: sceneInstanceCount(parsed),
|
|
99
110
|
decimationApplied: 1
|
|
100
111
|
};
|
|
112
|
+
if (parsed.plates !== undefined)
|
|
113
|
+
info.plates = parsed.plates;
|
|
101
114
|
// Last-wins: a newer setSource (or a dispose) superseded this one — discard without mutating.
|
|
102
115
|
if (my !== sourceToken || disposed || stage === null)
|
|
103
116
|
return info;
|
|
104
|
-
|
|
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' }));
|
|
105
120
|
currentScene = parsed;
|
|
106
121
|
const f = content.framing;
|
|
107
122
|
if (f !== null)
|
|
108
123
|
stage.frameTo(f.center, f.radius);
|
|
124
|
+
progress({ stage: 'ready' });
|
|
109
125
|
emit({ type: 'ready', info });
|
|
110
126
|
return info;
|
|
111
127
|
}
|
|
@@ -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 ModelScene } from './scene-model.js';
|
|
16
|
+
import { type ModelPlateSummary, type ModelScene } 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
|
|
@@ -64,6 +64,15 @@ export interface RenderModelStillResult {
|
|
|
64
64
|
* size" the same way. Always 1 until model LOD lands (DD-022 Phase 2); reserved so the field is stable.
|
|
65
65
|
*/
|
|
66
66
|
decimationApplied: number;
|
|
67
|
+
/**
|
|
68
|
+
* Declared plate structure (DD-025), present only when the source explicitly declares plates
|
|
69
|
+
* (`capabilities.plates === 'known'`); absent for a single/plate-less source. `active` is the source's
|
|
70
|
+
* declared active plate id when present.
|
|
71
|
+
*/
|
|
72
|
+
plates?: {
|
|
73
|
+
list: ModelPlateSummary[];
|
|
74
|
+
active?: number;
|
|
75
|
+
};
|
|
67
76
|
/** Stable identity for caching: `hash(source) + options + envId`. */
|
|
68
77
|
cacheKey: string;
|
|
69
78
|
}
|
|
@@ -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,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
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"}
|
|
@@ -45,6 +45,7 @@ export async function renderModelStill(source, options) {
|
|
|
45
45
|
materials: scene.capabilities.materials,
|
|
46
46
|
instancedCount: sceneInstanceCount(scene),
|
|
47
47
|
decimationApplied: 1,
|
|
48
|
+
...(scene.plates !== undefined ? { plates: scene.plates } : {}),
|
|
48
49
|
cacheKey
|
|
49
50
|
};
|
|
50
51
|
}
|
package/dist/scene-model.d.ts
CHANGED
|
@@ -60,13 +60,48 @@ export interface ModelObject {
|
|
|
60
60
|
* transforms (GPU instancing), so memory scales with unique geometry, not copy count.
|
|
61
61
|
*/
|
|
62
62
|
instances?: Mat4[];
|
|
63
|
+
/**
|
|
64
|
+
* Per-**placement** plate membership (DD-025): `plateIds[i]` is the {@link ModelPlateSummary.id} (plater
|
|
65
|
+
* id) of placement `i` — aligned with {@link instances} when present, else index 0 is the single placement at
|
|
66
|
+
* {@link transform}. Plate membership lives on the placement, not the master, because the same reused
|
|
67
|
+
* master can be instantiated on more than one plate. Absent ⇒ the source declared no plate structure
|
|
68
|
+
* (`capabilities.plates: 'unavailable'`), never fabricated.
|
|
69
|
+
*/
|
|
70
|
+
plateIds?: number[];
|
|
63
71
|
/** Omitted ⇒ no source material ⇒ neutral default render + `capabilities.materials: 'unavailable'`. */
|
|
64
72
|
material?: ModelMaterial;
|
|
65
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* One declared plate in a multi-plate source (DD-025), paralleling the toolpath-side `PlateSummary`.
|
|
76
|
+
* Present only when the source **explicitly** declares plate structure (Bambu/Orca
|
|
77
|
+
* `Metadata/model_settings.config`); an undeclared/implicit single plate is not summarized here.
|
|
78
|
+
*/
|
|
79
|
+
export interface ModelPlateSummary {
|
|
80
|
+
/** The source's plater id (`plater_id`), stable within the scene. */
|
|
81
|
+
id: number;
|
|
82
|
+
/** Source-declared plate name when present and non-empty. */
|
|
83
|
+
name?: string;
|
|
84
|
+
/** Distinct master objects referenced by this plate's placements (a master shared across plates counts in each). */
|
|
85
|
+
objectCount: number;
|
|
86
|
+
/** Placements assigned to this plate. */
|
|
87
|
+
instanceCount: number;
|
|
88
|
+
/** Axis-aligned bounds of this plate's placements, in scene units. */
|
|
89
|
+
bounds: ModelBounds;
|
|
90
|
+
}
|
|
66
91
|
/** A presentation scene: one or more objects plus capability honesty about what the source carried. */
|
|
67
92
|
export interface ModelScene {
|
|
68
93
|
objects: ModelObject[];
|
|
69
94
|
bounds: ModelBounds;
|
|
95
|
+
/**
|
|
96
|
+
* Declared plate structure (DD-025). Present only when the source **explicitly** declares plates; a
|
|
97
|
+
* consumer shows a plate selector only when `capabilities.plates === 'known'`. `active` is the source's
|
|
98
|
+
* active/default plate id when declared (a consumer's natural initial selection — the renderer never
|
|
99
|
+
* forces all-plates).
|
|
100
|
+
*/
|
|
101
|
+
plates?: {
|
|
102
|
+
list: ModelPlateSummary[];
|
|
103
|
+
active?: number;
|
|
104
|
+
};
|
|
70
105
|
/** What the source actually carried (DD-001 ethos — never fabricated). */
|
|
71
106
|
capabilities: {
|
|
72
107
|
/** `'known'` when the source assigned colors/materials, else `'unavailable'`. */
|
|
@@ -78,6 +113,9 @@ export interface ModelScene {
|
|
|
78
113
|
/** `'known'` when the source reused geometry via instances (more placements than unique masters), so
|
|
79
114
|
* the scene preserved it as GPU instancing (DD-022); `'unavailable'` otherwise. */
|
|
80
115
|
instanced: Confidence;
|
|
116
|
+
/** `'known'` when the source **explicitly** declares plate structure (incl. an explicit single plate);
|
|
117
|
+
* `'unavailable'` for undeclared/implicit plate structure (DD-025). */
|
|
118
|
+
plates: Confidence;
|
|
81
119
|
};
|
|
82
120
|
}
|
|
83
121
|
/** The identity transform (column-major), for single-object sources. */
|
|
@@ -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,uGAAuG;IACvG,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED,uGAAuG;AACvG,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,MAAM,EAAE,WAAW,CAAC;IACpB,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;
|
|
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"}
|
package/dist/stl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stl.d.ts","sourceRoot":"","sources":["../src/stl.ts"],"names":[],"mappings":"AAQA,OAAO,EAAsD,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACvG,OAAO,EAAkC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAgB/E,0GAA0G;AAC1G,wBAAgB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"stl.d.ts","sourceRoot":"","sources":["../src/stl.ts"],"names":[],"mappings":"AAQA,OAAO,EAAsD,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACvG,OAAO,EAAkC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAgB/E,0GAA0G;AAC1G,wBAAgB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,UAAU,CAyD3F"}
|
package/dist/stl.js
CHANGED
package/dist/three-mf.d.ts
CHANGED
|
@@ -9,5 +9,34 @@ export interface Parse3mfOptions {
|
|
|
9
9
|
*/
|
|
10
10
|
filamentPalette?: readonly (string | undefined)[];
|
|
11
11
|
}
|
|
12
|
+
/** Declared plate structure parsed from `Metadata/model_settings.config` (Bambu/Orca) — DD-025. */
|
|
13
|
+
interface PlateConfig {
|
|
14
|
+
/** `${object_id}#${instance_id}` → plater_id, mapping each declared model-instance to its plate. */
|
|
15
|
+
membership: Map<string, number>;
|
|
16
|
+
/** plater_id → declared plate name (only non-empty names). */
|
|
17
|
+
names: Map<number, string>;
|
|
18
|
+
/** Declared plater_ids in source order. */
|
|
19
|
+
order: number[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parse the `<plate>` elements of a Bambu/Orca `Metadata/model_settings.config` (DD-025). Each `<plate>`
|
|
23
|
+
* carries a `plater_id` and a list of `<model_instance>` (`object_id` + `instance_id`) assigning the Nth
|
|
24
|
+
* placement of a build-item object to that plate. Clean-room from the observed format (parity with the
|
|
25
|
+
* RR-005 `paint_color` approach); tolerant scan — a malformed/absent config yields an empty result so the
|
|
26
|
+
* scene honestly reports `capabilities.plates: 'unavailable'` rather than a fabricated split.
|
|
27
|
+
*
|
|
28
|
+
* Returns `null` when no `<plate>` is declared (⇒ implicit single plate, `'unavailable'`).
|
|
29
|
+
*/
|
|
30
|
+
export declare function parsePlateConfig(xml: string): PlateConfig | null;
|
|
31
|
+
/**
|
|
32
|
+
* Parse per-object / per-part extruder assignments from `Metadata/model_settings.config` (Bambu/Orca) — a
|
|
33
|
+
* source-model colour convention where each object/part is solid-coloured by its assigned filament
|
|
34
|
+
* (`<object id="X">…<metadata key="extruder" value="N"/>`, and the same on nested `<part id="Y">`),
|
|
35
|
+
* 1-based into the `filament_colour` palette. Returns a map of id → 1-based extruder. Each object/part's own
|
|
36
|
+
* extruder is the FIRST `key="extruder"` in the window from its header to the next object/part header (so a
|
|
37
|
+
* part's extruder overrides its parent object's default). Empty map ⇒ no assignments declared.
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseObjectExtruders(xml: string): Map<string, number>;
|
|
12
40
|
export declare function parse3mf(source: Uint8Array | ArrayBuffer, limits?: ModelLimits, opts?: Parse3mfOptions): Promise<ModelScene>;
|
|
41
|
+
export {};
|
|
13
42
|
//# sourceMappingURL=three-mf.d.ts.map
|
package/dist/three-mf.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"three-mf.d.ts","sourceRoot":"","sources":["../src/three-mf.ts"],"names":[],"mappings":"AAqBA,OAAO,
|
|
1
|
+
{"version":3,"file":"three-mf.d.ts","sourceRoot":"","sources":["../src/three-mf.ts"],"names":[],"mappings":"AAqBA,OAAO,EAOL,KAAK,UAAU,EAEhB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAkC,KAAK,WAAW,EAAuB,MAAM,aAAa,CAAC;AAofpG,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,eAAe,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;CACnD;AAKD,mGAAmG;AACnG,UAAU,WAAW;IACnB,oGAAoG;IACpG,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,8DAA8D;IAC9D,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,2CAA2C;IAC3C,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAuBhE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAarE;AAyGD,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,UAAU,GAAG,WAAW,EAChC,MAAM,CAAC,EAAE,WAAW,EACpB,IAAI,CAAC,EAAE,eAAe,GACrB,OAAO,CAAC,UAAU,CAAC,CAsNrB"}
|
package/dist/three-mf.js
CHANGED
|
@@ -315,6 +315,29 @@ function parseModelPart(xml, lim) {
|
|
|
315
315
|
}
|
|
316
316
|
return { objects, palettes, unitScale, items };
|
|
317
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Record a placement's plate membership (DD-025): bump the plate's instance count, note the master it
|
|
320
|
+
* references (distinct-object count), and expand the plate's bounds by this placement's transformed AABB.
|
|
321
|
+
* A no-op when no plate is active (source declares no plates).
|
|
322
|
+
*/
|
|
323
|
+
function recordPlacementPlate(st, masterKey, localMin, localMax, affine) {
|
|
324
|
+
const p = st.currentPlateId;
|
|
325
|
+
if (p === undefined)
|
|
326
|
+
return;
|
|
327
|
+
st.plateInstanceCount.set(p, (st.plateInstanceCount.get(p) ?? 0) + 1);
|
|
328
|
+
let objs = st.plateObjects.get(p);
|
|
329
|
+
if (objs === undefined) {
|
|
330
|
+
objs = new Set();
|
|
331
|
+
st.plateObjects.set(p, objs);
|
|
332
|
+
}
|
|
333
|
+
objs.add(masterKey);
|
|
334
|
+
let b = st.plateBounds.get(p);
|
|
335
|
+
if (b === undefined) {
|
|
336
|
+
b = { min: [Infinity, Infinity, Infinity], max: [-Infinity, -Infinity, -Infinity] };
|
|
337
|
+
st.plateBounds.set(p, b);
|
|
338
|
+
}
|
|
339
|
+
expandBoundsByBox(b, localMin, localMax, affine);
|
|
340
|
+
}
|
|
318
341
|
/**
|
|
319
342
|
* Place one mesh object (DD-022 instancing). The FIRST time a master `${partKey}#${objectid}` is reached
|
|
320
343
|
* its geometry is built once in LOCAL space (unit-scaled, un-baked); every reach — first and repeats —
|
|
@@ -327,8 +350,11 @@ function placeMaster(obj, part, matrix, key, st, lim) {
|
|
|
327
350
|
const existing = st.masters.get(key);
|
|
328
351
|
if (existing !== undefined) {
|
|
329
352
|
existing.instances.push(mat4);
|
|
353
|
+
if (st.currentPlateId !== undefined)
|
|
354
|
+
existing.plates.push(st.currentPlateId);
|
|
330
355
|
st.totalInstances++;
|
|
331
356
|
expandBoundsByBox(st, existing.localMin, existing.localMax, affine);
|
|
357
|
+
recordPlacementPlate(st, key, existing.localMin, existing.localMax, affine);
|
|
332
358
|
return;
|
|
333
359
|
}
|
|
334
360
|
const triCount = obj.tris.length / 3;
|
|
@@ -403,12 +429,96 @@ function placeMaster(obj, part, matrix, key, st, lim) {
|
|
|
403
429
|
const object = { id: key, geometry, transform: mat4 };
|
|
404
430
|
if (obj.name !== undefined)
|
|
405
431
|
object.name = obj.name;
|
|
406
|
-
|
|
432
|
+
// Bambu/Orca per-object/part extruder solid colour (maintainer-approved): when the object declares no
|
|
433
|
+
// basematerials/colorgroup/paint colour, but the source assigns it an extruder and the project's
|
|
434
|
+
// `filament_colour` palette has that slot, colour the whole object with `palette[extruder - 1]` (a solid
|
|
435
|
+
// material colour). Honesty preserved: only when the source actually declares the mapping AND the palette
|
|
436
|
+
// resolves — otherwise the object stays neutral (`materials: 'unavailable'`). Never guessed.
|
|
437
|
+
if (!objHasColor && st.paintPalette.length > 0) {
|
|
438
|
+
const oid = key.substring(key.lastIndexOf('#') + 1);
|
|
439
|
+
const extruder = st.objectExtruders.get(oid);
|
|
440
|
+
if (extruder !== undefined) {
|
|
441
|
+
const col = st.paintPalette[extruder - 1];
|
|
442
|
+
if (col !== undefined) {
|
|
443
|
+
object.material = { color: col };
|
|
444
|
+
st.anyColor = true;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
st.masters.set(key, {
|
|
449
|
+
object,
|
|
450
|
+
instances: [mat4],
|
|
451
|
+
plates: st.currentPlateId !== undefined ? [st.currentPlateId] : [],
|
|
452
|
+
localMin,
|
|
453
|
+
localMax
|
|
454
|
+
});
|
|
407
455
|
st.built.push(object);
|
|
408
456
|
st.totalInstances++;
|
|
409
457
|
expandBoundsByBox(st, localMin, localMax, affine);
|
|
458
|
+
recordPlacementPlate(st, key, localMin, localMax, affine);
|
|
410
459
|
}
|
|
411
460
|
const hexPaletteToLinear = (hexes) => hexes.map((hex) => (hex !== undefined ? (srgbHexToLinear(hex) ?? [0.8, 0.8, 0.82]) : [0.8, 0.8, 0.82]));
|
|
461
|
+
/**
|
|
462
|
+
* Parse the `<plate>` elements of a Bambu/Orca `Metadata/model_settings.config` (DD-025). Each `<plate>`
|
|
463
|
+
* carries a `plater_id` and a list of `<model_instance>` (`object_id` + `instance_id`) assigning the Nth
|
|
464
|
+
* placement of a build-item object to that plate. Clean-room from the observed format (parity with the
|
|
465
|
+
* RR-005 `paint_color` approach); tolerant scan — a malformed/absent config yields an empty result so the
|
|
466
|
+
* scene honestly reports `capabilities.plates: 'unavailable'` rather than a fabricated split.
|
|
467
|
+
*
|
|
468
|
+
* Returns `null` when no `<plate>` is declared (⇒ implicit single plate, `'unavailable'`).
|
|
469
|
+
*/
|
|
470
|
+
export function parsePlateConfig(xml) {
|
|
471
|
+
const plateBlocks = xml.match(/<plate\b[\s\S]*?<\/plate>/gi);
|
|
472
|
+
if (plateBlocks === null || plateBlocks.length === 0)
|
|
473
|
+
return null;
|
|
474
|
+
const membership = new Map();
|
|
475
|
+
const names = new Map();
|
|
476
|
+
const order = [];
|
|
477
|
+
for (const block of plateBlocks) {
|
|
478
|
+
const idM = /key="plater_id"\s+value="(\d+)"/i.exec(block);
|
|
479
|
+
if (idM === null)
|
|
480
|
+
continue;
|
|
481
|
+
const plateId = parseInt(idM[1], 10);
|
|
482
|
+
if (!Number.isFinite(plateId))
|
|
483
|
+
continue;
|
|
484
|
+
order.push(plateId);
|
|
485
|
+
const nameM = /key="plater_name"\s+value="([^"]*)"/i.exec(block);
|
|
486
|
+
if (nameM !== null && nameM[1].length > 0)
|
|
487
|
+
names.set(plateId, nameM[1]);
|
|
488
|
+
// Each <model_instance> declares object_id + instance_id (both required to key a placement).
|
|
489
|
+
for (const inst of block.match(/<model_instance\b[\s\S]*?<\/model_instance>/gi) ?? []) {
|
|
490
|
+
const objM = /key="object_id"\s+value="(\d+)"/i.exec(inst);
|
|
491
|
+
const insM = /key="instance_id"\s+value="(\d+)"/i.exec(inst);
|
|
492
|
+
if (objM === null || insM === null)
|
|
493
|
+
continue;
|
|
494
|
+
membership.set(`${objM[1]}#${insM[1]}`, plateId);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return order.length > 0 ? { membership, names, order } : null;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Parse per-object / per-part extruder assignments from `Metadata/model_settings.config` (Bambu/Orca) — a
|
|
501
|
+
* source-model colour convention where each object/part is solid-coloured by its assigned filament
|
|
502
|
+
* (`<object id="X">…<metadata key="extruder" value="N"/>`, and the same on nested `<part id="Y">`),
|
|
503
|
+
* 1-based into the `filament_colour` palette. Returns a map of id → 1-based extruder. Each object/part's own
|
|
504
|
+
* extruder is the FIRST `key="extruder"` in the window from its header to the next object/part header (so a
|
|
505
|
+
* part's extruder overrides its parent object's default). Empty map ⇒ no assignments declared.
|
|
506
|
+
*/
|
|
507
|
+
export function parseObjectExtruders(xml) {
|
|
508
|
+
const map = new Map();
|
|
509
|
+
const headerRe = /<(?:object|part)\s+id="(\d+)"[^>]*>([\s\S]*?)(?=<(?:object|part)\s+id="|<\/config>|$)/g;
|
|
510
|
+
let m;
|
|
511
|
+
while ((m = headerRe.exec(xml)) !== null) {
|
|
512
|
+
const id = m[1];
|
|
513
|
+
const ext = /key="extruder"\s+value="(\d+)"/i.exec(m[2]);
|
|
514
|
+
if (ext !== null) {
|
|
515
|
+
const n = parseInt(ext[1], 10);
|
|
516
|
+
if (Number.isFinite(n) && n >= 1)
|
|
517
|
+
map.set(id, n);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return map;
|
|
521
|
+
}
|
|
412
522
|
/**
|
|
413
523
|
* Conservative UPPER bound on the XML bytes a single 3MF triangle (plus its amortized share of a shared
|
|
414
524
|
* vertex) occupies — used to turn an external part's uncompressed byte size into a LOWER bound on its
|
|
@@ -579,8 +689,11 @@ export async function parse3mf(source, limits, opts) {
|
|
|
579
689
|
}
|
|
580
690
|
}
|
|
581
691
|
}
|
|
582
|
-
//
|
|
692
|
+
// `model_settings.config` (Bambu/Orca) carries both the default extruder for unpainted facets AND the
|
|
693
|
+
// declared plate structure (DD-025) — read once.
|
|
583
694
|
let defaultExtruderIdx = 0;
|
|
695
|
+
let plateConfig = null;
|
|
696
|
+
let objectExtruders = new Map();
|
|
584
697
|
const modelSettingsEntry = entryByName('metadata/model_settings.config');
|
|
585
698
|
if (modelSettingsEntry !== undefined) {
|
|
586
699
|
try {
|
|
@@ -589,9 +702,13 @@ export async function parse3mf(source, limits, opts) {
|
|
|
589
702
|
const n = m !== null ? parseInt(m[1], 10) : NaN;
|
|
590
703
|
if (Number.isFinite(n) && n >= 1)
|
|
591
704
|
defaultExtruderIdx = n - 1;
|
|
705
|
+
plateConfig = parsePlateConfig(xml);
|
|
706
|
+
objectExtruders = parseObjectExtruders(xml);
|
|
592
707
|
}
|
|
593
708
|
catch {
|
|
594
709
|
defaultExtruderIdx = 0;
|
|
710
|
+
plateConfig = null;
|
|
711
|
+
objectExtruders = new Map();
|
|
595
712
|
}
|
|
596
713
|
}
|
|
597
714
|
const st = {
|
|
@@ -605,7 +722,13 @@ export async function parse3mf(source, limits, opts) {
|
|
|
605
722
|
min: [Infinity, Infinity, Infinity],
|
|
606
723
|
max: [-Infinity, -Infinity, -Infinity],
|
|
607
724
|
totalTris: 0,
|
|
608
|
-
totalInstances: 0
|
|
725
|
+
totalInstances: 0,
|
|
726
|
+
plateConfig,
|
|
727
|
+
objectExtruders,
|
|
728
|
+
currentPlateId: undefined,
|
|
729
|
+
plateBounds: new Map(),
|
|
730
|
+
plateInstanceCount: new Map(),
|
|
731
|
+
plateObjects: new Map()
|
|
609
732
|
};
|
|
610
733
|
// Resolve an object (mesh and/or components) within `part`, folding component transforms into the
|
|
611
734
|
// running placement `matrix` (DD-022) instead of baking vertices. `partKey` identifies the part so the
|
|
@@ -635,21 +758,33 @@ export async function parse3mf(source, limits, opts) {
|
|
|
635
758
|
const roots = mainPart.items.length > 0
|
|
636
759
|
? mainPart.items
|
|
637
760
|
: [...mainPart.objects.keys()].map((objectid) => ({ objectid, transform: null }));
|
|
761
|
+
// Per-object-id ordinal so the Nth build item of object X maps to plate `X#N` (DD-025).
|
|
762
|
+
const rootOrdinals = new Map();
|
|
638
763
|
for (const inst of roots) {
|
|
639
764
|
if (inst.transform !== null)
|
|
640
765
|
st.anyTransform = true;
|
|
766
|
+
if (st.plateConfig !== null) {
|
|
767
|
+
const ord = rootOrdinals.get(inst.objectid) ?? 0;
|
|
768
|
+
rootOrdinals.set(inst.objectid, ord + 1);
|
|
769
|
+
// Unmapped placement (declared plates but this one not listed) → the first declared plate, so
|
|
770
|
+
// plateIds stay complete and aligned; never fabricated silently beyond that fallback.
|
|
771
|
+
st.currentPlateId = st.plateConfig.membership.get(`${inst.objectid}#${ord}`) ?? st.plateConfig.order[0];
|
|
772
|
+
}
|
|
641
773
|
await resolve(mainPart, inst.objectid, inst.transform, mainKey, 0);
|
|
642
774
|
}
|
|
643
775
|
if (st.built.length === 0)
|
|
644
776
|
throw new ModelParseError('E_MODEL_EMPTY', '3MF contains no renderable geometry');
|
|
645
777
|
// Attach the instance list to any master reached more than once (a single placement keeps its
|
|
646
|
-
// `transform` and omits `instances`, per the contract).
|
|
778
|
+
// `transform` and omits `instances`, per the contract). Plate ids (DD-025) attach per placement,
|
|
779
|
+
// aligned with `instances` when present, else the single placement.
|
|
647
780
|
for (const rec of st.masters.values()) {
|
|
648
781
|
if (rec.instances.length > 1)
|
|
649
782
|
rec.object.instances = rec.instances;
|
|
783
|
+
if (rec.plates.length > 0)
|
|
784
|
+
rec.object.plateIds = rec.plates;
|
|
650
785
|
}
|
|
651
786
|
const bounds = { min: st.min, max: st.max };
|
|
652
|
-
|
|
787
|
+
const scene = {
|
|
653
788
|
objects: st.built,
|
|
654
789
|
bounds,
|
|
655
790
|
capabilities: {
|
|
@@ -658,7 +793,27 @@ export async function parse3mf(source, limits, opts) {
|
|
|
658
793
|
transforms: st.anyTransform ? 'known' : 'unavailable',
|
|
659
794
|
multiObject: st.built.length > 1 ? 'known' : 'unavailable',
|
|
660
795
|
// Instancing preserved when there are more placements than unique masters.
|
|
661
|
-
instanced: st.totalInstances > st.masters.size ? 'known' : 'unavailable'
|
|
796
|
+
instanced: st.totalInstances > st.masters.size ? 'known' : 'unavailable',
|
|
797
|
+
// Plate structure is `known` only when the source explicitly declared plates (DD-025).
|
|
798
|
+
plates: st.plateConfig !== null ? 'known' : 'unavailable'
|
|
662
799
|
}
|
|
663
800
|
};
|
|
801
|
+
const pc = st.plateConfig;
|
|
802
|
+
if (pc !== null) {
|
|
803
|
+
const list = pc.order.map((id) => {
|
|
804
|
+
const b = st.plateBounds.get(id);
|
|
805
|
+
const summary = {
|
|
806
|
+
id,
|
|
807
|
+
objectCount: st.plateObjects.get(id)?.size ?? 0,
|
|
808
|
+
instanceCount: st.plateInstanceCount.get(id) ?? 0,
|
|
809
|
+
bounds: b !== undefined ? { min: b.min, max: b.max } : { min: [0, 0, 0], max: [0, 0, 0] }
|
|
810
|
+
};
|
|
811
|
+
const name = pc.names.get(id);
|
|
812
|
+
if (name !== undefined)
|
|
813
|
+
summary.name = name;
|
|
814
|
+
return summary;
|
|
815
|
+
});
|
|
816
|
+
scene.plates = { list };
|
|
817
|
+
}
|
|
818
|
+
return scene;
|
|
664
819
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chestnutlabs/gcode-model-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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.
|
|
55
|
-
"@chestnutlabs/gcode-renderer-three": "0.
|
|
56
|
-
"@chestnutlabs/toolpath-core": "0.
|
|
54
|
+
"@chestnutlabs/gcode-containers": "0.13.0",
|
|
55
|
+
"@chestnutlabs/gcode-renderer-three": "0.13.0",
|
|
56
|
+
"@chestnutlabs/toolpath-core": "0.13.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/three": "0.178.0"
|