@preference-sl/pref-viewer 2.11.0-beta.14 → 2.11.0-beta.16

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preference-sl/pref-viewer",
3
- "version": "2.11.0-beta.14",
3
+ "version": "2.11.0-beta.16",
4
4
  "description": "Web Component to preview GLTF models with Babylon.js",
5
5
  "author": "Alex Moreno Palacio <amoreno@preference.es>",
6
6
  "scripts": {
@@ -144,6 +144,7 @@ export default class BabylonJSAnimationController {
144
144
  */
145
145
  dispose() {
146
146
  if (this.#highlightLayer) {
147
+ this.#highlightLayer.removeAllMeshes();
147
148
  this.#highlightLayer.dispose();
148
149
  this.#highlightLayer = null;
149
150
  }
@@ -2,10 +2,15 @@ import OpeningAnimation from "./babylonjs-animation-opening.js";
2
2
  import { PrefViewer3DAnimationMenuStyles } from "./styles.js";
3
3
 
4
4
  /**
5
- * OpeningAnimationMenu - Manages and renders the animation control menu for opening/closing animations in a Babylon.js scene.
5
+ * OpeningAnimationMenu - Manages and renders the interactive animation control menu for opening/closing animations in a Babylon.js scene.
6
6
  *
7
- * Responsibilities:
8
- * - Renders a GUI menu with buttons for controlling animation playback (open, close, pause, go to opened/closed, loop).
7
+ * Summary:
8
+ * Provides a floating HTML-based GUI menu with playback and loop controls for animated nodes, including buttons and a progress slider.
9
+ * Handles user interaction, updates UI state based on animation state, and invokes external callbacks for animation actions.
10
+ * Designed for integration with Babylon.js product configurators and interactive 3D applications.
11
+ *
12
+ * Key Features:
13
+ * - Renders a menu with buttons for controlling animation playback (open, close, pause, go to opened/closed, loop).
9
14
  * - Updates button states and slider position based on animation state, progress, and loop mode.
10
15
  * - Handles user interactions and invokes provided callbacks for animation actions.
11
16
  * - Synchronizes the slider value with animation progress, avoiding callback loops.
@@ -35,6 +40,7 @@ import { PrefViewer3DAnimationMenuStyles } from "./styles.js";
35
40
  * - #setPlayerButtonsState(): Updates all playback control buttons.
36
41
  * - #setLoopButtonsState(): Updates all loop control buttons.
37
42
  * - #createSlider(): Creates and configures the animation progress slider.
43
+ * - #onSliderInput(event): Handles the slider input event for animation progress.
38
44
  *
39
45
  * Usage Example:
40
46
  * const menu = new OpeningAnimationMenu(name, canvas, state, progress, loop, {
@@ -72,8 +78,6 @@ export default class OpeningAnimationMenu {
72
78
  repeatOff: `<svg id="repeat-off" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M2,5.27L3.28,4L20,20.72L18.73,22L15.73,19H7V22L3,18L7,14V17H13.73L7,10.27V11H5V8.27L2,5.27M17,13H19V17.18L17,15.18V13M17,5V2L21,6L17,10V7H8.82L6.82,5H17Z"/></svg>`,
73
79
  };
74
80
 
75
- #isSettingSliderValue = false;
76
-
77
81
  /**
78
82
  * Constructs the OpeningAnimationMenu and initializes the menu UI.
79
83
  * @param {string} name - The name of the animation.
@@ -95,29 +99,6 @@ export default class OpeningAnimationMenu {
95
99
  this.#createMenu(animationState);
96
100
  }
97
101
 
98
- /**
99
- * Initializes and renders the menu UI, including buttons and slider.
100
- * @private
101
- */
102
- #createMenu() {
103
- this.#containerMain = document.createElement("div");
104
- this.#containerMain.classList.add("pref-viewer-3d");
105
- this.#containerMain.classList.add("animation-menu");
106
- this.#containerMain.setAttribute("data-animation-name", this.#name);
107
- this.#canvas.parentElement.prepend(this.#containerMain);
108
-
109
- const style = document.createElement("style");
110
- style.textContent = PrefViewer3DAnimationMenuStyles;
111
- this.#containerMain.appendChild(style);
112
-
113
- this.#containerButtons = document.createElement("div");
114
- this.#containerButtons.classList.add("animation-menu-buttons");
115
- this.#containerMain.appendChild(this.#containerButtons);
116
-
117
- this.#createButtons();
118
- this.#createSlider();
119
- }
120
-
121
102
  /**
122
103
  * Internal helper to add a button to the menu.
123
104
  * Sets button appearance and attaches the callback for user interaction.
@@ -161,6 +142,46 @@ export default class OpeningAnimationMenu {
161
142
  this.#addButton("repeatOff", this.#icon.repeatOff, buttonsState.repeatOff.enabled, buttonsState.repeatOff.active, buttonsState.repeatOff.visible, this.#callbacks.onToggleLoop);
162
143
  }
163
144
 
145
+ /**
146
+ * Creates and configures the animation progress slider.
147
+ * @private
148
+ */
149
+ #createSlider() {
150
+ this.#slider = document.createElement("input");
151
+ this.#slider.setAttribute("type", "range");
152
+ this.#slider.setAttribute("min", "0");
153
+ this.#slider.setAttribute("max", "1");
154
+ this.#slider.setAttribute("step", "0.01");
155
+ this.#slider.setAttribute("value", this.#animationProgress.toString());
156
+ this.#slider.classList.add("animation-menu-slider");
157
+ this.#slider.addEventListener("input", this.#onSliderInput.bind(this));
158
+
159
+ this.#containerMain.appendChild(this.#slider);
160
+ }
161
+
162
+ /**
163
+ * Initializes and renders the menu UI, including buttons and slider.
164
+ * @private
165
+ */
166
+ #createMenu() {
167
+ this.#containerMain = document.createElement("div");
168
+ this.#containerMain.classList.add("pref-viewer-3d");
169
+ this.#containerMain.classList.add("animation-menu");
170
+ this.#containerMain.setAttribute("data-animation-name", this.#name);
171
+ this.#canvas.parentElement.prepend(this.#containerMain);
172
+
173
+ const style = document.createElement("style");
174
+ style.textContent = PrefViewer3DAnimationMenuStyles;
175
+ this.#containerMain.appendChild(style);
176
+
177
+ this.#containerButtons = document.createElement("div");
178
+ this.#containerButtons.classList.add("animation-menu-buttons");
179
+ this.#containerMain.appendChild(this.#containerButtons);
180
+
181
+ this.#createButtons();
182
+ this.#createSlider();
183
+ }
184
+
164
185
  /**
165
186
  * Retrieves a button control by its name.
166
187
  * @private
@@ -172,23 +193,33 @@ export default class OpeningAnimationMenu {
172
193
  }
173
194
 
174
195
  /**
175
- * Updates the visual state of a button (enabled, active, visible).
196
+ * Combines player and loop button states.
176
197
  * @private
177
- * @param {string} name - Button identifier.
178
- * @param {boolean} enabled
179
- * @param {boolean} active
180
- * @param {boolean} visible
198
+ * @returns {object}
181
199
  */
