@combeenation/3d-viewer 8.0.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/lib-cjs/api/classes/element.js +20 -25
  2. package/dist/lib-cjs/api/classes/element.js.map +1 -1
  3. package/dist/lib-cjs/api/classes/parameter.d.ts +46 -0
  4. package/dist/lib-cjs/api/classes/parameter.js +103 -0
  5. package/dist/lib-cjs/api/classes/parameter.js.map +1 -1
  6. package/dist/lib-cjs/api/classes/variant.d.ts +8 -0
  7. package/dist/lib-cjs/api/classes/variant.js +17 -5
  8. package/dist/lib-cjs/api/classes/variant.js.map +1 -1
  9. package/dist/lib-cjs/api/classes/viewer.d.ts +2 -2
  10. package/dist/lib-cjs/api/classes/viewer.js +9 -8
  11. package/dist/lib-cjs/api/classes/viewer.js.map +1 -1
  12. package/dist/lib-cjs/api/classes/viewerLight.js +1 -1
  13. package/dist/lib-cjs/api/classes/viewerLight.js.map +1 -1
  14. package/dist/lib-cjs/api/manager/gltfExportManager.d.ts +1 -0
  15. package/dist/lib-cjs/api/manager/gltfExportManager.js +7 -2
  16. package/dist/lib-cjs/api/manager/gltfExportManager.js.map +1 -1
  17. package/dist/lib-cjs/api/manager/tagManager.d.ts +25 -23
  18. package/dist/lib-cjs/api/manager/tagManager.js +176 -98
  19. package/dist/lib-cjs/api/manager/tagManager.js.map +1 -1
  20. package/dist/lib-cjs/api/manager/variantInstanceManager.d.ts +1 -1
  21. package/dist/lib-cjs/api/manager/variantInstanceManager.js +5 -7
  22. package/dist/lib-cjs/api/manager/variantInstanceManager.js.map +1 -1
  23. package/dist/lib-cjs/api/util/babylonHelper.d.ts +5 -3
  24. package/dist/lib-cjs/api/util/babylonHelper.js +51 -14
  25. package/dist/lib-cjs/api/util/babylonHelper.js.map +1 -1
  26. package/dist/lib-cjs/api/util/globalTypes.d.ts +26 -4
  27. package/dist/lib-cjs/api/util/resourceHelper.js +9 -1
  28. package/dist/lib-cjs/api/util/resourceHelper.js.map +1 -1
  29. package/dist/lib-cjs/api/util/sceneLoaderHelper.js +1 -1
  30. package/dist/lib-cjs/api/util/sceneLoaderHelper.js.map +1 -1
  31. package/dist/lib-cjs/api/util/structureHelper.d.ts +6 -6
  32. package/dist/lib-cjs/api/util/structureHelper.js +31 -28
  33. package/dist/lib-cjs/api/util/structureHelper.js.map +1 -1
  34. package/dist/lib-cjs/buildinfo.json +1 -1
  35. package/dist/lib-cjs/commonjs.tsconfig.tsbuildinfo +1 -1
  36. package/package.json +3 -2
  37. package/src/api/classes/element.ts +24 -34
  38. package/src/api/classes/parameter.ts +109 -0
  39. package/src/api/classes/variant.ts +20 -6
  40. package/src/api/classes/viewer.ts +12 -14
  41. package/src/api/classes/viewerLight.ts +2 -2
  42. package/src/api/manager/gltfExportManager.ts +8 -3
  43. package/src/api/manager/tagManager.ts +209 -122
  44. package/src/api/manager/variantInstanceManager.ts +5 -8
  45. package/src/api/util/babylonHelper.ts +60 -13
  46. package/src/api/util/globalTypes.ts +32 -4
  47. package/src/api/util/resourceHelper.ts +10 -3
  48. package/src/api/util/sceneLoaderHelper.ts +2 -2
  49. package/src/api/util/structureHelper.ts +29 -27
@@ -4,18 +4,16 @@ import { Parameter } from '../classes/parameter';
4
4
  import { Viewer } from '../classes/viewer';
5
5
  import {
6
6
  assertMeshCapability,
7
- cloneTransformNodeMaterial,
8
- injectNodeMetadata,
7
+ drawPaintableOnMaterial,
8
+ injectMetadata,
9
9
  mapTags,
10
10
  setMaterial,
11
- setMaterialColor,
12
- setMaterialMetallness,
13
- setMaterialRoughness,
14
11
  } from '../util/babylonHelper';
