@preference-sl/pref-viewer 2.10.0 → 2.11.0-beta.1
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 +6 -3
- package/src/babylonjs-controller.js +929 -0
- package/src/file-storage.js +288 -0
- package/src/gltf-resolver.js +288 -0
- package/src/index.js +492 -962
- package/src/panzoom-controller.js +494 -0
- package/src/pref-viewer-2d.js +441 -0
- package/src/pref-viewer-3d-data.js +178 -0
- package/src/pref-viewer-3d.js +457 -0
- package/src/pref-viewer-task.js +54 -0
- package/src/svg-resolver.js +281 -0
package/src/index.js
CHANGED
|
@@ -1,225 +1,144 @@
|
|
|
1
|
+
import { PrefViewer2D } from "./pref-viewer-2d.js";
|
|
2
|
+
import { PrefViewer3D } from "./pref-viewer-3d.js";
|
|
3
|
+
import {PrefViewerTask} from "./pref-viewer-task.js";
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
6
|
+
* PrefViewer - Custom Web Component for rendering and managing 2D and 3D product visualizations.
|
|
7
|
+
*
|
|
8
|
+
* Overview:
|
|
9
|
+
* - Encapsulates both 2D and 3D viewers using Babylon.js, supporting glTF/GLB models and environments.
|
|
10
|
+
* - Handles loading from remote URLs, Base64 data URIs, and IndexedDB sources.
|
|
11
|
+
* - Provides a unified API for loading models, scenes, drawings, materials, and configuration via attributes or methods.
|
|
12
|
+
* - Manages an internal task queue for sequential processing of viewer operations.
|
|
13
|
+
* - Emits custom events for loading, errors, and state changes to facilitate integration.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* - Use as a custom HTML element: <pref-viewer ...>
|
|
17
|
+
* - Configure via attributes (e.g., config, model, scene, materials, drawing, options).
|
|
18
|
+
* - Control visibility, downloads, and viewer mode via public methods.
|
|
5
19
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* • Exposes methods for making changes to the scene that replicate what the attribute observables do: 'loadConfig', 'loadModel', 'loadScene'.
|
|
13
|
-
* • Automatically handles scene creation (engine, camera, lighting) and resource cleanup.
|
|
14
|
-
* • Emits 'model-loaded' and 'model-error' events for integration.
|
|
20
|
+
* Public Methods:
|
|
21
|
+
* - loadConfig(config), loadModel(model), loadScene(scene), loadMaterials(materials), loadDrawing(drawing)
|
|
22
|
+
* - setOptions(options)
|
|
23
|
+
* - setMode(mode): Sets the viewer mode to "2d" or "3d" and updates component visibility.
|
|
24
|
+
* - showModel(), hideModel(), showScene(), hideScene()
|
|
25
|
+
* - downloadModelGLB(), downloadModelUSDZ(), downloadModelAndSceneGLB(), downloadModelAndSceneUSDZ()
|
|
15
26
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* <pref-viewer
|
|
21
|
-
* model='{ "storage": { "db": "PrefConfiguratorDB", "table": "gltfModels", "id": "1234-1234-1234-1234-1234" }, "visible": true" }'
|
|
22
|
-
* style="width:800px; height:600px;">
|
|
23
|
-
* </pref-viewer>
|
|
24
|
-
* ```
|
|
27
|
+
* Public Properties:
|
|
28
|
+
* - initialized: Indicates if the viewer is initialized.
|
|
29
|
+
* - loaded: Indicates if the viewer has finished loading.
|
|
30
|
+
* - loading: Indicates if the viewer is currently loading.
|
|
25
31
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* style="width:800px; height:600px;">
|
|
31
|
-
* </pref-viewer>
|
|
32
|
-
* ```
|
|
32
|
+
* Events:
|
|
33
|
+
* - "scene-loading": Dispatched when a loading operation starts.
|
|
34
|
+
* - "scene-loaded": Dispatched when a loading operation completes.
|
|
35
|
+
* - "scene-error": Dispatched when initialization fails.
|
|
33
36
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* style="width:800px; height:600px;">
|
|
39
|
-
* </pref-viewer>
|
|
40
|
-
* ```
|
|
37
|
+
* Notes:
|
|
38
|
+
* - Automatically creates and manages 2D and 3D viewer components in its shadow DOM.
|
|
39
|
+
* - Processes tasks sequentially to ensure consistent state.
|
|
40
|
+
* - Designed for extensibility and integration in product configurators and visualization tools.
|
|
41
41
|
*/
|
|
42
|
-
import { Engine, Scene, ArcRotateCamera, Vector3, Color4, HemisphericLight, DirectionalLight, PointLight, ShadowGenerator, LoadAssetContainerAsync, Tools, WebXRSessionManager, WebXRDefaultExperience, MeshBuilder, WebXRFeatureName, HDRCubeTexture, IblShadowsRenderPipeline } from "@babylonjs/core";
|
|
43
|
-
import "@babylonjs/loaders";
|
|
44
|
-
import { USDZExportAsync, GLTF2Export } from "@babylonjs/serializers";
|
|
45
|
-
import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression";
|
|
46
|
-
import { DracoCompression } from "@babylonjs/core/Meshes/Compression/dracoCompression";
|
|
47
|
-
import { initDb, loadModel } from "./gltf-storage.js";
|
|
48
|
-
|
|
49
|
-
class PrefViewerTask {
|
|
50
|
-
static Types = Object.freeze({
|
|
51
|
-
Config: "config",
|
|
52
|
-
Environment: "environment",
|
|
53
|
-
Materials: "materials",
|
|
54
|
-
Model: "model",
|
|
55
|
-
Options: "options",
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* value: any payload for the task
|
|
60
|
-
* type: must match one of PrefViewerTask.Types values (case-insensitive)
|
|
61
|
-
*/
|
|
62
|
-
constructor(value, type) {
|
|
63
|
-
this.value = value;
|
|
64
|
-
|
|
65
|
-
const t = typeof type === "string" ? type.toLowerCase() : String(type).toLowerCase();
|
|
66
|
-
const allowed = Object.values(PrefViewerTask.Types);
|
|
67
|
-
if (!allowed.includes(t)) {
|
|
68
|
-
throw new TypeError(
|
|
69
|
-
`PrefViewerTask: invalid type "${type}". Allowed types: ${allowed.join(", ")}`
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
this.type = t;
|
|
73
|
-
|
|
74
|
-
Object.freeze(this);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
42
|
class PrefViewer extends HTMLElement {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
#
|
|
43
|
+
#isInitialized = false;
|
|
44
|
+
#isLoaded = false;
|
|
45
|
+
#isLoading = false;
|
|
46
|
+
#mode = "3d";
|
|
83
47
|
|
|
84
|
-
#
|
|
85
|
-
containers: {
|
|
86
|
-
model: {
|
|
87
|
-
name: "model",
|
|
88
|
-
assetContainer: null,
|
|
89
|
-
show: true,
|
|
90
|
-
storage: null,
|
|
91
|
-
visible: false,
|
|
92
|
-
size: null,
|
|
93
|
-
timeStamp: null,
|
|
94
|
-
changed: { pending: false, success: false },
|
|
95
|
-
},
|
|
96
|
-
environment: {
|
|
97
|
-
name: "environment",
|
|
98
|
-
assetContainer: null,
|
|
99
|
-
show: true,
|
|
100
|
-
storage: null,
|
|
101
|
-
visible: false,
|
|
102
|
-
size: null,
|
|
103
|
-
timeStamp: null,
|
|
104
|
-
changed: { pending: false, success: false },
|
|
105
|
-
},
|
|
106
|
-
materials: {
|
|
107
|
-
name: "materials",
|
|
108
|
-
assetContainer: null,
|
|
109
|
-
storage: null,
|
|
110
|
-
show: true,
|
|
111
|
-
visible: false,
|
|
112
|
-
size: null,
|
|
113
|
-
timeStamp: null,
|
|
114
|
-
changed: { pending: false, success: false },
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
options: {
|
|
118
|
-
camera: {
|
|
119
|
-
value: null,
|
|
120
|
-
locked: true,
|
|
121
|
-
changed: { pending: false, success: false },
|
|
122
|
-
},
|
|
123
|
-
materials: {
|
|
124
|
-
innerWall: {
|
|
125
|
-
value: null,
|
|
126
|
-
prefix: "innerWall",
|
|
127
|
-
changed: { pending: false, success: false },
|
|
128
|
-
},
|
|
129
|
-
outerWall: {
|
|
130
|
-
value: null,
|
|
131
|
-
prefix: "outerWall",
|
|
132
|
-
changed: { pending: false, success: false },
|
|
133
|
-
},
|
|
134
|
-
innerFloor: {
|
|
135
|
-
value: null,
|
|
136
|
-
prefix: "innerFloor",
|
|
137
|
-
changed: { pending: false, success: false },
|
|
138
|
-
},
|
|
139
|
-
outerFloor: {
|
|
140
|
-
value: null,
|
|
141
|
-
prefix: "outerFloor",
|
|
142
|
-
changed: { pending: false, success: false },
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
// DOM elements
|
|
149
|
-
#wrapper = null;
|
|
150
|
-
#canvas = null;
|
|
48
|
+
#taskQueue = [];
|
|
151
49
|
|
|
152
|
-
|
|
153
|
-
#
|
|
154
|
-
#scene = null;
|
|
155
|
-
#camera = null;
|
|
156
|
-
#hemiLight = null;
|
|
157
|
-
#dirLight = null;
|
|
158
|
-
#cameraLight = null;
|
|
159
|
-
#shadowGen = null;
|
|
160
|
-
#XRExperience = null;
|
|
50
|
+
#component2D = null;
|
|
51
|
+
#component3D = null;
|
|
161
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Creates a new PrefViewer instance and attaches a shadow DOM.
|
|
55
|
+
* Initializes internal state and component references.
|
|
56
|
+
* @public
|
|
57
|
+
*/
|
|
162
58
|
constructor() {
|
|
163
59
|
super();
|
|
164
60
|
this.attachShadow({ mode: "open" });
|
|
165
|
-
this.#createCanvas();
|
|
166
|
-
this.#wrapCanvas();
|
|
167
|
-
// Point to whichever version you packaged or want to use:
|
|
168
|
-
const DRACO_BASE = "https://www.gstatic.com/draco/versioned/decoders/1.5.7";
|
|
169
|
-
DracoCompression.Configuration.decoder = {
|
|
170
|
-
// loader for the “wrapper” that pulls in the real WASM
|
|
171
|
-
wasmUrl: `${DRACO_BASE}/draco_wasm_wrapper_gltf.js`,
|
|
172
|
-
// the raw WebAssembly binary
|
|
173
|
-
wasmBinaryUrl: `${DRACO_BASE}/draco_decoder_gltf.wasm`,
|
|
174
|
-
// JS fallback if WASM isn’t available
|
|
175
|
-
fallbackUrl: `${DRACO_BASE}/draco_decoder_gltf.js`,
|
|
176
|
-
};
|
|
177
61
|
}
|
|
178
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Returns the list of attributes to observe for changes.
|
|
65
|
+
* @public
|
|
66
|
+
* @returns {string[]} Array of attribute names to observe.
|
|
67
|
+
*/
|
|
179
68
|
static get observedAttributes() {
|
|
180
|
-
return ["config", "model", "scene", "show-model", "show-scene"];
|
|
69
|
+
return ["config", "drawing", "materials", "mode", "model", "scene", "options", "show-model", "show-scene"];
|
|
181
70
|
}
|
|
182
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Observes changes to specific attributes and triggers corresponding actions.
|
|
74
|
+
* Loads configuration, drawing, model, scene, materials, or options when their attributes change.
|
|
75
|
+
* Toggles model or scene visibility when "show-model" or "show-scene" attributes change.
|
|
76
|
+
* @public
|
|
77
|
+
* @param {string} name - The name of the changed attribute.
|
|
78
|
+
* @param {*} _old - The previous value of the attribute (unused).
|
|
79
|
+
* @param {*} value - The new value of the attribute.
|
|
80
|
+
* @returns {void}
|
|
81
|
+
*/
|
|
183
82
|
attributeChangedCallback(name, _old, value) {
|
|
184
|
-
let data = null;
|
|
185
83
|
switch (name) {
|
|
186
84
|
case "config":
|
|
187
85
|
this.loadConfig(value);
|
|
188
86
|
break;
|
|
87
|
+
case "drawing":
|
|
88
|
+
this.loadDrawing(value);
|
|
89
|
+
break;
|
|
90
|
+
case "materials":
|
|
91
|
+
this.loadMaterials(value);
|
|
92
|
+
break;
|
|
93
|
+
case "mode":
|
|
94
|
+
if (_old === value || value.toLowerCase() === this.#mode) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.setMode(value.toLowerCase());
|
|
98
|
+
break;
|
|
189
99
|
case "model":
|
|
190
100
|
this.loadModel(value);
|
|
191
101
|
break;
|
|
192
102
|
case "scene":
|
|
193
103
|
this.loadScene(value);
|
|
194
104
|
break;
|
|
195
|
-
case "materials":
|
|
196
|
-
this.loadMaterials(value);
|
|
197
|
-
break;
|
|
198
105
|
case "options":
|
|
199
106
|
this.setOptions(value);
|
|
200
107
|
break;
|
|
201
108
|
case "show-model":
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
data ? this.showModel() : this.hideModel();
|
|
205
|
-
} else {
|
|
206
|
-
this.#data.containers.model.show = data;
|
|
109
|
+
if (_old === value) {
|
|
110
|
+
return;
|
|
207
111
|
}
|
|
112
|
+
const showModel = value.toLowerCase() === "true";
|
|
113
|
+
showModel ? this.showModel() : this.hideModel();
|
|
208
114
|
break;
|
|
209
115
|
case "show-scene":
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
data ? this.showScene() : this.hideScene();
|
|
213
|
-
} else {
|
|
214
|
-
this.#data.containers.environment.show = data;
|
|
116
|
+
if (_old === value) {
|
|
117
|
+
return;
|
|
215
118
|
}
|
|
119
|
+
const showScene = value.toLowerCase() === "true";
|
|
120
|
+
showScene ? this.showScene() : this.hideScene();
|
|
216
121
|
break;
|
|
217
122
|
}
|
|
218
123
|
}
|
|
219
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Called when the element is inserted into the DOM.
|
|
127
|
+
* Initializes the 3D and 2D viewer components and starts processing tasks.
|
|
128
|
+
* If the "config" attribute is missing, dispatches a "scene-error" event and stops initialization.
|
|
129
|
+
* @public
|
|
130
|
+
* @returns {void|boolean} Returns false if initialization fails; otherwise void.
|
|
131
|
+
*/
|
|
220
132
|
connectedCallback() {
|
|
133
|
+
this.#createComponent3D();
|
|
134
|
+
this.#createComponent2D();
|
|
135
|
+
|
|
136
|
+
if (!this.hasAttribute("mode")) {
|
|
137
|
+
this.setMode();
|
|
138
|
+
}
|
|
139
|
+
|
|
221
140
|
if (!this.hasAttribute("config")) {
|
|
222
|
-
const error = 'PrefViewer: provide "
|
|
141
|
+
const error = 'PrefViewer: provide "config" as a configuration object to initialize the viewer.';
|
|
223
142
|
console.error(error);
|
|
224
143
|
this.dispatchEvent(
|
|
225
144
|
new CustomEvent("scene-error", {
|
|
@@ -232,45 +151,98 @@ class PrefViewer extends HTMLElement {
|
|
|
232
151
|
return false;
|
|
233
152
|
}
|
|
234
153
|
|
|
235
|
-
this.#
|
|
236
|
-
this.initialized = true;
|
|
154
|
+
this.#isInitialized = true;
|
|
237
155
|
this.#processNextTask();
|
|
238
156
|
}
|
|
239
157
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Creates and appends the 2D viewer component to the shadow DOM.
|
|
160
|
+
* Sets the "visible" attribute to true by default.
|
|
161
|
+
* @private
|
|
162
|
+
* @returns {void}
|
|
163
|
+
*/
|
|
164
|
+
#createComponent2D() {
|
|
165
|
+
this.#component2D = document.createElement("pref-viewer-2d");
|
|
166
|
+
this.#component2D.setAttribute("visible", "false");
|
|
167
|
+
this.shadowRoot.appendChild(this.#component2D);
|
|
243
168
|
}
|
|
244
169
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Creates and appends the 3D viewer component to the shadow DOM.
|
|
172
|
+
* Sets the "visible" attribute to true by default.
|
|
173
|
+
* @private
|
|
174
|
+
* @returns {void}
|
|
175
|
+
*/
|
|
176
|
+
#createComponent3D() {
|
|
177
|
+
this.#component3D = document.createElement("pref-viewer-3d");
|
|
178
|
+
this.#component3D.setAttribute("visible", "false");
|
|
179
|
+
this.shadowRoot.appendChild(this.#component3D);
|
|
254
180
|
}
|
|
255
181
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Adds a new task to the internal queue for processing.
|
|
184
|
+
* If the viewer is initialized and not currently loading, immediately processes the next task.
|
|
185
|
+
* @private
|
|
186
|
+
* @param {*} value - The payload or data for the task.
|
|
187
|
+
* @param {string} type - The type of task (see PrefViewerTask.Types).
|
|
188
|
+
* @returns {void}
|
|
189
|
+
*/
|
|
190
|
+
#addTaskToQueue(value, type) {
|
|
191
|
+
this.#taskQueue.push(new PrefViewerTask(value, type));
|
|
192
|
+
if (this.#isInitialized && !this.#isLoading) {
|
|
193
|
+
this.#processNextTask();
|
|
194
|
+
}
|
|
265
195
|
}
|
|
266
196
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Processes the next task in the queue, if any.
|
|
199
|
+
* Dispatches the task to the appropriate handler based on its type.
|
|
200
|
+
* @private
|
|
201
|
+
* @returns {boolean|void} Returns false if the queue is empty; otherwise void.
|
|
202
|
+
*/
|
|
203
|
+
#processNextTask() {
|
|
204
|
+
if (!this.#taskQueue.length) {
|
|
205
|
+
return false;
|
|
272
206
|
}
|
|
273
|
-
this
|
|
207
|
+
const task = this.#taskQueue[0];
|
|
208
|
+
this.#taskQueue.shift();
|
|
209
|
+
switch (task.type) {
|
|
210
|
+
case PrefViewerTask.Types.Config:
|
|
211
|
+
this.#processConfig(task.value);
|
|
212
|
+
break;
|
|
213
|
+
case PrefViewerTask.Types.Drawing:
|
|
214
|
+
this.#processDrawing(task.value);
|
|
215
|
+
break;
|
|
216
|
+
case PrefViewerTask.Types.Environment:
|
|
217
|
+
this.#processEnvironment(task.value);
|
|
218
|
+
break;
|
|
219
|
+
case PrefViewerTask.Types.Materials:
|
|
220
|
+
this.#processMaterials(task.value);
|
|
221
|
+
break;
|
|
222
|
+
case PrefViewerTask.Types.Model:
|
|
223
|
+
this.#processModel(task.value);
|
|
224
|
+
break;
|
|
225
|
+
case PrefViewerTask.Types.Options:
|
|
226
|
+
this.#processOptions(task.value);
|
|
227
|
+
break;
|
|
228
|
+
case PrefViewerTask.Types.Visibility:
|
|
229
|
+
this.#processVisibility(task.value);
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Handles the start of a 3D loading operation.
|
|
236
|
+
* Updates loading state, sets attributes, and dispatches a "scene-loading" event.
|
|
237
|
+
* @private
|
|
238
|
+
* @returns {void}
|
|
239
|
+
*/
|
|
240
|
+
#on3DLoading() {
|
|
241
|
+
this.#isLoaded = false;
|
|
242
|
+
this.#isLoading = true;
|
|
243
|
+
|
|
244
|
+
this.removeAttribute("loaded-3d");
|
|
245
|
+
this.setAttribute("loading-3d", "");
|
|
274
246
|
this.dispatchEvent(
|
|
275
247
|
new CustomEvent("scene-loading", {
|
|
276
248
|
bubbles: true,
|
|
@@ -278,35 +250,16 @@ class PrefViewer extends HTMLElement {
|
|
|
278
250
|
composed: true,
|
|
279
251
|
})
|
|
280
252
|
);
|
|
281
|
-
this.#engine.stopRenderLoop(this.#renderLoop);
|
|
282
253
|
}
|
|
283
254
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
options_innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed.pending,
|
|
293
|
-
options_outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed.pending,
|
|
294
|
-
};
|
|
295
|
-
const loadedDetail = {
|
|
296
|
-
container_model: !!this.#data.containers.model.changed.success,
|
|
297
|
-
container_environment: !!this.#data.containers.environment.changed.success,
|
|
298
|
-
container_materials: !!this.#data.containers.materials.changed.success,
|
|
299
|
-
options_camera: !!this.#data.options.camera.changed.success,
|
|
300
|
-
options_innerWallMaterial: !!this.#data.options.materials.innerWall.changed.success,
|
|
301
|
-
options_outerWallMaterial: !!this.#data.options.materials.outerWall.changed.success,
|
|
302
|
-
options_innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed.success,
|
|
303
|
-
options_outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed.success,
|
|
304
|
-
};
|
|
305
|
-
const detail = {
|
|
306
|
-
tried: toLoadDetail,
|
|
307
|
-
success: loadedDetail,
|
|
308
|
-
};
|
|
309
|
-
|
|
255
|
+
/**
|
|
256
|
+
* Handles the completion of a 3D loading operation.
|
|
257
|
+
* Updates loading state, sets attributes, dispatches a "scene-loaded" event, and processes the next task.
|
|
258
|
+
* @private
|
|
259
|
+
* @param {object} [detail={}] - Optional details to include in the event.
|
|
260
|
+
* @returns {void}
|
|
261
|
+
*/
|
|
262
|
+
#on3DLoaded(detail = {}) {
|
|
310
263
|
this.dispatchEvent(
|
|
311
264
|
new CustomEvent("scene-loaded", {
|
|
312
265
|
bubbles: true,
|
|
@@ -316,755 +269,224 @@ class PrefViewer extends HTMLElement {
|
|
|
316
269
|
})
|
|
317
270
|
);
|
|
318
271
|
|
|
319
|
-
|
|
320
|
-
this
|
|
321
|
-
|
|
322
|
-
this.#resetChangedFlags();
|
|
272
|
+
this.removeAttribute("loading-3d");
|
|
273
|
+
this.setAttribute("loaded-3d", "");
|
|
323
274
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
this.setAttribute("loaded", "");
|
|
328
|
-
|
|
329
|
-
this.loaded = true;
|
|
330
|
-
this.loading = false;
|
|
331
|
-
|
|
332
|
-
this.#processNextTask();
|
|
275
|
+
this.#isLoaded = true;
|
|
276
|
+
this.#isLoading = false;
|
|
333
277
|
}
|
|
334
278
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
this.#
|
|
344
|
-
this.#data.options.camera.changed.success = false;
|
|
345
|
-
if (changed) {
|
|
346
|
-
this.#data.options.camera.changed.value = prev;
|
|
347
|
-
this.#data.options.camera.changed.locked = this.#data.options.camera.locked;
|
|
348
|
-
this.#data.options.camera.value = options.camera;
|
|
349
|
-
}
|
|
350
|
-
return changed;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
#checkMaterialsChanged(options) {
|
|
354
|
-
if (!options) {
|
|
355
|
-
return false;
|
|
356
|
-
}
|
|
357
|
-
let someChanged = false;
|
|
358
|
-
Object.keys(this.#data.options.materials).forEach((material) => {
|
|
359
|
-
const key = `${material}Material`;
|
|
360
|
-
const state = this.#data.options.materials[material];
|
|
361
|
-
const prev = state.value;
|
|
362
|
-
const incoming = options[key];
|
|
363
|
-
const changed = !!incoming && incoming !== prev;
|
|
364
|
-
|
|
365
|
-
state.changed.pending = changed;
|
|
366
|
-
state.changed.success = false;
|
|
367
|
-
if (changed) {
|
|
368
|
-
state.changed.value = prev;
|
|
369
|
-
state.value = incoming;
|
|
370
|
-
}
|
|
371
|
-
someChanged = someChanged || changed;
|
|
372
|
-
});
|
|
373
|
-
return someChanged;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
#storeChangedFlagsForContainer(container, success) {
|
|
377
|
-
if (success) {
|
|
378
|
-
container.timeStamp = container.changed.timeStamp;
|
|
379
|
-
container.size = container.changed.size;
|
|
380
|
-
container.changed.success = true;
|
|
381
|
-
} else {
|
|
382
|
-
container.changed.success = false;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
#resetChangedFlags() {
|
|
387
|
-
const reset = (node) => {
|
|
388
|
-
node.changed = { pending: false, success: false };
|
|
389
|
-
};
|
|
390
|
-
Object.values(this.#data.containers).forEach(reset);
|
|
391
|
-
Object.values(this.#data.options.materials).forEach(reset);
|
|
392
|
-
reset(this.#data.options.camera);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Babylon.js
|
|
396
|
-
async #initializeBabylon() {
|
|
397
|
-
this.#engine = new Engine(this.#canvas, true, { alpha: true });
|
|
398
|
-
this.#engine.disableUniformBuffers = true;
|
|
399
|
-
this.#scene = new Scene(this.#engine);
|
|
400
|
-
this.#scene.clearColor = new Color4(1, 1, 1, 1);
|
|
401
|
-
this.#createCamera();
|
|
402
|
-
this.#createLights();
|
|
403
|
-
this.#setupInteraction();
|
|
404
|
-
await this.#createXRExperience();
|
|
405
|
-
this.#engine.runRenderLoop(this.#renderLoop);
|
|
406
|
-
this.#canvasResizeObserver.observe(this.#canvas);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// If this function is defined as '#renderLoop() {}' it is not executed in 'this.#engine.runRenderLoop(this.#renderLoop)'
|
|
410
|
-
#renderLoop = () => {
|
|
411
|
-
this.#scene && this.#scene.render();
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
#addStylesToARButton() {
|
|
415
|
-
const css = '.babylonVRicon { color: #868686; border-color: #868686; border-style: solid; margin-left: 10px; height: 50px; width: 80px; background-color: rgba(51,51,51,0.7); background-image: url(data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%222048%22%20height%3D%221152%22%20viewBox%3D%220%200%202048%201152%22%20version%3D%221.1%22%3E%3Cpath%20transform%3D%22rotate%28180%201024%2C576.0000000000001%29%22%20d%3D%22m1109%2C896q17%2C0%2030%2C-12t13%2C-30t-12.5%2C-30.5t-30.5%2C-12.5l-170%2C0q-18%2C0%20-30.5%2C12.5t-12.5%2C30.5t13%2C30t30%2C12l170%2C0zm-85%2C256q59%2C0%20132.5%2C-1.5t154.5%2C-5.5t164.5%2C-11.5t163%2C-20t150%2C-30t124.5%2C-41.5q23%2C-11%2042%2C-24t38%2C-30q27%2C-25%2041%2C-61.5t14%2C-72.5l0%2C-257q0%2C-123%20-47%2C-232t-128%2C-190t-190%2C-128t-232%2C-47l-81%2C0q-37%2C0%20-68.5%2C14t-60.5%2C34.5t-55.5%2C45t-53%2C45t-53%2C34.5t-55.5%2C14t-55.5%2C-14t-53%2C-34.5t-53%2C-45t-55.5%2C-45t-60.5%2C-34.5t-68.5%2C-14l-81%2C0q-123%2C0%20-232%2C47t-190%2C128t-128%2C190t-47%2C232l0%2C257q0%2C68%2038%2C115t97%2C73q54%2C24%20124.5%2C41.5t150%2C30t163%2C20t164.5%2C11.5t154.5%2C5.5t132.5%2C1.5zm939%2C-298q0%2C39%20-24.5%2C67t-58.5%2C42q-54%2C23%20-122%2C39.5t-143.5%2C28t-155.5%2C19t-157%2C11t-148.5%2C5t-129.5%2C1.5q-59%2C0%20-130%2C-1.5t-148%2C-5t-157%2C-11t-155.5%2C-19t-143.5%2C-28t-122%2C-39.5q-34%2C-14%20-58.5%2C-42t-24.5%2C-67l0%2C-257q0%2C-106%2040.5%2C-199t110%2C-162.5t162.5%2C-109.5t199%2C-40l81%2C0q27%2C0%2052%2C14t50%2C34.5t51%2C44.5t55.5%2C44.5t63.5%2C34.5t74%2C14t74%2C-14t63.5%2C-34.5t55.5%2C-44.5t51%2C-44.5t50%2C-34.5t52%2C-14l14%2C0q37%2C0%2070%2C0.5t64.5%2C4.5t63.5%2C12t68%2C23q71%2C30%20128.5%2C78.5t98.5%2C110t63.5%2C133.5t22.5%2C149l0%2C257z%22%20fill%3D%22white%22%20/%3E%3C/svg%3E%0A); background-size: 80%; background-repeat:no-repeat; background-position: center; border: none; outline: none; transition: transform 0.125s ease-out } .babylonVRicon:hover { transform: scale(1.05) } .babylonVRicon:active {background-color: rgba(51,51,51,1) } .babylonVRicon:focus {background-color: rgba(51,51,51,1) }.babylonVRicon.vrdisplaypresenting { background-image: none;} .vrdisplaypresenting::after { content: "EXIT"} .xr-error::after { content: "ERROR"}';
|
|
416
|
-
const style = document.createElement("style");
|
|
417
|
-
style.appendChild(document.createTextNode(css));
|
|
418
|
-
this.#wrapper.appendChild(style);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
async #createXRExperience() {
|
|
422
|
-
if (this.#XRExperience) {
|
|
423
|
-
return true;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const sessionMode = "immersive-ar";
|
|
427
|
-
const sessionSupported = await WebXRSessionManager.IsSessionSupportedAsync(sessionMode);
|
|
428
|
-
if (!sessionSupported) {
|
|
429
|
-
console.info("PrefViewer: WebXR in mode AR is not supported");
|
|
430
|
-
return false;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
try {
|
|
434
|
-
const ground = MeshBuilder.CreateGround("ground", { width: 1000, height: 1000 }, this.#scene);
|
|
435
|
-
ground.isVisible = false;
|
|
436
|
-
|
|
437
|
-
const options = {
|
|
438
|
-
floorMeshes: [ground],
|
|
439
|
-
uiOptions: {
|
|
440
|
-
sessionMode: sessionMode,
|
|
441
|
-
renderTarget: "xrLayer",
|
|
442
|
-
referenceSpaceType: "local",
|
|
443
|
-
},
|
|
444
|
-
optionalFeatures: true,
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
this.#XRExperience = await WebXRDefaultExperience.CreateAsync(this.#scene, options);
|
|
448
|
-
|
|
449
|
-
const featuresManager = this.#XRExperience.baseExperience.featuresManager;
|
|
450
|
-
featuresManager.enableFeature(WebXRFeatureName.TELEPORTATION, "stable", {
|
|
451
|
-
xrInput: this.#XRExperience.input,
|
|
452
|
-
floorMeshes: [ground],
|
|
453
|
-
timeToTeleport: 1500,
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
this.#XRExperience.baseExperience.sessionManager.onXRReady.add(() => {
|
|
457
|
-
// Set the initial position of xrCamera: use nonVRCamera, which contains a copy of the original this.#scene.activeCamera before entering XR
|
|
458
|
-
this.#XRExperience.baseExperience.camera.setTransformationFromNonVRCamera(this.#XRExperience.baseExperience._nonVRCamera);
|
|
459
|
-
this.#XRExperience.baseExperience.camera.setTarget(Vector3.Zero());
|
|
460
|
-
this.#XRExperience.baseExperience.onInitialXRPoseSetObservable.notifyObservers(this.#XRExperience.baseExperience.camera);
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
this.#addStylesToARButton();
|
|
464
|
-
} catch (error) {
|
|
465
|
-
console.warn("PrefViewer: failed to create WebXR experience", error);
|
|
466
|
-
this.#XRExperience = null;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
#canvasResizeObserver = new ResizeObserver(() => this.#engine && this.#engine.resize());
|
|
471
|
-
|
|
472
|
-
#createCamera() {
|
|
473
|
-
this.#camera = new ArcRotateCamera("camera", (3 * Math.PI) / 2, Math.PI * 0.47, 10, Vector3.Zero(), this.#scene);
|
|
474
|
-
this.#camera.upperBetaLimit = Math.PI * 0.48;
|
|
475
|
-
this.#camera.lowerBetaLimit = Math.PI * 0.25;
|
|
476
|
-
this.#camera.lowerRadiusLimit = 5;
|
|
477
|
-
this.#camera.upperRadiusLimit = 20;
|
|
478
|
-
this.#camera.metadata = { locked: false };
|
|
479
|
-
this.#camera.attachControl(this.#canvas, true);
|
|
480
|
-
this.#scene.activeCamera = this.#camera;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
#createLights() {
|
|
484
|
-
this.#initEnvironmentTexture();
|
|
485
|
-
|
|
486
|
-
if (this.#scene.environmentTexture) {
|
|
487
|
-
return true;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// 1) Stronger ambient fill
|
|
491
|
-
this.#hemiLight = new HemisphericLight("hemiLight", new Vector3(-10, 10, -10), this.#scene);
|
|
492
|
-
this.#hemiLight.intensity = 0.6;
|
|
493
|
-
|
|
494
|
-
// 2) Directional light from the front-right, angled slightly down
|
|
495
|
-
this.#dirLight = new DirectionalLight("dirLight", new Vector3(-10, 10, -10), this.#scene);
|
|
496
|
-
this.#dirLight.position = new Vector3(5, 4, 5); // light is IN FRONT + ABOVE + to the RIGHT
|
|
497
|
-
this.#dirLight.intensity = 0.6;
|
|
498
|
-
|
|
499
|
-
// // 3) Soft shadows
|
|
500
|
-
this.#shadowGen = new ShadowGenerator(1024, this.#dirLight);
|
|
501
|
-
this.#shadowGen.useBlurExponentialShadowMap = true;
|
|
502
|
-
this.#shadowGen.blurKernel = 16;
|
|
503
|
-
this.#shadowGen.darkness = 0.5;
|
|
504
|
-
|
|
505
|
-
// 4) Camera‐attached headlight
|
|
506
|
-
this.#cameraLight = new PointLight("pl", this.#camera.position, this.#scene);
|
|
507
|
-
this.#cameraLight.parent = this.#camera;
|
|
508
|
-
this.#cameraLight.intensity = 0.3;
|
|
509
|
-
}
|
|
279
|
+
/**
|
|
280
|
+
* Handles the start of a 2D loading operation.
|
|
281
|
+
* Updates loading state, sets attributes, and dispatches a "drawing-loading" event.
|
|
282
|
+
* @private
|
|
283
|
+
* @returns {void}
|
|
284
|
+
*/
|
|
285
|
+
#on2DLoading() {
|
|
286
|
+
this.#isLoaded = false;
|
|
287
|
+
this.#isLoading = true;
|
|
510
288
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
hdrTexture.level = 2.0;
|
|
521
|
-
this.#scene.environmentTexture = hdrTexture;
|
|
289
|
+
this.removeAttribute("loaded-2d");
|
|
290
|
+
this.setAttribute("loading-2d", "");
|
|
291
|
+
this.dispatchEvent(
|
|
292
|
+
new CustomEvent("drawing-loading", {
|
|
293
|
+
bubbles: true,
|
|
294
|
+
cancelable: false,
|
|
295
|
+
composed: true,
|
|
296
|
+
})
|
|
297
|
+
);
|
|
522
298
|
}
|
|
523
299
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
triPlanarVoxelization: true,
|
|
539
|
-
shadowOpacity: 0.8,
|
|
540
|
-
},
|
|
541
|
-
[scene.activeCamera]
|
|
542
|
-
);
|
|
543
|
-
pipeline.allowDebugPasses = false;
|
|
544
|
-
pipeline.gbufferDebugEnabled = true;
|
|
545
|
-
pipeline.importanceSamplingDebugEnabled = false;
|
|
546
|
-
pipeline.voxelDebugEnabled = false;
|
|
547
|
-
pipeline.voxelDebugDisplayMip = 1;
|
|
548
|
-
pipeline.voxelDebugAxis = 2;
|
|
549
|
-
pipeline.voxelTracingDebugEnabled = false;
|
|
550
|
-
pipeline.spatialBlurPassDebugEnabled = false;
|
|
551
|
-
pipeline.accumulationPassDebugEnabled = false;
|
|
552
|
-
return pipeline;
|
|
553
|
-
};
|
|
554
|
-
|
|
555
|
-
let iblShadowsPipeline = createIBLShadowPipeline(this.#scene);
|
|
300
|
+
/**
|
|
301
|
+
* Handles the completion of a 2D loading operation.
|
|
302
|
+
* Updates loading state, sets attributes, dispatches a "drawing-loaded" event, and processes the next task.
|
|
303
|
+
* @private
|
|
304
|
+
* @returns {void}
|
|
305
|
+
*/
|
|
306
|
+
#on2DLoaded() {
|
|
307
|
+
this.dispatchEvent(
|
|
308
|
+
new CustomEvent("drawing-loaded", {
|
|
309
|
+
bubbles: true,
|
|
310
|
+
cancelable: false,
|
|
311
|
+
composed: true,
|
|
312
|
+
})
|
|
313
|
+
);
|
|
556
314
|
|
|
557
|
-
this
|
|
558
|
-
|
|
559
|
-
return false;
|
|
560
|
-
}
|
|
561
|
-
iblShadowsPipeline.addShadowCastingMesh(mesh);
|
|
562
|
-
iblShadowsPipeline.updateSceneBounds();
|
|
563
|
-
});
|
|
315
|
+
this.removeAttribute("loading-2d");
|
|
316
|
+
this.setAttribute("loaded-2d", "");
|
|
564
317
|
|
|
565
|
-
this.#
|
|
566
|
-
|
|
567
|
-
});
|
|
318
|
+
this.#isLoaded = true;
|
|
319
|
+
this.#isLoading = false;
|
|
568
320
|
}
|
|
569
321
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
322
|
+
/**
|
|
323
|
+
* Processes a configuration object by loading it into the 3D component.
|
|
324
|
+
* Dispatches loading events and processes the next task when finished.
|
|
325
|
+
* @private
|
|
326
|
+
* @param {object} config - The configuration object to process.
|
|
327
|
+
* @returns {void}
|
|
328
|
+
*/
|
|
329
|
+
#processConfig(config) {
|
|
330
|
+
if (!this.#component3D) {
|
|
331
|
+
return;
|
|
574
332
|
}
|
|
575
333
|
|
|
576
|
-
this.#
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
mesh.receiveShadows = true;
|
|
581
|
-
if (!mesh.name === "hdri") {
|
|
582
|
-
this.#shadowGen.addShadowCaster(mesh, true);
|
|
583
|
-
}
|
|
334
|
+
this.#on3DLoading();
|
|
335
|
+
this.#component3D.load(config).then((detail) => {
|
|
336
|
+
this.#on3DLoaded(detail);
|
|
337
|
+
this.#processNextTask();
|
|
584
338
|
});
|
|
585
339
|
}
|
|
586
340
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
341
|
+
/**
|
|
342
|
+
* Processes a drawing object by loading it into the 2D component.
|
|
343
|
+
* Processes the next task when finished.
|
|
344
|
+
* @private
|
|
345
|
+
* @param {object} drawing - The drawing object to process.
|
|
346
|
+
* @returns {void}
|
|
347
|
+
*/
|
|
348
|
+
#processDrawing(drawing) {
|
|
349
|
+
if (!this.#component2D) {
|
|
350
|
+
return;
|
|
596
351
|
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
#setupInteraction() {
|
|
600
|
-
this.#canvas.addEventListener("wheel", (event) => {
|
|
601
|
-
if (!this.#scene || !this.#camera) {
|
|
602
|
-
return false;
|
|
603
|
-
}
|
|
604
|
-
//const pick = this.#scene.pick(this.#scene.pointerX, this.#scene.pointerY);
|
|
605
|
-
//this.#camera.target = pick.hit ? pick.pickedPoint.clone() : this.#camera.target;
|
|
606
|
-
if (!this.#scene.activeCamera.metadata?.locked) {
|
|
607
|
-
this.#scene.activeCamera.inertialRadiusOffset -= event.deltaY * this.#scene.activeCamera.wheelPrecision * 0.001;
|
|
608
|
-
}
|
|
609
|
-
event.preventDefault();
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
#disposeEngine() {
|
|
614
|
-
if (!this.#engine) return;
|
|
615
|
-
this.#engine.dispose();
|
|
616
|
-
this.#engine = this.#scene = this.#camera = null;
|
|
617
|
-
this.#hemiLight = this.#dirLight = this.#cameraLight = null;
|
|
618
|
-
this.#shadowGen = null;
|
|
619
|
-
}
|
|
620
352
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
xhr.open("HEAD", uri, true);
|
|
626
|
-
xhr.responseType = "blob";
|
|
627
|
-
xhr.onload = () => {
|
|
628
|
-
if (xhr.status === 200) {
|
|
629
|
-
const size = parseInt(xhr.getResponseHeader("Content-Length"));
|
|
630
|
-
const timeStamp = new Date(xhr.getResponseHeader("Last-Modified")).toISOString();
|
|
631
|
-
resolve([size, timeStamp]);
|
|
632
|
-
} else {
|
|
633
|
-
resolve([0, null]);
|
|
634
|
-
}
|
|
635
|
-
};
|
|
636
|
-
xhr.onerror = () => {
|
|
637
|
-
resolve([0, null]);
|
|
638
|
-
};
|
|
639
|
-
xhr.send();
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
#transformUrl(url) {
|
|
644
|
-
return new Promise((resolve) => {
|
|
645
|
-
resolve(url.replace(/^blob:[^/]+\//i, "").replace(/\\/g, "/"));
|
|
353
|
+
this.#on2DLoading();
|
|
354
|
+
this.#component2D.load(drawing).then(() => {
|
|
355
|
+
this.#on2DLoaded();
|
|
356
|
+
this.#processNextTask();
|
|
646
357
|
});
|
|
647
358
|
}
|
|
648
359
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
return { blob, extension, size };
|
|
660
|
-
}
|
|
661
|
-
let isJson = false;
|
|
662
|
-
try {
|
|
663
|
-
JSON.parse(decoded);
|
|
664
|
-
isJson = true;
|
|
665
|
-
} catch { }
|
|
666
|
-
extension = isJson ? ".gltf" : ".glb";
|
|
667
|
-
const type = isJson ? "model/gltf+json" : "model/gltf-binary";
|
|
668
|
-
const array = Uint8Array.from(decoded, (c) => c.charCodeAt(0));
|
|
669
|
-
blob = new Blob([array], { type });
|
|
670
|
-
return { blob, extension, size };
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
async #initStorage(db, table) {
|
|
674
|
-
if (window.gltfDB && window.gltfDB.name === db && window.gltfDB.objectStoreNames.contains(table)) {
|
|
675
|
-
return true;
|
|
676
|
-
}
|
|
677
|
-
await initDb(db, table);
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
// Methods for managing Asset Containers
|
|
681
|
-
#setVisibilityOfWallAndFloorInModel(show) {
|
|
682
|
-
if (!this.#data.containers.model.assetContainer || !this.#data.containers.model.visible) {
|
|
683
|
-
return false;
|
|
684
|
-
}
|
|
685
|
-
show = show !== undefined ? show : this.#data.containers.environment.visible;
|
|
686
|
-
const prefixes = Object.values(this.#data.options.materials).map((material) => material.prefix);
|
|
687
|
-
this.#data.containers.model.assetContainer.meshes.filter((meshToFilter) => prefixes.some((prefix) => meshToFilter.name.startsWith(prefix))).forEach((mesh) => mesh.setEnabled(show));
|
|
360
|
+
/**
|
|
361
|
+
* Processes an environment (scene) object by wrapping it in a config and loading it.
|
|
362
|
+
* @private
|
|
363
|
+
* @param {object} environment - The environment/scene object to process.
|
|
364
|
+
* @returns {void}
|
|
365
|
+
*/
|
|
366
|
+
#processEnvironment(environment) {
|
|
367
|
+
const config = {};
|
|
368
|
+
config.scene = environment;
|
|
369
|
+
this.#processConfig(config);
|
|
688
370
|
}
|
|
689
371
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const containers = [];
|
|
701
|
-
if (this.#data.containers.model.assetContainer && (this.#data.containers.model.changed.pending || this.#data.containers.materials.changed.pending || optionMaterial.changed.pending)) {
|
|
702
|
-
containers.push(this.#data.containers.model.assetContainer);
|
|
703
|
-
}
|
|
704
|
-
if (this.#data.containers.environment.assetContainer && (this.#data.containers.environment.changed.pending || this.#data.containers.materials.changed.pending || optionMaterial.changed.pending)) {
|
|
705
|
-
containers.push(this.#data.containers.environment.assetContainer);
|
|
706
|
-
}
|
|
707
|
-
if (containers.length === 0) {
|
|
708
|
-
return false;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
let someSetted = false;
|
|
712
|
-
containers.forEach((container) =>
|
|
713
|
-
container.meshes
|
|
714
|
-
.filter((meshToFilter) => meshToFilter.name.startsWith(optionMaterial.prefix))
|
|
715
|
-
.forEach((mesh) => {
|
|
716
|
-
mesh.material = material;
|
|
717
|
-
someSetted = true;
|
|
718
|
-
})
|
|
719
|
-
);
|
|
720
|
-
|
|
721
|
-
if (someSetted) {
|
|
722
|
-
optionMaterial.changed.success = true;
|
|
723
|
-
} else if (optionMaterial.changed.pending) {
|
|
724
|
-
optionMaterial.value = optionMaterial.changed.value;
|
|
725
|
-
optionMaterial.changed.success = false;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
return someSetted;
|
|
372
|
+
/**
|
|
373
|
+
* Processes a materials object by wrapping it in a config and loading it.
|
|
374
|
+
* @private
|
|
375
|
+
* @param {object} materials - The materials object to process.
|
|
376
|
+
* @returns {void}
|
|
377
|
+
*/
|
|
378
|
+
#processMaterials(materials) {
|
|
379
|
+
const config = {};
|
|
380
|
+
config.materials = materials;
|
|
381
|
+
this.#processConfig(config);
|
|
729
382
|
}
|
|
730
383
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
384
|
+
/**
|
|
385
|
+
* Processes a model object by wrapping it in a config and loading it.
|
|
386
|
+
* @private
|
|
387
|
+
* @param {object} model - The model object to process.
|
|
388
|
+
* @returns {void}
|
|
389
|
+
*/
|
|
390
|
+
#processModel(model) {
|
|
391
|
+
const config = {};
|
|
392
|
+
config.model = model;
|
|
393
|
+
this.#processConfig(config);
|
|
738
394
|
}
|
|
739
395
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
396
|
+
/**
|
|
397
|
+
* Processes viewer options by loading them into the 3D component.
|
|
398
|
+
* Dispatches loading events and processes the next task.
|
|
399
|
+
* @private
|
|
400
|
+
* @param {object} options - The options object to process.
|
|
401
|
+
* @returns {void}
|
|
402
|
+
*/
|
|
403
|
+
#processOptions(options) {
|
|
404
|
+
if (!this.#component3D) {
|
|
405
|
+
return;
|
|
743
406
|
}
|
|
744
407
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
}
|
|
750
|
-
if (camera) {
|
|
751
|
-
camera.metadata = { locked: this.#data.options.camera.changed.locked };
|
|
752
|
-
this.#data.options.camera.value = this.#data.options.camera.changed.value;
|
|
753
|
-
this.#data.options.camera.locked = this.#data.options.camera.changed.locked;
|
|
754
|
-
this.#data.options.camera.changed.success = false;
|
|
755
|
-
} else {
|
|
756
|
-
camera = this.#camera;
|
|
757
|
-
this.#data.options.camera.value = null;
|
|
758
|
-
this.#data.options.camera.locked = this.#camera.metadata.locked;
|
|
759
|
-
this.#data.options.camera.changed.success = false;
|
|
760
|
-
}
|
|
761
|
-
} else {
|
|
762
|
-
camera.metadata = { locked: this.#data.options.camera.locked };
|
|
763
|
-
if (this.#data.options.camera.changed.pending) {
|
|
764
|
-
this.#data.options.camera.changed.success = true;
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
if (!this.#data.options.camera.locked && this.#data.options.camera.value !== null) {
|
|
768
|
-
camera.attachControl(this.#canvas, true);
|
|
769
|
-
}
|
|
770
|
-
this.#scene.activeCamera = camera;
|
|
771
|
-
return true;
|
|
408
|
+
this.#on3DLoading();
|
|
409
|
+
const detail = this.#component3D.setOptions(options);
|
|
410
|
+
this.#on3DLoaded(detail);
|
|
411
|
+
this.#processNextTask();
|
|
772
412
|
}
|
|
773
413
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
414
|
+
/**
|
|
415
|
+
* Processes visibility configuration for the model and scene.
|
|
416
|
+
* Shows or hides the model and/or scene based on the config, then processes the next task.
|
|
417
|
+
* @private
|
|
418
|
+
* @param {object} config - The visibility configuration object.
|
|
419
|
+
* @returns {void}
|
|
420
|
+
*/
|
|
421
|
+
#processVisibility(config) {
|
|
422
|
+
if (!this.#component3D) {
|
|
423
|
+
return;
|
|
777
424
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
#removeContainer(container) {
|
|
785
|
-
if (!container.assetContainer || !container.visible) {
|
|
786
|
-
return false;
|
|
425
|
+
const showModel = config.model?.visible;
|
|
426
|
+
const showScene = config.scene?.visible;
|
|
427
|
+
if (showModel !== undefined) {
|
|
428
|
+
showModel ? this.#component3D.showModel() : this.#component3D.hideModel();
|
|
787
429
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
container.visible = false;
|
|
791
|
-
return true;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
#replaceContainer(container, newAssetContainer) {
|
|
795
|
-
if (container.assetContainer) {
|
|
796
|
-
this.#removeContainer(container);
|
|
797
|
-
container.assetContainer.dispose();
|
|
798
|
-
container.assetContainer = null;
|
|
430
|
+
if (showScene !== undefined) {
|
|
431
|
+
showScene ? this.#component3D.showEnvironment() : this.#component3D.hideEnvironment();
|
|
799
432
|
}
|
|
800
|
-
this.#
|
|
801
|
-
container.assetContainer = newAssetContainer;
|
|
802
|
-
this.#addContainer(container);
|
|
803
|
-
return true;
|
|
433
|
+
this.#processNextTask();
|
|
804
434
|
}
|
|
805
435
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
if (!source) {
|
|
828
|
-
return false;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
let file = null;
|
|
832
|
-
|
|
833
|
-
let { blob, extension, size } = this.#decodeBase64(source);
|
|
834
|
-
if (blob && extension) {
|
|
835
|
-
file = new File([blob], `${container.name}${extension}`, {
|
|
836
|
-
type: blob.type,
|
|
837
|
-
});
|
|
838
|
-
if (!container.changed.pending) {
|
|
839
|
-
if (container.timeStamp === null && container.size === size) {
|
|
840
|
-
container.changed = { pending: false, success: false };
|
|
841
|
-
return false;
|
|
842
|
-
} else {
|
|
843
|
-
container.changed = { pending: true, size: size, success: false, timeStamp: null };
|
|
844
|
-
}
|
|
845
|
-
}
|
|
436
|
+
/**
|
|
437
|
+
* ---------------------------
|
|
438
|
+
* Public methods
|
|
439
|
+
* ---------------------------
|
|
440
|
+
*/
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Sets the viewer mode to "2d" or "3d" and updates component visibility accordingly.
|
|
444
|
+
* @public
|
|
445
|
+
* @param {string} [mode=this.#mode] - The mode to set ("2d" or "3d").
|
|
446
|
+
* @returns {void}
|
|
447
|
+
*/
|
|
448
|
+
setMode(mode = this.#mode) {
|
|
449
|
+
if (mode !== "2d" && mode !== "3d") {
|
|
450
|
+
console.warn(`PrefViewer: invalid mode "${mode}". Allowed modes are "2d" and "3d".`);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
this.#mode = mode;
|
|
454
|
+
if (mode === "2d") {
|
|
455
|
+
this.#component3D?.hide();
|
|
456
|
+
this.#component2D?.show();
|
|
846
457
|
} else {
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
const [fileSize, fileTimeStamp] = await this.#getServerFileDataHeader(source);
|
|
850
|
-
if (container.size === fileSize && container.timeStamp === fileTimeStamp) {
|
|
851
|
-
container.changed = { pending: false, success: false };
|
|
852
|
-
return false;
|
|
853
|
-
} else {
|
|
854
|
-
container.changed = { pending: true, size: fileSize, success: false, timeStamp: fileTimeStamp };
|
|
855
|
-
}
|
|
458
|
+
this.#component3D?.show();
|
|
459
|
+
this.#component2D?.hide();
|
|
856
460
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
let options = {
|
|
860
|
-
pluginExtension: extension,
|
|
861
|
-
pluginOptions: {
|
|
862
|
-
gltf: {
|
|
863
|
-
compileMaterials: true,
|
|
864
|
-
loadAllMaterials: true,
|
|
865
|
-
loadOnlyMaterials: container.name === "materials",
|
|
866
|
-
preprocessUrlAsync: this.#transformUrl,
|
|
867
|
-
},
|
|
868
|
-
},
|
|
869
|
-
};
|
|
870
|
-
|
|
871
|
-
return LoadAssetContainerAsync(file || source, this.#scene, options);
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
async #loadContainers(loadModel = true, loadEnvironment = true, loadMaterials = true) {
|
|
875
|
-
this.#engine.stopRenderLoop(this.#renderLoop);
|
|
876
|
-
|
|
877
|
-
const promiseArray = [];
|
|
878
|
-
promiseArray.push(loadModel ? this.#loadAssetContainer(this.#data.containers.model) : false);
|
|
879
|
-
promiseArray.push(loadEnvironment ? this.#loadAssetContainer(this.#data.containers.environment) : false);
|
|
880
|
-
promiseArray.push(loadMaterials ? this.#loadAssetContainer(this.#data.containers.materials) : false);
|
|
881
|
-
|
|
882
|
-
Promise.allSettled(promiseArray)
|
|
883
|
-
.then((values) => {
|
|
884
|
-
const modelContainer = values[0];
|
|
885
|
-
const environmentContainer = values[1];
|
|
886
|
-
const materialsContainer = values[2];
|
|
887
|
-
|
|
888
|
-
if (modelContainer.status === "fulfilled" && modelContainer.value) {
|
|
889
|
-
modelContainer.value.lights = [];
|
|
890
|
-
this.#replaceContainer(this.#data.containers.model, modelContainer.value);
|
|
891
|
-
this.#storeChangedFlagsForContainer(this.#data.containers.model, true);
|
|
892
|
-
} else {
|
|
893
|
-
if (this.#data.containers.model.assetContainer && this.#data.containers.model.show !== this.#data.containers.model.visible) {
|
|
894
|
-
this.#data.containers.model.show ? this.#addContainer(this.#data.containers.model) : this.#removeContainer(this.#data.containers.model);
|
|
895
|
-
}
|
|
896
|
-
this.#storeChangedFlagsForContainer(this.#data.containers.model, false);
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
if (environmentContainer.status === "fulfilled" && environmentContainer.value) {
|
|
900
|
-
this.#replaceContainer(this.#data.containers.environment, environmentContainer.value);
|
|
901
|
-
this.#storeChangedFlagsForContainer(this.#data.containers.environment, true);
|
|
902
|
-
} else {
|
|
903
|
-
if (this.#data.containers.environment.assetContainer && this.#data.containers.environment.show !== this.#data.containers.environment.visible) {
|
|
904
|
-
this.#data.containers.environment.show ? this.#addContainer(this.#data.containers.environment) : this.#removeContainer(this.#data.containers.environment);
|
|
905
|
-
}
|
|
906
|
-
this.#storeChangedFlagsForContainer(this.#data.containers.environment, false);
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
if (materialsContainer.status === "fulfilled" && materialsContainer.value) {
|
|
910
|
-
this.#replaceContainer(this.#data.containers.materials, materialsContainer.value);
|
|
911
|
-
this.#storeChangedFlagsForContainer(this.#data.containers.materials, true);
|
|
912
|
-
} else {
|
|
913
|
-
this.#storeChangedFlagsForContainer(this.#data.containers.materials, false);
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
this.#setOptionsMaterials();
|
|
917
|
-
this.#setOptionsCamera();
|
|
918
|
-
this.#setVisibilityOfWallAndFloorInModel();
|
|
919
|
-
})
|
|
920
|
-
.catch((error) => {
|
|
921
|
-
this.loaded = true;
|
|
922
|
-
console.error("PrefViewer: failed to load model", error);
|
|
923
|
-
this.dispatchEvent(
|
|
924
|
-
new CustomEvent("scene-error", {
|
|
925
|
-
bubbles: true,
|
|
926
|
-
cancelable: false,
|
|
927
|
-
composed: true,
|
|
928
|
-
detail: { error: error },
|
|
929
|
-
})
|
|
930
|
-
);
|
|
931
|
-
})
|
|
932
|
-
.finally(async () => {
|
|
933
|
-
this.#setMaxSimultaneousLights();
|
|
934
|
-
this.#initShadows();
|
|
935
|
-
await this.#setStatusLoaded();
|
|
936
|
-
});
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
// Tasks
|
|
940
|
-
#addTaskToQueue(value, type) {
|
|
941
|
-
this.#taskQueue.push(new PrefViewerTask(value, type));
|
|
942
|
-
if (this.initialized && !this.loading) {
|
|
943
|
-
this.#processNextTask();
|
|
461
|
+
if (this.getAttribute("mode") !== mode) {
|
|
462
|
+
this.setAttribute("mode", mode);
|
|
944
463
|
}
|
|
945
464
|
}
|
|
946
465
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
case PrefViewerTask.Types.Config:
|
|
955
|
-
this.#processConfig(task.value);
|
|
956
|
-
break;
|
|
957
|
-
case PrefViewerTask.Types.Model:
|
|
958
|
-
this.#processModel(task.value);
|
|
959
|
-
break;
|
|
960
|
-
case PrefViewerTask.Types.Environment:
|
|
961
|
-
this.#processEnvironment(task.value);
|
|
962
|
-
break;
|
|
963
|
-
case PrefViewerTask.Types.Materials:
|
|
964
|
-
this.#processMaterials(task.value);
|
|
965
|
-
break;
|
|
966
|
-
case PrefViewerTask.Types.Options:
|
|
967
|
-
this.#processOptions(task.value);
|
|
968
|
-
break;
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
#processConfig(config) {
|
|
973
|
-
this.#setStatusLoading();
|
|
974
|
-
|
|
975
|
-
// Containers
|
|
976
|
-
const loadModel = !!config.model?.storage;
|
|
977
|
-
this.#data.containers.model.changed.pending = loadModel;
|
|
978
|
-
this.#data.containers.model.changed.success = false;
|
|
979
|
-
this.#data.containers.model.changed.storage = this.#data.containers.model.storage;
|
|
980
|
-
this.#data.containers.model.storage = loadModel ? config.model.storage : this.#data.containers.model.storage;
|
|
981
|
-
this.#data.containers.model.show = config.model?.visible !== undefined ? config.model.visible : this.#data.containers.model.show;
|
|
982
|
-
|
|
983
|
-
const loadEnvironment = !!config.scene?.storage;
|
|
984
|
-
this.#data.containers.environment.changed.pending = loadEnvironment;
|
|
985
|
-
this.#data.containers.environment.changed.success = false;
|
|
986
|
-
this.#data.containers.environment.changed.storage = this.#data.containers.environment.storage;
|
|
987
|
-
this.#data.containers.environment.storage = loadEnvironment ? config.scene.storage : this.#data.containers.environment.storage;
|
|
988
|
-
this.#data.containers.environment.show = config.scene?.visible !== undefined ? config.scene.visible : this.#data.containers.environment.show;
|
|
989
|
-
|
|
990
|
-
const loadMaterials = !!config.materials?.storage;
|
|
991
|
-
this.#data.containers.materials.changed.pending = loadMaterials;
|
|
992
|
-
this.#data.containers.materials.changed.success = false;
|
|
993
|
-
this.#data.containers.materials.changed.storage = this.#data.containers.materials.storage;
|
|
994
|
-
this.#data.containers.materials.storage = loadMaterials ? config.materials.storage : this.#data.containers.materials.storage;
|
|
995
|
-
|
|
996
|
-
// Options
|
|
997
|
-
if (config.options) {
|
|
998
|
-
this.#checkCameraChanged(config.options);
|
|
999
|
-
this.#checkMaterialsChanged(config.options);
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
this.#loadContainers(loadModel, loadEnvironment, loadMaterials);
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
#processModel(model) {
|
|
1006
|
-
this.#setStatusLoading();
|
|
1007
|
-
|
|
1008
|
-
const loadModel = !!model.storage;
|
|
1009
|
-
this.#data.containers.model.changed.pending = loadModel;
|
|
1010
|
-
this.#data.containers.model.changed.success = false;
|
|
1011
|
-
this.#data.containers.model.changed.storage = this.#data.containers.model.storage;
|
|
1012
|
-
this.#data.containers.model.storage = loadModel ? model.storage : this.#data.containers.model.storage;
|
|
1013
|
-
this.#data.containers.model.show = model.visible !== undefined ? model.visible : this.#data.containers.model.show;
|
|
1014
|
-
|
|
1015
|
-
this.initialized && this.#loadContainers(loadModel, false, false);
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
#processEnvironment(environment) {
|
|
1019
|
-
this.#setStatusLoading();
|
|
1020
|
-
|
|
1021
|
-
const loadEnvironment = !!environment.storage;
|
|
1022
|
-
this.#data.containers.environment.changed.pending = loadEnvironment;
|
|
1023
|
-
this.#data.containers.environment.changed.success = false;
|
|
1024
|
-
this.#data.containers.environment.changed.storage = this.#data.containers.environment.storage;
|
|
1025
|
-
this.#data.containers.environment.storage = loadEnvironment ? environment.storage : this.#data.containers.environment.storage;
|
|
1026
|
-
this.#data.containers.environment.show = environment.visible !== undefined ? environment.visible : this.#data.containers.environment.show;
|
|
1027
|
-
|
|
1028
|
-
this.#loadContainers(false, loadEnvironment, false);
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
#processMaterials(materials) {
|
|
1032
|
-
this.#setStatusLoading();
|
|
1033
|
-
|
|
1034
|
-
const loadMaterials = !!materials.storage;
|
|
1035
|
-
this.#data.containers.materials.changed.pending = loadMaterials;
|
|
1036
|
-
this.#data.containers.materials.changed.success = false;
|
|
1037
|
-
this.#data.containers.materials.changed.storage = this.#data.containers.materials.storage;
|
|
1038
|
-
this.#data.containers.materials.storage = loadMaterials ? materials.storage : this.#data.containers.materials.storage;
|
|
1039
|
-
|
|
1040
|
-
this.#loadContainers(false, false, loadMaterials);
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
async #processOptions(options) {
|
|
1044
|
-
this.#setStatusLoading();
|
|
1045
|
-
|
|
1046
|
-
let someSetted = false;
|
|
1047
|
-
if (this.#checkCameraChanged(options)) {
|
|
1048
|
-
someSetted = someSetted || this.#setOptionsCamera();
|
|
1049
|
-
}
|
|
1050
|
-
if (this.#checkMaterialsChanged(options)) {
|
|
1051
|
-
someSetted = someSetted || this.#setOptionsMaterials();
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
await this.#setStatusLoaded();
|
|
1055
|
-
|
|
1056
|
-
return someSetted;
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
// Public Methods
|
|
466
|
+
/**
|
|
467
|
+
* Loads a configuration object or JSON string and adds it to the task queue.
|
|
468
|
+
* If the config contains a drawing, adds a drawing task as well.
|
|
469
|
+
* @public
|
|
470
|
+
* @param {object|string} config - Configuration object or JSON string.
|
|
471
|
+
* @returns {boolean|void} Returns false if config is invalid; otherwise void.
|
|
472
|
+
*/
|
|
1060
473
|
loadConfig(config) {
|
|
1061
474
|
config = typeof config === "string" ? JSON.parse(config) : config;
|
|
1062
475
|
if (!config) {
|
|
1063
476
|
return false;
|
|
1064
477
|
}
|
|
478
|
+
if (config.drawing) {
|
|
479
|
+
this.#addTaskToQueue(config.drawing, "drawing");
|
|
480
|
+
}
|
|
1065
481
|
this.#addTaskToQueue(config, "config");
|
|
1066
482
|
}
|
|
1067
483
|
|
|
484
|
+
/**
|
|
485
|
+
* Loads a model object or JSON string and adds it to the task queue.
|
|
486
|
+
* @public
|
|
487
|
+
* @param {object|string} model - Model object or JSON string.
|
|
488
|
+
* @returns {boolean|void} Returns false if model is invalid; otherwise void.
|
|
489
|
+
*/
|
|
1068
490
|
loadModel(model) {
|
|
1069
491
|
model = typeof model === "string" ? JSON.parse(model) : model;
|
|
1070
492
|
if (!model) {
|
|
@@ -1073,6 +495,12 @@ class PrefViewer extends HTMLElement {
|
|
|
1073
495
|
this.#addTaskToQueue(model, "model");
|
|
1074
496
|
}
|
|
1075
497
|
|
|
498
|
+
/**
|
|
499
|
+
* Loads a scene/environment object or JSON string and adds it to the task queue.
|
|
500
|
+
* @public
|
|
501
|
+
* @param {object|string} scene - Scene object or JSON string.
|
|
502
|
+
* @returns {boolean|void} Returns false if scene is invalid; otherwise void.
|
|
503
|
+
*/
|
|
1076
504
|
loadScene(scene) {
|
|
1077
505
|
scene = typeof scene === "string" ? JSON.parse(scene) : scene;
|
|
1078
506
|
if (!scene) {
|
|
@@ -1081,6 +509,12 @@ class PrefViewer extends HTMLElement {
|
|
|
1081
509
|
this.#addTaskToQueue(scene, "environment");
|
|
1082
510
|
}
|
|
1083
511
|
|
|
512
|
+
/**
|
|
513
|
+
* Loads materials object or JSON string and adds it to the task queue.
|
|
514
|
+
* @public
|
|
515
|
+
* @param {object|string} materials - Materials object or JSON string.
|
|
516
|
+
* @returns {boolean|void} Returns false if materials are invalid; otherwise void.
|
|
517
|
+
*/
|
|
1084
518
|
loadMaterials(materials) {
|
|
1085
519
|
materials = typeof materials === "string" ? JSON.parse(materials) : materials;
|
|
1086
520
|
if (!materials) {
|
|
@@ -1089,6 +523,26 @@ class PrefViewer extends HTMLElement {
|
|
|
1089
523
|
this.#addTaskToQueue(materials, "materials");
|
|
1090
524
|
}
|
|
1091
525
|
|
|
526
|
+
/**
|
|
527
|
+
* Loads a drawing object or JSON string and adds it to the task queue.
|
|
528
|
+
* @public
|
|
529
|
+
* @param {object|string} drawing - Drawing object or JSON string.
|
|
530
|
+
* @returns {boolean|void} Returns false if drawing is invalid; otherwise void.
|
|
531
|
+
*/
|
|
532
|
+
loadDrawing(drawing) {
|
|
533
|
+
drawing = typeof drawing === "string" ? JSON.parse(drawing) : drawing;
|
|
534
|
+
if (!drawing) {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
this.#addTaskToQueue(drawing, "drawing");
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Sets viewer options from an object or JSON string and adds them to the task queue.
|
|
542
|
+
* @public
|
|
543
|
+
* @param {object|string} options - Options object or JSON string.
|
|
544
|
+
* @returns {boolean|void} Returns false if options are invalid; otherwise void.
|
|
545
|
+
*/
|
|
1092
546
|
setOptions(options) {
|
|
1093
547
|
options = typeof options === "string" ? JSON.parse(options) : options;
|
|
1094
548
|
if (!options) {
|
|
@@ -1097,55 +551,131 @@ class PrefViewer extends HTMLElement {
|
|
|
1097
551
|
this.#addTaskToQueue(options, "options");
|
|
1098
552
|
}
|
|
1099
553
|
|
|
554
|
+
/**
|
|
555
|
+
* Shows the 3D model by setting its visibility to true.
|
|
556
|
+
* Adds a visibility task to the queue for processing.
|
|
557
|
+
* @public
|
|
558
|
+
* @returns {void}
|
|
559
|
+
*/
|
|
1100
560
|
showModel() {
|
|
1101
|
-
|
|
1102
|
-
this.#
|
|
561
|
+
const config = { model: { visible: true } };
|
|
562
|
+
this.#addTaskToQueue(config, "visibility");
|
|
1103
563
|
}
|
|
1104
564
|
|
|
565
|
+
/**
|
|
566
|
+
* Hides the 3D model by setting its visibility to false.
|
|
567
|
+
* Adds a visibility task to the queue for processing.
|
|
568
|
+
* @public
|
|
569
|
+
* @returns {void}
|
|
570
|
+
*/
|
|
1105
571
|
hideModel() {
|
|
1106
|
-
|
|
1107
|
-
this.#
|
|
572
|
+
const config = { model: { visible: false } };
|
|
573
|
+
this.#addTaskToQueue(config, "visibility");
|
|
1108
574
|
}
|
|
1109
575
|
|
|
576
|
+
/**
|
|
577
|
+
* Shows the scene/environment by setting its visibility to true.
|
|
578
|
+
* Adds a visibility task to the queue for processing.
|
|
579
|
+
* @public
|
|
580
|
+
* @returns {void}
|
|
581
|
+
*/
|
|
1110
582
|
showScene() {
|
|
1111
|
-
|
|
1112
|
-
this.#
|
|
1113
|
-
this.#setVisibilityOfWallAndFloorInModel();
|
|
583
|
+
const config = { scene: { visible: true } };
|
|
584
|
+
this.#addTaskToQueue(config, "visibility");
|
|
1114
585
|
}
|
|
1115
586
|
|
|
587
|
+
/**
|
|
588
|
+
* Hides the scene/environment by setting its visibility to false.
|
|
589
|
+
* Adds a visibility task to the queue for processing.
|
|
590
|
+
* @public
|
|
591
|
+
* @returns {void}
|
|
592
|
+
*/
|
|
1116
593
|
hideScene() {
|
|
1117
|
-
|
|
1118
|
-
this.#
|
|
1119
|
-
this.#setVisibilityOfWallAndFloorInModel();
|
|
594
|
+
const config = { scene: { visible: false } };
|
|
595
|
+
this.#addTaskToQueue(config, "visibility");
|
|
1120
596
|
}
|
|
1121
597
|
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Initiates download of the current 3D model in GLB format.
|
|
601
|
+
* @public
|
|
602
|
+
* @returns {void}
|
|
603
|
+
*/
|
|
1122
604
|
downloadModelGLB() {
|
|
1123
|
-
|
|
1124
|
-
|
|
605
|
+
if (!this.#component3D) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
this.#component3D.downloadModelGLB();
|
|
1125
610
|
}
|
|
1126
611
|
|
|
612
|
+
/**
|
|
613
|
+
* Initiates download of the current 3D model in USDZ format.
|
|
614
|
+
* @public
|
|
615
|
+
* @returns {void}
|
|
616
|
+
*/
|
|
1127
617
|
downloadModelUSDZ() {
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
});
|
|
618
|
+
if (!this.#component3D) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
this.#component3D.downloadModelUSDZ();
|
|
1134
623
|
}
|
|
1135
624
|
|
|
625
|
+
/**
|
|
626
|
+
* Initiates download of both the 3D model and scene in USDZ format.
|
|
627
|
+
* @public
|
|
628
|
+
* @returns {void}
|
|
629
|
+
*/
|
|
1136
630
|
downloadModelAndSceneUSDZ() {
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
});
|
|
631
|
+
if (!this.#component3D) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
this.#component3D.downloadModelAndSceneUSDZ();
|
|
1143
636
|
}
|
|
1144
637
|
|
|
638
|
+
/**
|
|
639
|
+
* Initiates download of both the 3D model and scene in GLB format.
|
|
640
|
+
* @public
|
|
641
|
+
* @returns {void}
|
|
642
|
+
*/
|
|
1145
643
|
downloadModelAndSceneGLB() {
|
|
1146
|
-
|
|
1147
|
-
|
|
644
|
+
if (!this.#component3D) {
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
this.#component3D.downloadModelAndSceneGLB();
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Indicates whether the viewer has been initialized.
|
|
653
|
+
* @public
|
|
654
|
+
* @returns {boolean} True if initialized; otherwise, false.
|
|
655
|
+
*/
|
|
656
|
+
get initialized() {
|
|
657
|
+
return this.#isInitialized;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Indicates whether the viewer has finished loading.
|
|
662
|
+
* @public
|
|
663
|
+
* @returns {boolean} True if loaded; otherwise, false.
|
|
664
|
+
*/
|
|
665
|
+
get loaded() {
|
|
666
|
+
return this.#isLoaded;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Indicates whether the viewer is currently loading.
|
|
671
|
+
* @public
|
|
672
|
+
* @returns {boolean} True if loading; otherwise, false.
|
|
673
|
+
*/
|
|
674
|
+
get loading() {
|
|
675
|
+
return this.#isLoading;
|
|
1148
676
|
}
|
|
1149
677
|
}
|
|
1150
678
|
|
|
679
|
+
customElements.define("pref-viewer-2d", PrefViewer2D);
|
|
680
|
+
customElements.define("pref-viewer-3d", PrefViewer3D);
|
|
1151
681
|
customElements.define("pref-viewer", PrefViewer);
|