@preference-sl/pref-viewer 2.11.0-beta.7 → 2.11.0-beta.9
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 +3 -2
- package/src/babylonjs-controller.js +277 -49
- package/src/css/pref-viewer-2d.css +39 -0
- package/src/css/pref-viewer-3d.css +28 -0
- package/src/css/pref-viewer-dialog.css +105 -0
- package/src/css/pref-viewer.css +11 -0
- package/src/index.js +150 -12
- package/src/pref-viewer-2d.js +1 -1
- package/src/pref-viewer-3d.js +73 -8
- package/src/pref-viewer-dialog.js +139 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@preference-sl/pref-viewer",
|
|
3
|
-
"version": "2.11.0-beta.
|
|
3
|
+
"version": "2.11.0-beta.9",
|
|
4
4
|
"description": "Web Component to preview GLTF models with Babylon.js",
|
|
5
5
|
"author": "Alex Moreno Palacio <amoreno@preference.es>",
|
|
6
6
|
"scripts": {
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"@panzoom/panzoom": "^4.6.0",
|
|
41
41
|
"babylonjs-gltf2interface": "^8.36.1",
|
|
42
42
|
"idb": "^8.0.3",
|
|
43
|
-
"is-svg": "^6.1.0"
|
|
43
|
+
"is-svg": "^6.1.0",
|
|
44
|
+
"jszip": "^3.10.1"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"esbuild": "^0.25.10",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ArcRotateCamera, AssetContainer, Color4, DirectionalLight, Engine, HDRCubeTexture, HemisphericLight, IblShadowsRenderPipeline, LoadAssetContainerAsync, MeshBuilder, PointLight, Scene, ShadowGenerator, Tools, Vector3, WebXRDefaultExperience, WebXRFeatureName, WebXRSessionManager } from "@babylonjs/core";
|
|
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
2
|
import { DracoCompression } from "@babylonjs/core/Meshes/Compression/dracoCompression";
|
|
3
3
|
import "@babylonjs/loaders";
|
|
4
4
|
import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression";
|
|
@@ -16,7 +16,7 @@ import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
|
|
|
16
16
|
* - Handles loading, replacing, and disposing of 3D models, environments, and materials.
|
|
17
17
|
* - Configures advanced rendering features such as Draco mesh compression, shadows, and image-based lighting (IBL).
|
|
18
18
|
* - Integrates Babylon.js WebXR experience for augmented reality (AR) support.
|
|
19
|
-
* - Provides methods for downloading models and scenes in GLB and USDZ formats.
|
|
19
|
+
* - Provides methods for downloading models and scenes in GLB, GLTF, and USDZ formats.
|
|
20
20
|
* - Manages camera and material options, container visibility, and user interactions.
|
|
21
21
|
* - Observes canvas resize events and updates the engine accordingly.
|
|
22
22
|
*
|
|
@@ -26,7 +26,7 @@ import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
|
|
|
26
26
|
* - Load assets: await controller.load();
|
|
27
27
|
* - Set camera/material options: controller.setCameraOptions(), controller.setMaterialOptions();
|
|
28
28
|
* - Control visibility: controller.setContainerVisibility(name, show);
|
|
29
|
-
* - Download assets: controller.downloadModelGLB(), controller.downloadModelUSDZ(),
|
|
29
|
+
* - Download assets: controller.downloadModelGLB(), controller.downloadModelGLTF(), controller.downloadModelUSDZ(), controller.downloadModelAndSceneGLB(), controller.downloadModelAndSceneGLTF(), controller.downloadModelAndSceneUSDZ();
|
|
30
30
|
* - Disable rendering: controller.disable();
|
|
31
31
|
*
|
|
32
32
|
* Public Methods:
|
|
@@ -36,20 +36,29 @@ import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
|
|
|
36
36
|
* - setCameraOptions(): Applies camera options from configuration.
|
|
37
37
|
* - setMaterialOptions(): Applies material options from configuration.
|
|
38
38
|
* - setContainerVisibility(name, show): Shows or hides a container by name.
|
|
39
|
-
* -
|
|
39
|
+
* - downloadGLB(content): Downloads the current scene, model, or environment as a GLB file.
|
|
40
|
+
* - downloadGLTF(content): Downloads the current scene, model, or environment as a glTF ZIP file.
|
|
41
|
+
* - downloadUSDZ(content): Downloads the current scene, model, or environment as a USDZ file.
|
|
40
42
|
*
|
|
41
43
|
* Private Methods:
|
|
42
44
|
* - #configureDracoCompression(): Sets up Draco mesh compression.
|
|
45
|
+
* - #renderLoop(): Babylon.js render loop callback.
|
|
46
|
+
* - #addStylesToARButton(): Styles AR button.
|
|
47
|
+
* - #createXRExperience(): Initializes WebXR AR experience.
|
|
43
48
|
* - #createCamera(), #createLights(), #initializeEnvironmentTexture(), #initializeIBLShadows(), #initializeShadows(): Scene setup.
|
|
44
|
-
* - #
|
|
49
|
+
* - #setMaxSimultaneousLights(): Updates max simultaneous lights for materials.
|
|
50
|
+
* - #enableInteraction(), #disableInteraction(): Canvas interaction handlers.
|
|
45
51
|
* - #disposeEngine(): Disposes engine and resources.
|
|
52
|
+
* - #onMouseWheel(event), #onKeyUp(event): Canvas event handlers.
|
|
46
53
|
* - #setOptionsMaterial(), #setOptions_Materials(), #setOptions_Camera(): Applies material/camera options.
|
|
47
54
|
* - #findContainerByName(), #addContainer(), #removeContainer(), #replaceContainer(): Container management.
|
|
55
|
+
* - #getPrefViewer3DComponent(), #getPrefViewerComponent(): Custom element references.
|
|
56
|
+
* - #updateVisibilityAttributeInComponents(): Updates parent visibility attributes.
|
|
48
57
|
* - #setVisibilityOfWallAndFloorInModel(): Controls wall/floor mesh visibility.
|
|
49
58
|
* - #stopRender(), #startRender(): Render loop control.
|
|
50
59
|
* - #loadAssetContainer(), #loadContainers(): Asset loading.
|
|
51
|
-
* - #
|
|
52
|
-
* - #
|
|
60
|
+
* - #downloadZip(): Generates and downloads a ZIP file.
|
|
61
|
+
* - #openDownloadDialog(): Opens the modal download dialog.
|
|
53
62
|
*
|
|
54
63
|
* Notes:
|
|
55
64
|
* - Designed for integration with PrefViewer and GLTFResolver.
|
|
@@ -393,23 +402,22 @@ export default class BabylonJSController {
|
|
|
393
402
|
|
|
394
403
|
/**
|
|
395
404
|
* Sets up interaction handlers for the Babylon.js canvas.
|
|
396
|
-
* Adds a wheel event listener to control camera zoom based on mouse wheel input.
|
|
397
|
-
* Prevents zoom if the active camera is locked.
|
|
398
405
|
* @private
|
|
399
406
|
* @returns {void}
|
|
400
407
|
*/
|
|
401
|
-
#
|
|
402
|
-
this.#canvas.addEventListener("wheel", (
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
408
|
+
#enableInteraction() {
|
|
409
|
+
this.#canvas.addEventListener("wheel", this.#onMouseWheel.bind(this));
|
|
410
|
+
this.#canvas.addEventListener("keyup", this.#onKeyUp.bind(this));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Removes interaction event listeners from the Babylon.js canvas.
|
|
415
|
+
* @private
|
|
416
|
+
* @returns {void}
|
|
417
|
+
*/
|
|
418
|
+
#disableInteraction() {
|
|
419
|
+
this.#canvas.removeEventListener("wheel", this.#onMouseWheel.bind(this));
|
|
420
|
+
this.#canvas.removeEventListener("keyup", this.#onKeyUp.bind(this));
|
|
413
421
|
}
|
|
414
422
|
|
|
415
423
|
/**
|
|
@@ -428,6 +436,43 @@ export default class BabylonJSController {
|
|
|
428
436
|
this.#XRExperience = null;
|
|
429
437
|
}
|
|
430
438
|
|
|
439
|
+
/**
|
|
440
|
+
* Handles mouse wheel events on the Babylon.js canvas for zooming the camera.
|
|
441
|
+
* @private
|
|
442
|
+
* @param {WheelEvent} event - The mouse wheel event.
|
|
443
|
+
* @returns {void|false}
|
|
444
|
+
*/
|
|
445
|
+
#onMouseWheel(event) {
|
|
446
|
+
if (!this.#scene || !this.#camera) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
//const pick = this.#scene.pick(this.#scene.pointerX, this.#scene.pointerY);
|
|
450
|
+
//this.#camera.target = pick.hit ? pick.pickedPoint.clone() : this.#camera.target;
|
|
451
|
+
if (!this.#scene.activeCamera.metadata?.locked) {
|
|
452
|
+
this.#scene.activeCamera.inertialRadiusOffset -= event.deltaY * this.#scene.activeCamera.wheelPrecision * 0.001;
|
|
453
|
+
}
|
|
454
|
+
event.preventDefault();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Handles keyup events on the Babylon.js canvas for triggering model and scene downloads.
|
|
459
|
+
* @private
|
|
460
|
+
* @param {KeyboardEvent} event - The keyup event.
|
|
461
|
+
* @returns {void}
|
|
462
|
+
*/
|
|
463
|
+
#onKeyUp(event) {
|
|
464
|
+
// CTRL + ALT + letter
|
|
465
|
+
if (event.ctrlKey && event.altKey && event.key !== undefined) {
|
|
466
|
+
switch (event.key.toLowerCase()) {
|
|
467
|
+
case "d":
|
|
468
|
+
this.#openDownloadDialog();
|
|
469
|
+
break;
|
|
470
|
+
default:
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
431
476
|
/**
|
|
432
477
|
* Applies material options from the configuration to the relevant meshes.
|
|
433
478
|
* @private
|
|
@@ -584,6 +629,13 @@ export default class BabylonJSController {
|
|
|
584
629
|
*/
|
|
585
630
|
#getPrefViewerComponent() {
|
|
586
631
|
if (this.#prefViewer === undefined) {
|
|
632
|
+
if (this.#prefViewer3D === undefined) {
|
|
633
|
+
this.#getPrefViewer3DComponent();
|
|
634
|
+
}
|
|
635
|
+
if (!this.#prefViewer3D) {
|
|
636
|
+
this.#prefViewer = null;
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
587
639
|
const rootNode = this.#prefViewer3D ? this.#prefViewer3D.getRootNode().host : null;
|
|
588
640
|
this.#prefViewer = rootNode && rootNode.nodeName === "PREF-VIEWER" ? rootNode : null;
|
|
589
641
|
}
|
|
@@ -771,7 +823,7 @@ export default class BabylonJSController {
|
|
|
771
823
|
if (this.#babylonJSAnimationController) {
|
|
772
824
|
this.#babylonJSAnimationController.dispose();
|
|
773
825
|
this.#babylonJSAnimationController = null;
|
|
774
|
-
}
|
|
826
|
+
}
|
|
775
827
|
values.forEach((result) => {
|
|
776
828
|
const container = result.value ? result.value[0] : null;
|
|
777
829
|
const assetContainer = result.value ? result.value[1] : null;
|
|
@@ -788,7 +840,7 @@ export default class BabylonJSController {
|
|
|
788
840
|
container.state.setSuccess(false);
|
|
789
841
|
}
|
|
790
842
|
});
|
|
791
|
-
|
|
843
|
+
|
|
792
844
|
this.#setOptions_Materials();
|
|
793
845
|
this.#setOptions_Camera();
|
|
794
846
|
this.#setVisibilityOfWallAndFloorInModel();
|
|
@@ -809,6 +861,122 @@ export default class BabylonJSController {
|
|
|
809
861
|
return detail;
|
|
810
862
|
}
|
|
811
863
|
|
|
864
|
+
/**
|
|
865
|
+
* Appends the current date and time to the provided name string in the format: name_YYYY-MM-DD_HH.mm.ss
|
|
866
|
+
* @private
|
|
867
|
+
* @param {string} name - The base name to which the date and time will be appended.
|
|
868
|
+
* @returns {string} The name with the appended date and time.
|
|
869
|
+
*/
|
|
870
|
+
#addDateToName(name) {
|
|
871
|
+
const now = new Date();
|
|
872
|
+
const year = now.getFullYear();
|
|
873
|
+
const month = (now.getMonth() + 1).toString().padStart(2, "0");
|
|
874
|
+
const day = now.getDate().toString().padStart(2, "0");
|
|
875
|
+
const hours = now.getHours().toString().padStart(2, "0");
|
|
876
|
+
const minutes = now.getMinutes().toString().padStart(2, "0");
|
|
877
|
+
const seconds = now.getSeconds().toString().padStart(2, "0");
|
|
878
|
+
name = `${name}_${year}-${month}-${day}_${hours}.${minutes}.${seconds}`;
|
|
879
|
+
return name;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Generates and downloads a ZIP file with the specified folder name and files.
|
|
884
|
+
* @private
|
|
885
|
+
* @param {Object} files - An object where keys are file names and values are file contents.
|
|
886
|
+
* @param {string} [name="files"] - The name for the root folder inside the ZIP.
|
|
887
|
+
* @param {string} [comment=""] - A comment to include in the ZIP file.
|
|
888
|
+
* @param {boolean} [addDateInName=false] - Whether to append the current date/time to the folder name.
|
|
889
|
+
* @returns {void}
|
|
890
|
+
* @description
|
|
891
|
+
* Uses JSZip to create the ZIP and triggers a browser download.
|
|
892
|
+
*/
|
|
893
|
+
#downloadZip(files, name = "files", comment = "", addDateInName = false) {
|
|
894
|
+
name = addDateInName ? this.#addDateToName(name) : name;
|
|
895
|
+
|
|
896
|
+
const JSZip = require("jszip");
|
|
897
|
+
const zip = new JSZip();
|
|
898
|
+
zip.comment = comment;
|
|
899
|
+
|
|
900
|
+
const rootFolder = zip.folder(name);
|
|
901
|
+
Object.keys(files).forEach((key) => {
|
|
902
|
+
rootFolder.file(key, files[key]);
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
zip.generateAsync({ type: "blob" }).then((content) => {
|
|
906
|
+
const zipName = `${name}.zip`;
|
|
907
|
+
const a = document.createElement("a");
|
|
908
|
+
const url = window.URL.createObjectURL(content);
|
|
909
|
+
a.href = url;
|
|
910
|
+
a.download = zipName;
|
|
911
|
+
a.click();
|
|
912
|
+
window.URL.revokeObjectURL(url);
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Opens a modal dialog for downloading the 3D scene, model, or environment.
|
|
918
|
+
* @private
|
|
919
|
+
* @returns {void}
|
|
920
|
+
*/
|
|
921
|
+
#openDownloadDialog() {
|
|
922
|
+
this.#getPrefViewerComponent();
|
|
923
|
+
if (!this.#prefViewer) {
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const header = "Download 3D Scene";
|
|
928
|
+
const content = `
|
|
929
|
+
<form slot="content" id="download-dialog-form" style="display:flex;flex-direction:column;gap:16px;">
|
|
930
|
+
<h4>Content</h4>
|
|
931
|
+
<div style="display:flex;flex-direction:row;gap:16px;margin:0 8px 0 8px;">
|
|
932
|
+
<label><input type="radio" name="content" value="1"> Model</label>
|
|
933
|
+
<label><input type="radio" name="content" value="2"> Scene</label>
|
|
934
|
+
<label><input type="radio" name="content" value="0" checked> Both</label>
|
|
935
|
+
</div>
|
|
936
|
+
<h4>Format</h4>
|
|
937
|
+
<div style="display:flex;flex-direction:row;gap:16px;margin:0 8px 0 8px;">
|
|
938
|
+
<label><input type="radio" name="format" value="gltf" checked> glTF (ZIP)</label>
|
|
939
|
+
<label><input type="radio" name="format" value="glb"> GLB</label>
|
|
940
|
+
<label><input type="radio" name="format" value="usdz"> USDZ</label>
|
|
941
|
+
</div>
|
|
942
|
+
</form>`;
|
|
943
|
+
const footer = `
|
|
944
|
+
<button type="button" class="primary" id="download-dialog-download">Download</button>
|
|
945
|
+
<button type="button" id="download-dialog-cancel">Cancel</button>`;
|
|
946
|
+
|
|
947
|
+
const dialog = this.#prefViewer.openDialog(header, content, footer);
|
|
948
|
+
|
|
949
|
+
if (!dialog) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// Button event handlers
|
|
954
|
+
const form = dialog.querySelector("#download-dialog-form");
|
|
955
|
+
const downloadButton = dialog.querySelector("#download-dialog-download");
|
|
956
|
+
const cancelButton = dialog.querySelector("#download-dialog-cancel");
|
|
957
|
+
|
|
958
|
+
downloadButton.onclick = () => {
|
|
959
|
+
const contentValue = form.content.value;
|
|
960
|
+
const formatValue = form.format.value;
|
|
961
|
+
switch (formatValue) {
|
|
962
|
+
case "glb":
|
|
963
|
+
this.downloadGLB(Number(contentValue));
|
|
964
|
+
break;
|
|
965
|
+
case "gltf":
|
|
966
|
+
this.downloadGLTF(Number(contentValue));
|
|
967
|
+
break;
|
|
968
|
+
case "usdz":
|
|
969
|
+
this.downloadUSDZ(Number(contentValue));
|
|
970
|
+
break;
|
|
971
|
+
}
|
|
972
|
+
this.#prefViewer.closeDialog();
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
cancelButton.onclick = () => {
|
|
976
|
+
this.#prefViewer.closeDialog();
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
|
|
812
980
|
/**
|
|
813
981
|
* ---------------------------
|
|
814
982
|
* Public methods
|
|
@@ -830,7 +998,7 @@ export default class BabylonJSController {
|
|
|
830
998
|
this.#scene.clearColor = new Color4(1, 1, 1, 1);
|
|
831
999
|
this.#createCamera();
|
|
832
1000
|
this.#createLights();
|
|
833
|
-
this.#
|
|
1001
|
+
this.#enableInteraction();
|
|
834
1002
|
await this.#createXRExperience();
|
|
835
1003
|
this.#startRender();
|
|
836
1004
|
this.#canvasResizeObserver = new ResizeObserver(() => this.#engine && this.#engine.resize());
|
|
@@ -844,8 +1012,13 @@ export default class BabylonJSController {
|
|
|
844
1012
|
* @returns {void}
|
|
845
1013
|
*/
|
|
846
1014
|
disable() {
|
|
847
|
-
this.#disposeEngine();
|
|
848
1015
|
this.#canvasResizeObserver.disconnect();
|
|
1016
|
+
this.#disableInteraction();
|
|
1017
|
+
if (this.#babylonJSAnimationController) {
|
|
1018
|
+
this.#babylonJSAnimationController.dispose();
|
|
1019
|
+
this.#babylonJSAnimationController = null;
|
|
1020
|
+
}
|
|
1021
|
+
this.#disposeEngine();
|
|
849
1022
|
}
|
|
850
1023
|
|
|
851
1024
|
/**
|
|
@@ -909,50 +1082,105 @@ export default class BabylonJSController {
|
|
|
909
1082
|
}
|
|
910
1083
|
|
|
911
1084
|
/**
|
|
912
|
-
*
|
|
1085
|
+
* Downloads the current scene, model, or environment as a GLB file.
|
|
913
1086
|
* @public
|
|
1087
|
+
* @param {number} content - 0 for full scene, 1 for model, 2 for environment.
|
|
914
1088
|
* @returns {void}
|
|
915
1089
|
*/
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1090
|
+
downloadGLB(content = 0) {
|
|
1091
|
+
let fileName = "";
|
|
1092
|
+
let container = null;
|
|
1093
|
+
switch (content) {
|
|
1094
|
+
case 0:
|
|
1095
|
+
fileName = "full-scene";
|
|
1096
|
+
container = this.#scene;
|
|
1097
|
+
break;
|
|
1098
|
+
case 1:
|
|
1099
|
+
fileName = "model";
|
|
1100
|
+
container = this.#containers.model.assetContainer;
|
|
1101
|
+
break;
|
|
1102
|
+
case 2:
|
|
1103
|
+
fileName = "scene";
|
|
1104
|
+
container = this.#containers.environment.assetContainer;
|
|
1105
|
+
break;
|
|
1106
|
+
default:
|
|
1107
|
+
break;
|
|
1108
|
+
}
|
|
1109
|
+
fileName = this.#addDateToName(fileName);
|
|
1110
|
+
GLTF2Export.GLBAsync(container, fileName, { exportWithoutWaitingForScene: true }).then((glb) => {
|
|
1111
|
+
if (glb) {
|
|
1112
|
+
glb.downloadFiles();
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
919
1115
|
}
|
|
920
1116
|
|
|
921
1117
|
/**
|
|
922
|
-
*
|
|
1118
|
+
* Downloads the current scene, model, or environment as a glTF ZIP file.
|
|
923
1119
|
* @public
|
|
1120
|
+
* @param {number} content - 0 for full scene, 1 for model, 2 for environment.
|
|
924
1121
|
* @returns {void}
|
|
925
1122
|
*/
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1123
|
+
downloadGLTF(content = 0) {
|
|
1124
|
+
let fileName = "";
|
|
1125
|
+
let container = null;
|
|
1126
|
+
let comment = "";
|
|
1127
|
+
switch (content) {
|
|
1128
|
+
case 0:
|
|
1129
|
+
fileName = "full-scene";
|
|
1130
|
+
comment = "Export of the complete scene in glTF format, including the model and the environment.";
|
|
1131
|
+
container = this.#scene;
|
|
1132
|
+
break;
|
|
1133
|
+
case 1:
|
|
1134
|
+
fileName = "model";
|
|
1135
|
+
comment = "Export of model scene in glTF format.";
|
|
1136
|
+
container = this.#containers.model.assetContainer;
|
|
1137
|
+
break;
|
|
1138
|
+
case 2:
|
|
1139
|
+
fileName = "scene";
|
|
1140
|
+
comment = "Export of the environment scene in glTF format.";
|
|
1141
|
+
container = this.#containers.environment.assetContainer;
|
|
1142
|
+
break;
|
|
1143
|
+
default:
|
|
1144
|
+
break;
|
|
1145
|
+
}
|
|
1146
|
+
comment += "\nPrefViewer by Preference, S.L.\npreference.com\n";
|
|
1147
|
+
GLTF2Export.GLTFAsync(container, fileName, { exportWithoutWaitingForScene: true }).then((gltf) => {
|
|
1148
|
+
if (gltf?.files) {
|
|
1149
|
+
this.#downloadZip(gltf.files, fileName, comment, true);
|
|
931
1150
|
}
|
|
932
1151
|
});
|
|
933
1152
|
}
|
|
934
1153
|
|
|
935
1154
|
/**
|
|
936
|
-
*
|
|
1155
|
+
* Downloads the current scene, model, or environment as a USDZ file.
|
|
937
1156
|
* @public
|
|
1157
|
+
* @param {number} content - 0 for full scene, 1 for model, 2 for environment.
|
|
938
1158
|
* @returns {void}
|
|
939
1159
|
*/
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1160
|
+
downloadUSDZ(content = 0) {
|
|
1161
|
+
let fileName = "";
|
|
1162
|
+
let container = null;
|
|
1163
|
+
switch (content) {
|
|
1164
|
+
case 0:
|
|
1165
|
+
fileName = "full-scene";
|
|
1166
|
+
container = this.#scene;
|
|
1167
|
+
break;
|
|
1168
|
+
case 1:
|
|
1169
|
+
fileName = "model";
|
|
1170
|
+
container = this.#containers.model.assetContainer;
|
|
1171
|
+
break;
|
|
1172
|
+
case 2:
|
|
1173
|
+
fileName = "scene";
|
|
1174
|
+
container = this.#containers.environment.assetContainer;
|
|
1175
|
+
break;
|
|
1176
|
+
default:
|
|
1177
|
+
break;
|
|
1178
|
+
}
|
|
1179
|
+
fileName = this.#addDateToName(fileName);
|
|
1180
|
+
USDZExportAsync(container).then((response) => {
|
|
943
1181
|
if (response) {
|
|
944
1182
|
Tools.Download(new Blob([response], { type: "model/vnd.usdz+zip" }), `${fileName}.usdz`);
|
|
945
1183
|
}
|
|
946
1184
|
});
|
|
947
1185
|
}
|
|
948
|
-
|
|
949
|
-
/**
|
|
950
|
-
* Initiates download of the entire scene (model and environment) as a GLB file.
|
|
951
|
-
* @public
|
|
952
|
-
* @returns {void}
|
|
953
|
-
*/
|
|
954
|
-
downloadModelAndSceneGLB() {
|
|
955
|
-
const fileName = "scene";
|
|
956
|
-
GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
|
|
957
|
-
}
|
|
958
1186
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* Variables */
|
|
2
|
+
pref-viewer-2d {
|
|
3
|
+
--pref-viewer-2d-bg-color: #ffffff;
|
|
4
|
+
--pref-viewer-2d-svg-padding: 10px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
pref-viewer-2d[visible="true"] {
|
|
8
|
+
display: block;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
pref-viewer-2d[visible="false"] {
|
|
12
|
+
display: none;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pref-viewer-2d {
|
|
16
|
+
grid-column: 1;
|
|
17
|
+
grid-row: 1;
|
|
18
|
+
overflow: hidden;
|
|
19
|
+
min-width: 0;
|
|
20
|
+
min-height: 0;
|
|
21
|
+
align-self: stretch;
|
|
22
|
+
justify-self: stretch;
|
|
23
|
+
background: var(--pref-viewer-2d-bg-color);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pref-viewer-2d,
|
|
27
|
+
pref-viewer-2d>div,
|
|
28
|
+
pref-viewer-2d>div>svg {
|
|
29
|
+
width: 100%;
|
|
30
|
+
height: 100%;
|
|
31
|
+
display: block;
|
|
32
|
+
position: relative;
|
|
33
|
+
outline: none;
|
|
34
|
+
box-sizing: border-box;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pref-viewer-2d>div>svg {
|
|
38
|
+
padding: var(--pref-viewer-2d-svg-padding);
|
|
39
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
pref-viewer-3d[visible="true"] {
|
|
2
|
+
display: block;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
pref-viewer-3d[visible="false"] {
|
|
6
|
+
display: none;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
pref-viewer-3d {
|
|
10
|
+
grid-column: 1;
|
|
11
|
+
grid-row: 1;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
min-width: 0;
|
|
14
|
+
min-height: 0;
|
|
15
|
+
align-self: stretch;
|
|
16
|
+
justify-self: stretch;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pref-viewer-3d,
|
|
20
|
+
pref-viewer-3d>div,
|
|
21
|
+
pref-viewer-3d>div>canvas {
|
|
22
|
+
width: 100%;
|
|
23
|
+
height: 100%;
|
|
24
|
+
display: block;
|
|
25
|
+
position: relative;
|
|
26
|
+
outline: none;
|
|
27
|
+
box-sizing: border-box;
|
|
28
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/* Variables */
|
|
2
|
+
pref-viewer-dialog {
|
|
3
|
+
--brand-color: #ff6700;
|
|
4
|
+
--dialog-general-space: 16px;
|
|
5
|
+
--dialog-bg-color: #ffffff;
|
|
6
|
+
--dialog-backdrop-color: rgba(0, 0, 0, 0.25);
|
|
7
|
+
--dialog-border-color: #e7e7e7;
|
|
8
|
+
--dialog-border-radius: 8px;
|
|
9
|
+
--dialog-box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
|
|
10
|
+
--button-default-bg-color: #bbbbbb;
|
|
11
|
+
--button-default-bg-color-hover: #a1a1a1;
|
|
12
|
+
--button-primary-bg-color: color-mix(in oklab, var(--brand-color), white 25%);
|
|
13
|
+
--button-primary-bg-color-hover: var(--brand-color);
|
|
14
|
+
--button-border-radius: 4px;
|
|
15
|
+
--button-padding-horizontal: 16px;
|
|
16
|
+
--button-padding-vertical: 8px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pref-viewer-dialog:not {
|
|
20
|
+
display: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pref-viewer-dialog[open] {
|
|
24
|
+
font-family: 'Roboto', ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
|
25
|
+
grid-row: 1;
|
|
26
|
+
grid-column: 1;
|
|
27
|
+
overflow: hidden;
|
|
28
|
+
min-width: 0;
|
|
29
|
+
min-height: 0;
|
|
30
|
+
align-self: stretch;
|
|
31
|
+
justify-self: stretch;
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
background-color: var(--dialog-backdrop-color);
|
|
36
|
+
position: relative;
|
|
37
|
+
z-index: 1000;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pref-viewer-dialog>.dialog-wrapper {
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
align-items: stretch;
|
|
44
|
+
background: var(--dialog-bg-color);
|
|
45
|
+
border: 1px solid var(--dialog-border-color);
|
|
46
|
+
border-radius: var(--dialog-border-radius);
|
|
47
|
+
box-shadow: var(--dialog-box-shadow);
|
|
48
|
+
padding: 0;
|
|
49
|
+
min-width: 320px;
|
|
50
|
+
max-width: 90%;
|
|
51
|
+
max-height: 90%;
|
|
52
|
+
overflow: auto;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
pref-viewer-dialog .dialog-header {
|
|
56
|
+
padding: var(--dialog-general-space);
|
|
57
|
+
border-bottom: 1px solid var(--dialog-border-color);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pref-viewer-dialog .dialog-header h3 {
|
|
61
|
+
margin: 0;
|
|
62
|
+
font-weight: 500;
|
|
63
|
+
font-size: 1.1em;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pref-viewer-dialog .dialog-content {
|
|
67
|
+
padding: var(--dialog-general-space);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pref-viewer-dialog .dialog-content h4 {
|
|
71
|
+
margin: 0;
|
|
72
|
+
font-weight: 500;
|
|
73
|
+
font-size: 1.05em;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pref-viewer-dialog .dialog-footer {
|
|
77
|
+
border-top: 1px solid var(--dialog-border-color);
|
|
78
|
+
padding: var(--dialog-general-space);
|
|
79
|
+
display: flex;
|
|
80
|
+
gap: var(--dialog-general-space);
|
|
81
|
+
justify-content: stretch;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
pref-viewer-dialog .dialog-footer button {
|
|
85
|
+
width: 100%;
|
|
86
|
+
font-size: 1em;
|
|
87
|
+
padding: var(--button-padding-vertical) var(--button-padding-horizontal);
|
|
88
|
+
border-radius: var(--button-border-radius);
|
|
89
|
+
border: none;
|
|
90
|
+
background: var(--button-default-bg-color);
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
transition: background 0.2s;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pref-viewer-dialog .dialog-footer button.primary {
|
|
96
|
+
background: var(--button-primary-bg-color);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
pref-viewer-dialog .dialog-footer button:hover {
|
|
100
|
+
background: var(--button-default-bg-color-hover);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
pref-viewer-dialog .dialog-footer button.primary:hover {
|
|
104
|
+
background: var(--button-primary-bg-color-hover);
|
|
105
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PrefViewer2D } from "./pref-viewer-2d.js";
|
|
2
2
|
import { PrefViewer3D } from "./pref-viewer-3d.js";
|
|
3
|
+
import { PrefViewerDialog } from "./pref-viewer-dialog.js";
|
|
3
4
|
import { PrefViewerTask } from "./pref-viewer-task.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -29,12 +30,32 @@ import { PrefViewerTask } from "./pref-viewer-task.js";
|
|
|
29
30
|
* - mode: Viewer mode ("2d" or "3d").
|
|
30
31
|
*
|
|
31
32
|
* Public Methods:
|
|
32
|
-
* - loadConfig(config)
|
|
33
|
-
* -
|
|
33
|
+
* - loadConfig(config): Loads a configuration object or JSON string.
|
|
34
|
+
* - loadModel(model): Loads a model object or JSON string.
|
|
35
|
+
* - loadScene(scene): Loads a scene/environment object or JSON string.
|
|
36
|
+
* - loadMaterials(materials): Loads materials object or JSON string.
|
|
37
|
+
* - loadDrawing(drawing): Loads a drawing object or JSON string.
|
|
38
|
+
* - setOptions(options): Sets viewer options from an object or JSON string.
|
|
34
39
|
* - setMode(mode): Sets the viewer mode to "2d" or "3d" and updates component visibility.
|
|
35
|
-
* - showModel()
|
|
36
|
-
* -
|
|
37
|
-
* -
|
|
40
|
+
* - showModel(): Shows the 3D model.
|
|
41
|
+
* - hideModel(): Hides the 3D model.
|
|
42
|
+
* - showScene(): Shows the 3D environment/scene.
|
|
43
|
+
* - hideScene(): Hides the 3D environment/scene.
|
|
44
|
+
* - zoomCenter(): Centers the 2D drawing view.
|
|
45
|
+
* - zoomExtentsAll(): Zooms the 2D drawing to fit all content.
|
|
46
|
+
* - zoomIn(): Zooms in on the 2D drawing.
|
|
47
|
+
* - zoomOut(): Zooms out of the 2D drawing.
|
|
48
|
+
* - downloadModelGLB(): Downloads the current 3D model as a GLB file.
|
|
49
|
+
* - downloadModelGLTF(): Downloads the current 3D model as a glTF ZIP file.
|
|
50
|
+
* - downloadModelUSDZ(): Downloads the current 3D model as a USDZ file.
|
|
51
|
+
* - downloadModelAndSceneGLB(): Downloads both the model and scene as a GLB file.
|
|
52
|
+
* - downloadModelAndSceneGLTF(): Downloads both the model and scene as a glTF ZIP file.
|
|
53
|
+
* - downloadModelAndSceneUSDZ(): Downloads both the model and scene as a USDZ file.
|
|
54
|
+
* - downloadSceneGLB(): Downloads the environment as a GLB file.
|
|
55
|
+
* - downloadSceneGLTF(): Downloads the environment as a glTF ZIP file.
|
|
56
|
+
* - downloadSceneUSDZ(): Downloads the environment as a USDZ file.
|
|
57
|
+
* - openDialog(title, content, footer): Opens a modal dialog with the specified title, content, and footer.
|
|
58
|
+
* - closeDialog(): Closes the currently open dialog, if any, and removes it from the DOM.
|
|
38
59
|
*
|
|
39
60
|
* Public Properties:
|
|
40
61
|
* - isInitialized: Indicates if the viewer is initialized.
|
|
@@ -63,8 +84,10 @@ class PrefViewer extends HTMLElement {
|
|
|
63
84
|
|
|
64
85
|
#taskQueue = [];
|
|
65
86
|
|
|
87
|
+
#wrapper = null;
|
|
66
88
|
#component2D = null;
|
|
67
89
|
#component3D = null;
|
|
90
|
+
#dialog = null;
|
|
68
91
|
|
|
69
92
|
/**
|
|
70
93
|
* Creates a new PrefViewer instance and attaches a shadow DOM.
|
|
@@ -145,6 +168,14 @@ class PrefViewer extends HTMLElement {
|
|
|
145
168
|
* @returns {void|boolean} Returns false if initialization fails; otherwise void.
|
|
146
169
|
*/
|
|
147
170
|
connectedCallback() {
|
|
171
|
+
this.#wrapper = document.createElement("div");
|
|
172
|
+
this.#wrapper.classList.add("pref-viewer-wrapper");
|
|
173
|
+
this.shadowRoot.append(this.#wrapper);
|
|
174
|
+
|
|
175
|
+
const style = document.createElement("style");
|
|
176
|
+
style.textContent = `@import "../src/css/pref-viewer.css";`;
|
|
177
|
+
this.shadowRoot.append(style);
|
|
178
|
+
|
|
148
179
|
this.#createComponent3D();
|
|
149
180
|
this.#createComponent2D();
|
|
150
181
|
|
|
@@ -166,7 +197,7 @@ class PrefViewer extends HTMLElement {
|
|
|
166
197
|
this.#component2D = document.createElement("pref-viewer-2d");
|
|
167
198
|
this.#component2D.setAttribute("visible", "false");
|
|
168
199
|
this.#component2D.addEventListener("drawing-zoom-changed", this.#on2DZoomChanged.bind(this));
|
|
169
|
-
this.
|
|
200
|
+
this.#wrapper.appendChild(this.#component2D);
|
|
170
201
|
}
|
|
171
202
|
|
|
172
203
|
/**
|
|
@@ -178,7 +209,7 @@ class PrefViewer extends HTMLElement {
|
|
|
178
209
|
#createComponent3D() {
|
|
179
210
|
this.#component3D = document.createElement("pref-viewer-3d");
|
|
180
211
|
this.#component3D.setAttribute("visible", "false");
|
|
181
|
-
this.
|
|
212
|
+
this.#wrapper.appendChild(this.#component3D);
|
|
182
213
|
}
|
|
183
214
|
|
|
184
215
|
/**
|
|
@@ -480,11 +511,14 @@ class PrefViewer extends HTMLElement {
|
|
|
480
511
|
this.#component3D?.hide();
|
|
481
512
|
this.#component2D?.show();
|
|
482
513
|
} else {
|
|
483
|
-
this.#component3D?.show();
|
|
484
514
|
this.#component2D?.hide();
|
|
515
|
+
this.#component3D?.show();
|
|
485
516
|
}
|
|
486
517
|
if (this.getAttribute("mode") !== mode) {
|
|
487
518
|
this.setAttribute("mode", mode);
|
|
519
|
+
if (this.#dialog) {
|
|
520
|
+
this.closeDialog();
|
|
521
|
+
}
|
|
488
522
|
}
|
|
489
523
|
}
|
|
490
524
|
|
|
@@ -689,6 +723,19 @@ class PrefViewer extends HTMLElement {
|
|
|
689
723
|
this.#component3D.downloadModelGLB();
|
|
690
724
|
}
|
|
691
725
|
|
|
726
|
+
/**
|
|
727
|
+
* Initiates download of the current 3D model in GLTF format.
|
|
728
|
+
* @public
|
|
729
|
+
* @returns {void}
|
|
730
|
+
*/
|
|
731
|
+
downloadModelGLTF() {
|
|
732
|
+
if (!this.#component3D) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
this.#component3D.downloadModelGLTF();
|
|
737
|
+
}
|
|
738
|
+
|
|
692
739
|
/**
|
|
693
740
|
* Initiates download of the current 3D model in USDZ format.
|
|
694
741
|
* @public
|
|
@@ -703,7 +750,33 @@ class PrefViewer extends HTMLElement {
|
|
|
703
750
|
}
|
|
704
751
|
|
|
705
752
|
/**
|
|
706
|
-
* Initiates download of
|
|
753
|
+
* Initiates download of the current complete scene (3D model and environment) in GLB format.
|
|
754
|
+
* @public
|
|
755
|
+
* @returns {void}
|
|
756
|
+
*/
|
|
757
|
+
downloadModelAndSceneGLB() {
|
|
758
|
+
if (!this.#component3D) {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
this.#component3D.downloadModelAndSceneGLB();
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Initiates download of the current complete scene (3D model and environment) in GLTF format.
|
|
767
|
+
* @public
|
|
768
|
+
* @returns {void}
|
|
769
|
+
*/
|
|
770
|
+
downloadModelAndSceneGLTF() {
|
|
771
|
+
if (!this.#component3D) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
this.#component3D.downloadModelAndSceneGLTF();
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Initiates download of the current complete scene (3D model and environment) in USDZ format.
|
|
707
780
|
* @public
|
|
708
781
|
* @returns {void}
|
|
709
782
|
*/
|
|
@@ -716,18 +789,82 @@ class PrefViewer extends HTMLElement {
|
|
|
716
789
|
}
|
|
717
790
|
|
|
718
791
|
/**
|
|
719
|
-
* Initiates download of
|
|
792
|
+
* Initiates download of the current 3D environment in GLB format.
|
|
720
793
|
* @public
|
|
721
794
|
* @returns {void}
|
|
722
795
|
*/
|
|
723
|
-
|
|
796
|
+
downloadSceneGLB() {
|
|
724
797
|
if (!this.#component3D) {
|
|
725
798
|
return;
|
|
726
799
|
}
|
|
727
800
|
|
|
728
|
-
this.#component3D.
|
|
801
|
+
this.#component3D.downloadSceneGLB();
|
|
729
802
|
}
|
|
730
803
|
|
|
804
|
+
/**
|
|
805
|
+
* Initiates download of the current 3D environment in GLTF format.
|
|
806
|
+
* @public
|
|
807
|
+
* @returns {void}
|
|
808
|
+
*/
|
|
809
|
+
downloadSceneGLTF() {
|
|
810
|
+
if (!this.#component3D) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
this.#component3D.downloadSceneGLTF();
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Initiates download of the current 3D environment in USDZ format.
|
|
819
|
+
* @public
|
|
820
|
+
* @returns {void}
|
|
821
|
+
*/
|
|
822
|
+
downloadSceneUSDZ() {
|
|
823
|
+
if (!this.#component3D) {
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
this.#component3D.downloadSceneUSDZ();
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Opens a modal dialog with the specified title, content, and footer.
|
|
831
|
+
* @public
|
|
832
|
+
* @param {string} title - The dialog title to display in the header.
|
|
833
|
+
* @param {string} content - The HTML content to display in the dialog body.
|
|
834
|
+
* @param {string} footer - The HTML content to display in the dialog footer (e.g., action buttons).
|
|
835
|
+
* @returns {HTMLElement} The created dialog element.
|
|
836
|
+
* @description
|
|
837
|
+
* If a dialog is already open, it is closed before opening the new one.
|
|
838
|
+
* The dialog is appended to the viewer's shadow DOM and returned for further manipulation.
|
|
839
|
+
*/
|
|
840
|
+
openDialog(title, content, footer) {
|
|
841
|
+
if (this.#dialog && this.#dialog.hasAttribute("open")) {
|
|
842
|
+
this.#dialog.close();
|
|
843
|
+
}
|
|
844
|
+
this.#dialog = document.createElement("pref-viewer-dialog");
|
|
845
|
+
this.shadowRoot.querySelector(".pref-viewer-wrapper").appendChild(this.#dialog);
|
|
846
|
+
const opened = this.#dialog.open(title, content, footer);
|
|
847
|
+
return opened ? this.#dialog : null;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Closes the currently open dialog, if any, and removes it from the DOM.
|
|
852
|
+
* @public
|
|
853
|
+
* @returns {void}
|
|
854
|
+
*/
|
|
855
|
+
closeDialog() {
|
|
856
|
+
if (this.#dialog) {
|
|
857
|
+
this.#dialog.close();
|
|
858
|
+
this.#dialog = null;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* ---------------------------
|
|
864
|
+
* Public properties
|
|
865
|
+
* ---------------------------
|
|
866
|
+
*/
|
|
867
|
+
|
|
731
868
|
/**
|
|
732
869
|
* Indicates whether the viewer has been initialized.
|
|
733
870
|
* @public
|
|
@@ -776,4 +913,5 @@ class PrefViewer extends HTMLElement {
|
|
|
776
913
|
|
|
777
914
|
customElements.define("pref-viewer-2d", PrefViewer2D);
|
|
778
915
|
customElements.define("pref-viewer-3d", PrefViewer3D);
|
|
916
|
+
customElements.define("pref-viewer-dialog", PrefViewerDialog);
|
|
779
917
|
customElements.define("pref-viewer", PrefViewer);
|
package/src/pref-viewer-2d.js
CHANGED
|
@@ -144,7 +144,7 @@ export class PrefViewer2D extends HTMLElement {
|
|
|
144
144
|
this.#wrapper = document.createElement("div");
|
|
145
145
|
this.append(this.#wrapper);
|
|
146
146
|
const style = document.createElement("style");
|
|
147
|
-
style.textContent =
|
|
147
|
+
style.textContent = `@import "../src/css/pref-viewer-2d.css";`;
|
|
148
148
|
this.append(style);
|
|
149
149
|
}
|
|
150
150
|
|
package/src/pref-viewer-3d.js
CHANGED
|
@@ -31,9 +31,14 @@ import BabylonJSController from "./babylonjs-controller.js";
|
|
|
31
31
|
* - showEnvironment(): Shows the 3D environment/scene.
|
|
32
32
|
* - hideEnvironment(): Hides the 3D environment/scene.
|
|
33
33
|
* - downloadModelGLB(): Downloads the current 3D model as a GLB file.
|
|
34
|
+
* - downloadModelGLTF(): Downloads the current 3D model as a glTF ZIP file.
|
|
34
35
|
* - downloadModelUSDZ(): Downloads the current 3D model as a USDZ file.
|
|
35
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.
|
|
36
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.
|
|
37
42
|
*
|
|
38
43
|
* Public Properties:
|
|
39
44
|
* - isInitialized: Indicates whether the component has completed initialization.
|
|
@@ -155,7 +160,7 @@ export class PrefViewer3D extends HTMLElement {
|
|
|
155
160
|
this.#canvas = document.createElement("canvas");
|
|
156
161
|
this.#wrapper.appendChild(this.#canvas);
|
|
157
162
|
const style = document.createElement("style");
|
|
158
|
-
style.textContent =
|
|
163
|
+
style.textContent = `@import "../src/css/pref-viewer-3d.css";`;
|
|
159
164
|
this.append(style);
|
|
160
165
|
}
|
|
161
166
|
|
|
@@ -567,7 +572,19 @@ export class PrefViewer3D extends HTMLElement {
|
|
|
567
572
|
if (!this.#babylonJSController) {
|
|
568
573
|
return;
|
|
569
574
|
}
|
|
570
|
-
this.#babylonJSController.
|
|
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);
|
|
571
588
|
}
|
|
572
589
|
|
|
573
590
|
/**
|
|
@@ -579,11 +596,35 @@ export class PrefViewer3D extends HTMLElement {
|
|
|
579
596
|
if (!this.#babylonJSController) {
|
|
580
597
|
return;
|
|
581
598
|
}
|
|
582
|
-
this.#babylonJSController.
|
|
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);
|
|
583
612
|
}
|
|
584
613
|
|
|
585
614
|
/**
|
|
586
|
-
* Downloads
|
|
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.
|
|
587
628
|
* @public
|
|
588
629
|
* @returns {void}
|
|
589
630
|
*/
|
|
@@ -591,19 +632,43 @@ export class PrefViewer3D extends HTMLElement {
|
|
|
591
632
|
if (!this.#babylonJSController) {
|
|
592
633
|
return;
|
|
593
634
|
}
|
|
594
|
-
this.#babylonJSController.
|
|
635
|
+
this.#babylonJSController.downloadUSDZ(0);
|
|
595
636
|
}
|
|
596
637
|
|
|
597
638
|
/**
|
|
598
|
-
* Downloads
|
|
639
|
+
* Downloads the current 3D environment as a GLB file.
|
|
599
640
|
* @public
|
|
600
641
|
* @returns {void}
|
|
601
642
|
*/
|
|
602
|
-
|
|
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() {
|
|
603
668
|
if (!this.#babylonJSController) {
|
|
604
669
|
return;
|
|
605
670
|
}
|
|
606
|
-
this.#babylonJSController.
|
|
671
|
+
this.#babylonJSController.downloadUSDZ(2);
|
|
607
672
|
}
|
|
608
673
|
|
|
609
674
|
/**
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PrefViewerDialog - Custom Web Component for displaying modal dialogs in PrefViewer.
|
|
3
|
+
*
|
|
4
|
+
* Overview:
|
|
5
|
+
* - Provides a modal dialog overlay for user interactions such as downloads or confirmations.
|
|
6
|
+
* - Handles DOM creation, styling, and open/close logic.
|
|
7
|
+
* - Ensures dialog content is centered and styled appropriately.
|
|
8
|
+
* - Automatically focuses the canvas when closed for improved accessibility.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* - Use as a custom HTML element: <pref-viewer-dialog></pref-viewer-dialog>
|
|
12
|
+
* - Call open(title, content, footer) to display the dialog with a header, content, and footer.
|
|
13
|
+
* - Call close() to hide and remove the dialog.
|
|
14
|
+
*
|
|
15
|
+
* Methods:
|
|
16
|
+
* - constructor(): Initializes the custom element.
|
|
17
|
+
* - connectedCallback(): Called when the element is added to the DOM; sets up DOM and styles.
|
|
18
|
+
* - disconnectedCallback(): Called when the element is removed from the DOM; cleans up resources.
|
|
19
|
+
* - open(title, content, footer): Displays the dialog and sets its header, content, and footer.
|
|
20
|
+
* - close(): Hides and removes the dialog, refocuses the canvas if available.
|
|
21
|
+
* - #createDOMElements(): Creates the dialog structure and applies styles.
|
|
22
|
+
*/
|
|
23
|
+
export class PrefViewerDialog extends HTMLElement {
|
|
24
|
+
#wrapper = null;
|
|
25
|
+
#header = null;
|
|
26
|
+
#content = null;
|
|
27
|
+
#footer = null;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Initializes the custom dialog element.
|
|
31
|
+
* Only calls super(); heavy initialization is deferred to connectedCallback.
|
|
32
|
+
*/
|
|
33
|
+
constructor() {
|
|
34
|
+
super();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Called when the element is inserted into the DOM.
|
|
39
|
+
* Sets up the dialog's DOM structure and styles.
|
|
40
|
+
* @returns {void}
|
|
41
|
+
*/
|
|
42
|
+
connectedCallback() {
|
|
43
|
+
this.#createDOMElements();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Called when the element is removed from the DOM.
|
|
48
|
+
* Cleans up resources and event listeners.
|
|
49
|
+
* @returns {void}
|
|
50
|
+
*/
|
|
51
|
+
disconnectedCallback() {
|
|
52
|
+
this.removeEventListener("click", this.#closeOnBackdropClick.bind(this));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Creates the dialog's DOM structure and applies CSS styles.
|
|
57
|
+
* Adds a click handler to close the dialog when clicking outside the content.
|
|
58
|
+
* @private
|
|
59
|
+
* @returns {void}
|
|
60
|
+
*/
|
|
61
|
+
#createDOMElements() {
|
|
62
|
+
this.#wrapper = document.createElement("div");
|
|
63
|
+
this.#wrapper.classList.add("dialog-wrapper");
|
|
64
|
+
this.#wrapper.innerHTML = `
|
|
65
|
+
<div class="dialog-header"><h3 class="dialog-header-title"></h3></div>
|
|
66
|
+
<div class="dialog-content"></div>
|
|
67
|
+
<div class="dialog-footer"></div>`;
|
|
68
|
+
this.append(this.#wrapper);
|
|
69
|
+
|
|
70
|
+
const style = document.createElement("style");
|
|
71
|
+
style.textContent = `@import "../src/css/pref-viewer-dialog.css";`;
|
|
72
|
+
this.append(style);
|
|
73
|
+
|
|
74
|
+
this.addEventListener("click", this.#closeOnBackdropClick.bind(this));
|
|
75
|
+
|
|
76
|
+
this.#header = this.#wrapper.querySelector(".dialog-header");
|
|
77
|
+
this.#content = this.#wrapper.querySelector(".dialog-content");
|
|
78
|
+
this.#footer = this.#wrapper.querySelector(".dialog-footer");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#closeOnBackdropClick(event) {
|
|
82
|
+
if (event.target === this) {
|
|
83
|
+
this.close();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Opens the dialog and sets its header, content, and footer.
|
|
89
|
+
* @param {string} title - The dialog title to display in the header.
|
|
90
|
+
* @param {string} content - The HTML content to display in the dialog body.
|
|
91
|
+
* @param {string} footer - The HTML content to display in the dialog footer (e.g., action buttons).
|
|
92
|
+
* @returns {boolean} True if the dialog was opened, false if no content was provided.
|
|
93
|
+
*/
|
|
94
|
+
open(title = "", content = "", footer = "") {
|
|
95
|
+
if (this.#wrapper === null || this.#header === null || this.#content === null || this.#footer === null) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
if (title === "" && content === "" && footer === "") {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (title === "") {
|
|
103
|
+
this.#header.style.display = "none";
|
|
104
|
+
}
|
|
105
|
+
if (footer === "") {
|
|
106
|
+
this.#footer.style.display = "none";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.#header.querySelector(".dialog-header-title").innerHTML = title;
|
|
110
|
+
this.#content.innerHTML = content;
|
|
111
|
+
this.#footer.innerHTML = footer;
|
|
112
|
+
this.setAttribute("open", "");
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Closes and removes the dialog from the DOM.
|
|
118
|
+
* @returns {void}
|
|
119
|
+
*/
|
|
120
|
+
close() {
|
|
121
|
+
this.removeAttribute("open");
|
|
122
|
+
const parent = this.getRootNode().host;
|
|
123
|
+
if (parent) {
|
|
124
|
+
// Refocus 3D canvas or 2D component for accessibility
|
|
125
|
+
const canvas = parent.shadowRoot.querySelector("pref-viewer-3d[visible='true'] canvas");
|
|
126
|
+
if (canvas) {
|
|
127
|
+
canvas.focus();
|
|
128
|
+
} else {
|
|
129
|
+
const component2D = parent.shadowRoot.querySelector("pref-viewer-2d[visible='true']");
|
|
130
|
+
if (component2D) {
|
|
131
|
+
component2D.focus();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
}
|
|
136
|
+
this.#wrapper = this.#header = this.#content = this.#footer = null;
|
|
137
|
+
this.remove();
|
|
138
|
+
}
|
|
139
|
+
}
|