@combeenation/3d-viewer 14.0.0 → 14.0.1-rc1

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.
Files changed (65) hide show
  1. package/README.md +9 -9
  2. package/dist/lib-cjs/buildinfo.json +3 -3
  3. package/dist/lib-cjs/commonjs.tsconfig.tsbuildinfo +1 -1
  4. package/dist/lib-cjs/index.d.ts +62 -62
  5. package/dist/lib-cjs/index.js +94 -94
  6. package/dist/lib-cjs/internal/cbn-custom-babylon-loader-plugin.d.ts +10 -10
  7. package/dist/lib-cjs/internal/cbn-custom-babylon-loader-plugin.js +131 -131
  8. package/dist/lib-cjs/internal/cloning-helper.d.ts +19 -19
  9. package/dist/lib-cjs/internal/cloning-helper.js +163 -163
  10. package/dist/lib-cjs/internal/device-helper.d.ts +9 -9
  11. package/dist/lib-cjs/internal/device-helper.js +24 -24
  12. package/dist/lib-cjs/internal/geometry-helper.d.ts +21 -21
  13. package/dist/lib-cjs/internal/geometry-helper.js +145 -145
  14. package/dist/lib-cjs/internal/metadata-helper.d.ts +26 -26
  15. package/dist/lib-cjs/internal/metadata-helper.js +50 -50
  16. package/dist/lib-cjs/internal/paintable-helper.d.ts +40 -40
  17. package/dist/lib-cjs/internal/paintable-helper.js +286 -286
  18. package/dist/lib-cjs/internal/tags-helper.d.ts +12 -12
  19. package/dist/lib-cjs/internal/tags-helper.js +37 -37
  20. package/dist/lib-cjs/manager/camera-manager.d.ts +110 -110
  21. package/dist/lib-cjs/manager/camera-manager.js +206 -206
  22. package/dist/lib-cjs/manager/debug-manager.d.ts +60 -60
  23. package/dist/lib-cjs/manager/debug-manager.js +217 -217
  24. package/dist/lib-cjs/manager/event-manager.d.ts +52 -52
  25. package/dist/lib-cjs/manager/event-manager.js +71 -71
  26. package/dist/lib-cjs/manager/gltf-export-manager.d.ts +84 -75
  27. package/dist/lib-cjs/manager/gltf-export-manager.js +290 -278
  28. package/dist/lib-cjs/manager/gltf-export-manager.js.map +1 -1
  29. package/dist/lib-cjs/manager/material-manager.d.ts +35 -35
  30. package/dist/lib-cjs/manager/material-manager.js +125 -125
  31. package/dist/lib-cjs/manager/model-manager.d.ts +145 -145
  32. package/dist/lib-cjs/manager/model-manager.js +382 -382
  33. package/dist/lib-cjs/manager/parameter-manager.d.ts +210 -210
  34. package/dist/lib-cjs/manager/parameter-manager.js +514 -514
  35. package/dist/lib-cjs/manager/scene-manager.d.ts +45 -45
  36. package/dist/lib-cjs/manager/scene-manager.js +64 -64
  37. package/dist/lib-cjs/manager/texture-manager.d.ts +12 -12
  38. package/dist/lib-cjs/manager/texture-manager.js +43 -43
  39. package/dist/lib-cjs/viewer-error.d.ts +48 -48
  40. package/dist/lib-cjs/viewer-error.js +60 -60
  41. package/dist/lib-cjs/viewer.d.ts +115 -115
  42. package/dist/lib-cjs/viewer.js +217 -217
  43. package/package.json +91 -91
  44. package/src/buildinfo.json +3 -3
  45. package/src/dev.ts +47 -47
  46. package/src/global-types.d.ts +39 -39
  47. package/src/index.ts +81 -81
  48. package/src/internal/cbn-custom-babylon-loader-plugin.ts +159 -159
  49. package/src/internal/cloning-helper.ts +225 -225
  50. package/src/internal/device-helper.ts +25 -25
  51. package/src/internal/geometry-helper.ts +181 -181
  52. package/src/internal/metadata-helper.ts +63 -63
  53. package/src/internal/paintable-helper.ts +310 -310
  54. package/src/internal/tags-helper.ts +41 -41
  55. package/src/manager/camera-manager.ts +365 -365
  56. package/src/manager/debug-manager.ts +245 -245
  57. package/src/manager/event-manager.ts +72 -72
  58. package/src/manager/gltf-export-manager.ts +357 -341
  59. package/src/manager/material-manager.ts +135 -135
  60. package/src/manager/model-manager.ts +458 -458
  61. package/src/manager/parameter-manager.ts +652 -652
  62. package/src/manager/scene-manager.ts +101 -101
  63. package/src/manager/texture-manager.ts +32 -32
  64. package/src/viewer-error.ts +68 -68
  65. package/src/viewer.ts +290 -290
