@combeenation/3d-viewer 12.4.1 → 13.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.
- package/dist/lib-cjs/buildinfo.json +1 -1
- package/dist/lib-cjs/commonjs.tsconfig.tsbuildinfo +1 -1
- package/dist/lib-cjs/index.d.ts +63 -63
- package/dist/lib-cjs/index.js +81 -114
- package/dist/lib-cjs/index.js.map +1 -1
- package/dist/lib-cjs/internal/cbnCustomBabylonLoaderPlugin.d.ts +10 -0
- package/dist/lib-cjs/internal/cbnCustomBabylonLoaderPlugin.js +124 -0
- package/dist/lib-cjs/internal/cbnCustomBabylonLoaderPlugin.js.map +1 -0
- package/dist/lib-cjs/internal/cloningHelper.d.ts +19 -0
- package/dist/lib-cjs/internal/cloningHelper.js +165 -0
- package/dist/lib-cjs/internal/cloningHelper.js.map +1 -0
- package/dist/lib-cjs/internal/deviceHelper.d.ts +9 -0
- package/dist/lib-cjs/{api/util → internal}/deviceHelper.js +6 -10
- package/dist/lib-cjs/internal/deviceHelper.js.map +1 -0
- package/dist/lib-cjs/internal/geometryHelper.d.ts +21 -0
- package/dist/lib-cjs/{api/util → internal}/geometryHelper.js +57 -24
- package/dist/lib-cjs/internal/geometryHelper.js.map +1 -0
- package/dist/lib-cjs/internal/metadataHelper.d.ts +26 -0
- package/dist/lib-cjs/internal/metadataHelper.js +51 -0
- package/dist/lib-cjs/internal/metadataHelper.js.map +1 -0
- package/dist/lib-cjs/internal/paintableHelper.d.ts +40 -0
- package/dist/lib-cjs/internal/paintableHelper.js +287 -0
- package/dist/lib-cjs/internal/paintableHelper.js.map +1 -0
- package/dist/lib-cjs/internal/tagsHelper.d.ts +12 -0
- package/dist/lib-cjs/internal/tagsHelper.js +38 -0
- package/dist/lib-cjs/internal/tagsHelper.js.map +1 -0
- package/dist/lib-cjs/manager/cameraManager.d.ts +51 -0
- package/dist/lib-cjs/manager/cameraManager.js +154 -0
- package/dist/lib-cjs/manager/cameraManager.js.map +1 -0
- package/dist/lib-cjs/manager/debugManager.d.ts +60 -0
- package/dist/lib-cjs/manager/debugManager.js +218 -0
- package/dist/lib-cjs/manager/debugManager.js.map +1 -0
- package/dist/lib-cjs/manager/eventManager.d.ts +52 -0
- package/dist/lib-cjs/manager/eventManager.js +72 -0
- package/dist/lib-cjs/manager/eventManager.js.map +1 -0
- package/dist/lib-cjs/{api/manager → manager}/gltfExportManager.d.ts +29 -34
- package/dist/lib-cjs/{api/manager → manager}/gltfExportManager.js +99 -120
- package/dist/lib-cjs/manager/gltfExportManager.js.map +1 -0
- package/dist/lib-cjs/manager/materialManager.d.ts +35 -0
- package/dist/lib-cjs/manager/materialManager.js +126 -0
- package/dist/lib-cjs/manager/materialManager.js.map +1 -0
- package/dist/lib-cjs/manager/modelManager.d.ts +145 -0
- package/dist/lib-cjs/manager/modelManager.js +381 -0
- package/dist/lib-cjs/manager/modelManager.js.map +1 -0
- package/dist/lib-cjs/manager/parameterManager.d.ts +210 -0
- package/dist/lib-cjs/manager/parameterManager.js +515 -0
- package/dist/lib-cjs/manager/parameterManager.js.map +1 -0
- package/dist/lib-cjs/manager/sceneManager.d.ts +45 -0
- package/dist/lib-cjs/manager/sceneManager.js +65 -0
- package/dist/lib-cjs/manager/sceneManager.js.map +1 -0
- package/dist/lib-cjs/manager/screenshotManager.d.ts +36 -0
- package/dist/lib-cjs/manager/screenshotManager.js +40 -0
- package/dist/lib-cjs/manager/screenshotManager.js.map +1 -0
- package/dist/lib-cjs/manager/textureManager.d.ts +12 -0
- package/dist/lib-cjs/manager/textureManager.js +44 -0
- package/dist/lib-cjs/manager/textureManager.js.map +1 -0
- package/dist/lib-cjs/viewer.d.ts +117 -0
- package/dist/lib-cjs/viewer.js +222 -0
- package/dist/lib-cjs/viewer.js.map +1 -0
- package/dist/lib-cjs/{api/classes/viewerError.d.ts → viewerError.d.ts} +6 -1
- package/dist/lib-cjs/{api/classes/viewerError.js → viewerError.js} +6 -1
- package/dist/lib-cjs/viewerError.js.map +1 -0
- package/package.json +10 -11
- package/src/dev.ts +14 -37
- package/src/{types.d.ts → globalTypes.d.ts} +8 -18
- package/src/index.ts +79 -113
- package/src/internal/cbnCustomBabylonLoaderPlugin.ts +149 -0
- package/src/internal/cloningHelper.ts +225 -0
- package/src/internal/deviceHelper.ts +25 -0
- package/src/{api/util → internal}/geometryHelper.ts +63 -24
- package/src/internal/metadataHelper.ts +63 -0
- package/src/internal/paintableHelper.ts +310 -0
- package/src/internal/tagsHelper.ts +41 -0
- package/src/manager/cameraManager.ts +236 -0
- package/src/manager/debugManager.ts +245 -0
- package/src/manager/eventManager.ts +72 -0
- package/src/{api/manager → manager}/gltfExportManager.ts +132 -125
- package/src/manager/materialManager.ts +135 -0
- package/src/manager/modelManager.ts +456 -0
- package/src/manager/parameterManager.ts +652 -0
- package/src/manager/sceneManager.ts +101 -0
- package/src/manager/screenshotManager.ts +59 -0
- package/src/manager/textureManager.ts +32 -0
- package/src/viewer.ts +296 -0
- package/src/{api/classes/viewerError.ts → viewerError.ts} +6 -1
- package/dist/lib-cjs/api/classes/animationInterface.d.ts +0 -8
- package/dist/lib-cjs/api/classes/animationInterface.js +0 -3
- package/dist/lib-cjs/api/classes/animationInterface.js.map +0 -1
- package/dist/lib-cjs/api/classes/dottedPath.d.ts +0 -79
- package/dist/lib-cjs/api/classes/dottedPath.js +0 -167
- package/dist/lib-cjs/api/classes/dottedPath.js.map +0 -1
- package/dist/lib-cjs/api/classes/element.d.ts +0 -153
- package/dist/lib-cjs/api/classes/element.js +0 -703
- package/dist/lib-cjs/api/classes/element.js.map +0 -1
- package/dist/lib-cjs/api/classes/event.d.ts +0 -401
- package/dist/lib-cjs/api/classes/event.js +0 -425
- package/dist/lib-cjs/api/classes/event.js.map +0 -1
- package/dist/lib-cjs/api/classes/eventBroadcaster.d.ts +0 -26
- package/dist/lib-cjs/api/classes/eventBroadcaster.js +0 -50
- package/dist/lib-cjs/api/classes/eventBroadcaster.js.map +0 -1
- package/dist/lib-cjs/api/classes/fuzzyMap.d.ts +0 -7
- package/dist/lib-cjs/api/classes/fuzzyMap.js +0 -22
- package/dist/lib-cjs/api/classes/fuzzyMap.js.map +0 -1
- package/dist/lib-cjs/api/classes/parameter.d.ts +0 -410
- package/dist/lib-cjs/api/classes/parameter.js +0 -643
- package/dist/lib-cjs/api/classes/parameter.js.map +0 -1
- package/dist/lib-cjs/api/classes/parameterObservable.d.ts +0 -36
- package/dist/lib-cjs/api/classes/parameterObservable.js +0 -73
- package/dist/lib-cjs/api/classes/parameterObservable.js.map +0 -1
- package/dist/lib-cjs/api/classes/parameterizable.d.ts +0 -15
- package/dist/lib-cjs/api/classes/parameterizable.js +0 -103
- package/dist/lib-cjs/api/classes/parameterizable.js.map +0 -1
- package/dist/lib-cjs/api/classes/placementAnimation.d.ts +0 -45
- package/dist/lib-cjs/api/classes/placementAnimation.js +0 -177
- package/dist/lib-cjs/api/classes/placementAnimation.js.map +0 -1
- package/dist/lib-cjs/api/classes/variant.d.ts +0 -261
- package/dist/lib-cjs/api/classes/variant.js +0 -873
- package/dist/lib-cjs/api/classes/variant.js.map +0 -1
- package/dist/lib-cjs/api/classes/variantInstance.d.ts +0 -53
- package/dist/lib-cjs/api/classes/variantInstance.js +0 -126
- package/dist/lib-cjs/api/classes/variantInstance.js.map +0 -1
- package/dist/lib-cjs/api/classes/variantParameterizable.d.ts +0 -17
- package/dist/lib-cjs/api/classes/variantParameterizable.js +0 -87
- package/dist/lib-cjs/api/classes/variantParameterizable.js.map +0 -1
- package/dist/lib-cjs/api/classes/viewer.d.ts +0 -215
- package/dist/lib-cjs/api/classes/viewer.js +0 -709
- package/dist/lib-cjs/api/classes/viewer.js.map +0 -1
- package/dist/lib-cjs/api/classes/viewerError.js.map +0 -1
- package/dist/lib-cjs/api/classes/viewerLight.d.ts +0 -66
- package/dist/lib-cjs/api/classes/viewerLight.js +0 -345
- package/dist/lib-cjs/api/classes/viewerLight.js.map +0 -1
- package/dist/lib-cjs/api/internal/lensRendering.d.ts +0 -8
- package/dist/lib-cjs/api/internal/lensRendering.js +0 -12
- package/dist/lib-cjs/api/internal/lensRendering.js.map +0 -1
- package/dist/lib-cjs/api/internal/sceneSetup.d.ts +0 -13
- package/dist/lib-cjs/api/internal/sceneSetup.js +0 -228
- package/dist/lib-cjs/api/internal/sceneSetup.js.map +0 -1
- package/dist/lib-cjs/api/manager/animationManager.d.ts +0 -30
- package/dist/lib-cjs/api/manager/animationManager.js +0 -127
- package/dist/lib-cjs/api/manager/animationManager.js.map +0 -1
- package/dist/lib-cjs/api/manager/gltfExportManager.js.map +0 -1
- package/dist/lib-cjs/api/manager/sceneManager.d.ts +0 -33
- package/dist/lib-cjs/api/manager/sceneManager.js +0 -129
- package/dist/lib-cjs/api/manager/sceneManager.js.map +0 -1
- package/dist/lib-cjs/api/manager/tagManager.d.ts +0 -118
- package/dist/lib-cjs/api/manager/tagManager.js +0 -531
- package/dist/lib-cjs/api/manager/tagManager.js.map +0 -1
- package/dist/lib-cjs/api/manager/textureLoadManager.d.ts +0 -22
- package/dist/lib-cjs/api/manager/textureLoadManager.js +0 -108
- package/dist/lib-cjs/api/manager/textureLoadManager.js.map +0 -1
- package/dist/lib-cjs/api/manager/variantInstanceManager.d.ts +0 -106
- package/dist/lib-cjs/api/manager/variantInstanceManager.js +0 -291
- package/dist/lib-cjs/api/manager/variantInstanceManager.js.map +0 -1
- package/dist/lib-cjs/api/store/specStorage.d.ts +0 -32
- package/dist/lib-cjs/api/store/specStorage.js +0 -66
- package/dist/lib-cjs/api/store/specStorage.js.map +0 -1
- package/dist/lib-cjs/api/util/babylonHelper.d.ts +0 -238
- package/dist/lib-cjs/api/util/babylonHelper.js +0 -826
- package/dist/lib-cjs/api/util/babylonHelper.js.map +0 -1
- package/dist/lib-cjs/api/util/debugHelper.d.ts +0 -9
- package/dist/lib-cjs/api/util/debugHelper.js +0 -94
- package/dist/lib-cjs/api/util/debugHelper.js.map +0 -1
- package/dist/lib-cjs/api/util/deviceHelper.d.ts +0 -9
- package/dist/lib-cjs/api/util/deviceHelper.js.map +0 -1
- package/dist/lib-cjs/api/util/geometryHelper.d.ts +0 -17
- package/dist/lib-cjs/api/util/geometryHelper.js.map +0 -1
- package/dist/lib-cjs/api/util/globalTypes.d.ts +0 -490
- package/dist/lib-cjs/api/util/globalTypes.js +0 -2
- package/dist/lib-cjs/api/util/globalTypes.js.map +0 -1
- package/dist/lib-cjs/api/util/resourceHelper.d.ts +0 -58
- package/dist/lib-cjs/api/util/resourceHelper.js +0 -215
- package/dist/lib-cjs/api/util/resourceHelper.js.map +0 -1
- package/dist/lib-cjs/api/util/sceneLoaderHelper.d.ts +0 -58
- package/dist/lib-cjs/api/util/sceneLoaderHelper.js +0 -229
- package/dist/lib-cjs/api/util/sceneLoaderHelper.js.map +0 -1
- package/dist/lib-cjs/api/util/stringHelper.d.ts +0 -13
- package/dist/lib-cjs/api/util/stringHelper.js +0 -33
- package/dist/lib-cjs/api/util/stringHelper.js.map +0 -1
- package/dist/lib-cjs/api/util/structureHelper.d.ts +0 -9
- package/dist/lib-cjs/api/util/structureHelper.js +0 -58
- package/dist/lib-cjs/api/util/structureHelper.js.map +0 -1
- package/src/api/classes/animationInterface.ts +0 -10
- package/src/api/classes/dottedPath.ts +0 -181
- package/src/api/classes/element.ts +0 -766
- package/src/api/classes/event.ts +0 -457
- package/src/api/classes/eventBroadcaster.ts +0 -52
- package/src/api/classes/fuzzyMap.ts +0 -21
- package/src/api/classes/parameter.ts +0 -686
- package/src/api/classes/parameterObservable.ts +0 -73
- package/src/api/classes/parameterizable.ts +0 -87
- package/src/api/classes/placementAnimation.ts +0 -162
- package/src/api/classes/variant.ts +0 -965
- package/src/api/classes/variantInstance.ts +0 -123
- package/src/api/classes/variantParameterizable.ts +0 -83
- package/src/api/classes/viewer.ts +0 -751
- package/src/api/classes/viewerLight.ts +0 -335
- package/src/api/internal/debugViewer.ts +0 -90
- package/src/api/internal/lensRendering.ts +0 -9
- package/src/api/internal/sceneSetup.ts +0 -208
- package/src/api/manager/animationManager.ts +0 -143
- package/src/api/manager/sceneManager.ts +0 -134
- package/src/api/manager/tagManager.ts +0 -572
- package/src/api/manager/textureLoadManager.ts +0 -107
- package/src/api/manager/variantInstanceManager.ts +0 -306
- package/src/api/store/specStorage.ts +0 -68
- package/src/api/util/babylonHelper.ts +0 -915
- package/src/api/util/debugHelper.ts +0 -121
- package/src/api/util/deviceHelper.ts +0 -31
- package/src/api/util/globalTypes.ts +0 -566
- package/src/api/util/resourceHelper.ts +0 -201
- package/src/api/util/sceneLoaderHelper.ts +0 -247
- package/src/api/util/stringHelper.ts +0 -30
- package/src/api/util/structureHelper.ts +0 -62
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbstractMesh,
|
|
3
|
+
BaseTexture,
|
|
4
|
+
Material,
|
|
5
|
+
StandardMaterial,
|
|
6
|
+
Viewer,
|
|
7
|
+
ViewerError,
|
|
8
|
+
ViewerErrorIds,
|
|
9
|
+
ViewerEvent,
|
|
10
|
+
} from '../index';
|
|
11
|
+
import {
|
|
12
|
+
clearInternalMetadataValue,
|
|
13
|
+
getInternalMetadataValue,
|
|
14
|
+
setInternalMetadataValue,
|
|
15
|
+
} from '../internal/metadataHelper';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Manager for material related tasks
|
|
19
|
+
*/
|
|
20
|
+
export class MaterialManager {
|
|
21
|
+
/**
|
|
22
|
+
* CAUTION: this has to be in sync with the Combeenation backend!
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
public static readonly CBN_FALLBACK_MATERIAL_NAME = '$fallback';
|
|
26
|
+
|
|
27
|
+
protected _createMaterialPromises: { [materialId: string]: Promise<Material | null> } = {};
|
|
28
|
+
|
|
29
|
+
/** @internal */
|
|
30
|
+
public constructor(protected viewer: Viewer) {}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Assigns material with certain id to desired mesh.\
|
|
34
|
+
* Creates the material if not already available (see {@link getOrCreateMaterial}).
|
|
35
|
+
*/
|
|
36
|
+
public async setMaterialOnMesh(materialId: string, mesh: AbstractMesh): Promise<void> {
|
|
37
|
+
setInternalMetadataValue(mesh, 'materialToBeSet', materialId);
|
|
38
|
+
|
|
39
|
+
const material = await this.getOrCreateMaterial(materialId, mesh);
|
|
40
|
+
|
|
41
|
+
// there may have been later "setMaterialOnMesh" calls with faster material creation
|
|
42
|
+
// make sure to assign the latest material!
|
|
43
|
+
if (getInternalMetadataValue(mesh, 'materialToBeSet') === materialId) {
|
|
44
|
+
mesh.material = material;
|
|
45
|
+
clearInternalMetadataValue(mesh, 'materialToBeSet');
|
|
46
|
+
clearInternalMetadataValue(mesh, 'deferredMaterial');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Returns material with certain id if already available in the scene.\
|
|
52
|
+
* If this is not the case material is created from a
|
|
53
|
+
* "[Material asset](https://docs.combeenation.com/docs/howto-create-and-use-babylon-and-material-asset)"
|
|
54
|
+
* on the Combeenation server.\
|
|
55
|
+
* Waits until textures of material are fully loaded and shader is compiled. In this way "flickering" effects
|
|
56
|
+
* will be avoided, since the material would be incomplete without its loaded textures.
|
|
57
|
+
*
|
|
58
|
+
* @param mesh Required for shader compilation check, can be omitted if this check should not be done.\
|
|
59
|
+
* Use {@link setMaterialOnMesh} instead if the material should be applied on the mesh immediately.
|
|
60
|
+
*/
|
|
61
|
+
public async getOrCreateMaterial(materialId: string, mesh?: AbstractMesh): Promise<Material> {
|
|
62
|
+
let chosenMaterial: Material | null = this.viewer.scene.materials.find(mat => mat.id === materialId) ?? null;
|
|
63
|
+
|
|
64
|
+
if (!chosenMaterial) {
|
|
65
|
+
// material not available yet, request it from Combeenation assets
|
|
66
|
+
const existingCreationProm = this._createMaterialPromises[materialId];
|
|
67
|
+
if (existingCreationProm !== undefined) {
|
|
68
|
+
// request is already pending, just wait for the result
|
|
69
|
+
chosenMaterial = await existingCreationProm;
|
|
70
|
+
} else {
|
|
71
|
+
this.viewer.eventManager.fireEvent(ViewerEvent.MaterialCreationStart, materialId);
|
|
72
|
+
|
|
73
|
+
// request not pending, call the dedicated function
|
|
74
|
+
const newCreationProm = this._createMaterial(materialId, mesh);
|
|
75
|
+
// store the promise in a global map, so that subsequent requests can reference it
|
|
76
|
+
this._createMaterialPromises[materialId] = newCreationProm;
|
|
77
|
+
chosenMaterial = await newCreationProm;
|
|
78
|
+
|
|
79
|
+
delete this._createMaterialPromises[materialId];
|
|
80
|
+
|
|
81
|
+
const allMaterialsCreated = !Object.entries(this._createMaterialPromises).length;
|
|
82
|
+
// signalize event system that material is created now
|
|
83
|
+
// also check if there are other material creation processes still pending, as this is an important information
|
|
84
|
+
// e.g. for showing load masks
|
|
85
|
+
this.viewer.eventManager.fireEvent(ViewerEvent.MaterialCreationEnd, materialId, allMaterialsCreated);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return chosenMaterial as Material;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
protected async _createMaterial(materialId: string, mesh?: AbstractMesh): Promise<Material> {
|
|
93
|
+
if (materialId === MaterialManager.CBN_FALLBACK_MATERIAL_NAME) {
|
|
94
|
+
const fallbackMaterial = new StandardMaterial(MaterialManager.CBN_FALLBACK_MATERIAL_NAME, this.viewer.scene);
|
|
95
|
+
fallbackMaterial.disableLighting = true;
|
|
96
|
+
|
|
97
|
+
return fallbackMaterial;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const materialDefinition = await window.Cbn?.Assets.getMaterial(materialId);
|
|
101
|
+
// The generic `Material.Parse` actually returns a more specific material like `BABYLON.StandardMaterial`,
|
|
102
|
+
// `BABYLON.PBRMaterial` or stuff like `BABYLON.PBRMetallicRoughnessMaterial` etc. based on the given `customType`
|
|
103
|
+
// within the material JSON definition
|
|
104
|
+
const material = materialDefinition && Material.Parse(materialDefinition, this.viewer.scene, '');
|
|
105
|
+
|
|
106
|
+
if (!material) {
|
|
107
|
+
throw new ViewerError({
|
|
108
|
+
id: ViewerErrorIds.MaterialCouldNotBeParsed,
|
|
109
|
+
message: `Material with id "${materialId}" could not be parsed`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await this.viewer.parameterManager.applyParameterValuesToMaterial(material);
|
|
114
|
+
|
|
115
|
+
const matReadyProms = [
|
|
116
|
+
new Promise<void>(resolve => BaseTexture.WhenAllReady(material.getActiveTextures(), resolve)),
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
if (mesh) {
|
|
120
|
+
// this promise should only take some time on the first call of the corresponding shader (eg: PBRMaterial shader)
|
|
121
|
+
// on each other call of the same material/shader type there should be not be a waiting time, or at maximum one
|
|
122
|
+
// frame
|
|
123
|
+
// https://forum.babylonjs.com/t/mesh-flickering-when-setting-material-initially/37312
|
|
124
|
+
matReadyProms.push(material.forceCompilationAsync(mesh));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// material needs to fulfill 2 criterias before it's ready to use
|
|
128
|
+
// - textures need to be "ready" => downloaded
|
|
129
|
+
// - dedicated shader needs to be compiled
|
|
130
|
+
// if this would not be the case you can see some "flickering" when setting the material
|
|
131
|
+
await Promise.all(matReadyProms);
|
|
132
|
+
|
|
133
|
+
return material;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AssetContainer,
|
|
3
|
+
BuiltInParameter,
|
|
4
|
+
MaterialManager,
|
|
5
|
+
MeshBuilder,
|
|
6
|
+
ParameterManager,
|
|
7
|
+
SceneLoader,
|
|
8
|
+
TransformNode,
|
|
9
|
+
Viewer,
|
|
10
|
+
ViewerError,
|
|
11
|
+
ViewerErrorIds,
|
|
12
|
+
} from '../index';
|
|
13
|
+
import { cloneModelAssetContainer } from '../internal/cloningHelper';
|
|
14
|
+
import { getInternalMetadataValue } from '../internal/metadataHelper';
|
|
15
|
+
import { isArray } from 'lodash-es';
|
|
16
|
+
|
|
17
|
+
export type ModelAssetDefinition = {
|
|
18
|
+
name: string;
|
|
19
|
+
url: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type ModelVisibilityEntry = {
|
|
23
|
+
name: string;
|
|
24
|
+
visible: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Callback for renaming nodes when cloning nodes in a model
|
|
29
|
+
*/
|
|
30
|
+
export type NodeNamingStrategy = (node: TransformNode, newModelName: string) => string;
|
|
31
|
+
/**
|
|
32
|
+
* Callback for renaming tags when cloning nodes in a model
|
|
33
|
+
*/
|
|
34
|
+
export type TagNamingStrategy = (tag: string, newModelName: string) => string;
|
|
35
|
+
|
|
36
|
+
export type ModelCloneOptions = {
|
|
37
|
+
nodeNamingStrategy: NodeNamingStrategy;
|
|
38
|
+
tagNamingStrategy: TagNamingStrategy;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type Model = {
|
|
42
|
+
name: string;
|
|
43
|
+
url: string;
|
|
44
|
+
state: ModelAssetState;
|
|
45
|
+
assetContainer: AssetContainer;
|
|
46
|
+
isClone: boolean;
|
|
47
|
+
visibilityCallId?: number;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type ModelAssetState = 'notLoaded' | 'loading' | 'loaded' | 'inScene';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Manager for handling 3d models.\
|
|
54
|
+
* Responsible for loading models and handling their visibility.\
|
|
55
|
+
* Also contains advanced features like model cloning.
|
|
56
|
+
*/
|
|
57
|
+
export class ModelManager {
|
|
58
|
+
/**
|
|
59
|
+
* CAUTION: this has to be in sync with the Combeenation backend!
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
public static readonly CBN_FALLBACK_MODEL_ASSET_NAME = '$fallback';
|
|
63
|
+
|
|
64
|
+
protected _modelAssets: {
|
|
65
|
+
[name: string]: Model;
|
|
66
|
+
} = {};
|
|
67
|
+
protected _fallbackModelAsset: Model = {
|
|
68
|
+
name: ModelManager.CBN_FALLBACK_MODEL_ASSET_NAME,
|
|
69
|
+
url: '',
|
|
70
|
+
state: 'loaded',
|
|
71
|
+
assetContainer: new AssetContainer(),
|
|
72
|
+
isClone: false,
|
|
73
|
+
};
|
|
74
|
+
protected _loadModelPromises: { [modelName: string]: Promise<void> } = {};
|
|
75
|
+
|
|
76
|
+
get modelNames(): string[] {
|
|
77
|
+
return Object.keys(this._modelAssets);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Nested models were targeted with slash notation previously, as Spec couldn't work with dots.
|
|
82
|
+
* Dots were interpreted as inherited model.
|
|
83
|
+
* Now the Hive viewer parameters are returning slash notated asset names, whereas the asset names from the
|
|
84
|
+
* prepacked asset manager are dot notated.
|
|
85
|
+
* This function converts slash to dot notation, so that models can be target with slash and dot notation alike.
|
|
86
|
+
*/
|
|
87
|
+
protected static _replaceSlashes(inputStr: string): string {
|
|
88
|
+
return inputStr.split('/').join('.');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public constructor(protected viewer: Viewer) {}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Creates content for the fallback model, which is actually just a black cube.
|
|
95
|
+
* This is used for 3d assets that are linked in a data source but not available (yet).
|
|
96
|
+
* The Combeenation backend exchanges the missing 3d asset name with "$fallback", which indicates that we shouldn't
|
|
97
|
+
* throw an error but just show the fallback model instead.
|
|
98
|
+
*
|
|
99
|
+
* @internal
|
|
100
|
+
*/
|
|
101
|
+
public createFallbackModel(): void {
|
|
102
|
+
const fallbackCube = MeshBuilder.CreateBox('$fallbackCube', { size: 1 }, this.viewer.scene);
|
|
103
|
+
fallbackCube._parentContainer = this._fallbackModelAsset.assetContainer;
|
|
104
|
+
this._fallbackModelAsset.assetContainer.meshes.push(fallbackCube);
|
|
105
|
+
// NOTE: this is theoretically async, but in reality it's not when getting the material description from the CBN
|
|
106
|
+
// server
|
|
107
|
+
// also this is just a fallback that shouldn't be relevant in productive configurators
|
|
108
|
+
this.viewer.materialManager.setMaterialOnMesh(MaterialManager.CBN_FALLBACK_MATERIAL_NAME, fallbackCube);
|
|
109
|
+
|
|
110
|
+
this._fallbackModelAsset.assetContainer.removeFromScene();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Register models for 3d viewer, this is required for each model before it can be loaded/shown.\
|
|
115
|
+
* The "viewer control" inside the Combeenation framework calls this function automatically with all assigned
|
|
116
|
+
* 3d assets.
|
|
117
|
+
*/
|
|
118
|
+
public registerModels(modelAssets: ModelAssetDefinition[]): void {
|
|
119
|
+
for (const { name, url } of modelAssets) {
|
|
120
|
+
const existingModel = this._modelAssets[name];
|
|
121
|
+
if (existingModel) {
|
|
122
|
+
console.warn(`Model ${name} is already registered`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const model: Model = { name, url, state: 'notLoaded', assetContainer: new AssetContainer(), isClone: false };
|
|
127
|
+
this._modelAssets[name] = model;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Loads the model and all it's assigned materials.\
|
|
133
|
+
* This function should be used for "eager loading" of models, so that model switching will be instant in the
|
|
134
|
+
* process.\
|
|
135
|
+
* If you don't plan to do this, just use {@link setModelVisibility} as this function will load the model under the
|
|
136
|
+
* hood the first time the model gets visible.
|
|
137
|
+
*/
|
|
138
|
+
public async loadModel(name: string): Promise<void> {
|
|
139
|
+
const model = this._getModel(name);
|
|
140
|
+
if (!model) {
|
|
141
|
+
throw new ViewerError({
|
|
142
|
+
id: ViewerErrorIds.ModelNotRegistered,
|
|
143
|
+
message: `Can't load model "${name}" as model is not registered`,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (model.state !== 'notLoaded') {
|
|
148
|
+
console.warn(`Model ${name} is already loaded or currently loading`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
await this._loadModel(model);
|
|
153
|
+
await this._prepareModelForScene(model);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Enables/disables multiple one or multiple models simultaniously.\
|
|
158
|
+
* All required models are loaded before adjusting the visibility to avoid flashing in the 3d scene.
|
|
159
|
+
*
|
|
160
|
+
* @returns Array of changed visibility status, combined with the corresponding model name
|
|
161
|
+
*/
|
|
162
|
+
public async setModelVisibility(
|
|
163
|
+
modelVisibility: ModelVisibilityEntry | ModelVisibilityEntry[]
|
|
164
|
+
): Promise<ModelVisibilityEntry[]> {
|
|
165
|
+
const showModels: Model[] = [];
|
|
166
|
+
const hideModels: Model[] = [];
|
|
167
|
+
|
|
168
|
+
if (!isArray(modelVisibility)) {
|
|
169
|
+
modelVisibility = [modelVisibility];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
for (const entry of modelVisibility) {
|
|
173
|
+
const model = this._getModel(entry.name);
|
|
174
|
+
if (!model) {
|
|
175
|
+
throw new ViewerError({
|
|
176
|
+
id: ViewerErrorIds.ModelNotRegistered,
|
|
177
|
+
message: `Can't set visibility of model "${entry.name}" as model is not registered`,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// there can be multiple "setModelVisibility" calls while the model is loading
|
|
182
|
+
// loading can be awaited, but it has to be ensured, that the last call of "setModelVisibility" has priority
|
|
183
|
+
// therefore we store the id of the call in the model
|
|
184
|
+
const curVisibilityCallId = (model.visibilityCallId ?? 0) + 1;
|
|
185
|
+
model.visibilityCallId = curVisibilityCallId;
|
|
186
|
+
|
|
187
|
+
if (entry.visible) {
|
|
188
|
+
if (model.state === 'notLoaded') {
|
|
189
|
+
await this._loadModel(model);
|
|
190
|
+
|
|
191
|
+
// check if this is still the latest visibility call
|
|
192
|
+
if (model.visibilityCallId === curVisibilityCallId) {
|
|
193
|
+
showModels.push(model);
|
|
194
|
+
}
|
|
195
|
+
} else if (model.state === 'loading') {
|
|
196
|
+
await this._loadModelPromises[model.name];
|
|
197
|
+
|
|
198
|
+
if (model.visibilityCallId === curVisibilityCallId) {
|
|
199
|
+
showModels.push(model);
|
|
200
|
+
}
|
|
201
|
+
} else if (model.state === 'loaded') {
|
|
202
|
+
showModels.push(model);
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
if (model.state === 'loading') {
|
|
206
|
+
await this._loadModelPromises[model.name];
|
|
207
|
+
|
|
208
|
+
if (model.visibilityCallId === curVisibilityCallId) {
|
|
209
|
+
hideModels.push(model);
|
|
210
|
+
}
|
|
211
|
+
} else if (model.state === 'inScene') {
|
|
212
|
+
hideModels.push(model);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
for (const showModel of showModels) {
|
|
218
|
+
await this._prepareModelForScene(showModel);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
for (const showModel of showModels) {
|
|
222
|
+
await this._showModel(showModel, true);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
for (const hideModel of hideModels) {
|
|
226
|
+
this._hideModel(hideModel);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const returnVal: ModelVisibilityEntry[] = [];
|
|
230
|
+
showModels.forEach(showModel => returnVal.push({ name: showModel.name, visible: true }));
|
|
231
|
+
hideModels.forEach(hideModel => returnVal.push({ name: hideModel.name, visible: false }));
|
|
232
|
+
|
|
233
|
+
return returnVal;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Create a clone of an existing model.\
|
|
238
|
+
* Only the geometry (meshes, transform nodes) gets cloned, as materials are typically shared across the whole scene.
|
|
239
|
+
*
|
|
240
|
+
* @param show show model immediately after cloning
|
|
241
|
+
* @param options additional options for the cloning procedure, like renaming algorithms
|
|
242
|
+
*/
|
|
243
|
+
public async cloneModel(
|
|
244
|
+
name: string,
|
|
245
|
+
newModelName: string,
|
|
246
|
+
show: boolean = true,
|
|
247
|
+
options?: ModelCloneOptions
|
|
248
|
+
): Promise<void> {
|
|
249
|
+
const sourceModel = this._getModel(name);
|
|
250
|
+
if (!sourceModel) {
|
|
251
|
+
throw new ViewerError({
|
|
252
|
+
id: ViewerErrorIds.ModelNotRegistered,
|
|
253
|
+
message: `Can't clone model "${name}" as model is not registered`,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const existingModel = this._getModel(newModelName);
|
|
258
|
+
if (existingModel) {
|
|
259
|
+
throw new ViewerError({
|
|
260
|
+
id: ViewerErrorIds.ModelAlreadyExists,
|
|
261
|
+
message: `Can't create clone as model "${newModelName}" already exists`,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (sourceModel.state === 'notLoaded') {
|
|
266
|
+
await this._loadModel(sourceModel);
|
|
267
|
+
} else if (sourceModel.state === 'loading') {
|
|
268
|
+
await this._loadModelPromises[sourceModel.name];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const clonedModel: Model = {
|
|
272
|
+
name: newModelName,
|
|
273
|
+
url: sourceModel.url,
|
|
274
|
+
state: 'loaded',
|
|
275
|
+
assetContainer: cloneModelAssetContainer(
|
|
276
|
+
sourceModel.assetContainer,
|
|
277
|
+
newModelName,
|
|
278
|
+
{
|
|
279
|
+
nodeNamingStrategy: options?.nodeNamingStrategy,
|
|
280
|
+
tagNamingStrategy: options?.tagNamingStrategy,
|
|
281
|
+
},
|
|
282
|
+
this.viewer.scene
|
|
283
|
+
),
|
|
284
|
+
isClone: true,
|
|
285
|
+
};
|
|
286
|
+
this._modelAssets[newModelName] = clonedModel;
|
|
287
|
+
|
|
288
|
+
if (show) {
|
|
289
|
+
await this._showModel(clonedModel);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Removes a cloned model from the scene and from the internal model storage.\
|
|
295
|
+
* Deleted clones can not be shown again.
|
|
296
|
+
*/
|
|
297
|
+
public deleteClonedModel(name: string): void {
|
|
298
|
+
const model = this._getModel(name);
|
|
299
|
+
if (!model) {
|
|
300
|
+
throw new ViewerError({
|
|
301
|
+
id: ViewerErrorIds.ModelNotRegistered,
|
|
302
|
+
message: `Can't delete cloned model "${name}" as model is not registered`,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!model.isClone) {
|
|
307
|
+
throw new ViewerError({
|
|
308
|
+
id: ViewerErrorIds.ModelIsNotAClone,
|
|
309
|
+
message: `Can't delete model "${name}" as model is not a clone`,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
model.assetContainer.dispose();
|
|
314
|
+
|
|
315
|
+
delete this._modelAssets[name];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Removes all cloned models from the scene and the internal model storage.
|
|
320
|
+
*/
|
|
321
|
+
public deleteAllClonedModels(): void {
|
|
322
|
+
const clonedModelNames = Object.values(this._modelAssets)
|
|
323
|
+
.filter(model => model.isClone)
|
|
324
|
+
.map(model => model.name);
|
|
325
|
+
|
|
326
|
+
clonedModelNames.forEach(modelName => this.deleteClonedModel(modelName));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Loads and returns the asset container of a certain model.\
|
|
331
|
+
* This can be used to access the models content without having to add it to the scene.\
|
|
332
|
+
* A typical use case is to clone or instantiate a node from a "library" model.
|
|
333
|
+
*/
|
|
334
|
+
public async getAssetContainerOfModel(name: string): Promise<AssetContainer> {
|
|
335
|
+
const model = this._getModel(name);
|
|
336
|
+
if (!model) {
|
|
337
|
+
throw new ViewerError({
|
|
338
|
+
id: ViewerErrorIds.ModelNotRegistered,
|
|
339
|
+
message: `Can't get asset container of model "${name}" as model is not registered`,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (model.state === 'notLoaded') {
|
|
344
|
+
await this._loadModel(model);
|
|
345
|
+
} else if (model.state === 'loading') {
|
|
346
|
+
await this._loadModelPromises[model.name];
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
await this._prepareModelForScene(model);
|
|
350
|
+
|
|
351
|
+
return model.assetContainer;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Get model by name
|
|
356
|
+
*/
|
|
357
|
+
protected _getModel(name: string): Model | undefined {
|
|
358
|
+
if (name === '$fallback') {
|
|
359
|
+
return this._fallbackModelAsset;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const replacedName = ModelManager._replaceSlashes(name);
|
|
363
|
+
const model = this._modelAssets[replacedName];
|
|
364
|
+
|
|
365
|
+
return model;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Load model into scene, but don't show it immediately
|
|
370
|
+
*/
|
|
371
|
+
protected async _loadModel(model: Model): Promise<void> {
|
|
372
|
+
const loadModelPromise = async (): Promise<void> => {
|
|
373
|
+
model.state = 'loading';
|
|
374
|
+
const curEnvTexture = this.viewer.scene.environmentTexture;
|
|
375
|
+
const curEnvIntensity = this.viewer.scene.environmentIntensity;
|
|
376
|
+
|
|
377
|
+
let assetContainer;
|
|
378
|
+
try {
|
|
379
|
+
assetContainer = await SceneLoader.LoadAssetContainerAsync('', model.url, this.viewer.scene);
|
|
380
|
+
} catch (e) {
|
|
381
|
+
throw new ViewerError({
|
|
382
|
+
id: ViewerErrorIds.AssetLoadingFailed,
|
|
383
|
+
message: `Error loading model "${model.name}" from url "${model.url}":\n${(e as Error).message}`,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// remove all lights and cameras from the asset, as this data should be handled globally in the scene instead of
|
|
388
|
+
// being in a model context
|
|
389
|
+
[...assetContainer.lights].forEach(light => light.dispose());
|
|
390
|
+
[...assetContainer.cameras].forEach(camera => camera.dispose());
|
|
391
|
+
|
|
392
|
+
// environment texture and intensity has been overwritten by load asset container function
|
|
393
|
+
this.viewer.scene.environmentTexture = curEnvTexture;
|
|
394
|
+
this.viewer.scene.environmentIntensity = curEnvIntensity;
|
|
395
|
+
|
|
396
|
+
model.assetContainer = assetContainer;
|
|
397
|
+
model.state = 'loaded';
|
|
398
|
+
|
|
399
|
+
delete this._loadModelPromises[model.name];
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
this._loadModelPromises[model.name] = loadModelPromise();
|
|
403
|
+
return this._loadModelPromises[model.name];
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Apply parameter to the model in the asset container.
|
|
408
|
+
* This is typically done before the model is added to the scene to avoid changes on the visible model, which ensures
|
|
409
|
+
* a smooth model switch behaviour.
|
|
410
|
+
*/
|
|
411
|
+
protected async _prepareModelForScene(model: Model): Promise<void> {
|
|
412
|
+
await this.viewer.parameterManager.applyAllParameterValuesToModel(model.assetContainer);
|
|
413
|
+
// parameter manager did his job, now apply the deferred materials to all meshes that are not explicitely hidden by
|
|
414
|
+
// the parameter manager
|
|
415
|
+
await this._applyDeferredMaterialsForAllVisibleMeshes(model);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Add assets of asset container to scene, do the scene preparation if required.
|
|
420
|
+
* CAUTION: model is expected to be in the correct loading state already.
|
|
421
|
+
*
|
|
422
|
+
* @param skipPreparation optionally skip applying parameter values to model before showing it
|
|
423
|
+
* (see {@link _prepareModelForScene})
|
|
424
|
+
*/
|
|
425
|
+
protected async _showModel(model: Model, skipPreparation?: boolean): Promise<void> {
|
|
426
|
+
if (!skipPreparation) {
|
|
427
|
+
await this._prepareModelForScene(model);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
model.assetContainer.addToScene();
|
|
431
|
+
model.state = 'inScene';
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Remove assets of asset container from scene
|
|
436
|
+
*/
|
|
437
|
+
protected _hideModel(model: Model): void {
|
|
438
|
+
model.assetContainer.removeFromScene();
|
|
439
|
+
model.state = 'loaded';
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Creates and assigns each "deferred" material to the corresponding mesh, if the mesh is visible.
|
|
444
|
+
* Model should be ready to use immediately after this function has done it's job.
|
|
445
|
+
*/
|
|
446
|
+
protected async _applyDeferredMaterialsForAllVisibleMeshes(model: Model): Promise<void> {
|
|
447
|
+
for (const mesh of model.assetContainer.meshes) {
|
|
448
|
+
const deferredMaterial = getInternalMetadataValue(mesh, 'deferredMaterial');
|
|
449
|
+
const rawVisibleValue = this.viewer.parameterManager.getParameterValueOfNode(mesh, BuiltInParameter.Visible);
|
|
450
|
+
const visible = rawVisibleValue === undefined || ParameterManager.parseBoolean(rawVisibleValue) === true;
|
|
451
|
+
if (deferredMaterial && visible) {
|
|
452
|
+
await this.viewer.materialManager.setMaterialOnMesh(deferredMaterial as string, mesh);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|