182
- #setButtonState(name, enabled, active, visible = true) {
183
- const button = this.#getButtonByName(name);
184
- if (!button) {
185
- return;
186
- }
187
- const classToRemove = ["active", "enabled", "disabled", "hidden", "visible"];
188
- button.classList.remove(...classToRemove);
189
- const stateClass = active ? "active" : enabled ? "enabled" : "disabled";
190
- const visibilityClass = visible ? "visible" : "hidden";
191
- button.classList.add(stateClass, visibilityClass);
200
+ #getButtonsState() {
201
+ return Object.assign(this.#getPlayerButtonsState(), this.#getLoopButtonsState());
202
+ }
203
+
204
+ /**
205
+ * Returns the state for loop control buttons.
206
+ * @private
207
+ * @returns {object}
208
+ */
209
+ #getLoopButtonsState() {
210
+ const buttonsState = {
211
+ repeat: {
212
+ enabled: this.#animationLoop,
213
+ active: false,
214
+ visible: this.#animationLoop,
215
+ },
216
+ repeatOff: {
217
+ enabled: !this.#animationLoop,
218
+ active: false,
219
+ visible: !this.#animationLoop,
220
+ },
221
+ };
222
+ return buttonsState;
192
223
  }
193
224
 
194
225
  /**
@@ -228,33 +259,33 @@ export default class OpeningAnimationMenu {
228
259
  }
229
260
 
230
261
  /**
231
- * Returns the state for loop control buttons.
262
+ * Updates the visual state of a button (enabled, active, visible).
232
263
  * @private
233
- * @returns {object}
264
+ * @param {string} name - Button identifier.
265
+ * @param {boolean} enabled
266
+ * @param {boolean} active
267
+ * @param {boolean} visible
234
268
  */