@@ -1,341 +1,357 @@
1
- import {
2
- DynamicTexture,
3
- ExcludedGeometryList,
4
- GLTF2Export,
5
- IExportOptions,
6
- InstancedMesh,
7
- Material,
8
- Mesh,
9
- Node,
10
- PBRMaterial,
11
- RenderTargetTexture,
12
- TransformNode,
13
- Vector3,
14
- Viewer,
15
- } from '../index';
16
- import { getIsScaledDownDevice } from '../internal/device-helper';
17
- import { bakeGeometryOfMesh, createMeshFromInstancedMesh, resetTransformation } from '../internal/geometry-helper';
18
- import { isNodeExcluded } from '../internal/geometry-helper';
19
- import { cloneInternalMetadata, getInternalMetadataValue, setInternalMetadataValue } from '../internal/metadata-helper';
20
-
21
- /**
22
- * Manager for gltf export and augmented reality features
23
- */
24
- export class GltfExportManager {
25
- protected static readonly _EXPORT_ROOT_NAME = '__export_root__';
26
-
27
- protected _maxTextureSize: number;
28
-
29
- /**
30
- * Defines options for the export.
31
- * We don't allow the user to overwrite certain settings, since we rely on properties like `removeNoopRootNodes` to
32
- * stay `true` in order to make the AR export work.
33
- * We could theoretically allow it if AR optimization is not desired, but this may confuse the user.
34
- */
35
- protected static _gltfExportOptions(optimizeForAR: boolean, excluded?: ExcludedGeometryList): IExportOptions {
36
- return {
37
- shouldExportNode: function (node: Node): boolean {
38
- if (optimizeForAR) {
39
- // we explicitely marked nodes, that should be exported in AR mode
40
- return getInternalMetadataValue(node, 'exportNode') as boolean;
41
- } else {
42
- // use the default export node check (enabled state, exclusion list, etc...)
43
- return GltfExportManager._shouldExportNode(node, excluded);
44
- }
45
- },
46
- };
47
- }
48
-
49
- /**
50
- * Checks if a node should be available in the export
51
- */
52
- protected static _shouldExportNode(node: Node, excluded?: ExcludedGeometryList): boolean {
53
- if (!(node instanceof TransformNode)) {
54
- return false;
55
- }
56
- // TODO WTT: think of adding "BackgroundHelper" and nodes with "infiniteDistance" here as well, at least in AR mode
57
- if (!node.isEnabled()) {
58
- return false;
59
- }
60
- if (excluded && isNodeExcluded(node, excluded)) {
61
- return false;
62
- }
63
-
64
- return true;
65
- }
66
-
67
- /**
68
- * Creates a clone of the material which should be used for the export.
69
- * This is mostly required for recreating textures with lower sizes.
70
- * CAUTION: Material exchanging is not supported for materials that contain certain texture types:
71
- * - Dynamic textures (Paintables): Cloning dynamic textures doesn't clone the canvas context
72
- * => so the clone is just empty
73
- * - Render target textures: Disposing the clone will leave the scene in a "not ready" state
74
- * => this scenario is not fully analyzed yet, but it's not really worth the effort right now, since this kind of
75
- * of texture is not really used ATM
76
- */
77
- protected static _exchangeMaterial(material: Material): void {
78
- const baseTextures = material.getActiveTextures();
79
- const hasDynamicTextures = baseTextures.some(texture => texture instanceof DynamicTexture);
80
- const hasRenderTargetTextures = baseTextures.some(texture => texture instanceof RenderTargetTexture);
81
- if (hasDynamicTextures || hasRenderTargetTextures) {
82
- const textureTypesString = [
83
- hasDynamicTextures ? 'Dynamic Textures' : '',
84
- hasRenderTargetTextures ? 'Render Target Textures' : '',
85
- ]
86
- .filter(Boolean)
87
- .join();
88
- console.warn(
89
- `Couldn't exchange material "${material.name}" in GLB export, as it contains unsupported texture type(s) (${textureTypesString}). The export will still work, but the textures of this material will keep their original size.`
90
- );
91
-
92
- return;
93
- }
94
-
95
- const newName = `${material.name}_clone`;
96
- const clonedMaterial = material.clone(newName)!;
97
- cloneInternalMetadata(material, clonedMaterial);
98
- const clonedTextures = clonedMaterial.getActiveTextures();
99
-
100
- // mark all exported textures, so that they will be deleted after the export
101
- clonedTextures.forEach(texture => setInternalMetadataValue(texture, 'deleteAfterExport', true));
102
-
103
- setInternalMetadataValue(material, 'exchangeMaterialWith', clonedMaterial.uniqueId);
104
- setInternalMetadataValue(clonedMaterial, 'deleteAfterExport', true);
105
- }
106
-
107
- /** @internal */
108
- public constructor(protected viewer: Viewer) {
109
- // store initial max texture size, so that we can restore it in the post processing
110
- this._maxTextureSize = viewer.engine.getCaps().maxTextureSize;
111
- }
112
-
113
- /**
114
- * Exports selected nodes to a file.
115
- * @param filename Optional name of the exported .GLB file.
116
- * @param optimizeForAR Adjusts the exported GLB so that known issues get automatically fixed, this
117
- * is mostly targeting Apples .usdz format.
118
- * @param excluded Optional list of geometry (meshes, elements, variants,...) to be excluded from the export.
119
- */
120
- public async exportGlb(
121
- filename = 'glb-export.glb',
122
- optimizeForAR: boolean = false,
123
- excluded?: ExcludedGeometryList
124
- ): Promise<File | undefined> {
125
- await this._exportPreProcess(optimizeForAR, excluded);
126
-
127
- const glbData = await GLTF2Export.GLBAsync(
128
- this.viewer.scene,
129
- 'dummy',
130
- GltfExportManager._gltfExportOptions(optimizeForAR, excluded)
131
- );
132
-
133
- await this._exportPostProcess(optimizeForAR);
134
-
135
- const resBlob = glbData.glTFFiles['dummy.glb'];
136
- // check if result is valid, according to the typings this could also be a string
137
- if (resBlob instanceof Blob) {
138
- if (!filename.endsWith('.glb')) {
139
- filename += '.glb';
140
- }
141
- return new File([resBlob], filename);
142
- } else {
143
- // result was not a valid blob
144
- return undefined;
145
- }
146
- }
147
-
148
- /**
149
- * Exports selected nodes to GLTF. This may result in more than one file, since textures are exported seperately.
150
- * @param filename Name of the main (text-based) .GLTF file referring to separate texture files.
151
- * @param optimizeForAR Adjusts the exported GLB so that known issues get automatically fixed, this
152
- * is mostly targeting Apples .usdz format.
153
- * @param excluded Optional list of geometry (meshes, elements, variants,...) to be excluded from the export.
154
- */
155
- public async exportGltfToFile(
156
- filename: string,
157
- optimizeForAR: boolean = false,
158
- excluded?: ExcludedGeometryList
159
- ): Promise<void> {
160
- await this._exportPreProcess(optimizeForAR, excluded);
161
-
162
- const gltf = await GLTF2Export.GLTFAsync(
163
- this.viewer.scene,
164
- filename,
165
- GltfExportManager._gltfExportOptions(optimizeForAR, excluded)
166
- );
167
- gltf.downloadFiles();
168
-
169
- this._exportPostProcess(optimizeForAR);
170
- }
171
-
172
- /**
173
- * Exports selected nodes to GLB. This results in one binary file.
174
- * @param filename Name of the main (text-based) .GLTF file referring to seperate texture files.
175
- * @param optimizeForAR Adjusts the exported GLB so that known issues get automatically fixed, this
176
- * is mostly targeting Apples .usdz format.
177
- * @param excluded Optional list of geometry (meshes, elements, variants,...) to be excluded from the export.
178
- */
179
- public async exportGlbToFile(
180
- filename: string,
181
- optimizeForAR: boolean = false,
182
- excluded?: ExcludedGeometryList
183
- ): Promise<void> {
184
- await this._exportPreProcess(optimizeForAR, excluded);
185
-
186
- const glb = await GLTF2Export.GLBAsync(
187
- this.viewer.scene,
188
- filename,
189
- GltfExportManager._gltfExportOptions(optimizeForAR, excluded)
190
- );
191
- glb.downloadFiles();
192
-
193
- await this._exportPostProcess(optimizeForAR);
194
- }
195
-
196
- /**
197
- * Prepares scene for GLB export.
198
- * This is very important for AR exports, since we have to do a lot of conversions to satisfy Apples .usdz format.
199
- */
200
- protected async _exportPreProcess(optimizeForAR: boolean, excluded?: ExcludedGeometryList): Promise<void> {
201
- if (!optimizeForAR) {
202
- // actually nothing to do if AR optimization is not required
203
- return;
204
- }
205
-
206
- // pause rendering, since we are altering the active scene, which should not be visible to the user
207
- this.viewer.pauseRendering();
208
-
209
- // exchange materials if required
210
- // the AR export should never contain textures larger than 1024 px:
211
- // - iOS devices will crash most likely when trying to access AR endpoints with such files
212
- // - file size will be reduced, which leads to faster loading times
213
- // - textures > 1024 px shouldn't make a difference on mobiles anyway
214
- // we don't have to rescale anything if are already on a downscaled device, since the textures are already <= 1024
215
- // also we have to be very cautios with copying textures on these devices, since we are potentially very limited
216
- // with the available memory
217
- const isScaledDownDevice = getIsScaledDownDevice(this.viewer.viewerSettings.limitTextureSize);
218
- if (!isScaledDownDevice) {
219
- // the idea is to re-create all textures with a smaller texture size
220
- // we have to exchange all materials for this to work
221
- this.viewer.engine.clearInternalTexturesCache();
222
- this.viewer.engine.getCaps().maxTextureSize = 1024;
223
-
224
- this.viewer.scene.materials
225
- .filter(material => material instanceof PBRMaterial)
226
- .forEach(material => GltfExportManager._exchangeMaterial(material as PBRMaterial));
227
- }
228
-
229
- // since Babylon.js v6 the conversion to right handed GLB coordinate system is done differently
230
- // previously the geometry itself has been altered, now they negate the scaling of all root nodes
231
- // this is an issue for us, since we will receive this negated scalings in the exported GLB, which leads to issues
232
- // on iOS devices
233
- // we fix that by adding a top level root node with a negative scaling as well
234
- // the exporter just removes this node as he detects a "noop root node" (implementation details of Babylon.js)
235
- // everything beneath this node remains untouched
236
- // TODO BJS Update: Test AR export on iPhones as well and double check if we still need this special logic
237
- const exportRootNode = new TransformNode(GltfExportManager._EXPORT_ROOT_NAME, this.viewer.scene);
238
- exportRootNode.scaling = new Vector3(-1, 1, 1);
239
- setInternalMetadataValue(exportRootNode, 'exportNode', true);
240
- setInternalMetadataValue(exportRootNode, 'deleteAfterExport', true);
241
-
242
- // create clones of each node (recursively), optionally exchange with cloned materials and mark these nodes for the
243
- // export
244
- this.viewer.scene.rootNodes
245
- .filter(rootNode => rootNode.name !== GltfExportManager._EXPORT_ROOT_NAME)
246
- .forEach(rootNode => this._prepareNodeForExport(rootNode, exportRootNode, excluded));
247
-
248
- // bake transformation of all meshes, so that no negative scalings are left
249
- // it's important that this is done AFTER instanced meshes have been converted (_prepareNodeForExport)
250
- this._getNodesMarkedForExport(true).forEach(mesh => bakeGeometryOfMesh(mesh as Mesh));
251
-
252
- // reset transformation of all "TransformNodes", which couldn't be handled by the geometry baking algorithm
253
- // it's important that this is done AFTER all geometries have been baked
254
- this._getNodesMarkedForExport().forEach(node => resetTransformation(node as TransformNode));
255
- }
256
-
257
- /**
258
- * Cleans up scene after export
259
- */
260
- protected async _exportPostProcess(optimizeForAR: boolean): Promise<void> {
261
- if (!optimizeForAR) {
262
- // nothing to do, since no changes have been made without AR optimizations
263
- return;
264
- }
265
-
266
- // dispose all nodes, materials and textures that have only been created for the export
267
- this.viewer.scene.rootNodes
268
- .filter(rootNode => getInternalMetadataValue(rootNode, 'deleteAfterExport'))
269
- .forEach(rootNode => rootNode.dispose());
270
-
271
- this.viewer.scene.materials
272
- .filter(mat => getInternalMetadataValue(mat, 'deleteAfterExport'))
273
- .forEach(material => material.dispose(false, false));
274
-
275
- this.viewer.scene.textures
276
- .filter(texture => getInternalMetadataValue(texture, 'deleteAfterExport'))
277
- .forEach(texture => texture.dispose());
278
-
279
- // reset engines max texture size and resume rendering
280
- this.viewer.engine.getCaps().maxTextureSize = this._maxTextureSize;
281
- this.viewer.resumeRendering();
282
- }
283
-
284
- /**
285
- * Creates a clone of the node which should be used for the export.
286
- * Also switches to the cloned material if required.
287
- */
288
- protected _prepareNodeForExport(node: Node, clonedParent: TransformNode, excluded?: ExcludedGeometryList): void {
289
- if (!GltfExportManager._shouldExportNode(node, excluded)) {
290
- return;
291
- }
292
-
293
- // from here on we only have TransformNodes
294
- const transformNode = node as TransformNode;
295
-
296
- // clone original node and create unique name (via uniqueId) for the export
297
- const clonedNodeName = `${transformNode.name}_${transformNode.uniqueId}`;
298
- const clonedNode =
299
- transformNode instanceof InstancedMesh
300
- ? createMeshFromInstancedMesh(transformNode, clonedNodeName, clonedParent)
301
- : transformNode.clone(clonedNodeName, clonedParent, true)!;
302
- cloneInternalMetadata(transformNode, clonedNode);
303
-
304
- // exchange material
305
- if (clonedNode instanceof Mesh) {
306
- const exchangeWithMaterial =
307
- clonedNode.material && getInternalMetadataValue(clonedNode.material, 'exchangeMaterialWith');
308
- if (exchangeWithMaterial) {
309
- clonedNode.material = this.viewer.scene.getMaterialByUniqueID(exchangeWithMaterial as number);
310
- }
311
- }
312
-
313
- // signalize that this is a cloned node
314
- setInternalMetadataValue(clonedNode, 'exportNode', true);
315
- setInternalMetadataValue(clonedNode, 'deleteAfterExport', true);
316
-
317
- // handle children
318
- const childs = transformNode.getChildTransformNodes(true);
319
- childs.forEach(child => this._prepareNodeForExport(child, clonedNode, excluded));
320
- }
321
-
322
- /**
323
- * Help function for receiving all nodes that are marked for the export
324
- */
325
- protected _getNodesMarkedForExport(meshesOnly?: boolean): TransformNode[] {
326
- const nodes: TransformNode[] = [...this.viewer.scene.meshes];
327
- if (!meshesOnly) {
328
- nodes.push(...this.viewer.scene.transformNodes);
329
- }
330
-
331
- const filteredNodes = nodes.filter(
332
- node =>
333
- getInternalMetadataValue(node, 'exportNode') &&
334
- // in the desired use cases we want to exclude the export root node
335
- // maybe add a parameter if we have to include it in certain scenarios in the future
336
- node.name !== GltfExportManager._EXPORT_ROOT_NAME
337
- );
338
-
339
- return filteredNodes;
340
- }
341
- }
1
+ import {
2
+ DynamicTexture,
3
+ ExcludedGeometryList,
4
+ GLTF2Export,
5
+ IExportOptions,
6
+ InstancedMesh,
7
+ Material,
8
+ Mesh,
9
+ Node,
10
+ PBRMaterial,
11
+ RenderTargetTexture,
12
+ TransformNode,
13
+ Vector3,
14
+ Viewer,
15
+ } from '../index';
16
+ import { getIsScaledDownDevice } from '../internal/device-helper';
17
+ import { bakeGeometryOfMesh, createMeshFromInstancedMesh, resetTransformation } from '../internal/geometry-helper';
18
+ import { isNodeExcluded } from '../internal/geometry-helper';
19
+ import { cloneInternalMetadata, getInternalMetadataValue, setInternalMetadataValue } from '../internal/metadata-helper';
20
+ import type { AnimationGroup } from '@babylonjs/core/Animations/animationGroup';
21
+
22
+ /**
23
+ * Manager for gltf export and augmented reality features
24
+ */
25
+ export class GltfExportManager {
26
+ protected static readonly _EXPORT_ROOT_NAME = '__export_root__';
27
+
28
+ protected _maxTextureSize: number;
29
+
30
+ /**
31
+ * Animation groups which have been removed from the scene before export for AR and which need to be re-added
32
+ * afterwards.
33
+ *
34
+ * AR does not work on Android (i.e. the modelviewer) when animation groups are present in the scene.
35
+ * See CB-10055 for more details.
36
+ */
37
+ protected _animationGroupsToRestore: AnimationGroup[] = [];
38
+
39
+ /**
40
+ * Defines options for the export.
41
+ * We don't allow the user to overwrite certain settings, since we rely on properties like `removeNoopRootNodes` to
42
+ * stay `true` in order to make the AR export work.
43
+ * We could theoretically allow it if AR optimization is not desired, but this may confuse the user.
44
+ */
45
+ protected static _gltfExportOptions(optimizeForAR: boolean, excluded?: ExcludedGeometryList): IExportOptions {
46
+ return {
47
+ shouldExportNode: function (node: Node): boolean {
48
+ if (optimizeForAR) {
49
+ // we explicitely marked nodes, that should be exported in AR mode
50
+ return getInternalMetadataValue(node, 'exportNode') as boolean;
51
+ } else {
52
+ // use the default export node check (enabled state, exclusion list, etc...)
53
+ return GltfExportManager._shouldExportNode(node, excluded);
54
+ }
55
+ },
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Checks if a node should be available in the export
61
+ */
62
+ protected static _shouldExportNode(node: Node, excluded?: ExcludedGeometryList): boolean {
63
+ if (!(node instanceof TransformNode)) {
64
+ return false;
65
+ }
66
+ // TODO WTT: think of adding "BackgroundHelper" and nodes with "infiniteDistance" here as well, at least in AR mode
67
+ if (!node.isEnabled()) {
68
+ return false;
69
+ }
70
+ if (excluded && isNodeExcluded(node, excluded)) {
71
+ return false;
72
+ }
73
+
74
+ return true;
75
+ }
76
+
77
+ /**
78
+ * Creates a clone of the material which should be used for the export.
79
+ * This is mostly required for recreating textures with lower sizes.
80
+ * CAUTION: Material exchanging is not supported for materials that contain certain texture types:
81
+ * - Dynamic textures (Paintables): Cloning dynamic textures doesn't clone the canvas context
82
+ * => so the clone is just empty
83
+ * - Render target textures: Disposing the clone will leave the scene in a "not ready" state
84
+ * => this scenario is not fully analyzed yet, but it's not really worth the effort right now, since this kind of
85
+ * of texture is not really used ATM
86
+ */
87
+ protected static _exchangeMaterial(material: Material): void {
88
+ const baseTextures = material.getActiveTextures();
89
+ const hasDynamicTextures = baseTextures.some(texture => texture instanceof DynamicTexture);
90
+ const hasRenderTargetTextures = baseTextures.some(texture => texture instanceof RenderTargetTexture);
91
+ if (hasDynamicTextures || hasRenderTargetTextures) {
92
+ const textureTypesString = [
93
+ hasDynamicTextures ? 'Dynamic Textures' : '',
94
+ hasRenderTargetTextures ? 'Render Target Textures' : '',
95
+ ]
96
+ .filter(Boolean)
97
+ .join();
98
+ console.warn(
99
+ `Couldn't exchange material "${material.name}" in GLB export, as it contains unsupported texture type(s) (${textureTypesString}). The export will still work, but the textures of this material will keep their original size.`
100
+ );
101
+
102
+ return;
103
+ }
104
+
105
+ const newName = `${material.name}_clone`;
106
+ const clonedMaterial = material.clone(newName)!;
107
+ cloneInternalMetadata(material, clonedMaterial);
108
+ const clonedTextures = clonedMaterial.getActiveTextures();
109
+
110
+ // mark all exported textures, so that they will be deleted after the export
111
+ clonedTextures.forEach(texture => setInternalMetadataValue(texture, 'deleteAfterExport', true));
112
+
113
+ setInternalMetadataValue(material, 'exchangeMaterialWith', clonedMaterial.uniqueId);
114
+ setInternalMetadataValue(clonedMaterial, 'deleteAfterExport', true);
115
+ }
116
+
117
+ /** @internal */
118
+ public constructor(protected viewer: Viewer) {
119
+ // store initial max texture size, so that we can restore it in the post processing
120
+ this._maxTextureSize = viewer.engine.getCaps().maxTextureSize;
121
+ }
122
+
123
+ /**
124
+ * Exports selected nodes to a file.
125
+ * @param filename Optional name of the exported .GLB file.
126
+ * @param optimizeForAR Adjusts the exported GLB so that known issues get automatically fixed, this
127
+ * is mostly targeting Apples .usdz format.
128
+ * @param excluded Optional list of geometry (meshes, elements, variants,...) to be excluded from the export.
129
+ */
130
+ public async exportGlb(
131
+ filename = 'glb-export.glb',
132
+ optimizeForAR: boolean = false,
133
+ excluded?: ExcludedGeometryList
134
+ ): Promise<File | undefined> {
135
+ await this._exportPreProcess(optimizeForAR, excluded);
136
+
137
+ const glbData = await GLTF2Export.GLBAsync(
138
+ this.viewer.scene,
139
+ 'dummy',
140
+ GltfExportManager._gltfExportOptions(optimizeForAR, excluded)
141
+ );
142
+
143
+ await this._exportPostProcess(optimizeForAR);
144
+
145
+ const resBlob = glbData.glTFFiles['dummy.glb'];
146
+ // check if result is valid, according to the typings this could also be a string
147
+ if (resBlob instanceof Blob) {
148
+ if (!filename.endsWith('.glb')) {
149
+ filename += '.glb';
150
+ }
151
+ return new File([resBlob], filename);
152
+ } else {
153
+ // result was not a valid blob
154
+ return undefined;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Exports selected nodes to GLTF. This may result in more than one file, since textures are exported seperately.
160
+ * @param filename Name of the main (text-based) .GLTF file referring to separate texture files.
161
+ * @param optimizeForAR Adjusts the exported GLB so that known issues get automatically fixed, this
162
+ * is mostly targeting Apples .usdz format.
163
+ * @param excluded Optional list of geometry (meshes, elements, variants,...) to be excluded from the export.
164
+ */
165
+ public async exportGltfToFile(
166
+ filename: string,
167
+ optimizeForAR: boolean = false,
168
+ excluded?: ExcludedGeometryList
169
+ ): Promise<void> {
170
+ await this._exportPreProcess(optimizeForAR, excluded);
171
+
172
+ const gltf = await GLTF2Export.GLTFAsync(
173
+ this.viewer.scene,
174
+ filename,
175
+ GltfExportManager._gltfExportOptions(optimizeForAR, excluded)
176
+ );
177
+ gltf.downloadFiles();
178
+
179
+ this._exportPostProcess(optimizeForAR);
180
+ }
181
+
182
+ /**
183
+ * Exports selected nodes to GLB. This results in one binary file.
184
+ * @param filename Name of the main (text-based) .GLTF file referring to seperate texture files.
185
+ * @param optimizeForAR Adjusts the exported GLB so that known issues get automatically fixed, this
186
+ * is mostly targeting Apples .usdz format.
187
+ * @param excluded Optional list of geometry (meshes, elements, variants,...) to be excluded from the export.
188
+ */
189
+ public async exportGlbToFile(
190
+ filename: string,
191
+ optimizeForAR: boolean = false,
192
+ excluded?: ExcludedGeometryList
193
+ ): Promise<void> {
194
+ await this._exportPreProcess(optimizeForAR, excluded);
195
+
196
+ const glb = await GLTF2Export.GLBAsync(
197
+ this.viewer.scene,
198
+ filename,
199
+ GltfExportManager._gltfExportOptions(optimizeForAR, excluded)
200
+ );
201
+ glb.downloadFiles();
202
+
203
+ await this._exportPostProcess(optimizeForAR);
204
+ }
205
+
206
+ /**
207
+ * Prepares scene for GLB export.
208
+ * This is very important for AR exports, since we have to do a lot of conversions to satisfy Apples .usdz format.
209
+ */
210
+ protected async _exportPreProcess(optimizeForAR: boolean, excluded?: ExcludedGeometryList): Promise<void> {
211
+ if (!optimizeForAR) {
212
+ // actually nothing to do if AR optimization is not required
213
+ return;
214
+ }
215
+
216
+ // pause rendering, since we are altering the active scene, which should not be visible to the user
217
+ this.viewer.pauseRendering();
218
+
219
+ this._animationGroupsToRestore = [...this.viewer.scene.animationGroups];
220
+ this._animationGroupsToRestore.forEach(x => this.viewer.scene.removeAnimationGroup(x));
221
+
222
+ // exchange materials if required
223
+ // the AR export should never contain textures larger than 1024 px:
224
+ // - iOS devices will crash most likely when trying to access AR endpoints with such files
225
+ // - file size will be reduced, which leads to faster loading times
226
+ // - textures > 1024 px shouldn't make a difference on mobiles anyway
227
+ // we don't have to rescale anything if are already on a downscaled device, since the textures are already <= 1024
228
+ // also we have to be very cautios with copying textures on these devices, since we are potentially very limited
229
+ // with the available memory
230
+ const isScaledDownDevice = getIsScaledDownDevice(this.viewer.viewerSettings.limitTextureSize);
231
+ if (!isScaledDownDevice) {
232
+ // the idea is to re-create all textures with a smaller texture size
233
+ // we have to exchange all materials for this to work
234
+ this.viewer.engine.clearInternalTexturesCache();
235
+ this.viewer.engine.getCaps().maxTextureSize = 1024;
236
+
237
+ this.viewer.scene.materials
238
+ .filter(material => material instanceof PBRMaterial)
239
+ .forEach(material => GltfExportManager._exchangeMaterial(material as PBRMaterial));
240
+ }
241
+
242
+ // since Babylon.js v6 the conversion to right handed GLB coordinate system is done differently
243
+ // previously the geometry itself has been altered, now they negate the scaling of all root nodes
244
+ // this is an issue for us, since we will receive this negated scalings in the exported GLB, which leads to issues
245
+ // on iOS devices
246
+ // we fix that by adding a top level root node with a negative scaling as well
247
+ // the exporter just removes this node as he detects a "noop root node" (implementation details of Babylon.js)
248
+ // everything beneath this node remains untouched
249
+ // TODO BJS Update: Test AR export on iPhones as well and double check if we still need this special logic
250
+ const exportRootNode = new TransformNode(GltfExportManager._EXPORT_ROOT_NAME, this.viewer.scene);
251
+ exportRootNode.scaling = new Vector3(-1, 1, 1);
252
+ setInternalMetadataValue(exportRootNode, 'exportNode', true);
253
+ setInternalMetadataValue(exportRootNode, 'deleteAfterExport', true);
254
+
255
+ // create clones of each node (recursively), optionally exchange with cloned materials and mark these nodes for the
256
+ // export
257
+ this.viewer.scene.rootNodes
258
+ .filter(rootNode => rootNode.name !== GltfExportManager._EXPORT_ROOT_NAME)
259
+ .forEach(rootNode => this._prepareNodeForExport(rootNode, exportRootNode, excluded));
260
+
261
+ // bake transformation of all meshes, so that no negative scalings are left
262
+ // it's important that this is done AFTER instanced meshes have been converted (_prepareNodeForExport)
263
+ this._getNodesMarkedForExport(true).forEach(mesh => bakeGeometryOfMesh(mesh as Mesh));
264
+
265
+ // reset transformation of all "TransformNodes", which couldn't be handled by the geometry baking algorithm
266
+ // it's important that this is done AFTER all geometries have been baked
267
+ this._getNodesMarkedForExport().forEach(node => resetTransformation(node as TransformNode));
268
+ }
269
+
270
+ /**
271
+ * Cleans up scene after export
272
+ */
273
+ protected async _exportPostProcess(optimizeForAR: boolean): Promise<void> {
274
+ if (!optimizeForAR) {
275
+ // nothing to do, since no changes have been made without AR optimizations
276
+ return;
277
+ }
278
+
279
+ this._animationGroupsToRestore.forEach(x => this.viewer.scene.addAnimationGroup(x));
280
+ this._animationGroupsToRestore = [];
281
+
282
+ // dispose all nodes, materials and textures that have only been created for the export
283
+ this.viewer.scene.rootNodes
284
+ .filter(rootNode => getInternalMetadataValue(rootNode, 'deleteAfterExport'))
285
+ .forEach(rootNode => rootNode.dispose());
286
+
287
+ this.viewer.scene.materials
288
+ .filter(mat => getInternalMetadataValue(mat, 'deleteAfterExport'))
289
+ .forEach(material => material.dispose(false, false));
290
+
291
+ this.viewer.scene.textures
292
+ .filter(texture => getInternalMetadataValue(texture, 'deleteAfterExport'))
293
+ .forEach(texture => texture.dispose());
294
+
295
+ // reset engines max texture size and resume rendering
296
+ this.viewer.engine.getCaps().maxTextureSize = this._maxTextureSize;
297
+ this.viewer.resumeRendering();
298
+ }
299
+
300
+ /**
301
+ * Creates a clone of the node which should be used for the export.
302
+ * Also switches to the cloned material if required.
303
+ */
304
+ protected _prepareNodeForExport(node: Node, clonedParent: TransformNode, excluded?: ExcludedGeometryList): void {
305
+ if (!GltfExportManager._shouldExportNode(node, excluded)) {
306
+ return;
307
+ }
308
+
309
+ // from here on we only have TransformNodes
310
+ const transformNode = node as TransformNode;
311
+
312
+ // clone original node and create unique name (via uniqueId) for the export
313
+ const clonedNodeName = `${transformNode.name}_${transformNode.uniqueId}`;
314
+ const clonedNode =
315
+ transformNode instanceof InstancedMesh
316
+ ? createMeshFromInstancedMesh(transformNode, clonedNodeName, clonedParent)
317
+ : transformNode.clone(clonedNodeName, clonedParent, true)!;
318
+ cloneInternalMetadata(transformNode, clonedNode);
319
+
320
+ // exchange material
321
+ if (clonedNode instanceof Mesh) {
322
+ const exchangeWithMaterial =
323
+ clonedNode.material && getInternalMetadataValue(clonedNode.material, 'exchangeMaterialWith');
324
+ if (exchangeWithMaterial) {
325
+ clonedNode.material = this.viewer.scene.getMaterialByUniqueID(exchangeWithMaterial as number);
326
+ }
327
+ }
328
+
329
+ // signalize that this is a cloned node
330
+ setInternalMetadataValue(clonedNode, 'exportNode', true);
331
+ setInternalMetadataValue(clonedNode, 'deleteAfterExport', true);
332
+
333
+ // handle children
334
+ const childs = transformNode.getChildTransformNodes(true);
335
+ childs.forEach(child => this._prepareNodeForExport(child, clonedNode, excluded));
336
+ }
337
+
338
+ /**
339
+ * Help function for receiving all nodes that are marked for the export
340
+ */
341
+ protected _getNodesMarkedForExport(meshesOnly?: boolean): TransformNode[] {
342
+ const nodes: TransformNode[] = [...this.viewer.scene.meshes];
343
+ if (!meshesOnly) {
344
+ nodes.push(...this.viewer.scene.transformNodes);
345
+ }
346
+
347
+ const filteredNodes = nodes.filter(
348
+ node =>
349
+ getInternalMetadataValue(node, 'exportNode') &&
350
+ // in the desired use cases we want to exclude the export root node
351
+ // maybe add a parameter if we have to include it in certain scenarios in the future
352
+ node.name !== GltfExportManager._EXPORT_ROOT_NAME
353
+ );
354
+
355
+ return filteredNodes;
356
+ }
357
+ }