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

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