@combeenation/3d-viewer 12.4.1 → 12.4.3

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 (114) hide show
  1. package/README.md +9 -9
  2. package/dist/lib-cjs/api/classes/animationInterface.d.ts +8 -8
  3. package/dist/lib-cjs/api/classes/animationInterface.js +2 -2
  4. package/dist/lib-cjs/api/classes/dottedPath.d.ts +79 -79
  5. package/dist/lib-cjs/api/classes/dottedPath.js +166 -166
  6. package/dist/lib-cjs/api/classes/element.d.ts +153 -153
  7. package/dist/lib-cjs/api/classes/element.js +702 -702
  8. package/dist/lib-cjs/api/classes/event.d.ts +401 -401
  9. package/dist/lib-cjs/api/classes/event.js +424 -424
  10. package/dist/lib-cjs/api/classes/eventBroadcaster.d.ts +26 -26
  11. package/dist/lib-cjs/api/classes/eventBroadcaster.js +49 -49
  12. package/dist/lib-cjs/api/classes/fuzzyMap.d.ts +7 -7
  13. package/dist/lib-cjs/api/classes/fuzzyMap.js +21 -21
  14. package/dist/lib-cjs/api/classes/parameter.d.ts +410 -410
  15. package/dist/lib-cjs/api/classes/parameter.js +642 -642
  16. package/dist/lib-cjs/api/classes/parameterObservable.d.ts +36 -36
  17. package/dist/lib-cjs/api/classes/parameterObservable.js +72 -72
  18. package/dist/lib-cjs/api/classes/parameterizable.d.ts +15 -15
  19. package/dist/lib-cjs/api/classes/parameterizable.js +102 -102
  20. package/dist/lib-cjs/api/classes/placementAnimation.d.ts +45 -45
  21. package/dist/lib-cjs/api/classes/placementAnimation.js +176 -176
  22. package/dist/lib-cjs/api/classes/variant.d.ts +261 -261
  23. package/dist/lib-cjs/api/classes/variant.js +872 -872
  24. package/dist/lib-cjs/api/classes/variantInstance.d.ts +53 -53
  25. package/dist/lib-cjs/api/classes/variantInstance.js +125 -125
  26. package/dist/lib-cjs/api/classes/variantParameterizable.d.ts +17 -17
  27. package/dist/lib-cjs/api/classes/variantParameterizable.js +86 -86
  28. package/dist/lib-cjs/api/classes/viewer.d.ts +215 -215
  29. package/dist/lib-cjs/api/classes/viewer.js +708 -708
  30. package/dist/lib-cjs/api/classes/viewerError.d.ts +43 -43
  31. package/dist/lib-cjs/api/classes/viewerError.js +55 -55
  32. package/dist/lib-cjs/api/classes/viewerLight.d.ts +66 -66
  33. package/dist/lib-cjs/api/classes/viewerLight.js +344 -344
  34. package/dist/lib-cjs/api/internal/lensRendering.d.ts +8 -8
  35. package/dist/lib-cjs/api/internal/lensRendering.js +11 -11
  36. package/dist/lib-cjs/api/internal/sceneSetup.d.ts +13 -13
  37. package/dist/lib-cjs/api/internal/sceneSetup.js +227 -227
  38. package/dist/lib-cjs/api/manager/animationManager.d.ts +30 -30
  39. package/dist/lib-cjs/api/manager/animationManager.js +126 -126
  40. package/dist/lib-cjs/api/manager/gltfExportManager.d.ts +80 -80
  41. package/dist/lib-cjs/api/manager/gltfExportManager.js +300 -299
  42. package/dist/lib-cjs/api/manager/gltfExportManager.js.map +1 -1
  43. package/dist/lib-cjs/api/manager/sceneManager.d.ts +33 -33
  44. package/dist/lib-cjs/api/manager/sceneManager.js +128 -128
  45. package/dist/lib-cjs/api/manager/tagManager.d.ts +118 -118
  46. package/dist/lib-cjs/api/manager/tagManager.js +530 -530
  47. package/dist/lib-cjs/api/manager/textureLoadManager.d.ts +22 -22
  48. package/dist/lib-cjs/api/manager/textureLoadManager.js +107 -107
  49. package/dist/lib-cjs/api/manager/variantInstanceManager.d.ts +106 -106
  50. package/dist/lib-cjs/api/manager/variantInstanceManager.js +290 -290
  51. package/dist/lib-cjs/api/store/specStorage.d.ts +32 -32
  52. package/dist/lib-cjs/api/store/specStorage.js +65 -65
  53. package/dist/lib-cjs/api/util/babylonHelper.d.ts +238 -238
  54. package/dist/lib-cjs/api/util/babylonHelper.js +825 -825
  55. package/dist/lib-cjs/api/util/debugHelper.d.ts +9 -9
  56. package/dist/lib-cjs/api/util/debugHelper.js +93 -93
  57. package/dist/lib-cjs/api/util/deviceHelper.d.ts +9 -9
  58. package/dist/lib-cjs/api/util/deviceHelper.js +28 -28
  59. package/dist/lib-cjs/api/util/geometryHelper.d.ts +17 -17
  60. package/dist/lib-cjs/api/util/geometryHelper.js +112 -112
  61. package/dist/lib-cjs/api/util/globalTypes.d.ts +490 -490
  62. package/dist/lib-cjs/api/util/globalTypes.js +1 -1
  63. package/dist/lib-cjs/api/util/resourceHelper.d.ts +58 -58
  64. package/dist/lib-cjs/api/util/resourceHelper.js +214 -214
  65. package/dist/lib-cjs/api/util/sceneLoaderHelper.d.ts +58 -58
  66. package/dist/lib-cjs/api/util/sceneLoaderHelper.js +228 -228
  67. package/dist/lib-cjs/api/util/stringHelper.d.ts +13 -13
  68. package/dist/lib-cjs/api/util/stringHelper.js +32 -32
  69. package/dist/lib-cjs/api/util/structureHelper.d.ts +9 -9
  70. package/dist/lib-cjs/api/util/structureHelper.js +57 -57
  71. package/dist/lib-cjs/buildinfo.json +3 -3
  72. package/dist/lib-cjs/commonjs.tsconfig.tsbuildinfo +1 -1
  73. package/dist/lib-cjs/index.d.ts +63 -63
  74. package/dist/lib-cjs/index.js +128 -128
  75. package/package.json +93 -92
  76. package/src/api/classes/animationInterface.ts +10 -10
  77. package/src/api/classes/dottedPath.ts +181 -181
  78. package/src/api/classes/element.ts +766 -766
  79. package/src/api/classes/event.ts +457 -457
  80. package/src/api/classes/eventBroadcaster.ts +52 -52
  81. package/src/api/classes/fuzzyMap.ts +21 -21
  82. package/src/api/classes/parameter.ts +686 -686
  83. package/src/api/classes/parameterObservable.ts +73 -73
  84. package/src/api/classes/parameterizable.ts +87 -87
  85. package/src/api/classes/placementAnimation.ts +162 -162
  86. package/src/api/classes/variant.ts +965 -965
  87. package/src/api/classes/variantInstance.ts +123 -123
  88. package/src/api/classes/variantParameterizable.ts +83 -83
  89. package/src/api/classes/viewer.ts +751 -751
  90. package/src/api/classes/viewerError.ts +63 -63
  91. package/src/api/classes/viewerLight.ts +335 -335
  92. package/src/api/internal/debugViewer.ts +90 -90
  93. package/src/api/internal/lensRendering.ts +9 -9
  94. package/src/api/internal/sceneSetup.ts +208 -208
  95. package/src/api/manager/animationManager.ts +143 -143
  96. package/src/api/manager/gltfExportManager.ts +337 -334
  97. package/src/api/manager/sceneManager.ts +134 -134
  98. package/src/api/manager/tagManager.ts +572 -572
  99. package/src/api/manager/textureLoadManager.ts +107 -107
  100. package/src/api/manager/variantInstanceManager.ts +306 -306
  101. package/src/api/store/specStorage.ts +68 -68
  102. package/src/api/util/babylonHelper.ts +915 -915
  103. package/src/api/util/debugHelper.ts +121 -121
  104. package/src/api/util/deviceHelper.ts +31 -31
  105. package/src/api/util/geometryHelper.ts +142 -142
  106. package/src/api/util/globalTypes.ts +566 -566
  107. package/src/api/util/resourceHelper.ts +201 -201
  108. package/src/api/util/sceneLoaderHelper.ts +247 -247
  109. package/src/api/util/stringHelper.ts +30 -30
  110. package/src/api/util/structureHelper.ts +62 -62
  111. package/src/buildinfo.json +3 -3
  112. package/src/dev.ts +70 -70
  113. package/src/index.ts +116 -116
  114. package/src/types.d.ts +49 -49
