@combeenation/3d-viewer 17.0.0 → 17.1.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 (44) hide show
  1. package/dist/lib-cjs/buildinfo.json +1 -1
  2. package/dist/lib-cjs/commonjs.tsconfig.tsbuildinfo +1 -1
  3. package/dist/lib-cjs/internal/cbn-custom-babylon-loader-plugin.js +1 -1
  4. package/dist/lib-cjs/internal/cbn-custom-babylon-loader-plugin.js.map +1 -1
  5. package/dist/lib-cjs/internal/cloning-helper.js +4 -4
  6. package/dist/lib-cjs/internal/cloning-helper.js.map +1 -1
  7. package/dist/lib-cjs/internal/geometry-helper.d.ts +1 -1
  8. package/dist/lib-cjs/internal/geometry-helper.js +1 -2
  9. package/dist/lib-cjs/internal/geometry-helper.js.map +1 -1
  10. package/dist/lib-cjs/internal/metadata-helper.js +8 -3
  11. package/dist/lib-cjs/internal/metadata-helper.js.map +1 -1
  12. package/dist/lib-cjs/internal/paintable-helper.js +4 -1
  13. package/dist/lib-cjs/internal/paintable-helper.js.map +1 -1
  14. package/dist/lib-cjs/internal/tags-helper.d.ts +3 -4
  15. package/dist/lib-cjs/internal/tags-helper.js +6 -10
  16. package/dist/lib-cjs/internal/tags-helper.js.map +1 -1
  17. package/dist/lib-cjs/internal/texture-parameter-helper.js +9 -3
  18. package/dist/lib-cjs/internal/texture-parameter-helper.js.map +1 -1
  19. package/dist/lib-cjs/manager/camera-manager.js +8 -2
  20. package/dist/lib-cjs/manager/camera-manager.js.map +1 -1
  21. package/dist/lib-cjs/manager/gltf-export-manager.d.ts +1 -2
  22. package/dist/lib-cjs/manager/gltf-export-manager.js +3 -19
  23. package/dist/lib-cjs/manager/gltf-export-manager.js.map +1 -1
  24. package/dist/lib-cjs/manager/model-manager.js +13 -1
  25. package/dist/lib-cjs/manager/model-manager.js.map +1 -1
  26. package/dist/lib-cjs/manager/parameter-manager.d.ts +13 -4
  27. package/dist/lib-cjs/manager/parameter-manager.js +97 -25
  28. package/dist/lib-cjs/manager/parameter-manager.js.map +1 -1
  29. package/dist/lib-cjs/viewer-error.d.ts +2 -1
  30. package/dist/lib-cjs/viewer-error.js +2 -1
  31. package/dist/lib-cjs/viewer-error.js.map +1 -1
  32. package/package.json +11 -11
  33. package/src/internal/cbn-custom-babylon-loader-plugin.ts +2 -2
  34. package/src/internal/cloning-helper.ts +5 -5
  35. package/src/internal/geometry-helper.ts +3 -3
  36. package/src/internal/metadata-helper.ts +8 -6
  37. package/src/internal/paintable-helper.ts +4 -1
  38. package/src/internal/tags-helper.ts +4 -8
  39. package/src/internal/texture-parameter-helper.ts +9 -3
  40. package/src/manager/camera-manager.ts +10 -2
  41. package/src/manager/gltf-export-manager.ts +8 -27
  42. package/src/manager/model-manager.ts +15 -1
  43. package/src/manager/parameter-manager.ts +108 -29
  44. package/src/viewer-error.ts +2 -1
