@combeenation/3d-viewer 17.1.0-beta1 → 18.0.0-beta1
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 +2 -0
- package/dist/lib-cjs/index.js +2 -0
- package/dist/lib-cjs/index.js.map +1 -1
- package/dist/lib-cjs/internal/asset-helper.d.ts +32 -0
- package/dist/lib-cjs/internal/asset-helper.js +105 -0
- package/dist/lib-cjs/internal/asset-helper.js.map +1 -0
- package/dist/lib-cjs/internal/cbn-custom-babylon-loader-plugin.d.ts +18 -0
- package/dist/lib-cjs/internal/cbn-custom-babylon-loader-plugin.js +22 -3
- package/dist/lib-cjs/internal/cbn-custom-babylon-loader-plugin.js.map +1 -1
- package/dist/lib-cjs/internal/texture-parameter-helper.js +26 -7
- package/dist/lib-cjs/internal/texture-parameter-helper.js.map +1 -1
- package/dist/lib-cjs/manager/camera-manager.js +4 -2
- package/dist/lib-cjs/manager/camera-manager.js.map +1 -1
- package/dist/lib-cjs/manager/debug-manager.js +1 -1
- package/dist/lib-cjs/manager/debug-manager.js.map +1 -1
- package/dist/lib-cjs/manager/model-manager.d.ts +11 -33
- package/dist/lib-cjs/manager/model-manager.js +47 -106
- package/dist/lib-cjs/manager/model-manager.js.map +1 -1
- package/dist/lib-cjs/manager/parameter-manager.d.ts +16 -11
- package/dist/lib-cjs/manager/parameter-manager.js +78 -69
- package/dist/lib-cjs/manager/parameter-manager.js.map +1 -1
- package/dist/lib-cjs/manager/scene-manager.d.ts +111 -5
- package/dist/lib-cjs/manager/scene-manager.js +269 -10
- package/dist/lib-cjs/manager/scene-manager.js.map +1 -1
- package/dist/lib-cjs/viewer-error.d.ts +1 -0
- package/dist/lib-cjs/viewer-error.js +1 -0
- package/dist/lib-cjs/viewer-error.js.map +1 -1
- package/dist/lib-cjs/viewer.d.ts +4 -13
- package/dist/lib-cjs/viewer.js +3 -37
- package/dist/lib-cjs/viewer.js.map +1 -1
- package/package.json +21 -12
- package/src/index.ts +2 -0
- package/src/internal/asset-helper.ts +115 -0
- package/src/internal/cbn-custom-babylon-loader-plugin.ts +30 -3
- package/src/internal/texture-parameter-helper.ts +25 -8
- package/src/manager/camera-manager.ts +4 -2
- package/src/manager/debug-manager.ts +1 -1
- package/src/manager/model-manager.ts +55 -137
- package/src/manager/parameter-manager.ts +93 -74
- package/src/manager/scene-manager.ts +366 -10
- package/src/viewer-error.ts +1 -0
- package/src/viewer.ts +13 -55
- package/src/dev.ts +0 -47
|
@@ -1,33 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AssetContainer,
|
|
3
|
-
BuiltInParameter,
|
|
4
3
|
DecalConfiguration,
|
|
5
4
|
MaterialManager,
|
|
6
5
|
MeshBuilder,
|
|
7
|
-
ParameterManager,
|
|
8
|
-
SceneLoader,
|
|
9
6
|
TransformNode,
|
|
10
7
|
Viewer,
|
|
11
8
|
ViewerError,
|
|
12
9
|
ViewerErrorIds,
|
|
13
10
|
} from '../index';
|
|
11
|
+
import { BaseAsset, loadAsset, prepareAssetForScene } from '../internal/asset-helper';
|
|
14
12
|
import { cloneModelAssetContainer } from '../internal/cloning-helper';
|
|
15
|
-
import { getInternalMetadataValue } from '../internal/metadata-helper';
|
|
16
13
|
import { isArray } from 'lodash-es';
|
|
17
14
|
|
|
18
|
-
/**
|
|
19
|
-
* Contains cbn custom data, like decals.
|
|
20
|
-
* This is just a temporary type, as the `loadAssetContainer` function only returns an asset container, which can be
|
|
21
|
-
* altered by our file loader plugin.
|
|
22
|
-
* After loading the model, `cbnData` is cropped and a pure asset container is available for further processing.
|
|
23
|
-
*
|
|
24
|
-
* @internal
|
|
25
|
-
*/
|
|
26
|
-
export class ExtendedAssetContainer extends AssetContainer {
|
|
27
|
-
cbnData?: CbnBabylonFileData;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type CbnBabylonFileData = { decals?: ParsedDecalConfiguration[] };
|
|
31
15
|
export type ParsedDecalConfiguration = DecalConfiguration & { materialId?: string; tags?: string };
|
|
32
16
|
|
|
33
17
|
export type ModelAssetDefinition = {
|
|
@@ -62,20 +46,14 @@ export type ModelCloneOptions = {
|
|
|
62
46
|
tagNamingStrategy?: TagNamingStrategy;
|
|
63
47
|
};
|
|
64
48
|
|
|
65
|
-
type
|
|
66
|
-
|
|
67
|
-
url: string;
|
|
68
|
-
state: ModelAssetState;
|
|
69
|
-
assetContainer: AssetContainer;
|
|
70
|
-
cbnBabylonFileData?: CbnBabylonFileData;
|
|
49
|
+
type ModelAsset = BaseAsset & {
|
|
50
|
+
decals?: ParsedDecalConfiguration[];
|
|
71
51
|
isClone: boolean;
|
|
72
52
|
// only set for "instantiated" clones
|
|
73
53
|
sourceModelName?: string;
|
|
74
54
|
visibilityCallId?: number;
|
|
75
55
|
};
|
|
76
56
|
|
|
77
|
-
type ModelAssetState = 'notLoaded' | 'loading' | 'loaded' | 'inScene';
|
|
78
|
-
|
|
79
57
|
/**
|
|
80
58
|
* Manager for handling 3d models.\
|
|
81
59
|
* Responsible for loading models and handling their visibility.\
|
|
@@ -88,8 +66,8 @@ export class ModelManager {
|
|
|
88
66
|
*/
|
|
89
67
|
public static readonly CBN_FALLBACK_MODEL_ASSET_NAME = '$fallback';
|
|
90
68
|
|
|
91
|
-
protected _modelAssets: { [name: string]:
|
|
92
|
-
protected _fallbackModelAsset:
|
|
69
|
+
protected _modelAssets: { [name: string]: ModelAsset } = {};
|
|
70
|
+
protected _fallbackModelAsset: ModelAsset = {
|
|
93
71
|
name: ModelManager.CBN_FALLBACK_MODEL_ASSET_NAME,
|
|
94
72
|
url: '',
|
|
95
73
|
state: 'loaded',
|
|
@@ -113,6 +91,7 @@ export class ModelManager {
|
|
|
113
91
|
return inputStr.split('/').join('.');
|
|
114
92
|
}
|
|
115
93
|
|
|
94
|
+
/** @internal */
|
|
116
95
|
public constructor(protected viewer: Viewer) {}
|
|
117
96
|
|
|
118
97
|
/**
|
|
@@ -144,11 +123,11 @@ export class ModelManager {
|
|
|
144
123
|
for (const { name, url } of modelAssets) {
|
|
145
124
|
const existingModel = this._modelAssets[name];
|
|
146
125
|
if (existingModel) {
|
|
147
|
-
console.warn(`Model ${name} is already registered`);
|
|
126
|
+
console.warn(`Model "${name}" is already registered`);
|
|
148
127
|
return;
|
|
149
128
|
}
|
|
150
129
|
|
|
151
|
-
const model:
|
|
130
|
+
const model: ModelAsset = { name, url, state: 'notLoaded', assetContainer: new AssetContainer(), isClone: false };
|
|
152
131
|
this._modelAssets[name] = model;
|
|
153
132
|
}
|
|
154
133
|
}
|
|
@@ -170,7 +149,7 @@ export class ModelManager {
|
|
|
170
149
|
}
|
|
171
150
|
|
|
172
151
|
if (model.state !== 'notLoaded') {
|
|
173
|
-
console.warn(`Model ${name} is already loaded or currently loading`);
|
|
152
|
+
console.warn(`Model "${name}" is already loaded or currently loading`);
|
|
174
153
|
return;
|
|
175
154
|
}
|
|
176
155
|
|
|
@@ -187,14 +166,14 @@ export class ModelManager {
|
|
|
187
166
|
public async setModelVisibility(
|
|
188
167
|
modelVisibility: ModelVisibilityEntry | ModelVisibilityEntry[]
|
|
189
168
|
): Promise<ModelVisibilityEntry[]> {
|
|
190
|
-
const
|
|
191
|
-
const
|
|
169
|
+
const modelsToShow: ModelAsset[] = [];
|
|
170
|
+
const modelsToHide: ModelAsset[] = [];
|
|
192
171
|
|
|
193
172
|
if (!isArray(modelVisibility)) {
|
|
194
173
|
modelVisibility = [modelVisibility];
|
|
195
174
|
}
|
|
196
175
|
|
|
197
|
-
|
|
176
|
+
const loadModelProms = modelVisibility.map(async entry => {
|
|
198
177
|
const model = this._getModel(entry.name);
|
|
199
178
|
if (!model) {
|
|
200
179
|
throw new ViewerError({
|
|
@@ -206,8 +185,10 @@ export class ModelManager {
|
|
|
206
185
|
// there can be multiple "setModelVisibility" calls while the model is loading
|
|
207
186
|
// loading can be awaited, but it has to be ensured, that the last call of "setModelVisibility" has priority
|
|
208
187
|
// therefore we store the id of the call in the model
|
|
209
|
-
|
|
210
|
-
model.visibilityCallId
|
|
188
|
+
model.visibilityCallId = (model.visibilityCallId ?? 0) + 1;
|
|
189
|
+
const curVisibilityCallId = model.visibilityCallId;
|
|
190
|
+
let showModel = false;
|
|
191
|
+
let hideModel = false;
|
|
211
192
|
|
|
212
193
|
if (entry.visible) {
|
|
213
194
|
if (model.state === 'notLoaded') {
|
|
@@ -215,45 +196,51 @@ export class ModelManager {
|
|
|
215
196
|
|
|
216
197
|
// check if this is still the latest visibility call
|
|
217
198
|
if (model.visibilityCallId === curVisibilityCallId) {
|
|
218
|
-
|
|
199
|
+
showModel = true;
|
|
219
200
|
}
|
|
220
201
|
} else if (model.state === 'loading') {
|
|
221
202
|
await this._loadModelPromises[model.name];
|
|
222
203
|
|
|
223
204
|
if (model.visibilityCallId === curVisibilityCallId) {
|
|
224
|
-
|
|
205
|
+
showModel = true;
|
|
225
206
|
}
|
|
226
207
|
} else if (model.state === 'loaded') {
|
|
227
|
-
|
|
208
|
+
showModel = true;
|
|
228
209
|
}
|
|
229
210
|
} else {
|
|
230
211
|
if (model.state === 'loading') {
|
|
231
212
|
await this._loadModelPromises[model.name];
|
|
232
213
|
|
|
233
214
|
if (model.visibilityCallId === curVisibilityCallId) {
|
|
234
|
-
|
|
215
|
+
hideModel = true;
|
|
235
216
|
}
|
|
236
217
|
} else if (model.state === 'inScene') {
|
|
237
|
-
|
|
218
|
+
hideModel = true;
|
|
238
219
|
}
|
|
239
220
|
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
for (const showModel of showModels) {
|
|
243
|
-
await this._prepareModelForScene(showModel);
|
|
244
|
-
}
|
|
245
221
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
222
|
+
if (showModel) {
|
|
223
|
+
await this._prepareModelForScene(model);
|
|
224
|
+
modelsToShow.push(model);
|
|
225
|
+
} else if (hideModel) {
|
|
226
|
+
modelsToHide.push(model);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
// model loading and preparation (e.g. create materials) is done simultaniously for all models to save time
|
|
230
|
+
// model (`ModelManager._loadModelPromises`) and material queues (`MaterialManager._createMaterialPromises`) are
|
|
231
|
+
// responsible for correct loading state handling of these instances
|
|
232
|
+
await Promise.all(loadModelProms);
|
|
253
233
|
|
|
254
234
|
const returnVal: ModelVisibilityEntry[] = [];
|
|
255
|
-
|
|
256
|
-
|
|
235
|
+
modelsToShow.forEach(model => {
|
|
236
|
+
// show model is not async, as preparation has already been done
|
|
237
|
+
this._showModel(model, true);
|
|
238
|
+
returnVal.push({ name: model.name, visible: true });
|
|
239
|
+
});
|
|
240
|
+
modelsToHide.forEach(model => {
|
|
241
|
+
this._hideModel(model);
|
|
242
|
+
returnVal.push({ name: model.name, visible: false });
|
|
243
|
+
});
|
|
257
244
|
|
|
258
245
|
return returnVal;
|
|
259
246
|
}
|
|
@@ -294,7 +281,7 @@ export class ModelManager {
|
|
|
294
281
|
await this._loadModelPromises[sourceModel.name];
|
|
295
282
|
}
|
|
296
283
|
|
|
297
|
-
const clonedModel:
|
|
284
|
+
const clonedModel: ModelAsset = {
|
|
298
285
|
name: newModelName,
|
|
299
286
|
url: sourceModel.url,
|
|
300
287
|
state: 'loaded',
|
|
@@ -400,14 +387,14 @@ export class ModelManager {
|
|
|
400
387
|
await this._prepareModelForScene(model);
|
|
401
388
|
}
|
|
402
389
|
|
|
403
|
-
return model.
|
|
390
|
+
return model.decals ?? [];
|
|
404
391
|
}
|
|
405
392
|
|
|
406
393
|
/**
|
|
407
394
|
* Get model by name
|
|
408
395
|
*/
|
|
409
|
-
protected _getModel(name: string):
|
|
410
|
-
if (name ===
|
|
396
|
+
protected _getModel(name: string): ModelAsset | undefined {
|
|
397
|
+
if (name === ModelManager.CBN_FALLBACK_MODEL_ASSET_NAME) {
|
|
411
398
|
return this._fallbackModelAsset;
|
|
412
399
|
}
|
|
413
400
|
|
|
@@ -420,67 +407,16 @@ export class ModelManager {
|
|
|
420
407
|
/**
|
|
421
408
|
* Load model into scene, but don't show it immediately
|
|
422
409
|
*/
|
|
423
|
-
protected async _loadModel(model:
|
|
410
|
+
protected async _loadModel(model: ModelAsset): Promise<void> {
|
|
424
411
|
const loadModelPromise = async (): Promise<void> => {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
// CB-9240: Babylon.js doesn't recognize gzipped babylon files (`.babylon.gz`) as such, leading to a warning
|
|
430
|
-
// message, therefore we overwrite the plugin extension actively for such files
|
|
431
|
-
let pluginExtension;
|
|
432
|
-
try {
|
|
433
|
-
// URL constructor can throw for "valid" URL, which happened to be the case for the test asset environment where
|
|
434
|
-
// the urls are relative (starting with ".")
|
|
435
|
-
const urlObj = new URL(model.url);
|
|
436
|
-
if (urlObj.pathname.endsWith('.babylon.gz')) {
|
|
437
|
-
pluginExtension = '.babylon';
|
|
438
|
-
}
|
|
439
|
-
} catch (e) {}
|
|
440
|
-
|
|
441
|
-
let assetContainer;
|
|
442
|
-
try {
|
|
443
|
-
const fullContainer = (await SceneLoader.LoadAssetContainerAsync(
|
|
444
|
-
'',
|
|
445
|
-
model.url,
|
|
446
|
-
this.viewer.scene,
|
|
447
|
-
undefined,
|
|
448
|
-
pluginExtension
|
|
449
|
-
)) as ExtendedAssetContainer;
|
|
450
|
-
|
|
451
|
-
// crop and store custom cbn data from .babylon file
|
|
452
|
-
model.cbnBabylonFileData = fullContainer.cbnData;
|
|
453
|
-
delete fullContainer.cbnData;
|
|
454
|
-
|
|
455
|
-
// from here it's a basic asset container again
|
|
456
|
-
assetContainer = fullContainer as AssetContainer;
|
|
457
|
-
} catch (e) {
|
|
458
|
-
throw new ViewerError({
|
|
459
|
-
id: ViewerErrorIds.AssetLoadingFailed,
|
|
460
|
-
message: (e as Error).message,
|
|
461
|
-
});
|
|
462
|
-
}
|
|
412
|
+
const { cbnData } = await loadAsset(model, this.viewer);
|
|
413
|
+
|
|
414
|
+
model.decals = cbnData?.decals;
|
|
463
415
|
|
|
464
416
|
// remove all lights and cameras from the asset, as this data should be handled globally in the scene instead of
|
|
465
417
|
// being in a model context
|
|
466
|
-
[...assetContainer.lights].forEach(light => light.dispose());
|
|
467
|
-
[...assetContainer.cameras].forEach(camera => camera.dispose());
|
|
468
|
-
|
|
469
|
-
// materials should be a "global" thing and not assigned to an asset container
|
|
470
|
-
// this is not relevant in most of the cases, since materials are cropped from babylon models on the CBN server
|
|
471
|
-
// anyway
|
|
472
|
-
assetContainer.materials.forEach(material => {
|
|
473
|
-
this.viewer.scene.addMaterial(material);
|
|
474
|
-
material._parentContainer = null;
|
|
475
|
-
});
|
|
476
|
-
assetContainer.materials = [];
|
|
477
|
-
|
|
478
|
-
// environment texture and intensity has been overwritten by load asset container function
|
|
479
|
-
this.viewer.scene.environmentTexture = curEnvTexture;
|
|
480
|
-
this.viewer.scene.environmentIntensity = curEnvIntensity;
|
|
481
|
-
|
|
482
|
-
model.assetContainer = assetContainer;
|
|
483
|
-
model.state = 'loaded';
|
|
418
|
+
[...model.assetContainer.lights].forEach(light => light.dispose());
|
|
419
|
+
[...model.assetContainer.cameras].forEach(camera => camera.dispose());
|
|
484
420
|
|
|
485
421
|
delete this._loadModelPromises[model.name];
|
|
486
422
|
};
|
|
@@ -494,7 +430,7 @@ export class ModelManager {
|
|
|
494
430
|
* This is typically done before the model is added to the scene to avoid changes on the visible model, which ensures
|
|
495
431
|
* a smooth model switch behaviour.
|
|
496
432
|
*/
|
|
497
|
-
protected async _prepareModelForScene(model:
|
|
433
|
+
protected async _prepareModelForScene(model: ModelAsset): Promise<void> {
|
|
498
434
|
if (model.sourceModelName) {
|
|
499
435
|
// source model has to be prepared for scene as well in "instantiate" mode, because materials would not be
|
|
500
436
|
// loaded otherwise
|
|
@@ -504,10 +440,7 @@ export class ModelManager {
|
|
|
504
440
|
await this._prepareModelForScene(sourceModel);
|
|
505
441
|
}
|
|
506
442
|
|
|
507
|
-
await this.viewer
|
|
508
|
-
// parameter manager did his job, now apply the deferred materials to all meshes that are not explicitely hidden by
|
|
509
|
-
// the parameter manager
|
|
510
|
-
await this._applyDeferredMaterialsForAllVisibleMeshes(model);
|
|
443
|
+
await prepareAssetForScene(model, this.viewer);
|
|
511
444
|
}
|
|
512
445
|
|
|
513
446
|
/**
|
|
@@ -517,7 +450,7 @@ export class ModelManager {
|
|
|
517
450
|
* @param skipPreparation optionally skip applying parameter values to model before showing it
|
|
518
451
|
* (see {@link _prepareModelForScene})
|
|
519
452
|
*/
|
|
520
|
-
protected async _showModel(model:
|
|
453
|
+
protected async _showModel(model: ModelAsset, skipPreparation?: boolean): Promise<void> {
|
|
521
454
|
if (!skipPreparation) {
|
|
522
455
|
await this._prepareModelForScene(model);
|
|
523
456
|
}
|
|
@@ -529,23 +462,8 @@ export class ModelManager {
|
|
|
529
462
|
/**
|
|
530
463
|
* Remove assets of asset container from scene
|
|
531
464
|
*/
|
|
532
|
-
protected _hideModel(model:
|
|
465
|
+
protected _hideModel(model: ModelAsset): void {
|
|
533
466
|
model.assetContainer.removeFromScene();
|
|
534
467
|
model.state = 'loaded';
|
|
535
468
|
}
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* Creates and assigns each "deferred" material to the corresponding mesh, if the mesh is visible.
|
|
539
|
-
* Model should be ready to use immediately after this function has done it's job.
|
|
540
|
-
*/
|
|
541
|
-
protected async _applyDeferredMaterialsForAllVisibleMeshes(model: Model): Promise<void> {
|
|
542
|
-
for (const mesh of model.assetContainer.meshes) {
|
|
543
|
-
const deferredMaterial = getInternalMetadataValue(mesh, 'deferredMaterial');
|
|
544
|
-
const rawVisibleValue = this.viewer.parameterManager.getParameterValueOfNode(mesh, BuiltInParameter.Visible);
|
|
545
|
-
const visible = rawVisibleValue === undefined || ParameterManager.parseBoolean(rawVisibleValue) === true;
|
|
546
|
-
if (deferredMaterial && visible) {
|
|
547
|
-
await this.viewer.materialManager.setMaterialOnMesh(deferredMaterial as string, mesh);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
469
|
}
|
|
@@ -258,6 +258,7 @@ export class ParameterManager {
|
|
|
258
258
|
protected _parameterEntries: ParameterEntry[] = [];
|
|
259
259
|
protected _parameterObserver: { [parameterName: ParameterName]: ParameterObserver } = {};
|
|
260
260
|
|
|
261
|
+
/** @internal */
|
|
261
262
|
public constructor(protected viewer: Viewer) {
|
|
262
263
|
this._addBuiltInParameterObservers();
|
|
263
264
|
}
|
|
@@ -459,49 +460,28 @@ export class ParameterManager {
|
|
|
459
460
|
}
|
|
460
461
|
|
|
461
462
|
/**
|
|
462
|
-
*
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
* @internal
|
|
466
|
-
*/
|
|
467
|
-
public getParameterValueOfNode(node: TransformNode, parameterName: string): ParameterValue | undefined {
|
|
468
|
-
const nodeParamValue = this.getParameterValue({ nodeName: node.name }, parameterName);
|
|
469
|
-
if (nodeParamValue !== undefined) {
|
|
470
|
-
return nodeParamValue;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const tags = getTagsAsStrArr(node);
|
|
474
|
-
const tagParamValue = tags.reduce<ParameterValue | undefined>((accValue, curTag) => {
|
|
475
|
-
// NOTE: it is possible that values are available for multiple tags
|
|
476
|
-
// in this case the resulting parameter value is quite "random" as the last tag in the tag string of the node has
|
|
477
|
-
// priority
|
|
478
|
-
const tagParamValue = this.getParameterValue({ tagName: curTag }, parameterName);
|
|
479
|
-
return accValue ?? tagParamValue;
|
|
480
|
-
}, undefined);
|
|
481
|
-
|
|
482
|
-
return tagParamValue;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* @returns Desired parameter value of a certain material.
|
|
487
|
-
* Tags are considered as well but have lower priority than material parameters, as these are more specific.
|
|
488
|
-
* Unused ATM, but added for consistency as counter part for {@link getParameterValueOfNode}
|
|
463
|
+
* Retrieves visibility state of node, considering pending parameter values and node hierarchy.\
|
|
464
|
+
* `node.isEnabled()` alone is insufficient here, as visibility observers for the desired node or one of it's parents
|
|
465
|
+
* may be called in the same cycle, overwriting the visibility state again.
|
|
489
466
|
*
|
|
490
467
|
* @internal
|
|
491
468
|
*/
|
|
492
|
-
public
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
469
|
+
public getNestedVisibilityParameterValueOfNode(node: TransformNode): boolean {
|
|
470
|
+
let curNode: TransformNode | null = node;
|
|
471
|
+
let visibleByParameter: boolean | undefined = undefined;
|
|
472
|
+
while (curNode && visibleByParameter !== false) {
|
|
473
|
+
const curNodeVisibleByParameter = this._getParameterValueOfNode(curNode, BuiltInParameter.Visible);
|
|
474
|
+
if (curNodeVisibleByParameter !== undefined) {
|
|
475
|
+
visibleByParameter = curNodeVisibleByParameter as boolean;
|
|
476
|
+
}
|
|
477
|
+
curNode = curNode.parent as TransformNode;
|
|
496
478
|
}
|
|
497
479
|
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
return accValue ?? tagParamValue;
|
|
502
|
-
}, undefined);
|
|
480
|
+
// check state of nested visibility parameter value and fall back to basic node enabled state
|
|
481
|
+
const visible =
|
|
482
|
+
visibleByParameter !== undefined ? ParameterManager.parseBoolean(visibleByParameter) : node.isEnabled();
|
|
503
483
|
|
|
504
|
-
return
|
|
484
|
+
return visible;
|
|
505
485
|
}
|
|
506
486
|
|
|
507
487
|
/**
|
|
@@ -511,33 +491,40 @@ export class ParameterManager {
|
|
|
511
491
|
this.setParameterObserver(BuiltInParameter.Visible, async ({ nodes, newValue }) => {
|
|
512
492
|
const visible = ParameterManager.parseBoolean(newValue);
|
|
513
493
|
|
|
514
|
-
|
|
515
|
-
|
|
494
|
+
const observerProms = nodes.map(async node => {
|
|
495
|
+
const visibleNested = this.getNestedVisibilityParameterValueOfNode(node);
|
|
496
|
+
if (visibleNested) {
|
|
516
497
|
// if a mesh gets visible by this operation we have to activate the assigned material which is stored in the
|
|
517
498
|
// internal metadata
|
|
518
499
|
// => consider child meshes as well (CB-10143)
|
|
519
500
|
const activatedNodes = [node, ...node.getChildMeshes(false)];
|
|
520
|
-
|
|
521
|
-
const
|
|
522
|
-
|
|
501
|
+
const setMaterialProms = activatedNodes.map(async an => {
|
|
502
|
+
const anVisibleNested = this.getNestedVisibilityParameterValueOfNode(an);
|
|
503
|
+
const anDeferredMaterial = getInternalMetadataValue(an, 'deferredMaterial');
|
|
504
|
+
// skip applying material if it's already set via the parameter
|
|
505
|
+
const anMaterialParamValue = this._getParameterValueOfNode(an, BuiltInParameter.Material) as
|
|
506
|
+
| string
|
|
507
|
+
| undefined;
|
|
508
|
+
if (anVisibleNested && anDeferredMaterial && !anMaterialParamValue) {
|
|
523
509
|
await this.viewer.materialManager.setMaterialOnMesh(
|
|
524
|
-
|
|
510
|
+
anDeferredMaterial,
|
|
525
511
|
// this cast is fine, as deferred material can only be set on meshes
|
|
526
|
-
|
|
512
|
+
an as AbstractMesh
|
|
527
513
|
);
|
|
528
514
|
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
node.setEnabled(true);
|
|
532
|
-
} else {
|
|
533
|
-
node.setEnabled(false);
|
|
515
|
+
});
|
|
516
|
+
await Promise.all(setMaterialProms);
|
|
534
517
|
}
|
|
535
|
-
|
|
518
|
+
|
|
519
|
+
// set enabled state anyway
|
|
520
|
+
node.setEnabled(visible);
|
|
521
|
+
});
|
|
522
|
+
await Promise.all(observerProms);
|
|
536
523
|
});
|
|
537
524
|
this.setParameterObserver(BuiltInParameter.Material, async ({ nodes, newValue }) => {
|
|
538
525
|
const material = ParameterManager.parseString(newValue);
|
|
539
526
|
|
|
540
|
-
|
|
527
|
+
const observerProms = nodes.map(async node => {
|
|
541
528
|
if (!(node instanceof AbstractMesh)) {
|
|
542
529
|
throw new ViewerError({
|
|
543
530
|
id: ViewerErrorIds.InvalidParameterSubject,
|
|
@@ -545,28 +532,15 @@ export class ParameterManager {
|
|
|
545
532
|
});
|
|
546
533
|
}
|
|
547
534
|
|
|
548
|
-
//
|
|
549
|
-
|
|
550
|
-
// we have to go through all parents as well, because a parent may have been set to false, which also disables
|
|
551
|
-
// this child node
|
|
552
|
-
let curNode: TransformNode | null = node;
|
|
553
|
-
let visibleByParameter: boolean | undefined = undefined;
|
|
554
|
-
while (curNode && visibleByParameter !== false) {
|
|
555
|
-
const curNodeVisibleByParameter = this.getParameterValueOfNode(curNode, BuiltInParameter.Visible);
|
|
556
|
-
if (curNodeVisibleByParameter !== undefined) {
|
|
557
|
-
visibleByParameter = curNodeVisibleByParameter as boolean;
|
|
558
|
-
}
|
|
559
|
-
curNode = curNode.parent as TransformNode;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
const visible =
|
|
563
|
-
visibleByParameter !== undefined ? ParameterManager.parseBoolean(visibleByParameter) : node.isEnabled();
|
|
535
|
+
// only set material if the mesh is visible, or gets visible by the parameter update
|
|
536
|
+
const visible = this.getNestedVisibilityParameterValueOfNode(node);
|
|
564
537
|
if (visible) {
|
|
565
538
|
await this.viewer.materialManager.setMaterialOnMesh(material, node);
|
|
566
539
|
} else {
|
|
567
540
|
setInternalMetadataValue(node, 'deferredMaterial', material);
|
|
568
541
|
}
|
|
569
|
-
}
|
|
542
|
+
});
|
|
543
|
+
await Promise.all(observerProms);
|
|
570
544
|
});
|
|
571
545
|
this.setParameterObserver(BuiltInParameter.Position, async ({ nodes, newValue }) => {
|
|
572
546
|
const position = ParameterManager.parseVector(newValue);
|
|
@@ -710,12 +684,15 @@ export class ParameterManager {
|
|
|
710
684
|
const tagParamEntries = parameterEntries.filter(entry => isTagParameterSubject(entry.subject));
|
|
711
685
|
const nonTagParamEntries = parameterEntries.filter(entry => !isTagParameterSubject(entry.subject));
|
|
712
686
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
687
|
+
const tagParamProms = tagParamEntries.map(entry =>
|
|
688
|
+
this._applyParameterValue(entry.subject, entry.parameterName, assetContainer)
|
|
689
|
+
);
|
|
690
|
+
await Promise.all(tagParamProms);
|
|
691
|
+
|
|
692
|
+
const nonTagParamProms = nonTagParamEntries.map(entry =>
|
|
693
|
+
this._applyParameterValue(entry.subject, entry.parameterName, assetContainer)
|
|
694
|
+
);
|
|
695
|
+
await Promise.all(nonTagParamProms);
|
|
719
696
|
}
|
|
720
697
|
|
|
721
698
|
/**
|
|
@@ -811,4 +788,46 @@ export class ParameterManager {
|
|
|
811
788
|
|
|
812
789
|
return entries;
|
|
813
790
|
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* @returns Desired parameter value of a certain node.\
|
|
794
|
+
* Tags are considered as well but have lower priority than node parameters, as these are more specific.
|
|
795
|
+
*/
|
|
796
|
+
protected _getParameterValueOfNode(node: TransformNode, parameterName: string): ParameterValue | undefined {
|
|
797
|
+
const nodeParamValue = this.getParameterValue({ nodeName: node.name }, parameterName);
|
|
798
|
+
if (nodeParamValue !== undefined) {
|
|
799
|
+
return nodeParamValue;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const tags = getTagsAsStrArr(node);
|
|
803
|
+
const tagParamValue = tags.reduce<ParameterValue | undefined>((accValue, curTag) => {
|
|
804
|
+
// NOTE: it is possible that values are available for multiple tags
|
|
805
|
+
// in this case the resulting parameter value is quite "random" as the last tag in the tag string of the node has
|
|
806
|
+
// priority
|
|
807
|
+
const tagParamValue = this.getParameterValue({ tagName: curTag }, parameterName);
|
|
808
|
+
return accValue ?? tagParamValue;
|
|
809
|
+
}, undefined);
|
|
810
|
+
|
|
811
|
+
return tagParamValue;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* @returns Desired parameter value of a certain material.\
|
|
816
|
+
* Tags are considered as well but have lower priority than material parameters, as these are more specific.\
|
|
817
|
+
* Unused ATM, but added for consistency as counter part for {@link _getParameterValueOfNode}
|
|
818
|
+
*/
|
|
819
|
+
protected _getParameterValueOfMaterial(material: Material, parameterName: string): ParameterValue | undefined {
|
|
820
|
+
const materialParamValue = this.getParameterValue({ materialName: material.name }, parameterName);
|
|
821
|
+
if (materialParamValue !== undefined) {
|
|
822
|
+
return materialParamValue;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const tags = getTagsAsStrArr(material);
|
|
826
|
+
const tagParamValue = tags.reduce<ParameterValue | undefined>((accValue, curTag) => {
|
|
827
|
+
const tagParamValue = this.getParameterValue({ tagName: curTag }, parameterName);
|
|
828
|
+
return accValue ?? tagParamValue;
|
|
829
|
+
}, undefined);
|
|
830
|
+
|
|
831
|
+
return tagParamValue;
|
|
832
|
+
}
|
|
814
833
|
}
|