@onerjs/serializers 8.41.6 → 8.41.8
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.configuration.d.ts +9 -0
- package/3MF/3mfSerializer.configuration.js +11 -0
- package/3MF/3mfSerializer.configuration.js.map +1 -0
- package/3MF/3mfSerializer.d.ts +132 -0
- package/3MF/3mfSerializer.js +328 -0
- package/3MF/3mfSerializer.js.map +1 -0
- package/3MF/core/index.d.ts +2 -0
- package/3MF/core/index.js +4 -0
- package/3MF/core/index.js.map +1 -0
- package/3MF/core/model/3mf.builder.d.ts +231 -0
- package/3MF/core/model/3mf.builder.js +403 -0
- package/3MF/core/model/3mf.builder.js.map +1 -0
- package/3MF/core/model/3mf.d.ts +329 -0
- package/3MF/core/model/3mf.interfaces.d.ts +321 -0
- package/3MF/core/model/3mf.interfaces.js +39 -0
- package/3MF/core/model/3mf.interfaces.js.map +1 -0
- package/3MF/core/model/3mf.js +377 -0
- package/3MF/core/model/3mf.js.map +1 -0
- package/3MF/core/model/3mf.opc.d.ts +66 -0
- package/3MF/core/model/3mf.opc.interfaces.d.ts +126 -0
- package/3MF/core/model/3mf.opc.interfaces.js +75 -0
- package/3MF/core/model/3mf.opc.interfaces.js.map +1 -0
- package/3MF/core/model/3mf.opc.js +91 -0
- package/3MF/core/model/3mf.opc.js.map +1 -0
- package/3MF/core/model/3mf.serializer.d.ts +118 -0
- package/3MF/core/model/3mf.serializer.js +171 -0
- package/3MF/core/model/3mf.serializer.js.map +1 -0
- package/3MF/core/model/3mf.types.d.ts +46 -0
- package/3MF/core/model/3mf.types.js +2 -0
- package/3MF/core/model/3mf.types.js.map +1 -0
- package/3MF/core/model/index.d.ts +7 -0
- package/3MF/core/model/index.js +8 -0
- package/3MF/core/model/index.js.map +1 -0
- package/3MF/core/xml/index.d.ts +6 -0
- package/3MF/core/xml/index.js +7 -0
- package/3MF/core/xml/index.js.map +1 -0
- package/3MF/core/xml/xml.builder.bytes.d.ts +33 -0
- package/3MF/core/xml/xml.builder.bytes.js +60 -0
- package/3MF/core/xml/xml.builder.bytes.js.map +1 -0
- package/3MF/core/xml/xml.builder.d.ts +94 -0
- package/3MF/core/xml/xml.builder.js +286 -0
- package/3MF/core/xml/xml.builder.js.map +1 -0
- package/3MF/core/xml/xml.builder.string.d.ts +19 -0
- package/3MF/core/xml/xml.builder.string.js +35 -0
- package/3MF/core/xml/xml.builder.string.js.map +1 -0
- package/3MF/core/xml/xml.interfaces.d.ts +91 -0
- package/3MF/core/xml/xml.interfaces.js +110 -0
- package/3MF/core/xml/xml.interfaces.js.map +1 -0
- package/3MF/core/xml/xml.serializer.d.ts +39 -0
- package/3MF/core/xml/xml.serializer.format.d.ts +92 -0
- package/3MF/core/xml/xml.serializer.format.js +122 -0
- package/3MF/core/xml/xml.serializer.format.js.map +1 -0
- package/3MF/core/xml/xml.serializer.js +261 -0
- package/3MF/core/xml/xml.serializer.js.map +1 -0
- package/3MF/index.d.ts +3 -0
- package/3MF/index.js +5 -0
- package/3MF/index.js.map +1 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/legacy/legacy-3mfSerializer.d.ts +1 -0
- package/legacy/legacy-3mfSerializer.js +20 -0
- package/legacy/legacy-3mfSerializer.js.map +1 -0
- package/legacy/legacy.d.ts +1 -0
- package/legacy/legacy.js +1 -0
- package/legacy/legacy.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Class used to store configuration of the 3MF Serializer
|
|
3
|
+
*/
|
|
4
|
+
export class ThreeMfSerializerGlobalConfiguration {
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Url to use to load the fflate library (for zip compression)
|
|
8
|
+
*/
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
10
|
+
ThreeMfSerializerGlobalConfiguration.FFLATEUrl = "https://unpkg.com/fflate@0.8.2";
|
|
11
|
+
//# sourceMappingURL=3mfSerializer.configuration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"3mfSerializer.configuration.js","sourceRoot":"","sources":["../../../../dev/serializers/src/3MF/3mfSerializer.configuration.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,OAAO,oCAAoC;;AAC7C;;GAEG;AACH,gEAAgE;AAClD,8CAAS,GAAG,gCAAgC,CAAC","sourcesContent":["/**\r\n * Class used to store configuration of the 3MF Serializer\r\n */\r\nexport class ThreeMfSerializerGlobalConfiguration {\r\n /**\r\n * Url to use to load the fflate library (for zip compression)\r\n */\r\n // eslint-disable-next-line @typescript-eslint/naming-convention\r\n public static FFLATEUrl = \"https://unpkg.com/fflate@0.8.2\";\r\n}\r\n"]}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Mesh } from "@onerjs/core/Meshes/mesh.js";
|
|
2
|
+
import { InstancedMesh } from "@onerjs/core/Meshes/instancedMesh.js";
|
|
3
|
+
import { type ThreeMfModelBuilder } from "./core/model/3mf.builder.js";
|
|
4
|
+
import { type I3mfModel } from "./core/model/3mf.interfaces.js";
|
|
5
|
+
import { AbstractThreeMfSerializer, type IThreeMfSerializerOptions } from "./core/model/3mf.serializer.js";
|
|
6
|
+
/**
|
|
7
|
+
* Options controlling how meshes are exported into the 3MF model.
|
|
8
|
+
*
|
|
9
|
+
* Notes:
|
|
10
|
+
* - These flags are kept generic here and are expected to be interpreted by the concrete serializer/model builder.
|
|
11
|
+
* - Defaults are set in AbstractThreeMfSerializer.DEFAULT_3MF_EXPORTER_OPTIONS.
|
|
12
|
+
*/
|
|
13
|
+
export interface IBjsThreeMfSerializerOptions extends IThreeMfSerializerOptions {
|
|
14
|
+
/**
|
|
15
|
+
* If true, export mesh instances (multiple references to the same geometry) when supported.
|
|
16
|
+
* If false, geometry may be duplicated depending on the concrete implementation.
|
|
17
|
+
*/
|
|
18
|
+
exportInstances?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Babylon.js to 3MF serializer.
|
|
22
|
+
*
|
|
23
|
+
* This serializer converts Babylon meshes into a 3MF model, then relies on the base class
|
|
24
|
+
* (AbstractThreeMfSerializer) to package the OPC parts into a zip stream.
|
|
25
|
+
*
|
|
26
|
+
* Design notes:
|
|
27
|
+
* - First pass: export "source" meshes (non-instances) and build an index to map Babylon mesh/submesh to 3MF object id.
|
|
28
|
+
* - Second pass (optional): export instances as additional build items referencing the original object ids.
|
|
29
|
+
* - Submesh export is handled by extracting per-submesh vertex/index buffers so materials/colors can be preserved
|
|
30
|
+
* by downstream steps that attach per-object properties.
|
|
31
|
+
*/
|
|
32
|
+
export declare class BjsThreeMfSerializer extends AbstractThreeMfSerializer<Mesh | InstancedMesh, IBjsThreeMfSerializerOptions> {
|
|
33
|
+
/**
|
|
34
|
+
*
|
|
35
|
+
*/
|
|
36
|
+
static DefaultOptions: IBjsThreeMfSerializerOptions;
|
|
37
|
+
/**
|
|
38
|
+
* Babylon's vertex buffer semantic for positions.
|
|
39
|
+
* Babylon uses string-based "kind" keys for vertex buffers.
|
|
40
|
+
*/
|
|
41
|
+
private static _PositionKind;
|
|
42
|
+
/**
|
|
43
|
+
* Cached promise so we only attempt to load fflate once.
|
|
44
|
+
* This prevents multiple concurrent LoadScriptAsync calls.
|
|
45
|
+
*/
|
|
46
|
+
private _fflateReadyPromise?;
|
|
47
|
+
/**
|
|
48
|
+
* @param opts serializer options (merged with defaults in base class).
|
|
49
|
+
*/
|
|
50
|
+
constructor(opts?: Partial<IBjsThreeMfSerializerOptions>);
|
|
51
|
+
/**
|
|
52
|
+
* Build a 3MF model from Babylon meshes.
|
|
53
|
+
*
|
|
54
|
+
* Important: this method should not allocate huge intermediate data unless needed.
|
|
55
|
+
* Submesh extraction does allocate new position/index arrays for each exported submesh.
|
|
56
|
+
* @param builder
|
|
57
|
+
* @param meshes
|
|
58
|
+
* @returns
|
|
59
|
+
*/
|
|
60
|
+
toModel(builder: ThreeMfModelBuilder, ...meshes: Array<Mesh | InstancedMesh>): I3mfModel;
|
|
61
|
+
/**
|
|
62
|
+
* Ensure the zip library (fflate) is available in the current runtime.
|
|
63
|
+
*
|
|
64
|
+
* Host assumptions:
|
|
65
|
+
* - This implementation relies on fflate being exposed on globalThis.fflate.
|
|
66
|
+
* - If it is not present, it loads a script from ThreeMfSerializerGlobalConfiguration.FFLATEUrl using Babylon Tools.LoadScriptAsync.
|
|
67
|
+
* @returns
|
|
68
|
+
*/
|
|
69
|
+
ensureZipLibReadyAsync(): Promise<any>;
|
|
70
|
+
/**
|
|
71
|
+
* Extract a single SubMesh into a standalone vertex/index buffer pair.
|
|
72
|
+
*
|
|
73
|
+
* Why:
|
|
74
|
+
* - 3MF mesh objects typically reference a contiguous vertex array and triangle indices.
|
|
75
|
+
* - Babylon SubMesh references a slice of the global index buffer, but shares the vertex buffer.
|
|
76
|
+
* - To serialize each submesh independently, we build a compacted vertex buffer containing only the used vertices
|
|
77
|
+
* and remap indices accordingly.
|
|
78
|
+
*
|
|
79
|
+
* Complexity:
|
|
80
|
+
* - O(indexCount) time, O(uniqueVerticesInSubmesh) additional memory.
|
|
81
|
+
* @param mesh
|
|
82
|
+
* @param sm
|
|
83
|
+
* @returns
|
|
84
|
+
*/
|
|
85
|
+
private _extractSubMesh;
|
|
86
|
+
/**
|
|
87
|
+
* Group items by a computed key.
|
|
88
|
+
* Used to group instances by sourceMesh so the resulting XML is easier to read and debug.
|
|
89
|
+
* @param items
|
|
90
|
+
* @param key
|
|
91
|
+
* @returns
|
|
92
|
+
*/
|
|
93
|
+
private _groupBy;
|
|
94
|
+
/**
|
|
95
|
+
* Basis conversion from Babylon coordinate system to the expected 3MF coordinate system.
|
|
96
|
+
*
|
|
97
|
+
* Here we rotate +90 degrees around X:
|
|
98
|
+
* - This is commonly used to convert between Y-up and Z-up conventions.
|
|
99
|
+
* - Verify this matches your pipeline (Babylon is typically left-handed Y-up).
|
|
100
|
+
*/
|
|
101
|
+
private static readonly _R_BJS_TO_3MF;
|
|
102
|
+
/**
|
|
103
|
+
* Converts a Babylon.js 4x4 matrix into a 3MF 3x4 transform matrix and writes the result into ref.
|
|
104
|
+
*
|
|
105
|
+
* Babylon.js conventions:
|
|
106
|
+
* - Babylon exposes matrices with logical row/column indexing (M(row, column)).
|
|
107
|
+
* - It stores the 16 coefficients in a contiguous array in row-major order:
|
|
108
|
+
* [ M00, M01, M02, M03,
|
|
109
|
+
* M10, M11, M12, M13,
|
|
110
|
+
* M20, M21, M22, M23,
|
|
111
|
+
* M30, M31, M32, M33 ]
|
|
112
|
+
*
|
|
113
|
+
* 3MF expectation:
|
|
114
|
+
* - 3MF uses an affine transform represented as a 3x4 matrix (12 values).
|
|
115
|
+
* - The values are taken from the first 3 columns of the 4x4 matrix, across the 4 rows:
|
|
116
|
+
* m00 m01 m02 m10 m11 m12 m20 m21 m22 m30 m31 m32
|
|
117
|
+
*
|
|
118
|
+
* Steps:
|
|
119
|
+
* 1) Compose Babylon transform with the basis change:
|
|
120
|
+
* tmp = tBjs * _R_BJS_TO_3MF
|
|
121
|
+
* 2) Extract the 12 coefficients in 3MF order from tmp.m.
|
|
122
|
+
*
|
|
123
|
+
* Interop note:
|
|
124
|
+
* - Do not transpose here. We only reorder values to match the 3MF 3x4 serialization order.
|
|
125
|
+
* - Transposition is only relevant when interfacing with code that assumes column-major storage.
|
|
126
|
+
*
|
|
127
|
+
* @param tBjs Babylon.js 4x4 matrix.
|
|
128
|
+
* @param ref Output 3MF 3x4 matrix container (ref.values assigned).
|
|
129
|
+
* @returns ref, for chaining.
|
|
130
|
+
*/
|
|
131
|
+
private _handleBjsTo3mfMatrixTransformToRef;
|
|
132
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { Mesh } from "@onerjs/core/Meshes/mesh.js";
|
|
2
|
+
import { InstancedMesh } from "@onerjs/core/Meshes/instancedMesh.js";
|
|
3
|
+
import { Matrix } from "@onerjs/core/Maths/math.js";
|
|
4
|
+
import { Tools } from "@onerjs/core/Misc/tools.js";
|
|
5
|
+
// 3MF
|
|
6
|
+
import { ThreeMfMeshBuilder } from "./core/model/3mf.builder.js";
|
|
7
|
+
import { Matrix3d } from "./core/model/3mf.js";
|
|
8
|
+
import { ST_Unit } from "./core/model/3mf.interfaces.js";
|
|
9
|
+
import { AbstractThreeMfSerializer } from "./core/model/3mf.serializer.js";
|
|
10
|
+
import { ThreeMfSerializerGlobalConfiguration } from "./3mfSerializer.configuration.js";
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
*/
|
|
14
|
+
class IncrementalIdFactory {
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* @param from
|
|
18
|
+
* @param to
|
|
19
|
+
* @param step
|
|
20
|
+
*/
|
|
21
|
+
constructor(from = 0, to = Number.MIN_SAFE_INTEGER, step = 1) {
|
|
22
|
+
this._from = from;
|
|
23
|
+
this._to = to;
|
|
24
|
+
this._step = step;
|
|
25
|
+
this._i = from;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
*
|
|
29
|
+
* @returns
|
|
30
|
+
*/
|
|
31
|
+
next() {
|
|
32
|
+
if (this._i < this._to) {
|
|
33
|
+
throw new Error("ST_ResourceID out of bound");
|
|
34
|
+
}
|
|
35
|
+
const v = this._i;
|
|
36
|
+
this._i += this._step;
|
|
37
|
+
return v;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* @returns
|
|
42
|
+
*/
|
|
43
|
+
reset() {
|
|
44
|
+
this._i = this._from;
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Babylon.js to 3MF serializer.
|
|
50
|
+
*
|
|
51
|
+
* This serializer converts Babylon meshes into a 3MF model, then relies on the base class
|
|
52
|
+
* (AbstractThreeMfSerializer) to package the OPC parts into a zip stream.
|
|
53
|
+
*
|
|
54
|
+
* Design notes:
|
|
55
|
+
* - First pass: export "source" meshes (non-instances) and build an index to map Babylon mesh/submesh to 3MF object id.
|
|
56
|
+
* - Second pass (optional): export instances as additional build items referencing the original object ids.
|
|
57
|
+
* - Submesh export is handled by extracting per-submesh vertex/index buffers so materials/colors can be preserved
|
|
58
|
+
* by downstream steps that attach per-object properties.
|
|
59
|
+
*/
|
|
60
|
+
export class BjsThreeMfSerializer extends AbstractThreeMfSerializer {
|
|
61
|
+
/**
|
|
62
|
+
* @param opts serializer options (merged with defaults in base class).
|
|
63
|
+
*/
|
|
64
|
+
constructor(opts = {}) {
|
|
65
|
+
super({ ...BjsThreeMfSerializer.DefaultOptions, ...opts });
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build a 3MF model from Babylon meshes.
|
|
69
|
+
*
|
|
70
|
+
* Important: this method should not allocate huge intermediate data unless needed.
|
|
71
|
+
* Submesh extraction does allocate new position/index arrays for each exported submesh.
|
|
72
|
+
* @param builder
|
|
73
|
+
* @param meshes
|
|
74
|
+
* @returns
|
|
75
|
+
*/
|
|
76
|
+
toModel(builder, ...meshes) {
|
|
77
|
+
// avoid parasits..
|
|
78
|
+
meshes = meshes.filter((m) => m instanceof Mesh || m instanceof InstancedMesh);
|
|
79
|
+
const idFactory = new IncrementalIdFactory();
|
|
80
|
+
const modelBuilder = builder;
|
|
81
|
+
/**
|
|
82
|
+
* Index mapping Babylon elements to the created 3MF object.
|
|
83
|
+
*
|
|
84
|
+
* Why:
|
|
85
|
+
* - Instances need to reference the base object id.
|
|
86
|
+
* - When exporting submeshes, instances should reference per-submesh objects rather than the whole mesh.
|
|
87
|
+
*
|
|
88
|
+
* Key type:
|
|
89
|
+
* - When exportSubmeshes is true: we store entries for each SubMesh.
|
|
90
|
+
* - Otherwise: we store entries for each Mesh.
|
|
91
|
+
*/
|
|
92
|
+
const index = new Map();
|
|
93
|
+
/**
|
|
94
|
+
* If exportInstances is enabled, we collect instanced meshes during the first pass and process them later.
|
|
95
|
+
* If exportInstances is disabled, instances are ignored (they will not appear in the output).
|
|
96
|
+
*/
|
|
97
|
+
const instances = this.options.exportInstances ? [] : null;
|
|
98
|
+
// First pass: export base meshes (non-instances).
|
|
99
|
+
// This creates the "resource objects" referenced by build items.
|
|
100
|
+
for (let j = 0; j < meshes.length; j++) {
|
|
101
|
+
const babylonMesh = meshes[j];
|
|
102
|
+
if (babylonMesh instanceof InstancedMesh) {
|
|
103
|
+
// Defer instance handling to the second pass.
|
|
104
|
+
instances?.push(babylonMesh);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const objectName = babylonMesh.name || `mesh${j}`;
|
|
108
|
+
// Convert Babylon world matrix to 3MF build transform (3x4).
|
|
109
|
+
// This transform will be attached to the build item referencing the created object.
|
|
110
|
+
const worldTransform = babylonMesh.getWorldMatrix();
|
|
111
|
+
const buildTransform = this._handleBjsTo3mfMatrixTransformToRef(worldTransform, Matrix3d.Zero());
|
|
112
|
+
// Submeshes can carry material/color boundaries in Babylon.
|
|
113
|
+
// When exportSubmeshes is enabled, we export each submesh as its own 3MF object so
|
|
114
|
+
// consumers can attach per-object properties (e.g. colors) later.
|
|
115
|
+
const subMeshes = babylonMesh.subMeshes;
|
|
116
|
+
if (subMeshes === undefined) {
|
|
117
|
+
// very unlikely...
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
// Babylon.js automatically creates one SubMesh covering the whole mesh if you don’t define any.
|
|
121
|
+
const isStandalone = subMeshes.length == 1;
|
|
122
|
+
for (let k = 0; k < subMeshes.length; k++) {
|
|
123
|
+
const subMesh = subMeshes[k];
|
|
124
|
+
const data = this._extractSubMesh(babylonMesh, subMesh);
|
|
125
|
+
if (data) {
|
|
126
|
+
const submeshName = isStandalone ? `${objectName}` : `${objectName}_${k}`;
|
|
127
|
+
const object = new ThreeMfMeshBuilder(idFactory.next()).withData(data).withName(submeshName).build();
|
|
128
|
+
// Add object to resources.
|
|
129
|
+
modelBuilder.withMesh(object);
|
|
130
|
+
// Add a build item referencing the object at the mesh world transform.
|
|
131
|
+
modelBuilder.withBuild(object.id, buildTransform);
|
|
132
|
+
// Cache mapping for instances (instances will reference this object id).
|
|
133
|
+
index.set(subMesh, object);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Second pass: export instances as additional build items.
|
|
138
|
+
//
|
|
139
|
+
// In 3MF terms:
|
|
140
|
+
// - We do not duplicate geometry for each instance.
|
|
141
|
+
// - We emit multiple build items referencing the same object id, each with its own transform.
|
|
142
|
+
if (instances && instances.length) {
|
|
143
|
+
// Group instances by their source mesh to keep related builds close in the XML.
|
|
144
|
+
const grouped = this._groupBy(instances, (i) => i.sourceMesh);
|
|
145
|
+
for (const [_babylonMesh, _instances] of Array.from(grouped.entries())) {
|
|
146
|
+
if (!_instances || !_instances.length) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
for (let j = 0; j < _instances.length; j++) {
|
|
150
|
+
const mesh = _instances[j];
|
|
151
|
+
const worldTransform = mesh.getWorldMatrix();
|
|
152
|
+
// If we exported submeshes, the base "resource objects" are per-submesh.
|
|
153
|
+
// For an instance we emit a build item per submesh object.
|
|
154
|
+
const subMeshes = _babylonMesh.subMeshes;
|
|
155
|
+
for (let k = 0; k < subMeshes.length; k++) {
|
|
156
|
+
const subMesh = subMeshes[k];
|
|
157
|
+
// Look up the 3MF object created for this submesh in the first pass.
|
|
158
|
+
const objectRef = index.get(subMesh);
|
|
159
|
+
if (objectRef) {
|
|
160
|
+
modelBuilder.withBuild(objectRef.id, this._handleBjsTo3mfMatrixTransformToRef(worldTransform, Matrix3d.Zero()));
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return modelBuilder.build();
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Ensure the zip library (fflate) is available in the current runtime.
|
|
171
|
+
*
|
|
172
|
+
* Host assumptions:
|
|
173
|
+
* - This implementation relies on fflate being exposed on globalThis.fflate.
|
|
174
|
+
* - If it is not present, it loads a script from ThreeMfSerializerGlobalConfiguration.FFLATEUrl using Babylon Tools.LoadScriptAsync.
|
|
175
|
+
* @returns
|
|
176
|
+
*/
|
|
177
|
+
async ensureZipLibReadyAsync() {
|
|
178
|
+
if (this._fflateReadyPromise) {
|
|
179
|
+
return await this._fflateReadyPromise;
|
|
180
|
+
}
|
|
181
|
+
this._fflateReadyPromise = (async () => {
|
|
182
|
+
// globalThis is the global object in all modern JS runtimes (browser, workers, Node, etc.).
|
|
183
|
+
const g = globalThis;
|
|
184
|
+
// If fflate is not already present, load it dynamically.
|
|
185
|
+
// This assumes the loaded script sets globalThis.fflate.
|
|
186
|
+
if (!g.fflate) {
|
|
187
|
+
await Tools.LoadScriptAsync(ThreeMfSerializerGlobalConfiguration.FFLATEUrl);
|
|
188
|
+
}
|
|
189
|
+
return g.fflate;
|
|
190
|
+
})();
|
|
191
|
+
return await this._fflateReadyPromise;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Extract a single SubMesh into a standalone vertex/index buffer pair.
|
|
195
|
+
*
|
|
196
|
+
* Why:
|
|
197
|
+
* - 3MF mesh objects typically reference a contiguous vertex array and triangle indices.
|
|
198
|
+
* - Babylon SubMesh references a slice of the global index buffer, but shares the vertex buffer.
|
|
199
|
+
* - To serialize each submesh independently, we build a compacted vertex buffer containing only the used vertices
|
|
200
|
+
* and remap indices accordingly.
|
|
201
|
+
*
|
|
202
|
+
* Complexity:
|
|
203
|
+
* - O(indexCount) time, O(uniqueVerticesInSubmesh) additional memory.
|
|
204
|
+
* @param mesh
|
|
205
|
+
* @param sm
|
|
206
|
+
* @returns
|
|
207
|
+
*/
|
|
208
|
+
_extractSubMesh(mesh, sm) {
|
|
209
|
+
const allInd = mesh.getIndices();
|
|
210
|
+
if (!allInd) {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
const allPos = mesh.getVerticesData(BjsThreeMfSerializer._PositionKind);
|
|
214
|
+
if (!allPos) {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
// Fast path: the submesh covers the full index buffer, so reuse the original arrays.
|
|
218
|
+
// Note: This returns Babylon-owned arrays; if callers mutate, they will affect source mesh data.
|
|
219
|
+
// Kept as-is to avoid extra allocations.
|
|
220
|
+
if (sm.indexStart == 0 && sm.indexCount == allInd.length) {
|
|
221
|
+
return {
|
|
222
|
+
positions: allPos,
|
|
223
|
+
indices: allInd,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const indStart = sm.indexStart;
|
|
227
|
+
// Map old vertex index -> new compacted vertex index.
|
|
228
|
+
const map = new Map();
|
|
229
|
+
// Compacted positions (x,y,z repeated). We assemble into number[] then convert to Float32Array at the end.
|
|
230
|
+
const newPositions = [];
|
|
231
|
+
// Indices for the compacted vertex buffer.
|
|
232
|
+
// Uint32Array is used to support large meshes; ensure downstream 3MF writer supports 32-bit indices if needed.
|
|
233
|
+
const newIndices = new Uint32Array(sm.indexCount);
|
|
234
|
+
for (let i = 0; i < sm.indexCount; i++) {
|
|
235
|
+
const oldVi = allInd[indStart + i];
|
|
236
|
+
let newVi = map.get(oldVi);
|
|
237
|
+
if (newVi === undefined) {
|
|
238
|
+
newVi = map.size;
|
|
239
|
+
map.set(oldVi, newVi);
|
|
240
|
+
// Copy the corresponding position (assumes positions are 3-floats per vertex).
|
|
241
|
+
// If the source mesh uses a different stride or includes morph targets, this ignores them.
|
|
242
|
+
const p = oldVi * 3;
|
|
243
|
+
newPositions.push(allPos[p], allPos[p + 1], allPos[p + 2]);
|
|
244
|
+
}
|
|
245
|
+
newIndices[i] = newVi;
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
positions: new Float32Array(newPositions),
|
|
249
|
+
indices: newIndices,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Group items by a computed key.
|
|
254
|
+
* Used to group instances by sourceMesh so the resulting XML is easier to read and debug.
|
|
255
|
+
* @param items
|
|
256
|
+
* @param key
|
|
257
|
+
* @returns
|
|
258
|
+
*/
|
|
259
|
+
_groupBy(items, key) {
|
|
260
|
+
const m = new Map();
|
|
261
|
+
for (const it of items) {
|
|
262
|
+
const k = key(it);
|
|
263
|
+
const arr = m.get(k);
|
|
264
|
+
if (arr) {
|
|
265
|
+
arr.push(it);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
m.set(k, [it]);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return m;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Converts a Babylon.js 4x4 matrix into a 3MF 3x4 transform matrix and writes the result into ref.
|
|
275
|
+
*
|
|
276
|
+
* Babylon.js conventions:
|
|
277
|
+
* - Babylon exposes matrices with logical row/column indexing (M(row, column)).
|
|
278
|
+
* - It stores the 16 coefficients in a contiguous array in row-major order:
|
|
279
|
+
* [ M00, M01, M02, M03,
|
|
280
|
+
* M10, M11, M12, M13,
|
|
281
|
+
* M20, M21, M22, M23,
|
|
282
|
+
* M30, M31, M32, M33 ]
|
|
283
|
+
*
|
|
284
|
+
* 3MF expectation:
|
|
285
|
+
* - 3MF uses an affine transform represented as a 3x4 matrix (12 values).
|
|
286
|
+
* - The values are taken from the first 3 columns of the 4x4 matrix, across the 4 rows:
|
|
287
|
+
* m00 m01 m02 m10 m11 m12 m20 m21 m22 m30 m31 m32
|
|
288
|
+
*
|
|
289
|
+
* Steps:
|
|
290
|
+
* 1) Compose Babylon transform with the basis change:
|
|
291
|
+
* tmp = tBjs * _R_BJS_TO_3MF
|
|
292
|
+
* 2) Extract the 12 coefficients in 3MF order from tmp.m.
|
|
293
|
+
*
|
|
294
|
+
* Interop note:
|
|
295
|
+
* - Do not transpose here. We only reorder values to match the 3MF 3x4 serialization order.
|
|
296
|
+
* - Transposition is only relevant when interfacing with code that assumes column-major storage.
|
|
297
|
+
*
|
|
298
|
+
* @param tBjs Babylon.js 4x4 matrix.
|
|
299
|
+
* @param ref Output 3MF 3x4 matrix container (ref.values assigned).
|
|
300
|
+
* @returns ref, for chaining.
|
|
301
|
+
*/
|
|
302
|
+
_handleBjsTo3mfMatrixTransformToRef(tBjs, ref) {
|
|
303
|
+
const tmp = tBjs.multiplyToRef(BjsThreeMfSerializer._R_BJS_TO_3MF, Matrix.Zero());
|
|
304
|
+
const a = tmp.m;
|
|
305
|
+
// a is Babylon row-major storage. Extract rows 0..3, cols 0..2 in 3MF order.
|
|
306
|
+
// 3MF order: m00 m01 m02 m10 m11 m12 m20 m21 m22 m30 m31 m32
|
|
307
|
+
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]];
|
|
308
|
+
return ref;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
*
|
|
313
|
+
*/
|
|
314
|
+
BjsThreeMfSerializer.DefaultOptions = { unit: ST_Unit.meter, exportInstances: false };
|
|
315
|
+
/**
|
|
316
|
+
* Babylon's vertex buffer semantic for positions.
|
|
317
|
+
* Babylon uses string-based "kind" keys for vertex buffers.
|
|
318
|
+
*/
|
|
319
|
+
BjsThreeMfSerializer._PositionKind = "position";
|
|
320
|
+
/**
|
|
321
|
+
* Basis conversion from Babylon coordinate system to the expected 3MF coordinate system.
|
|
322
|
+
*
|
|
323
|
+
* Here we rotate +90 degrees around X:
|
|
324
|
+
* - This is commonly used to convert between Y-up and Z-up conventions.
|
|
325
|
+
* - Verify this matches your pipeline (Babylon is typically left-handed Y-up).
|
|
326
|
+
*/
|
|
327
|
+
BjsThreeMfSerializer._R_BJS_TO_3MF = Matrix.RotationX(Math.PI / 2).multiply(Matrix.Scaling(1, -1, 1));
|
|
328
|
+
//# sourceMappingURL=3mfSerializer.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../dev/serializers/src/3MF/core/index.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC","sourcesContent":["/* eslint-disable @typescript-eslint/no-restricted-imports */\r\nexport * from \"./xml/index\";\r\nexport * from \"./model/index\";\r\n"]}
|