235
- #getLoopButtonsState() {
236
- const buttonsState = {
237
- repeat: {
238
- enabled: this.#animationLoop,
239
- active: false,
240
- visible: this.#animationLoop,
241
- },
242
- repeatOff: {
243
- enabled: !this.#animationLoop,
244
- active: false,
245
- visible: !this.#animationLoop,
246
- },
247
- };
248
- return buttonsState;
269
+ #setButtonState(name, enabled, active, visible = true) {
270
+ const button = this.#getButtonByName(name);
271
+ if (!button) {
272
+ return;
273
+ }
274
+ const classToRemove = ["active", "enabled", "disabled", "hidden", "visible"];
275
+ button.classList.remove(...classToRemove);
276
+ const stateClass = active ? "active" : enabled ? "enabled" : "disabled";
277
+ const visibilityClass = visible ? "visible" : "hidden";
278
+ button.classList.add(stateClass, visibilityClass);
249
279
  }
250
280
 
251
281
  /**
252
- * Combines player and loop button states.
282
+ * Updates all loop control buttons according to current loop mode.
253
283
  * @private
254
- * @returns {object}
255
284
  */
256
- #getButtonsState() {
257
- return Object.assign(this.#getPlayerButtonsState(), this.#getLoopButtonsState());
285
+ #setLoopButtonsState() {
286
+ const buttonsState = this.#getLoopButtonsState();
287
+ this.#setButtonState("repeat", buttonsState.repeat.enabled, buttonsState.repeat.active, buttonsState.repeat.visible);
288
+ this.#setButtonState("repeatOff", buttonsState.repeatOff.enabled, buttonsState.repeatOff.active, buttonsState.repeatOff.visible);
258
289
  }
259
290
 
260
291
  /**
@@ -271,39 +302,17 @@ export default class OpeningAnimationMenu {
271
302
  }
272
303
 
273
304
  /**
274
- * Updates all loop control buttons according to current loop mode.
275
- * @private
276
- */
277
- #setLoopButtonsState() {
278
- const buttonsState = this.#getLoopButtonsState();
279
- this.#setButtonState("repeat", buttonsState.repeat.enabled, buttonsState.repeat.active, buttonsState.repeat.visible);
280
- this.#setButtonState("repeatOff", buttonsState.repeatOff.enabled, buttonsState.repeatOff.active, buttonsState.repeatOff.visible);
281
- }
282
-
283
- /**
284
- * Creates and configures the animation progress slider.
305
+ * Handles the slider input event for animation progress.
285
306
  * @private
307
+ * @param {Event} event - The input event from the slider.
308
+ * @returns {void}
309
+ * @description
310
+ * If the slider value is being set programmatically, the event is ignored to prevent callback loops.
286
311
  */