15
- import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
12
+ import { createImageFromImgSrc, createImageFromSvg } from '../util/resourceHelper';
16
13
  import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
17
14
  import { Tags } from '@babylonjs/core/Misc/tags';
18
- import { clone, get, has, merge, uniq } from 'lodash-es';
15
+ import isSvg from 'is-svg';
16
+ import { clone, get, merge, uniq } from 'lodash-es';
19
17
 
20
18
  export class TagManager {
21
19
  public readonly parameters: TagManagerParameterBag = new FuzzyMap();
@@ -37,7 +35,7 @@ export class TagManager {
37
35
  tagName: string,
38
36
  parameterName: string,
39
37
  value: ParameterValue
40
- ): Promise<ParameterObserverResult> {
38
+ ): Promise<TagManagerParameterObserverResultBag> {
41
39
  return await this.handleParameter({ tagName: tagName }, parameterName, value);
42
40
  }
43
41
 
@@ -49,22 +47,35 @@ export class TagManager {
49
47
  nodeName: string,
50
48
  parameterName: string,
51
49
  value: ParameterValue
52
- ): Promise<ParameterObserverResult> {
50
+ ): Promise<TagManagerParameterObserverResultBag> {
53
51
  return await this.handleParameter({ nodeName: nodeName }, parameterName, value);
54
52
  }
55
53
 
54
+ /**
55
+ * Calls the {@link ParameterObserver} for given {@link Parameter} and applies the {@link ParameterValue} on all
56
+ * materials with given materialName.
57
+ */
58
+ public async setMaterialParameterValue(
59
+ materialName: string,
60
+ parameterName: string,
61
+ value: ParameterValue
62
+ ): Promise<TagManagerParameterObserverResultBag> {
63
+ return await this.handleParameter({ materialName: materialName }, parameterName, value);
64
+ }
65
+
56
66
  /**
57
67
  * Splits the passed {@link TagManagerParameterValue}s into individual values and calls all {@link ParameterObserver}s
58
68
  * 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
69
+ * map of passed {@link TagManagerParameterValue}s and the associated {@link TagManagerParameterObserverResult} of the
60
70
  * {@link ParameterObserver}.
61
71
  */
62
72
  public async setParameterValues(
63
73
  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>[] = [];
74
+ ): Promise<Map<TagManagerParameterValue, TagManagerParameterObserverResultBag>> {
75
+ const observerResultMap: Map<TagManagerParameterValue, TagManagerParameterObserverResultBag> = new Map();
76
+ const tagPromises: Promise<TagManagerParameterObserverResultBag>[] = [];
77
+ const nodePromises: Promise<TagManagerParameterObserverResultBag>[] = [];
78
+ const materialPromises: Promise<TagManagerParameterObserverResultBag>[] = [];
68
79
  for (const value of values) {
69
80
  const subject: TagManagerSubject = {};
70
81
  if (value.tagName) {
@@ -73,6 +84,9 @@ export class TagManager {
73
84
  if (value.nodeName) {
74
85
  subject.nodeName = value.nodeName;
75
86
  }
87
+ if (value.materialName) {
88
+ subject.materialName = value.materialName;
89
+ }
76
90
  const observerResultPromise = this.handleParameter(subject, value.parameterName, value.value);
77
91
  observerResultPromise.then(result => observerResultMap.set(value, result));
78
92
  if (value.tagName) {
@@ -81,32 +95,37 @@ export class TagManager {
81
95
  if (value.nodeName) {
82
96
  nodePromises.push(observerResultPromise);
83
97
  }
98
+ if (value.materialName) {
99
+ materialPromises.push(observerResultPromise);
100
+ }
84
101
  }
85
102
  await Promise.all(tagPromises);
86
103
  await Promise.all(nodePromises);
104
+ await Promise.all(materialPromises);
87
105
  return observerResultMap;
88
106
  }
89
107
 
90
108
  /**
91
109
  * 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.
110
+ * {@link parameters} and the given object.
93
111
  */
94
- public getSubjectsFor(node: TransformNode): TagManagerSubject[] {
112
+ public getSubjectsFor(object: TransformNode | Material): TagManagerSubject[] {
95
113
  return [...this.parameters.keys()].filter(
96
114
  subject =>
97
- (subject.tagName && Tags.MatchesQuery(node, subject.tagName)) ||
98
- (subject.nodeName && node.name === subject.nodeName)
115
+ (subject.tagName && Tags.MatchesQuery(object, subject.tagName)) ||
116
+ (subject.nodeName && object.name === subject.nodeName) ||
117
+ (subject.materialName && object.name === subject.materialName)
99
118
  );
100
119
  }
101
120
 
102
121
  /**
103
122
  * 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.
123
+ * {@link parameters} and the given objects.
105
124
  */
106
- public getApplicableSubjectsFor(nodes: TransformNode[]): TagManagerSubject[] {
125
+ public getApplicableSubjectsFor(objects: (TransformNode | Material)[]): TagManagerSubject[] {
107
126
  let applicableSubjects: TagManagerSubject[] = [];
108
- for (const node of nodes) {
109
- const subjects = this.getSubjectsFor(node);
127
+ for (const object of objects) {
128
+ const subjects = this.getSubjectsFor(object);
110
129
  applicableSubjects = [...applicableSubjects, ...subjects];
111
130
  }
112
131
  return uniq(applicableSubjects);
@@ -117,12 +136,12 @@ export class TagManager {
117
136
  */
118
137
  public async applyExistingParameterValuesFor(
119
138
  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>[] = [];
139
+ ): Promise<Map<TagManagerSubject, TagManagerParameterObserverResultBag>> {
140
+ const observerResultMap: Map<TagManagerSubject, TagManagerParameterObserverResultBag> = new Map();
141
+ let tagPromises: Promise<TagManagerParameterObserverResultBag>[] = [];
142
+ let nodePromises: Promise<TagManagerParameterObserverResultBag>[] = [];
143
+ let materialPromises: Promise<TagManagerParameterObserverResultBag>[] = [];
124
144
  for (const subject of subjects) {
125
- this.clearAppliedNodeMetadataParameters(this.getNodesBySubject(subject));
126
145
  const observerResultPromises = this.handleParameterBag(subject, this.parameters.get(subject)!);
127
146
  for (const observerResultPromise of observerResultPromises) {
128
147
  observerResultPromise.then(result => observerResultMap.set(subject, result));
@@ -133,9 +152,13 @@ export class TagManager {
133
152
  if (subject.nodeName) {
134
153
  nodePromises = [...nodePromises, ...observerResultPromises];
135
154
  }
155
+ if (subject.materialName) {
156
+ materialPromises = [...materialPromises, ...observerResultPromises];
157
+ }
136
158
  }
137
159
  await Promise.all(tagPromises);
138
160
  await Promise.all(nodePromises);
161
+ await Promise.all(materialPromises);
139
162
  return observerResultMap;
140
163
  }
141
164
 
@@ -144,15 +167,15 @@ export class TagManager {
144
167
  * {@link TagManager}'s {@link parameters} are applied to all given nodes.
145
168
  */
146
169
  public async applyExistingParameterValuesTo(
147
- nodes: TransformNode[]
148
- ): Promise<Map<TagManagerSubject, ParameterObserverResult>> {
149
- return this.applyExistingParameterValuesFor(this.getApplicableSubjectsFor(nodes));
170
+ objects: (TransformNode | Material)[]
171
+ ): Promise<Map<TagManagerSubject, TagManagerParameterObserverResultBag>> {
172
+ return this.applyExistingParameterValuesFor(this.getApplicableSubjectsFor(objects));
150
173
  }
151
174
 
152
175
  /**
153
176
  * Applies all existing states of the {@link TagManager}'s {@link parameters} for all {@link TagManagerSubject}s.
154
177
  */
155
- public async applyExistingParameterValues(): Promise<Map<TagManagerSubject, ParameterObserverResult>> {
178
+ public async applyExistingParameterValues(): Promise<Map<TagManagerSubject, TagManagerParameterObserverResultBag>> {
156
179
  return this.applyExistingParameterValuesFor([...this.parameters.keys()]);
157
180
  }
158
181
 
@@ -248,18 +271,46 @@ export class TagManager {
248
271
  }
249
272
 
250
273
  /**
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.
274
+ * Gets all materials for given {@link TagManagerSubject} on the Babylon.js scene.
275
+ */
276
+ public getMaterialsBySubject(subject: TagManagerSubject, predicate?: (material: Material) => boolean): Material[] {
277
+ let materials: Material[] = [];
278
+ if (subject.tagName) {
279
+ materials = [...materials, ...this.viewer.scene.getMaterialByTags(subject.tagName, predicate)];
280
+ }
281
+ if (subject.materialName) {
282
+ materials = [...materials, this.viewer.scene.getMaterialByName(subject.materialName)].filter(
283
+ t => !!t && (!predicate || predicate(t))
284
+ ) as Material[];
285
+ }
286
+ return materials;
287
+ }
288
+
289
+ /**
290
+ * Registers observers that are called on every new object added to the Babylon.js scene.
291
+ * The observers ensure that each new object gets the state of the {@link TagManager}'s {@link parameters} applied.
253
292
  */
254
- public registerNewTransformNodeObservers(scene: Scene) {
293
+ public registerNewObjectObservers(scene: Scene) {
294
+ // nodes and meshes
255
295
  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.
296
+ /* NOTE:
297
+ Removed the check for created instances here since it is not guaranteed that all instances
298
+ created means that there is no scenario where new nodes should get TagManager parameters applied.
299
+ Instead, we check for the state to be enabled via the `onEnabledStateChangedObservable` below.
300
+ */
301
+ //if (node.name === Viewer.BOUNDING_BOX_NAME || !this.viewer.variantInstances.areAllDefinitionsCreated) {
302
+ if (node.name === Viewer.BOUNDING_BOX_NAME) {
260
303
  return;
261
304
  }
262
- this.applyExistingParameterValuesTo([node]).then();
305
+ const onEnabledStateChangedObserver = node.onEnabledStateChangedObservable.add(async state => {
306
+ if (!state) {
307
+ // if the node is disabled, means ignoring also "ghost nodes"
308
+ return;
309
+ }
310
+ await this.applyExistingParameterValuesTo([node]);
311
+ node.onEnabledStateChangedObservable.remove(onEnabledStateChangedObserver);
312
+ });
313
+ node.onEnabledStateChangedObservable.makeObserverBottomPriority(onEnabledStateChangedObserver!);
263
314
  };
264
315
  scene.onNewTransformNodeAddedObservable.makeObserverBottomPriority(
265
316
  scene.onNewTransformNodeAddedObservable.add(onNewTransformNodeAdded)!
@@ -267,6 +318,21 @@ export class TagManager {
267
318
  scene.onNewMeshAddedObservable.makeObserverBottomPriority(
268
319
  scene.onNewMeshAddedObservable.add(onNewTransformNodeAdded)!
269
320
  );
321
+ // materials
322
+ const onNewMaterialAdded = (material: Material) => {
323
+ /* NOTE:
324
+ We have to wait until a material is bound to a Mesh. In all other scenarios, the material
325
+ is not "fully ready" or even empty (without working attributes/properties).
326
+ */
327
+ const onMaterialBindObserver = material.onBindObservable.add(async abstractMesh => {
328
+ await this.applyExistingParameterValuesTo([material]);
329
+ material.onBindObservable.remove(onMaterialBindObserver);
330
+ });
331
+ material.onBindObservable.makeObserverBottomPriority(onMaterialBindObserver!);
332
+ };
333
+ scene.onNewMaterialAddedObservable.makeObserverBottomPriority(
334
+ scene.onNewMaterialAddedObservable.add(onNewMaterialAdded)!
335
+ );
270
336
  }
271
337
 
272
338
  /**
@@ -275,12 +341,7 @@ export class TagManager {
275
341
  */
276
342
  protected addParameterObservers(): TagManager {
277
343
  this.parameterObservers.set(Parameter.VISIBLE, async payload => {
278
- let visible;
279
- try {
280
- visible = Parameter.parseBoolean(payload.newValue);
281
- } catch (e) {
282
- return false;
283
- }
344
+ const visible = Parameter.parseBoolean(payload.newValue);
284
345
  if (visible === true) {
285
346
  for (const node of payload.nodes) {
286
347
  node.setEnabled(true);
@@ -317,57 +378,76 @@ export class TagManager {
317
378
  }
318
379
  return true;
319
380
  });
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
- }
381
+ this.parameterObservers.set(Parameter.COLOR, async payload => {
382
+ const color = Parameter.parseColor(payload.newValue);
383
+ for (const material of payload.materials) {
384
+ const materialCls = material.getClassName();
385
+ switch (materialCls) {
386
+ case 'PBRMaterial':
387
+ (material as PBRMaterial).albedoColor = color.toLinearSpace();
388
+ break;
389
+ case 'StandardMaterial':
390
+ (material as StandardMaterial).diffuseColor = color;
391
+ break;
392
+ default:
393
+ throw new Error(`Setting color for material of instance "${materialCls}" not implemented (yet).`);
332
394
  }
333
- setMaterialColor(node, parsedValue, false);
334
395
  }
335
396
  return true;
336
397
  });
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
- }
398
+ this.parameterObservers.set(Parameter.ROUGHNESS, async payload => {
399
+ const roughness = Parameter.parseNumber(payload.newValue);
400
+ for (const material of payload.materials) {
401
+ const materialCls = material.getClassName();
402
+ switch (materialCls) {
403
+ case 'PBRMaterial':
404
+ (material as PBRMaterial).roughness = roughness;
405
+ break;
406
+ case 'StandardMaterial':
407
+ (material as StandardMaterial).roughness = roughness;
408
+ break;
409
+ default:
410
+ throw new Error(`Setting roughness for material of instance "${materialCls}" not implemented (yet).`);
349
411
  }
350
- setMaterialRoughness(node, parsedValue, false);
351
412
  }
352
413
  return true;
353
414
  });
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
- }
415
+ this.parameterObservers.set(Parameter.METALLIC, async payload => {
416
+ const metallic = Parameter.parseNumber(payload.newValue);
417
+ for (const material of payload.materials) {
418
+ const materialCls = material.getClassName();
419
+ switch (materialCls) {
420
+ case 'PBRMaterial':
421
+ (material as PBRMaterial).metallic = metallic;
422
+ break;
423
+ default:
424
+ throw new Error(`Setting metallic for material of instance "${materialCls}" not implemented (yet).`);
366
425
  }
367
- setMaterialMetallness(node, parsedValue, false);
368
426
  }
369
427
  return true;
370
428
  });
429
+ this.parameterObservers.set(Parameter.PAINTABLE, async payload => {
430
+ const paintableValue: PaintableValue = Parameter.parsePaintableValue(payload.newValue);
431
+
432
+ // check if value is svg or image source, do the conversion accordingly
433
+ const srcIsSvg = isSvg(paintableValue.src);
434
+ if (!srcIsSvg && paintableValue.src.includes('<svg') && paintableValue.src.includes('</svg>')) {
435
+ // seems like the user tried to use a SVG string, as <svg> tags are used
436
+ // inform the user that this is not a valid SVG string
437
+ throw new Error(`Setting paintable value failed: "${paintableValue.src}" is no valid SVG string.`);
438
+ }
439
+
440
+ const imageSource: CanvasImageSource = srcIsSvg
441
+ ? await createImageFromSvg(paintableValue.src)
442
+ : await createImageFromImgSrc(paintableValue.src);
443
+
444
+ // apply image source on desired material(s)
445
+ for (const material of payload.materials) {
446
+ drawPaintableOnMaterial(material, imageSource, this.viewer.scene, paintableValue.options);
447
+ }
448
+
449
+ return true;
450
+ });
371
451
  return this;
372
452
  }
373
453
 
@@ -379,8 +459,8 @@ export class TagManager {
379
459
  protected handleParameterBag(
380
460
  subject: TagManagerSubject,
381
461
  parameters: ParameterBag
382
- ): Promise<ParameterObserverResult>[] {
383
- const observerPromises: Promise<ParameterObserverResult>[] = [];
462
+ ): Promise<TagManagerParameterObserverResultBag>[] {
463
+ const observerPromises: Promise<TagManagerParameterObserverResultBag>[] = [];
384
464
  for (const parameter in parameters) {
385
465
  observerPromises.push(this.handleParameter(subject, parameter, parameters[parameter]));
386
466
  }
@@ -397,36 +477,61 @@ export class TagManager {
397
477
  subject: TagManagerSubject,
398
478
  parameter: string,
399
479
  parameterValue: ParameterValue
400
- ): Promise<ParameterObserverResult> {
480
+ ): Promise<TagManagerParameterObserverResultBag> {
401
481
  Parameter.assertParameter(Parameter.declarations, parameter, parameterValue);
402
- if (!this.parameterObservers.has(parameter)) {
403
- return Promise.resolve(false);
404
- }
405
482
  const parameterBag = this.parameters.get(subject) || {};
406
483
  const oldValue = parameterBag[parameter];
407
- if (oldValue === parameterValue) {
408
- return Promise.resolve(false);
484
+ const parameterObserverResultBag: TagManagerParameterObserverResultBag = {
485
+ subject: subject,
486
+ parameter: parameter,
487
+ nodes: [],
488
+ materials: [],
489
+ oldValue: oldValue,
490
+ newValue: parameterValue,
491
+ parameterObserverResult: false,
492
+ };
493
+ if (!this.parameterObservers.has(parameter)) {
494
+ return Promise.resolve(parameterObserverResultBag);
409
495
  }
410
496
  this.parameters.set(subject, parameterBag);
411
497
  this.parameters.get(subject)![parameter] = parameterValue;
412
- const nodes = this.getNodesBySubject(subject);
413
- if (nodes.length === 0) {
414
- return Promise.resolve(false);
498
+ // get objects by subject and filter nodes by state
499
+ const nodes = this.getNodesBySubject(subject).filter(
500
+ o => o.metadata?.tagManager?.parameters?.[parameter] !== parameterValue
501
+ );
502
+ const materials = this.getMaterialsBySubject(subject).filter(
503
+ o => o.metadata?.tagManager?.parameters?.[parameter] !== parameterValue
504
+ );
505
+ if (nodes.length === 0 && materials.length === 0) {
506
+ return Promise.resolve(parameterObserverResultBag);
415
507
  }
416
- 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;
508
+ // store state in objects
509
+ for (const object of [...nodes, ...materials]) {
510
+ injectMetadata(object, { tagManager: { parameters: { [parameter]: parameterValue } } }, false);
421
511
  }
422
- emitter.emit(Event.TAG_MANAGER_PARAMETER_COMMITTED, {
512
+
513
+ const observer = this.parameterObservers.get(parameter)!;
514
+ const result = await observer({
423
515
  subject: subject,
424
- parameter: parameter,
425
516
  nodes: nodes,
426
- oldValue: oldValue,
517
+ materials: materials,
427
518
  newValue: parameterValue,
428
- result: result,
519
+ oldValue: oldValue,
429
520
  });
521
+
522
+ if (result === false) {
523
+ // reset to old value if observer was not successful
524
+ this.parameters.get(subject)![parameter] = oldValue;
525
+ // store old state in objects
526
+ for (const object of [...nodes, ...materials]) {
527
+ injectMetadata(object, { tagManager: { parameters: { [parameter]: oldValue } } }, false);
528
+ }
529
+ }
530
+ parameterObserverResultBag.nodes = nodes;
531
+ parameterObserverResultBag.materials = materials;
532
+ parameterObserverResultBag.parameterObserverResult = result;
533
+ emitter.emit(Event.TAG_MANAGER_PARAMETER_COMMITTED, parameterObserverResultBag);
534
+ return parameterObserverResultBag;
430
535
  }
431
536
 
432
537
  /**
@@ -441,17 +546,14 @@ export class TagManager {
441
546
  if (oldNode) {
442
547
  accNodeMapping[oldNode.name] = curNode.name;
443
548
  }
444
-
445
549
  return accNodeMapping;
446
550
  }, {} as TagMapping);
447
-
448
551
  for (const subject of this.parameters.keys()) {
449
552
  const isNode = subject.nodeName;
450
553
  const newName = isNode ? nodeMapping[subject.nodeName] : tagMapping[subject.tagName];
451
554
  if (!newName) {
452
555
  continue;
453
556
  }
454
-
455
557
  const newSubject = clone(subject);
456
558
  newSubject[isNode ? 'nodeName' : 'tagName'] = newName;
457
559
  const parameterBag = clone(this.parameters.get(subject)!);
@@ -459,19 +561,4 @@ export class TagManager {
459
561
  this.parameters.set(newSubject, parameterBag);
460
562
  }
461
563
  }
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
564
  }
@@ -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
  }