@onerjs/serializers 8.42.8 → 8.42.9
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/3MF/3mfSerializer.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Mesh } from "@onerjs/core/Meshes/mesh.js";
|
|
|
2
2
|
import { InstancedMesh } from "@onerjs/core/Meshes/instancedMesh.js";
|
|
3
3
|
import { type ThreeMfModelBuilder } from "./core/model/3mf.builder.js";
|
|
4
4
|
import { type I3mfModel } from "./core/model/3mf.interfaces.js";
|
|
5
|
-
import { AbstractThreeMfSerializer, type
|
|
5
|
+
import { AbstractThreeMfSerializer, type IThreeMfSerializerBaseOptions } from "./core/model/3mf.serializer.js";
|
|
6
6
|
/**
|
|
7
7
|
* Options controlling how meshes are exported into the 3MF model.
|
|
8
8
|
*
|
|
@@ -10,7 +10,7 @@ import { AbstractThreeMfSerializer, type IThreeMfSerializerOptions } from "./cor
|
|
|
10
10
|
* - These flags are kept generic here and are expected to be interpreted by the concrete serializer/model builder.
|
|
11
11
|
* - Defaults are set in AbstractThreeMfSerializer.DEFAULT_3MF_EXPORTER_OPTIONS.
|
|
12
12
|
*/
|
|
13
|
-
export interface
|
|
13
|
+
export interface IThreeMfSerializerOptions extends IThreeMfSerializerBaseOptions {
|
|
14
14
|
/**
|
|
15
15
|
* If true, export mesh instances (multiple references to the same geometry) when supported.
|
|
16
16
|
* If false, geometry may be duplicated depending on the concrete implementation.
|
|
@@ -29,11 +29,11 @@ export interface IBjsThreeMfSerializerOptions extends IThreeMfSerializerOptions
|
|
|
29
29
|
* - Submesh export is handled by extracting per-submesh vertex/index buffers so materials/colors can be preserved
|
|
30
30
|
* by downstream steps that attach per-object properties.
|
|
31
31
|
*/
|
|
32
|
-
export declare class
|
|
32
|
+
export declare class ThreeMfSerializer extends AbstractThreeMfSerializer<Mesh | InstancedMesh, IThreeMfSerializerOptions> {
|
|
33
33
|
/**
|
|
34
34
|
*
|
|
35
35
|
*/
|
|
36
|
-
static DefaultOptions:
|
|
36
|
+
static DefaultOptions: IThreeMfSerializerOptions;
|
|
37
37
|
/**
|
|
38
38
|
* Babylon's vertex buffer semantic for positions.
|
|
39
39
|
* Babylon uses string-based "kind" keys for vertex buffers.
|
|
@@ -47,7 +47,7 @@ export declare class BjsThreeMfSerializer extends AbstractThreeMfSerializer<Mesh
|
|
|
47
47
|
/**
|
|
48
48
|
* @param opts serializer options (merged with defaults in base class).
|
|
49
49
|
*/
|
|
50
|
-
constructor(opts?: Partial<
|
|
50
|
+
constructor(opts?: Partial<IThreeMfSerializerOptions>);
|
|
51
51
|
/**
|
|
52
52
|
* Build a 3MF model from Babylon meshes.
|
|
53
53
|
*
|
|
@@ -128,5 +128,5 @@ export declare class BjsThreeMfSerializer extends AbstractThreeMfSerializer<Mesh
|
|
|
128
128
|
* @param ref Output 3MF 3x4 matrix container (ref.values assigned).
|
|
129
129
|
* @returns ref, for chaining.
|
|
130
130
|
*/
|
|
131
|
-
private
|
|
131
|
+
private _handleBabylonTo3mfMatrixTransformToRef;
|
|
132
132
|
}
|
package/3MF/3mfSerializer.js
CHANGED
|
@@ -57,12 +57,12 @@ class IncrementalIdFactory {
|
|
|
57
57
|
* - Submesh export is handled by extracting per-submesh vertex/index buffers so materials/colors can be preserved
|
|
58
58
|
* by downstream steps that attach per-object properties.
|
|
59
59
|
*/
|
|
60
|
-
export class
|
|
60
|
+
export class ThreeMfSerializer extends AbstractThreeMfSerializer {
|
|
61
61
|
/**
|
|
62
62
|
* @param opts serializer options (merged with defaults in base class).
|
|
63
63
|
*/
|
|
64
64
|
constructor(opts = {}) {
|
|
65
|
-
super({ ...
|
|
65
|
+
super({ ...ThreeMfSerializer.DefaultOptions, ...opts });
|
|
66
66
|
}
|
|
67
67
|
/**
|
|
68
68
|
* Build a 3MF model from Babylon meshes.
|
|
@@ -108,7 +108,7 @@ export class BjsThreeMfSerializer extends AbstractThreeMfSerializer {
|
|
|
108
108
|
// Convert Babylon world matrix to 3MF build transform (3x4).
|
|
109
109
|
// This transform will be attached to the build item referencing the created object.
|
|
110
110
|
const worldTransform = babylonMesh.getWorldMatrix();
|
|
111
|
-
const buildTransform = this.
|
|
111
|
+
const buildTransform = this._handleBabylonTo3mfMatrixTransformToRef(worldTransform, Matrix3d.Zero());
|
|
112
112
|
// Submeshes can carry material/color boundaries in Babylon.
|
|
113
113
|
// When exportSubmeshes is enabled, we export each submesh as its own 3MF object so
|
|
114
114
|
// consumers can attach per-object properties (e.g. colors) later.
|
|
@@ -157,7 +157,7 @@ export class BjsThreeMfSerializer extends AbstractThreeMfSerializer {
|
|
|
157
157
|
// Look up the 3MF object created for this submesh in the first pass.
|
|
158
158
|
const objectRef = index.get(subMesh);
|
|
159
159
|
if (objectRef) {
|
|
160
|
-
modelBuilder.withBuild(objectRef.id, this.
|
|
160
|
+
modelBuilder.withBuild(objectRef.id, this._handleBabylonTo3mfMatrixTransformToRef(worldTransform, Matrix3d.Zero()));
|
|
161
161
|
continue;
|
|
162
162
|
}
|
|
163
163
|
}
|
|
@@ -210,7 +210,7 @@ export class BjsThreeMfSerializer extends AbstractThreeMfSerializer {
|
|
|
210
210
|
if (!allInd) {
|
|
211
211
|
return undefined;
|
|
212
212
|
}
|
|
213
|
-
const allPos = mesh.getVerticesData(
|
|
213
|
+
const allPos = mesh.getVerticesData(ThreeMfSerializer._PositionKind);
|
|
214
214
|
if (!allPos) {
|
|
215
215
|
return undefined;
|
|
216
216
|
}
|
|
@@ -299,8 +299,8 @@ export class BjsThreeMfSerializer extends AbstractThreeMfSerializer {
|
|
|
299
299
|
* @param ref Output 3MF 3x4 matrix container (ref.values assigned).
|
|
300
300
|
* @returns ref, for chaining.
|
|
301
301
|
*/
|
|
302
|
-
|
|
303
|
-
const tmp = tBjs.multiplyToRef(
|
|
302
|
+
_handleBabylonTo3mfMatrixTransformToRef(tBjs, ref) {
|
|
303
|
+
const tmp = tBjs.multiplyToRef(ThreeMfSerializer._R_BJS_TO_3MF, Matrix.Zero());
|
|
304
304
|
const a = tmp.m;
|
|
305
305
|
// a is Babylon row-major storage. Extract rows 0..3, cols 0..2 in 3MF order.
|
|
306
306
|
// 3MF order: m00 m01 m02 m10 m11 m12 m20 m21 m22 m30 m31 m32
|
|
@@ -311,12 +311,12 @@ export class BjsThreeMfSerializer extends AbstractThreeMfSerializer {
|
|
|
311
311
|
/**
|
|
312
312
|
*
|
|
313
313
|
*/
|
|
314
|
-
|
|
314
|
+
ThreeMfSerializer.DefaultOptions = { unit: ST_Unit.meter, exportInstances: false };
|
|
315
315
|
/**
|
|
316
316
|
* Babylon's vertex buffer semantic for positions.
|
|
317
317
|
* Babylon uses string-based "kind" keys for vertex buffers.
|
|
318
318
|
*/
|
|
319
|
-
|
|
319
|
+
ThreeMfSerializer._PositionKind = "position";
|
|
320
320
|
/**
|
|
321
321
|
* Basis conversion from Babylon coordinate system to the expected 3MF coordinate system.
|
|
322
322
|
*
|
|
@@ -324,5 +324,5 @@ BjsThreeMfSerializer._PositionKind = "position";
|
|
|
324
324
|
* - This is commonly used to convert between Y-up and Z-up conventions.
|
|
325
325
|
* - Verify this matches your pipeline (Babylon is typically left-handed Y-up).
|
|
326
326
|
*/
|
|
327
|
-
|
|
327
|
+
ThreeMfSerializer._R_BJS_TO_3MF = Matrix.RotationX(Math.PI / 2).multiply(Matrix.Scaling(1, -1, 1));
|
|
328
328
|
//# sourceMappingURL=3mfSerializer.js.map
|
package/3MF/3mfSerializer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"3mfSerializer.js","sourceRoot":"","sources":["../../../../dev/serializers/src/3MF/3mfSerializer.ts"],"names":[],"mappings":"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","sourcesContent":["// Babylonjs\r\nimport type { SubMesh } from \"core/Meshes\";\r\nimport { Mesh } from \"core/Meshes/mesh\";\r\nimport { InstancedMesh } from \"core/Meshes/instancedMesh\";\r\nimport { Matrix } from \"core/Maths/math\";\r\nimport { Tools } from \"core/Misc/tools\";\r\n\r\n// 3MF\r\nimport { ThreeMfMeshBuilder, type ThreeMfModelBuilder } from \"./core/model/3mf.builder\";\r\nimport { Matrix3d } from \"./core/model/3mf\";\r\n\r\nimport { ST_Unit, type I3mfModel, type I3mfObject } from \"./core/model/3mf.interfaces\";\r\nimport type { I3mfVertexData } from \"./core/model/3mf.types\";\r\nimport { AbstractThreeMfSerializer, type IThreeMfSerializerOptions } from \"./core/model/3mf.serializer\";\r\nimport { ThreeMfSerializerGlobalConfiguration } from \"./3mfSerializer.configuration\";\r\n\r\n/**\r\n *\r\n */\r\nclass IncrementalIdFactory {\r\n /** */\r\n _from: number;\r\n /** */\r\n _to: number;\r\n /** */\r\n _step: number;\r\n /** */\r\n _i: number;\r\n\r\n /**\r\n *\r\n * @param from\r\n * @param to\r\n * @param step\r\n */\r\n public constructor(from: number = 0, to: number = Number.MIN_SAFE_INTEGER, step: number = 1) {\r\n this._from = from;\r\n this._to = to;\r\n this._step = step;\r\n this._i = from;\r\n }\r\n\r\n /**\r\n *\r\n * @returns\r\n */\r\n public next(): number {\r\n if (this._i < this._to) {\r\n throw new Error(\"ST_ResourceID out of bound\");\r\n }\r\n const v = this._i;\r\n this._i += this._step;\r\n return v;\r\n }\r\n\r\n /**\r\n *\r\n * @returns\r\n */\r\n public reset(): IncrementalIdFactory {\r\n this._i = this._from;\r\n return this;\r\n }\r\n}\r\n\r\n/**\r\n * Options controlling how meshes are exported into the 3MF model.\r\n *\r\n * Notes:\r\n * - These flags are kept generic here and are expected to be interpreted by the concrete serializer/model builder.\r\n * - Defaults are set in AbstractThreeMfSerializer.DEFAULT_3MF_EXPORTER_OPTIONS.\r\n */\r\nexport interface IBjsThreeMfSerializerOptions extends IThreeMfSerializerOptions {\r\n /**\r\n * If true, export mesh instances (multiple references to the same geometry) when supported.\r\n * If false, geometry may be duplicated depending on the concrete implementation.\r\n */\r\n exportInstances?: boolean;\r\n}\r\n\r\n/**\r\n * Babylon.js to 3MF serializer.\r\n *\r\n * This serializer converts Babylon meshes into a 3MF model, then relies on the base class\r\n * (AbstractThreeMfSerializer) to package the OPC parts into a zip stream.\r\n *\r\n * Design notes:\r\n * - First pass: export \"source\" meshes (non-instances) and build an index to map Babylon mesh/submesh to 3MF object id.\r\n * - Second pass (optional): export instances as additional build items referencing the original object ids.\r\n * - Submesh export is handled by extracting per-submesh vertex/index buffers so materials/colors can be preserved\r\n * by downstream steps that attach per-object properties.\r\n */\r\nexport class BjsThreeMfSerializer extends AbstractThreeMfSerializer<Mesh | InstancedMesh, IBjsThreeMfSerializerOptions> {\r\n /**\r\n *\r\n */\r\n static DefaultOptions: IBjsThreeMfSerializerOptions = { unit: ST_Unit.meter, exportInstances: false };\r\n\r\n /**\r\n * Babylon's vertex buffer semantic for positions.\r\n * Babylon uses string-based \"kind\" keys for vertex buffers.\r\n */\r\n private static _PositionKind = \"position\";\r\n\r\n /**\r\n * Cached promise so we only attempt to load fflate once.\r\n * This prevents multiple concurrent LoadScriptAsync calls.\r\n */\r\n private _fflateReadyPromise?: Promise<any>;\r\n\r\n /**\r\n * @param opts serializer options (merged with defaults in base class).\r\n */\r\n public constructor(opts: Partial<IBjsThreeMfSerializerOptions> = {}) {\r\n super({ ...BjsThreeMfSerializer.DefaultOptions, ...opts });\r\n }\r\n\r\n /**\r\n * Build a 3MF model from Babylon meshes.\r\n *\r\n * Important: this method should not allocate huge intermediate data unless needed.\r\n * Submesh extraction does allocate new position/index arrays for each exported submesh.\r\n * @param builder\r\n * @param meshes\r\n * @returns\r\n */\r\n public override toModel(builder: ThreeMfModelBuilder, ...meshes: Array<Mesh | InstancedMesh>): I3mfModel {\r\n // avoid parasits..\r\n meshes = meshes.filter((m) => m instanceof Mesh || m instanceof InstancedMesh);\r\n\r\n const idFactory = new IncrementalIdFactory();\r\n\r\n const modelBuilder = builder;\r\n\r\n /**\r\n * Index mapping Babylon elements to the created 3MF object.\r\n *\r\n * Why:\r\n * - Instances need to reference the base object id.\r\n * - When exporting submeshes, instances should reference per-submesh objects rather than the whole mesh.\r\n *\r\n * Key type:\r\n * - When exportSubmeshes is true: we store entries for each SubMesh.\r\n * - Otherwise: we store entries for each Mesh.\r\n */\r\n const index = new Map<Mesh | SubMesh, I3mfObject>();\r\n\r\n /**\r\n * If exportInstances is enabled, we collect instanced meshes during the first pass and process them later.\r\n * If exportInstances is disabled, instances are ignored (they will not appear in the output).\r\n */\r\n const instances: Array<InstancedMesh> | null = this.options.exportInstances ? [] : null;\r\n\r\n // First pass: export base meshes (non-instances).\r\n // This creates the \"resource objects\" referenced by build items.\r\n for (let j = 0; j < meshes.length; j++) {\r\n const babylonMesh = meshes[j];\r\n if (babylonMesh instanceof InstancedMesh) {\r\n // Defer instance handling to the second pass.\r\n instances?.push(babylonMesh);\r\n continue;\r\n }\r\n\r\n const objectName = babylonMesh.name || `mesh${j}`;\r\n\r\n // Convert Babylon world matrix to 3MF build transform (3x4).\r\n // This transform will be attached to the build item referencing the created object.\r\n const worldTransform = babylonMesh.getWorldMatrix();\r\n const buildTransform = this._handleBjsTo3mfMatrixTransformToRef(worldTransform, Matrix3d.Zero());\r\n\r\n // Submeshes can carry material/color boundaries in Babylon.\r\n // When exportSubmeshes is enabled, we export each submesh as its own 3MF object so\r\n // consumers can attach per-object properties (e.g. colors) later.\r\n const subMeshes = babylonMesh.subMeshes;\r\n if (subMeshes === undefined) {\r\n // very unlikely...\r\n continue;\r\n }\r\n\r\n // Babylon.js automatically creates one SubMesh covering the whole mesh if you don’t define any.\r\n const isStandalone = subMeshes.length == 1;\r\n for (let k = 0; k < subMeshes.length; k++) {\r\n const subMesh = subMeshes[k];\r\n\r\n const data = this._extractSubMesh(babylonMesh, subMesh);\r\n\r\n if (data) {\r\n const submeshName = isStandalone ? `${objectName}` : `${objectName}_${k}`;\r\n\r\n const object = new ThreeMfMeshBuilder(idFactory.next()).withData(data).withName(submeshName).build();\r\n\r\n // Add object to resources.\r\n modelBuilder.withMesh(object);\r\n\r\n // Add a build item referencing the object at the mesh world transform.\r\n modelBuilder.withBuild(object.id, buildTransform);\r\n\r\n // Cache mapping for instances (instances will reference this object id).\r\n index.set(subMesh, object);\r\n }\r\n }\r\n }\r\n\r\n // Second pass: export instances as additional build items.\r\n //\r\n // In 3MF terms:\r\n // - We do not duplicate geometry for each instance.\r\n // - We emit multiple build items referencing the same object id, each with its own transform.\r\n if (instances && instances.length) {\r\n // Group instances by their source mesh to keep related builds close in the XML.\r\n const grouped = this._groupBy(instances, (i) => i.sourceMesh);\r\n\r\n for (const [_babylonMesh, _instances] of Array.from(grouped.entries())) {\r\n if (!_instances || !_instances.length) {\r\n continue;\r\n }\r\n\r\n for (let j = 0; j < _instances.length; j++) {\r\n const mesh = _instances[j];\r\n const worldTransform = mesh.getWorldMatrix();\r\n\r\n // If we exported submeshes, the base \"resource objects\" are per-submesh.\r\n // For an instance we emit a build item per submesh object.\r\n const subMeshes = _babylonMesh.subMeshes;\r\n\r\n for (let k = 0; k < subMeshes.length; k++) {\r\n const subMesh = subMeshes[k];\r\n\r\n // Look up the 3MF object created for this submesh in the first pass.\r\n const objectRef = index.get(subMesh);\r\n\r\n if (objectRef) {\r\n modelBuilder.withBuild(objectRef.id, this._handleBjsTo3mfMatrixTransformToRef(worldTransform, Matrix3d.Zero()));\r\n continue;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return modelBuilder.build();\r\n }\r\n\r\n /**\r\n * Ensure the zip library (fflate) is available in the current runtime.\r\n *\r\n * Host assumptions:\r\n * - This implementation relies on fflate being exposed on globalThis.fflate.\r\n * - If it is not present, it loads a script from ThreeMfSerializerGlobalConfiguration.FFLATEUrl using Babylon Tools.LoadScriptAsync.\r\n * @returns\r\n */\r\n public override async ensureZipLibReadyAsync(): Promise<any> {\r\n if (this._fflateReadyPromise) {\r\n return await this._fflateReadyPromise;\r\n }\r\n\r\n this._fflateReadyPromise = (async () => {\r\n // globalThis is the global object in all modern JS runtimes (browser, workers, Node, etc.).\r\n const g = globalThis as any;\r\n\r\n // If fflate is not already present, load it dynamically.\r\n // This assumes the loaded script sets globalThis.fflate.\r\n if (!g.fflate) {\r\n await Tools.LoadScriptAsync(ThreeMfSerializerGlobalConfiguration.FFLATEUrl);\r\n }\r\n\r\n return g.fflate;\r\n })();\r\n\r\n return await this._fflateReadyPromise;\r\n }\r\n\r\n /**\r\n * Extract a single SubMesh into a standalone vertex/index buffer pair.\r\n *\r\n * Why:\r\n * - 3MF mesh objects typically reference a contiguous vertex array and triangle indices.\r\n * - Babylon SubMesh references a slice of the global index buffer, but shares the vertex buffer.\r\n * - To serialize each submesh independently, we build a compacted vertex buffer containing only the used vertices\r\n * and remap indices accordingly.\r\n *\r\n * Complexity:\r\n * - O(indexCount) time, O(uniqueVerticesInSubmesh) additional memory.\r\n * @param mesh\r\n * @param sm\r\n * @returns\r\n */\r\n private _extractSubMesh(mesh: Mesh, sm: SubMesh): I3mfVertexData | undefined {\r\n const allInd = mesh.getIndices();\r\n if (!allInd) {\r\n return undefined;\r\n }\r\n\r\n const allPos = mesh.getVerticesData(BjsThreeMfSerializer._PositionKind);\r\n if (!allPos) {\r\n return undefined;\r\n }\r\n\r\n // Fast path: the submesh covers the full index buffer, so reuse the original arrays.\r\n // Note: This returns Babylon-owned arrays; if callers mutate, they will affect source mesh data.\r\n // Kept as-is to avoid extra allocations.\r\n if (sm.indexStart == 0 && sm.indexCount == allInd.length) {\r\n return {\r\n positions: allPos,\r\n indices: allInd,\r\n };\r\n }\r\n\r\n const indStart = sm.indexStart;\r\n\r\n // Map old vertex index -> new compacted vertex index.\r\n const map = new Map<number, number>();\r\n\r\n // Compacted positions (x,y,z repeated). We assemble into number[] then convert to Float32Array at the end.\r\n const newPositions: number[] = [];\r\n\r\n // Indices for the compacted vertex buffer.\r\n // Uint32Array is used to support large meshes; ensure downstream 3MF writer supports 32-bit indices if needed.\r\n const newIndices = new Uint32Array(sm.indexCount);\r\n\r\n for (let i = 0; i < sm.indexCount; i++) {\r\n const oldVi = allInd[indStart + i];\r\n\r\n let newVi = map.get(oldVi);\r\n if (newVi === undefined) {\r\n newVi = map.size;\r\n map.set(oldVi, newVi);\r\n\r\n // Copy the corresponding position (assumes positions are 3-floats per vertex).\r\n // If the source mesh uses a different stride or includes morph targets, this ignores them.\r\n const p = oldVi * 3;\r\n newPositions.push(allPos[p], allPos[p + 1], allPos[p + 2]);\r\n }\r\n\r\n newIndices[i] = newVi;\r\n }\r\n\r\n return {\r\n positions: new Float32Array(newPositions),\r\n indices: newIndices,\r\n };\r\n }\r\n\r\n /**\r\n * Group items by a computed key.\r\n * Used to group instances by sourceMesh so the resulting XML is easier to read and debug.\r\n * @param items\r\n * @param key\r\n * @returns\r\n */\r\n private _groupBy<T, K>(items: readonly T[], key: (v: T) => K): Map<K, T[]> {\r\n const m = new Map<K, T[]>();\r\n for (const it of items) {\r\n const k = key(it);\r\n const arr = m.get(k);\r\n if (arr) {\r\n arr.push(it);\r\n } else {\r\n m.set(k, [it]);\r\n }\r\n }\r\n return m;\r\n }\r\n\r\n /**\r\n * Basis conversion from Babylon coordinate system to the expected 3MF coordinate system.\r\n *\r\n * Here we rotate +90 degrees around X:\r\n * - This is commonly used to convert between Y-up and Z-up conventions.\r\n * - Verify this matches your pipeline (Babylon is typically left-handed Y-up).\r\n */\r\n private static readonly _R_BJS_TO_3MF = Matrix.RotationX(Math.PI / 2).multiply(Matrix.Scaling(1, -1, 1));\r\n\r\n /**\r\n * Converts a Babylon.js 4x4 matrix into a 3MF 3x4 transform matrix and writes the result into ref.\r\n *\r\n * Babylon.js conventions:\r\n * - Babylon exposes matrices with logical row/column indexing (M(row, column)).\r\n * - It stores the 16 coefficients in a contiguous array in row-major order:\r\n * [ M00, M01, M02, M03,\r\n * M10, M11, M12, M13,\r\n * M20, M21, M22, M23,\r\n * M30, M31, M32, M33 ]\r\n *\r\n * 3MF expectation:\r\n * - 3MF uses an affine transform represented as a 3x4 matrix (12 values).\r\n * - The values are taken from the first 3 columns of the 4x4 matrix, across the 4 rows:\r\n * m00 m01 m02 m10 m11 m12 m20 m21 m22 m30 m31 m32\r\n *\r\n * Steps:\r\n * 1) Compose Babylon transform with the basis change:\r\n * tmp = tBjs * _R_BJS_TO_3MF\r\n * 2) Extract the 12 coefficients in 3MF order from tmp.m.\r\n *\r\n * Interop note:\r\n * - Do not transpose here. We only reorder values to match the 3MF 3x4 serialization order.\r\n * - Transposition is only relevant when interfacing with code that assumes column-major storage.\r\n *\r\n * @param tBjs Babylon.js 4x4 matrix.\r\n * @param ref Output 3MF 3x4 matrix container (ref.values assigned).\r\n * @returns ref, for chaining.\r\n */\r\n private _handleBjsTo3mfMatrixTransformToRef(tBjs: Matrix, ref: Matrix3d): Matrix3d {\r\n const tmp = tBjs.multiplyToRef(BjsThreeMfSerializer._R_BJS_TO_3MF, Matrix.Zero());\r\n const a = tmp.m;\r\n\r\n // a is Babylon row-major storage. Extract rows 0..3, cols 0..2 in 3MF order.\r\n // 3MF order: m00 m01 m02 m10 m11 m12 m20 m21 m22 m30 m31 m32\r\n ref.values = [a[0], a[1], a[2], a[4], a[5], a[6], a[8], a[9], a[10], a[12], a[13], a[14]];\r\n return ref;\r\n }\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"3mfSerializer.js","sourceRoot":"","sources":["../../../../dev/serializers/src/3MF/3mfSerializer.ts"],"names":[],"mappings":"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","sourcesContent":["// Babylonjs\r\nimport type { SubMesh } from \"core/Meshes\";\r\nimport { Mesh } from \"core/Meshes/mesh\";\r\nimport { InstancedMesh } from \"core/Meshes/instancedMesh\";\r\nimport { Matrix } from \"core/Maths/math\";\r\nimport { Tools } from \"core/Misc/tools\";\r\n\r\n// 3MF\r\nimport { ThreeMfMeshBuilder, type ThreeMfModelBuilder } from \"./core/model/3mf.builder\";\r\nimport { Matrix3d } from \"./core/model/3mf\";\r\n\r\nimport { ST_Unit, type I3mfModel, type I3mfObject } from \"./core/model/3mf.interfaces\";\r\nimport type { I3mfVertexData } from \"./core/model/3mf.types\";\r\nimport { AbstractThreeMfSerializer, type IThreeMfSerializerBaseOptions } from \"./core/model/3mf.serializer\";\r\nimport { ThreeMfSerializerGlobalConfiguration } from \"./3mfSerializer.configuration\";\r\n\r\n/**\r\n *\r\n */\r\nclass IncrementalIdFactory {\r\n /** */\r\n _from: number;\r\n /** */\r\n _to: number;\r\n /** */\r\n _step: number;\r\n /** */\r\n _i: number;\r\n\r\n /**\r\n *\r\n * @param from\r\n * @param to\r\n * @param step\r\n */\r\n public constructor(from: number = 0, to: number = Number.MIN_SAFE_INTEGER, step: number = 1) {\r\n this._from = from;\r\n this._to = to;\r\n this._step = step;\r\n this._i = from;\r\n }\r\n\r\n /**\r\n *\r\n * @returns\r\n */\r\n public next(): number {\r\n if (this._i < this._to) {\r\n throw new Error(\"ST_ResourceID out of bound\");\r\n }\r\n const v = this._i;\r\n this._i += this._step;\r\n return v;\r\n }\r\n\r\n /**\r\n *\r\n * @returns\r\n */\r\n public reset(): IncrementalIdFactory {\r\n this._i = this._from;\r\n return this;\r\n }\r\n}\r\n\r\n/**\r\n * Options controlling how meshes are exported into the 3MF model.\r\n *\r\n * Notes:\r\n * - These flags are kept generic here and are expected to be interpreted by the concrete serializer/model builder.\r\n * - Defaults are set in AbstractThreeMfSerializer.DEFAULT_3MF_EXPORTER_OPTIONS.\r\n */\r\nexport interface IThreeMfSerializerOptions extends IThreeMfSerializerBaseOptions {\r\n /**\r\n * If true, export mesh instances (multiple references to the same geometry) when supported.\r\n * If false, geometry may be duplicated depending on the concrete implementation.\r\n */\r\n exportInstances?: boolean;\r\n}\r\n\r\n/**\r\n * Babylon.js to 3MF serializer.\r\n *\r\n * This serializer converts Babylon meshes into a 3MF model, then relies on the base class\r\n * (AbstractThreeMfSerializer) to package the OPC parts into a zip stream.\r\n *\r\n * Design notes:\r\n * - First pass: export \"source\" meshes (non-instances) and build an index to map Babylon mesh/submesh to 3MF object id.\r\n * - Second pass (optional): export instances as additional build items referencing the original object ids.\r\n * - Submesh export is handled by extracting per-submesh vertex/index buffers so materials/colors can be preserved\r\n * by downstream steps that attach per-object properties.\r\n */\r\nexport class ThreeMfSerializer extends AbstractThreeMfSerializer<Mesh | InstancedMesh, IThreeMfSerializerOptions> {\r\n /**\r\n *\r\n */\r\n static DefaultOptions: IThreeMfSerializerOptions = { unit: ST_Unit.meter, exportInstances: false };\r\n\r\n /**\r\n * Babylon's vertex buffer semantic for positions.\r\n * Babylon uses string-based \"kind\" keys for vertex buffers.\r\n */\r\n private static _PositionKind = \"position\";\r\n\r\n /**\r\n * Cached promise so we only attempt to load fflate once.\r\n * This prevents multiple concurrent LoadScriptAsync calls.\r\n */\r\n private _fflateReadyPromise?: Promise<any>;\r\n\r\n /**\r\n * @param opts serializer options (merged with defaults in base class).\r\n */\r\n public constructor(opts: Partial<IThreeMfSerializerOptions> = {}) {\r\n super({ ...ThreeMfSerializer.DefaultOptions, ...opts });\r\n }\r\n\r\n /**\r\n * Build a 3MF model from Babylon meshes.\r\n *\r\n * Important: this method should not allocate huge intermediate data unless needed.\r\n * Submesh extraction does allocate new position/index arrays for each exported submesh.\r\n * @param builder\r\n * @param meshes\r\n * @returns\r\n */\r\n public override toModel(builder: ThreeMfModelBuilder, ...meshes: Array<Mesh | InstancedMesh>): I3mfModel {\r\n // avoid parasits..\r\n meshes = meshes.filter((m) => m instanceof Mesh || m instanceof InstancedMesh);\r\n\r\n const idFactory = new IncrementalIdFactory();\r\n\r\n const modelBuilder = builder;\r\n\r\n /**\r\n * Index mapping Babylon elements to the created 3MF object.\r\n *\r\n * Why:\r\n * - Instances need to reference the base object id.\r\n * - When exporting submeshes, instances should reference per-submesh objects rather than the whole mesh.\r\n *\r\n * Key type:\r\n * - When exportSubmeshes is true: we store entries for each SubMesh.\r\n * - Otherwise: we store entries for each Mesh.\r\n */\r\n const index = new Map<Mesh | SubMesh, I3mfObject>();\r\n\r\n /**\r\n * If exportInstances is enabled, we collect instanced meshes during the first pass and process them later.\r\n * If exportInstances is disabled, instances are ignored (they will not appear in the output).\r\n */\r\n const instances: Array<InstancedMesh> | null = this.options.exportInstances ? [] : null;\r\n\r\n // First pass: export base meshes (non-instances).\r\n // This creates the \"resource objects\" referenced by build items.\r\n for (let j = 0; j < meshes.length; j++) {\r\n const babylonMesh = meshes[j];\r\n if (babylonMesh instanceof InstancedMesh) {\r\n // Defer instance handling to the second pass.\r\n instances?.push(babylonMesh);\r\n continue;\r\n }\r\n\r\n const objectName = babylonMesh.name || `mesh${j}`;\r\n\r\n // Convert Babylon world matrix to 3MF build transform (3x4).\r\n // This transform will be attached to the build item referencing the created object.\r\n const worldTransform = babylonMesh.getWorldMatrix();\r\n const buildTransform = this._handleBabylonTo3mfMatrixTransformToRef(worldTransform, Matrix3d.Zero());\r\n\r\n // Submeshes can carry material/color boundaries in Babylon.\r\n // When exportSubmeshes is enabled, we export each submesh as its own 3MF object so\r\n // consumers can attach per-object properties (e.g. colors) later.\r\n const subMeshes = babylonMesh.subMeshes;\r\n if (subMeshes === undefined) {\r\n // very unlikely...\r\n continue;\r\n }\r\n\r\n // Babylon.js automatically creates one SubMesh covering the whole mesh if you don’t define any.\r\n const isStandalone = subMeshes.length == 1;\r\n for (let k = 0; k < subMeshes.length; k++) {\r\n const subMesh = subMeshes[k];\r\n\r\n const data = this._extractSubMesh(babylonMesh, subMesh);\r\n\r\n if (data) {\r\n const submeshName = isStandalone ? `${objectName}` : `${objectName}_${k}`;\r\n\r\n const object = new ThreeMfMeshBuilder(idFactory.next()).withData(data).withName(submeshName).build();\r\n\r\n // Add object to resources.\r\n modelBuilder.withMesh(object);\r\n\r\n // Add a build item referencing the object at the mesh world transform.\r\n modelBuilder.withBuild(object.id, buildTransform);\r\n\r\n // Cache mapping for instances (instances will reference this object id).\r\n index.set(subMesh, object);\r\n }\r\n }\r\n }\r\n\r\n // Second pass: export instances as additional build items.\r\n //\r\n // In 3MF terms:\r\n // - We do not duplicate geometry for each instance.\r\n // - We emit multiple build items referencing the same object id, each with its own transform.\r\n if (instances && instances.length) {\r\n // Group instances by their source mesh to keep related builds close in the XML.\r\n const grouped = this._groupBy(instances, (i) => i.sourceMesh);\r\n\r\n for (const [_babylonMesh, _instances] of Array.from(grouped.entries())) {\r\n if (!_instances || !_instances.length) {\r\n continue;\r\n }\r\n\r\n for (let j = 0; j < _instances.length; j++) {\r\n const mesh = _instances[j];\r\n const worldTransform = mesh.getWorldMatrix();\r\n\r\n // If we exported submeshes, the base \"resource objects\" are per-submesh.\r\n // For an instance we emit a build item per submesh object.\r\n const subMeshes = _babylonMesh.subMeshes;\r\n\r\n for (let k = 0; k < subMeshes.length; k++) {\r\n const subMesh = subMeshes[k];\r\n\r\n // Look up the 3MF object created for this submesh in the first pass.\r\n const objectRef = index.get(subMesh);\r\n\r\n if (objectRef) {\r\n modelBuilder.withBuild(objectRef.id, this._handleBabylonTo3mfMatrixTransformToRef(worldTransform, Matrix3d.Zero()));\r\n continue;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return modelBuilder.build();\r\n }\r\n\r\n /**\r\n * Ensure the zip library (fflate) is available in the current runtime.\r\n *\r\n * Host assumptions:\r\n * - This implementation relies on fflate being exposed on globalThis.fflate.\r\n * - If it is not present, it loads a script from ThreeMfSerializerGlobalConfiguration.FFLATEUrl using Babylon Tools.LoadScriptAsync.\r\n * @returns\r\n */\r\n public override async ensureZipLibReadyAsync(): Promise<any> {\r\n if (this._fflateReadyPromise) {\r\n return await this._fflateReadyPromise;\r\n }\r\n\r\n this._fflateReadyPromise = (async () => {\r\n // globalThis is the global object in all modern JS runtimes (browser, workers, Node, etc.).\r\n const g = globalThis as any;\r\n\r\n // If fflate is not already present, load it dynamically.\r\n // This assumes the loaded script sets globalThis.fflate.\r\n if (!g.fflate) {\r\n await Tools.LoadScriptAsync(ThreeMfSerializerGlobalConfiguration.FFLATEUrl);\r\n }\r\n\r\n return g.fflate;\r\n })();\r\n\r\n return await this._fflateReadyPromise;\r\n }\r\n\r\n /**\r\n * Extract a single SubMesh into a standalone vertex/index buffer pair.\r\n *\r\n * Why:\r\n * - 3MF mesh objects typically reference a contiguous vertex array and triangle indices.\r\n * - Babylon SubMesh references a slice of the global index buffer, but shares the vertex buffer.\r\n * - To serialize each submesh independently, we build a compacted vertex buffer containing only the used vertices\r\n * and remap indices accordingly.\r\n *\r\n * Complexity:\r\n * - O(indexCount) time, O(uniqueVerticesInSubmesh) additional memory.\r\n * @param mesh\r\n * @param sm\r\n * @returns\r\n */\r\n private _extractSubMesh(mesh: Mesh, sm: SubMesh): I3mfVertexData | undefined {\r\n const allInd = mesh.getIndices();\r\n if (!allInd) {\r\n return undefined;\r\n }\r\n\r\n const allPos = mesh.getVerticesData(ThreeMfSerializer._PositionKind);\r\n if (!allPos) {\r\n return undefined;\r\n }\r\n\r\n // Fast path: the submesh covers the full index buffer, so reuse the original arrays.\r\n // Note: This returns Babylon-owned arrays; if callers mutate, they will affect source mesh data.\r\n // Kept as-is to avoid extra allocations.\r\n if (sm.indexStart == 0 && sm.indexCount == allInd.length) {\r\n return {\r\n positions: allPos,\r\n indices: allInd,\r\n };\r\n }\r\n\r\n const indStart = sm.indexStart;\r\n\r\n // Map old vertex index -> new compacted vertex index.\r\n const map = new Map<number, number>();\r\n\r\n // Compacted positions (x,y,z repeated). We assemble into number[] then convert to Float32Array at the end.\r\n const newPositions: number[] = [];\r\n\r\n // Indices for the compacted vertex buffer.\r\n // Uint32Array is used to support large meshes; ensure downstream 3MF writer supports 32-bit indices if needed.\r\n const newIndices = new Uint32Array(sm.indexCount);\r\n\r\n for (let i = 0; i < sm.indexCount; i++) {\r\n const oldVi = allInd[indStart + i];\r\n\r\n let newVi = map.get(oldVi);\r\n if (newVi === undefined) {\r\n newVi = map.size;\r\n map.set(oldVi, newVi);\r\n\r\n // Copy the corresponding position (assumes positions are 3-floats per vertex).\r\n // If the source mesh uses a different stride or includes morph targets, this ignores them.\r\n const p = oldVi * 3;\r\n newPositions.push(allPos[p], allPos[p + 1], allPos[p + 2]);\r\n }\r\n\r\n newIndices[i] = newVi;\r\n }\r\n\r\n return {\r\n positions: new Float32Array(newPositions),\r\n indices: newIndices,\r\n };\r\n }\r\n\r\n /**\r\n * Group items by a computed key.\r\n * Used to group instances by sourceMesh so the resulting XML is easier to read and debug.\r\n * @param items\r\n * @param key\r\n * @returns\r\n */\r\n private _groupBy<T, K>(items: readonly T[], key: (v: T) => K): Map<K, T[]> {\r\n const m = new Map<K, T[]>();\r\n for (const it of items) {\r\n const k = key(it);\r\n const arr = m.get(k);\r\n if (arr) {\r\n arr.push(it);\r\n } else {\r\n m.set(k, [it]);\r\n }\r\n }\r\n return m;\r\n }\r\n\r\n /**\r\n * Basis conversion from Babylon coordinate system to the expected 3MF coordinate system.\r\n *\r\n * Here we rotate +90 degrees around X:\r\n * - This is commonly used to convert between Y-up and Z-up conventions.\r\n * - Verify this matches your pipeline (Babylon is typically left-handed Y-up).\r\n */\r\n private static readonly _R_BJS_TO_3MF = Matrix.RotationX(Math.PI / 2).multiply(Matrix.Scaling(1, -1, 1));\r\n\r\n /**\r\n * Converts a Babylon.js 4x4 matrix into a 3MF 3x4 transform matrix and writes the result into ref.\r\n *\r\n * Babylon.js conventions:\r\n * - Babylon exposes matrices with logical row/column indexing (M(row, column)).\r\n * - It stores the 16 coefficients in a contiguous array in row-major order:\r\n * [ M00, M01, M02, M03,\r\n * M10, M11, M12, M13,\r\n * M20, M21, M22, M23,\r\n * M30, M31, M32, M33 ]\r\n *\r\n * 3MF expectation:\r\n * - 3MF uses an affine transform represented as a 3x4 matrix (12 values).\r\n * - The values are taken from the first 3 columns of the 4x4 matrix, across the 4 rows:\r\n * m00 m01 m02 m10 m11 m12 m20 m21 m22 m30 m31 m32\r\n *\r\n * Steps:\r\n * 1) Compose Babylon transform with the basis change:\r\n * tmp = tBjs * _R_BJS_TO_3MF\r\n * 2) Extract the 12 coefficients in 3MF order from tmp.m.\r\n *\r\n * Interop note:\r\n * - Do not transpose here. We only reorder values to match the 3MF 3x4 serialization order.\r\n * - Transposition is only relevant when interfacing with code that assumes column-major storage.\r\n *\r\n * @param tBjs Babylon.js 4x4 matrix.\r\n * @param ref Output 3MF 3x4 matrix container (ref.values assigned).\r\n * @returns ref, for chaining.\r\n */\r\n private _handleBabylonTo3mfMatrixTransformToRef(tBjs: Matrix, ref: Matrix3d): Matrix3d {\r\n const tmp = tBjs.multiplyToRef(ThreeMfSerializer._R_BJS_TO_3MF, Matrix.Zero());\r\n const a = tmp.m;\r\n\r\n // a is Babylon row-major storage. Extract rows 0..3, cols 0..2 in 3MF order.\r\n // 3MF order: m00 m01 m02 m10 m11 m12 m20 m21 m22 m30 m31 m32\r\n ref.values = [a[0], a[1], a[2], a[4], a[5], a[6], a[8], a[9], a[10], a[12], a[13], a[14]];\r\n return ref;\r\n }\r\n}\r\n"]}
|
|
@@ -9,7 +9,7 @@ import { ST_Unit } from "./3mf.interfaces.js";
|
|
|
9
9
|
* - These flags are kept generic here and are expected to be interpreted by the concrete serializer/model builder.
|
|
10
10
|
* - Defaults are set in AbstractThreeMfSerializer.DEFAULT_3MF_EXPORTER_OPTIONS.
|
|
11
11
|
*/
|
|
12
|
-
export interface
|
|
12
|
+
export interface IThreeMfSerializerBaseOptions {
|
|
13
13
|
/**
|
|
14
14
|
* define the unit. Default is millimeter
|
|
15
15
|
*/
|
|
@@ -45,7 +45,7 @@ export interface I3mfSerializer<T> {
|
|
|
45
45
|
* - Providing/initializing the zip implementation (ensureZipLibReadyAsync is abstract).
|
|
46
46
|
* - Defining how meshes map to 3MF objects (toModel is abstract).
|
|
47
47
|
*/
|
|
48
|
-
export declare abstract class AbstractThreeMfSerializer<T, O extends
|
|
48
|
+
export declare abstract class AbstractThreeMfSerializer<T, O extends IThreeMfSerializerBaseOptions> implements I3mfSerializer<T> {
|
|
49
49
|
private _o;
|
|
50
50
|
/**
|
|
51
51
|
* @param opts user-provided options overriding defaults.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"3mf.serializer.js","sourceRoot":"","sources":["../../../../../../dev/serializers/src/3MF/core/model/3mf.serializer.ts"],"names":[],"mappings":"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","sourcesContent":["// 3MF\r\nimport { ThreeMfDocumentBuilder, ThreeMfModelBuilder } from \"./3mf.builder\";\r\nimport { ContentTypeFileName, ModelFileName, Object3dDirName, RelationshipDirName, RelationshipFileName, type I3mfDocument } from \"./3mf.opc.interfaces\";\r\nimport type { I3mfModel } from \"./3mf.interfaces\";\r\nimport { ST_Unit } from \"./3mf.interfaces\";\r\n\r\n// XML\r\nimport { XmlBuilder } from \"../xml/xml.builder\";\r\nimport { XmlSerializer } from \"../xml/xml.serializer\";\r\nimport { type ByteSink, Utf8XmlWriterToBytes } from \"../xml/xml.builder.bytes\";\r\n\r\n/**\r\n * Options controlling how meshes are exported into the 3MF model.\r\n *\r\n * Notes:\r\n * - These flags are kept generic here and are expected to be interpreted by the concrete serializer/model builder.\r\n * - Defaults are set in AbstractThreeMfSerializer.DEFAULT_3MF_EXPORTER_OPTIONS.\r\n */\r\nexport interface IThreeMfSerializerOptions {\r\n /**\r\n * define the unit. Default is millimeter\r\n */\r\n unit?: ST_Unit;\r\n\r\n /**\r\n *\r\n */\r\n metadata?: Record<string, string>;\r\n}\r\n\r\n/**\r\n * Minimal contract for a 3MF serializer that can stream its output through a sink callback.\r\n *\r\n * The sink callback receives:\r\n * - err: any error produced by the serialization pipeline (if any)\r\n * - chunk: a chunk of bytes to append to the destination\r\n * - final: true when this is the last chunk\r\n *\r\n * Important:\r\n * - Implementations should call sink with final=true exactly once, or rely on the underlying zip lib to do so.\r\n * - Consumers may ignore final if they just buffer everything.\r\n */\r\nexport interface I3mfSerializer<T> {\r\n serializeAsync(sink: (err: any, chunk: Uint8Array, final: boolean) => void, ...meshes: Array<T>): Promise<void>;\r\n}\r\n\r\n/**\r\n * Base class for 3MF serialization.\r\n *\r\n * Responsibilities:\r\n * - Convert user meshes to a 3MF model (toModel).\r\n * - Wrap the 3MF document parts into an OPC container (zip) and stream bytes through the sink.\r\n *\r\n * Non-responsibilities:\r\n * - Providing/initializing the zip implementation (ensureZipLibReadyAsync is abstract).\r\n * - Defining how meshes map to 3MF objects (toModel is abstract).\r\n */\r\nexport abstract class AbstractThreeMfSerializer<T, O extends IThreeMfSerializerOptions> implements I3mfSerializer<T> {\r\n private _o: O;\r\n\r\n /**\r\n * @param opts user-provided options overriding defaults.\r\n */\r\n public constructor(opts: O) {\r\n this._o = opts;\r\n }\r\n\r\n /**\r\n * Expose the resolved options (defaults + overrides) as readonly.\r\n */\r\n public get options(): Readonly<O> {\r\n return this._o;\r\n }\r\n\r\n /**\r\n * Generic 3MF binary serializer.\r\n * Pipeline overview:\r\n * 1. ensureZipLibReadyAsync provides a zip implementation (host-dependent).\r\n * 2. Convert meshes into an I3mfDocument (OPC parts + model).\r\n * 3. Create a zip target that streams through the provided sink.\r\n * 4. Serialize XML parts into zip entries.\r\n * 5. End the zip stream.\r\n * @param sink a callback receiving byte chunks; enables streaming without buffering the full archive in memory.\r\n * @param meshes the meshes to serialize.\r\n * @returns\r\n */\r\n public async serializeAsync(sink: (err: any, chunk: Uint8Array, final: boolean) => void, ...meshes: Array<T>): Promise<void> {\r\n // The zip library (e.g. fflate) may need dynamic import / polyfills depending on host.\r\n // This is delegated to the concrete serializer implementation.\r\n const lib = await this.ensureZipLibReadyAsync();\r\n\r\n // If no lib is available, we silently do nothing today.\r\n // Consider: throwing could be better to fail fast. Kept as-is to preserve current behavior.\r\n if (!lib) {\r\n return;\r\n }\r\n\r\n const zip = lib.Zip;\r\n const zipDeflate = lib.ZipDeflate;\r\n\r\n // We expect a \"fflate-like\" API. If it is missing, fail with an explicit error.\r\n if (!zip || !zipDeflate) {\r\n throw new Error(\"fflate Zip / ZipDeflate not available\");\r\n }\r\n\r\n /**\r\n * Adapt a fflate entry to our ByteSink interface used by Utf8XmlWriterToBytes.\r\n *\r\n * fflate entry.push(chunk, final) is the writer endpoint for compressed bytes.\r\n * @param entry\r\n * @returns\r\n */\r\n const makeByteSinkFromFflateEntry = function (entry: any): ByteSink {\r\n return { push: (chunk: any, final: any) => entry.push(chunk, final) };\r\n };\r\n\r\n /**\r\n * Serialize an in-memory XML object into a compressed zip entry.\r\n *\r\n * Notes:\r\n * - \"object\" here is expected to be a structure understood by XmlSerializer.\r\n * - Compression level 6 is a reasonable default for speed/size tradeoff.\r\n * @param target\r\n * @param name\r\n * @param object\r\n */\r\n const serializeEntry = function (target: any, name: string, object: any): void {\r\n const entry = new zipDeflate(name, { level: 6 });\r\n target.add(entry);\r\n\r\n // Convert XmlSerializer output into UTF-8 bytes and push to the zip entry.\r\n const sink = makeByteSinkFromFflateEntry(entry);\r\n const w = new Utf8XmlWriterToBytes(sink);\r\n\r\n // Build XML declaration and serialize.\r\n const b = new XmlBuilder(w).dec(\"1.0\", \"UTF-8\");\r\n const s = new XmlSerializer(b);\r\n s.serialize(object);\r\n\r\n // Ensure the writer flushes any pending bytes and closes the stream for this entry.\r\n w.finish();\r\n };\r\n\r\n // Convert meshes into a 3MF document (content types + relationships + model part).\r\n const doc = this.toDocument(...meshes);\r\n if (!doc) {\r\n // Nothing to write. Current behavior: no zip output.\r\n return;\r\n }\r\n\r\n // Create the zip container. It will forward produced bytes to the provided sink.\r\n const target = new zip(sink);\r\n\r\n // Save the root content types part.\r\n // In OPC, [Content_Types].xml defines the MIME/content types for the package parts.\r\n serializeEntry(target, ContentTypeFileName, doc.contentTypes);\r\n\r\n // Save relationships.\r\n // In OPC, relationships live under _rels and map parts to each other.\r\n serializeEntry(target, `${RelationshipDirName}${RelationshipFileName}`, doc.relationships);\r\n\r\n // Save the 3D model part.\r\n // In 3MF, the main model is typically under /3D/3dmodel.model.\r\n serializeEntry(target, `${Object3dDirName}${ModelFileName}`, doc.model);\r\n\r\n // Finalize the archive stream.\r\n // This should trigger the zip implementation to call sink(..., final=true) when done.\r\n target.end();\r\n }\r\n\r\n /**\r\n * Build a full 3MF OPC document from meshes.\r\n *\r\n * The default behavior uses ThreeMfDocumentBuilder with the model produced by toModel().\r\n * Override if you need custom parts (textures, thumbnails, print ticket, etc.).\r\n * @param meshes\r\n * @returns\r\n */\r\n public toDocument(...meshes: Array<T>): I3mfDocument | undefined {\r\n const b = new ThreeMfModelBuilder().withUnit(this._o?.unit ?? ST_Unit.millimeter);\r\n if (this.options.metadata) {\r\n for (const m in this.options.metadata) {\r\n b.withMetaData(m, this.options.metadata[m]);\r\n }\r\n }\r\n return new ThreeMfDocumentBuilder().withModel(this.toModel(b, ...meshes)).build();\r\n }\r\n\r\n /**\r\n * Convert input meshes into a 3MF model.\r\n *\r\n * Implementations typically:\r\n * - Create resources and objects.\r\n * - Define build items (instances) when exportInstances is enabled.\r\n * - Encode geometry and properties required by your pipeline.\r\n */\r\n public abstract toModel(builder: ThreeMfModelBuilder, ...meshes: Array<T>): I3mfModel;\r\n\r\n /**\r\n * Provide a zip implementation for the current host/runtime.\r\n *\r\n * This might be provided by the framework implementation, but it could differ depending on the host\r\n * (native, Node.js, browser, etc.).\r\n *\r\n * Expected shape (fflate-like):\r\n * - return [ Zip, ZipDeflate ] at minimum.\r\n *\r\n * Notes:\r\n * - In a browser, this might require bundler configuration or dynamic import.\r\n * - In Node.js, this might be a direct import of \"fflate\" or another compatible implementation.\r\n */\r\n public abstract ensureZipLibReadyAsync(): Promise<any>;\r\n}\r\n\r\n/**\r\n * Convenience helpers around serializers.\r\n */\r\nexport class ThreeMf {\r\n /**\r\n * Serialize to a single in-memory buffer.\r\n *\r\n * This is a helper that buffers all chunks produced by serializeAsync, then concatenates them.\r\n * Use serializeAsync directly if you want true streaming to a file/response.\r\n * @param s\r\n * @param meshes\r\n * @returns\r\n */\r\n public static async SerializeToMemoryAsync<A>(s: I3mfSerializer<A>, ...meshes: Array<A>): Promise<Uint8Array | undefined> {\r\n const chunks = new Array<Uint8Array>();\r\n let size = 0;\r\n\r\n // Sink used by the zip stream. We ignore `final` because we buffer everything anyway.\r\n // If `err` is used by the underlying zip lib, callers may want to detect/throw here.\r\n const sink = function (err: any, chunk: Uint8Array, _final: boolean) {\r\n // Current behavior: ignore err and keep accumulating.\r\n // Consider: if err is non-null, you might want to throw or record it.\r\n chunks.push(chunk);\r\n size += chunk.length;\r\n };\r\n\r\n await s.serializeAsync(sink, ...meshes);\r\n\r\n // If nothing was written, return undefined (matches current behavior).\r\n if (!size) {\r\n return undefined;\r\n }\r\n\r\n // Concatenate chunks into a single Uint8Array.\r\n const buffer = new Uint8Array(size);\r\n let off = 0;\r\n for (const c of chunks) {\r\n buffer.set(c, off);\r\n off += c.length;\r\n }\r\n return buffer;\r\n }\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"3mf.serializer.js","sourceRoot":"","sources":["../../../../../../dev/serializers/src/3MF/core/model/3mf.serializer.ts"],"names":[],"mappings":"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","sourcesContent":["// 3MF\r\nimport { ThreeMfDocumentBuilder, ThreeMfModelBuilder } from \"./3mf.builder\";\r\nimport { ContentTypeFileName, ModelFileName, Object3dDirName, RelationshipDirName, RelationshipFileName, type I3mfDocument } from \"./3mf.opc.interfaces\";\r\nimport type { I3mfModel } from \"./3mf.interfaces\";\r\nimport { ST_Unit } from \"./3mf.interfaces\";\r\n\r\n// XML\r\nimport { XmlBuilder } from \"../xml/xml.builder\";\r\nimport { XmlSerializer } from \"../xml/xml.serializer\";\r\nimport { type ByteSink, Utf8XmlWriterToBytes } from \"../xml/xml.builder.bytes\";\r\n\r\n/**\r\n * Options controlling how meshes are exported into the 3MF model.\r\n *\r\n * Notes:\r\n * - These flags are kept generic here and are expected to be interpreted by the concrete serializer/model builder.\r\n * - Defaults are set in AbstractThreeMfSerializer.DEFAULT_3MF_EXPORTER_OPTIONS.\r\n */\r\nexport interface IThreeMfSerializerBaseOptions {\r\n /**\r\n * define the unit. Default is millimeter\r\n */\r\n unit?: ST_Unit;\r\n\r\n /**\r\n *\r\n */\r\n metadata?: Record<string, string>;\r\n}\r\n\r\n/**\r\n * Minimal contract for a 3MF serializer that can stream its output through a sink callback.\r\n *\r\n * The sink callback receives:\r\n * - err: any error produced by the serialization pipeline (if any)\r\n * - chunk: a chunk of bytes to append to the destination\r\n * - final: true when this is the last chunk\r\n *\r\n * Important:\r\n * - Implementations should call sink with final=true exactly once, or rely on the underlying zip lib to do so.\r\n * - Consumers may ignore final if they just buffer everything.\r\n */\r\nexport interface I3mfSerializer<T> {\r\n serializeAsync(sink: (err: any, chunk: Uint8Array, final: boolean) => void, ...meshes: Array<T>): Promise<void>;\r\n}\r\n\r\n/**\r\n * Base class for 3MF serialization.\r\n *\r\n * Responsibilities:\r\n * - Convert user meshes to a 3MF model (toModel).\r\n * - Wrap the 3MF document parts into an OPC container (zip) and stream bytes through the sink.\r\n *\r\n * Non-responsibilities:\r\n * - Providing/initializing the zip implementation (ensureZipLibReadyAsync is abstract).\r\n * - Defining how meshes map to 3MF objects (toModel is abstract).\r\n */\r\nexport abstract class AbstractThreeMfSerializer<T, O extends IThreeMfSerializerBaseOptions> implements I3mfSerializer<T> {\r\n private _o: O;\r\n\r\n /**\r\n * @param opts user-provided options overriding defaults.\r\n */\r\n public constructor(opts: O) {\r\n this._o = opts;\r\n }\r\n\r\n /**\r\n * Expose the resolved options (defaults + overrides) as readonly.\r\n */\r\n public get options(): Readonly<O> {\r\n return this._o;\r\n }\r\n\r\n /**\r\n * Generic 3MF binary serializer.\r\n * Pipeline overview:\r\n * 1. ensureZipLibReadyAsync provides a zip implementation (host-dependent).\r\n * 2. Convert meshes into an I3mfDocument (OPC parts + model).\r\n * 3. Create a zip target that streams through the provided sink.\r\n * 4. Serialize XML parts into zip entries.\r\n * 5. End the zip stream.\r\n * @param sink a callback receiving byte chunks; enables streaming without buffering the full archive in memory.\r\n * @param meshes the meshes to serialize.\r\n * @returns\r\n */\r\n public async serializeAsync(sink: (err: any, chunk: Uint8Array, final: boolean) => void, ...meshes: Array<T>): Promise<void> {\r\n // The zip library (e.g. fflate) may need dynamic import / polyfills depending on host.\r\n // This is delegated to the concrete serializer implementation.\r\n const lib = await this.ensureZipLibReadyAsync();\r\n\r\n // If no lib is available, we silently do nothing today.\r\n // Consider: throwing could be better to fail fast. Kept as-is to preserve current behavior.\r\n if (!lib) {\r\n return;\r\n }\r\n\r\n const zip = lib.Zip;\r\n const zipDeflate = lib.ZipDeflate;\r\n\r\n // We expect a \"fflate-like\" API. If it is missing, fail with an explicit error.\r\n if (!zip || !zipDeflate) {\r\n throw new Error(\"fflate Zip / ZipDeflate not available\");\r\n }\r\n\r\n /**\r\n * Adapt a fflate entry to our ByteSink interface used by Utf8XmlWriterToBytes.\r\n *\r\n * fflate entry.push(chunk, final) is the writer endpoint for compressed bytes.\r\n * @param entry\r\n * @returns\r\n */\r\n const makeByteSinkFromFflateEntry = function (entry: any): ByteSink {\r\n return { push: (chunk: any, final: any) => entry.push(chunk, final) };\r\n };\r\n\r\n /**\r\n * Serialize an in-memory XML object into a compressed zip entry.\r\n *\r\n * Notes:\r\n * - \"object\" here is expected to be a structure understood by XmlSerializer.\r\n * - Compression level 6 is a reasonable default for speed/size tradeoff.\r\n * @param target\r\n * @param name\r\n * @param object\r\n */\r\n const serializeEntry = function (target: any, name: string, object: any): void {\r\n const entry = new zipDeflate(name, { level: 6 });\r\n target.add(entry);\r\n\r\n // Convert XmlSerializer output into UTF-8 bytes and push to the zip entry.\r\n const sink = makeByteSinkFromFflateEntry(entry);\r\n const w = new Utf8XmlWriterToBytes(sink);\r\n\r\n // Build XML declaration and serialize.\r\n const b = new XmlBuilder(w).dec(\"1.0\", \"UTF-8\");\r\n const s = new XmlSerializer(b);\r\n s.serialize(object);\r\n\r\n // Ensure the writer flushes any pending bytes and closes the stream for this entry.\r\n w.finish();\r\n };\r\n\r\n // Convert meshes into a 3MF document (content types + relationships + model part).\r\n const doc = this.toDocument(...meshes);\r\n if (!doc) {\r\n // Nothing to write. Current behavior: no zip output.\r\n return;\r\n }\r\n\r\n // Create the zip container. It will forward produced bytes to the provided sink.\r\n const target = new zip(sink);\r\n\r\n // Save the root content types part.\r\n // In OPC, [Content_Types].xml defines the MIME/content types for the package parts.\r\n serializeEntry(target, ContentTypeFileName, doc.contentTypes);\r\n\r\n // Save relationships.\r\n // In OPC, relationships live under _rels and map parts to each other.\r\n serializeEntry(target, `${RelationshipDirName}${RelationshipFileName}`, doc.relationships);\r\n\r\n // Save the 3D model part.\r\n // In 3MF, the main model is typically under /3D/3dmodel.model.\r\n serializeEntry(target, `${Object3dDirName}${ModelFileName}`, doc.model);\r\n\r\n // Finalize the archive stream.\r\n // This should trigger the zip implementation to call sink(..., final=true) when done.\r\n target.end();\r\n }\r\n\r\n /**\r\n * Build a full 3MF OPC document from meshes.\r\n *\r\n * The default behavior uses ThreeMfDocumentBuilder with the model produced by toModel().\r\n * Override if you need custom parts (textures, thumbnails, print ticket, etc.).\r\n * @param meshes\r\n * @returns\r\n */\r\n public toDocument(...meshes: Array<T>): I3mfDocument | undefined {\r\n const b = new ThreeMfModelBuilder().withUnit(this._o?.unit ?? ST_Unit.millimeter);\r\n if (this.options.metadata) {\r\n for (const m in this.options.metadata) {\r\n b.withMetaData(m, this.options.metadata[m]);\r\n }\r\n }\r\n return new ThreeMfDocumentBuilder().withModel(this.toModel(b, ...meshes)).build();\r\n }\r\n\r\n /**\r\n * Convert input meshes into a 3MF model.\r\n *\r\n * Implementations typically:\r\n * - Create resources and objects.\r\n * - Define build items (instances) when exportInstances is enabled.\r\n * - Encode geometry and properties required by your pipeline.\r\n */\r\n public abstract toModel(builder: ThreeMfModelBuilder, ...meshes: Array<T>): I3mfModel;\r\n\r\n /**\r\n * Provide a zip implementation for the current host/runtime.\r\n *\r\n * This might be provided by the framework implementation, but it could differ depending on the host\r\n * (native, Node.js, browser, etc.).\r\n *\r\n * Expected shape (fflate-like):\r\n * - return [ Zip, ZipDeflate ] at minimum.\r\n *\r\n * Notes:\r\n * - In a browser, this might require bundler configuration or dynamic import.\r\n * - In Node.js, this might be a direct import of \"fflate\" or another compatible implementation.\r\n */\r\n public abstract ensureZipLibReadyAsync(): Promise<any>;\r\n}\r\n\r\n/**\r\n * Convenience helpers around serializers.\r\n */\r\nexport class ThreeMf {\r\n /**\r\n * Serialize to a single in-memory buffer.\r\n *\r\n * This is a helper that buffers all chunks produced by serializeAsync, then concatenates them.\r\n * Use serializeAsync directly if you want true streaming to a file/response.\r\n * @param s\r\n * @param meshes\r\n * @returns\r\n */\r\n public static async SerializeToMemoryAsync<A>(s: I3mfSerializer<A>, ...meshes: Array<A>): Promise<Uint8Array | undefined> {\r\n const chunks = new Array<Uint8Array>();\r\n let size = 0;\r\n\r\n // Sink used by the zip stream. We ignore `final` because we buffer everything anyway.\r\n // If `err` is used by the underlying zip lib, callers may want to detect/throw here.\r\n const sink = function (err: any, chunk: Uint8Array, _final: boolean) {\r\n // Current behavior: ignore err and keep accumulating.\r\n // Consider: if err is non-null, you might want to throw or record it.\r\n chunks.push(chunk);\r\n size += chunk.length;\r\n };\r\n\r\n await s.serializeAsync(sink, ...meshes);\r\n\r\n // If nothing was written, return undefined (matches current behavior).\r\n if (!size) {\r\n return undefined;\r\n }\r\n\r\n // Concatenate chunks into a single Uint8Array.\r\n const buffer = new Uint8Array(size);\r\n let off = 0;\r\n for (const c of chunks) {\r\n buffer.set(c, off);\r\n off += c.length;\r\n }\r\n return buffer;\r\n }\r\n}\r\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onerjs/serializers",
|
|
3
|
-
"version": "8.42.
|
|
3
|
+
"version": "8.42.9",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"module": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
"postcompile": "build-tools -c add-js-to-es6"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@onerjs/core": "8.42.
|
|
21
|
+
"@onerjs/core": "8.42.9",
|
|
22
22
|
"@dev/build-tools": "^1.0.0",
|
|
23
23
|
"@lts/serializers": "^1.0.0",
|
|
24
|
-
"babylonjs-gltf2interface": "^8.42.
|
|
24
|
+
"babylonjs-gltf2interface": "^8.42.9"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"@onerjs/core": "^8.0.0",
|