@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +203 -321
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preference-sl/pref-viewer",
3
- "version": "2.10.0-beta.10",
3
+ "version": "2.10.0-beta.12",
4
4
  "description": "Web Component to preview GLTF models with Babylon.js",
5
5
  "author": "Alex Moreno Palacio <amoreno@preference.es>",
6
6
  "scripts": {
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
- #logLevel = PrefViewer.DEFAULT_LOG_LEVEL;
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
- if (name === "log-level") {
217
- this.#setLogLevel(value);
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.#logDebug("Attribute change detected", { name, value: this.#summarizeValue(value) });
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
- this.#logError("Missing required config attribute", { error });
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.#logInfo("Initialization completed", { initialized: this.#initialized });
285
+ this.#log.info("initialized = true");
273
286
  }
274
287
 
275
288
  disconnectedCallback() {
276
- this.#logDebug("Disconnected from DOM, disposing resources");
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.#logDebug("Creating rendering canvas");
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.#logDebug("Wrapping canvas inside container div");
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
- this.#data.options.camera.changed = options.camera && options.camera !== this.#data.options.camera.value ? true : false;
314
- this.#data.options.camera.value = this.#data.options.camera.changed ? options.camera : this.#data.options.camera.value;
315
- this.#logDebug("Camera option processed", { changed: this.#data.options.camera.changed, value: this.#data.options.camera.value });
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
- this.#data.options.materials[material].changed = options[key] && options[key] !== this.#data.options.materials[material].value ? true : false;
328
- this.#data.options.materials[material].value = this.#data.options.materials[material].changed ? options[key] : this.#data.options.materials[material].value;
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.#logDebug("Material options processed", {
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.#logInfo("Initializing Babylon engine and scene");
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.#logInfo("Babylon initialization finished", { xrEnabled: !!this.#XRExperience });
374
+ this.#log.timeEnd("babylon:init");
375
+ this.#log.groupEnd();
370
376
  }
371
377
 
372
378
  addStylesToARButton() {
373
- this.#logDebug("Adding styles to AR button");
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
- this.#logInfo("WebXR session mode not supported", { sessionMode });
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
- this.#logWarn("Failed to create XR experience", { error });
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
- if (this.#engine) {
437
- this.#logDebug("Resize observer triggered");
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.#logDebug("Creating default camera");
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.#logDebug("Creating scene lights");
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.#logDebug("Setting up canvas interaction listeners");
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
- this.#logDebug("Dispose engine called but engine already null");
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.#logDebug("Requesting server file header", { uri });
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.#logDebug("Received server file header", { uri, size, timestamp });
513
+ this.#log.debug("HEAD ok", { size, timestamp });
536
514
  resolve(size, timestamp);
537
515
  } else {
538
- this.#logWarn("Failed to retrieve server file header", { uri, status: xhr.status });
516
+ this.#log.warn("HEAD failed", { status: xhr.status });
539
517
  resolve(0, null);
540
518
  }
541
519
  };
542
520
  xhr.onerror = () => {
543
- this.#logError("Error requesting server file header", { uri });
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
- const transformed = url.replace(/^blob:[^/]+\//i, "").replace(/\\/g, "/");
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.#logDebug("Decoding Base64 payload", { length: base64 ? base64.length : 0 });
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.#logWarn("Failed to decode Base64 string");
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.#logDebug("Initializing storage access", { db, table });
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.#logWarn("Requested material not found", { value: optionMaterial.value });
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
- if (!this.#data.containers.materials.assetContainer) {
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.#logWarn("Requested camera not found", { name: this.#data.options.camera.value });
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.#logDebug("Replacing container asset", { name: container.name });
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
- const storage = container?.storage;
753
- this.#logDebug("Requested asset container load", {
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.#logWarn("No storage configuration provided for container", { container: container?.name });
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.#logDebug("IndexedDB model unchanged; skipping reload", { container: container.name });
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.#logWarn("No source resolved for container", { container: container.name });
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.#logDebug("Source detected as Base64", { container: container.name, extension });
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.#logDebug("Base64 model unchanged; skipping reload", { container: container.name, size });
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.#logDebug("Remote model unchanged; skipping reload", {
812
- container: container.name,
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.#logInfo("Loading asset container", { container: container.name, extension });
834
- return LoadAssetContainerAsync(file || source, this.#scene, options);
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.#logInfo("Starting container load", { loadModel, loadEnvironment, loadMaterials });
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
- this.#logInfo("Materials container loaded successfully");
872
- } else {
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.#logInfo("Containers load routine completed");
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.#logDebug("Dispatched model-loaded event");
805
+ this.#log.groupEnd();
891
806
  })
892
807
  .catch((error) => {
893
- this.#logError("Failed to load containers", { error });
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.#logInfo("loadConfig called", { initialized: this.#initialized, inputType: typeof config });
907
- if (typeof config === "string") {
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.#logWarn("No config provided");
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.#logDebug("Config applied", {
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.#logInfo("setOptions called", { optionsProvided: !!options });
850
+ this.#log.group("setOptions()");
943
851
  if (!options) {
944
- this.#logWarn("setOptions called without options");
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.#logInfo("loadModel called", { initialized: this.#initialized, inputType: typeof model });
964
- if (typeof model === "string") {
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.#logWarn("No model payload provided");
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.#logInfo("loadScene called", { initialized: this.#initialized, inputType: typeof scene });
987
- if (typeof scene === "string") {
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.#logWarn("No scene payload provided");
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.#logInfo("Initiating GLB download for model", { fileName });
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.#logInfo("Initiating GLB download for scene", { fileName });
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