@combeenation/3d-viewer 8.0.0 → 8.1.0-alpha1

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 (33) hide show
  1. package/dist/lib-cjs/api/classes/parameter.d.ts +27 -0
  2. package/dist/lib-cjs/api/classes/parameter.js +27 -0
  3. package/dist/lib-cjs/api/classes/parameter.js.map +1 -1
  4. package/dist/lib-cjs/api/classes/variant.d.ts +8 -0
  5. package/dist/lib-cjs/api/classes/variant.js +12 -0
  6. package/dist/lib-cjs/api/classes/variant.js.map +1 -1
  7. package/dist/lib-cjs/api/classes/viewer.d.ts +2 -2
  8. package/dist/lib-cjs/api/classes/viewer.js +9 -8
  9. package/dist/lib-cjs/api/classes/viewer.js.map +1 -1
  10. package/dist/lib-cjs/api/manager/gltfExportManager.d.ts +1 -0
  11. package/dist/lib-cjs/api/manager/gltfExportManager.js +7 -2
  12. package/dist/lib-cjs/api/manager/gltfExportManager.js.map +1 -1
  13. package/dist/lib-cjs/api/manager/tagManager.d.ts +25 -23
  14. package/dist/lib-cjs/api/manager/tagManager.js +153 -89
  15. package/dist/lib-cjs/api/manager/tagManager.js.map +1 -1
  16. package/dist/lib-cjs/api/manager/variantInstanceManager.d.ts +1 -1
  17. package/dist/lib-cjs/api/manager/variantInstanceManager.js +5 -7
  18. package/dist/lib-cjs/api/manager/variantInstanceManager.js.map +1 -1
  19. package/dist/lib-cjs/api/util/globalTypes.d.ts +16 -4
  20. package/dist/lib-cjs/api/util/structureHelper.d.ts +6 -6
  21. package/dist/lib-cjs/api/util/structureHelper.js +31 -28
  22. package/dist/lib-cjs/api/util/structureHelper.js.map +1 -1
  23. package/dist/lib-cjs/buildinfo.json +1 -1
  24. package/dist/lib-cjs/commonjs.tsconfig.tsbuildinfo +1 -1
  25. package/package.json +2 -2
  26. package/src/api/classes/parameter.ts +30 -0
  27. package/src/api/classes/variant.ts +14 -0
  28. package/src/api/classes/viewer.ts +12 -14
  29. package/src/api/manager/gltfExportManager.ts +8 -3
  30. package/src/api/manager/tagManager.ts +178 -119
  31. package/src/api/manager/variantInstanceManager.ts +5 -8
  32. package/src/api/util/globalTypes.ts +20 -4
  33. package/src/api/util/structureHelper.ts +29 -27
@@ -2,20 +2,10 @@ import { Event, emitter } from '../classes/event';
2
2
  import { FuzzyMap } from '../classes/fuzzyMap';
3
3
  import { Parameter } from '../classes/parameter';
4
4
  import { Viewer } from '../classes/viewer';
5
- import {
6
- assertMeshCapability,
7
- cloneTransformNodeMaterial,
8
- injectNodeMetadata,
9
- mapTags,
10
- setMaterial,
11
- setMaterialColor,
12
- setMaterialMetallness,
13
- setMaterialRoughness,
14
- } from '../util/babylonHelper';
15
- import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
5
+ import { assertMeshCapability, mapTags, setMaterial } from '../util/babylonHelper';
16
6
  import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
17
7
  import { Tags } from '@babylonjs/core/Misc/tags';
18
- import { clone, get, has, merge, uniq } from 'lodash-es';
8
+ import { clone, get, merge, uniq } from 'lodash-es';
19
9
 
