@preference-sl/pref-viewer 2.16.0 → 2.16.2-beta.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/README.md +129 -102
- package/index.d.ts +373 -12
- package/package.json +2 -1
- package/src/babylonjs-animation-opening.js +11 -5
- package/src/babylonjs-controller.js +442 -65
- package/src/panzoom-controller.js +36 -17
- package/src/pref-viewer-2d.js +395 -21
- package/src/pref-viewer-3d.js +140 -85
- package/src/pref-viewer-menu-3d.js +84 -40
- package/src/pref-viewer-task.js +7 -2
- package/src/pref-viewer.js +615 -185
- package/src/styles.js +111 -29
package/src/pref-viewer-3d.js
CHANGED
|
@@ -25,9 +25,6 @@ import FileStorage from "./file-storage.js";
|
|
|
25
25
|
* - show-model: Show or hide the 3D model ("true"/"false").
|
|
26
26
|
* - show-scene: Show or hide the 3D environment ("true"/"false").
|
|
27
27
|
* - visible: Show or hide the entire viewer ("true"/"false").
|
|
28
|
-
* - show-animation-menu: Show or hide animation controls in the 3D controller.
|
|
29
|
-
* - highlight-color: Hex color used for hover highlight on animated meshes.
|
|
30
|
-
* - highlight-enabled: Enable or disable hover highlight ("true"/"false").
|
|
31
28
|
*
|
|
32
29
|
* Public Methods:
|
|
33
30
|
* - show(): Shows the 3D viewer component.
|
|
@@ -40,6 +37,13 @@ import FileStorage from "./file-storage.js";
|
|
|
40
37
|
* - hideModel(): Hides the 3D model.
|
|
41
38
|
* - showEnvironment(): Shows the 3D environment/scene.
|
|
42
39
|
* - hideEnvironment(): Hides the 3D environment/scene.
|
|
40
|
+
* - isIBLAvailable(): Reports whether a valid IBL environment map is currently available.
|
|
41
|
+
* - isAnimationMenuEnabled(): Reports whether the animation control menu is currently enabled.
|
|
42
|
+
* - getDebugInfo(): Returns a diagnostic snapshot of the underlying Babylon.js runtime.
|
|
43
|
+
* - getAnimations(): Returns a snapshot of all available animations and their current state.
|
|
44
|
+
* - playAnimation(name, action): Controls a named animation by applying the specified action.
|
|
45
|
+
* - setAnimationProgress(name, progress): Seeks a named animation to a normalized progress position (0–1).
|
|
46
|
+
* - setAnimationLoop(name, loop): Enables or disables loop mode for a named animation.
|
|
43
47
|
* - downloadModelGLB(): Downloads the current 3D model as a GLB file.
|
|
44
48
|
* - downloadModelGLTF(): Downloads the current 3D model as a glTF ZIP file.
|
|
45
49
|
* - downloadModelUSDZ(): Downloads the current 3D model as a USDZ file.
|
|
@@ -60,14 +64,16 @@ import FileStorage from "./file-storage.js";
|
|
|
60
64
|
* - #checkNeedToUpdateCamera(options): Detects camera option changes and marks them pending.
|
|
61
65
|
* - #checkNeedToUpdateMaterials(options): Resolves material overrides that require updates.
|
|
62
66
|
* - #checkNeedToUpdateIBL(options): Fetches HDR URLs/timestamps and enqueues IBL updates when needed.
|
|
67
|
+
* - #checkNeedToUpdateLighting(options): Resolves time-of-day changes and marks lighting as pending.
|
|
63
68
|
* - #resetUpdateFlags(): Clears pending/success flags after loads or option-setting flows.
|
|
64
69
|
* - #onLoading/#onLoaded/#onSettingOptions/#onSetOptions(): Dispatch lifecycle events and synchronize attributes.
|
|
65
70
|
*
|
|
66
71
|
* Events:
|
|
67
|
-
* - "
|
|
68
|
-
* - "
|
|
69
|
-
* - "
|
|
70
|
-
* - "
|
|
72
|
+
* - "pref-viewer-3d-loading": Dispatched when a loading operation starts. Legacy alias: "scene-loading".
|
|
73
|
+
* - "pref-viewer-3d-loaded": Dispatched when a loading operation completes. Legacy alias: "scene-loaded".
|
|
74
|
+
* - "pref-viewer-3d-options-applying": Dispatched when viewer options are being set. Legacy alias: "scene-setting-options".
|
|
75
|
+
* - "pref-viewer-3d-options-applied": Dispatched when viewer options have been set. Legacy alias: "scene-set-options".
|
|
76
|
+
* - "pref-viewer-3d-animation-changed": Dispatched by OpeningAnimation instances via the pref-viewer-3d host when an animation state, progress, or loop value changes.
|
|
71
77
|
*
|
|
72
78
|
* Notes:
|
|
73
79
|
* - Internal state and heavy initialization are handled in connectedCallback.
|
|
@@ -101,7 +107,7 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
101
107
|
* @returns {string[]} Array of attribute names that trigger attributeChangedCallback.
|
|
102
108
|
*/
|
|
103
109
|
static get observedAttributes() {
|
|
104
|
-
return ["show-model", "show-scene", "visible"
|
|
110
|
+
return ["show-model", "show-scene", "visible"];
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
/**
|
|
@@ -114,7 +120,6 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
114
120
|
* - "show-model": shows or hides the model according to the attribute value ("true"/"false").
|
|
115
121
|
* - "show-scene": shows or hides the scene according to the attribute value ("true"/"false").
|
|
116
122
|
* - "visible": shows or hides the component according to the attribute value ("true"/"false").
|
|
117
|
-
* - "show-animation-menu", "highlight-color", and "highlight-enabled": forward runtime UI/highlight settings to BabylonJSController.
|
|
118
123
|
*/
|
|
119
124
|
attributeChangedCallback(name, _old, value) {
|
|
120
125
|
switch (name) {
|
|
@@ -142,18 +147,6 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
142
147
|
this.hide();
|
|
143
148
|
}
|
|
144
149
|
break;
|
|
145
|
-
case "show-animation-menu":
|
|
146
|
-
case "highlight-color":
|
|
147
|
-
case "highlight-enabled":
|
|
148
|
-
// Pass these to BabylonJSController if initialized
|
|
149
|
-
if (this.#babylonJSController && typeof this.#babylonJSController.setIlluminationConfig === "function") {
|
|
150
|
-
this.#babylonJSController.setIlluminationConfig({
|
|
151
|
-
showAnimationMenu: this.getAttribute("show-animation-menu"),
|
|
152
|
-
highlightColor: this.getAttribute("highlight-color"),
|
|
153
|
-
highlightEnabled: this.getAttribute("highlight-enabled"),
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
break;
|
|
157
150
|
}
|
|
158
151
|
}
|
|
159
152
|
|
|
@@ -234,8 +227,10 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
234
227
|
outerFloor: new MaterialData("outerFloor", undefined, undefined, ["outerFloor"]),
|
|
235
228
|
},
|
|
236
229
|
ibl: new IBLData(),
|
|
237
|
-
|
|
238
|
-
|
|
230
|
+
render: {
|
|
231
|
+
showAnimationMenu: false,
|
|
232
|
+
lightingTimeOfDay: 0.5,
|
|
233
|
+
showLightingTimeOfDay: false,
|
|
239
234
|
},
|
|
240
235
|
},
|
|
241
236
|
};
|
|
@@ -250,15 +245,6 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
250
245
|
this.#babylonJSController = new BabylonJSController(this.#canvas, this.#data.containers, this.#data.options);
|
|
251
246
|
this.#babylonJSController.enable();
|
|
252
247
|
|
|
253
|
-
// Apply highlight attributes that were set before the controller was ready
|
|
254
|
-
const highlightColor = this.getAttribute("highlight-color");
|
|
255
|
-
const highlightEnabled = this.getAttribute("highlight-enabled");
|
|
256
|
-
if (highlightColor !== null || highlightEnabled !== null) {
|
|
257
|
-
this.#babylonJSController.setIlluminationConfig({
|
|
258
|
-
highlightColor: highlightColor ?? undefined,
|
|
259
|
-
highlightEnabled: highlightEnabled ?? undefined,
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
248
|
}
|
|
263
249
|
|
|
264
250
|
/**
|
|
@@ -430,29 +416,37 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
430
416
|
iblState.setValues(url, cachedUrl, intensity, shadows, timeStamp);
|
|
431
417
|
}
|
|
432
418
|
}
|
|
433
|
-
|
|
419
|
+
|
|
434
420
|
return needUpdate;
|
|
435
421
|
}
|
|
436
422
|
|
|
437
423
|
/**
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
424
|
+
* Checks whether any lighting-related value in `options.render` differs from the cached state.
|
|
425
|
+
* `options.render.showLightingTimeOfDay` updates slider visibility, while
|
|
426
|
+
* `options.render.lightingTimeOfDay` updates the normalized 0..1 lighting value.
|
|
441
427
|
* @private
|
|
442
|
-
* @param {object} options - Options payload containing
|
|
443
|
-
* @
|
|
428
|
+
* @param {object} options - Options payload containing a `render` sub-object.
|
|
429
|
+
* @param {boolean|string} [options.render.showLightingTimeOfDay] - Whether the time-of-day slider should be visible.
|
|
430
|
+
* @param {number} [options.render.lightingTimeOfDay] - Desired time-of-day, normalized to 0..1.
|
|
431
|
+
* @returns {boolean} True when either the visibility flag or the time value differs from the cached state.
|
|
444
432
|
*/
|
|
445
433
|
#checkNeedToUpdateLighting(options) {
|
|
446
434
|
if (!options) {
|
|
447
435
|
return false;
|
|
448
436
|
}
|
|
449
437
|
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
438
|
+
const incomingTimeEnabled = options.render?.showLightingTimeOfDay;
|
|
439
|
+
if (incomingTimeEnabled !== undefined) {
|
|
440
|
+
const dataTimeEnabled = this.#data.options.render.showLightingTimeOfDay;
|
|
441
|
+
const needUpdate = dataTimeEnabled !== incomingTimeEnabled;
|
|
442
|
+
this.#data.options.render.showLightingTimeOfDay = incomingTimeEnabled;
|
|
443
|
+
if (needUpdate) {
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const dataTime = this.#data.options.render.lightingTimeOfDay;
|
|
449
|
+
const incomingTime = typeof options.render?.lightingTimeOfDay === "number" ? options.render.lightingTimeOfDay : undefined;
|
|
456
450
|
|
|
457
451
|
if (!Number.isFinite(incomingTime)) {
|
|
458
452
|
return false;
|
|
@@ -460,9 +454,9 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
460
454
|
|
|
461
455
|
const normalizedTime = Math.min(1, Math.max(0, incomingTime));
|
|
462
456
|
const controllerTime = this.#babylonJSController?.getRenderSettings?.()?.lightingTimeOfDay;
|
|
463
|
-
const currentTime = Number.isFinite(controllerTime) ? controllerTime :
|
|
457
|
+
const currentTime = Number.isFinite(controllerTime) ? controllerTime : dataTime;
|
|
464
458
|
const needUpdate = normalizedTime !== currentTime;
|
|
465
|
-
|
|
459
|
+
this.#data.options.render.lightingTimeOfDay = normalizedTime;
|
|
466
460
|
return needUpdate;
|
|
467
461
|
}
|
|
468
462
|
|
|
@@ -477,8 +471,10 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
477
471
|
bubbles: true, // indicates whether the event bubbles up through the DOM tree or not
|
|
478
472
|
cancelable: true, // indicates whether the event can be canceled, and therefore prevented as if the event never happened
|
|
479
473
|
composed: false, // indicates whether or not the event will propagate across the shadow DOM boundary into the standard DOM
|
|
474
|
+
|
|
480
475
|
};
|
|
481
|
-
this.dispatchEvent(new CustomEvent("
|
|
476
|
+
this.dispatchEvent(new CustomEvent("pref-viewer-3d-loading", customEventOptions));
|
|
477
|
+
this.dispatchEvent(new CustomEvent("scene-loading", customEventOptions)); // Legacy alias for backward compatibility
|
|
482
478
|
|
|
483
479
|
this.removeAttribute("loaded");
|
|
484
480
|
this.setAttribute("loading", "");
|
|
@@ -526,7 +522,8 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
526
522
|
composed: false,
|
|
527
523
|
detail: detail,
|
|
528
524
|
};
|
|
529
|
-
this.dispatchEvent(new CustomEvent("
|
|
525
|
+
this.dispatchEvent(new CustomEvent("pref-viewer-3d-loaded", customEventOptions));
|
|
526
|
+
this.dispatchEvent(new CustomEvent("scene-loaded", customEventOptions)); // Legacy alias for backward compatibility
|
|
530
527
|
|
|
531
528
|
this.removeAttribute("loading");
|
|
532
529
|
this.setAttribute("loaded", "");
|
|
@@ -549,7 +546,8 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
549
546
|
cancelable: true,
|
|
550
547
|
composed: false,
|
|
551
548
|
};
|
|
552
|
-
this.dispatchEvent(new CustomEvent("
|
|
549
|
+
this.dispatchEvent(new CustomEvent("pref-viewer-3d-options-applying", customEventOptions));
|
|
550
|
+
this.dispatchEvent(new CustomEvent("scene-setting-options", customEventOptions)); // Legacy alias for backward compatibility
|
|
553
551
|
|
|
554
552
|
this.removeAttribute("loaded");
|
|
555
553
|
this.setAttribute("loading", "");
|
|
@@ -591,7 +589,8 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
591
589
|
composed: false,
|
|
592
590
|
detail: detail,
|
|
593
591
|
};
|
|
594
|
-
this.dispatchEvent(new CustomEvent("
|
|
592
|
+
this.dispatchEvent(new CustomEvent("pref-viewer-3d-options-applied", customEventOptions));
|
|
593
|
+
this.dispatchEvent(new CustomEvent("scene-set-options", customEventOptions)); // Legacy alias for backward compatibility
|
|
595
594
|
|
|
596
595
|
this.removeAttribute("loading");
|
|
597
596
|
this.setAttribute("loaded", "");
|
|
@@ -648,10 +647,15 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
648
647
|
this.#checkNeedToUpdateContainers(config);
|
|
649
648
|
|
|
650
649
|
// Options
|
|
651
|
-
let needUpdateLighting = false;
|
|
652
650
|
const iblOptions = config.scene?.ibl
|
|
653
|
-
|
|
654
|
-
|
|
651
|
+
? { ...(config.options ?? {}), ibl: { ...(config.options?.ibl ?? {}), ...config.scene.ibl } }
|
|
652
|
+
: config.options;
|
|
653
|
+
|
|
654
|
+
if (iblOptions) {
|
|
655
|
+
await this.#checkNeedToUpdateIBL(iblOptions);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
let needUpdateLighting = false;
|
|
655
659
|
|
|
656
660
|
if (config.options) {
|
|
657
661
|
this.#checkNeedToUpdateCamera(config.options);
|
|
@@ -659,16 +663,15 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
659
663
|
needUpdateLighting = this.#checkNeedToUpdateLighting(config.options);
|
|
660
664
|
}
|
|
661
665
|
|
|
662
|
-
if (iblOptions) {
|
|
663
|
-
await this.#checkNeedToUpdateIBL(iblOptions);
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
const loadDetail = await this.#babylonJSController.load(forceReload);
|
|
667
|
-
|
|
668
666
|
if (needUpdateLighting && typeof this.#babylonJSController.setIlluminationConfig === "function") {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
667
|
+
const lightingConfig = {};
|
|
668
|
+
if (config.options?.render?.lightingTimeOfDay !== undefined) {
|
|
669
|
+
lightingConfig.lightingTimeOfDay = config.options.render.lightingTimeOfDay;
|
|
670
|
+
}
|
|
671
|
+
if (config.options?.render?.showLightingTimeOfDay !== undefined) {
|
|
672
|
+
lightingConfig.showLightingTimeOfDay = config.options.render.showLightingTimeOfDay;
|
|
673
|
+
}
|
|
674
|
+
this.#babylonJSController.setIlluminationConfig(lightingConfig);
|
|
672
675
|
}
|
|
673
676
|
|
|
674
677
|
// Apply initial render settings if provided in options
|
|
@@ -676,6 +679,8 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
676
679
|
await this.applyRenderSettings(config.options.render);
|
|
677
680
|
}
|
|
678
681
|
|
|
682
|
+
const loadDetail = await this.#babylonJSController.load(forceReload);
|
|
683
|
+
|
|
679
684
|
return { ...loadDetail, load: this.#onLoaded() };
|
|
680
685
|
}
|
|
681
686
|
|
|
@@ -706,17 +711,22 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
706
711
|
}
|
|
707
712
|
const needUpdateLighting = this.#checkNeedToUpdateLighting(options);
|
|
708
713
|
if (needUpdateLighting && typeof this.#babylonJSController.setIlluminationConfig === "function") {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
714
|
+
const lightingConfig = {};
|
|
715
|
+
if (options.render?.lightingTimeOfDay !== undefined) {
|
|
716
|
+
lightingConfig.lightingTimeOfDay = options.render.lightingTimeOfDay;
|
|
717
|
+
}
|
|
718
|
+
if (options.render?.showLightingTimeOfDay !== undefined) {
|
|
719
|
+
lightingConfig.showLightingTimeOfDay = options.render.showLightingTimeOfDay;
|
|
720
|
+
}
|
|
721
|
+
this.#babylonJSController.setIlluminationConfig(lightingConfig);
|
|
712
722
|
someSetted = true;
|
|
713
723
|
}
|
|
714
|
-
|
|
724
|
+
|
|
715
725
|
// Apply render settings if provided
|
|
716
726
|
if (options.render) {
|
|
717
727
|
await this.applyRenderSettings(options.render);
|
|
718
728
|
}
|
|
719
|
-
|
|
729
|
+
|
|
720
730
|
const detail = this.#onSetOptions();
|
|
721
731
|
return { success: someSetted, detail: detail };
|
|
722
732
|
}
|
|
@@ -949,6 +959,38 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
949
959
|
return typeof cachedUrl === "string" ? cachedUrl.length > 0 : Boolean(cachedUrl);
|
|
950
960
|
}
|
|
951
961
|
|
|
962
|
+
/**
|
|
963
|
+
* Reports whether the animation control menu is currently enabled.
|
|
964
|
+
* @public
|
|
965
|
+
* @returns {boolean} True when the animation menu option is active; otherwise false.
|
|
966
|
+
*/
|
|
967
|
+
isAnimationMenuEnabled() {
|
|
968
|
+
const renderSettings = this.getRenderSettings?.() ?? null;
|
|
969
|
+
if (renderSettings?.showAnimationMenu !== undefined) {
|
|
970
|
+
return renderSettings.showAnimationMenu === true;
|
|
971
|
+
}
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Reports whether the lighting time-of-day control should be visible and interactive.
|
|
977
|
+
* Requires both `showLightingTimeOfDay === true` and a finite `lightingTimeOfDay` value.
|
|
978
|
+
* @public
|
|
979
|
+
* @returns {boolean} True when the time-of-day slider is explicitly visible and has a valid value; otherwise false.
|
|
980
|
+
*/
|
|
981
|
+
isLightingTimeOfDayAvailable() {
|
|
982
|
+
const renderSettings = this.getRenderSettings?.() ?? null;
|
|
983
|
+
return renderSettings?.showLightingTimeOfDay === true && Number.isFinite(renderSettings.lightingTimeOfDay);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Returns a diagnostic snapshot of the underlying Babylon.js runtime.
|
|
988
|
+
* @public
|
|
989
|
+
* @returns {object|null} Debug information, or null when the controller is not initialized.
|
|
990
|
+
*/
|
|
991
|
+
getDebugInfo() {
|
|
992
|
+
return this.#babylonJSController?.getDebugInfo?.() ?? null;
|
|
993
|
+
}
|
|
952
994
|
/**
|
|
953
995
|
* Applies render settings received from PrefViewerMenu3D or another UI.
|
|
954
996
|
* Highlight and time-of-day lighting values are pushed directly to BabylonJSController; AA/SSAO/IBL/shadow changes
|
|
@@ -959,8 +1001,10 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
959
1001
|
* @param {boolean} [settings.ambientOcclusionEnabled] - Enables or disables SSAO.
|
|
960
1002
|
* @param {boolean} [settings.iblEnabled] - Enables or disables image-based lighting.
|
|
961
1003
|
* @param {boolean} [settings.shadowsEnabled] - Enables or disables scene shadows.
|
|
1004
|
+
* @param {boolean|string} [settings.showAnimationMenu] - Enables/disables the animation controls when animations are available.
|
|
962
1005
|
* @param {boolean|string} [settings.highlightEnabled] - Enables or disables hover highlight; string "false" is normalized to false.
|
|
963
1006
|
* @param {string} [settings.highlightColor] - Hex color used for hover highlight.
|
|
1007
|
+
* @param {boolean|string} [settings.showLightingTimeOfDay] - Shows/hides the lighting time-of-day control.
|
|
964
1008
|
* @param {number} [settings.lightingTimeOfDay] - Normalized 0..1 time-of-day value.
|
|
965
1009
|
* @param {boolean} [settings.persist=true] - False for transient lighting updates such as playback frames.
|
|
966
1010
|
* @returns {Promise<{changed: boolean, success: boolean, detail?: object, controller?: object}>} Resolves with the apply/reload summary.
|
|
@@ -970,30 +1014,41 @@ export default class PrefViewer3D extends HTMLElement {
|
|
|
970
1014
|
return { changed: false, success: false };
|
|
971
1015
|
}
|
|
972
1016
|
|
|
973
|
-
|
|
974
|
-
if (settings.
|
|
975
|
-
|
|
976
|
-
// Convert string "false" to boolean false
|
|
977
|
-
const highlightEnabled = settings.highlightEnabled === "false" ? false : settings.highlightEnabled;
|
|
978
|
-
this.#babylonJSController.setIlluminationConfig({
|
|
979
|
-
highlightEnabled: highlightEnabled,
|
|
980
|
-
highlightColor: settings.highlightColor,
|
|
981
|
-
});
|
|
982
|
-
}
|
|
1017
|
+
const illuminationSettings = {};
|
|
1018
|
+
if (settings.showAnimationMenu !== undefined) {
|
|
1019
|
+
illuminationSettings.showAnimationMenu = settings.showAnimationMenu;
|
|
983
1020
|
}
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1021
|
+
if (settings.highlightEnabled !== undefined) {
|
|
1022
|
+
illuminationSettings.highlightEnabled = settings.highlightEnabled;
|
|
1023
|
+
}
|
|
1024
|
+
if (settings.highlightColor !== undefined) {
|
|
1025
|
+
illuminationSettings.highlightColor = settings.highlightColor;
|
|
1026
|
+
}
|
|
1027
|
+
if (settings.showLightingTimeOfDay !== undefined) {
|
|
1028
|
+
illuminationSettings.showLightingTimeOfDay = settings.showLightingTimeOfDay;
|
|
1029
|
+
}
|
|
1030
|
+
if (settings.lightingTimeOfDay !== undefined) {
|
|
987
1031
|
const lightingTimeOfDay = Number(settings.lightingTimeOfDay);
|
|
988
1032
|
if (Number.isFinite(lightingTimeOfDay)) {
|
|
989
|
-
|
|
990
|
-
lightingTimeOfDay,
|
|
991
|
-
persist: settings.persist !== false,
|
|
992
|
-
});
|
|
1033
|
+
illuminationSettings.lightingTimeOfDay = lightingTimeOfDay;
|
|
993
1034
|
}
|
|
994
1035
|
}
|
|
1036
|
+
if (settings.persist !== undefined) {
|
|
1037
|
+
illuminationSettings.persist = settings.persist;
|
|
1038
|
+
}
|
|
1039
|
+
if (Object.keys(illuminationSettings).length > 0 && typeof this.#babylonJSController.setIlluminationConfig === "function") {
|
|
1040
|
+
this.#babylonJSController.setIlluminationConfig(illuminationSettings);
|
|
1041
|
+
}
|
|
995
1042
|
|
|
996
|
-
const {
|
|
1043
|
+
const {
|
|
1044
|
+
showAnimationMenu: _sam,
|
|
1045
|
+
highlightEnabled: _he,
|
|
1046
|
+
highlightColor: _hc,
|
|
1047
|
+
showLightingTimeOfDay: _slt,
|
|
1048
|
+
lightingTimeOfDay: _lt,
|
|
1049
|
+
persist: _persist,
|
|
1050
|
+
...reloadSettings
|
|
1051
|
+
} = settings;
|
|
997
1052
|
const scheduled = this.#babylonJSController.scheduleRenderSettingsReload(reloadSettings);
|
|
998
1053
|
if (!scheduled.changed) {
|
|
999
1054
|
return { changed: false, success: true };
|
|
@@ -10,7 +10,7 @@ import { DEFAULT_LOCALE, resolveLocale, translate } from "./localization/i18n.js
|
|
|
10
10
|
* - A hue slider for the highlight color.
|
|
11
11
|
* - A time-of-day slider with play/pause continuous lighting-cycle animation.
|
|
12
12
|
*
|
|
13
|
-
* Every interaction emits `pref-viewer-
|
|
13
|
+
* Every interaction emits `pref-viewer-3d-render-settings-change-requested` immediately; there is no Apply button or
|
|
14
14
|
* draft/pending state. Switch changes emit the full settings snapshot, while highlight color and
|
|
15
15
|
* lighting changes emit partial payloads to avoid unnecessary scene reloads.
|
|
16
16
|
*
|
|
@@ -19,13 +19,13 @@ import { DEFAULT_LOCALE, resolveLocale, translate } from "./localization/i18n.js
|
|
|
19
19
|
* - `setApplying(isApplying)`: locks interactions while an async apply operation is running.
|
|
20
20
|
* - `setViewerHover(isHovering)`: drives panel visibility from the 3D viewport hover state.
|
|
21
21
|
* - `setEnabled(isEnabled)`: hides the menu when 3D mode is unavailable.
|
|
22
|
-
* - `
|
|
22
|
+
* - `setElementEnabled(settingKey, isEnabled)`: enables/disables a specific switch or slider at runtime.
|
|
23
|
+
* - `setElementVisible(settingKey, isVisible)`: shows/hides a specific switch or slider at runtime.
|
|
23
24
|
* - `setCulture(cultureId)`: switches the display locale.
|
|
24
25
|
*
|
|
25
|
-
* Observed attributes: `culture
|
|
26
|
-
* The illumination attributes are mirrored for compatibility with hosts that still set them declaratively.
|
|
26
|
+
* Observed attributes: `culture`. Runtime render values are synchronized through `setSettings()`.
|
|
27
27
|
*
|
|
28
|
-
* Events emitted: `pref-viewer-
|
|
28
|
+
* Events emitted: `pref-viewer-3d-render-settings-change-requested` - `{ detail: { settings: object } }`.
|
|
29
29
|
*/
|
|
30
30
|
export default class PrefViewerMenu3D extends HTMLElement {
|
|
31
31
|
#elements = {
|
|
@@ -41,6 +41,8 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
41
41
|
lightingSlider: null,
|
|
42
42
|
lightingValue: null,
|
|
43
43
|
panel: null,
|
|
44
|
+
sliders: {},
|
|
45
|
+
sliderWrappers: {},
|
|
44
46
|
subtitle: null,
|
|
45
47
|
switches: {},
|
|
46
48
|
switchCopyNodes: {},
|
|
@@ -65,7 +67,8 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
65
67
|
typeof this.#DEFAULT_RENDER_SETTINGS[key] === "boolean"
|
|
66
68
|
);
|
|
67
69
|
#settings = { ...this.#DEFAULT_RENDER_SETTINGS };
|
|
68
|
-
#
|
|
70
|
+
#elementAvailability = {};
|
|
71
|
+
#elementVisibility = {};
|
|
69
72
|
#isApplying = false;
|
|
70
73
|
#isEnabled = true;
|
|
71
74
|
#isLightingPlaying = false;
|
|
@@ -92,35 +95,22 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
92
95
|
* @returns {string[]} Array of attribute names to observe.
|
|
93
96
|
*/
|
|
94
97
|
static get observedAttributes() {
|
|
95
|
-
return ["culture"
|
|
98
|
+
return ["culture"];
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
/**
|
|
99
102
|
* Reacts to observed attribute mutations and mirrors them into the internal settings/UI state.
|
|
100
103
|
* @public
|
|
101
|
-
* @param {"culture"
|
|
104
|
+
* @param {"culture"} name - Attribute name that changed.
|
|
102
105
|
* @param {string|null} _old - Previous attribute value (unused).
|
|
103
106
|
* @param {string|null} value - New attribute value.
|
|
104
107
|
* @returns {void}
|
|
105
108
|
*/
|
|
106
109
|
attributeChangedCallback(name, _old, value) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const switchEl = this.#elements.switches?.illuminationEnabled;
|
|
112
|
-
if (switchEl) {
|
|
113
|
-
switchEl.checked = this.#settings.illuminationEnabled;
|
|
114
|
-
}
|
|
115
|
-
} else if (name === "illumination-color") {
|
|
116
|
-
this.#settings.illuminationColor = value;
|
|
117
|
-
this.#updateColorPickerPreview(value);
|
|
118
|
-
} else if (name === "lighting-time-of-day") {
|
|
119
|
-
const parsed = Number(value);
|
|
120
|
-
if (Number.isFinite(parsed)) {
|
|
121
|
-
this.#settings.lightingTimeOfDay = this.#clampLightingTime(parsed);
|
|
122
|
-
this.#updateLightingSliderUI(this.#settings.lightingTimeOfDay);
|
|
123
|
-
}
|
|
110
|
+
switch (name) {
|
|
111
|
+
case "culture":
|
|
112
|
+
this.#setCultureInternal(value || DEFAULT_LOCALE);
|
|
113
|
+
break;
|
|
124
114
|
}
|
|
125
115
|
}
|
|
126
116
|
|
|
@@ -171,19 +161,19 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
171
161
|
<div class="menu-switches">
|
|
172
162
|
${this.#MENU_SWITCHES.map((key) => this.#renderSwitchRow(key, this.#texts.features)).join("")}
|
|
173
163
|
</div>
|
|
174
|
-
<div class="menu-color-picker" data-setting="highlightColor">
|
|
164
|
+
<div class="menu-color-picker menu-slider" data-setting="highlightColor">
|
|
175
165
|
<div class="color-slider-row">
|
|
176
166
|
<span class="color-picker-label">${this.#texts.features?.highlightColor?.label || "Highlight Color"}</span>
|
|
177
167
|
<span class="color-preview" style="background-color: ${this.#settings.highlightColor || '#ff6700'}"></span>
|
|
178
168
|
</div>
|
|
179
|
-
<input type="range" class="color-slider" min="0" max="359" value="${PrefViewerMenu3D.#hexToHue(this.#settings.highlightColor || '#ff6700')}">
|
|
169
|
+
<input type="range" data-setting="highlightColor" class="color-slider" min="0" max="359" value="${PrefViewerMenu3D.#hexToHue(this.#settings.highlightColor || '#ff6700')}">
|
|
180
170
|
</div>
|
|
181
|
-
<div class="menu-lighting-slider" data-setting="lightingTimeOfDay">
|
|
171
|
+
<div class="menu-lighting-slider menu-slider" data-setting="lightingTimeOfDay">
|
|
182
172
|
<div class="lighting-slider-row">
|
|
183
|
-
<span class="lighting-slider-label">${this.#texts.features?.lightingTimeOfDay?.label || "Time of day"}</span>
|
|
173
|
+
<span class="lighting-slider-label menu-slider-label">${this.#texts.features?.lightingTimeOfDay?.label || "Time of day"}</span>
|
|
184
174
|
<span class="lighting-slider-value">${PrefViewerMenu3D.#formatLightingTime(this.#settings.lightingTimeOfDay ?? 0.5)}</span>
|
|
185
175
|
</div>
|
|
186
|
-
<input type="range" class="lighting-slider" min="0" max="1000" step="1" value="${Math.round((this.#settings.lightingTimeOfDay ?? 0.5) * 1000)}">
|
|
176
|
+
<input type="range" data-setting="lightingTimeOfDay" class="lighting-slider" min="0" max="1000" step="1" value="${Math.round((this.#settings.lightingTimeOfDay ?? 0.5) * 1000)}">
|
|
187
177
|
<div class="lighting-slider-markers" aria-hidden="true">
|
|
188
178
|
<span class="lighting-slider-start">${this.#texts.features?.lightingTimeOfDay?.startLabel || "Night"}</span>
|
|
189
179
|
<span class="lighting-slider-mid">${this.#texts.features?.lightingTimeOfDay?.midLabel || "Day"}</span>
|
|
@@ -325,7 +315,8 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
325
315
|
this.#elements.panel = this.querySelector(".menu-panel");
|
|
326
316
|
this.#elements.title = this.querySelector(".menu-title");
|
|
327
317
|
this.#elements.subtitle = this.querySelector(".menu-subtitle");
|
|
328
|
-
|
|
318
|
+
|
|
319
|
+
// Cache slider elements
|
|
329
320
|
this.#elements.colorPicker = this.querySelector(".menu-color-picker");
|
|
330
321
|
this.#elements.colorSlider = this.querySelector(".color-slider");
|
|
331
322
|
this.#elements.colorPickerPreview = this.querySelector(".color-preview");
|
|
@@ -337,6 +328,11 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
337
328
|
this.#elements.lightingSliderEnd = this.querySelector(".lighting-slider-end");
|
|
338
329
|
this.#elements.lightingSlider = this.querySelector(".lighting-slider");
|
|
339
330
|
this.#elements.lightingValue = this.querySelector(".lighting-slider-value");
|
|
331
|
+
|
|
332
|
+
this.#elements.sliders["highlightColor"] = this.querySelector("input[data-setting='highlightColor']");
|
|
333
|
+
this.#elements.sliderWrappers["highlightColor"] = this.querySelector(".menu-slider[data-setting='highlightColor']");
|
|
334
|
+
this.#elements.sliders["lightingTimeOfDay"] = this.querySelector("input[data-setting='lightingTimeOfDay']");
|
|
335
|
+
this.#elements.sliderWrappers["lightingTimeOfDay"] = this.querySelector(".menu-slider[data-setting='lightingTimeOfDay']");
|
|
340
336
|
|
|
341
337
|
this.#MENU_SWITCHES.forEach((key) => {
|
|
342
338
|
this.#elements.switches[key] = this.querySelector(`input[data-setting="${key}"]`);
|
|
@@ -668,12 +664,13 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
668
664
|
return;
|
|
669
665
|
}
|
|
670
666
|
const detail = { settings: options.partial ? { ...extraSettings } : { ...this.#settings, ...extraSettings } };
|
|
671
|
-
|
|
667
|
+
const eventOptions = {
|
|
672
668
|
bubbles: true,
|
|
673
669
|
cancelable: true,
|
|
674
670
|
composed: true,
|
|
675
671
|
detail,
|
|
676
|
-
}
|
|
672
|
+
};
|
|
673
|
+
this.dispatchEvent(new CustomEvent("pref-viewer-3d-render-settings-change-requested", eventOptions));
|
|
677
674
|
}
|
|
678
675
|
|
|
679
676
|
/**
|
|
@@ -684,10 +681,29 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
684
681
|
#updateSwitches() {
|
|
685
682
|
Object.entries(this.#elements.switches).forEach(([key, input]) => {
|
|
686
683
|
if (input) {
|
|
687
|
-
const isEnabled = this.#
|
|
684
|
+
const isEnabled = this.#elementAvailability[key] !== false;
|
|
685
|
+
const isVisible = this.#elementVisibility[key] !== false;
|
|
688
686
|
input.checked = Boolean(this.#settings[key]);
|
|
689
687
|
input.disabled = !isEnabled;
|
|
690
688
|
this.#elements.switchWrappers[key]?.toggleAttribute("data-disabled", !isEnabled);
|
|
689
|
+
this.#elements.switchWrappers[key]?.toggleAttribute("data-hidden", !isVisible);
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Synchronizes slider states with the current settings snapshot.
|
|
696
|
+
* @private
|
|
697
|
+
* @returns {void}
|
|
698
|
+
*/
|
|
699
|
+
#updateSliders() {
|
|
700
|
+
Object.entries(this.#elements.sliders).forEach(([key, input]) => {
|
|
701
|
+
if (input) {
|
|
702
|
+
const isEnabled = this.#elementAvailability[key] !== false;
|
|
703
|
+
const isVisible = this.#elementVisibility[key] !== false;
|
|
704
|
+
input.disabled = !isEnabled;
|
|
705
|
+
this.#elements.sliderWrappers[key]?.toggleAttribute("data-disabled", !isEnabled);
|
|
706
|
+
this.#elements.sliderWrappers[key]?.toggleAttribute("data-hidden", !isVisible);
|
|
691
707
|
}
|
|
692
708
|
});
|
|
693
709
|
}
|
|
@@ -754,6 +770,7 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
754
770
|
if (this.#settings.lightingTimeOfDay !== undefined) {
|
|
755
771
|
this.#updateLightingSliderUI(this.#settings.lightingTimeOfDay);
|
|
756
772
|
}
|
|
773
|
+
this.#updateSliders();
|
|
757
774
|
this.#updateLightingPlayButtonUI();
|
|
758
775
|
this.setApplying(false);
|
|
759
776
|
}
|
|
@@ -777,17 +794,44 @@ export default class PrefViewerMenu3D extends HTMLElement {
|
|
|
777
794
|
}
|
|
778
795
|
|
|
779
796
|
/**
|
|
780
|
-
* Enables or disables a specific render-setting switch in the menu.
|
|
797
|
+
* Enables or disables a specific render-setting switch or slider in the menu.
|
|
781
798
|
* @public
|
|
782
|
-
* @param {string} settingKey -
|
|
799
|
+
* @param {string} settingKey - Setting key (e.g. "iblEnabled", "highlightColor", or "lightingTimeOfDay").
|
|
783
800
|
* @param {boolean} [isEnabled=true] - True to allow interaction, false to disable it.
|
|
784
801
|
* @returns {void}
|
|
785
802
|
*/
|
|
786
|
-
|
|
787
|
-
if (!settingKey
|
|
803
|
+
setElementEnabled(settingKey, isEnabled = true) {
|
|
804
|
+
if (!settingKey) {
|
|
788
805
|
return;
|
|
789
806
|
}
|
|
790
|
-
|
|
791
|
-
this.#
|
|
807
|
+
|
|
808
|
+
if (this.#MENU_SWITCHES.includes(settingKey)) {
|
|
809
|
+
this.#elementAvailability[settingKey] = Boolean(isEnabled);
|
|
810
|
+
this.#updateSwitches();
|
|
811
|
+
} else if (this.#elements.sliders[settingKey]) {
|
|
812
|
+
this.#elementAvailability[settingKey] = Boolean(isEnabled);
|
|
813
|
+
this.#updateSliders();
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Shows or hides a specific render-setting switch or slider in the menu.
|
|
819
|
+
* @public
|
|
820
|
+
* @param {string} settingKey - Setting key (e.g. "iblEnabled", "highlightColor", or "lightingTimeOfDay").
|
|
821
|
+
* @param {boolean} [isVisible=true] - True to show the switch, false to hide it.
|
|
822
|
+
* @returns {void}
|
|
823
|
+
*/
|
|
824
|
+
setElementVisible(settingKey, isVisible = true) {
|
|
825
|
+
if (!settingKey) {
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (this.#MENU_SWITCHES.includes(settingKey)) {
|
|
830
|
+
this.#elementVisibility[settingKey] = Boolean(isVisible);
|
|
831
|
+
this.#updateSwitches();
|
|
832
|
+
} else if (this.#elements.sliders[settingKey]) {
|
|
833
|
+
this.#elementVisibility[settingKey] = Boolean(isVisible);
|
|
834
|
+
this.#updateSliders();
|
|
835
|
+
}
|
|
792
836
|
}
|
|
793
837
|
}
|