@preference-sl/pref-viewer 2.11.0-beta.0 → 2.11.0-beta.10

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.
@@ -0,0 +1,700 @@
1
+ import { CameraData, ContainerData, MaterialData } from "./pref-viewer-3d-data.js";
2
+ import BabylonJSController from "./babylonjs-controller.js";
3
+
4
+ /**
5
+ * PrefViewer3D - Custom Web Component for interactive 3D visualization and configuration.
6
+ *
7
+ * Overview:
8
+ * - Encapsulates a Babylon.js-powered 3D viewer for displaying models, environments, and materials.
9
+ * - Manages internal state for containers (model, environment, materials) and options (camera, materials).
10
+ * - Handles asset loading, configuration, and option updates through attributes and public methods.
11
+ * - Provides API for showing/hiding the viewer, model, and environment, and for downloading assets.
12
+ * - Emits custom events for loading, loaded, and option-setting states.
13
+ *
14
+ * Usage:
15
+ * - Use as a custom HTML element: <pref-viewer-3d ...>
16
+ * - Configure via attributes (config, options, show-model, show-scene, visible).
17
+ * - Control viewer state and assets via public methods.
18
+ *
19
+ * Observed Attributes:
20
+ * - show-model: Show or hide the 3D model ("true"/"false").
21
+ * - show-scene: Show or hide the 3D environment ("true"/"false").
22
+ * - visible: Show or hide the entire viewer ("true"/"false").
23
+ *
24
+ * Public Methods:
25
+ * - show(): Shows the 3D viewer component.
26
+ * - hide(): Hides the 3D viewer component.
27
+ * - load(config): Loads the provided configuration into the viewer.
28
+ * - setOptions(options): Sets viewer options such as camera and materials.
29
+ * - showModel(): Shows the 3D model.
30
+ * - hideModel(): Hides the 3D model.
31
+ * - showEnvironment(): Shows the 3D environment/scene.
32
+ * - hideEnvironment(): Hides the 3D environment/scene.
33
+ * - downloadModelGLB(): Downloads the current 3D model as a GLB file.
34
+ * - downloadModelGLTF(): Downloads the current 3D model as a glTF ZIP file.
35
+ * - downloadModelUSDZ(): Downloads the current 3D model as a USDZ file.
36
+ * - downloadModelAndSceneGLB(): Downloads both the model and scene as a GLB file.
37
+ * - downloadModelAndSceneGLTF(): Downloads both the model and scene as a glTF ZIP file.
38
+ * - downloadModelAndSceneUSDZ(): Downloads both the model and scene as a USDZ file.
39
+ * - downloadSceneGLB(): Downloads the environment as a GLB file.
40
+ * - downloadSceneGLTF(): Downloads the environment as a glTF ZIP file.
41
+ * - downloadSceneUSDZ(): Downloads the environment as a USDZ file.
42
+ *
43
+ * Public Properties:
44
+ * - isInitialized: Indicates whether the component has completed initialization.
45
+ * - isLoaded: Indicates whether the GLTF/GLB content is loaded and ready.
46
+ * - isVisible: Indicates whether the component is currently visible.
47
+ *
48
+ * Events:
49
+ * - "scene-loading": Dispatched when a loading operation starts.
50
+ * - "scene-loaded": Dispatched when a loading operation completes.
51
+ * - "scene-setting-options": Dispatched when viewer options are being set.
52
+ * - "scene-set-options": Dispatched when viewer options have been set.
53
+ *
54
+ * Notes:
55
+ * - Internal state and heavy initialization are handled in connectedCallback.
56
+ * - Designed for extensibility and integration in product configurators and visualization tools.
57
+ * - All resource management and rendering operations are performed asynchronously for performance.
58
+ */
59
+ export class PrefViewer3D extends HTMLElement {
60
+ // State flags
61
+ #isInitialized = false;
62
+ #isLoaded = false;
63
+ #isVisible = false;
64
+
65
+ #wrapper = null;
66
+ #canvas = null;
67
+
68
+ #data = null; // Component data
69
+
70
+ #babylonJSController = null; // BabylonJSController instance
71
+
72
+ /**
73
+ * Create a new instance of the 3D viewer component.
74
+ * The constructor only calls super(); heavy initialization happens in connectedCallback.
75
+ */
76
+ constructor() {
77
+ super();
78
+ }
79
+
80
+ /**
81
+ * Attributes observed by the custom element.
82
+ * @returns {string[]} Array of attribute names that trigger attributeChangedCallback.
83
+ */
84
+ static get observedAttributes() {
85
+ return ["show-model", "show-scene", "visible"];
86
+ }
87
+
88
+ /**
89
+ * Handles changes to observed attributes and updates the component state accordingly.
90
+ * @param {string} name - The name of the changed attribute.
91
+ * @param {string|null} _old - The previous value of the attribute.
92
+ * @param {string|null} value - The new value of the attribute.
93
+ * @returns {void}
94
+ * @description
95
+ * - "show-model": shows or hides the model according to the attribute value ("true"/"false").
96
+ * - "show-scene": shows or hides the scene according to the attribute value ("true"/"false").
97
+ * - "visible": shows or hides the component according to the attribute value ("true"/"false").
98
+ */
99
+ attributeChangedCallback(name, _old, value) {
100
+ switch (name) {
101
+ case "show-model":
102
+ if (_old === value) {
103
+ return;
104
+ }
105
+ const showModel = value.toLowerCase?.() === "true";
106
+ showModel ? this.showModel() : this.hideModel();
107
+ break;
108
+ case "show-scene":
109
+ if (_old === value) {
110
+ return;
111
+ }
112
+ const showScene = value.toLowerCase?.() === "true";
113
+ showScene ? this.showEnvironment() : this.hideEnvironment();
114
+ break;
115
+ case "visible":
116
+ if (_old === value) {
117
+ return;
118
+ }
119
+ if (value === "true" && this.#isVisible === false) {
120
+ this.show();
121
+ } else if (value === "false" && this.#isVisible === true) {
122
+ this.hide();
123
+ }
124
+ break;
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Called when the element is added to the DOM.
130
+ * Creates the DOM structure, sets initial visibility, initializes component data, and sets up the BabylonJSController.
131
+ * Performs heavy initialization tasks required for the 3D viewer.
132
+ * @returns {void}
133
+ */
134
+ connectedCallback() {
135
+ this.#createDOMElements();
136
+ this.#setInitialVisibility();
137
+ this.#initializeData();
138
+ this.#initializeBabylonJS();
139
+ }
140
+
141
+ /**
142
+ * Called when the element is removed from the DOM.
143
+ * Disables the BabylonJSController to clean up resources and event listeners associated with the 3D viewer.
144
+ * @returns {void}
145
+ */
146
+ disconnectedCallback() {
147
+ if (this.#babylonJSController) {
148
+ this.#babylonJSController.disable();
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Create DOM structure and basic styles used by the component.
154
+ * @private
155
+ * @returns {void}
156
+ */
157
+ #createDOMElements() {
158
+ this.#wrapper = document.createElement("div");
159
+ this.append(this.#wrapper);
160
+ this.#canvas = document.createElement("canvas");
161
+ this.#wrapper.appendChild(this.#canvas);
162
+ const style = document.createElement("style");
163
+ style.textContent = `@import "/dist/css/pref-viewer-3d.css";`;
164
+ this.append(style);
165
+ }
166
+
167
+ /**
168
+ * Set initial visibility based on inline style and the visible attribute.
169
+ * @private
170
+ * @returns {void}
171
+ * @description If the "visible" attribute is not present, defaults to visible.
172
+ */
173
+ #setInitialVisibility() {
174
+ const visible = !this.hasAttribute("visible") || this.getAttribute("visible") === "true";
175
+ visible ? this.show() : this.hide();
176
+ }
177
+
178
+ /**
179
+ * Initializes the internal data structure for containers and options used by the 3D viewer.
180
+ * Sets up default states for model, environment, materials, camera, and material options.
181
+ * @private
182
+ * @returns {void}
183
+ */
184
+ #initializeData() {
185
+ this.#data = {
186
+ containers: {
187
+ model: new ContainerData("model"),
188
+ environment: new ContainerData("environment"),
189
+ materials: new ContainerData("materials"),
190
+ },
191
+ options: {
192
+ camera: new CameraData("activeCamera", null, true),
193
+ materials: {
194
+ innerWall: new MaterialData("innerWall", undefined, undefined, ["innerWall"]),
195
+ outerWall: new MaterialData("outerWall", undefined, undefined, ["outerWall"]),
196
+ innerFloor: new MaterialData("innerFloor", undefined, undefined, ["innerFloor"]),
197
+ outerFloor: new MaterialData("outerFloor", undefined, undefined, ["outerFloor"]),
198
+ },
199
+ },
200
+ };
201
+ }
202
+
203
+ /**
204
+ * Initializes the BabylonJSController and enables the Babylon.js engine for rendering.
205
+ * @private
206
+ * @returns {void}
207
+ */
208
+ #initializeBabylonJS() {
209
+ this.#babylonJSController = new BabylonJSController(this.#canvas, this.#data.containers, this.#data.options);
210
+ this.#babylonJSController.enable();
211
+ }
212
+
213
+ /**
214
+ * Resets update flags for all containers and material/camera options after loading or setting options.
215
+ * @private
216
+ * @returns {void}
217
+ */
218
+ #resetUpdateFlags() {
219
+ Object.values(this.#data.containers).forEach((container) => container.reset());
220
+ Object.values(this.#data.options.materials).forEach((material) => material.reset());
221
+ this.#data.options.camera.reset();
222
+ }
223
+
224
+ /**
225
+ * Checks if any container (model, environment, materials) needs to be updated based on the provided config.
226
+ * Sets pending state for containers if storage or visibility changes are detected.
227
+ * @private
228
+ * @param {object} config - Configuration object containing model, scene, and materials info.
229
+ * @returns {void}
230
+ */
231
+ #checkNeedToUpdateContainers(config) {
232
+ const modelState = this.#data.containers.model;
233
+ const modelHasStorage = !!config.model?.storage;
234
+ const modelNeedToChangeVisibility = config.model?.visible !== undefined && config.model?.visible !== modelState.show;
235
+ if (modelHasStorage) {
236
+ modelState.setPending(modelHasStorage ? config.model.storage : null, modelNeedToChangeVisibility ? config.model.visible : undefined);
237
+ } else if (modelNeedToChangeVisibility) {
238
+ modelState.show = config.model.visible;
239
+ }
240
+
241
+ const environmentState = this.#data.containers.environment;
242
+ const environmentHasStorage = !!config.scene?.storage;
243
+ const environmentNeedToChangeVisibility = config.scene?.visible !== undefined && config.scene?.visible !== environmentState.show;
244
+ if (environmentHasStorage) {
245
+ environmentState.setPending(environmentHasStorage ? config.scene.storage : null, environmentNeedToChangeVisibility ? config.scene.visible : undefined);
246
+ } else if (environmentNeedToChangeVisibility) {
247
+ environmentState.show = config.scene.visible;
248
+ }
249
+
250
+ // Materials container. Visibility is not established for this container because it does not contain geometry.
251
+ const materialsState = this.#data.containers.materials;
252
+ const materialsHasStorage = !!config.materials?.storage;
253
+ if (materialsHasStorage) {
254
+ materialsState.setPending(materialsHasStorage ? config.materials.storage : null, null);
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Checks if the camera option needs to be updated based on the provided options.
260
+ * Sets pending state for the camera if a change is detected.
261
+ * @private
262
+ * @param {object} options - Options object containing camera info.
263
+ * @returns {boolean} True if camera needs update, false otherwise.
264
+ */
265
+ #checkNeedToUpdateCamera(options) {
266
+ if (!options || options.camera === undefined || options.camera === "") {
267
+ return false;
268
+ }
269
+ const cameraState = this.#data.options.camera;
270
+ const needUpdate = options.camera !== cameraState.value;
271
+ if (needUpdate) {
272
+ cameraState.setPending(options.camera);
273
+ }
274
+ return needUpdate;
275
+ }
276
+
277
+ /**
278
+ * Checks if any material option needs to be updated based on the provided options.
279
+ * Sets pending state for materials if changes are detected.
280
+ * @private
281
+ * @param {object} options - Options object containing material info.
282
+ * @returns {boolean} True if any material needs update, false otherwise.
283
+ */
284
+ #checkNeedToUpdateMaterials(options) {
285
+ if (!options) {
286
+ return false;
287
+ }
288
+ let someNeedUpdate = false;
289
+ Object.keys(this.#data.options.materials).forEach((material) => {
290
+ const key = `${material}Material`;
291
+ const materialState = this.#data.options.materials[material];
292
+ const previousValue = materialState.value;
293
+ const incomingValue = options[key];
294
+ const needUpdate = !!incomingValue && incomingValue !== previousValue;
295
+ if (needUpdate) {
296
+ materialState.setPending(incomingValue);
297
+ }
298
+ someNeedUpdate = someNeedUpdate || needUpdate;
299
+ });
300
+ return someNeedUpdate;
301
+ }
302
+
303
+ /**
304
+ * Dispatches a "prefviewer3d-loading" event and updates loading state attributes.
305
+ * Used internally when a loading operation starts.
306
+ * @private
307
+ * @returns {void}
308
+ */
309
+ #onLoading() {
310
+ const customEventOptions = {
311
+ bubbles: true, // indicates whether the event bubbles up through the DOM tree or not
312
+ cancelable: true, // indicates whether the event can be canceled, and therefore prevented as if the event never happened
313
+ composed: false, // indicates whether or not the event will propagate across the shadow DOM boundary into the standard DOM
314
+ };
315
+ this.dispatchEvent(new CustomEvent("scene-loading", customEventOptions));
316
+
317
+ this.removeAttribute("loaded");
318
+ this.setAttribute("loading", "");
319
+
320
+ if (this.#isLoaded === true) {
321
+ this.#isLoaded = false;
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Dispatches a "prefviewer3d-loaded" event with details about what was loaded and updates state attributes.
327
+ * Used internally when a loading operation completes.
328
+ * @private
329
+ * @returns {object} Detail object describing what was tried and what succeeded.
330
+ */
331
+ #onLoaded() {
332
+ const toLoadDetail = {
333
+ container_model: this.#data.containers.model.isPending,
334
+ container_scene: this.#data.containers.environment.isPending,
335
+ container_materials: this.#data.containers.materials.isPending,
336
+ options_camera: this.#data.options.camera.isPending,
337
+ options_material_innerWall: this.#data.options.materials.innerWall.isPending,
338
+ options_material_outerWall: this.#data.options.materials.outerWall.isPending,
339
+ options_material_innerFloor: this.#data.options.materials.innerFloor.isPending,
340
+ options_material_outerFloor: this.#data.options.materials.outerFloor.isPending,
341
+ };
342
+ const loadedDetail = {
343
+ container_model: this.#data.containers.model.isSuccess,
344
+ container_scene: this.#data.containers.environment.isSuccess,
345
+ container_materials: this.#data.containers.materials.isSuccess,
346
+ options_camera: this.#data.options.camera.isSuccess,
347
+ options_material_innerWall: this.#data.options.materials.innerWall.isSuccess,
348
+ options_material_outerWall: this.#data.options.materials.outerWall.isSuccess,
349
+ options_material_innerFloor: this.#data.options.materials.innerFloor.isSuccess,
350
+ options_material_outerFloor: this.#data.options.materials.outerFloor.isSuccess,
351
+ };
352
+ const detail = {
353
+ tried: toLoadDetail,
354
+ success: loadedDetail,
355
+ };
356
+
357
+ const customEventOptions = {
358
+ bubbles: true,
359
+ cancelable: true,
360
+ composed: false,
361
+ detail: detail,
362
+ };
363
+ this.dispatchEvent(new CustomEvent("scene-loaded", customEventOptions));
364
+
365
+ this.removeAttribute("loading");
366
+ this.setAttribute("loaded", "");
367
+
368
+ this.#resetUpdateFlags();
369
+ this.#isLoaded = true;
370
+
371
+ return detail;
372
+ }
373
+
374
+ /**
375
+ * Dispatches a "prefviewer3d-setting-options" event and updates loading state attributes.
376
+ * Used internally when options are being set.
377
+ * @private
378
+ * @returns {void}
379
+ */
380
+ #onSettingOptions() {
381
+ const customEventOptions = {
382
+ bubbles: true,
383
+ cancelable: true,
384
+ composed: false,
385
+ };
386
+ this.dispatchEvent(new CustomEvent("scene-setting-options", customEventOptions));
387
+
388
+ this.removeAttribute("loaded");
389
+ this.setAttribute("loading", "");
390
+
391
+ if (this.#isLoaded === true) {
392
+ this.#isLoaded = false;
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Dispatches a "prefviewer3d-loaded" event with details about what options were set and updates state attributes.
398
+ * Used internally when options have been set.
399
+ * @private
400
+ * @returns {object} Detail object describing what was tried and what succeeded.
401
+ */
402
+ #onSetOptions() {
403
+ const toSetDetail = {
404
+ camera: this.#data.options.camera.isPending,
405
+ innerWallMaterial: this.#data.options.materials.innerWall.isPending,
406
+ outerWallMaterial: this.#data.options.materials.outerWall.isPending,
407
+ innerFloorMaterial: this.#data.options.materials.innerFloor.isPending,
408
+ outerFloorMaterial: this.#data.options.materials.outerFloor.isPending,
409
+ };
410
+ const settedDetail = {
411
+ camera: this.#data.options.camera.isSuccess,
412
+ innerWallMaterial: this.#data.options.materials.innerWall.isSuccess,
413
+ outerWallMaterial: this.#data.options.materials.outerWall.isSuccess,
414
+ innerFloorMaterial: this.#data.options.materials.innerFloor.isSuccess,
415
+ outerFloorMaterial: this.#data.options.materials.outerFloor.isSuccess,
416
+ };
417
+ const detail = {
418
+ tried: toSetDetail,
419
+ success: settedDetail,
420
+ };
421
+
422
+ const customEventOptions = {
423
+ bubbles: true,
424
+ cancelable: true,
425
+ composed: false,
426
+ detail: detail,
427
+ };
428
+ this.dispatchEvent(new CustomEvent("scene-set-options", customEventOptions));
429
+
430
+ this.removeAttribute("loading");
431
+ this.setAttribute("loaded", "");
432
+
433
+ this.#resetUpdateFlags();
434
+ this.#isLoaded = true;
435
+
436
+ return detail;
437
+ }
438
+
439
+ /**
440
+ * ---------------------------
441
+ * Public methods
442
+ * ---------------------------
443
+ */
444
+
445
+ /**
446
+ * Hides the 3D viewer component by updating its visibility state and DOM attribute.
447
+ * @public
448
+ * @returns {void}
449
+ */
450
+ hide() {
451
+ this.#isVisible = false;
452
+ this.setAttribute("visible", "false");
453
+ }
454
+
455
+ /**
456
+ * Shows the 3D viewer component by updating its visibility state and DOM attribute.
457
+ * @public
458
+ * @returns {void}
459
+ */
460
+ show() {
461
+ this.#isVisible = true;
462
+ this.setAttribute("visible", "true");
463
+ }
464
+
465
+ /**
466
+ * Loads the provided configuration into the 3D viewer.
467
+ * Updates containers and options, triggers loading events, and returns the loading result.
468
+ * @public
469
+ * @param {object} config - Configuration object containing containers and options.
470
+ * @returns {Promise<object>} Resolves with an object containing success and error status and loading details.
471
+ */
472
+ async load(config) {
473
+ if (!this.#babylonJSController) {
474
+ return;
475
+ }
476
+
477
+ this.#onLoading();
478
+
479
+ // Containers
480
+ this.#checkNeedToUpdateContainers(config);
481
+
482
+ // Options
483
+ if (config.options) {
484
+ this.#checkNeedToUpdateCamera(config.options);
485
+ this.#checkNeedToUpdateMaterials(config.options);
486
+ }
487
+
488
+ const loadDetail = await this.#babylonJSController.load();
489
+
490
+ return { ...loadDetail, load: this.#onLoaded() };
491
+ }
492
+
493
+ /**
494
+ * Sets viewer options such as camera and materials.
495
+ * Updates internal states, triggers option setting events, and returns the result.
496
+ * @public
497
+ * @param {object} options - Options object containing camera and material settings.
498
+ * @returns {object} Object containing success status and details of set options.
499
+ */
500
+ setOptions(options) {
501
+ if (!this.#babylonJSController) {
502
+ return;
503
+ }
504
+
505
+ this.#onSettingOptions();
506
+
507
+ let someSetted = false;
508
+ if (this.#checkNeedToUpdateCamera(options)) {
509
+ someSetted = someSetted || this.#babylonJSController.setCameraOptions();
510
+ }
511
+ if (this.#checkNeedToUpdateMaterials(options)) {
512
+ someSetted = someSetted || this.#babylonJSController.setMaterialOptions();
513
+ }
514
+ const detail = this.#onSetOptions();
515
+ return { success: someSetted, detail: detail };
516
+ }
517
+
518
+ /**
519
+ * Shows the 3D model within the viewer.
520
+ * @public
521
+ * @returns {void}
522
+ */
523
+ showModel() {
524
+ if (!this.#babylonJSController) {
525
+ return;
526
+ }
527
+ this.#babylonJSController.setContainerVisibility("model", true);
528
+ }
529
+
530
+ /**
531
+ * Hides the 3D model within the viewer.
532
+ * @public
533
+ * @returns {void}
534
+ */
535
+ hideModel() {
536
+ if (!this.#babylonJSController) {
537
+ return;
538
+ }
539
+ this.#babylonJSController.setContainerVisibility("model", false);
540
+ }
541
+
542
+ /**
543
+ * Shows the 3D environment/scene within the viewer.
544
+ * @public
545
+ * @returns {void}
546
+ */
547
+ showEnvironment() {
548
+ if (!this.#babylonJSController) {
549
+ return;
550
+ }
551
+ this.#babylonJSController.setContainerVisibility("environment", true);
552
+ }
553
+
554
+ /**
555
+ * Hides the 3D environment/scene within the viewer.
556
+ * @public
557
+ * @returns {void}
558
+ */
559
+ hideEnvironment() {
560
+ if (!this.#babylonJSController) {
561
+ return;
562
+ }
563
+ this.#babylonJSController.setContainerVisibility("environment", false);
564
+ }
565
+
566
+ /**
567
+ * Downloads the current 3D model as a GLB file.
568
+ * @public
569
+ * @returns {void}
570
+ */
571
+ downloadModelGLB() {
572
+ if (!this.#babylonJSController) {
573
+ return;
574
+ }
575
+ this.#babylonJSController.downloadGLB(1);
576
+ }
577
+
578
+ /**
579
+ * Downloads the current 3D model as a glTF file (ZIP with gltf and all resources (textures, buffers, etc.)).
580
+ * @public
581
+ * @returns {void}
582
+ */
583
+ downloadModelGLTF() {
584
+ if (!this.#babylonJSController) {
585
+ return;
586
+ }
587
+ this.#babylonJSController.downloadGLTF(1);
588
+ }
589
+
590
+ /**
591
+ * Downloads the current 3D model as a USDZ file.
592
+ * @public
593
+ * @returns {void}
594
+ */
595
+ downloadModelUSDZ() {
596
+ if (!this.#babylonJSController) {
597
+ return;
598
+ }
599
+ this.#babylonJSController.downloadUSDZ(1);
600
+ }
601
+
602
+ /**
603
+ * Downloads the current 3D complete scene (model and environment) as a GLB file.
604
+ * @public
605
+ * @returns {void}
606
+ */
607
+ downloadModelAndSceneGLB() {
608
+ if (!this.#babylonJSController) {
609
+ return;
610
+ }
611
+ this.#babylonJSController.downloadModelGLB(0);
612
+ }
613
+
614
+ /**
615
+ * Downloads the current 3D complete scene (model and environment) as a glTF file (ZIP with gltf and all resources (textures, buffers, etc.)).
616
+ * @public
617
+ * @returns {void}
618
+ */
619
+ downloadModelAndSceneGLTF() {
620
+ if (!this.#babylonJSController) {
621
+ return;
622
+ }
623
+ this.#babylonJSController.downloadGLTF(0);
624
+ }
625
+
626
+ /**
627
+ * Downloads the current 3D complete scene (model and environment) as a USDZ file.
628
+ * @public
629
+ * @returns {void}
630
+ */
631
+ downloadModelAndSceneUSDZ() {
632
+ if (!this.#babylonJSController) {
633
+ return;
634
+ }
635
+ this.#babylonJSController.downloadUSDZ(0);
636
+ }
637
+
638
+ /**
639
+ * Downloads the current 3D environment as a GLB file.
640
+ * @public
641
+ * @returns {void}
642
+ */
643
+ downloadSceneGLB() {
644
+ if (!this.#babylonJSController) {
645
+ return;
646
+ }
647
+ this.#babylonJSController.downloadGLB(2);
648
+ }
649
+
650
+ /**
651
+ * Downloads the current 3D environment as a glTF file (ZIP with gltf and all resources (textures, buffers, etc.)).
652
+ * @public
653
+ * @returns {void}
654
+ */
655
+ downloadSceneGLTF() {
656
+ if (!this.#babylonJSController) {
657
+ return;
658
+ }
659
+ this.#babylonJSController.downloadGLTF(2);
660
+ }
661
+
662
+ /**
663
+ * Downloads the current 3D environment as a USDZ file.
664
+ * @public
665
+ * @returns {void}
666
+ */
667
+ downloadSceneUSDZ() {
668
+ if (!this.#babylonJSController) {
669
+ return;
670
+ }
671
+ this.#babylonJSController.downloadUSDZ(2);
672
+ }
673
+
674
+ /**
675
+ * Indicates whether the component has completed its initialization.
676
+ * @public
677
+ * @returns {boolean} True if the component is initialized; otherwise, false.
678
+ */
679
+ get isInitialized() {
680
+ return this.#isInitialized;
681
+ }
682
+
683
+ /**
684
+ * Indicates whether the GLTF/GLB content is loaded and ready.
685
+ * @public
686
+ * @returns {boolean} True if the GLTF/GLB is loaded; otherwise, false.
687
+ */
688
+ get isLoaded() {
689
+ return this.#isLoaded;
690
+ }
691
+
692
+ /**
693
+ * Indicates whether the component is currently visible.
694
+ * @public
695
+ * @returns {boolean} True if the component is visible; otherwise, false.
696
+ */
697
+ get isVisible() {
698
+ return this.#isVisible;
699
+ }
700
+ }