287
- #createSlider() {
288
- this.#slider = document.createElement("input");
289
- this.#slider.setAttribute("type", "range");
290
- this.#slider.setAttribute("min", "0");
291
- this.#slider.setAttribute("max", "1");
292
- this.#slider.setAttribute("step", "0.01");
293
- this.#slider.setAttribute("value", this.#animationProgress.toString());
294
- this.#slider.classList.add("animation-menu-slider");
295
-
296
- this.#slider.addEventListener("input", (event) => {
297
- if (this.#isSettingSliderValue) {
298
- this.#isSettingSliderValue = false;
299
- return;
300
- }
301
- if (this.#callbacks && typeof this.#callbacks.onSetAnimationProgress === "function") {
302
- this.#callbacks.onSetAnimationProgress(event.target.value);
303
- }
304
- });
305
-
306
- this.#containerMain.appendChild(this.#slider);
312
+ #onSliderInput(event) {
313
+ if (this.#callbacks && typeof this.#callbacks.onSetAnimationProgress === "function") {
314
+ this.#callbacks.onSetAnimationProgress(event.target.value);
315
+ }
307
316
  }
308
317
 
309
318
  /**
@@ -344,14 +353,12 @@ export default class OpeningAnimationMenu {
344
353
 
345
354
  /**
346
355
  * Sets the animation progress value and updates the slider position.
347
- * When called, the slider value is updated programmatically without triggering the slider's value change callback.
348
356
  * @public
349
357
  * @param {number} progress - The new animation progress value (between 0 and 1).
350
358
  */
351
359
  set animationProgress(progress) {
352
360
  this.#animationProgress = progress;
353
- this.#isSettingSliderValue = true;
354
- this.#slider.value = progress;
361
+ this.#slider.value = progress.toString();
355
362
  }
356
363
 
