@combeenation/3d-viewer 16.0.0-alpha1 → 16.0.0-beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,43 @@
1
1
  import { IAssetContainer, Material, Mesh, MeshBuilder, Tuple, Vector3, ViewerError, ViewerErrorIds } from '..';
2
2
 
3
+ export const DEFAULT_DECAL_CONFIG = {
4
+ normal: [0, 0, 1],
5
+ angle: 0,
6
+ offset: 0.001,
7
+ scaling: [1, 1, 1],
8
+ captureUVS: false,
9
+ cullBackFaces: true,
10
+ localMode: true,
11
+ sideOrientation: Material.ClockWiseSideOrientation,
12
+ };
13
+
3
14
  export type DecalConfiguration = {
4
15
  // basic settings for decal creation
5
16
  name: string;
6
17
  sourceMeshName: string;
7
18
  position: Tuple<number, 3>;
8
- normal: Tuple<number, 3>;
9
19
  size: Tuple<number, 3>;
10
- angle: number;
11
20
 
12
- /** Shifts decals away from source mesh in direction of "normal" vector */
13
- offset: number;
21
+ /** Default `[0, 0, 1]` (forward direction) */
22
+ normal?: Tuple<number, 3>;
23
+
24
+ /** Default `0` */
25
+ angle?: number;
26
+
27
+ /**
28
+ * Shifts decals away from source mesh in direction of "normal" vector
29
+ *
30
+ * Default `0.001` (1mm)
31
+ */
32
+ offset?: number;
33
+
34
+ /**
35
+ * Can be used for shifting the decal away from the source mesh as alternative to `offset`.\
36
+ * Use this setting when the offset should be applied in radial direction (e.g. cylinders)
37
+ *
38
+ * Default `[1, 1, 1]` (no offset through scaling)
39
+ */
40
+ scaling?: Tuple<number, 3>;
14
41
 
15
42
  /**
16
43
  * `true`: Use UV mapping from source mesh\
@@ -60,19 +87,19 @@ export function createDecalMesh(config: DecalConfiguration, assetContainer: IAss
60
87
  }
61
88
 
62
89
  const position = Vector3.FromArray(config.position);
63
- const normal = Vector3.FromArray(config.normal).normalizeToNew();
90
+ const normal = Vector3.FromArray(config.normal ?? DEFAULT_DECAL_CONFIG.normal).normalizeToNew();
64
91
 
65
- const localMode = config.localMode ?? true;
92
+ const localMode = config.localMode ?? DEFAULT_DECAL_CONFIG.localMode;
66
93
  const worldNormal = localMode ? Vector3.TransformNormal(normal, sourceMesh.getWorldMatrix()) : normal;
67
94
 
68
95
  const decalMesh = MeshBuilder.CreateDecal(config.name, sourceMesh, {
69
96
  position: position,
70
97
  normal: normal,
71
98
  size: Vector3.FromArray(config.size),
72
- angle: config.angle,
73
- captureUVS: config.captureUVS ?? false,
74
- cullBackFaces: config.cullBackFaces ?? true,
75
- localMode: config.localMode ?? true,
99
+ angle: config.angle ?? DEFAULT_DECAL_CONFIG.angle,
100
+ captureUVS: config.captureUVS ?? DEFAULT_DECAL_CONFIG.captureUVS,
101
+ cullBackFaces: config.cullBackFaces ?? DEFAULT_DECAL_CONFIG.cullBackFaces,
102
+ localMode: config.localMode ?? DEFAULT_DECAL_CONFIG.localMode,
76
103
  });
77
104
 
78
105
  if (!addToScene) {
@@ -84,12 +111,15 @@ export function createDecalMesh(config: DecalConfiguration, assetContainer: IAss
84
111
  decalMesh._parentContainer = assetContainer;
85
112
  assetContainer.meshes.push(decalMesh);
86
113
 
87
- decalMesh.sideOrientation = config.sideOrientation ?? Material.ClockWiseSideOrientation;
114
+ decalMesh.sideOrientation = config.sideOrientation ?? DEFAULT_DECAL_CONFIG.sideOrientation;
88
115
 
89
116
  // move decal away from mesh to avoid clipping
90
117
  // NOTE: zOffset of material can't be used, since it's not supported in glTF and therefore not usable in AR
91
- const offsetVector = worldNormal.scale(config.offset);
118
+ const offsetVector = worldNormal.scale(config.offset ?? DEFAULT_DECAL_CONFIG.offset);
92
119
  decalMesh.position.addInPlace(offsetVector);
93
120
 
121
+ // apply scaling, this is an alternative approach for creating an offset, which is preferred for round surfaces
122
+ decalMesh.scaling = Vector3.FromArray(config.scaling ?? DEFAULT_DECAL_CONFIG.scaling);
123
+
94
124
  return decalMesh;
95
125
  }
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  AssetContainer,
3
- DecalConfiguration,
3
+ ExtendedAssetContainer,
4
4
  ISceneLoaderPlugin,
5
5
  InstancedMesh,
6
+ ParsedDecalConfiguration,
6
7
  SceneLoader,
7
8
  ViewerError,
8
9
  ViewerErrorIds,
@@ -10,20 +11,7 @@ import {
10
11
  } from '../index';
11
12
  import { setInternalMetadataValue } from './metadata-helper';
12
13
  import { deleteAllTags, getTags, setTagsAsString } from './tags-helper';
13
- import { isArray, isNumber, isString } from 'lodash-es';
14
-
15
- /**
16
- * Contains cbn custom data, like decals.
17
- * This is just a temporary type, as the `loadAssetContainer` function only returns an asset container, which can be
18
- * altered by our file loader plugin.
19
- * After loading the model, `cbnData` is cropped and a pure asset container is available for further processing.
20
- */
21
- export class ExtendedAssetContainer extends AssetContainer {
22
- cbnData?: CbnBabylonFileData;
23
- }
24
-
25
- export type CbnBabylonFileData = { decals?: ParsedDecalConfiguration[] };
26
- export type ParsedDecalConfiguration = DecalConfiguration & { materialId?: string };
14
+ import { isArray, isString } from 'lodash-es';
27
15
 
28
16
  type DataWithMeshes = { meshes: unknown[] };
29
17
  type DataWithDecalConfigurations = { cbnData: { decals: unknown[] } };
@@ -39,6 +27,8 @@ type InstanceData = {
39
27
  tags?: string;
40
28
  };
41
29
 
30
+ let customLoader: ISceneLoaderPlugin;
31
+
42
32
  /**
43
33
  * Create and return a custom loader plugin to be registered with SceneLoader, that allows
44
34
  * us to run our own code against the input data before using the standard procedure to
@@ -51,12 +41,18 @@ type InstanceData = {
51
41
  * editor area
52
42
  */
53
43
  export function registerCustomCbnBabylonLoaderPlugin(): void {
44
+ if (customLoader) {
45
+ // create the custom loader only once, otherwise receiving the current .babylon plugin would return the custom
46
+ // loader, resulting in multiple calls of the plugin (e.g. decals will be created multiple times)
47
+ return;
48
+ }
49
+
54
50
  // get original plugin for babylon files
55
51
  // we only want to manipulate Combeenation 3d assets, which are represented as babylon files
56
52
  // the plugin is not used for GLB files, "local" babylon are also not really affected by this plugin
57
53
  const previousLoaderPlugin = SceneLoader.GetPluginForExtension('.babylon') as ISceneLoaderPlugin;
58
54
 
59
- const customLoader: ISceneLoaderPlugin = {
55
+ customLoader = {
60
56
  name: 'cbnCustomBabylonLoader',
61
57
  extensions: '.babylon',
62
58
  importMesh: previousLoaderPlugin.importMesh,
@@ -104,13 +100,7 @@ function _isMeshInstanceData(data: any): data is InstanceData {
104
100
 
105
101
  function _isDecalConfiguration(data: any): data is ParsedDecalConfiguration {
106
102
  const hasValidProps =
107
- isString(data.name) &&
108
- isString(data.sourceMeshName) &&
109
- isArray(data.position) &&
110
- isArray(data.normal) &&
111
- isArray(data.size) &&
112
- isNumber(data.angle) &&
113
- isNumber(data.offset);
103
+ isString(data.name) && isString(data.sourceMeshName) && isArray(data.position) && isArray(data.size);
114
104
 
115
105
  if (!hasValidProps) {
116
106
  throw new ViewerError({
@@ -200,13 +190,17 @@ function _createDecals(dataParsed: unknown, container: AssetContainer): void {
200
190
  const validatedDecals = dataParsed.cbnData.decals.filter(_isDecalConfiguration);
201
191
 
202
192
  validatedDecals.forEach(decalConfig => {
203
- const { materialId, ...decalMeshConfig } = decalConfig;
193
+ const { materialId, tags, ...decalMeshConfig } = decalConfig;
204
194
  const decalMesh = createDecalMesh(decalMeshConfig, container, false);
205
195
 
206
- // optionally set material directly
196
+ // optionally set material and tags directly
207
197
  if (materialId) {
208
198
  window.Cbn?.Assets.assertMaterialExists(materialId);
209
199
  setInternalMetadataValue(decalMesh, 'deferredMaterial', materialId);
210
200
  }
201
+
202
+ if (tags) {
203
+ setTagsAsString(decalMesh, tags);
204
+ }
211
205
  });
212
206
  }
@@ -47,7 +47,7 @@ export class MaterialManager {
47
47
 
48
48
  // there may have been later "setMaterialOnMesh" calls with faster material creation
49
49
  // make sure to assign the latest material!
50
- if (getInternalMetadataValue(mesh, 'materialToBeSet') === materialId) {
50
+ if (material && getInternalMetadataValue(mesh, 'materialToBeSet') === materialId) {
51
51
  mesh.material = material;
52
52
  clearInternalMetadataValue(mesh, 'materialToBeSet');
53
53
  clearInternalMetadataValue(mesh, 'deferredMaterial');
@@ -65,7 +65,7 @@ export class MaterialManager {
65
65
  * @param mesh Required for shader compilation check, can be omitted if this check should not be done.\
66
66
  * Use {@link setMaterialOnMesh} instead if the material should be applied on the mesh immediately.
67
67
  */
68
- public async getOrCreateMaterial(materialId: string, mesh?: AbstractMesh): Promise<Material> {
68
+ public async getOrCreateMaterial(materialId: string, mesh?: AbstractMesh): Promise<Material | null> {
69
69
  let chosenMaterial: Material | null = this.viewer.scene.materials.find(mat => mat.id === materialId) ?? null;
70
70
 
71
71
  if (!chosenMaterial) {
@@ -78,7 +78,7 @@ export class MaterialManager {
78
78
  this.viewer.eventManager.fireEvent(ViewerEvent.MaterialCreationStart, materialId);
79
79
 
80
80
  // request not pending, call the dedicated function
81
- const newCreationProm = this._createMaterial(materialId, mesh);
81
+ const newCreationProm = this._createFromMaterialAsset(materialId, mesh);
82
82
  // store the promise in a global map, so that subsequent requests can reference it
83
83
  this._createMaterialPromises[materialId] = newCreationProm;
84
84
  chosenMaterial = await newCreationProm;
@@ -93,7 +93,7 @@ export class MaterialManager {
93
93
  }
94
94
  }
95
95
 
96
- return chosenMaterial as Material;
96
+ return chosenMaterial;
97
97
  }
98
98
 
99
99
  /**
@@ -105,7 +105,7 @@ export class MaterialManager {
105
105
  materialId: string,
106
106
  newMaterialId: string,
107
107
  options?: MaterialCloneOptions
108
- ): Promise<Material> {
108
+ ): Promise<Material | null> {
109
109
  const existingMaterial = this._clonedMaterials[newMaterialId];
110
110
  if (existingMaterial) {
111
111
  throw new ViewerError({
@@ -115,6 +115,10 @@ export class MaterialManager {
115
115
  }
116
116
 
117
117
  const sourceMaterial = await this.getOrCreateMaterial(materialId);
118
+ if (!sourceMaterial) {
119
+ return null;
120
+ }
121
+
118
122
  const clonedMaterial = CloningHelper.cloneMaterial(sourceMaterial, newMaterialId, options?.tagNamingStrategy);
119
123
 
120
124
  await this.viewer.parameterManager.applyParameterValuesToMaterial(clonedMaterial);
@@ -151,7 +155,7 @@ export class MaterialManager {
151
155
  Object.keys(this._clonedMaterials).forEach(materialId => this.deleteClonedMaterial(materialId));
152
156
  }
153
157
 
154
- protected async _createMaterial(materialId: string, mesh?: AbstractMesh): Promise<Material> {
158
+ protected async _createFromMaterialAsset(materialId: string, mesh?: AbstractMesh): Promise<Material | null> {
155
159
  if (materialId === MaterialManager.CBN_FALLBACK_MATERIAL_NAME) {
156
160
  const fallbackMaterial = new StandardMaterial(MaterialManager.CBN_FALLBACK_MATERIAL_NAME, this.viewer.scene);
157
161
  fallbackMaterial.disableLighting = true;
@@ -159,17 +163,29 @@ export class MaterialManager {
159
163
  return fallbackMaterial;
160
164
  }
161
165
 
162
- const materialDefinition = await window.Cbn?.Assets.getMaterial(materialId);
166
+ if (!window.Cbn) {
167
+ return null;
168
+ }
169
+
170
+ const materialDefinition = await window.Cbn.Assets.getMaterial(materialId);
171
+
172
+ if (!materialDefinition) {
173
+ console.warn(
174
+ `Trying to create material "${materialId}" from Combeenation asset but according asset does not exist or is not connected to the configurator.`
175
+ );
176
+ return null;
177
+ }
178
+
163
179
  // The generic `Material.Parse` actually returns a more specific material like `BABYLON.StandardMaterial`,
164
180
  // `BABYLON.PBRMaterial` or stuff like `BABYLON.PBRMetallicRoughnessMaterial` etc. based on the given `customType`
165
181
  // within the material JSON definition
166
- const material = materialDefinition && Material.Parse(materialDefinition, this.viewer.scene, '');
182
+ const material = Material.Parse(materialDefinition, this.viewer.scene, '');
167
183
 
168
184
  if (!material) {
169
- throw new ViewerError({
170
- id: ViewerErrorIds.MaterialCouldNotBeParsed,
171
- message: `Material with id "${materialId}" could not be parsed`,
172
- });
185
+ console.warn(
186
+ `Failed to create material "${materialId}" from Combeenation asset. Seems like the material could not be parsed.`
187
+ );
188
+ return null;
173
189
  }
174
190
 
175
191
  await this.viewer.parameterManager.applyParameterValuesToMaterial(material);
@@ -11,15 +11,25 @@ import {
11
11
  ViewerError,
12
12
  ViewerErrorIds,
13
13
  } from '../index';
14
- import {
15
- CbnBabylonFileData,
16
- ExtendedAssetContainer,
17
- ParsedDecalConfiguration,
18
- } from '../internal/cbn-custom-babylon-loader-plugin';
19
14
  import { cloneModelAssetContainer } from '../internal/cloning-helper';
20
15
  import { getInternalMetadataValue } from '../internal/metadata-helper';
21
16
  import { isArray } from 'lodash-es';
22
17
 
18
+ /**
19
+ * Contains cbn custom data, like decals.
20
+ * This is just a temporary type, as the `loadAssetContainer` function only returns an asset container, which can be
21
+ * altered by our file loader plugin.
22
+ * After loading the model, `cbnData` is cropped and a pure asset container is available for further processing.
23
+ *
24
+ * @internal
25
+ */
26
+ export class ExtendedAssetContainer extends AssetContainer {
27
+ cbnData?: CbnBabylonFileData;
28
+ }
29
+
30
+ type CbnBabylonFileData = { decals?: ParsedDecalConfiguration[] };
31
+ export type ParsedDecalConfiguration = DecalConfiguration & { materialId?: string; tags?: string };
32
+
23
33
  export type ModelAssetDefinition = {
24
34
  name: string;
25
35
  url: string;
@@ -372,7 +382,7 @@ export class ModelManager {
372
382
  * Decals are already converted to "normal" meshes when loading a model, still the original decals configuration can
373
383
  * be useful e.g. for alterning decals.
374
384
  */
375
- public async getDecalsConfigurationOfModel(name: string): Promise<DecalConfiguration[]> {
385
+ public async getDecalsConfigurationOfModel(name: string): Promise<ParsedDecalConfiguration[]> {
376
386
  const model = this._getModel(name);
377
387
  if (!model) {
378
388
  throw new ViewerError({
@@ -391,16 +401,7 @@ export class ModelManager {
391
401
  await this._prepareModelForScene(model);
392
402
  }
393
403
 
394
- // crop materialId as this is not part of `DecalConfiguration`
395
- const decals = ((model.cbnBabylonFileData?.decals ?? []) as ParsedDecalConfiguration[]).map<DecalConfiguration>(
396
- parsedDecal => {
397
- const croppedDecal = parsedDecal;
398
- delete croppedDecal.materialId;
399
- return croppedDecal;
400
- }
401
- );
402
-
403
- return decals;
404
+ return model.cbnBabylonFileData?.decals ?? [];
404
405
  }
405
406
 
406
407
  /**
@@ -377,6 +377,13 @@ export class ParameterManager {
377
377
  const materialParamEntries = this._getEntriesOfSubject({ materialName: material.id });
378
378
  parameterEntriesToApply.push(...materialParamEntries);
379
379
 
380
+ // also get parameter entries that have this material set as value, this is the case for node <=> material
381
+ // assignment parameter (`BuiltInParameter.Material`)
382
+ const materialAssignmentParamEntries = this._parameterEntries.filter(
383
+ entry => entry.parameterName === BuiltInParameter.Material && entry.value === material.id
384
+ );
385
+ parameterEntriesToApply.push(...materialAssignmentParamEntries);
386
+
380
387
  await this._applyParameterValues(parameterEntriesToApply);
381
388
  }
382
389
 
@@ -25,7 +25,6 @@ export const ViewerErrorIds = {
25
25
  ModelAlreadyExists: 'ModelAlreadyExists',
26
26
  ModelIsNotAClone: 'ModelIsNotAClone',
27
27
  AssetLoadingFailed: 'AssetLoadingFailed',
28
- MaterialCouldNotBeParsed: 'MaterialCouldNotBeParsed',
29
28
  TextureCouldNotBeParsed: 'TextureCouldNotBeParsed',
30
29
  MaterialAlreadyExists: 'MaterialAlreadyExists',
31
30
  NotAClonedMaterial: 'NotAClonedMaterial',