@preference-sl/pref-viewer 2.10.0-beta.10 → 2.10.0-beta.12
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 +1 -1
- package/src/index.js +203 -321
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* =============================================================================
|
|
3
|
-
* PrefViewer Web Component (JavaScript)
|
|
3
|
+
* PrefViewer Web Component (JavaScript) — con logging opcional por atributo
|
|
4
4
|
* =============================================================================
|
|
5
5
|
*
|
|
6
6
|
* Overview
|
|
@@ -47,13 +47,77 @@ import { DracoCompression } from "@babylonjs/core/Meshes/Compression/dracoCompre
|
|
|
47
47
|
import { initDb, loadModel } from "./gltf-storage.js";
|
|
48
48
|
|
|
49
49
|
class PrefViewer extends HTMLElement {
|
|
50
|
-
static LOG_PREFIX = "[PrefViewer]";
|
|
51
|
-
static LOG_LEVELS = { none: 0, error: 1, warn: 2, info: 3, debug: 4 };
|
|
52
|
-
// Por defecto NO loggear: control únicamente vía atributo `log-level`
|
|
53
|
-
static DEFAULT_LOG_LEVEL = "none";
|
|
54
|
-
|
|
55
50
|
#initialized = false;
|
|
56
|
-
|
|
51
|
+
|
|
52
|
+
// --- Logging -------------------------------------------------------------
|
|
53
|
+
#debugEnabled = false;
|
|
54
|
+
#logPrefix = "PrefViewer";
|
|
55
|
+
#timers = new Map();
|
|
56
|
+
#log = {
|
|
57
|
+
debug: () => {},
|
|
58
|
+
info: () => {},
|
|
59
|
+
warn: () => {},
|
|
60
|
+
error: () => {},
|
|
61
|
+
time: () => {},
|
|
62
|
+
timeEnd: () => {},
|
|
63
|
+
group: () => {},
|
|
64
|
+
groupEnd: () => {},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
#enableDebug(enable) {
|
|
68
|
+
const on = !!enable;
|
|
69
|
+
this.#debugEnabled = on;
|
|
70
|
+
if (!on) {
|
|
71
|
+
this.#log = {
|
|
72
|
+
debug: () => {},
|
|
73
|
+
info: () => {},
|
|
74
|
+
warn: () => {},
|
|
75
|
+
error: () => {},
|
|
76
|
+
time: () => {},
|
|
77
|
+
timeEnd: () => {},
|
|
78
|
+
group: () => {},
|
|
79
|
+
groupEnd: () => {},
|
|
80
|
+
};
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const tag = (lvl, args) => {
|
|
84
|
+
if (!args || !args.length) return [`${this.#logPrefix}:`];
|
|
85
|
+
const [first, ...rest] = args;
|
|
86
|
+
if (typeof first === "string") return [`${this.#logPrefix} [${lvl}] ${first}`, ...rest];
|
|
87
|
+
return [`${this.#logPrefix} [${lvl}]`, first, ...rest];
|
|
88
|
+
};
|
|
89
|
+
this.#log = {
|
|
90
|
+
debug: (...a) => console.debug(...tag("debug", a)),
|
|
91
|
+
info: (...a) => console.info(...tag("info", a)),
|
|
92
|
+
warn: (...a) => console.warn(...tag("warn", a)),
|
|
93
|
+
error: (...a) => console.error(...tag("error", a)),
|
|
94
|
+
time: (label) => {
|
|
95
|
+
const key = `${label}`;
|
|
96
|
+
this.#timers.set(key, performance.now());
|
|
97
|
+
console.debug(`${this.#logPrefix} [time] ${key} start`);
|
|
98
|
+
},
|
|
99
|
+
timeEnd: (label) => {
|
|
100
|
+
const key = `${label}`;
|
|
101
|
+
if (this.#timers.has(key)) {
|
|
102
|
+
const delta = (performance.now() - this.#timers.get(key)).toFixed(2);
|
|
103
|
+
console.debug(`${this.#logPrefix} [time] ${key} ${delta}ms`);
|
|
104
|
+
this.#timers.delete(key);
|
|
105
|
+
} else {
|
|
106
|
+
console.debug(`${this.#logPrefix} [time] ${key} (no start)`);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
group: (title) => console.group?.(`${this.#logPrefix} ${title}`),
|
|
110
|
+
groupEnd: () => console.groupEnd?.(),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#readDebugAttribute() {
|
|
115
|
+
const v = (this.getAttribute("debug") || "").trim().toLowerCase();
|
|
116
|
+
// true/1/yes/on
|
|
117
|
+
const enabled = v === "true" || v === "1" || v === "yes" || v === "on" || v === "debug";
|
|
118
|
+
this.#enableDebug(enabled);
|
|
119
|
+
}
|
|
120
|
+
// ------------------------------------------------------------------------
|
|
57
121
|
|
|
58
122
|
#data = {
|
|
59
123
|
containers: {
|
|
@@ -119,67 +183,6 @@ class PrefViewer extends HTMLElement {
|
|
|
119
183
|
},
|
|
120
184
|
};
|
|
121
185
|
|
|
122
|
-
#log(level, message, context) {
|
|
123
|
-
const levels = PrefViewer.LOG_LEVELS;
|
|
124
|
-
const current = levels[this.#logLevel] ?? levels[PrefViewer.DEFAULT_LOG_LEVEL];
|
|
125
|
-
const incoming = levels[level] ?? levels.info;
|
|
126
|
-
if (incoming > current || current === levels.none) return;
|
|
127
|
-
|
|
128
|
-
const logger = console[level] ?? console.log;
|
|
129
|
-
if (context !== undefined) {
|
|
130
|
-
logger(`${PrefViewer.LOG_PREFIX}: ${message}`, context);
|
|
131
|
-
} else {
|
|
132
|
-
logger(`${PrefViewer.LOG_PREFIX}: ${message}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
#setLogLevel(level) {
|
|
137
|
-
const value = String(level || "").toLowerCase();
|
|
138
|
-
this.#logLevel = (value in PrefViewer.LOG_LEVELS) ? value : PrefViewer.DEFAULT_LOG_LEVEL;
|
|
139
|
-
this.#logInfo("Log level set", { level: this.#logLevel });
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
#logDebug(message, context) {
|
|
143
|
-
this.#log("debug", message, context);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
#logInfo(message, context) {
|
|
147
|
-
this.#log("info", message, context);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
#logWarn(message, context) {
|
|
151
|
-
this.#log("warn", message, context);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
#logError(message, context) {
|
|
155
|
-
this.#log("error", message, context);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
#summarizeValue(value) {
|
|
159
|
-
if (typeof value === "string" && value.length > 150) {
|
|
160
|
-
return `${value.slice(0, 150)}… (${value.length} chars)`;
|
|
161
|
-
}
|
|
162
|
-
return value;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
#describeStorage(storage) {
|
|
166
|
-
if (!storage) {
|
|
167
|
-
return "none";
|
|
168
|
-
}
|
|
169
|
-
if (storage.db && storage.table && storage.id) {
|
|
170
|
-
return `IndexedDB(${storage.db}/${storage.table}#${storage.id})`;
|
|
171
|
-
}
|
|
172
|
-
if (typeof storage.url === "string") {
|
|
173
|
-
return storage.url.startsWith("data:") ? "data-url" : storage.url;
|
|
174
|
-
}
|
|
175
|
-
return "unknown";
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
static get observedAttributes() {
|
|
179
|
-
// Añadimos "log-level" para controlar logs fuera del objeto config
|
|
180
|
-
return ["config", "model", "scene", "show-model", "show-scene", "log-level"];
|
|
181
|
-
}
|
|
182
|
-
|
|
183
186
|
// DOM elements
|
|
184
187
|
#wrapper = null;
|
|
185
188
|
#canvas = null;
|
|
@@ -196,8 +199,9 @@ class PrefViewer extends HTMLElement {
|
|
|
196
199
|
|
|
197
200
|
constructor() {
|
|
198
201
|
super();
|
|
199
|
-
this.#logDebug("Constructing PrefViewer instance");
|
|
200
202
|
this.attachShadow({ mode: "open" });
|
|
203
|
+
this.#readDebugAttribute();
|
|
204
|
+
this.#log.info("constructor()");
|
|
201
205
|
this.#createCanvas();
|
|
202
206
|
this.#wrapCanvas();
|
|
203
207
|
// Point to whichever version you packaged or want to use:
|
|
@@ -210,15 +214,24 @@ class PrefViewer extends HTMLElement {
|
|
|
210
214
|
// JS fallback if WASM isn’t available
|
|
211
215
|
fallbackUrl: `${DRACO_BASE}/draco_decoder_gltf.js`,
|
|
212
216
|
};
|
|
217
|
+
this.#log.debug("Draco decoder configured", DracoCompression.Configuration.decoder);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
static get observedAttributes() {
|
|
221
|
+
// Añadimos "debug" como atributo observable
|
|
222
|
+
return ["config", "model", "scene", "show-model", "show-scene", "debug"];
|
|
213
223
|
}
|
|
214
224
|
|
|
215
225
|
attributeChangedCallback(name, _old, value) {
|
|
216
|
-
|
|
217
|
-
|
|
226
|
+
// Nota: el cambio de "debug" debe ocurrir antes de loguear otras cosas.
|
|
227
|
+
if (name === "debug") {
|
|
228
|
+
this.#enableDebug((value || "").toLowerCase() === "true" || value === "1" || value === "yes" || value === "on" || value === "debug");
|
|
229
|
+
this.#log.info(`attributeChanged: debug -> ${this.#debugEnabled}`);
|
|
218
230
|
return;
|
|
219
231
|
}
|
|
220
232
|
|
|
221
|
-
this.#
|
|
233
|
+
this.#log.debug("attributeChangedCallback()", { name, oldValue: _old, value });
|
|
234
|
+
|
|
222
235
|
let data = null;
|
|
223
236
|
switch (name) {
|
|
224
237
|
case "config":
|
|
@@ -232,8 +245,8 @@ class PrefViewer extends HTMLElement {
|
|
|
232
245
|
break;
|
|
233
246
|
case "show-model":
|
|
234
247
|
data = value.toLowerCase?.() === "true";
|
|
235
|
-
this.#logDebug("Toggling model visibility from attribute", { visible: data, initialized: this.#initialized });
|
|
236
248
|
if (this.#initialized) {
|
|
249
|
+
this.#log.info(`attr show-model -> ${data}`);
|
|
237
250
|
data ? this.showModel() : this.hideModel();
|
|
238
251
|
} else {
|
|
239
252
|
this.#data.containers.model.show = data;
|
|
@@ -241,8 +254,8 @@ class PrefViewer extends HTMLElement {
|
|
|
241
254
|
break;
|
|
242
255
|
case "show-scene":
|
|
243
256
|
data = value.toLowerCase?.() === "true";
|
|
244
|
-
this.#logDebug("Toggling scene visibility from attribute", { visible: data, initialized: this.#initialized });
|
|
245
257
|
if (this.#initialized) {
|
|
258
|
+
this.#log.info(`attr show-scene -> ${data}`);
|
|
246
259
|
data ? this.showScene() : this.hideScene();
|
|
247
260
|
} else {
|
|
248
261
|
this.#data.containers.environment.show = data;
|
|
@@ -252,9 +265,10 @@ class PrefViewer extends HTMLElement {
|
|
|
252
265
|
}
|
|
253
266
|
|
|
254
267
|
connectedCallback() {
|
|
268
|
+
this.#log.info("connectedCallback()");
|
|
255
269
|
if (!this.hasAttribute("config")) {
|
|
256
270
|
const error = 'PrefViewer: provide "models" as array of model and environment';
|
|
257
|
-
|
|
271
|
+
console.error(error);
|
|
258
272
|
this.dispatchEvent(
|
|
259
273
|
new CustomEvent("model-error", {
|
|
260
274
|
detail: { error: new Error(error) },
|
|
@@ -265,22 +279,21 @@ class PrefViewer extends HTMLElement {
|
|
|
265
279
|
return false;
|
|
266
280
|
}
|
|
267
281
|
|
|
268
|
-
this.#logDebug("Connected to DOM, initializing Babylon");
|
|
269
282
|
this.#initializeBabylon();
|
|
270
283
|
this.#loadContainers(true, true, true);
|
|
271
284
|
this.#initialized = true;
|
|
272
|
-
this.#
|
|
285
|
+
this.#log.info("initialized = true");
|
|
273
286
|
}
|
|
274
287
|
|
|
275
288
|
disconnectedCallback() {
|
|
276
|
-
this.#
|
|
289
|
+
this.#log.info("disconnectedCallback()");
|
|
277
290
|
this.#disposeEngine();
|
|
278
291
|
this.#canvasResizeObserver.disconnect();
|
|
279
292
|
}
|
|
280
293
|
|
|
281
294
|
// Web Component
|
|
282
295
|
#createCanvas() {
|
|
283
|
-
this.#
|
|
296
|
+
this.#log.debug("#createCanvas()");
|
|
284
297
|
this.#canvas = document.createElement("canvas");
|
|
285
298
|
Object.assign(this.#canvas.style, {
|
|
286
299
|
width: "100%",
|
|
@@ -288,11 +301,10 @@ class PrefViewer extends HTMLElement {
|
|
|
288
301
|
display: "block",
|
|
289
302
|
outline: "none",
|
|
290
303
|
});
|
|
291
|
-
this.#logDebug("Canvas element created and styled");
|
|
292
304
|
}
|
|
293
305
|
|
|
294
306
|
#wrapCanvas() {
|
|
295
|
-
this.#
|
|
307
|
+
this.#log.debug("#wrapCanvas()");
|
|
296
308
|
this.#wrapper = document.createElement("div");
|
|
297
309
|
Object.assign(this.#wrapper.style, {
|
|
298
310
|
width: "100%",
|
|
@@ -301,59 +313,53 @@ class PrefViewer extends HTMLElement {
|
|
|
301
313
|
});
|
|
302
314
|
this.#wrapper.appendChild(this.#canvas);
|
|
303
315
|
this.shadowRoot.append(this.#wrapper);
|
|
304
|
-
this.#logDebug("Canvas wrapper appended to shadow DOM");
|
|
305
316
|
}
|
|
306
317
|
|
|
307
318
|
// Data
|
|
308
319
|
#checkCameraChanged(options) {
|
|
309
320
|
if (!options || !options.camera) {
|
|
310
|
-
this.#logDebug("Camera options not provided or unchanged");
|
|
311
321
|
return false;
|
|
312
322
|
}
|
|
313
|
-
|
|
314
|
-
this.#data.options.camera.
|
|
315
|
-
this.#
|
|
323
|
+
const prev = this.#data.options.camera.value;
|
|
324
|
+
this.#data.options.camera.changed = options.camera && options.camera !== prev ? true : false;
|
|
325
|
+
this.#data.options.camera.value = this.#data.options.camera.changed ? options.camera : prev;
|
|
326
|
+
this.#log.debug("#checkCameraChanged()", { prev, next: this.#data.options.camera.value, changed: this.#data.options.camera.changed });
|
|
316
327
|
return this.#data.options.camera.changed;
|
|
317
328
|
}
|
|
318
329
|
|
|
319
330
|
#checkMaterialsChanged(options) {
|
|
320
331
|
if (!options) {
|
|
321
|
-
this.#logDebug("Material options not provided");
|
|
322
332
|
return false;
|
|
323
333
|
}
|
|
324
334
|
let someChanged = false;
|
|
325
335
|
Object.keys(this.#data.options.materials).forEach((material) => {
|
|
326
336
|
const key = `${material}Material`;
|
|
327
|
-
|
|
328
|
-
this.#data.options.materials[material].
|
|
337
|
+
const prev = this.#data.options.materials[material].value;
|
|
338
|
+
this.#data.options.materials[material].changed = options[key] && options[key] !== prev ? true : false;
|
|
339
|
+
this.#data.options.materials[material].value = this.#data.options.materials[material].changed ? options[key] : prev;
|
|
329
340
|
someChanged = someChanged || this.#data.options.materials[material].changed;
|
|
330
341
|
});
|
|
331
|
-
this.#
|
|
332
|
-
changed: someChanged,
|
|
333
|
-
values: Object.entries(this.#data.options.materials).reduce((acc, [key, entry]) => {
|
|
334
|
-
acc[key] = { value: entry.value, changed: entry.changed };
|
|
335
|
-
return acc;
|
|
336
|
-
}, {}),
|
|
337
|
-
});
|
|
342
|
+
this.#log.debug("#checkMaterialsChanged()", { someChanged, values: this.#data.options.materials });
|
|
338
343
|
return someChanged;
|
|
339
344
|
}
|
|
340
345
|
|
|
341
346
|
#storeChangedFlagsForContainer(container) {
|
|
347
|
+
this.#log.debug("#storeChangedFlagsForContainer()", { name: container.name, changed: container.changed });
|
|
342
348
|
container.timestamp = container.changed.timestamp;
|
|
343
349
|
container.size = container.changed.size;
|
|
344
|
-
this.#logDebug("Stored change flags for container", { name: container.name, timestamp: container.timestamp, size: container.size });
|
|
345
350
|
}
|
|
346
351
|
|
|
347
352
|
#resetChangedFlags() {
|
|
353
|
+
this.#log.debug("#resetChangedFlags()");
|
|
348
354
|
Object.values(this.#data.containers).forEach((container) => (container.changed = false));
|
|
349
355
|
Object.values(this.#data.options.materials).forEach((material) => (material.changed = false));
|
|
350
356
|
this.#data.options.camera.changed = false;
|
|
351
|
-
this.#logDebug("Reset change flags across containers and options");
|
|
352
357
|
}
|
|
353
358
|
|
|
354
359
|
// Babylon.js
|
|
355
360
|
async #initializeBabylon() {
|
|
356
|
-
this.#
|
|
361
|
+
this.#log.group("#initializeBabylon()");
|
|
362
|
+
this.#log.time("babylon:init");
|
|
357
363
|
this.#engine = new Engine(this.#canvas, true, { alpha: true });
|
|
358
364
|
this.#scene = new Scene(this.#engine);
|
|
359
365
|
this.#scene.clearColor = new Color4(1, 1, 1, 1);
|
|
@@ -363,36 +369,35 @@ class PrefViewer extends HTMLElement {
|
|
|
363
369
|
|
|
364
370
|
this.#engine.runRenderLoop(() => this.#scene && this.#scene.render());
|
|
365
371
|
this.#canvasResizeObserver.observe(this.#canvas);
|
|
366
|
-
this.#logDebug("Engine render loop started and resize observer attached");
|
|
367
372
|
|
|
368
373
|
await this.#createXRExperience();
|
|
369
|
-
this.#
|
|
374
|
+
this.#log.timeEnd("babylon:init");
|
|
375
|
+
this.#log.groupEnd();
|
|
370
376
|
}
|
|
371
377
|
|
|
372
378
|
addStylesToARButton() {
|
|
373
|
-
this.#
|
|
379
|
+
this.#log.debug("addStylesToARButton()");
|
|
374
380
|
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"}';
|
|
375
381
|
const style = document.createElement("style");
|
|
376
382
|
style.appendChild(document.createTextNode(css));
|
|
377
383
|
this.#wrapper.appendChild(style);
|
|
378
|
-
this.#logDebug("AR button styles applied");
|
|
379
384
|
}
|
|
380
385
|
|
|
381
386
|
async #createXRExperience() {
|
|
382
387
|
if (this.#XRExperience) {
|
|
383
|
-
this.#logDebug("XR experience already created, skipping");
|
|
384
388
|
return true;
|
|
385
389
|
}
|
|
386
390
|
|
|
387
|
-
this.#logDebug("Attempting to create XR experience");
|
|
388
391
|
const sessionMode = "immersive-ar";
|
|
392
|
+
this.#log.info("#createXRExperience()", { sessionMode });
|
|
389
393
|
const sessionSupported = await WebXRSessionManager.IsSessionSupportedAsync(sessionMode);
|
|
390
394
|
if (!sessionSupported) {
|
|
391
|
-
|
|
395
|
+
console.info("PrefViewer: WebXR in mode AR is not supported");
|
|
392
396
|
return false;
|
|
393
397
|
}
|
|
394
398
|
|
|
395
399
|
try {
|
|
400
|
+
this.#log.time("xr:create");
|
|
396
401
|
const ground = MeshBuilder.CreateGround("ground", { width: 1000, height: 1000 }, this.#scene);
|
|
397
402
|
ground.isVisible = false;
|
|
398
403
|
|
|
@@ -407,7 +412,6 @@ class PrefViewer extends HTMLElement {
|
|
|
407
412
|
};
|
|
408
413
|
|
|
409
414
|
this.#XRExperience = await WebXRDefaultExperience.CreateAsync(this.#scene, options);
|
|
410
|
-
this.#logInfo("XR experience created successfully", { sessionMode });
|
|
411
415
|
|
|
412
416
|
const featuresManager = this.#XRExperience.baseExperience.featuresManager;
|
|
413
417
|
featuresManager.enableFeature(WebXRFeatureName.TELEPORTATION, "stable", {
|
|
@@ -415,32 +419,31 @@ class PrefViewer extends HTMLElement {
|
|
|
415
419
|
floorMeshes: [ground],
|
|
416
420
|
timeToTeleport: 1500,
|
|
417
421
|
});
|
|
418
|
-
this.#logDebug("XR teleportation feature enabled");
|
|
419
422
|
|
|
420
423
|
this.#XRExperience.baseExperience.sessionManager.onXRReady.add(() => {
|
|
424
|
+
this.#log.info("XR onXRReady");
|
|
421
425
|
// Set the initial position of xrCamera: use nonVRCamera, which contains a copy of the original this.#scene.activeCamera before entering XR
|
|
422
426
|
this.#XRExperience.baseExperience.camera.setTransformationFromNonVRCamera(this.#XRExperience.baseExperience._nonVRCamera);
|
|
423
427
|
this.#XRExperience.baseExperience.camera.setTarget(Vector3.Zero());
|
|
424
428
|
this.#XRExperience.baseExperience.onInitialXRPoseSetObservable.notifyObservers(this.#XRExperience.baseExperience.camera);
|
|
425
|
-
this.#logDebug("XR session ready and camera transformed");
|
|
426
429
|
});
|
|
427
430
|
|
|
428
431
|
this.addStylesToARButton();
|
|
432
|
+
this.#log.timeEnd("xr:create");
|
|
429
433
|
} catch (error) {
|
|
430
|
-
|
|
434
|
+
console.warn("PrefViewer: failed to create WebXR experience", error);
|
|
435
|
+
this.#log.error("XR creation failed", error);
|
|
431
436
|
this.#XRExperience = null;
|
|
432
437
|
}
|
|
433
438
|
}
|
|
434
439
|
|
|
435
440
|
#canvasResizeObserver = new ResizeObserver(() => {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
this.#engine.resize();
|
|
439
|
-
}
|
|
441
|
+
this.#log.debug("ResizeObserver -> engine.resize()");
|
|
442
|
+
this.#engine && this.#engine.resize();
|
|
440
443
|
});
|
|
441
444
|
|
|
442
445
|
#createCamera() {
|
|
443
|
-
this.#
|
|
446
|
+
this.#log.debug("#createCamera()");
|
|
444
447
|
this.#camera = new ArcRotateCamera("camera", (3 * Math.PI) / 2, Math.PI * 0.47, 10, Vector3.Zero(), this.#scene);
|
|
445
448
|
this.#camera.upperBetaLimit = Math.PI * 0.48;
|
|
446
449
|
this.#camera.lowerBetaLimit = Math.PI * 0.25;
|
|
@@ -449,16 +452,10 @@ class PrefViewer extends HTMLElement {
|
|
|
449
452
|
this.#camera.metadata = { locked: false }
|
|
450
453
|
this.#camera = this.#camera;
|
|
451
454
|
this.#camera.attachControl(this.#canvas, true);
|
|
452
|
-
this.#logDebug("Camera created", {
|
|
453
|
-
upperBetaLimit: this.#camera.upperBetaLimit,
|
|
454
|
-
lowerBetaLimit: this.#camera.lowerBetaLimit,
|
|
455
|
-
lowerRadiusLimit: this.#camera.lowerRadiusLimit,
|
|
456
|
-
upperRadiusLimit: this.#camera.upperRadiusLimit,
|
|
457
|
-
});
|
|
458
455
|
}
|
|
459
456
|
|
|
460
457
|
#createLights() {
|
|
461
|
-
this.#
|
|
458
|
+
this.#log.debug("#createLights()");
|
|
462
459
|
// 1) Stronger ambient fill
|
|
463
460
|
this.#hemiLight = new HemisphericLight("hemiLight", new Vector3(-10, 10, -10), this.#scene);
|
|
464
461
|
this.#hemiLight.intensity = 0.6;
|
|
@@ -478,52 +475,33 @@ class PrefViewer extends HTMLElement {
|
|
|
478
475
|
this.#cameraLight = new PointLight("pl", this.#camera.position, this.#scene);
|
|
479
476
|
this.#cameraLight.parent = this.#camera;
|
|
480
477
|
this.#cameraLight.intensity = 0.3;
|
|
481
|
-
this.#logDebug("Scene lights configured", {
|
|
482
|
-
hemiIntensity: this.#hemiLight.intensity,
|
|
483
|
-
dirIntensity: this.#dirLight.intensity,
|
|
484
|
-
pointIntensity: this.#cameraLight.intensity,
|
|
485
|
-
shadowKernel: this.#shadowGen.blurKernel,
|
|
486
|
-
});
|
|
487
478
|
}
|
|
488
479
|
|
|
489
480
|
#setupInteraction() {
|
|
490
|
-
this.#
|
|
481
|
+
this.#log.debug("#setupInteraction()");
|
|
491
482
|
this.#canvas.addEventListener("wheel", (event) => {
|
|
492
483
|
if (!this.#scene || !this.#camera) {
|
|
493
|
-
this.#logWarn("Wheel interaction ignored; scene or camera missing");
|
|
494
484
|
return false;
|
|
495
485
|
}
|
|
496
|
-
//const pick = this.#scene.pick(this.#scene.pointerX, this.#scene.pointerY);
|
|
497
|
-
//this.#camera.target = pick.hit ? pick.pickedPoint.clone() : this.#camera.target;
|
|
498
486
|
if (!this.#scene.activeCamera.metadata?.locked) {
|
|
499
487
|
this.#scene.activeCamera.inertialRadiusOffset -= event.deltaY * this.#scene.activeCamera.wheelPrecision * 0.001;
|
|
500
|
-
this.#logDebug("Processed wheel interaction", {
|
|
501
|
-
deltaY: event.deltaY,
|
|
502
|
-
inertialRadiusOffset: this.#scene.activeCamera.inertialRadiusOffset,
|
|
503
|
-
});
|
|
504
|
-
} else {
|
|
505
|
-
this.#logDebug("Wheel interaction ignored because camera is locked");
|
|
506
488
|
}
|
|
507
489
|
event.preventDefault();
|
|
508
490
|
});
|
|
509
491
|
}
|
|
510
492
|
|
|
511
493
|
#disposeEngine() {
|
|
512
|
-
if (!this.#engine)
|
|
513
|
-
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
this.#logDebug("Disposing Babylon resources");
|
|
494
|
+
if (!this.#engine) return;
|
|
495
|
+
this.#log.info("#disposeEngine()");
|
|
517
496
|
this.#engine.dispose();
|
|
518
497
|
this.#engine = this.#scene = this.#camera = null;
|
|
519
498
|
this.#hemiLight = this.#dirLight = this.#cameraLight = null;
|
|
520
499
|
this.#shadowGen = null;
|
|
521
|
-
this.#logDebug("Babylon resources disposed");
|
|
522
500
|
}
|
|
523
501
|
|
|
524
502
|
// Utility methods for loading gltf/glb
|
|
525
503
|
async #getServerFileDataHeader(uri) {
|
|
526
|
-
this.#
|
|
504
|
+
this.#log.debug("#getServerFileDataHeader()", { uri });
|
|
527
505
|
return new Promise((resolve) => {
|
|
528
506
|
const xhr = new XMLHttpRequest();
|
|
529
507
|
xhr.open("HEAD", uri, true);
|
|
@@ -532,15 +510,15 @@ class PrefViewer extends HTMLElement {
|
|
|
532
510
|
if (xhr.status === 200) {
|
|
533
511
|
const size = parseInt(xhr.getResponseHeader("Content-Length"));
|
|
534
512
|
const timestamp = new Date(xhr.getResponseHeader("Last-Modified")).toISOString();
|
|
535
|
-
this.#
|
|
513
|
+
this.#log.debug("HEAD ok", { size, timestamp });
|
|
536
514
|
resolve(size, timestamp);
|
|
537
515
|
} else {
|
|
538
|
-
this.#
|
|
516
|
+
this.#log.warn("HEAD failed", { status: xhr.status });
|
|
539
517
|
resolve(0, null);
|
|
540
518
|
}
|
|
541
519
|
};
|
|
542
520
|
xhr.onerror = () => {
|
|
543
|
-
this.#
|
|
521
|
+
this.#log.error("HEAD network error");
|
|
544
522
|
resolve(0, null);
|
|
545
523
|
};
|
|
546
524
|
xhr.send();
|
|
@@ -548,15 +526,14 @@ class PrefViewer extends HTMLElement {
|
|
|
548
526
|
}
|
|
549
527
|
|
|
550
528
|
#transformUrl(url) {
|
|
529
|
+
this.#log.debug("#transformUrl()", { url });
|
|
551
530
|
return new Promise((resolve) => {
|
|
552
|
-
|
|
553
|
-
this.#logDebug("Transformed URL", { original: url, transformed });
|
|
554
|
-
resolve(transformed);
|
|
531
|
+
resolve(url.replace(/^blob:[^/]+\//i, "").replace(/\\/g, "/"));
|
|
555
532
|
});
|
|
556
533
|
}
|
|
557
534
|
|
|
558
535
|
#decodeBase64(base64) {
|
|
559
|
-
this.#
|
|
536
|
+
this.#log.debug("#decodeBase64()", { length: (base64 || "").length });
|
|
560
537
|
const [, payload] = base64.split(",");
|
|
561
538
|
const raw = payload || base64;
|
|
562
539
|
let decoded = "";
|
|
@@ -566,62 +543,49 @@ class PrefViewer extends HTMLElement {
|
|
|
566
543
|
try {
|
|
567
544
|
decoded = atob(raw);
|
|
568
545
|
} catch {
|
|
569
|
-
this.#
|
|
546
|
+
this.#log.warn("atob failed (not base64?)");
|
|
570
547
|
return { blob, extension, size };
|
|
571
548
|
}
|
|
572
549
|
let isJson = false;
|
|
573
550
|
try {
|
|
574
551
|
JSON.parse(decoded);
|
|
575
552
|
isJson = true;
|
|
576
|
-
} catch {
|
|
553
|
+
} catch {}
|
|
577
554
|
extension = isJson ? ".gltf" : ".glb";
|
|
578
555
|
const type = isJson ? "model/gltf+json" : "model/gltf-binary";
|
|
579
556
|
const array = Uint8Array.from(decoded, (c) => c.charCodeAt(0));
|
|
580
557
|
blob = new Blob([array], { type });
|
|
581
|
-
this.#logDebug("Decoded Base64 payload", { isJson, size, extension });
|
|
582
558
|
return { blob, extension, size };
|
|
583
559
|
}
|
|
584
560
|
|
|
585
561
|
async #initStorage(db, table) {
|
|
586
|
-
this.#
|
|
562
|
+
this.#log.debug("#initStorage()", { db, table });
|
|
587
563
|
if (window.gltfDB && window.gltfDB.name === db && window.gltfDB.objectStoreNames.contains(table)) {
|
|
588
|
-
this.#logDebug("Reusing existing IndexedDB connection", { db, table });
|
|
589
564
|
return true;
|
|
590
565
|
}
|
|
591
566
|
await initDb(db, table);
|
|
592
|
-
this.#logDebug("IndexedDB initialized", { db, table });
|
|
593
567
|
}
|
|
594
568
|
|
|
595
569
|
// Methods for managing Asset Containers
|
|
596
570
|
#setVisibilityOfWallAndFloorInModel(show) {
|
|
571
|
+
this.#log.debug("#setVisibilityOfWallAndFloorInModel()", { show });
|
|
597
572
|
if (!this.#data.containers.model.assetContainer || !this.#data.containers.model.visible) {
|
|
598
|
-
this.#logDebug("Skipping wall/floor visibility update", {
|
|
599
|
-
hasModel: !!this.#data.containers.model.assetContainer,
|
|
600
|
-
modelVisible: this.#data.containers.model.visible,
|
|
601
|
-
});
|
|
602
573
|
return false;
|
|
603
574
|
}
|
|
604
575
|
show = show !== undefined ? show : this.#data.containers.environment.visible;
|
|
605
576
|
const prefixes = Object.values(this.#data.options.materials).map((material) => material.prefix);
|
|
606
577
|
this.#data.containers.model.assetContainer.meshes.filter((meshToFilter) => prefixes.some((prefix) => meshToFilter.name.startsWith(prefix))).forEach((mesh) => mesh.setEnabled(show));
|
|
607
|
-
this.#logDebug("Updated wall and floor visibility", { show });
|
|
608
578
|
}
|
|
609
579
|
|
|
610
580
|
#setOptionsMaterial(optionMaterial) {
|
|
611
581
|
if (!optionMaterial || !optionMaterial.prefix || !optionMaterial.value) {
|
|
612
|
-
this.#logWarn("Material option invalid", { optionMaterial });
|
|
613
582
|
return false;
|
|
614
583
|
}
|
|
615
|
-
|
|
616
|
-
this.#logDebug("Applying material option", {
|
|
617
|
-
prefix: optionMaterial.prefix,
|
|
618
|
-
value: optionMaterial.value,
|
|
619
|
-
changed: optionMaterial.changed,
|
|
620
|
-
});
|
|
584
|
+
this.#log.debug("#setOptionsMaterial()", optionMaterial);
|
|
621
585
|
|
|
622
586
|
const material = this.#data.containers.materials.assetContainer?.materials.find((mat) => mat.name === optionMaterial.value) || null;
|
|
623
587
|
if (!material) {
|
|
624
|
-
this.#
|
|
588
|
+
this.#log.warn("material not found", { wanted: optionMaterial.value });
|
|
625
589
|
return false;
|
|
626
590
|
}
|
|
627
591
|
|
|
@@ -633,7 +597,6 @@ class PrefViewer extends HTMLElement {
|
|
|
633
597
|
containers.push(this.#data.containers.environment.assetContainer);
|
|
634
598
|
}
|
|
635
599
|
if (containers.length === 0) {
|
|
636
|
-
this.#logDebug("No containers required material update", { prefix: optionMaterial.prefix });
|
|
637
600
|
return false;
|
|
638
601
|
}
|
|
639
602
|
|
|
@@ -644,145 +607,98 @@ class PrefViewer extends HTMLElement {
|
|
|
644
607
|
.forEach((mesh) => {
|
|
645
608
|
mesh.material = material;
|
|
646
609
|
someSetted = true;
|
|
647
|
-
this.#logDebug("Assigned material to mesh", { mesh: mesh.name, material: material.name });
|
|
648
610
|
})
|
|
649
611
|
);
|
|
650
612
|
|
|
651
|
-
this.#logDebug("Material option applied", {
|
|
652
|
-
prefix: optionMaterial.prefix,
|
|
653
|
-
value: optionMaterial.value,
|
|
654
|
-
applied: someSetted,
|
|
655
|
-
containers: containers.map((container) => container.name),
|
|
656
|
-
});
|
|
657
|
-
|
|
658
613
|
return someSetted;
|
|
659
614
|
}
|
|
660
615
|
|
|
661
616
|
#setOptionsMaterials() {
|
|
662
|
-
|
|
663
|
-
this.#logDebug("Skipping materials update; materials container is missing");
|
|
664
|
-
return false;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
this.#logDebug("Applying material options batch");
|
|
617
|
+
this.#log.debug("#setOptionsMaterials()");
|
|
668
618
|
let someSetted = false;
|
|
669
619
|
Object.values(this.#data.options.materials).forEach((material) => {
|
|
670
620
|
let settedMaterial = this.#setOptionsMaterial(material);
|
|
671
621
|
someSetted = someSetted || settedMaterial;
|
|
672
622
|
});
|
|
673
|
-
this.#logDebug("Material batch processing finished", { appliedAny: someSetted });
|
|
674
623
|
return someSetted;
|
|
675
624
|
}
|
|
676
625
|
|
|
677
626
|
#setOptionsCamera() {
|
|
627
|
+
this.#log.debug("#setOptionsCamera()", this.#data.options.camera);
|
|
678
628
|
if (!this.#data.options.camera.value || (!this.#data.options.camera.changed && !this.#data.containers.model.assetContainer.changed)) {
|
|
679
|
-
this.#logDebug("No camera option update necessary", {
|
|
680
|
-
value: this.#data.options.camera.value,
|
|
681
|
-
changed: this.#data.options.camera.changed,
|
|
682
|
-
modelChanged: this.#data.containers.model.assetContainer?.changed,
|
|
683
|
-
});
|
|
684
629
|
return false;
|
|
685
630
|
}
|
|
686
631
|
|
|
687
632
|
let camera = this.#data.containers.model.assetContainer?.cameras.find((cam) => cam.name === this.#data.options.camera.value) || null;
|
|
688
633
|
if (!camera) {
|
|
689
|
-
this.#
|
|
634
|
+
this.#log.warn("camera not found", { wanted: this.#data.options.camera.value });
|
|
690
635
|
return false;
|
|
691
636
|
}
|
|
692
637
|
|
|
693
638
|
camera.metadata = { locked: this.#data.options.camera.locked };
|
|
694
639
|
if (!this.#data.options.camera.locked) {
|
|
695
640
|
camera.attachControl(this.#canvas, true);
|
|
696
|
-
this.#logDebug("Attached unlocked camera control", { camera: camera.name });
|
|
697
|
-
} else {
|
|
698
|
-
this.#logDebug("Using locked camera configuration", { camera: camera.name });
|
|
699
641
|
}
|
|
700
642
|
this.#scene.activeCamera = camera;
|
|
701
|
-
this.#logDebug("Active camera set", { camera: camera.name });
|
|
702
643
|
|
|
703
644
|
return true;
|
|
704
645
|
}
|
|
705
646
|
|
|
706
647
|
#addContainer(container) {
|
|
707
648
|
if (container.assetContainer && !container.visible && container.show) {
|
|
649
|
+
this.#log.debug("#addContainer()", { name: container.name });
|
|
708
650
|
container.assetContainer.addAllToScene();
|
|
709
651
|
container.visible = true;
|
|
710
|
-
this.#logDebug("Added container to scene", { name: container.name });
|
|
711
|
-
} else {
|
|
712
|
-
this.#logDebug("Skipped adding container", {
|
|
713
|
-
name: container?.name,
|
|
714
|
-
hasAssetContainer: !!container?.assetContainer,
|
|
715
|
-
visible: container?.visible,
|
|
716
|
-
show: container?.show,
|
|
717
|
-
});
|
|
718
652
|
}
|
|
719
653
|
}
|
|
720
654
|
|
|
721
655
|
#removeContainer(container) {
|
|
722
656
|
if (container.assetContainer && container.visible) {
|
|
657
|
+
this.#log.debug("#removeContainer()", { name: container.name });
|
|
723
658
|
container.assetContainer.removeAllFromScene();
|
|
724
659
|
container.visible = false;
|
|
725
|
-
this.#logDebug("Removed container from scene", { name: container.name });
|
|
726
|
-
} else {
|
|
727
|
-
this.#logDebug("Skipped removing container", {
|
|
728
|
-
name: container?.name,
|
|
729
|
-
hasAssetContainer: !!container?.assetContainer,
|
|
730
|
-
visible: container?.visible,
|
|
731
|
-
});
|
|
732
660
|
}
|
|
733
661
|
}
|
|
734
662
|
|
|
735
663
|
#replaceContainer(container, newAssetContainer) {
|
|
736
|
-
this.#
|
|
664
|
+
this.#log.debug("#replaceContainer()", { name: container.name });
|
|
737
665
|
this.#removeContainer(container);
|
|
738
666
|
container.assetContainer = newAssetContainer;
|
|
739
667
|
container.assetContainer.meshes.forEach((mesh) => {
|
|
740
668
|
mesh.receiveShadows = true;
|
|
741
669
|
this.#shadowGen.addShadowCaster(mesh, true);
|
|
742
|
-
this.#logDebug("Configured mesh for shadows", { container: container.name, mesh: mesh.name });
|
|
743
670
|
});
|
|
744
671
|
this.#addContainer(container);
|
|
745
|
-
this.#logDebug("Container replacement complete", {
|
|
746
|
-
name: container.name,
|
|
747
|
-
meshCount: container.assetContainer.meshes.length,
|
|
748
|
-
});
|
|
749
672
|
}
|
|
750
673
|
|
|
751
674
|
async #loadAssetContainer(container) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
container: container?.name,
|
|
755
|
-
storage: this.#describeStorage(storage),
|
|
756
|
-
});
|
|
675
|
+
this.#log.group(`#loadAssetContainer(${container?.name})`);
|
|
676
|
+
let storage = container?.storage;
|
|
757
677
|
|
|
758
678
|
if (!storage) {
|
|
759
|
-
this.#
|
|
679
|
+
this.#log.warn("no storage provided");
|
|
680
|
+
this.#log.groupEnd();
|
|
760
681
|
return false;
|
|
761
682
|
}
|
|
762
683
|
|
|
763
684
|
let source = storage.url || null;
|
|
764
685
|
|
|
765
686
|
if (storage.db && storage.table && storage.id) {
|
|
766
|
-
this.#logDebug("Loading container from IndexedDB", {
|
|
767
|
-
container: container.name,
|
|
768
|
-
db: storage.db,
|
|
769
|
-
table: storage.table,
|
|
770
|
-
id: storage.id,
|
|
771
|
-
});
|
|
772
687
|
await this.#initStorage(storage.db, storage.table);
|
|
773
688
|
const object = await loadModel(storage.id, storage.table);
|
|
774
689
|
source = object.data;
|
|
775
690
|
if (object.timestamp === container.timestamp) {
|
|
776
|
-
this.#
|
|
691
|
+
this.#log.info("no change from IndexedDB (timestamp match)");
|
|
692
|
+
this.#log.groupEnd();
|
|
777
693
|
return false;
|
|
778
694
|
} else {
|
|
779
695
|
container.changed = { timestamp: object.timestamp, size: object.size };
|
|
780
|
-
this.#logDebug("IndexedDB model marked as changed", { container: container.name, metadata: container.changed });
|
|
781
696
|
}
|
|
782
697
|
}
|
|
783
698
|
|
|
784
699
|
if (!source) {
|
|
785
|
-
this.#
|
|
700
|
+
this.#log.warn("no source URL or data");
|
|
701
|
+
this.#log.groupEnd();
|
|
786
702
|
return false;
|
|
787
703
|
}
|
|
788
704
|
|
|
@@ -790,17 +706,17 @@ class PrefViewer extends HTMLElement {
|
|
|
790
706
|
|
|
791
707
|
let { blob, extension, size } = this.#decodeBase64(source);
|
|
792
708
|
if (blob && extension) {
|
|
793
|
-
this.#
|
|
709
|
+
this.#log.debug("source is base64", { extension, size });
|
|
794
710
|
file = new File([blob], `${container.name}${extension}`, {
|
|
795
711
|
type: blob.type,
|
|
796
712
|
});
|
|
797
713
|
if (!container.changed) {
|
|
798
714
|
if (container.timestamp === null && container.size === size) {
|
|
799
|
-
this.#
|
|
715
|
+
this.#log.info("no change (base64 size match)");
|
|
716
|
+
this.#log.groupEnd();
|
|
800
717
|
return false;
|
|
801
718
|
} else {
|
|
802
719
|
container.changed = { timestamp: null, size: size };
|
|
803
|
-
this.#logDebug("Base64 model marked as changed", { container: container.name, size });
|
|
804
720
|
}
|
|
805
721
|
}
|
|
806
722
|
} else {
|
|
@@ -808,15 +724,11 @@ class PrefViewer extends HTMLElement {
|
|
|
808
724
|
extension = extMatch ? `.${extMatch[1].toLowerCase()}` : ".gltf";
|
|
809
725
|
const { fileSize, fileTimestamp } = await this.#getServerFileDataHeader(source);
|
|
810
726
|
if (container.timestamp === fileTimestamp && container.size === fileSize) {
|
|
811
|
-
this.#
|
|
812
|
-
|
|
813
|
-
fileTimestamp,
|
|
814
|
-
fileSize,
|
|
815
|
-
});
|
|
727
|
+
this.#log.info("no change (remote HEAD match)");
|
|
728
|
+
this.#log.groupEnd();
|
|
816
729
|
return false;
|
|
817
730
|
} else {
|
|
818
731
|
container.changed = { timestamp: fileTimestamp, size: fileSize };
|
|
819
|
-
this.#logDebug("Remote model marked as changed", { container: container.name, metadata: container.changed });
|
|
820
732
|
}
|
|
821
733
|
}
|
|
822
734
|
|
|
@@ -830,12 +742,15 @@ class PrefViewer extends HTMLElement {
|
|
|
830
742
|
},
|
|
831
743
|
};
|
|
832
744
|
|
|
833
|
-
this.#
|
|
834
|
-
|
|
745
|
+
this.#log.time(`babylon:LoadAssetContainerAsync:${container.name}`);
|
|
746
|
+
const res = LoadAssetContainerAsync(file || source, this.#scene, options);
|
|
747
|
+
this.#log.groupEnd();
|
|
748
|
+
return res;
|
|
835
749
|
}
|
|
836
750
|
|
|
837
751
|
async #loadContainers(loadModel = true, loadEnvironment = true, loadMaterials = true) {
|
|
838
|
-
this.#
|
|
752
|
+
this.#log.group("#loadContainers()");
|
|
753
|
+
this.#log.debug("flags", { loadModel, loadEnvironment, loadMaterials });
|
|
839
754
|
const promiseArray = [];
|
|
840
755
|
promiseArray.push(loadModel ? this.#loadAssetContainer(this.#data.containers.model) : false);
|
|
841
756
|
promiseArray.push(loadEnvironment ? this.#loadAssetContainer(this.#data.containers.environment) : false);
|
|
@@ -848,29 +763,29 @@ class PrefViewer extends HTMLElement {
|
|
|
848
763
|
const materialsContainer = values[2];
|
|
849
764
|
|
|
850
765
|
if (modelContainer.status === "fulfilled" && modelContainer.value) {
|
|
766
|
+
this.#log.timeEnd(`babylon:LoadAssetContainerAsync:model`);
|
|
851
767
|
this.#replaceContainer(this.#data.containers.model, modelContainer.value);
|
|
852
768
|
this.#storeChangedFlagsForContainer(this.#data.containers.model);
|
|
853
|
-
this.#logInfo("Model container loaded successfully");
|
|
854
769
|
} else {
|
|
770
|
+
this.#log.debug("model container unchanged / not loaded");
|
|
855
771
|
this.#data.containers.model.show ? this.#addContainer(this.#data.containers.model) : this.#removeContainer(this.#data.containers.model);
|
|
856
|
-
this.#logDebug("Model container load skipped or failed", { status: modelContainer.status });
|
|
857
772
|
}
|
|
858
773
|
|
|
859
774
|
if (environmentContainer.status === "fulfilled" && environmentContainer.value) {
|
|
775
|
+
this.#log.timeEnd(`babylon:LoadAssetContainerAsync:environment`);
|
|
860
776
|
this.#replaceContainer(this.#data.containers.environment, environmentContainer.value);
|
|
861
777
|
this.#storeChangedFlagsForContainer(this.#data.containers.environment);
|
|
862
|
-
this.#logInfo("Environment container loaded successfully");
|
|
863
778
|
} else {
|
|
779
|
+
this.#log.debug("environment container unchanged / not loaded");
|
|
864
780
|
this.#data.containers.environment.show ? this.#addContainer(this.#data.containers.environment) : this.#removeContainer(this.#data.containers.environment);
|
|
865
|
-
this.#logDebug("Environment container load skipped or failed", { status: environmentContainer.status });
|
|
866
781
|
}
|
|
867
782
|
|
|
868
783
|
if (materialsContainer.status === "fulfilled" && materialsContainer.value) {
|
|
784
|
+
this.#log.timeEnd(`babylon:LoadAssetContainerAsync:materials`);
|
|
869
785
|
this.#replaceContainer(this.#data.containers.materials, materialsContainer.value);
|
|
870
786
|
this.#storeChangedFlagsForContainer(this.#data.containers.materials);
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
this.#logDebug("Materials container load skipped or failed", { status: materialsContainer.status });
|
|
787
|
+
} else if (loadMaterials) {
|
|
788
|
+
this.#log.debug("materials container unchanged / not loaded");
|
|
874
789
|
}
|
|
875
790
|
|
|
876
791
|
this.#setOptionsMaterials();
|
|
@@ -879,7 +794,7 @@ class PrefViewer extends HTMLElement {
|
|
|
879
794
|
|
|
880
795
|
this.#resetChangedFlags();
|
|
881
796
|
|
|
882
|
-
this.#
|
|
797
|
+
this.#log.info("dispatch: model-loaded");
|
|
883
798
|
this.dispatchEvent(
|
|
884
799
|
new CustomEvent("model-loaded", {
|
|
885
800
|
detail: { success: "" },
|
|
@@ -887,10 +802,11 @@ class PrefViewer extends HTMLElement {
|
|
|
887
802
|
composed: true,
|
|
888
803
|
})
|
|
889
804
|
);
|
|
890
|
-
this.#
|
|
805
|
+
this.#log.groupEnd();
|
|
891
806
|
})
|
|
892
807
|
.catch((error) => {
|
|
893
|
-
|
|
808
|
+
console.error("PrefViewer: failed to load model", error);
|
|
809
|
+
this.#log.error("failed to load model", error);
|
|
894
810
|
this.dispatchEvent(
|
|
895
811
|
new CustomEvent("model-error", {
|
|
896
812
|
detail: { error: error },
|
|
@@ -898,22 +814,17 @@ class PrefViewer extends HTMLElement {
|
|
|
898
814
|
composed: true,
|
|
899
815
|
})
|
|
900
816
|
);
|
|
817
|
+
this.#log.groupEnd();
|
|
901
818
|
});
|
|
902
819
|
}
|
|
903
820
|
|
|
904
821
|
// Public Methods
|
|
905
822
|
loadConfig(config) {
|
|
906
|
-
this.#
|
|
907
|
-
|
|
908
|
-
try {
|
|
909
|
-
config = JSON.parse(config);
|
|
910
|
-
} catch (error) {
|
|
911
|
-
this.#logError("Failed to parse config JSON", { error });
|
|
912
|
-
throw error;
|
|
913
|
-
}
|
|
914
|
-
}
|
|
823
|
+
this.#log.group("loadConfig()");
|
|
824
|
+
config = typeof config === "string" ? JSON.parse(config) : config;
|
|
915
825
|
if (!config) {
|
|
916
|
-
this.#
|
|
826
|
+
this.#log.warn("no config");
|
|
827
|
+
this.#log.groupEnd();
|
|
917
828
|
return false;
|
|
918
829
|
}
|
|
919
830
|
|
|
@@ -930,145 +841,116 @@ class PrefViewer extends HTMLElement {
|
|
|
930
841
|
this.#checkMaterialsChanged(config.options);
|
|
931
842
|
}
|
|
932
843
|
|
|
933
|
-
this.#
|
|
934
|
-
modelStorage: this.#describeStorage(this.#data.containers.model.storage),
|
|
935
|
-
environmentStorage: this.#describeStorage(this.#data.containers.environment.storage),
|
|
936
|
-
});
|
|
937
|
-
|
|
844
|
+
this.#log.debug("config applied", { containers: this.#data.containers, options: this.#data.options });
|
|
938
845
|
this.#initialized && this.#loadContainers(true, true, true);
|
|
846
|
+
this.#log.groupEnd();
|
|
939
847
|
}
|
|
940
848
|
|
|
941
849
|
setOptions(options) {
|
|
942
|
-
this.#
|
|
850
|
+
this.#log.group("setOptions()");
|
|
943
851
|
if (!options) {
|
|
944
|
-
this.#
|
|
852
|
+
this.#log.warn("no options");
|
|
853
|
+
this.#log.groupEnd();
|
|
945
854
|
return false;
|
|
946
855
|
}
|
|
947
856
|
let someSetted = false;
|
|
948
857
|
if (this.#checkCameraChanged(options)) {
|
|
949
|
-
this.#logDebug("Camera options changed via setOptions");
|
|
950
858
|
someSetted = someSetted || this.#setOptionsCamera();
|
|
951
859
|
}
|
|
952
860
|
if (this.#checkMaterialsChanged(options)) {
|
|
953
|
-
this.#logDebug("Material options changed via setOptions");
|
|
954
861
|
someSetted = someSetted || this.#setOptionsMaterials();
|
|
955
862
|
}
|
|
956
863
|
this.#resetChangedFlags();
|
|
957
|
-
this.#logDebug("setOptions completed", { appliedAny: someSetted });
|
|
958
864
|
debugger;
|
|
865
|
+
this.#log.debug("setOptions result", { someSetted });
|
|
866
|
+
this.#log.groupEnd();
|
|
959
867
|
return someSetted;
|
|
960
868
|
}
|
|
961
869
|
|
|
962
870
|
loadModel(model) {
|
|
963
|
-
this.#
|
|
964
|
-
|
|
965
|
-
try {
|
|
966
|
-
model = JSON.parse(model);
|
|
967
|
-
} catch (error) {
|
|
968
|
-
this.#logError("Failed to parse model JSON", { error });
|
|
969
|
-
throw error;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
871
|
+
this.#log.group("loadModel()");
|
|
872
|
+
model = typeof model === "string" ? JSON.parse(model) : model;
|
|
972
873
|
if (!model) {
|
|
973
|
-
this.#
|
|
874
|
+
this.#log.warn("no model");
|
|
875
|
+
this.#log.groupEnd();
|
|
974
876
|
return false;
|
|
975
877
|
}
|
|
976
878
|
this.#data.containers.model.storage = model.storage || null;
|
|
977
879
|
this.#data.containers.model.show = model.visible !== undefined ? model.visible : this.#data.containers.model.show;
|
|
978
|
-
this.#logDebug("Model configuration updated", {
|
|
979
|
-
storage: this.#describeStorage(this.#data.containers.model.storage),
|
|
980
|
-
show: this.#data.containers.model.show,
|
|
981
|
-
});
|
|
982
880
|
this.#initialized && this.#loadContainers(true, false, false);
|
|
881
|
+
this.#log.groupEnd();
|
|
983
882
|
}
|
|
984
883
|
|
|
985
884
|
loadScene(scene) {
|
|
986
|
-
this.#
|
|
987
|
-
|
|
988
|
-
try {
|
|
989
|
-
scene = JSON.parse(scene);
|
|
990
|
-
} catch (error) {
|
|
991
|
-
this.#logError("Failed to parse scene JSON", { error });
|
|
992
|
-
throw error;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
885
|
+
this.#log.group("loadScene()");
|
|
886
|
+
scene = typeof scene === "string" ? JSON.parse(scene) : scene;
|
|
995
887
|
if (!scene) {
|
|
996
|
-
this.#
|
|
888
|
+
this.#log.warn("no scene");
|
|
889
|
+
this.#log.groupEnd();
|
|
997
890
|
return false;
|
|
998
891
|
}
|
|
999
892
|
this.#data.containers.environment.storage = scene.storage || null;
|
|
1000
893
|
this.#data.containers.environment.show = scene.visible !== undefined ? scene.visible : this.#data.containers.environment.show;
|
|
1001
|
-
this.#logDebug("Scene configuration updated", {
|
|
1002
|
-
storage: this.#describeStorage(this.#data.containers.environment.storage),
|
|
1003
|
-
show: this.#data.containers.environment.show,
|
|
1004
|
-
});
|
|
1005
894
|
this.#initialized && this.#loadContainers(false, true, false);
|
|
895
|
+
this.#log.groupEnd();
|
|
1006
896
|
}
|
|
1007
897
|
|
|
1008
898
|
showModel() {
|
|
899
|
+
this.#log.info("showModel()");
|
|
1009
900
|
this.#data.containers.model.show = true;
|
|
1010
901
|
this.#addContainer(this.#data.containers.model);
|
|
1011
|
-
this.#logInfo("Model visibility set to true");
|
|
1012
902
|
}
|
|
1013
903
|
|
|
1014
904
|
hideModel() {
|
|
905
|
+
this.#log.info("hideModel()");
|
|
1015
906
|
this.#data.containers.model.show = false;
|
|
1016
907
|
this.#removeContainer(this.#data.containers.model);
|
|
1017
|
-
this.#logInfo("Model visibility set to false");
|
|
1018
908
|
}
|
|
1019
909
|
|
|
1020
910
|
showScene() {
|
|
911
|
+
this.#log.info("showScene()");
|
|
1021
912
|
this.#data.containers.environment.show = true;
|
|
1022
913
|
this.#addContainer(this.#data.containers.environment);
|
|
1023
914
|
this.#setVisibilityOfWallAndFloorInModel();
|
|
1024
|
-
this.#logInfo("Scene visibility set to true");
|
|
1025
915
|
}
|
|
1026
916
|
|
|
1027
917
|
hideScene() {
|
|
918
|
+
this.#log.info("hideScene()");
|
|
1028
919
|
this.#data.containers.environment.show = false;
|
|
1029
920
|
this.#removeContainer(this.#data.containers.environment);
|
|
1030
921
|
this.#setVisibilityOfWallAndFloorInModel();
|
|
1031
|
-
this.#logInfo("Scene visibility set to false");
|
|
1032
922
|
}
|
|
1033
923
|
|
|
1034
924
|
downloadModelGLB() {
|
|
925
|
+
this.#log.info("downloadModelGLB()");
|
|
1035
926
|
const fileName = "model";
|
|
1036
|
-
this.#
|
|
1037
|
-
GLTF2Export.GLBAsync(this.#data.containers.model.assetContainer, fileName, { exportWithoutWaitingForScene: true }).then((glb) => {
|
|
1038
|
-
this.#logDebug("Model GLB export ready", { fileName });
|
|
1039
|
-
glb.downloadFiles();
|
|
1040
|
-
});
|
|
927
|
+
GLTF2Export.GLBAsync(this.#data.containers.model.assetContainer, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
|
|
1041
928
|
}
|
|
1042
929
|
|
|
1043
930
|
downloadModelUSDZ() {
|
|
931
|
+
this.#log.info("downloadModelUSDZ()");
|
|
1044
932
|
const fileName = "model";
|
|
1045
|
-
this.#logInfo("Initiating USDZ download for model", { fileName });
|
|
1046
933
|
USDZExportAsync(this.#data.containers.model.assetContainer).then((response) => {
|
|
1047
934
|
if (response) {
|
|
1048
935
|
Tools.Download(new Blob([response], { type: "model/vnd.usdz+zip" }), `${fileName}.usdz`);
|
|
1049
|
-
this.#logDebug("Model USDZ export ready", { fileName });
|
|
1050
936
|
}
|
|
1051
937
|
});
|
|
1052
938
|
}
|
|
1053
939
|
|
|
1054
940
|
downloadModelAndSceneUSDZ() {
|
|
941
|
+
this.#log.info("downloadModelAndSceneUSDZ()");
|
|
1055
942
|
const fileName = "scene";
|
|
1056
|
-
this.#logInfo("Initiating USDZ download for scene", { fileName });
|
|
1057
943
|
USDZExportAsync(this.#scene).then((response) => {
|
|
1058
944
|
if (response) {
|
|
1059
945
|
Tools.Download(new Blob([response], { type: "model/vnd.usdz+zip" }), `${fileName}.usdz`);
|
|
1060
|
-
this.#logDebug("Scene USDZ export ready", { fileName });
|
|
1061
946
|
}
|
|
1062
947
|
});
|
|
1063
948
|
}
|
|
1064
949
|
|
|
1065
950
|
downloadModelAndSceneGLB() {
|
|
951
|
+
this.#log.info("downloadModelAndSceneGLB()");
|
|
1066
952
|
const fileName = "scene";
|
|
1067
|
-
this.#
|
|
1068
|
-
GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) => {
|
|
1069
|
-
this.#logDebug("Scene GLB export ready", { fileName });
|
|
1070
|
-
glb.downloadFiles();
|
|
1071
|
-
});
|
|
953
|
+
GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
|
|
1072
954
|
}
|
|
1073
955
|
}
|
|
1074
956
|
|