20
10
  export class TagManager {
21
11
  public readonly parameters: TagManagerParameterBag = new FuzzyMap();
@@ -37,7 +27,7 @@ export class TagManager {
37
27
  tagName: string,
38
28
  parameterName: string,
39
29
  value: ParameterValue
40
- ): Promise<ParameterObserverResult> {
30
+ ): Promise<TagManagerParameterObserverResultBag> {
41
31
  return await this.handleParameter({ tagName: tagName }, parameterName, value);
42
32
  }
43
33
 
@@ -49,22 +39,35 @@ export class TagManager {
49
39
  nodeName: string,
50
40
  parameterName: string,
51
41
  value: ParameterValue
52
- ): Promise<ParameterObserverResult> {
42
+ ): Promise<TagManagerParameterObserverResultBag> {
53
43
  return await this.handleParameter({ nodeName: nodeName }, parameterName, value);
54
44
  }
55
45
 
46
+ /**
47
+ * Calls the {@link ParameterObserver} for given {@link Parameter} and applies the {@link ParameterValue} on all
48
+ * materials with given materialName.
49
+ */
50
+ public async setMaterialParameterValue(
51
+ materialName: string,
52
+ parameterName: string,
53
+ value: ParameterValue
54
+ ): Promise<TagManagerParameterObserverResultBag> {
55
+ return await this.handleParameter({ materialName: materialName }, parameterName, value);
56
+ }
57
+
56
58
  /**
57
59
  * Splits the passed {@link TagManagerParameterValue}s into individual values and calls all {@link ParameterObserver}s
58
60
  * of all {@link Parameter}s with the respective {@link TagManagerSubject} and {@link ParameterValue}. The result is a
59
- * map of passed {@link TagManagerParameterValue}s and the associated {@link ParameterObserverResult} of the
61
+ * map of passed {@link TagManagerParameterValue}s and the associated {@link TagManagerParameterObserverResult} of the
60
62
  * {@link ParameterObserver}.
61
63
  */
62
64
  public async setParameterValues(
63
65
  values: TagManagerParameterValue[]
64
- ): Promise<Map<TagManagerParameterValue, ParameterObserverResult>> {
65
- const observerResultMap: Map<TagManagerParameterValue, ParameterObserverResult> = new Map();
66
- const tagPromises: Promise<ParameterObserverResult>[] = [];
67
- const nodePromises: Promise<ParameterObserverResult>[] = [];
66
+ ): Promise<Map<TagManagerParameterValue, TagManagerParameterObserverResultBag>> {
67
+ const observerResultMap: Map<TagManagerParameterValue, TagManagerParameterObserverResultBag> = new Map();
68
+ const tagPromises: Promise<TagManagerParameterObserverResultBag>[] = [];
69
+ const nodePromises: Promise<TagManagerParameterObserverResultBag>[] = [];
70
+ const materialPromises: Promise<TagManagerParameterObserverResultBag>[] = [];
68
71
  for (const value of values) {
69
72
  const subject: TagManagerSubject = {};
70
73
  if (value.tagName) {
@@ -73,6 +76,9 @@ export class TagManager {
73
76
  if (value.nodeName) {
74
77
  subject.nodeName = value.nodeName;
75
78
  }
79
+ if (value.materialName) {
80
+ subject.materialName = value.materialName;
81
+ }
76
82
  const observerResultPromise = this.handleParameter(subject, value.parameterName, value.value);
77
83
  observerResultPromise.then(result => observerResultMap.set(value, result));
78
84
  if (value.tagName) {
@@ -81,32 +87,37 @@ export class TagManager {
81
87
  if (value.nodeName) {
82
88
  nodePromises.push(observerResultPromise);
83
89
  }
90
+ if (value.materialName) {
91
+ materialPromises.push(observerResultPromise);
92
+ }
84
93
  }
85
94
  await Promise.all(tagPromises);
86
95
  await Promise.all(nodePromises);
96
+ await Promise.all(materialPromises);
87
97
  return observerResultMap;
88
98
  }
89
99
 
90
100
  /**
91
101
  * Gets a list of {@link TagManagerSubject}s that are present in both the state of the {@link TagManager}'s
92
- * {@link parameters} and the given node.
102
+ * {@link parameters} and the given object.
93
103
  */
94
- public getSubjectsFor(node: TransformNode): TagManagerSubject[] {
104
+ public getSubjectsFor(object: TransformNode | Material): TagManagerSubject[] {
95
105
  return [...this.parameters.keys()].filter(
96
106
  subject =>
97
- (subject.tagName && Tags.MatchesQuery(node, subject.tagName)) ||
98
- (subject.nodeName && node.name === subject.nodeName)
107
+ (subject.tagName && Tags.MatchesQuery(object, subject.tagName)) ||
108
+ (subject.nodeName && object.name === subject.nodeName) ||
109
+ (subject.materialName && object.name === subject.materialName)
99
110
  );
100
111
  }
101
112
 
102
113
  /**
103
114
  * Gets a list of {@link TagManagerSubject}s that are present in both the state of the {@link TagManager}'s
104
- * {@link parameters} and the given nodes.
115
+ * {@link parameters} and the given objects.
105
116
  */
106
- public getApplicableSubjectsFor(nodes: TransformNode[]): TagManagerSubject[] {
117
+ public getApplicableSubjectsFor(objects: (TransformNode | Material)[]): TagManagerSubject[] {
107
118
  let applicableSubjects: TagManagerSubject[] = [];
108
- for (const node of nodes) {
109
- const subjects = this.getSubjectsFor(node);
119
+ for (const object of objects) {
120
+ const subjects = this.getSubjectsFor(object);
110
121
  applicableSubjects = [...applicableSubjects, ...subjects];
111
122
  }
112
123
  return uniq(applicableSubjects);
@@ -117,12 +128,12 @@ export class TagManager {
117
128
  */
118
129
  public async applyExistingParameterValuesFor(
119
130
  subjects: TagManagerSubject[]
120
- ): Promise<Map<TagManagerSubject, ParameterObserverResult>> {
121
- const observerResultMap: Map<TagManagerSubject, ParameterObserverResult> = new Map();
122
- let tagPromises: Promise<ParameterObserverResult>[] = [];
123
- let nodePromises: Promise<ParameterObserverResult>[] = [];
131
+ ): Promise<Map<TagManagerSubject, TagManagerParameterObserverResultBag>> {
132
+ const observerResultMap: Map<TagManagerSubject, TagManagerParameterObserverResultBag> = new Map();
133
+ let tagPromises: Promise<TagManagerParameterObserverResultBag>[] = [];
134
+ let nodePromises: Promise<TagManagerParameterObserverResultBag>[] = [];
135
+ let materialPromises: Promise<TagManagerParameterObserverResultBag>[] = [];
124
136
  for (const subject of subjects) {
125
- this.clearAppliedNodeMetadataParameters(this.getNodesBySubject(subject));
126
137
  const observerResultPromises = this.handleParameterBag(subject, this.parameters.get(subject)!);
127
138
  for (const observerResultPromise of observerResultPromises) {
128
139
  observerResultPromise.then(result => observerResultMap.set(subject, result));
@@ -133,9 +144,13 @@ export class TagManager {
133
144
  if (subject.nodeName) {
134
145
  nodePromises = [...nodePromises, ...observerResultPromises];
135
146
  }
147
+ if (subject.materialName) {
148
+ materialPromises = [...materialPromises, ...observerResultPromises];
149
+ }
136
150
  }
137
151
  await Promise.all(tagPromises);
138
152
  await Promise.all(nodePromises);
153
+ await Promise.all(materialPromises);
139
154
  return observerResultMap;
140
155
  }
141
156
 
@@ -144,15 +159,15 @@ export class TagManager {
144
159
  * {@link TagManager}'s {@link parameters} are applied to all given nodes.
145
160
  */
146
161
  public async applyExistingParameterValuesTo(
147
- nodes: TransformNode[]
148
- ): Promise<Map<TagManagerSubject, ParameterObserverResult>> {
149
- return this.applyExistingParameterValuesFor(this.getApplicableSubjectsFor(nodes));
162
+ objects: (TransformNode | Material)[]
163
+ ): Promise<Map<TagManagerSubject, TagManagerParameterObserverResultBag>> {
164
+ return this.applyExistingParameterValuesFor(this.getApplicableSubjectsFor(objects));
150
165
  }
151
166
 
152
167
  /**
153
168
  * Applies all existing states of the {@link TagManager}'s {@link parameters} for all {@link TagManagerSubject}s.
154
169
  */
155
- public async applyExistingParameterValues(): Promise<Map<TagManagerSubject, ParameterObserverResult>> {
170
+ public async applyExistingParameterValues(): Promise<Map<TagManagerSubject, TagManagerParameterObserverResultBag>> {
156
171
  return this.applyExistingParameterValuesFor([...this.parameters.keys()]);
157
172
  }
158
173
 
@@ -248,18 +263,46 @@ export class TagManager {
248
263
  }
249
264
 
250
265
  /**
251
- * Registers observers that are called on every new node added to the Babylon.js scene. The observers ensure that each
252
- * new node gets the state of the {@link TagManager}'s {@link parameters} applied.
266
+ * Gets all materials for given {@link TagManagerSubject} on the Babylon.js scene.
267
+ */
268
+ public getMaterialsBySubject(subject: TagManagerSubject, predicate?: (material: Material) => boolean): Material[] {
269
+ let materials: Material[] = [];
270
+ if (subject.tagName) {
271
+ materials = [...materials, ...this.viewer.scene.getMaterialByTags(subject.tagName, predicate)];
272
+ }
273
+ if (subject.materialName) {
274
+ materials = [...materials, this.viewer.scene.getMaterialByName(subject.materialName)].filter(
275
+ t => !!t && (!predicate || predicate(t))
276
+ ) as Material[];
277
+ }
278
+ return materials;
279
+ }
280
+
281
+ /**
282
+ * Registers observers that are called on every new object added to the Babylon.js scene.
283
+ * The observers ensure that each new object gets the state of the {@link TagManager}'s {@link parameters} applied.
253
284
  */
254
- public registerNewTransformNodeObservers(scene: Scene) {
285
+ public registerNewObjectObservers(scene: Scene) {
286
+ // nodes and meshes
255
287
  const onNewTransformNodeAdded = (node: TransformNode) => {
256
- if (node.name === Viewer.BOUNDING_BOX_NAME || !this.viewer.variantInstances.areAllDefinitionsCreated) {
257
- // Avoid duplicate calls here. The parameter values are already applied when a variant instance has finished
258
- // creating (see event listener in viewer for VARIANT_INSTANCE_CREATED). This callback should only trigger when
259
- // a consumer "manually" clones a node.
288
+ /* NOTE:
289
+ Removed the check for created instances here since it is not guaranteed that all instances
290
+ created means that there is no scenario where new nodes should get TagManager parameters applied.
291
+ Instead, we check for the state to be enabled via the `onEnabledStateChangedObservable` below.
292
+ */
293
+ //if (node.name === Viewer.BOUNDING_BOX_NAME || !this.viewer.variantInstances.areAllDefinitionsCreated) {
294
+ if (node.name === Viewer.BOUNDING_BOX_NAME) {
260
295
  return;
261
296
  }
262
- this.applyExistingParameterValuesTo([node]).then();
297
+ const onEnabledStateChangedObserver = node.onEnabledStateChangedObservable.add(async state => {
298
+ if (!state) {
299
+ // if the node is disabled, means ignoring also "ghost nodes"
300
+ return;
301
+ }
302
+ await this.applyExistingParameterValuesTo([node]);
303
+ node.onEnabledStateChangedObservable.remove(onEnabledStateChangedObserver);
304
+ });
305
+ node.onEnabledStateChangedObservable.makeObserverBottomPriority(onEnabledStateChangedObserver!);
263
306
  };
264
307
  scene.onNewTransformNodeAddedObservable.makeObserverBottomPriority(
265
308
  scene.onNewTransformNodeAddedObservable.add(onNewTransformNodeAdded)!
@@ -267,6 +310,21 @@ export class TagManager {
267
310
  scene.onNewMeshAddedObservable.makeObserverBottomPriority(
268
311
  scene.onNewMeshAddedObservable.add(onNewTransformNodeAdded)!
269
312
  );
313
+ // materials
314
+ const onNewMaterialAdded = (material: Material) => {
315
+ /* NOTE:
316
+ We have to wait until a material is bound to a Mesh. In all other scenarios, the material
317
+ is not "fully ready" or even empty (without working attributes/properties).
318
+ */
319
+ const onMaterialBindObserver = material.onBindObservable.add(async abstractMesh => {
320
+ await this.applyExistingParameterValuesTo([material]);
321
+ material.onBindObservable.remove(onMaterialBindObserver);
322
+ });
323
+ material.onBindObservable.makeObserverBottomPriority(onMaterialBindObserver!);
324
+ };
325
+ scene.onNewMaterialAddedObservable.makeObserverBottomPriority(
326
+ scene.onNewMaterialAddedObservable.add(onNewMaterialAdded)!
327
+ );
270
328
  }
271
329
 
272
330
  /**
@@ -317,54 +375,51 @@ export class TagManager {
317
375
  }
318
376
  return true;
319
377
  });
320
- this.parameterObservers.set(Parameter.MATERIAL_COLOR, async payload => {
321
- const parsedValue = Parameter.parseColor(payload.newValue);
322
- for (const node of payload.nodes) {
323
- assertMeshCapability(node, Parameter.MATERIAL_COLOR);
324
- if (this.viewer.cloneMaterialsOnMutation) {
325
- if (!has(node.metadata, 'dirty.material')) {
326
- cloneTransformNodeMaterial(node);
327
- }
328
- if (!has(node.metadata, 'dirty.material.color')) {
329
- // inject initial value and mark as dirty
330
- injectNodeMetadata(node, { dirty: { material: { color: payload.oldValue } } }, false);
331
- }
378
+ this.parameterObservers.set(Parameter.COLOR, async payload => {
379
+ const color = Parameter.parseColor(payload.newValue);
380
+ for (const material of payload.materials) {
381
+ const materialCls = material.getClassName();
382
+ switch (materialCls) {
383
+ case 'PBRMaterial':
384
+ (material as PBRMaterial).albedoColor = color.toLinearSpace();
385
+ break;
386
+ case 'StandardMaterial':
387
+ (material as StandardMaterial).diffuseColor = color;
388
+ break;
389
+ default:
390
+ throw new Error(`Setting color for material of instance "${materialCls}" not implemented (yet).`);
332
391
  }
333
- setMaterialColor(node, parsedValue, false);
334
392
  }
335
393
  return true;
336
394
  });
337
- this.parameterObservers.set(Parameter.MATERIAL_ROUGHNESS, async payload => {
338
- const parsedValue = Parameter.parseNumber(payload.newValue);
339
- for (const node of payload.nodes as AbstractMesh[]) {
340
- assertMeshCapability(node, Parameter.MATERIAL_ROUGHNESS);
341
- if (this.viewer.cloneMaterialsOnMutation) {
342
- if (!has(node.metadata, 'dirty.material')) {
343
- cloneTransformNodeMaterial(node);
344
- }
345
- if (!has(node.metadata, 'dirty.material.roughness')) {
346
- // inject initial value and mark as dirty
347
- injectNodeMetadata(node, { dirty: { material: { color: payload.oldValue } } }, false);
348
- }
395
+ this.parameterObservers.set(Parameter.ROUGHNESS, async payload => {
396
+ const roughness = Parameter.parseNumber(payload.newValue);
397
+ for (const material of payload.materials) {
398
+ const materialCls = material.getClassName();
399
+ switch (materialCls) {
400
+ case 'PBRMaterial':
401
+ (material as PBRMaterial).roughness = roughness;
402
+ break;
403
+ case 'StandardMaterial':
404
+ (material as StandardMaterial).roughness = roughness;
405
+ break;
406
+ default:
407
+ throw new Error(`Setting roughness for material of instance "${materialCls}" not implemented (yet).`);
349
408
  }
350
- setMaterialRoughness(node, parsedValue, false);
351
409
  }
352
410
  return true;
353
411
  });
354
- this.parameterObservers.set(Parameter.MATERIAL_METALLNESS, async payload => {
355
- const parsedValue = Parameter.parseNumber(payload.newValue);
356
- for (const node of payload.nodes as AbstractMesh[]) {
357
- assertMeshCapability(node, Parameter.MATERIAL_METALLNESS);
358
- if (this.viewer.cloneMaterialsOnMutation) {
359
- if (!has(node.metadata, 'dirty.material')) {
360
- cloneTransformNodeMaterial(node);
361
- }
362
- if (!has(node.metadata, 'dirty.material.metallness')) {
363
- // inject initial value and mark as dirty
364
- injectNodeMetadata(node, { dirty: { material: { color: payload.oldValue } } }, false);
365
- }
412
+ this.parameterObservers.set(Parameter.METALLNESS, async payload => {
413
+ const metallness = Parameter.parseNumber(payload.newValue);
414
+ for (const material of payload.materials) {
415
+ const materialCls = material.getClassName();
416
+ switch (materialCls) {
417
+ case 'PBRMaterial':
418
+ (material as PBRMaterial).metallic = metallness;
419
+ break;
420
+ default:
421
+ throw new Error(`Setting metallness for material of instance "${materialCls}" not implemented (yet).`);
366
422
  }
367
- setMaterialMetallness(node, parsedValue, false);
368
423
  }
369
424
  return true;
370
425
  });
@@ -379,8 +434,8 @@ export class TagManager {
379
434
  protected handleParameterBag(
380
435
  subject: TagManagerSubject,
381
436
  parameters: ParameterBag
382
- ): Promise<ParameterObserverResult>[] {
383
- const observerPromises: Promise<ParameterObserverResult>[] = [];
437
+ ): Promise<TagManagerParameterObserverResultBag>[] {
438
+ const observerPromises: Promise<TagManagerParameterObserverResultBag>[] = [];
384
439
  for (const parameter in parameters) {
385
440
  observerPromises.push(this.handleParameter(subject, parameter, parameters[parameter]));
386
441
  }
@@ -397,36 +452,58 @@ export class TagManager {
397
452
  subject: TagManagerSubject,
398
453
  parameter: string,
399
454
  parameterValue: ParameterValue
400
- ): Promise<ParameterObserverResult> {
455
+ ): Promise<TagManagerParameterObserverResultBag> {
401
456
  Parameter.assertParameter(Parameter.declarations, parameter, parameterValue);
402
- if (!this.parameterObservers.has(parameter)) {
403
- return Promise.resolve(false);
404
- }
405
457
  const parameterBag = this.parameters.get(subject) || {};
406
458
  const oldValue = parameterBag[parameter];
459
+ const parameterObserverResultBag: TagManagerParameterObserverResultBag = {
460
+ subject: subject,
461
+ parameter: parameter,
462
+ nodes: [],
463
+ materials: [],
464
+ oldValue: oldValue,
465
+ newValue: parameterValue,
466
+ parameterObserverResult: false,
467
+ };
468
+ if (!this.parameterObservers.has(parameter)) {
469
+ return Promise.resolve(parameterObserverResultBag);
470
+ }
407
471
  if (oldValue === parameterValue) {
408
- return Promise.resolve(false);
472
+ /* NOTE:
473
+ The following is wrong in different scenarios. If a node enters life and the subject/parameter
474
+ applied in the past, the new node will not be altered by the subsequent observers, because of
475
+ the return on the next line. If we want to tackle that problem and do an early return to not
476
+ trigger observers twice, we have to hold a state for every node/material and the applied
477
+ parameter value. This could be highly cost sensitive, so I would just avoid that check
478
+ at all. Babylon will not trigger changes if the value on a node/material does not change
479
+ anyway.
480
+ */
481
+ //return Promise.resolve(parameterObserverResultBag);
409
482
  }
410
483
  this.parameters.set(subject, parameterBag);
411
484
  this.parameters.get(subject)![parameter] = parameterValue;
412
485
  const nodes = this.getNodesBySubject(subject);
413
- if (nodes.length === 0) {
414
- return Promise.resolve(false);
486
+ const materials = this.getMaterialsBySubject(subject);
487
+ if (nodes.length === 0 && materials.length === 0) {
488
+ return Promise.resolve(parameterObserverResultBag);
415
489
  }
416
490
  const observer = this.parameterObservers.get(parameter)!;
417
- const result = await observer({ subject: subject, nodes: nodes, newValue: parameterValue, oldValue: oldValue });
418
- if (result === false) {
419
- // reset to old value if observer was not successful
420
- this.parameters.get(subject)![parameter] = oldValue;
421
- }
422
- emitter.emit(Event.TAG_MANAGER_PARAMETER_COMMITTED, {
491
+ const result = await observer({
423
492
  subject: subject,
424
- parameter: parameter,
425
493
  nodes: nodes,
426
- oldValue: oldValue,
494
+ materials: materials,
427
495
  newValue: parameterValue,
428
- result: result,
496
+ oldValue: oldValue,
429
497
  });
498
+ if (result === false) {
499
+ // reset to old value if observer was not successful
500
+ this.parameters.get(subject)![parameter] = oldValue;
501
+ }
502
+ parameterObserverResultBag.nodes = nodes;
503
+ parameterObserverResultBag.materials = materials;
504
+ parameterObserverResultBag.parameterObserverResult = result;
505
+ emitter.emit(Event.TAG_MANAGER_PARAMETER_COMMITTED, parameterObserverResultBag);
506
+ return parameterObserverResultBag;
430
507
  }
431
508
 
432
509
  /**
@@ -441,17 +518,14 @@ export class TagManager {
441
518
  if (oldNode) {
442
519
  accNodeMapping[oldNode.name] = curNode.name;
443
520
  }
444
-
445
521
  return accNodeMapping;
446
522
  }, {} as TagMapping);
447
-
448
523
  for (const subject of this.parameters.keys()) {
449
524
  const isNode = subject.nodeName;
450
525
  const newName = isNode ? nodeMapping[subject.nodeName] : tagMapping[subject.tagName];
451
526
  if (!newName) {
452
527
  continue;
453
528
  }
454
-
455
529
  const newSubject = clone(subject);
456
530
  newSubject[isNode ? 'nodeName' : 'tagName'] = newName;
457
531
  const parameterBag = clone(this.parameters.get(subject)!);
@@ -459,19 +533,4 @@ export class TagManager {
459
533
  this.parameters.set(newSubject, parameterBag);
460
534
  }
461
535
  }
462
-
463
- /**
464
- * Clears the applied {@link ParameterBag} state of the node's metadata to ensure that observers trigger (once again).
465
- * This is necessary e.g. after mapping tags when a nodeName subject shall override the parameterValue of a mapped
466
- * tag.
467
- * @protected
468
- */
469
- protected clearAppliedNodeMetadataParameters(nodes: TransformNode[]): void {
470
- for (const node of nodes) {
471
- if (!node.metadata?.tagManager?.parameters) {
472
- continue;
473
- }
474
- node.metadata.tagManager.parameters = {};
475
- }
476
- }
477
536
  }
@@ -120,15 +120,13 @@ export class VariantInstanceManager extends EventBroadcaster {
120
120
  public async create(
121
121
  dottedPath: DottedPathArgument,
122
122
  name?: string,
123
- parameters?: ParameterBag,
124
- tagManagerParameterValues?: TagManagerParameterValue[]
123
+ parameters?: ParameterBag
125
124
  ): Promise<VariantInstance> {
126
125
  const variant = DottedPath.create(dottedPath).path;
127
126
  const definition = {
128
127
  name: this.ensureUniqueName(name ? name : variant),
129
128
  variant: variant,
130
129
  parameters: parameters,
131
- tagManagerParameterValues: tagManagerParameterValues,
132
130
  };
133
131
  return await this.createFromDefinition(definition);
134
132
  }
@@ -246,7 +244,10 @@ export class VariantInstanceManager extends EventBroadcaster {
246
244
  this.variantInstances.set(definition.name, variantInstanceClone);
247
245
  reportDuplicateNodeNames(duplicateNodeNames(this.viewer.scene.getNodes(), n => n instanceof TransformNode));
248
246
  this.viewer.tagManager.mapNodesAndTags(variantInstanceClone.variant.elementNodesFlat, tagMapping ?? {});
249
- await this.viewer.tagManager.applyExistingParameterValuesTo(variantInstanceClone.variant.elementNodesFlat);
247
+ await this.viewer.tagManager.applyExistingParameterValuesTo([
248
+ ...variantInstanceClone.variant.elementNodesFlat,
249
+ ...variantInstanceClone.variant.elementAbstractMeshesFlat.map(n => n.material!).filter(m => !!m),
250
+ ]);
250
251
  variantInstance.broadcastEvent(Event.VARIANT_INSTANCE_CLONED, variantInstanceClone);
251
252
  return variantInstanceClone;
252
253
  }
@@ -285,10 +286,6 @@ export class VariantInstanceManager extends EventBroadcaster {
285
286
  const variant = await this.rootVariant.getDescendant(definition.variant);
286
287
  const variantInstance = await VariantInstance.createLiving(variant, definition.name, definition.parameters);
287
288
  this.variantInstances.set(definition.name, variantInstance);
288
- // set tag manager parameter values if all definitions has been created.
289
- if (definition.tagManagerParameterValues && this.areAllDefinitionsCreated) {
290
- await this.viewer.tagManager.setParameterValues(definition.tagManagerParameterValues);
291
- }
292
289
  this.broadcastEvent(Event.VARIANT_INSTANCE_CREATED, variantInstance);
293
290
  return variantInstance;
294
291
  }
@@ -280,7 +280,7 @@ type ScreenshotSettings = {
280
280
  /**
281
281
  * Use this to define geometry to be excluded from autofocus, GLB export, etc.
282
282
  */
283
- type ExcludedGeometry = Mesh | AbstractMesh | VariantInstance | Variant | VariantElement | Node | TagManagerSubject;
283
+ type ExcludedGeometry = TransformNode | VariantInstance | Variant | VariantElement | TagManagerSubject;
284
284
  type ExcludedGeometryList = ExcludedGeometry[];
285
285
 
286
286
  type AutofocusSettings = {
@@ -370,7 +370,6 @@ type VariantInstanceDefinition = {
370
370
  name?: string;
371
371
  variant: DottedPathArgument;
372
372
  parameters?: ParameterBag;
373
- tagManagerParameterValues?: TagManagerParameterValue[];
374
373
  lazy?: boolean;
375
374
  };
376
375
 
@@ -403,7 +402,7 @@ type DottedPathArgument = string | string[] | DottedPath;
403
402
  type ParameterObserverResult = boolean | void;
404
403
 
405
404
  type ParameterObserver = (
406
- payload: any,
405
+ object: any,
407
406
  oldValue: Undefinable<ParameterValue>,
408
407
  newValue: ParameterValue
409
408
  ) => Promise<ParameterObserverResult>;
@@ -487,13 +486,18 @@ type TagMapping = {
487
486
  type TagManagerSubject = {
488
487
  tagName?: string;
489
488
  nodeName?: string;
489
+ materialName?: string;
490
490
  };
491
491
 
492
+ type TagManagerParameterObserverResult = ParameterObserverResult;
493
+
492
494
  /**
493
495
  * The observer should return `false` in cases where the given value was not actually applied. E.g. when wanting to
494
496
  * apply a property on the given `node`s material which doesn't exist at the time the observer is called etc.
495
497
  */
496
- type TagManagerParameterObserver = (payload: TagManagerParameterObserverPayload) => Promise<ParameterObserverResult>;
498
+ type TagManagerParameterObserver = (
499
+ payload: TagManagerParameterObserverPayload
500
+ ) => Promise<TagManagerParameterObserverResult>;
497
501
 
498
502
  type TagManagerParameterBag = FuzzyMap<TagManagerSubject, ParameterBag>;
499
503
 
@@ -502,6 +506,7 @@ type TagManagerParameterObserverBag = Map<string, TagManagerParameterObserver>;
502
506
  type TagManagerParameterObserverPayload = {
503
507
  subject: TagManagerSubject;
504
508
  nodes: TransformNode[];
509
+ materials: Material[];
505
510
  newValue: ParameterValue;
506
511
  oldValue: Undefinable<ParameterValue>;
507
512
  };
@@ -509,10 +514,21 @@ type TagManagerParameterObserverPayload = {
509
514
  type TagManagerParameterValue = {
510
515
  tagName?: string;
511
516
  nodeName?: string;
517
+ materialName?: string;
512
518
  parameterName: string;
513
519
  value: ParameterValue;
514
520
  };
515
521
 
522
+ type TagManagerParameterObserverResultBag = {
523
+ subject: TagManagerSubject;
524
+ parameter: string;
525
+ nodes: TransformNode[];
526
+ materials: Material[];
527
+ oldValue: ParameterValue;
528
+ newValue: ParameterValue;
529
+ parameterObserverResult: TagManagerParameterObserverResult;
530
+ };
531
+
516
532
  type NodeNamingStrategyPayload = {
517
533
  variantInstance: VariantInstance;
518
534
  variant: Variant;