357
364
  /**
@@ -1,24 +1,31 @@
1
- import { ArcRotateCamera, AssetContainer, Camera, Color4, DirectionalLight, Engine, HDRCubeTexture, HemisphericLight, IblShadowsRenderPipeline, LoadAssetContainerAsync, MeshBuilder, PointerEventTypes, PointLight, Scene, ShadowGenerator, Tools, Vector3, WebXRDefaultExperience, WebXRFeatureName, WebXRSessionManager } from "@babylonjs/core";
1
+ import { ArcRotateCamera, AssetContainer, Camera, Color4, DirectionalLight, Engine, HDRCubeTexture, HemisphericLight, IblShadowsRenderPipeline, LoadAssetContainerAsync, MeshBuilder, PointerEventTypes, PointLight, Scene, ShadowGenerator, Tools, Vector3, WebXRDefaultExperience, WebXRFeatureName, WebXRSessionManager, WebXRState } from "@babylonjs/core";
2
2
  import { DracoCompression } from "@babylonjs/core/Meshes/Compression/dracoCompression.js";
3
3
  import "@babylonjs/loaders";
4
4
  import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression.js";
5
5
  import { USDZExportAsync, GLTF2Export } from "@babylonjs/serializers";
6
+ import JSZip from "jszip";
6
7
 
7
8
  import GLTFResolver from "./gltf-resolver.js";
8
9
  import { MaterialData } from "./pref-viewer-3d-data.js";
9
10
  import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
10
11
 
11
12
  /**
12
- * BabylonJSController - Main controller for managing Babylon.js 3D scenes, assets, and interactions.
13
+ * BabylonJSController - Main controller for managing Babylon.js 3D scenes, assets, rendering, and user interactions in PrefViewer.
13
14
  *
14
- * Responsibilities:
15
+ * Summary:
16
+ * Provides a high-level API for initializing, loading, displaying, and exporting 3D models and environments using Babylon.js.
17
+ * Manages advanced rendering features, AR integration, asset containers, and user interaction logic.
18
+ *
19
+ * Key Responsibilities:
15
20
  * - Initializes and manages the Babylon.js engine, scene, camera, lights, and asset containers.
16
21
  * - Handles loading, replacing, and disposing of 3D models, environments, and materials.
17
22
  * - Configures advanced rendering features such as Draco mesh compression, shadows, and image-based lighting (IBL).
18
23
  * - Integrates Babylon.js WebXR experience for augmented reality (AR) support.
19
- * - Provides methods for downloading models and scenes in GLB, GLTF, and USDZ formats.
24
+ * - Provides methods for downloading models and scenes in GLB, glTF (ZIP), and USDZ formats.
20
25
  * - Manages camera and material options, container visibility, and user interactions.
21
26
  * - Observes canvas resize events and updates the engine accordingly.
27
+ * - Designed for integration with PrefViewer and GLTFResolver.
28
+ * - All resource management and rendering operations are performed asynchronously for performance.
22
29
  *
23
30
  * Usage:
24
31
  * - Instantiate: const controller = new BabylonJSController(canvas, containers, options);
@@ -26,7 +33,7 @@ import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
26
33
  * - Load assets: await controller.load();
27
34
  * - Set camera/material options: controller.setCameraOptions(), controller.setMaterialOptions();
28
35
  * - Control visibility: controller.setContainerVisibility(name, show);
29
- * - Download assets: controller.downloadModelGLB(), controller.downloadModelGLTF(), controller.downloadModelUSDZ(), controller.downloadModelAndSceneGLB(), controller.downloadModelAndSceneGLTF(), controller.downloadModelAndSceneUSDZ();
36
+ * - Download assets: controller.downloadGLB(), controller.downloadGLTF(), controller.downloadUSDZ();
30
37
  * - Disable rendering: controller.disable();
31
38
  *
32
39
  * Public Methods:
@@ -40,7 +47,7 @@ import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
40
47
  * - downloadGLTF(content): Downloads the current scene, model, or environment as a glTF ZIP file.
41
48
  * - downloadUSDZ(content): Downloads the current scene, model, or environment as a USDZ file.
42
49
  *
43
- * Private Methods:
50
+ * Private Methods (using ECMAScript private fields):
44
51
  * - #configureDracoCompression(): Sets up Draco mesh compression.
45
52
  * - #renderLoop(): Babylon.js render loop callback.
46
53
  * - #addStylesToARButton(): Styles AR button.
@@ -58,6 +65,8 @@ import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
58
65
  * - #onKeyUp(event): Handles keyup events for download dialog and shortcuts.
59
66
  * - #enableInteraction(): Adds canvas and scene interaction event listeners.
60
67
  * - #disableInteraction(): Removes canvas and scene interaction event listeners.
68
+ * - #disposeAnimationController(): Disposes the animation controller if it exists.
69
+ * - #disposeXRExperience(): Disposes the Babylon.js WebXR experience if it exists.
61
70
  * - #disposeEngine(): Disposes engine and releases all resources.
62
71
  * - #setOptionsMaterial(optionMaterial): Applies a material option to relevant meshes.
63
72
  * - #setOptions_Materials(): Applies all material options from configuration.
@@ -77,11 +86,6 @@ import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
77
86
  * - #addDateToName(name): Appends the current date/time to a name string.
78
87
  * - #downloadZip(files, name, comment, addDateInName): Generates and downloads a ZIP file.
79
88
  * - #openDownloadDialog(): Opens the modal download dialog.
80
- *
81
- * Notes:
82
- * - Designed for integration with PrefViewer and GLTFResolver.
83
- * - Supports advanced Babylon.js features for product visualization and configurators.
84
- * - All resource management and rendering operations are performed asynchronously for performance.
85
89
  */
86
90
  export default class BabylonJSController {
87
91
  // Canvas HTML element
@@ -463,6 +467,46 @@ export default class BabylonJSController {
463
467
  }
464
468
  }
465
469
 
