@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
@@ -14,6 +14,8 @@ import { Light } from '@babylonjs/core/Lights/light';
14
14
  import { PBRMaterial } from '@babylonjs/core/Materials/PBR/pbrMaterial';
15
15
  import { BaseTexture } from '@babylonjs/core/Materials/Textures/baseTexture';
16
16
  import { CubeTexture } from '@babylonjs/core/Materials/Textures/cubeTexture';
17
+ import { DynamicTexture } from '@babylonjs/core/Materials/Textures/dynamicTexture';
18
+ import { Texture } from '@babylonjs/core/Materials/Textures/texture';
17
19
  import { Material } from '@babylonjs/core/Materials/material';
18
20
  import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
19
21
  import { Axis } from '@babylonjs/core/Maths/math.axis';
@@ -103,7 +105,7 @@ const cloneTransformNode = function (
103
105
  if (clone) {
104
106
  clone.id = newName;
105
107
  clone.metadata = cloneDeep(node.metadata);
106
- injectNodeMetadata(clone, { cloneSource: node }, false);
108
+ injectMetadata(clone, { cloneSource: node }, false);
107
109
  // if the cloned node is of type InstancedMesh, due to a bug(?),
108
110
  // the InstancedMesh.isEnabled state may have changed after cloning.
109
111
  // in that case, set the clone's enabled state to the original's state
@@ -166,15 +168,15 @@ const cloneTransformNodeMaterial = function (
166
168
  };
167
169
 
168
170
  /**
169
- * @param node
171
+ * @param object
170
172
  * @param deep
171
173
  * @param metadata
172
174
  */
173
- const injectNodeMetadata = function (node: Node, metadata: {}, deep: boolean = true) {
174
- node.metadata = merge({}, node.metadata, metadata);
175
- if (deep && node instanceof TransformNode) {
176
- const children = node.getChildTransformNodes(true);
177
- children.forEach(child => injectNodeMetadata(child, metadata, deep));
175
+ const injectMetadata = function (object: Node | Material, metadata: {}, deep: boolean = true) {
176
+ object.metadata = merge({}, object.metadata, metadata);
177
+ if (deep && object instanceof TransformNode) {
178
+ const children = object.getChildTransformNodes(true);
179
+ children.forEach(child => injectMetadata(child, metadata, deep));
178
180
  }
179
181
  };
180
182
 
@@ -277,13 +279,13 @@ const disableNodeWithParents = function (node: Node) {
277
279
  const transformTransformNode = function (node: TransformNode, transformation: TransformationDefinition) {
278
280
  // scaling
279
281
  if (!has(node.metadata, 'scaling.initial')) {
280
- injectNodeMetadata(node, { 'scaling.initial': node.scaling }, false);
282
+ injectMetadata(node, { 'scaling.initial': node.scaling }, false);
281
283
  }
282
284
  const initialScaling = get(node.metadata, 'scaling.initial') as Vector3;
283
285
  node.scaling = initialScaling.multiply(transformation.scaling);
284
286
  // position
285
287
  if (!has(node.metadata, 'position.initial')) {
286
- injectNodeMetadata(node, { 'position.initial': node.absolutePosition.clone() }, false);
288
+ injectMetadata(node, { 'position.initial': node.absolutePosition.clone() }, false);
287
289
  }
288
290
  const initialPosition = get(node.metadata, 'position.initial') as Vector3;
289
291
  node.setAbsolutePosition(initialPosition.add(transformation.position).multiply(transformation.scaling));
@@ -293,7 +295,7 @@ const transformTransformNode = function (node: TransformNode, transformation: Tr
293
295
  if (!rotationQuaternion) {
294
296
  rotationQuaternion = Quaternion.RotationYawPitchRoll(node.rotation.x, node.rotation.y, node.rotation.z);
295
297
  }
296
- injectNodeMetadata(node, { 'rotation.initial': rotationQuaternion.asArray() }, false);
298
+ injectMetadata(node, { 'rotation.initial': rotationQuaternion.asArray() }, false);
297
299
  }
298
300
  const initialRotationQuaternion = Quaternion.FromArray(get(node.metadata, 'rotation.initial') as []);
299
301
  node.rotationQuaternion = initialRotationQuaternion;
@@ -409,7 +411,7 @@ const setMaterial = function (node: TransformNode, materialId: string, deep: boo
409
411
  const deferMaterialCreation = !materialExists && !node.isEnabled();
410
412
  if (deferMaterialCreation) {
411
413
  // do not set the material
412
- injectNodeMetadata(node, { [missingMaterialMetadataName]: materialId }, false);
414
+ injectMetadata(node, { [missingMaterialMetadataName]: materialId }, false);
413
415
  // if it already had the missing material flag before, there already exists an observer...
414
416
  if (!hasMissingMaterial) {
415
417
  addMissingMaterialObserver(node);
@@ -459,7 +461,7 @@ const getOrCreateMaterial = async function (scene: Scene, materialId: string, va
459
461
  */
460
462
  const applyMaterial = async function (material: Material, node: AbstractMesh) {
461
463
  // set current material id as last valid id, in this case all previously set materials on the node will be invalidated
462
- injectNodeMetadata(node, { [materialWaitingToBeSetMetadataName]: material.id }, false);
464
+ injectMetadata(node, { [materialWaitingToBeSetMetadataName]: material.id }, false);
463
465
 
464
466
  const promTexturesReady = new Promise<void>(resolve =>
465
467
  BaseTexture.WhenAllReady(material.getActiveTextures(), resolve)
@@ -783,6 +785,50 @@ const reportDuplicateNodeNames = function (nodeNames: string[]): void {
783
785
  }
784
786
  };
785
787
 
788
+ const drawPaintableOnMaterial = function (
789
+ material: Material,
790
+ imageSource: CanvasImageSource,
791
+ scene: Scene,
792
+ options?: PaintableOptions
793
+ ) {
794
+ // always take width and height from image source, scaling is done with uvScale properties
795
+ const widthAndHeight = {
796
+ width: imageSource.width as number,
797
+ height: imageSource.height as number,
798
+ };
799
+
800
+ const texture = new DynamicTexture(`${material.id}.paintable_texture`, widthAndHeight, scene);
801
+
802
+ // draw image on texture
803
+ const ctx = texture.getContext();
804
+ ctx.drawImage(imageSource, 0, 0);
805
+ texture.update();
806
+
807
+ // apply settings from paintable options to tweak position and scaling of image on the texture
808
+ texture.uScale = options?.uScale ?? texture.uScale;
809
+ texture.vScale = options?.vScale ?? texture.vScale;
810
+ texture.uOffset = options?.uOffset ?? texture.uOffset;
811
+ texture.vOffset = options?.vOffset ?? texture.vOffset;
812
+
813
+ // wrap mode is preferred, as it will always show the texture, no matter which position offset is currently chosen
814
+ // clamp mode requires more knowledge (and patience) when adjusting the uv scale and offset values
815
+ texture.wrapU = Texture.WRAP_ADDRESSMODE;
816
+ texture.wrapV = Texture.WRAP_ADDRESSMODE;
817
+
818
+ // apply the paintable texture on the dedicated material type
819
+ const materialCls = material.getClassName();
820
+ switch (materialCls) {
821
+ case 'PBRMaterial':
822
+ (material as PBRMaterial).albedoTexture = texture;
823
+ break;
824
+ case 'StandardMaterial':
825
+ (material as StandardMaterial).diffuseTexture = texture;
826
+ break;
827
+ default:
828
+ throw new Error(`Setting paintable texture for material of instance "${materialCls}" not implemented (yet).`);
829
+ }
830
+ };
831
+
786
832
  export {
787
833
  getRootNode,
788
834
  isTextureWithOnLoadObservable,
@@ -793,7 +839,7 @@ export {
793
839
  cloneTransformNodeMaterial,
794
840
  getOrCreateMaterial,
795
841
  applyMaterial,
796
- injectNodeMetadata,
842
+ injectMetadata,
797
843
  assertTransformNode,
798
844
  assertMeshCapability,
799
845
  activateTransformNode,
@@ -820,4 +866,5 @@ export {
820
866
  intersectingNodeNames,
821
867
  duplicateNodeNames,
822
868
  reportDuplicateNodeNames,
869
+ drawPaintableOnMaterial,
823
870
  };
@@ -82,6 +82,18 @@ type PaintableDefinitions = {
82
82
  [name: string]: PaintableDefinition;
83
83
  };
84
84
 
85
+ type PaintableValue = {
86
+ src: string;
87
+ options?: PaintableOptions;
88
+ };
89
+
90
+ type PaintableOptions = {
91
+ uScale?: number;
92
+ uOffset?: number;
93
+ vScale?: number;
94
+ vOffset?: number;
95
+ };
96
+
85
97
  type Asset = {
86
98
  rootUrl: string;
87
99
  fileName: string | undefined;
@@ -280,7 +292,7 @@ type ScreenshotSettings = {
280
292
  /**
281
293
  * Use this to define geometry to be excluded from autofocus, GLB export, etc.
282
294
  */
283
- type ExcludedGeometry = Mesh | AbstractMesh | VariantInstance | Variant | VariantElement | Node | TagManagerSubject;
295
+ type ExcludedGeometry = TransformNode | VariantInstance | Variant | VariantElement | TagManagerSubject;
284
296
  type ExcludedGeometryList = ExcludedGeometry[];
285
297
 
286
298
  type AutofocusSettings = {
@@ -370,7 +382,6 @@ type VariantInstanceDefinition = {
370
382
  name?: string;
371
383
  variant: DottedPathArgument;
372
384
  parameters?: ParameterBag;
373
- tagManagerParameterValues?: TagManagerParameterValue[];
374
385
  lazy?: boolean;
375
386
  };
376
387
 
@@ -403,7 +414,7 @@ type DottedPathArgument = string | string[] | DottedPath;
403
414
  type ParameterObserverResult = boolean | void;
404
415
 
405
416
  type ParameterObserver = (
406
- payload: any,
417
+ object: any,
407
418
  oldValue: Undefinable<ParameterValue>,
408
419
  newValue: ParameterValue
409
420
  ) => Promise<ParameterObserverResult>;
@@ -487,13 +498,18 @@ type TagMapping = {
487
498
  type TagManagerSubject = {
488
499
  tagName?: string;
489
500
  nodeName?: string;
501
+ materialName?: string;
490
502
  };
491
503
 
504
+ type TagManagerParameterObserverResult = ParameterObserverResult;
505
+
492
506
  /**
493
507
  * The observer should return `false` in cases where the given value was not actually applied. E.g. when wanting to
494
508
  * apply a property on the given `node`s material which doesn't exist at the time the observer is called etc.
495
509
  */
496
- type TagManagerParameterObserver = (payload: TagManagerParameterObserverPayload) => Promise<ParameterObserverResult>;
510
+ type TagManagerParameterObserver = (
511
+ payload: TagManagerParameterObserverPayload
512
+ ) => Promise<TagManagerParameterObserverResult>;
497
513
 
498
514
  type TagManagerParameterBag = FuzzyMap<TagManagerSubject, ParameterBag>;
499
515
 
@@ -502,6 +518,7 @@ type TagManagerParameterObserverBag = Map<string, TagManagerParameterObserver>;
502
518
  type TagManagerParameterObserverPayload = {
503
519
  subject: TagManagerSubject;
504
520
  nodes: TransformNode[];
521
+ materials: Material[];
505
522
  newValue: ParameterValue;
506
523
  oldValue: Undefinable<ParameterValue>;
507
524
  };
@@ -509,10 +526,21 @@ type TagManagerParameterObserverPayload = {
509
526
  type TagManagerParameterValue = {
510
527
  tagName?: string;
511
528
  nodeName?: string;
529
+ materialName?: string;
512
530
  parameterName: string;
513
531
  value: ParameterValue;
514
532
  };
515
533
 
534
+ type TagManagerParameterObserverResultBag = {
535
+ subject: TagManagerSubject;
536
+ parameter: string;
537
+ nodes: TransformNode[];
538
+ materials: Material[];
539
+ oldValue: ParameterValue;
540
+ newValue: ParameterValue;
541
+ parameterObserverResult: TagManagerParameterObserverResult;
542
+ };
543
+
516
544
  type NodeNamingStrategyPayload = {
517
545
  variantInstance: VariantInstance;
518
546
  variant: Variant;
@@ -135,6 +135,7 @@ const createImageFromImgSrc = async function (imgSrc: string): Promise<HTMLImage
135
135
  img.onload = () => {
136
136
  setTimeout(resolve, 0);
137
137
  };
138
+ img.crossOrigin = 'anonymous';
138
139
  img.src = imgSrc;
139
140
  });
140
141
 
@@ -151,9 +152,15 @@ const _embedAssets = async function (svgSrc: string): Promise<string> {
151
152
  // Regex copied from https://stackoverflow.com/a/8943487/1273551, not "stress tested"...
152
153
  const urlRegex = /(\bhttps?:\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi;
153
154
  const allUrls = svgSrc.match(urlRegex) as string[];
154
- const assetUrls = allUrls.filter(url =>
155
- _assetExtensions.some(extension => url.toLowerCase().endsWith(`.${extension}`))
156
- );
155
+
156
+ const assetUrls = allUrls.filter(url => {
157
+ const indexParam = url.indexOf('?');
158
+ // remove url parameter to recognize extension
159
+ if (indexParam > -1) {
160
+ url = url.substring(0, indexParam);
161
+ }
162
+ return _assetExtensions.some(extension => url.toLowerCase().endsWith(`.${extension}`));
163
+ });
157
164
  const assetBase64Fetcher = assetUrls.map(_fetchBase64AssetUrl);
158
165
  const assetFetcherResults = await Promise.all(assetBase64Fetcher);
159
166
  return assetFetcherResults.reduce((svgSrc, x) => svgSrc.replace(x.url, x.base64), svgSrc);
@@ -1,5 +1,5 @@
1
1
  import { Event, emitter } from '../classes/event';
2
- import { applyMaterial, getOrCreateMaterial, injectNodeMetadata } from './babylonHelper';
2
+ import { applyMaterial, getOrCreateMaterial, injectMetadata } from './babylonHelper';
3
3
  import { sleep } from './resourceHelper';
4
4
  import { ISceneLoaderPlugin } from '@babylonjs/core/Loading/sceneLoader';
5
5
  import { Material } from '@babylonjs/core/Materials/material';
@@ -101,7 +101,7 @@ export const addMissingMaterialMetadata = function (dataParsed: any, container:
101
101
  if (!materialOnOriginalMesh || materialOnImportedMesh === materialOnOriginalMesh) continue;
102
102
  // if we're here, the imported mesh has different material than original one
103
103
  window.Cbn?.Assets.assertMaterialExists(materialOnOriginalMesh);
104
- injectNodeMetadata(currMeshImported, { [missingMaterialMetadataName]: materialOnOriginalMesh }, false);
104
+ injectMetadata(currMeshImported, { [missingMaterialMetadataName]: materialOnOriginalMesh }, false);
105
105
  break;
106
106
  }
107
107
  });
@@ -1,58 +1,60 @@
1
1
  import { Element } from '../classes/element';
2
2
  import { Variant } from '../classes/variant';
3
3
  import { VariantInstance } from '../classes/variantInstance';
4
- import { Mesh } from '@babylonjs/core/Meshes/mesh';
4
+ import { GltfExportManager } from '../manager/gltfExportManager';
5
+ import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
5
6
  import { Tags } from '@babylonjs/core/Misc/tags';
6
7
 
7
8
  /**
8
- * Find out if a mesh is part of a list of excluded geometry
9
- * @param mesh BJS mesh
9
+ * Find out if a node is part of a list of excluded geometry
10
+ * @param node BJS node
10
11
  * @param list list of excluded geometry
11
- * @returns boolean based on whether mesh (or one of its parents) was found in list
12
+ * @returns boolean based on whether node (or one of its parents) was found in list
12
13
  */
13
- const isMeshIncludedInExclusionList = function (mesh: Mesh, list: ExcludedGeometryList): boolean {
14
- const checkMesh = (inputMesh: Mesh, meshToCheck: Mesh) => {
15
- return inputMesh.uniqueId === meshToCheck.uniqueId;
14
+ const isNodeIncludedInExclusionList = function (node: TransformNode, list: ExcludedGeometryList): boolean {
15
+ const checkNode = (inputNode: TransformNode, nodeToCheck: TransformNode) => {
16
+ return inputNode.uniqueId === nodeToCheck.uniqueId;
16
17
  };
17
- const checkElement = (inputEl: Element, meshToCheck: Mesh) => {
18
- return inputEl.meshesFlat.some(m => checkMesh(m, meshToCheck));
18
+ const checkElement = (inputEl: Element, nodeToCheck: TransformNode) => {
19
+ return inputEl.nodesFlat.some(m => checkNode(m, nodeToCheck));
19
20
  };
20
- const checkVariant = (inputVariant: Variant, meshToCheck: Mesh) => {
21
- return inputVariant.elements.some(el => checkElement(el, meshToCheck));
21
+ const checkVariant = (inputVariant: Variant, nodeToCheck: TransformNode) => {
22
+ return inputVariant.elements.some(el => checkElement(el, nodeToCheck));
22
23
  };
23
- const checkVariantInstance = (inputVarInst: VariantInstance, meshToCheck: Mesh) => {
24
- return inputVarInst.variant.elements.some(el => checkElement(el, meshToCheck));
24
+ const checkVariantInstance = (inputVarInst: VariantInstance, nodeToCheck: TransformNode) => {
25
+ return inputVarInst.variant.elements.some(el => checkElement(el, nodeToCheck));
25
26
  };
26
- const checkTagManagerSubject = (inputSubject: TagManagerSubject, meshToCheck: Mesh) => {
27
- const nameMatches = inputSubject.nodeName && inputSubject.nodeName === meshToCheck.name;
28
- const tagMatches = inputSubject.tagName && Tags.MatchesQuery(mesh, inputSubject.tagName);
27
+ const checkTagManagerSubject = (inputSubject: TagManagerSubject, nodeToCheck: TransformNode) => {
28
+ const nodeName = nodeToCheck.metadata?.[GltfExportManager.NAME_BEFORE_EXPORT_METADATA_PROPERTY] ?? nodeToCheck.name;
29
+ const nameMatches = inputSubject.nodeName && inputSubject.nodeName === nodeName;
30
+ const tagMatches = inputSubject.tagName && Tags.MatchesQuery(nodeToCheck, inputSubject.tagName);
29
31
  return nameMatches || tagMatches;
30
32
  };
31
- const check = (geometryToExclude: ExcludedGeometry, mesh: Mesh) => {
33
+ const check = (geometryToExclude: ExcludedGeometry, node: TransformNode) => {
32
34
  if (geometryToExclude instanceof VariantInstance) {
33
- return checkVariantInstance(geometryToExclude, mesh);
35
+ return checkVariantInstance(geometryToExclude, node);
34
36
  }
35
37
  if (geometryToExclude instanceof Variant) {
36
- return checkVariant(geometryToExclude, mesh);
38
+ return checkVariant(geometryToExclude, node);
37
39
  }
38
40
  if (geometryToExclude instanceof Element) {
39
- return checkElement(geometryToExclude, mesh);
41
+ return checkElement(geometryToExclude, node);
40
42
  }
41
- if (geometryToExclude instanceof Mesh) {
42
- return checkMesh(geometryToExclude, mesh);
43
+ if (geometryToExclude instanceof TransformNode) {
44
+ return checkNode(geometryToExclude, node);
43
45
  }
44
46
  if ((geometryToExclude as TagManagerSubject).tagName || (geometryToExclude as TagManagerSubject).nodeName) {
45
- return checkTagManagerSubject(geometryToExclude as TagManagerSubject, mesh);
47
+ return checkTagManagerSubject(geometryToExclude as TagManagerSubject, node);
46
48
  }
47
49
  return false;
48
50
  };
49
51
 
50
- let isExcluded = list.some(geometryToExclude => check(geometryToExclude, mesh));
51
- if (!isExcluded && mesh.parent instanceof Mesh) {
52
- isExcluded = isMeshIncludedInExclusionList(mesh.parent, list);
52
+ let isExcluded = list.some(geometryToExclude => check(geometryToExclude, node));
53
+ if (!isExcluded && node.parent instanceof TransformNode) {
54
+ isExcluded = isNodeIncludedInExclusionList(node.parent, list);
53
55
  }
54
56
 
55
57
  return isExcluded;
56
58
  };
57
59
 
58
- export { isMeshIncludedInExclusionList };
60
+ export { isNodeIncludedInExclusionList };