@preference-sl/pref-viewer 2.11.0-beta.8 → 2.11.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.
- package/package.json +61 -50
- package/src/babylonjs-animation-controller.js +58 -76
- package/src/babylonjs-animation-opening-menu.js +189 -154
- package/src/babylonjs-animation-opening.js +67 -36
- package/src/babylonjs-controller.js +233 -113
- package/src/file-storage.js +11 -2
- package/src/index.js +18 -866
- package/src/panzoom-controller.js +92 -55
- package/src/pref-viewer-2d.js +15 -3
- package/src/pref-viewer-3d-data.js +3 -2
- package/src/pref-viewer-3d.js +11 -4
- package/src/pref-viewer-dialog.js +71 -46
- package/src/pref-viewer-task.js +1 -1
- package/src/pref-viewer.js +934 -0
- package/src/styles.js +334 -0
- package/src/svg-resolver.js +23 -0
|
@@ -1,24 +1,31 @@
|
|
|
1
|
-
import { ArcRotateCamera, AssetContainer, Camera, Color4, DirectionalLight, Engine, HDRCubeTexture, HemisphericLight, IblShadowsRenderPipeline, LoadAssetContainerAsync, MeshBuilder, PointLight, Scene, ShadowGenerator, Tools, Vector3, WebXRDefaultExperience, WebXRFeatureName, WebXRSessionManager } from "@babylonjs/core";
|
|
2
|
-
import { DracoCompression } from "@babylonjs/core/Meshes/Compression/dracoCompression";
|
|
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
|
+
import { DracoCompression } from "@babylonjs/core/Meshes/Compression/dracoCompression.js";
|
|
3
3
|
import "@babylonjs/loaders";
|
|
4
|
-
import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression";
|
|
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
|
-
*
|
|
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,
|
|
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.
|
|
36
|
+
* - Download assets: controller.downloadGLB(), controller.downloadGLTF(), controller.downloadUSDZ();
|
|
30
37
|
* - Disable rendering: controller.disable();
|
|
31
38
|
*
|
|
32
39
|
* Public Methods:
|
|
@@ -40,30 +47,46 @@ 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):
|
|
51
|
+
* - #bindHandlers(): Pre-binds reusable event handlers to preserve stable references.
|
|
44
52
|
* - #configureDracoCompression(): Sets up Draco mesh compression.
|
|
45
53
|
* - #renderLoop(): Babylon.js render loop callback.
|
|
46
54
|
* - #addStylesToARButton(): Styles AR button.
|
|
47
55
|
* - #createXRExperience(): Initializes WebXR AR experience.
|
|
48
|
-
* - #createCamera()
|
|
49
|
-
* - #
|
|
50
|
-
* - #
|
|
51
|
-
* - #
|
|
52
|
-
* - #
|
|
53
|
-
* - #
|
|
54
|
-
* - #
|
|
55
|
-
* - #
|
|
56
|
-
* - #
|
|
57
|
-
* - #
|
|
58
|
-
* - #
|
|
59
|
-
* - #
|
|
60
|
-
* - #
|
|
56
|
+
* - #createCamera(): Creates and configures the main camera.
|
|
57
|
+
* - #createLights(): Creates and configures scene lights and shadows.
|
|
58
|
+
* - #initializeEnvironmentTexture(): Loads and sets the HDR environment texture.
|
|
59
|
+
* - #initializeIBLShadows(): Sets up IBL shadow pipeline and assigns meshes/materials.
|
|
60
|
+
* - #initializeShadows(): Sets up standard or IBL shadows for meshes.
|
|
61
|
+
* - #setMaxSimultaneousLights(): Updates max simultaneous lights for all materials.
|
|
62
|
+
* - #onPointerObservable(info): Handles pointer events and dispatches to pointer/mouse handlers.
|
|
63
|
+
* - #onPointerUp(event, pickInfo): Handles pointer up events (e.g., right-click for animation menu).
|
|
64
|
+
* - #onPointerMove(event, pickInfo): Handles pointer move events (e.g., mesh highlighting).
|
|
65
|
+
* - #onMouseWheel(event, pickInfo): Handles mouse wheel events for camera zoom.
|
|
66
|
+
* - #onKeyUp(event): Handles keyup events for download dialog and shortcuts.
|
|
67
|
+
* - #enableInteraction(): Adds canvas and scene interaction event listeners.
|
|
68
|
+
* - #disableInteraction(): Removes canvas and scene interaction event listeners.
|
|
69
|
+
* - #disposeAnimationController(): Disposes the animation controller if it exists.
|
|
70
|
+
* - #disposeXRExperience(): Disposes the Babylon.js WebXR experience if it exists.
|
|
71
|
+
* - #disposeEngine(): Disposes engine and releases all resources.
|
|
72
|
+
* - #setOptionsMaterial(optionMaterial): Applies a material option to relevant meshes.
|
|
73
|
+
* - #setOptions_Materials(): Applies all material options from configuration.
|
|
74
|
+
* - #setOptions_Camera(): Applies camera options from configuration.
|
|
75
|
+
* - #findContainerByName(name): Finds a container by its name.
|
|
76
|
+
* - #addContainer(container, updateVisibility): Adds a container to the scene and updates visibility.
|
|
77
|
+
* - #removeContainer(container, updateVisibility): Removes a container from the scene and updates visibility.
|
|
78
|
+
* - #replaceContainer(container, newAssetContainer): Replaces a container in the scene.
|
|
79
|
+
* - #getPrefViewer3DComponent(): Caches and retrieves the parent PREF-VIEWER-3D element.
|
|
80
|
+
* - #getPrefViewerComponent(): Caches and retrieves the parent PREF-VIEWER element.
|
|
81
|
+
* - #updateVisibilityAttributeInComponents(name, isVisible): Updates parent visibility attributes.
|
|
82
|
+
* - #setVisibilityOfWallAndFloorInModel(show): Controls wall/floor mesh visibility.
|
|
83
|
+
* - #stopRender(): Stops the Babylon.js render loop.
|
|
84
|
+
* - #startRender(): Starts the Babylon.js render loop.
|
|
85
|
+
* - #loadAssetContainer(container): Loads an asset container asynchronously.
|
|
86
|
+
* - #loadContainers(): Loads all asset containers and adds them to the scene.
|
|
87
|
+
* - #addDateToName(name): Appends the current date/time to a name string.
|
|
88
|
+
* - #downloadZip(files, name, comment, addDateInName): Generates and downloads a ZIP file.
|
|
61
89
|
* - #openDownloadDialog(): Opens the modal download dialog.
|
|
62
|
-
*
|
|
63
|
-
* Notes:
|
|
64
|
-
* - Designed for integration with PrefViewer and GLTFResolver.
|
|
65
|
-
* - Supports advanced Babylon.js features for product visualization and configurators.
|
|
66
|
-
* - All resource management and rendering operations are performed asynchronously for performance.
|
|
67
90
|
*/
|
|
68
91
|
export default class BabylonJSController {
|
|
69
92
|
// Canvas HTML element
|
|
@@ -72,7 +95,6 @@ export default class BabylonJSController {
|
|
|
72
95
|
// References to parent custom elements
|
|
73
96
|
#prefViewer3D = undefined;
|
|
74
97
|
#prefViewer = undefined;
|
|
75
|
-
#prefViewerDialog = null;
|
|
76
98
|
|
|
77
99
|
// Babylon.js core objects
|
|
78
100
|
#engine = null;
|
|
@@ -91,6 +113,12 @@ export default class BabylonJSController {
|
|
|
91
113
|
#gltfResolver = null; // GLTFResolver instance
|
|
92
114
|
#babylonJSAnimationController = null; // AnimationController instance
|
|
93
115
|
|
|
116
|
+
#handlers = {
|
|
117
|
+
onKeyUp: null,
|
|
118
|
+
onPointerObservable: null,
|
|
119
|
+
renderLoop: null,
|
|
120
|
+
};
|
|
121
|
+
|
|
94
122
|
/**
|
|
95
123
|
* Constructs a new BabylonJSController instance.
|
|
96
124
|
* Initializes the canvas, asset containers, and options for the Babylon.js scene.
|
|
@@ -113,6 +141,18 @@ export default class BabylonJSController {
|
|
|
113
141
|
};
|
|
114
142
|
});
|
|
115
143
|
this.#options = options;
|
|
144
|
+
this.#bindHandlers();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Pre-binds reusable event handlers to preserve stable references.
|
|
149
|
+
* @private
|
|
150
|
+
* @returns {void}
|
|
151
|
+
*/
|
|
152
|
+
#bindHandlers() {
|
|
153
|
+
this.#handlers.onKeyUp = this.#onKeyUp.bind(this);
|
|
154
|
+
this.#handlers.onPointerObservable = this.#onPointerObservable.bind(this);
|
|
155
|
+
this.#handlers.renderLoop = this.#renderLoop.bind(this);
|
|
116
156
|
}
|
|
117
157
|
|
|
118
158
|
/**
|
|
@@ -402,13 +442,34 @@ export default class BabylonJSController {
|
|
|
402
442
|
}
|
|
403
443
|
|
|
404
444
|
/**
|
|
405
|
-
*
|
|
445
|
+
* Handles pointer events observed on the Babylon.js scene.
|
|
446
|
+
* @private
|
|
447
|
+
* @param {PointerInfo} info - The pointer event information from Babylon.js.
|
|
448
|
+
* @returns {void}
|
|
449
|
+
*/
|
|
450
|
+
#onPointerObservable(info) {
|
|
451
|
+
const pickInfo = this.#scene.pick(this.#scene.pointerX, this.#scene.pointerY);
|
|
452
|
+
if (info.type === PointerEventTypes.POINTERUP) {
|
|
453
|
+
this.#onPointerUp(info.event, pickInfo);
|
|
454
|
+
} else if (info.type === PointerEventTypes.POINTERMOVE) {
|
|
455
|
+
this.#onPointerMove(info.event, pickInfo);
|
|
456
|
+
} else if (info.type === PointerEventTypes.POINTERWHEEL) {
|
|
457
|
+
this.#onMouseWheel(info.event, pickInfo);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Sets up interaction handlers for the Babylon.js canvas and scene.
|
|
406
463
|
* @private
|
|
407
464
|
* @returns {void}
|
|
408
465
|
*/
|
|
409
466
|
#enableInteraction() {
|
|
410
|
-
this.#canvas
|
|
411
|
-
|
|
467
|
+
if (this.#canvas) {
|
|
468
|
+
this.#canvas.addEventListener("keyup", this.#handlers.onKeyUp);
|
|
469
|
+
}
|
|
470
|
+
if (this.#scene) {
|
|
471
|
+
this.#scene.onPointerObservable.add(this.#handlers.onPointerObservable);
|
|
472
|
+
}
|
|
412
473
|
}
|
|
413
474
|
|
|
414
475
|
/**
|
|
@@ -417,8 +478,52 @@ export default class BabylonJSController {
|
|
|
417
478
|
* @returns {void}
|
|
418
479
|
*/
|
|
419
480
|
#disableInteraction() {
|
|
420
|
-
this.#canvas
|
|
421
|
-
|
|
481
|
+
if (this.#canvas) {
|
|
482
|
+
this.#canvas.removeEventListener("keyup", this.#handlers.onKeyUp);
|
|
483
|
+
}
|
|
484
|
+
if (this.#scene !== null) {
|
|
485
|
+
this.#scene.onPointerObservable.removeCallback(this.#handlers.onPointerObservable);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Disposes the BabylonJSAnimationController instance if it exists.
|
|
491
|
+
* @private
|
|
492
|
+
* @returns {void}
|
|
493
|
+
*/
|
|
494
|
+
#disposeAnimationController() {
|
|
495
|
+
if (this.#babylonJSAnimationController) {
|
|
496
|
+
this.#babylonJSAnimationController.dispose();
|
|
497
|
+
this.#babylonJSAnimationController = null;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Disposes the Babylon.js WebXR experience if it exists.
|
|
503
|
+
* @private
|
|
504
|
+
* @returns {void}
|
|
505
|
+
*/
|
|
506
|
+
#disposeXRExperience() {
|
|
507
|
+
if (!this.#XRExperience) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (this.#XRExperience.baseExperience.state === WebXRState.IN_XR) {
|
|
512
|
+
this.#XRExperience.baseExperience
|
|
513
|
+
.exitXRAsync()
|
|
514
|
+
.then(() => {
|
|
515
|
+
this.#XRExperience.dispose();
|
|
516
|
+
this.#XRExperience = null;
|
|
517
|
+
})
|
|
518
|
+
.catch((error) => {
|
|
519
|
+
console.warn("Error exiting XR experience:", error);
|
|
520
|
+
this.#XRExperience.dispose();
|
|
521
|
+
this.#XRExperience = null;
|
|
522
|
+
});
|
|
523
|
+
} else {
|
|
524
|
+
this.#XRExperience.dispose();
|
|
525
|
+
this.#XRExperience = null;
|
|
526
|
+
}
|
|
422
527
|
}
|
|
423
528
|
|
|
424
529
|
/**
|
|
@@ -434,21 +539,39 @@ export default class BabylonJSController {
|
|
|
434
539
|
this.#engine = this.#scene = this.#camera = null;
|
|
435
540
|
this.#hemiLight = this.#dirLight = this.#cameraLight = null;
|
|
436
541
|
this.#shadowGen = null;
|
|
437
|
-
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Handles keyup events on the Babylon.js canvas for triggering model and scene downloads.
|
|
546
|
+
* @private
|
|
547
|
+
* @param {KeyboardEvent} event - The keyup event.
|
|
548
|
+
* @returns {void}
|
|
549
|
+
*/
|
|
550
|
+
#onKeyUp(event) {
|
|
551
|
+
// CTRL + ALT + letter
|
|
552
|
+
if (event.ctrlKey && event.altKey && event.key !== undefined) {
|
|
553
|
+
switch (event.key.toLowerCase()) {
|
|
554
|
+
case "d":
|
|
555
|
+
this.#openDownloadDialog();
|
|
556
|
+
break;
|
|
557
|
+
default:
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
438
561
|
}
|
|
439
562
|
|
|
440
563
|
/**
|
|
441
564
|
* Handles mouse wheel events on the Babylon.js canvas for zooming the camera.
|
|
442
565
|
* @private
|
|
443
566
|
* @param {WheelEvent} event - The mouse wheel event.
|
|
444
|
-
* @
|
|
567
|
+
* @param {Object} pickInfo - The result of the scene pick operation (not used in this method).
|
|
568
|
+
* @returns {void|false} Returns false if there is no active camera; otherwise, void.
|
|
445
569
|
*/
|
|
446
|
-
#onMouseWheel(event) {
|
|
447
|
-
if (!this.#scene
|
|
570
|
+
#onMouseWheel(event, pickInfo) {
|
|
571
|
+
if (!this.#scene?.activeCamera) {
|
|
448
572
|
return false;
|
|
449
573
|
}
|
|
450
|
-
//
|
|
451
|
-
//this.#camera.target = pick.hit ? pick.pickedPoint.clone() : this.#camera.target;
|
|
574
|
+
//this.#scene.activeCamera.target = pickInfo.hit ? pickInfo.pickedPoint.clone() : this.#scene.activeCamera.target;
|
|
452
575
|
if (!this.#scene.activeCamera.metadata?.locked) {
|
|
453
576
|
this.#scene.activeCamera.inertialRadiusOffset -= event.deltaY * this.#scene.activeCamera.wheelPrecision * 0.001;
|
|
454
577
|
}
|
|
@@ -456,24 +579,35 @@ export default class BabylonJSController {
|
|
|
456
579
|
}
|
|
457
580
|
|
|
458
581
|
/**
|
|
459
|
-
* Handles
|
|
582
|
+
* Handles pointer up events on the Babylon.js scene.
|
|
460
583
|
* @private
|
|
461
|
-
* @param {
|
|
584
|
+
* @param {PointerEvent} event - The pointer up event.
|
|
585
|
+
* @param {PickInfo} pickInfo - The result of the scene pick operation.
|
|
462
586
|
* @returns {void}
|
|
463
587
|
*/
|
|
464
|
-
#
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
break;
|
|
471
|
-
default:
|
|
472
|
-
break;
|
|
588
|
+
#onPointerUp(event, pickInfo) {
|
|
589
|
+
if (this.#babylonJSAnimationController) {
|
|
590
|
+
this.#babylonJSAnimationController.hideMenu();
|
|
591
|
+
// Right click for showing animation menu
|
|
592
|
+
if (event.button === 2) {
|
|
593
|
+
this.#babylonJSAnimationController.showMenu(pickInfo);
|
|
473
594
|
}
|
|
474
595
|
}
|
|
475
596
|
}
|
|
476
597
|
|
|
598
|
+
/**
|
|
599
|
+
* Handles pointer move events on the Babylon.js scene.
|
|
600
|
+
* @private
|
|
601
|
+
* @param {PointerEvent} event - The pointer move event.
|
|
602
|
+
* @param {PickInfo} pickInfo - The result of the scene pick operation.
|
|
603
|
+
* @returns {void}
|
|
604
|
+
*/
|
|
605
|
+
#onPointerMove(event, pickInfo) {
|
|
606
|
+
if (this.#babylonJSAnimationController) {
|
|
607
|
+
this.#babylonJSAnimationController.hightlightMeshes(pickInfo);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
477
611
|
/**
|
|
478
612
|
* Applies material options from the configuration to the relevant meshes.
|
|
479
613
|
* @private
|
|
@@ -740,7 +874,7 @@ export default class BabylonJSController {
|
|
|
740
874
|
* @returns {void}
|
|
741
875
|
*/
|
|
742
876
|
#stopRender() {
|
|
743
|
-
this.#engine.stopRenderLoop(this.#renderLoop
|
|
877
|
+
this.#engine.stopRenderLoop(this.#handlers.renderLoop);
|
|
744
878
|
}
|
|
745
879
|
/**
|
|
746
880
|
* Starts the Babylon.js render loop for the current scene.
|
|
@@ -750,7 +884,7 @@ export default class BabylonJSController {
|
|
|
750
884
|
*/
|
|
751
885
|
async #startRender() {
|
|
752
886
|
await this.#scene.whenReadyAsync();
|
|
753
|
-
this.#engine.runRenderLoop(this.#renderLoop
|
|
887
|
+
this.#engine.runRenderLoop(this.#handlers.renderLoop);
|
|
754
888
|
}
|
|
755
889
|
|
|
756
890
|
/**
|
|
@@ -821,10 +955,7 @@ export default class BabylonJSController {
|
|
|
821
955
|
|
|
822
956
|
await Promise.allSettled(promiseArray)
|
|
823
957
|
.then((values) => {
|
|
824
|
-
|
|
825
|
-
this.#babylonJSAnimationController.dispose();
|
|
826
|
-
this.#babylonJSAnimationController = null;
|
|
827
|
-
}
|
|
958
|
+
this.#disposeAnimationController();
|
|
828
959
|
values.forEach((result) => {
|
|
829
960
|
const container = result.value ? result.value[0] : null;
|
|
830
961
|
const assetContainer = result.value ? result.value[1] : null;
|
|
@@ -862,6 +993,24 @@ export default class BabylonJSController {
|
|
|
862
993
|
return detail;
|
|
863
994
|
}
|
|
864
995
|
|
|
996
|
+
/**
|
|
997
|
+
* Appends the current date and time to the provided name string in the format: name_YYYY-MM-DD_HH.mm.ss
|
|
998
|
+
* @private
|
|
999
|
+
* @param {string} name - The base name to which the date and time will be appended.
|
|
1000
|
+
* @returns {string} The name with the appended date and time.
|
|
1001
|
+
*/
|
|
1002
|
+
#addDateToName(name) {
|
|
1003
|
+
const now = new Date();
|
|
1004
|
+
const year = now.getFullYear();
|
|
1005
|
+
const month = (now.getMonth() + 1).toString().padStart(2, "0");
|
|
1006
|
+
const day = now.getDate().toString().padStart(2, "0");
|
|
1007
|
+
const hours = now.getHours().toString().padStart(2, "0");
|
|
1008
|
+
const minutes = now.getMinutes().toString().padStart(2, "0");
|
|
1009
|
+
const seconds = now.getSeconds().toString().padStart(2, "0");
|
|
1010
|
+
name = `${name}_${year}-${month}-${day}_${hours}.${minutes}.${seconds}`;
|
|
1011
|
+
return name;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
865
1014
|
/**
|
|
866
1015
|
* Generates and downloads a ZIP file with the specified folder name and files.
|
|
867
1016
|
* @private
|
|
@@ -874,18 +1023,8 @@ export default class BabylonJSController {
|
|
|
874
1023
|
* Uses JSZip to create the ZIP and triggers a browser download.
|
|
875
1024
|
*/
|
|
876
1025
|
#downloadZip(files, name = "files", comment = "", addDateInName = false) {
|
|
877
|
-
|
|
878
|
-
const now = new Date();
|
|
879
|
-
const year = now.getFullYear();
|
|
880
|
-
const month = (now.getMonth() + 1).toString().padStart(2, "0");
|
|
881
|
-
const day = now.getDate().toString().padStart(2, "0");
|
|
882
|
-
const hours = now.getHours().toString().padStart(2, "0");
|
|
883
|
-
const minutes = now.getMinutes().toString().padStart(2, "0");
|
|
884
|
-
const seconds = now.getSeconds().toString().padStart(2, "0");
|
|
885
|
-
name = `${name}_${year}-${month}-${day}_${hours}.${minutes}.${seconds}`;
|
|
886
|
-
}
|
|
1026
|
+
name = addDateInName ? this.#addDateToName(name) : name;
|
|
887
1027
|
|
|
888
|
-
const JSZip = require("jszip");
|
|
889
1028
|
const zip = new JSZip();
|
|
890
1029
|
zip.comment = comment;
|
|
891
1030
|
|
|
@@ -915,56 +1054,39 @@ export default class BabylonJSController {
|
|
|
915
1054
|
if (!this.#prefViewer) {
|
|
916
1055
|
return;
|
|
917
1056
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
<h3 style="margin: 0 0 5px; 0">Download 3D Scene</h3>
|
|
925
|
-
<h4 style="margin:0;">Content</h4>
|
|
926
|
-
<div style="display:flex;flex-direction:row;gap:15px;margin:0 10px 0 10px;">
|
|
1057
|
+
|
|
1058
|
+
const header = "Download 3D Scene";
|
|
1059
|
+
const content = `
|
|
1060
|
+
<form slot="content" id="download-dialog-form" style="display:flex;flex-direction:column;gap:16px;">
|
|
1061
|
+
<h4>Content</h4>
|
|
1062
|
+
<div style="display:flex;flex-direction:row;gap:16px;margin:0 8px 0 8px;">
|
|
927
1063
|
<label><input type="radio" name="content" value="1"> Model</label>
|
|
928
1064
|
<label><input type="radio" name="content" value="2"> Scene</label>
|
|
929
1065
|
<label><input type="radio" name="content" value="0" checked> Both</label>
|
|
930
1066
|
</div>
|
|
931
|
-
<h4
|
|
932
|
-
<div style="display:flex;flex-direction:row;gap:
|
|
1067
|
+
<h4>Format</h4>
|
|
1068
|
+
<div style="display:flex;flex-direction:row;gap:16px;margin:0 8px 0 8px;">
|
|
933
1069
|
<label><input type="radio" name="format" value="gltf" checked> glTF (ZIP)</label>
|
|
934
1070
|
<label><input type="radio" name="format" value="glb"> GLB</label>
|
|
935
1071
|
<label><input type="radio" name="format" value="usdz"> USDZ</label>
|
|
936
1072
|
</div>
|
|
937
|
-
<div style="display:flex;gap:15px;justify-content:stretch;margin-top:10px;">
|
|
938
|
-
<button type="button" id="download-dialog-download">Download</button>
|
|
939
|
-
<button type="button" id="download-dialog-cancel">Cancel</button>
|
|
940
|
-
</div>
|
|
941
|
-
<style>
|
|
942
|
-
#download-dialog-download,
|
|
943
|
-
#download-dialog-cancel {
|
|
944
|
-
width: 100%;
|
|
945
|
-
padding: 8px 20px;
|
|
946
|
-
font-size: 1.1rem;
|
|
947
|
-
border-radius: 6px;
|
|
948
|
-
border: none;
|
|
949
|
-
background: #eee;
|
|
950
|
-
cursor: pointer;
|
|
951
|
-
transition: background 0.2s;
|
|
952
|
-
}
|
|
953
|
-
#download-dialog-download:hover,
|
|
954
|
-
#download-dialog-cancel:hover {
|
|
955
|
-
background: #e0e0e0;
|
|
956
|
-
}
|
|
957
|
-
</style>
|
|
958
1073
|
</form>`;
|
|
1074
|
+
const footer = `
|
|
1075
|
+
<button type="button" class="primary" id="download-dialog-download">Download</button>
|
|
1076
|
+
<button type="button" id="download-dialog-cancel">Cancel</button>`;
|
|
959
1077
|
|
|
960
|
-
this.#
|
|
1078
|
+
const dialog = this.#prefViewer.openDialog(header, content, footer);
|
|
1079
|
+
|
|
1080
|
+
if (!dialog) {
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
961
1083
|
|
|
962
1084
|
// Button event handlers
|
|
963
|
-
const form =
|
|
964
|
-
const
|
|
965
|
-
const
|
|
1085
|
+
const form = dialog.querySelector("#download-dialog-form");
|
|
1086
|
+
const downloadButton = dialog.querySelector("#download-dialog-download");
|
|
1087
|
+
const cancelButton = dialog.querySelector("#download-dialog-cancel");
|
|
966
1088
|
|
|
967
|
-
|
|
1089
|
+
downloadButton.onclick = () => {
|
|
968
1090
|
const contentValue = form.content.value;
|
|
969
1091
|
const formatValue = form.format.value;
|
|
970
1092
|
switch (formatValue) {
|
|
@@ -978,14 +1100,12 @@ export default class BabylonJSController {
|
|
|
978
1100
|
this.downloadUSDZ(Number(contentValue));
|
|
979
1101
|
break;
|
|
980
1102
|
}
|
|
981
|
-
this.#
|
|
1103
|
+
this.#prefViewer.closeDialog();
|
|
982
1104
|
};
|
|
983
1105
|
|
|
984
|
-
|
|
985
|
-
this.#
|
|
1106
|
+
cancelButton.onclick = () => {
|
|
1107
|
+
this.#prefViewer.closeDialog();
|
|
986
1108
|
};
|
|
987
|
-
|
|
988
|
-
this.#prefViewerDialog.open(content);
|
|
989
1109
|
}
|
|
990
1110
|
|
|
991
1111
|
/**
|
|
@@ -1003,7 +1123,7 @@ export default class BabylonJSController {
|
|
|
1003
1123
|
*/
|
|
1004
1124
|
async enable() {
|
|
1005
1125
|
this.#configureDracoCompression();
|
|
1006
|
-
this.#engine = new Engine(this.#canvas, true, { alpha: true });
|
|
1126
|
+
this.#engine = new Engine(this.#canvas, true, { alpha: true, stencil: true, preserveDrawingBuffer: false });
|
|
1007
1127
|
this.#engine.disableUniformBuffers = true;
|
|
1008
1128
|
this.#scene = new Scene(this.#engine);
|
|
1009
1129
|
this.#scene.clearColor = new Color4(1, 1, 1, 1);
|
|
@@ -1025,10 +1145,8 @@ export default class BabylonJSController {
|
|
|
1025
1145
|
disable() {
|
|
1026
1146
|
this.#canvasResizeObserver.disconnect();
|
|
1027
1147
|
this.#disableInteraction();
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
this.#babylonJSAnimationController = null;
|
|
1031
|
-
}
|
|
1148
|
+
this.#disposeAnimationController();
|
|
1149
|
+
this.#disposeXRExperience();
|
|
1032
1150
|
this.#disposeEngine();
|
|
1033
1151
|
}
|
|
1034
1152
|
|
|
@@ -1117,6 +1235,7 @@ export default class BabylonJSController {
|
|
|
1117
1235
|
default:
|
|
1118
1236
|
break;
|
|
1119
1237
|
}
|
|
1238
|
+
fileName = this.#addDateToName(fileName);
|
|
1120
1239
|
GLTF2Export.GLBAsync(container, fileName, { exportWithoutWaitingForScene: true }).then((glb) => {
|
|
1121
1240
|
if (glb) {
|
|
1122
1241
|
glb.downloadFiles();
|
|
@@ -1186,6 +1305,7 @@ export default class BabylonJSController {
|
|
|
1186
1305
|
default:
|
|
1187
1306
|
break;
|
|
1188
1307
|
}
|
|
1308
|
+
fileName = this.#addDateToName(fileName);
|
|
1189
1309
|
USDZExportAsync(container).then((response) => {
|
|
1190
1310
|
if (response) {
|
|
1191
1311
|
Tools.Download(new Blob([response], { type: "model/vnd.usdz+zip" }), `${fileName}.usdz`);
|
package/src/file-storage.js
CHANGED
|
@@ -174,7 +174,12 @@ export class FileStorage {
|
|
|
174
174
|
xhr.onload = () => {
|
|
175
175
|
if (xhr.status === 200) {
|
|
176
176
|
const blob = xhr.response;
|
|
177
|
-
const
|
|
177
|
+
const lastModified = xhr.getResponseHeader("Last-Modified");
|
|
178
|
+
let timeStamp = null;
|
|
179
|
+
if (lastModified) {
|
|
180
|
+
const parsed = new Date(lastModified);
|
|
181
|
+
timeStamp = Number.isNaN(parsed.valueOf()) ? null : parsed.toISOString();
|
|
182
|
+
}
|
|
178
183
|
file = { blob: blob, timeStamp: timeStamp };
|
|
179
184
|
resolve(file);
|
|
180
185
|
} else {
|
|
@@ -204,7 +209,11 @@ export class FileStorage {
|
|
|
204
209
|
xhr.responseType = "blob";
|
|
205
210
|
xhr.onload = () => {
|
|
206
211
|
if (xhr.status === 200) {
|
|
207
|
-
|
|
212
|
+
const lastModified = xhr.getResponseHeader("Last-Modified");
|
|
213
|
+
if (lastModified) {
|
|
214
|
+
const parsed = new Date(lastModified);
|
|
215
|
+
timeStamp = Number.isNaN(parsed.valueOf()) ? null : parsed.toISOString();
|
|
216
|
+
}
|
|
208
217
|
resolve(timeStamp);
|
|
209
218
|
} else {
|
|
210
219
|
resolve(timeStamp);
|