@@ -96,7 +96,7 @@ export function createBuiltInTextureParameter(parameterManager: ParameterManager
96
96
 
97
97
  if (!newTexture) {
98
98
  throw new ViewerError({
99
- id: ViewerErrorIds.TextureCouldNotBeParsed,
99
+ id: ViewerErrorIds.InvalidParameterValue,
100
100
  message: `Texture with url "${url}" could not be parsed`,
101
101
  });
102
102
  }
@@ -251,7 +251,10 @@ export function createBuiltInTextureParameter(parameterManager: ParameterManager
251
251
  for (const material of materials) {
252
252
  const materialCls = material.getClassName();
253
253
  if (materialCls !== 'PBRMaterial') {
254
- throw new Error(`Enabling detailmap for material of instance "${materialCls}" not implemented`);
254
+ throw new ViewerError({
255
+ id: ViewerErrorIds.InvalidParameterSubject,
256
+ message: `Enabling detailmap for material of instance "${materialCls}" not implemented`,
257
+ });
255
258
  }
256
259
 
257
260
  const pbrMaterial = material as PBRMaterial;
@@ -270,7 +273,10 @@ export function createBuiltInTextureParameter(parameterManager: ParameterManager
270
273
  function _assertAndConvertPBRMaterial(material: Material, channel: string, parameter: string): PBRMaterial {
271
274
  const materialCls = material.getClassName();
272
275
  if (materialCls !== 'PBRMaterial') {
273
- throw new Error(`Setting ${channel} ${parameter} for material of instance "${materialCls}" not implemented`);
276
+ throw new ViewerError({
277
+ id: ViewerErrorIds.InvalidParameterSubject,
278
+ message: `Setting ${channel} ${parameter} for material of instance "${materialCls}" not implemented`,
279
+ });
274
280
  }
275
281
 
276
282
  return material as PBRMaterial;
@@ -8,6 +8,8 @@ import {
8
8
  ScreenshotTools,
9
9
  Vector3,
10
10
  Viewer,
11
+ ViewerError,
12
+ ViewerErrorIds,
11
13
  ViewerEvent,
12
14
  } from '../index';
13
15
  import { isNodeExcluded } from '../internal/geometry-helper';
@@ -122,7 +124,10 @@ export class CameraManager {
122
124
  const activeCamera = this.viewer.scene.activeCamera;
123
125
 
124
126
  if (!(activeCamera instanceof ArcRotateCamera)) {
125
- throw new Error(`Camera type "${activeCamera?.getClassName()}" is not implemented for "autofocusActiveCamera".`);
127
+ throw new ViewerError({
128
+ id: ViewerErrorIds.InvalidCameraType,
129
+ message: `"autofocusActiveCamera" function is not implemented for camera type "${activeCamera?.getClassName()}"`,
130
+ });
126
131
  }
127
132
 
128
133
  // get bounding box of all visible meshes, this is the base for the autofocus algorithm
@@ -176,7 +181,10 @@ export class CameraManager {
176
181
  const activeCamera = this.viewer.scene.activeCamera;
177
182
 
178
183
  if (!(activeCamera instanceof ArcRotateCamera)) {
179
- throw new Error(`Camera type "${activeCamera?.getClassName()}" is not implemented yet for "moveActiveCameraTo".`);
184
+ throw new ViewerError({
185
+ id: ViewerErrorIds.InvalidCameraType,
186
+ message: `"moveActiveCameraTo" function is not implemented for camera type "${activeCamera?.getClassName()}"`,
187
+ });
180
188
  }
181
189
 
182
190
  if (durationMs === 0) {
@@ -11,7 +11,6 @@ import {
11
11
  PBRMaterial,
12
12
  RenderTargetTexture,
13
13
  TransformNode,
14
- Vector3,
15
14
  Viewer,
16
15
  } from '../index';
17
16
  import { getIsScaledDownDevice } from '../internal/device-helper';
@@ -28,8 +27,6 @@ import {
28
27
  * Manager for gltf export and augmented reality features
29
28
  */
30
29
  export class GltfExportManager {
31
- protected static readonly _EXPORT_ROOT_NAME = '__export_root__';
32
-
33
30
  protected _maxTextureSize: number;
34
31
 
35
32
  /**
@@ -234,24 +231,11 @@ export class GltfExportManager {
234
231
  .forEach(material => GltfExportManager._exchangeMaterial(material as PBRMaterial));
235
232
  }
236
233
 
237
- // since Babylon.js v6 the conversion to right handed GLB coordinate system is done differently
238
- // previously the geometry itself has been altered, now they negate the scaling of all root nodes
239
- // this is an issue for us, since we will receive this negated scalings in the exported GLB, which leads to issues
240
- // on iOS devices
241
- // we fix that by adding a top level root node with a negative scaling as well
242
- // the exporter just removes this node as he detects a "noop root node" (implementation details of Babylon.js)
243
- // everything beneath this node remains untouched
244
- // TODO BJS Update: Test AR export on iPhones as well and double check if we still need this special logic
245
- const exportRootNode = new TransformNode(GltfExportManager._EXPORT_ROOT_NAME, this.viewer.scene);
246
- exportRootNode.scaling = new Vector3(-1, 1, 1);
247
- setInternalMetadataValue(exportRootNode, 'exportNode', true);
248
- setInternalMetadataValue(exportRootNode, 'deleteAfterExport', true);
249
-
250
234
  // create clones of each node (recursively), optionally exchange with cloned materials and mark these nodes for the
251
235
  // export
252
236
  this.viewer.scene.rootNodes
253
- .filter(rootNode => rootNode.name !== GltfExportManager._EXPORT_ROOT_NAME)
254
- .forEach(rootNode => this._prepareNodeForExport(rootNode, exportRootNode, excluded));
237
+ // .filter(rootNode => rootNode.name !== GltfExportManager._EXPORT_ROOT_NAME)
238
+ .forEach(rootNode => this._prepareNodeForExport(rootNode, null, excluded));
255
239
 
256
240
  // bake transformation of all meshes, so that no negative scalings are left
257
241
  // it's important that this is done AFTER instanced meshes have been converted (_prepareNodeForExport)
@@ -295,7 +279,11 @@ export class GltfExportManager {
295
279
  * Creates a clone of the node which should be used for the export.
296
280
  * Also switches to the cloned material if required.
297
281
  */
298
- protected _prepareNodeForExport(node: Node, clonedParent: TransformNode, excluded?: ExcludedGeometryList): void {
282
+ protected _prepareNodeForExport(
283
+ node: Node,
284
+ clonedParent: TransformNode | null,
285
+ excluded?: ExcludedGeometryList
286
+ ): void {
299
287
  if (!GltfExportManager._shouldExportNode(node, excluded)) {
300
288
  return;
301
289
  }
@@ -338,14 +326,7 @@ export class GltfExportManager {
338
326
  nodes.push(...this.viewer.scene.transformNodes);
339
327
  }
340
328
 
341
- const filteredNodes = nodes.filter(
342
- node =>
343
- getInternalMetadataValue(node, 'exportNode') &&
344
- // in the desired use cases we want to exclude the export root node
345
- // maybe add a parameter if we have to include it in certain scenarios in the future
346
- node.name !== GltfExportManager._EXPORT_ROOT_NAME
347
- );
348
-
329
+ const filteredNodes = nodes.filter(node => getInternalMetadataValue(node, 'exportNode'));
349
330
  return filteredNodes;
350
331
  }
351
332
  }
@@ -426,12 +426,26 @@ export class ModelManager {
426
426
  const curEnvTexture = this.viewer.scene.environmentTexture;
427
427
  const curEnvIntensity = this.viewer.scene.environmentIntensity;
428
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
+
429
441
  let assetContainer;
430
442
  try {
431
443
  const fullContainer = (await SceneLoader.LoadAssetContainerAsync(
432
444
  '',
433
445
  model.url,
434
- this.viewer.scene
446
+ this.viewer.scene,
447
+ undefined,
448
+ pluginExtension
435
449
  )) as ExtendedAssetContainer;
436
450
 
437
451
  // crop and store custom cbn data from .babylon file
@@ -5,6 +5,7 @@ import {
5
5
  Material,
6
6
  PBRMaterial,
7
7
  StandardMaterial,
8
+ Tags,
8
9
  TransformNode,
9
10
  Vector3,
10
11
  Viewer,
@@ -13,14 +14,14 @@ import {
13
14
  } from '../index';
14
15
  import { getInternalMetadataValue, setInternalMetadataValue } from '../internal/metadata-helper';
15
16
  import { paintableParameterObserver } from '../internal/paintable-helper';
16
- import { getTags, hasTag } from '../internal/tags-helper';
17
+ import { getTagsAsStrArr } from '../internal/tags-helper';
17
18
  import {
18
19
  BuiltInTextureParameter,
19
20
  BuiltInTextureParameterKeys,
20
21
  ParameterTextureChannelsKeys,
21
22
  createBuiltInTextureParameter,
22
23
  } from '../internal/texture-parameter-helper';
23
- import { capitalize, isString } from 'lodash-es';
24
+ import { capitalize, isNumber, isString } from 'lodash-es';
24
25
 
25
26
  /**
26
27
  * Parameters with a built in observer implementation
@@ -34,6 +35,7 @@ export const BuiltInParameter = {
34
35
  Color: 'color',
35
36
  Roughness: 'roughness',
36
37
  Metallic: 'metallic',
38
+ Influence: 'influence',
37
39
  /**
38
40
  * Texture parameters are always a combination of the channel (e.g. `albedoTexture`) and a sub parameter
39
41
  * (e.g. `uScale`). Use this function to create the parameter accordingly
@@ -49,8 +51,10 @@ export const LegacyParameter = {
49
51
  Paintable: 'paintable',
50
52
  };
51
53
 
54
+ export type MorphTargetParameterValue = { name: string; value: number };
55
+
52
56
  export type ParameterName = string;
53
- export type ParameterValue = string | number | boolean;
57
+ export type ParameterValue = string | number | boolean | Vector3 | Color3 | MorphTargetParameterValue;
54
58
 
55
59
  export type TagParameterSubject = { tagName: string; nodeName?: never; materialName?: never };
56
60
  export type NodeParameterSubject = { tagName?: never; nodeName: string; materialName?: never };
@@ -133,7 +137,16 @@ export class ParameterManager {
133
137
  * Parses and converts input to a number value
134
138
  */
135
139
  public static parseNumber(value: ParameterValue): number {
136
- return parseFloat(value.toString());
140
+ const parsedNumber = parseFloat(value.toString());
141
+
142
+ if (isNaN(parsedNumber)) {
143
+ throw new ViewerError({
144
+ id: ViewerErrorIds.InvalidParameterValue,
145
+ message: `Unable to parse "${value}" to a number`,
146
+ });
147
+ }
148
+
149
+ return parsedNumber;
137
150
  }
138
151
 
139
152
  /**
@@ -143,11 +156,15 @@ export class ParameterManager {
143
156
  return value.toString();
144
157
  }
145
158
 
146
- // TODO WTT: enable setting Vector3 on the input directly
147
159
  /**
148
- * Parses a string of format "(x,y,z)"" to a "Vector3".
160
+ * Parses a string of format "(x,y,z)"" to a "Vector3".\
161
+ * Passing a Vector3 object directly is also valid.
149
162
  */
150
163
  public static parseVector(value: ParameterValue): Vector3 {
164
+ if (value instanceof Vector3) {
165
+ return value.clone();
166
+ }
167
+
151
168
  if (!isString(value)) {
152
169
  throw new ViewerError({
153
170
  id: ViewerErrorIds.InvalidParameterValue,
@@ -167,26 +184,35 @@ export class ParameterManager {
167
184
  }
168
185
  }
169
186
 
170
- // TODO WTT: enable setting Vector3 on the input directly (maybe quaternion as well)
171
187
  /**
172
- * Parses a string of format `'(x,y,z)'` with angular degrees to a `Vector3` with rotation information in radians.
188
+ * Parses a string of format `'(x,y,z)'` with angular degrees to a `Vector3` with rotation information in radians.\
189
+ * Passing a Vector3 object directly is also valid, in this case no degrees to radians conversion is made.
173
190
  */
174
191
  public static parseRotation(value: ParameterValue): Vector3 {
175
192
  const rotation = ParameterManager.parseVector(value);
176
- const deg2rad = (deg: number): number => {
177
- return (deg * Math.PI) / 180;
178
- };
179
- return rotation.set(deg2rad(rotation.x), deg2rad(rotation.y), deg2rad(rotation.z));
193
+
194
+ if (isString(value)) {
195
+ const deg2rad = (deg: number): number => {
196
+ return (deg * Math.PI) / 180;
197
+ };
198
+ return rotation.set(deg2rad(rotation.x), deg2rad(rotation.y), deg2rad(rotation.z));
199
+ } else {
200
+ return rotation;
201
+ }
180
202
  }
181
203
 
182
- // TODO WTT: enable setting Color3 on the input directly
183
204
  /**
184
- * Parses a string of format `'#rrggbb'` or `'(r,g,b)'` to a `Color3`.
205
+ * Parses a string of format `'#rrggbb'` or `'(r,g,b)'` to a `Color3`.\
206
+ * Passing a Color3 object directly is also valid.
185
207
  */
186
208
  public static parseColor(value: ParameterValue): Color3 {
209
+ if (value instanceof Color3) {
210
+ return value.clone();
211
+ }
212
+
187
213
  const cleanedValue = value.toString().split(' ').join('');
188
214
  if (cleanedValue.startsWith('#')) {
189
- return Color3.FromHexString(value.toString());
215
+ return Color3.FromHexString(cleanedValue);
190
216
  }
191
217
  if (cleanedValue.startsWith('(') && cleanedValue.endsWith(')')) {
192
218
  const rgb = cleanedValue.substring(1, cleanedValue.length - 1);
@@ -204,6 +230,31 @@ export class ParameterManager {
204
230
  });
205
231
  }
206
232
 
233
+ public static parseMorphTarget(value: ParameterValue): MorphTargetParameterValue {
234
+ let objValue;
235
+ if (isString(value)) {
236
+ try {
237
+ objValue = JSON.parse(value);
238
+ } catch {
239
+ throw new ViewerError({
240
+ id: ViewerErrorIds.InvalidParameterValue,
241
+ message: `Unable to parse "${value}" to a morph target parameter value: not a parseable string`,
242
+ });
243
+ }
244
+ } else {
245
+ objValue = value;
246
+ }
247
+
248
+ if (isString(objValue.name) && isNumber(objValue.value)) {
249
+ return { name: objValue.name, value: objValue.value };
250
+ } else {
251
+ throw new ViewerError({
252
+ id: ViewerErrorIds.InvalidParameterValue,
253
+ message: `Unable to parse "${value}" to a morph target parameter value: wrong format`,
254
+ });
255
+ }
256
+ }
257
+
207
258
  protected _parameterEntries: ParameterEntry[] = [];
208
259
  protected _parameterObserver: { [parameterName: ParameterName]: ParameterObserver } = {};
209
260
 
@@ -352,7 +403,7 @@ export class ParameterManager {
352
403
  * @internal
353
404
  */
354
405
  public async applyParameterValuesToMaterial(material: Material): Promise<void> {
355
- const tags = getTags(material);
406
+ const tags = getTagsAsStrArr(material);
356
407
 
357
408
  const parameterEntriesToApply: ParameterEntry[] = [];
358
409
  tags.forEach(tagName => {
@@ -383,7 +434,7 @@ export class ParameterManager {
383
434
  material: PBRMaterial,
384
435
  channel: ParameterTextureChannelsKeys
385
436
  ): Promise<void> {
386
- const tags = getTags(material);
437
+ const tags = getTagsAsStrArr(material);
387
438
 
388
439
  const parameterEntriesToApply: ParameterEntry[] = [];
389
440
  tags.forEach(tagName => {
@@ -419,7 +470,7 @@ export class ParameterManager {
419
470
  return nodeParamValue;
420
471
  }
421
472
 
422
- const tags = getTags(node);
473
+ const tags = getTagsAsStrArr(node);
423
474
  const tagParamValue = tags.reduce<ParameterValue | undefined>((accValue, curTag) => {
424
475
  // NOTE: it is possible that values are available for multiple tags
425
476
  // in this case the resulting parameter value is quite "random" as the last tag in the tag string of the node has
@@ -444,7 +495,7 @@ export class ParameterManager {
444
495
  return materialParamValue;
445
496
  }
446
497
 
447
- const tags = getTags(material);
498
+ const tags = getTagsAsStrArr(material);
448
499
  const tagParamValue = tags.reduce<ParameterValue | undefined>((accValue, curTag) => {
449
500
  const tagParamValue = this.getParameterValue({ tagName: curTag }, parameterName);
450
501
  return accValue ?? tagParamValue;
@@ -487,6 +538,13 @@ export class ParameterManager {
487
538
  const material = ParameterManager.parseString(newValue);
488
539
 
489
540
  for (const node of nodes) {
541
+ if (!(node instanceof AbstractMesh)) {
542
+ throw new ViewerError({
543
+ id: ViewerErrorIds.InvalidParameterSubject,
544
+ message: `Material can't be set, as the target node "${node.name}" is not a mesh`,
545
+ });
546
+ }
547
+
490
548
  // NOTE: don't use node.isEnabled() as visibility observer is probably called in same cycle but later
491
549
  // however the parameter value is already correct at this stage
492
550
  // we have to go through all parents as well, because a parent may have been set to false, which also disables
@@ -501,14 +559,10 @@ export class ParameterManager {
501
559
  curNode = curNode.parent as TransformNode;
502
560
  }
503
561
 
504
- // parameter visibility has priority, but if the visiblity is not controlled by the parameter use the plain
505
- // Babylon.js isEnabled() check
506
562
  const visible =
507
563
  visibleByParameter !== undefined ? ParameterManager.parseBoolean(visibleByParameter) : node.isEnabled();
508
564
  if (visible) {
509
- // TODO WTT: check mesh type and throw error if it doesn't fit
510
- // think of creating a framework with type guards (isMesh, canHaveMaterial, ...) around this
511
- await this.viewer.materialManager.setMaterialOnMesh(material, node as AbstractMesh);
565
+ await this.viewer.materialManager.setMaterialOnMesh(material, node);
512
566
  } else {
513
567
  setInternalMetadataValue(node, 'deferredMaterial', material);
514
568
  }
@@ -548,7 +602,10 @@ export class ParameterManager {
548
602
  (material as StandardMaterial).diffuseColor = color;
549
603
  break;
550
604
  default:
551
- throw new Error(`Setting color for material of instance "${materialCls}" not implemented`);
605
+ throw new ViewerError({
606
+ id: ViewerErrorIds.InvalidParameterSubject,
607
+ message: `Setting color for material of instance "${materialCls}" not implemented`,
608
+ });
552
609
  }
553
610
  }
554
611
  });
@@ -565,7 +622,10 @@ export class ParameterManager {
565
622
  (material as StandardMaterial).roughness = roughness;
566
623
  break;
567
624
  default:
568
- throw new Error(`Setting rougness for material of instance "${materialCls}" not implemented`);
625
+ throw new ViewerError({
626
+ id: ViewerErrorIds.InvalidParameterSubject,
627
+ message: `Setting rougness for material of instance "${materialCls}" not implemented`,
628
+ });
569
629
  }
570
630
  }
571
631
  });
@@ -579,7 +639,26 @@ export class ParameterManager {
579
639
  (material as PBRMaterial).metallic = metallic;
580
640
  break;
581
641
  default:
582
- throw new Error(`Setting metallic for material of instance "${materialCls}" not implemented`);
642
+ throw new ViewerError({
643
+ id: ViewerErrorIds.InvalidParameterSubject,
644
+ message: `Setting metallic for material of instance "${materialCls}" not implemented`,
645
+ });
646
+ }
647
+ }
648
+ });
649
+ this.setParameterObserver(BuiltInParameter.Influence, async ({ nodes, newValue }) => {
650
+ const morphTargetValue = ParameterManager.parseMorphTarget(newValue);
651
+
652
+ for (const node of nodes) {
653
+ const target = node instanceof AbstractMesh && node.morphTargetManager?.getTargetByName(morphTargetValue.name);
654
+
655
+ if (target) {
656
+ target.influence = morphTargetValue.value;
657
+ } else {
658
+ throw new ViewerError({
659
+ id: ViewerErrorIds.InvalidParameterSubject,
660
+ message: `Morph target couldn't be found, node: ${node.name} / morph target: ${morphTargetValue.name}`,
661
+ });
583
662
  }
584
663
  }
585
664
  });
@@ -679,7 +758,7 @@ export class ParameterManager {
679
758
  console.warn(`Multiple materials for material name "${subject.materialName}" have been found`);
680
759
  }
681
760
  } else if (isTagParameterSubject(subject)) {
682
- materials = assetContainer.materials.filter(material => hasTag(material, subject.tagName));
761
+ materials = assetContainer.materials.filter(material => Tags.MatchesQuery(material, subject.tagName));
683
762
  }
684
763
 
685
764
  return materials;
@@ -697,7 +776,7 @@ export class ParameterManager {
697
776
  console.warn(`Multiple nodes for node name "${subject.nodeName}" have been found`);
698
777
  }
699
778
  } else if (isTagParameterSubject(subject)) {
700
- nodes = allNodes.filter(node => hasTag(node, subject.tagName));
779
+ nodes = allNodes.filter(node => Tags.MatchesQuery(node, subject.tagName));
701
780
  }
702
781
 
703
782
  return nodes;
@@ -21,14 +21,15 @@ type ViewerErrorData = {
21
21
  */
22
22
  export const ViewerErrorIds = {
23
23
  InvalidParameterValue: 'InvalidParameterValue',
24
+ InvalidParameterSubject: 'InvalidParameterSubject',
24
25
  ModelNotRegistered: 'ModelNotRegistered',
25
26
  ModelAlreadyExists: 'ModelAlreadyExists',
26
27
  ModelIsNotAClone: 'ModelIsNotAClone',
27
28
  AssetLoadingFailed: 'AssetLoadingFailed',
28
- TextureCouldNotBeParsed: 'TextureCouldNotBeParsed',
29
29
  MaterialAlreadyExists: 'MaterialAlreadyExists',
30
30
  NotAClonedMaterial: 'NotAClonedMaterial',
31
31
  InvalidDecalConfiguration: 'InvalidDecalConfiguration',
32
+ InvalidCameraType: 'InvalidCameraType',
32
33
  };
33
34
 
34
35
  /** @internal */