470
+ /**
471
+ * Disposes the BabylonJSAnimationController instance if it exists.
472
+ * @private
473
+ * @returns {void}
474
+ */
475
+ #disposeAnimationController() {
476
+ if (this.#babylonJSAnimationController) {
477
+ this.#babylonJSAnimationController.dispose();
478
+ this.#babylonJSAnimationController = null;
479
+ }
480
+ }
481
+
482
+ /**
483
+ * Disposes the Babylon.js WebXR experience if it exists.
484
+ * @private
485
+ * @returns {void}
486
+ */
487
+ #disposeXRExperience() {
488
+ if (!this.#XRExperience) {
489
+ return;
490
+ }
491
+
492
+ if (this.#XRExperience.baseExperience.state === WebXRState.IN_XR) {
493
+ this.#XRExperience.baseExperience
494
+ .exitXRAsync()
495
+ .then(() => {
496
+ this.#XRExperience.dispose();
497
+ this.#XRExperience = null;
498
+ })
499
+ .catch((error) => {
500
+ console.warn("Error exiting XR experience:", error);
501
+ this.#XRExperience.dispose();
502
+ this.#XRExperience = null;
503
+ });
504
+ } else {
505
+ this.#XRExperience.dispose();
506
+ this.#XRExperience = null;
507
+ }
508
+ }
509
+
466
510
  /**
467
511
  * Disposes the Babylon.js engine and releases all associated resources.
468
512
  * @private
@@ -476,7 +520,6 @@ export default class BabylonJSController {
476
520
  this.#engine = this.#scene = this.#camera = null;
477
521
  this.#hemiLight = this.#dirLight = this.#cameraLight = null;
478
522
  this.#shadowGen = null;
479
- this.#XRExperience = null;
480
523
  }
481
524
 
482
525
  /**
@@ -893,10 +936,7 @@ export default class BabylonJSController {
893
936
 
894
937
  await Promise.allSettled(promiseArray)
895
938
  .then((values) => {
896
- if (this.#babylonJSAnimationController) {
897
- this.#babylonJSAnimationController.dispose();
898
- this.#babylonJSAnimationController = null;
899
- }
939
+ this.#disposeAnimationController();
900
940
  values.forEach((result) => {
901
941
  const container = result.value ? result.value[0] : null;
902
942
  const assetContainer = result.value ? result.value[1] : null;
@@ -966,7 +1006,6 @@ export default class BabylonJSController {
966
1006
  #downloadZip(files, name = "files", comment = "", addDateInName = false) {
967
1007
  name = addDateInName ? this.#addDateToName(name) : name;
968
1008
 
969
- const JSZip = require("jszip");
970
1009
  const zip = new JSZip();
971
1010
  zip.comment = comment;
972
1011
 
@@ -1065,7 +1104,7 @@ export default class BabylonJSController {
1065
1104
  */
1066
1105
  async enable() {
1067
1106
  this.#configureDracoCompression();
1068
- this.#engine = new Engine(this.#canvas, true, { alpha: true });
1107
+ this.#engine = new Engine(this.#canvas, true, { alpha: true, stencil: true, preserveDrawingBuffer: false });
1069
1108
  this.#engine.disableUniformBuffers = true;
1070
1109
  this.#scene = new Scene(this.#engine);
1071
1110
  this.#scene.clearColor = new Color4(1, 1, 1, 1);
@@ -1087,10 +1126,8 @@ export default class BabylonJSController {
1087
1126
  disable() {
1088
1127
  this.#canvasResizeObserver.disconnect();
1089
1128
  this.#disableInteraction();
1090
- if (this.#babylonJSAnimationController) {
1091
- this.#babylonJSAnimationController.dispose();
1092
- this.#babylonJSAnimationController = null;
1093
- }
1129
+ this.#disposeAnimationController();
1130
+ this.#disposeXRExperience();
1094
1131
  this.#disposeEngine();
1095
1132
  }
1096
1133
 
@@ -46,7 +46,7 @@ export class ContainerData {
46
46
  this.update.pending = true;
47
47
  this.update.storage = storage;
48
48
  this.update.success = false;
49
- this.update.show = show !== undefined ? show : this.update.show;
49
+ this.update.show = show !== undefined ? show : this.update.show !== null ? this.update.show : this.show;
50
50
  }
51
51
  setPendingCacheData(size = 0, timeStamp = null) {
52
52
  this.update.size = size;
@@ -62,7 +62,7 @@ export class ContainerData {
62
62
  return this.visible === true;
63
63
  }
64
64
  get mustBeShown() {
65
- return this.show === true;
65
+ return this.update.show !== null ? this.update.show === true : this.show === true;
66
66
  }
67
67
  }
68
68