@chestnutlabs/gcode-model-renderer 0.11.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 +191 -19
- 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":"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"}
|
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
|
|
@@ -428,17 +538,27 @@ const EST_REJECT_MARGIN = 2;
|
|
|
428
538
|
/**
|
|
429
539
|
* Fast structural cost estimate (DD-022 Phase 0), run after the (tiny, for production-extension files)
|
|
430
540
|
* main part is parsed but BEFORE any large external geometry part is decompressed. Walks the build-item /
|
|
431
|
-
* component tree counting
|
|
432
|
-
* - a mesh
|
|
433
|
-
* - an external `p:path`
|
|
434
|
-
*
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
541
|
+
* component tree counting **placements** (all of them) and a lower-bound **unique**-triangle estimate:
|
|
542
|
+
* - a mesh master's triangles are counted **once per unique master** (already parsed), NOT per placement;
|
|
543
|
+
* - an external `p:path` part is estimated from its ZIP-directory uncompressed size / the conservative
|
|
544
|
+
* bytes-per-triangle bound, counted **once per unique path** (a safe under-count), treated as a leaf.
|
|
545
|
+
* Because instancing renders each master once (DD-022 Phase 1), the triangle estimate must be UNIQUE — a
|
|
546
|
+
* full-sheet plate of ~40 copies costs its ~1 master, not ~40× baked, so it is NOT rejected here. The
|
|
547
|
+
* placement count still guards an instance bomb (`maxInstances`). Throws {@link ModelParseError} for a
|
|
548
|
+
* clear instance-bomb / oversize file so it rejects in sub-second time instead of ~10 s of decompress-and-
|
|
549
|
+
* bake; files near the budget fall through to the exact per-triangle parse.
|
|
438
550
|
*/
|
|
439
551
|
function estimateSceneCost(mainPart, entryByName, lim) {
|
|
440
552
|
let instanceCount = 0;
|
|
441
553
|
let estTriangles = 0;
|
|
554
|
+
// Unique masters already counted toward the triangle estimate (keyed by objectid / external path), so a
|
|
555
|
+
// reused master is counted once — matching the instanced render, not the old per-placement bake.
|
|
556
|
+
const countedMasters = new Set();
|
|
557
|
+
const checkInstances = () => {
|
|
558
|
+
if (instanceCount > lim.maxInstances) {
|
|
559
|
+
throw new ModelParseError('E_MODEL_TOO_MANY_INSTANCES', `3MF declares more than ${lim.maxInstances} instance placements`);
|
|
560
|
+
}
|
|
561
|
+
};
|
|
442
562
|
const visit = (part, objectid, depth) => {
|
|
443
563
|
if (depth > MAX_DEPTH)
|
|
444
564
|
return;
|
|
@@ -447,23 +567,30 @@ function estimateSceneCost(mainPart, entryByName, lim) {
|
|
|
447
567
|
return;
|
|
448
568
|
if (obj.tris.length > 0) {
|
|
449
569
|
instanceCount++;
|
|
450
|
-
|
|
570
|
+
const key = `local:${objectid}`;
|
|
571
|
+
if (!countedMasters.has(key)) {
|
|
572
|
+
countedMasters.add(key);
|
|
573
|
+
estTriangles += obj.tris.length / 3;
|
|
574
|
+
}
|
|
575
|
+
checkInstances();
|
|
451
576
|
}
|
|
452
577
|
for (const comp of obj.components) {
|
|
453
578
|
if (comp.path !== undefined) {
|
|
454
579
|
// External production-extension part: estimate from its directory size WITHOUT decompressing it.
|
|
455
|
-
const
|
|
580
|
+
const norm = comp.path.replace(/^\//, '').toLowerCase();
|
|
581
|
+
const entry = entryByName(norm);
|
|
456
582
|
if (entry !== undefined) {
|
|
457
583
|
instanceCount++;
|
|
458
|
-
|
|
584
|
+
if (!countedMasters.has(norm)) {
|
|
585
|
+
countedMasters.add(norm);
|
|
586
|
+
estTriangles += entry.uncompressedSize / EST_MAX_BYTES_PER_TRIANGLE;
|
|
587
|
+
}
|
|
588
|
+
checkInstances();
|
|
459
589
|
}
|
|
460
590
|
}
|
|
461
591
|
else {
|
|
462
592
|
visit(part, comp.objectid, depth + 1); // local sub-object (already-parsed geometry)
|
|
463
593
|
}
|
|
464
|
-
if (instanceCount > lim.maxInstances) {
|
|
465
|
-
throw new ModelParseError('E_MODEL_TOO_MANY_INSTANCES', `3MF declares more than ${lim.maxInstances} instance placements`);
|
|
466
|
-
}
|
|
467
594
|
}
|
|
468
595
|
};
|
|
469
596
|
const roots = mainPart.items.length > 0
|
|
@@ -562,8 +689,11 @@ export async function parse3mf(source, limits, opts) {
|
|
|
562
689
|
}
|
|
563
690
|
}
|
|
564
691
|
}
|
|
565
|
-
//
|
|
692
|
+
// `model_settings.config` (Bambu/Orca) carries both the default extruder for unpainted facets AND the
|
|
693
|
+
// declared plate structure (DD-025) — read once.
|
|
566
694
|
let defaultExtruderIdx = 0;
|
|
695
|
+
let plateConfig = null;
|
|
696
|
+
let objectExtruders = new Map();
|
|
567
697
|
const modelSettingsEntry = entryByName('metadata/model_settings.config');
|
|
568
698
|
if (modelSettingsEntry !== undefined) {
|
|
569
699
|
try {
|
|
@@ -572,9 +702,13 @@ export async function parse3mf(source, limits, opts) {
|
|
|
572
702
|
const n = m !== null ? parseInt(m[1], 10) : NaN;
|
|
573
703
|
if (Number.isFinite(n) && n >= 1)
|
|
574
704
|
defaultExtruderIdx = n - 1;
|
|
705
|
+
plateConfig = parsePlateConfig(xml);
|
|
706
|
+
objectExtruders = parseObjectExtruders(xml);
|
|
575
707
|
}
|
|
576
708
|
catch {
|
|
577
709
|
defaultExtruderIdx = 0;
|
|
710
|
+
plateConfig = null;
|
|
711
|
+
objectExtruders = new Map();
|
|
578
712
|
}
|
|
579
713
|
}
|
|
580
714
|
const st = {
|
|
@@ -588,7 +722,13 @@ export async function parse3mf(source, limits, opts) {
|
|
|
588
722
|
min: [Infinity, Infinity, Infinity],
|
|
589
723
|
max: [-Infinity, -Infinity, -Infinity],
|
|
590
724
|
totalTris: 0,
|
|
591
|
-
totalInstances: 0
|
|
725
|
+
totalInstances: 0,
|
|
726
|
+
plateConfig,
|
|
727
|
+
objectExtruders,
|
|
728
|
+
currentPlateId: undefined,
|
|
729
|
+
plateBounds: new Map(),
|
|
730
|
+
plateInstanceCount: new Map(),
|
|
731
|
+
plateObjects: new Map()
|
|
592
732
|
};
|
|
593
733
|
// Resolve an object (mesh and/or components) within `part`, folding component transforms into the
|
|
594
734
|
// running placement `matrix` (DD-022) instead of baking vertices. `partKey` identifies the part so the
|
|
@@ -618,21 +758,33 @@ export async function parse3mf(source, limits, opts) {
|
|
|
618
758
|
const roots = mainPart.items.length > 0
|
|
619
759
|
? mainPart.items
|
|
620
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();
|
|
621
763
|
for (const inst of roots) {
|
|
622
764
|
if (inst.transform !== null)
|
|
623
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
|
+
}
|
|
624
773
|
await resolve(mainPart, inst.objectid, inst.transform, mainKey, 0);
|
|
625
774
|
}
|
|
626
775
|
if (st.built.length === 0)
|
|
627
776
|
throw new ModelParseError('E_MODEL_EMPTY', '3MF contains no renderable geometry');
|
|
628
777
|
// Attach the instance list to any master reached more than once (a single placement keeps its
|
|
629
|
-
// `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.
|
|
630
780
|
for (const rec of st.masters.values()) {
|
|
631
781
|
if (rec.instances.length > 1)
|
|
632
782
|
rec.object.instances = rec.instances;
|
|
783
|
+
if (rec.plates.length > 0)
|
|
784
|
+
rec.object.plateIds = rec.plates;
|
|
633
785
|
}
|
|
634
786
|
const bounds = { min: st.min, max: st.max };
|
|
635
|
-
|
|
787
|
+
const scene = {
|
|
636
788
|
objects: st.built,
|
|
637
789
|
bounds,
|
|
638
790
|
capabilities: {
|
|
@@ -641,7 +793,27 @@ export async function parse3mf(source, limits, opts) {
|
|
|
641
793
|
transforms: st.anyTransform ? 'known' : 'unavailable',
|
|
642
794
|
multiObject: st.built.length > 1 ? 'known' : 'unavailable',
|
|
643
795
|
// Instancing preserved when there are more placements than unique masters.
|
|
644
|
-
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'
|
|
645
799
|
}
|
|
646
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;
|
|
647
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"
|