@@ -1,247 +1,247 @@
1
- import { Event, emitter } from '../classes/event';
2
- import { applyMaterial, getOrCreateMaterial, injectMetadata } from './babylonHelper';
3
- import { sleep } from './resourceHelper';
4
- import { ISceneLoaderPlugin } from '@babylonjs/core/Loading/sceneLoader';
5
- import { Material } from '@babylonjs/core/Materials/material';
6
- import { InstancedMesh } from '@babylonjs/core/Meshes/instancedMesh';
7
- import { Observer } from '@babylonjs/core/Misc/observable';
8
- import { Tags } from '@babylonjs/core/Misc/tags';
9
- import { AssetContainer } from '@babylonjs/core/assetContainer';
10
- //! overload DOM API Node due to name-clash with BJS
11
- import { Node as BjsNode } from '@babylonjs/core/node';
12
- import { Scene } from '@babylonjs/core/scene';
13
- import { Nullable } from '@babylonjs/core/types';
14
- import { isArray, isString } from 'lodash-es';
15
- import has from 'lodash-es/has';
16
-
17
- // map for keeping track of active "node enable" observers
18
- const enableObserverMap: {
19
- [concerningNodeId: string]: { currNodeId: string; observer: Nullable<Observer<boolean>> }[];
20
- } = {};
21
-
22
- export const missingMaterialMetadataName = 'missingMaterial';
23
- export const parsedMaterialIdMetadataName = 'parsedMaterialId';
24
-
25
- /**
26
- * Create and return a custom loader plugin to be registered with SceneLoader, that allows
27
- * us to run our own code against the input data before using the standard procedure to
28
- * import.
29
- * @param previousLoaderPlugin the actual loader that's executed after manipulating the input data
30
- * @returns Custom loader plugin to be registered with SceneLoader.RegisterPlugin()
31
- */
32
- export const getCustomCbnBabylonLoaderPlugin = function (previousLoaderPlugin: ISceneLoaderPlugin): ISceneLoaderPlugin {
33
- const customLoader: ISceneLoaderPlugin = {
34
- name: 'cbnCustomBabylonLoader',
35
- extensions: '.babylon',
36
- importMesh: previousLoaderPlugin.importMesh,
37
- load: previousLoaderPlugin.load,
38
- loadAssetContainer: function (scene, data, rootUrl, onError) {
39
- //* 1) --- manipulate ORIGINAL data
40
- const dataParsed = JSON.parse(data);
41
- //* 2) --- call default (non-custom) loading method
42
- const importedContainer = previousLoaderPlugin.loadAssetContainer(scene, data, rootUrl);
43
- //* 3) --- manipulate IMPORTED data
44
- addMissingMaterialMetadata(dataParsed, importedContainer);
45
- reconstructTagsForInstancedMeshes(dataParsed, importedContainer);
46
- //* 4) --- return imported data
47
- return importedContainer;
48
- },
49
- };
50
- return customLoader;
51
- };
52
-
53
- /**
54
- * Return an observer to be applied to meshes in order to post-load missing materials
55
- * upon set enabled/visible.
56
- *
57
- * @param concerningMesh Mesh to look for missing materials on, and create/apply to (when found).
58
- * @returns observer
59
- */
60
- export const getMaterialPostLoadObserver = function (concerningMesh: Mesh) {
61
- return async () => {
62
- const scene = concerningMesh.getScene();
63
-
64
- // can't check `isEnabled` immediatly, since the enabled state of parents and childs is not synced yet
65
- // postpone one cycle to ensure a correct parent-child enable relation
66
- await sleep(0);
67
-
68
- const hasBeenEnabled = concerningMesh.isEnabled(true);
69
- const materialMissing = has(concerningMesh.metadata, missingMaterialMetadataName);
70
- if (!hasBeenEnabled || !materialMissing) return;
71
- // get id of missing material
72
- const missingMatId = concerningMesh.metadata[missingMaterialMetadataName];
73
- // get material and apply it on the concerning mesh after all textures have been loaded
74
- const material = await getOrCreateMaterial(concerningMesh.getScene(), missingMatId);
75
- applyMaterial(material, concerningMesh).then(() =>
76
- emitter.emit(Event.MESH_MATERIAL_APPLIED, concerningMesh, material)
77
- );
78
- // since the material is there now, we do not need the related metadata tag anymore
79
- delete concerningMesh.metadata[missingMaterialMetadataName];
80
-
81
- // remove all "enable" observers that were assigned to the concerning mesh
82
- // the mesh got visible and therefore the observers are not needed anymore
83
- enableObserverMap[concerningMesh.id]?.forEach(entry => {
84
- const currNode = scene.getMeshById(entry.currNodeId);
85
- currNode?.onEnabledStateChangedObservable.remove(entry.observer);
86
- });
87
- // also remove from the local observer map
88
- delete enableObserverMap[concerningMesh.id];
89
- };
90
- };
91
-
92
- type InstanceData = {
93
- name: string;
94
- tags?: string;
95
- };
96
-
97
- function _isMeshInstanceData(data: any): data is InstanceData {
98
- const hasName = isString(data.name);
99
- const hasValidTags = !data.tags || isString(data.tags);
100
- return hasName && hasValidTags;
101
- }
102
-
103
- type MeshData = {
104
- name: string;
105
- materialId?: string;
106
- instances?: unknown[];
107
- };
108
-
109
- function _isMeshData(data: any): data is MeshData {
110
- const hasName = isString(data.name);
111
- const hasValidMaterialId = !data.materialId || isString(data.materialId);
112
-
113
- return hasName && hasValidMaterialId;
114
- }
115
-
116
- type DataWithMeshes = { meshes: unknown[] };
117
-
118
- function _isDataWithMeshes(data: any): data is DataWithMeshes {
119
- return data && isArray(data.meshes);
120
- }
121
-
122
- /**
123
- * Internal function that compares the original meshes on a .babylon file with what was loaded,
124
- * and tags missing materials with respective metadata on respective meshes.
125
- * @param dataParsed original data
126
- * @param container loaded data
127
- */
128
- export const addMissingMaterialMetadata = function (dataParsed: unknown, container: AssetContainer) {
129
- if (!_isDataWithMeshes(dataParsed)) return;
130
-
131
- const validatedMeshes = dataParsed.meshes.filter(_isMeshData);
132
-
133
- container.meshes.forEach(importedMesh => {
134
- const parsedMesh = validatedMeshes.find(mesh => mesh.name === importedMesh.name);
135
-
136
- // save original material id of the imported babylon or GLB file
137
- injectMetadata(importedMesh, { [parsedMaterialIdMetadataName]: parsedMesh?.materialId }, false);
138
-
139
- const materialOnImportedMesh = importedMesh.material?.id;
140
- const materialOnOriginalMesh = parsedMesh?.materialId;
141
-
142
- if (materialOnOriginalMesh && materialOnImportedMesh !== materialOnOriginalMesh) {
143
- window.Cbn?.Assets.assertMaterialExists(materialOnOriginalMesh);
144
- injectMetadata(importedMesh, { [missingMaterialMetadataName]: materialOnOriginalMesh }, false);
145
- }
146
- });
147
- };
148
-
149
- /**
150
- * Help function for manipulating tags of instances meshes after parsing.
151
- * Per default babylon attaches the tags of the source mesh to the instance, **but only** if no tags are set for the
152
- * instanced mesh. If the instanced mesh has dedicated tags set, the ones from the source mesh are **not** copied over.
153
- * In this case it's not possible to have tags on the source mesh but not on the instance, which is a problem with our
154
- * tagging system in the viewer and the Combeenation asset editor as well.
155
- * This function rejects the default tag import algorithm and just copies the tags of the original parsed node without
156
- * any parent synchronization.
157
- *
158
- * @param dataParsed original data
159
- * @param container loaded data
160
- */
161
- export const reconstructTagsForInstancedMeshes = function (dataParsed: unknown, container: AssetContainer) {
162
- if (!_isDataWithMeshes(dataParsed)) return;
163
-
164
- const validatedMeshes = dataParsed.meshes.filter(_isMeshData);
165
-
166
- container.meshes.forEach(importedMesh => {
167
- if (importedMesh instanceof InstancedMesh) {
168
- // remove all tags from the imported mesh if there are some, since these tags are probably coming from the
169
- // source mesh, if no tags are set there is no need for further operation though
170
- const importedTags = Tags.GetTags(importedMesh);
171
- if (importedTags) {
172
- Tags.RemoveTagsFrom(importedMesh, importedTags);
173
-
174
- // get tags of parsed instanced mesh and set them on the imported instanced mesh
175
- const parsedSourceMesh = validatedMeshes.find(mesh => mesh.name === importedMesh.sourceMesh.name);
176
- const validatedSourceMeshInstances = parsedSourceMesh?.instances?.filter(_isMeshInstanceData);
177
- const parsedInstancedMesh = validatedSourceMeshInstances?.find(mesh => mesh.name === importedMesh.name);
178
- const parsedTags = parsedInstancedMesh?.tags;
179
- if (parsedTags) {
180
- Tags.AddTagsTo(importedMesh, parsedTags);
181
- }
182
- }
183
- }
184
- });
185
- };
186
-
187
- /**
188
- * Adds an "onEnabledStateChanged" observer to the given mesh and all its parents:
189
- * The added observer (`getMaterialPostLoadObserver`) handles creation of missing materials once the given node is
190
- * enabled.
191
- */
192
- export const addMissingMaterialObserver = function (node: BjsNode) {
193
- // set the concerning node, i.e. the node the observer should check for missing material.
194
- // for instanced meshes, we want the sourcemesh here.
195
- const concerningNode = node instanceof InstancedMesh ? node.sourceMesh : (node as Mesh);
196
-
197
- // observer is pointless if concerning node has no missing material
198
- if (!has(concerningNode.metadata, missingMaterialMetadataName)) return;
199
-
200
- // for each of our AbstractMeshes, set an observer on the AbstractMesh itself and all of its parents.
201
- let currNode: Nullable<BjsNode> = node;
202
- while (currNode) {
203
- const callback = getMaterialPostLoadObserver(concerningNode);
204
- const observer = currNode.onEnabledStateChangedObservable.add(callback);
205
-
206
- // store the observer in a local map to keep track of the active "enable" observers
207
- // observers will be removed when the concerning node gets enabled
208
- if (!enableObserverMap[concerningNode.id]) {
209
- enableObserverMap[concerningNode.id] = [];
210
- }
211
- enableObserverMap[concerningNode.id].push({ currNodeId: currNode.id, observer });
212
-
213
- currNode = currNode.parent;
214
- }
215
- };
216
-
217
- export const removeMissingMaterialObserver = function (node: BjsNode) {
218
- // set the concerning node, i.e. the node the observer should check for missing material.
219
- // for instanced meshes, we want the sourcemesh here.
220
- const concerningNode = node instanceof InstancedMesh ? node.sourceMesh : (node as Mesh);
221
- let currNode: Nullable<BjsNode> = node;
222
- while (currNode) {
223
- enableObserverMap[concerningNode.id]?.forEach(entry => {
224
- if (entry.currNodeId === currNode?.id) {
225
- currNode.onEnabledStateChangedObservable.remove(entry.observer);
226
- }
227
- });
228
- currNode = currNode.parent;
229
- }
230
- delete enableObserverMap[concerningNode.id];
231
- };
232
-
233
- /**
234
- * Look up the provided materials (see library import) and create and return one if found.
235
- *
236
- * @param materialId Babylon.js material-id. E.g. 'concrete".
237
- * @param scene Babylon.js scene
238
- * @returns PBRMaterial | null
239
- */
240
- export const createMaterialFromCbnAssets = async function (materialId: string, scene: Scene): Promise<Material | null> {
241
- const materialDefinition = await window.Cbn?.Assets.getMaterial(materialId);
242
- // The generic `Material.Parse` actually returns a more specific material like `BABYLON.StandardMaterial`,
243
- // `BABYLON.PBRMaterial` or stuff like `BABYLON.PBRMetallicRoughnessMaterial` etc. based on the given `customType`
244
- // within the material JSON definition
245
- const material = materialDefinition && Material.Parse(materialDefinition, scene, '');
246
- return material || null;
247
- };
1
+ import { Event, emitter } from '../classes/event';
2
+ import { applyMaterial, getOrCreateMaterial, injectMetadata } from './babylonHelper';
3
+ import { sleep } from './resourceHelper';
4
+ import { ISceneLoaderPlugin } from '@babylonjs/core/Loading/sceneLoader';
5
+ import { Material } from '@babylonjs/core/Materials/material';
6
+ import { InstancedMesh } from '@babylonjs/core/Meshes/instancedMesh';
7
+ import { Observer } from '@babylonjs/core/Misc/observable';
8
+ import { Tags } from '@babylonjs/core/Misc/tags';
9
+ import { AssetContainer } from '@babylonjs/core/assetContainer';
10
+ //! overload DOM API Node due to name-clash with BJS
11
+ import { Node as BjsNode } from '@babylonjs/core/node';
12
+ import { Scene } from '@babylonjs/core/scene';
13
+ import { Nullable } from '@babylonjs/core/types';
14
+ import { isArray, isString } from 'lodash-es';
15
+ import has from 'lodash-es/has';
16
+
17
+ // map for keeping track of active "node enable" observers
18
+ const enableObserverMap: {
19
+ [concerningNodeId: string]: { currNodeId: string; observer: Nullable<Observer<boolean>> }[];
20
+ } = {};
21
+
22
+ export const missingMaterialMetadataName = 'missingMaterial';
23
+ export const parsedMaterialIdMetadataName = 'parsedMaterialId';
24
+
25
+ /**
26
+ * Create and return a custom loader plugin to be registered with SceneLoader, that allows
27
+ * us to run our own code against the input data before using the standard procedure to
28
+ * import.
29
+ * @param previousLoaderPlugin the actual loader that's executed after manipulating the input data
30
+ * @returns Custom loader plugin to be registered with SceneLoader.RegisterPlugin()
31
+ */
32
+ export const getCustomCbnBabylonLoaderPlugin = function (previousLoaderPlugin: ISceneLoaderPlugin): ISceneLoaderPlugin {
33
+ const customLoader: ISceneLoaderPlugin = {
34
+ name: 'cbnCustomBabylonLoader',
35
+ extensions: '.babylon',
36
+ importMesh: previousLoaderPlugin.importMesh,
37
+ load: previousLoaderPlugin.load,
38
+ loadAssetContainer: function (scene, data, rootUrl, onError) {
39
+ //* 1) --- manipulate ORIGINAL data
40
+ const dataParsed = JSON.parse(data);
41
+ //* 2) --- call default (non-custom) loading method
42
+ const importedContainer = previousLoaderPlugin.loadAssetContainer(scene, data, rootUrl);
43
+ //* 3) --- manipulate IMPORTED data
44
+ addMissingMaterialMetadata(dataParsed, importedContainer);
45
+ reconstructTagsForInstancedMeshes(dataParsed, importedContainer);
46
+ //* 4) --- return imported data
47
+ return importedContainer;
48
+ },
49
+ };
50
+ return customLoader;
51
+ };
52
+
53
+ /**
54
+ * Return an observer to be applied to meshes in order to post-load missing materials
55
+ * upon set enabled/visible.
56
+ *
57
+ * @param concerningMesh Mesh to look for missing materials on, and create/apply to (when found).
58
+ * @returns observer
59
+ */
60
+ export const getMaterialPostLoadObserver = function (concerningMesh: Mesh) {
61
+ return async () => {
62
+ const scene = concerningMesh.getScene();
63
+
64
+ // can't check `isEnabled` immediatly, since the enabled state of parents and childs is not synced yet
65
+ // postpone one cycle to ensure a correct parent-child enable relation
66
+ await sleep(0);
67
+
68
+ const hasBeenEnabled = concerningMesh.isEnabled(true);
69
+ const materialMissing = has(concerningMesh.metadata, missingMaterialMetadataName);
70
+ if (!hasBeenEnabled || !materialMissing) return;
71
+ // get id of missing material
72
+ const missingMatId = concerningMesh.metadata[missingMaterialMetadataName];
73
+ // get material and apply it on the concerning mesh after all textures have been loaded
74
+ const material = await getOrCreateMaterial(concerningMesh.getScene(), missingMatId);
75
+ applyMaterial(material, concerningMesh).then(() =>
76
+ emitter.emit(Event.MESH_MATERIAL_APPLIED, concerningMesh, material)
77
+ );
78
+ // since the material is there now, we do not need the related metadata tag anymore
79
+ delete concerningMesh.metadata[missingMaterialMetadataName];
80
+
81
+ // remove all "enable" observers that were assigned to the concerning mesh
82
+ // the mesh got visible and therefore the observers are not needed anymore
83
+ enableObserverMap[concerningMesh.id]?.forEach(entry => {
84
+ const currNode = scene.getMeshById(entry.currNodeId);
85
+ currNode?.onEnabledStateChangedObservable.remove(entry.observer);
86
+ });
87
+ // also remove from the local observer map
88
+ delete enableObserverMap[concerningMesh.id];
89
+ };
90
+ };
91
+
92
+ type InstanceData = {
93
+ name: string;
94
+ tags?: string;
95
+ };
96
+
97
+ function _isMeshInstanceData(data: any): data is InstanceData {
98
+ const hasName = isString(data.name);
99
+ const hasValidTags = !data.tags || isString(data.tags);
100
+ return hasName && hasValidTags;
101
+ }
102
+
103
+ type MeshData = {
104
+ name: string;
105
+ materialId?: string;
106
+ instances?: unknown[];
107
+ };
108
+
109
+ function _isMeshData(data: any): data is MeshData {
110
+ const hasName = isString(data.name);
111
+ const hasValidMaterialId = !data.materialId || isString(data.materialId);
112
+
113
+ return hasName && hasValidMaterialId;
114
+ }
115
+
116
+ type DataWithMeshes = { meshes: unknown[] };
117
+
118
+ function _isDataWithMeshes(data: any): data is DataWithMeshes {
119
+ return data && isArray(data.meshes);
120
+ }
121
+
122
+ /**
123
+ * Internal function that compares the original meshes on a .babylon file with what was loaded,
124
+ * and tags missing materials with respective metadata on respective meshes.
125
+ * @param dataParsed original data
126
+ * @param container loaded data
127
+ */
128
+ export const addMissingMaterialMetadata = function (dataParsed: unknown, container: AssetContainer) {
129
+ if (!_isDataWithMeshes(dataParsed)) return;
130
+
131
+ const validatedMeshes = dataParsed.meshes.filter(_isMeshData);
132
+
133
+ container.meshes.forEach(importedMesh => {
134
+ const parsedMesh = validatedMeshes.find(mesh => mesh.name === importedMesh.name);
135
+
136
+ // save original material id of the imported babylon or GLB file
137
+ injectMetadata(importedMesh, { [parsedMaterialIdMetadataName]: parsedMesh?.materialId }, false);
138
+
139
+ const materialOnImportedMesh = importedMesh.material?.id;
140
+ const materialOnOriginalMesh = parsedMesh?.materialId;
141
+
142
+ if (materialOnOriginalMesh && materialOnImportedMesh !== materialOnOriginalMesh) {
143
+ window.Cbn?.Assets.assertMaterialExists(materialOnOriginalMesh);
144
+ injectMetadata(importedMesh, { [missingMaterialMetadataName]: materialOnOriginalMesh }, false);
145
+ }
146
+ });
147
+ };
148
+
149
+ /**
150
+ * Help function for manipulating tags of instances meshes after parsing.
151
+ * Per default babylon attaches the tags of the source mesh to the instance, **but only** if no tags are set for the
152
+ * instanced mesh. If the instanced mesh has dedicated tags set, the ones from the source mesh are **not** copied over.
153
+ * In this case it's not possible to have tags on the source mesh but not on the instance, which is a problem with our
154
+ * tagging system in the viewer and the Combeenation asset editor as well.
155
+ * This function rejects the default tag import algorithm and just copies the tags of the original parsed node without
156
+ * any parent synchronization.
157
+ *
158
+ * @param dataParsed original data
159
+ * @param container loaded data
160
+ */
161
+ export const reconstructTagsForInstancedMeshes = function (dataParsed: unknown, container: AssetContainer) {
162
+ if (!_isDataWithMeshes(dataParsed)) return;
163
+
164
+ const validatedMeshes = dataParsed.meshes.filter(_isMeshData);
165
+
166
+ container.meshes.forEach(importedMesh => {
167
+ if (importedMesh instanceof InstancedMesh) {
168
+ // remove all tags from the imported mesh if there are some, since these tags are probably coming from the
169
+ // source mesh, if no tags are set there is no need for further operation though
170
+ const importedTags = Tags.GetTags(importedMesh);
171
+ if (importedTags) {
172
+ Tags.RemoveTagsFrom(importedMesh, importedTags);
173
+
174
+ // get tags of parsed instanced mesh and set them on the imported instanced mesh
175
+ const parsedSourceMesh = validatedMeshes.find(mesh => mesh.name === importedMesh.sourceMesh.name);
176
+ const validatedSourceMeshInstances = parsedSourceMesh?.instances?.filter(_isMeshInstanceData);
177
+ const parsedInstancedMesh = validatedSourceMeshInstances?.find(mesh => mesh.name === importedMesh.name);
178
+ const parsedTags = parsedInstancedMesh?.tags;
179
+ if (parsedTags) {
180
+ Tags.AddTagsTo(importedMesh, parsedTags);
181
+ }
182
+ }
183
+ }
184
+ });
185
+ };
186
+
187
+ /**
188
+ * Adds an "onEnabledStateChanged" observer to the given mesh and all its parents:
189
+ * The added observer (`getMaterialPostLoadObserver`) handles creation of missing materials once the given node is
190
+ * enabled.
191
+ */
192
+ export const addMissingMaterialObserver = function (node: BjsNode) {
193
+ // set the concerning node, i.e. the node the observer should check for missing material.
194
+ // for instanced meshes, we want the sourcemesh here.
195
+ const concerningNode = node instanceof InstancedMesh ? node.sourceMesh : (node as Mesh);
196
+
197
+ // observer is pointless if concerning node has no missing material
198
+ if (!has(concerningNode.metadata, missingMaterialMetadataName)) return;
199
+
200
+ // for each of our AbstractMeshes, set an observer on the AbstractMesh itself and all of its parents.
201
+ let currNode: Nullable<BjsNode> = node;
202
+ while (currNode) {
203
+ const callback = getMaterialPostLoadObserver(concerningNode);
204
+ const observer = currNode.onEnabledStateChangedObservable.add(callback);
205
+
206
+ // store the observer in a local map to keep track of the active "enable" observers
207
+ // observers will be removed when the concerning node gets enabled
208
+ if (!enableObserverMap[concerningNode.id]) {
209
+ enableObserverMap[concerningNode.id] = [];
210
+ }
211
+ enableObserverMap[concerningNode.id].push({ currNodeId: currNode.id, observer });
212
+
213
+ currNode = currNode.parent;
214
+ }
215
+ };
216
+
217
+ export const removeMissingMaterialObserver = function (node: BjsNode) {
218
+ // set the concerning node, i.e. the node the observer should check for missing material.
219
+ // for instanced meshes, we want the sourcemesh here.
220
+ const concerningNode = node instanceof InstancedMesh ? node.sourceMesh : (node as Mesh);
221
+ let currNode: Nullable<BjsNode> = node;
222
+ while (currNode) {
223
+ enableObserverMap[concerningNode.id]?.forEach(entry => {
224
+ if (entry.currNodeId === currNode?.id) {
225
+ currNode.onEnabledStateChangedObservable.remove(entry.observer);
226
+ }
227
+ });
228
+ currNode = currNode.parent;
229
+ }
230
+ delete enableObserverMap[concerningNode.id];
231
+ };
232
+
233
+ /**
234
+ * Look up the provided materials (see library import) and create and return one if found.
235
+ *
236
+ * @param materialId Babylon.js material-id. E.g. 'concrete".
237
+ * @param scene Babylon.js scene
238
+ * @returns PBRMaterial | null
239
+ */
240
+ export const createMaterialFromCbnAssets = async function (materialId: string, scene: Scene): Promise<Material | null> {
241
+ const materialDefinition = await window.Cbn?.Assets.getMaterial(materialId);
242
+ // The generic `Material.Parse` actually returns a more specific material like `BABYLON.StandardMaterial`,
243
+ // `BABYLON.PBRMaterial` or stuff like `BABYLON.PBRMetallicRoughnessMaterial` etc. based on the given `customType`
244
+ // within the material JSON definition
245
+ const material = materialDefinition && Material.Parse(materialDefinition, scene, '');
246
+ return material || null;
247
+ };
@@ -1,30 +1,30 @@
1
- /**
2
- * Creates a random uuidv4.
3
- */
4
- const uuidv4 = function () {
5
- return ([1e7].toString() + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => {
6
- const cNum = parseInt(c);
7
- return (cNum ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (cNum / 4)))).toString(16);
8
- });
9
- };
10
-
11
- /**
12
- * Converts a string from camel case to snake case.
13
- */
14
- const camelToSnakeCase = function (str: string): string {
15
- return str
16
- .replace(/([A-Z])/g, ' $1')
17
- .trim()
18
- .split(' ')
19
- .join('_')
20
- .toLowerCase();
21
- };
22
-
23
- /**
24
- * Replaces all dots from the input string with a desired character ('/' by default)
25
- */
26
- const replaceDots = function (str: string, replaceChar = '/'): string {
27
- return str.split('.').join(replaceChar);
28
- };
29
-
30
- export { uuidv4, camelToSnakeCase, replaceDots };
1
+ /**
2
+ * Creates a random uuidv4.
3
+ */
4
+ const uuidv4 = function () {
5
+ return ([1e7].toString() + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => {
6
+ const cNum = parseInt(c);
7
+ return (cNum ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (cNum / 4)))).toString(16);
8
+ });
9
+ };
10
+
11
+ /**
12
+ * Converts a string from camel case to snake case.
13
+ */
14
+ const camelToSnakeCase = function (str: string): string {
15
+ return str
16
+ .replace(/([A-Z])/g, ' $1')
17
+ .trim()
18
+ .split(' ')
19
+ .join('_')
20
+ .toLowerCase();
21
+ };
22
+
23
+ /**
24
+ * Replaces all dots from the input string with a desired character ('/' by default)
25
+ */
26
+ const replaceDots = function (str: string, replaceChar = '/'): string {
27
+ return str.split('.').join(replaceChar);
28
+ };
29
+
30
+ export { uuidv4, camelToSnakeCase, replaceDots };