@preference-sl/pref-viewer 2.10.0-beta.21 → 2.10.0-beta.23

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 +252 -260
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preference-sl/pref-viewer",
3
- "version": "2.10.0-beta.21",
3
+ "version": "2.10.0-beta.23",
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
@@ -39,23 +39,7 @@
39
39
  * </pref-viewer>
40
40
  * ```
41
41
  */
42
- import {
43
- Engine,
44
- Scene,
45
- ArcRotateCamera,
46
- Vector3,
47
- Color4,
48
- HemisphericLight,
49
- DirectionalLight,
50
- PointLight,
51
- ShadowGenerator,
52
- LoadAssetContainerAsync,
53
- Tools,
54
- WebXRSessionManager,
55
- WebXRDefaultExperience,
56
- MeshBuilder,
57
- WebXRFeatureName,
58
- } from "@babylonjs/core";
42
+ import { Engine, Scene, ArcRotateCamera, Vector3, Color4, HemisphericLight, DirectionalLight, PointLight, ShadowGenerator, LoadAssetContainerAsync, Tools, WebXRSessionManager, WebXRDefaultExperience, MeshBuilder, WebXRFeatureName } from "@babylonjs/core";
59
43
  import "@babylonjs/loaders";
60
44
  import { USDZExportAsync, GLTF2Export } from "@babylonjs/serializers";
61
45
  import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression";
@@ -145,45 +129,15 @@ class PrefViewer extends HTMLElement {
145
129
  #shadowGen = null;
146
130
  #XRExperience = null;
147
131
 
148
- // -------------------------
149
- // Logger util + timers
150
- // -------------------------
151
- #LOG_LEVEL = (window?.PREFV_LOG_LEVEL ?? "debug"); // 'debug' | 'info' | 'warn' | 'error'
152
- #timeMarks = new Map();
153
-
154
- #log(level, msg, extra) {
155
- const order = { debug: 0, info: 1, warn: 2, error: 3 };
156
- const cur = order[this.#LOG_LEVEL] ?? 0;
157
- const now = new Date().toISOString();
158
- if ((order[level] ?? 0) < cur) return;
159
- const line = `[PrefViewer][${level.toUpperCase()}][${now}] ${msg}`;
160
- try {
161
- if (extra !== undefined) console[level](line, extra);
162
- else console[level](line);
163
- } catch {
164
- console.log(line, extra);
165
- }
166
- }
167
- #timeStart(label) {
168
- this.#timeMarks.set(label, performance.now());
169
- this.#log("debug", `⏱️ start ${label}`);
170
- }
171
- #timeEnd(label) {
172
- const t0 = this.#timeMarks.get(label);
173
- if (t0 !== undefined) {
174
- const dt = (performance.now() - t0).toFixed(1);
175
- this.#log("debug", `⏱️ end ${label} +${dt}ms`);
176
- this.#timeMarks.delete(label);
177
- }
178
- }
179
-
180
132
  constructor() {
181
133
  super();
134
+ console.log("PrefViewer: constructor()");
182
135
  this.attachShadow({ mode: "open" });
183
136
  this.#createCanvas();
184
137
  this.#wrapCanvas();
185
138
  // Point to whichever version you packaged or want to use:
186
139
  const DRACO_BASE = "https://www.gstatic.com/draco/versioned/decoders/1.5.7";
140
+ console.log("PrefViewer: DRACO config base =", DRACO_BASE);
187
141
  DracoCompression.Configuration.decoder = {
188
142
  // loader for the “wrapper” that pulls in the real WASM
189
143
  wasmUrl: `${DRACO_BASE}/draco_wasm_wrapper_gltf.js`,
@@ -199,7 +153,7 @@ class PrefViewer extends HTMLElement {
199
153
  }
200
154
 
201
155
  attributeChangedCallback(name, _old, value) {
202
- this.#log("debug", `attributeChangedCallback: ${name}`, value);
156
+ console.log("PrefViewer: attributeChangedCallback()", { name, old: _old, value });
203
157
  let data = null;
204
158
  switch (name) {
205
159
  case "config":
@@ -214,6 +168,7 @@ class PrefViewer extends HTMLElement {
214
168
  case "show-model":
215
169
  data = value.toLowerCase?.() === "true";
216
170
  if (this.initialized) {
171
+ console.log("PrefViewer: toggling model visibility (attr)", data);
217
172
  data ? this.showModel() : this.hideModel();
218
173
  } else {
219
174
  this.#data.containers.model.show = data;
@@ -222,6 +177,7 @@ class PrefViewer extends HTMLElement {
222
177
  case "show-scene":
223
178
  data = value.toLowerCase?.() === "true";
224
179
  if (this.initialized) {
180
+ console.log("PrefViewer: toggling scene visibility (attr)", data);
225
181
  data ? this.showScene() : this.hideScene();
226
182
  } else {
227
183
  this.#data.containers.environment.show = data;
@@ -231,10 +187,9 @@ class PrefViewer extends HTMLElement {
231
187
  }
232
188
 
233
189
  connectedCallback() {
234
- this.#log("info", "connectedCallback");
190
+ console.log("PrefViewer: connectedCallback()");
235
191
  if (!this.hasAttribute("config")) {
236
192
  const error = 'PrefViewer: provide "models" as array of model and environment';
237
- this.#log("error", error);
238
193
  console.error(error);
239
194
  this.dispatchEvent(
240
195
  new CustomEvent("scene-error", {
@@ -249,17 +204,19 @@ class PrefViewer extends HTMLElement {
249
204
 
250
205
  this.#initializeBabylon();
251
206
  this.initialized = true;
207
+ console.log("PrefViewer: initialized = true, loading containers…");
252
208
  this.#loadContainers(true, true, true);
253
209
  }
254
210
 
255
211
  disconnectedCallback() {
256
- this.#log("info", "disconnectedCallback → dispose engine");
212
+ console.log("PrefViewer: disconnectedCallback()");
257
213
  this.#disposeEngine();
258
214
  this.#canvasResizeObserver.disconnect();
259
215
  }
260
216
 
261
217
  // Web Component
262
218
  #createCanvas() {
219
+ console.log("PrefViewer: #createCanvas()");
263
220
  this.#canvas = document.createElement("canvas");
264
221
  Object.assign(this.#canvas.style, {
265
222
  width: "100%",
@@ -270,6 +227,7 @@ class PrefViewer extends HTMLElement {
270
227
  }
271
228
 
272
229
  #wrapCanvas() {
230
+ console.log("PrefViewer: #wrapCanvas()");
273
231
  this.#wrapper = document.createElement("div");
274
232
  Object.assign(this.#wrapper.style, {
275
233
  width: "100%",
@@ -281,13 +239,13 @@ class PrefViewer extends HTMLElement {
281
239
  }
282
240
 
283
241
  #setStatusSceneLoading() {
242
+ console.log("PrefViewer: #setStatusSceneLoading()");
284
243
  this.loaded = false;
285
244
  this.loading = true;
286
245
  if (this.hasAttribute("loaded")) {
287
246
  this.removeAttribute("loaded");
288
247
  }
289
248
  this.setAttribute("loading", "");
290
- this.#log("info", "Escena → loading");
291
249
  this.dispatchEvent(
292
250
  new CustomEvent("scene-loading", {
293
251
  bubbles: true,
@@ -298,6 +256,7 @@ class PrefViewer extends HTMLElement {
298
256
  }
299
257
 
300
258
  #setStatusSceneLoaded() {
259
+ console.log("PrefViewer: #setStatusSceneLoaded()");
301
260
  this.loaded = true;
302
261
  this.loading = false;
303
262
 
@@ -331,7 +290,6 @@ class PrefViewer extends HTMLElement {
331
290
  this.removeAttribute("loading");
332
291
  }
333
292
  this.setAttribute("loaded", "");
334
- this.#log("info", "Escena → loaded", detail);
335
293
  this.dispatchEvent(
336
294
  new CustomEvent("scene-loaded", {
337
295
  bubbles: true,
@@ -340,10 +298,11 @@ class PrefViewer extends HTMLElement {
340
298
  detail: detail,
341
299
  })
342
300
  );
301
+ console.log("PrefViewer: scene-loaded detail =", detail);
343
302
  }
344
303
 
345
304
  #setStatusOptionsLoading() {
346
- this.#log("info", "Opciones → loading");
305
+ console.log("PrefViewer: #setStatusOptionsLoading()");
347
306
  this.dispatchEvent(
348
307
  new CustomEvent("options-loading", {
349
308
  bubbles: true,
@@ -354,6 +313,7 @@ class PrefViewer extends HTMLElement {
354
313
  }
355
314
 
356
315
  #setStatusOptionsLoaded() {
316
+ console.log("PrefViewer: #setStatusOptionsLoaded()");
357
317
  const toLoadDetail = {
358
318
  innerWallMaterial: !!this.#data.options.materials.innerWall.changed,
359
319
  outerWallMaterial: !!this.#data.options.materials.outerWall.changed,
@@ -372,7 +332,6 @@ class PrefViewer extends HTMLElement {
372
332
  success: loadedDetail,
373
333
  };
374
334
 
375
- this.#log("info", "Opciones → loaded", detail);
376
335
  this.dispatchEvent(
377
336
  new CustomEvent("options-loaded", {
378
337
  bubbles: true,
@@ -381,6 +340,7 @@ class PrefViewer extends HTMLElement {
381
340
  detail: detail,
382
341
  })
383
342
  );
343
+ console.log("PrefViewer: options-loaded detail =", detail);
384
344
  }
385
345
 
386
346
  // Data
@@ -389,88 +349,91 @@ class PrefViewer extends HTMLElement {
389
349
  return false;
390
350
  }
391
351
  const changed = options.camera !== this.#data.options.camera.value;
392
- this.#data.options.camera.changed = changed
393
- ? { oldValue: this.#data.options.camera.value, oldLocked: this.#data.options.camera.locked, success: false }
394
- : false;
352
+ console.log("PrefViewer: #checkCameraChanged()", { incoming: options.camera, previous: this.#data.options.camera.value, changed });
353
+ this.#data.options.camera.changed = changed ? { oldValue: this.#data.options.camera.value, oldLocked: this.#data.options.camera.locked, success: false } : false;
395
354
  if (changed) this.#data.options.camera.value = options.camera;
396
- this.#log("debug", `#checkCameraChanged → ${changed}`, {
397
- new: this.#data.options.camera.value,
398
- old: this.#data.options.camera.changed?.oldValue ?? null,
399
- });
355
+
400
356
  return changed;
401
357
  }
402
358
 
403
359
  #checkMaterialsChanged(options) {
404
- if (!options) return false;
360
+ if (!options) {
361
+ return false;
362
+ }
405
363
  let someChanged = false;
406
364
  Object.keys(this.#data.options.materials).forEach((material) => {
407
365
  const key = `${material}Material`;
408
366
  const materialChanged = options[key] && options[key] !== this.#data.options.materials[material].value ? true : false;
409
- this.#data.options.materials[material].changed = materialChanged
410
- ? { oldValue: this.#data.options.materials[material].value, success: false }
411
- : false;
367
+ console.log("PrefViewer: #checkMaterialsChanged()", { key, incoming: options[key], previous: this.#data.options.materials[material].value, materialChanged });
368
+ this.#data.options.materials[material].changed = materialChanged ? { oldValue: this.#data.options.materials[material].value, success: false } : false;
412
369
  this.#data.options.materials[material].value = materialChanged ? options[key] : this.#data.options.materials[material].value;
413
370
  someChanged = someChanged || this.#data.options.materials[material].changed;
414
371
  });
415
- this.#log("debug", `#checkMaterialsChanged → ${!!someChanged}`);
416
372
  return someChanged;
417
373
  }
418
374
 
419
375
  #storeChangedFlagsForContainer(container) {
376
+ console.log("PrefViewer: #storeChangedFlagsForContainer()", { name: container.name, changed: container.changed });
420
377
  container.timeStamp = container.changed.timeStamp;
421
378
  container.size = container.changed.size;
422
379
  container.changed.success = true;
423
380
  }
424
381
 
425
382
  #resetChangedFlags() {
426
- Object.values(this.#data.containers).forEach((container) => (container.changed = false));
427
- Object.values(this.#data.options.materials).forEach((material) => (material.changed = false));
428
- this.#data.options.camera.changed = false;
383
+ console.log("PrefViewer: #resetChangedFlags()");
384
+ Object.values(this.#data.containers).forEach((container) => (container.changed.success = false));
385
+ Object.values(this.#data.options.materials).forEach((material) => (material.changed.success = false));
386
+ this.#data.options.camera.changed.success = false;
429
387
  }
430
388
 
431
389
  // Babylon.js
432
390
  async #initializeBabylon() {
433
- this.#timeStart("initializeBabylon");
391
+ console.log("PrefViewer: #initializeBabylon() START");
434
392
  this.#engine = new Engine(this.#canvas, true, { alpha: true });
435
- this.#engine.disableUniformBuffers = true; // evita GL_MAX_*_UNIFORM_BUFFERS (workaround)
393
+ this.#engine.disableUniformBuffers = true; // <- evita el límite de GL_MAX_*_UNIFORM_BUFFERS // PROVISIONAL, YA QUE ESTO ES UN POCO OVERKILL
394
+ console.log("PrefViewer: Engine created", { disableUBO: this.#engine.disableUniformBuffers });
395
+
436
396
  this.#scene = new Scene(this.#engine);
437
397
  this.#scene.clearColor = new Color4(1, 1, 1, 1);
398
+ console.log("PrefViewer: Scene created, clearColor set to white");
399
+
438
400
  this.#createCamera();
439
- this.#createLights(); // luces desactivadas
401
+ this.#createLights();
440
402
  this.#setupInteraction();
441
403
 
442
- this.#log("info", "Engine y Scene creados", {
443
- webgl: this.#engine.webGLVersion,
444
- disableUBO: this.#engine.disableUniformBuffers,
445
- });
446
-
447
404
  this.#engine.runRenderLoop(() => this.#scene && this.#scene.render());
405
+ console.log("PrefViewer: runRenderLoop started");
448
406
  this.#canvasResizeObserver.observe(this.#canvas);
407
+ console.log("PrefViewer: ResizeObserver attached");
449
408
 
450
409
  await this.#createXRExperience();
451
- this.#timeEnd("initializeBabylon");
410
+ console.log("PrefViewer: #initializeBabylon() END");
452
411
  }
453
412
 
454
413
  addStylesToARButton() {
455
- const css =
456
- '.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"}';
414
+ console.log("PrefViewer: addStylesToARButton()");
415
+ const css = '.babylonVRicon { color: #868686; border-color: #868686; border-style: solid; margin-left: 10px; height: 50px; width: 80px; background-color: rgba(51,51,51,0.7); background-image: url(data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%222048%22%20height%3D%221152%22%20viewBox%3D%220%200%202048%201152%22%20version%3D%221.1%22%3E%3Cpath%20transform%3D%22rotate%28180%201024%2C576.0000000000001%29%22%20d%3D%22m1109%2C896q17%2C0%2030%2C-12t13%2C-30t-12.5%2C-30.5t-30.5%2C-12.5l-170%2C0q-18%2C0%20-30.5%2C12.5t-12.5%2C30.5t13%2C30t30%2C12l170%2C0zm-85%2C256q59%2C0%20132.5%2C-1.5t154.5%2C-5.5t164.5%2C-11.5t163%2C-20t150%2C-30t124.5%2C-41.5q23%2C-11%2042%2C-24t38%2C-30q27%2C-25%2041%2C-61.5t14%2C-72.5l0%2C-257q0%2C-123%20-47%2C-232t-128%2C-190t-190%2C-128t-232%2C-47l-81%2C0q-37%2C0%20-68.5%2C14t-60.5%2C34.5t-55.5%2C45t-53%2C45t-53%2C34.5t-55.5%2C14t-55.5%2C-14t-53%2C-34.5t-53%2C-45t-55.5%2C-45t-60.5%2C-34.5t-68.5%2C-14l-81%2C0q-123%2C0%20-232%2C47t-190%2C128t-128%2C190t-47%2C232l0%2C257q0%2C68%2038%2C115t97%2C73q54%2C24%20124.5%2C41.5t150%2C30t163%2C20t164.5%2C11.5t154.5%2C5.5t132.5%2C1.5zm939%2C-298q0%2C39%20-24.5%2C67t-58.5%2C42q-54%2C23%20-122%2C39.5t-143.5%2C28t-155.5%2C19t-157%2C11t-148.5%2C5t-129.5%2C1.5q-59%2C0%20-130%2C-1.5t-148%2C-5t-157%2C-11t-155.5%2C-19t-143.5%2C-28t-122%2C-39.5q-34%2C-14%20-58.5%2C-42t-24.5%2C-67l0%2C-257q0%2C-106%2040.5%2C-199t110%2C-162.5t162.5%2C-109.5t199%2C-40l81%2C0q27%2C0%2052%2C14t50%2C34.5t51%2C44.5t55.5%2C44.5t63.5%2C34.5t74%2C14t74%2C-14t63.5%2C-34.5t55.5%2C-44.5t51%2C-44.5t50%2C-34.5t52%2C-14l14%2C0q37%2C0%2070%2C0.5t64.5%2C4.5t63.5%2C12t68%2C23q71%2C30%20128.5%2C78.5t98.5%2C110t63.5%2C133.5t22.5%2C149l0%2C257z%22%20fill%3D%22white%22%20/%3E%3C/svg%3E%0A); background-size: 80%; background-repeat:no-repeat; background-position: center; border: none; outline: none; transition: transform 0.125s ease-out } .babylonVRicon:hover { transform: scale(1.05) } .babylonVRicon:active {background-color: rgba(51,51,51,1) } .babylonVRicon:focus {background-color: rgba(51,51,51,1) }.babylonVRicon.vrdisplaypresenting { background-image: none;} .vrdisplaypresenting::after { content: "EXIT"} .xr-error::after { content: "ERROR"}';
457
416
  const style = document.createElement("style");
458
417
  style.appendChild(document.createTextNode(css));
459
418
  this.#wrapper.appendChild(style);
460
419
  }
461
420
 
462
421
  async #createXRExperience() {
463
- if (this.#XRExperience) return true;
422
+ console.log("PrefViewer: #createXRExperience() START");
423
+ if (this.#XRExperience) {
424
+ console.log("PrefViewer: XR already exists, skipping.");
425
+ return true;
426
+ }
464
427
 
465
428
  const sessionMode = "immersive-ar";
466
429
  const sessionSupported = await WebXRSessionManager.IsSessionSupportedAsync(sessionMode);
430
+ console.log("PrefViewer: WebXR session supported =", sessionSupported);
467
431
  if (!sessionSupported) {
468
- this.#log("info", `WebXR no soportado para ${sessionMode}`);
432
+ console.info("PrefViewer: WebXR in mode AR is not supported");
469
433
  return false;
470
434
  }
471
435
 
472
436
  try {
473
- this.#log("debug", "Creando XR DefaultExperience…");
474
437
  const ground = MeshBuilder.CreateGround("ground", { width: 1000, height: 1000 }, this.#scene);
475
438
  ground.isVisible = false;
476
439
 
@@ -485,7 +448,7 @@ class PrefViewer extends HTMLElement {
485
448
  };
486
449
 
487
450
  this.#XRExperience = await WebXRDefaultExperience.CreateAsync(this.#scene, options);
488
- this.#log("info", "XR DefaultExperience creado");
451
+ console.log("PrefViewer: XR experience created");
489
452
 
490
453
  const featuresManager = this.#XRExperience.baseExperience.featuresManager;
491
454
  featuresManager.enableFeature(WebXRFeatureName.TELEPORTATION, "stable", {
@@ -495,75 +458,98 @@ class PrefViewer extends HTMLElement {
495
458
  });
496
459
 
497
460
  this.#XRExperience.baseExperience.sessionManager.onXRReady.add(() => {
461
+ console.log("PrefViewer: onXRReady - syncing camera pose");
498
462
  // Set the initial position of xrCamera: use nonVRCamera, which contains a copy of the original this.#scene.activeCamera before entering XR
499
- this.#log("debug", "XR Ready → copiando pose inicial a xrCamera");
500
- this.#XRExperience.baseExperience.camera.setTransformationFromNonVRCamera(
501
- this.#XRExperience.baseExperience._nonVRCamera
502
- );
463
+ this.#XRExperience.baseExperience.camera.setTransformationFromNonVRCamera(this.#XRExperience.baseExperience._nonVRCamera);
503
464
  this.#XRExperience.baseExperience.camera.setTarget(Vector3.Zero());
504
- this.#XRExperience.baseExperience.onInitialXRPoseSetObservable.notifyObservers(
505
- this.#XRExperience.baseExperience.camera
506
- );
465
+ this.#XRExperience.baseExperience.onInitialXRPoseSetObservable.notifyObservers(this.#XRExperience.baseExperience.camera);
507
466
  });
508
467
 
509
468
  this.addStylesToARButton();
510
469
  } catch (error) {
511
- this.#log("warn", "Falló la creación de la experiencia WebXR", error);
470
+ console.warn("PrefViewer: failed to create WebXR experience", error);
512
471
  this.#XRExperience = null;
513
472
  }
473
+ console.log("PrefViewer: #createXRExperience() END");
514
474
  }
515
475
 
516
476
  #canvasResizeObserver = new ResizeObserver(() => this.#engine && this.#engine.resize());
517
477
 
518
478
  #createCamera() {
479
+ console.log("PrefViewer: #createCamera()");
519
480
  this.#camera = new ArcRotateCamera("camera", (3 * Math.PI) / 2, Math.PI * 0.47, 10, Vector3.Zero(), this.#scene);
520
481
  this.#camera.upperBetaLimit = Math.PI * 0.48;
521
482
  this.#camera.lowerBetaLimit = Math.PI * 0.25;
522
483
  this.#camera.lowerRadiusLimit = 5;
523
484
  this.#camera.upperRadiusLimit = 20;
524
- this.#camera.metadata = { locked: false };
485
+ this.#camera.metadata = { locked: false }
525
486
  this.#camera.attachControl(this.#canvas, true);
526
487
  this.#scene.activeCamera = this.#camera;
527
- this.#log("debug", "Cámara creada y asignada como activa", {
528
- name: this.#camera.name,
529
- locked: this.#camera.metadata.locked,
488
+ console.log("PrefViewer: camera configured", {
489
+ upperBetaLimit: this.#camera.upperBetaLimit,
490
+ lowerBetaLimit: this.#camera.lowerBetaLimit,
491
+ lowerRadiusLimit: this.#camera.lowerRadiusLimit,
492
+ upperRadiusLimit: this.#camera.upperRadiusLimit
530
493
  });
531
494
  }
532
495
 
533
- // [LIGHTS OFF] — sin luces del componente
534
496
  #createLights() {
535
- this.#hemiLight = null;
536
- this.#dirLight = null;
537
- this.#cameraLight = null;
538
- this.#shadowGen = null;
539
- this.#log("info", "Luces internas desactivadas (#createLights). No se crean sombras.");
497
+ console.log("PrefViewer: #createLights()");
498
+ // 1) Stronger ambient fill
499
+ this.#hemiLight = new HemisphericLight("hemiLight", new Vector3(-10, 10, -10), this.#scene);
500
+ this.#hemiLight.intensity = 0.6;
501
+
502
+ // 2) Directional light from the front-right, angled slightly down
503
+ this.#dirLight = new DirectionalLight("dirLight", new Vector3(-10, 10, -10), this.#scene);
504
+ this.#dirLight.position = new Vector3(5, 4, 5); // light is IN FRONT + ABOVE + to the RIGHT
505
+ this.#dirLight.intensity = 0.6;
506
+
507
+ // 3) Soft shadows
508
+ this.#shadowGen = new ShadowGenerator(1024, this.#dirLight);
509
+ this.#shadowGen.useBlurExponentialShadowMap = true;
510
+ this.#shadowGen.blurKernel = 16;
511
+ this.#shadowGen.darkness = 0.5;
512
+
513
+ // 4) Camera‐attached headlight
514
+ this.#cameraLight = new PointLight("pl", this.#camera.position, this.#scene);
515
+ this.#cameraLight.parent = this.#camera;
516
+ this.#cameraLight.intensity = 0.3;
517
+
518
+ console.log("PrefViewer: lights created", {
519
+ hemiIntensity: this.#hemiLight.intensity,
520
+ dirIntensity: this.#dirLight.intensity,
521
+ shadowMapSize: 1024,
522
+ pointIntensity: this.#cameraLight.intensity
523
+ });
540
524
  }
541
525
 
542
526
  #setupInteraction() {
527
+ console.log("PrefViewer: #setupInteraction()");
543
528
  this.#canvas.addEventListener("wheel", (event) => {
544
- if (!this.#scene || !this.#camera) return false;
529
+ if (!this.#scene || !this.#camera) {
530
+ return false;
531
+ }
545
532
  if (!this.#scene.activeCamera.metadata?.locked) {
546
- this.#scene.activeCamera.inertialRadiusOffset -=
547
- event.deltaY * this.#scene.activeCamera.wheelPrecision * 0.001;
533
+ this.#scene.activeCamera.inertialRadiusOffset -= event.deltaY * this.#scene.activeCamera.wheelPrecision * 0.001;
548
534
  }
549
535
  event.preventDefault();
550
536
  });
551
537
  }
552
538
 
553
539
  #disposeEngine() {
540
+ console.log("PrefViewer: #disposeEngine()");
554
541
  if (!this.#engine) return;
555
542
  this.#shadowGen?.dispose();
556
- this.#scene?.lights?.slice().forEach((l) => l.dispose());
543
+ this.#scene?.lights?.slice().forEach(l => l.dispose());
557
544
  this.#engine.dispose();
558
545
  this.#engine = this.#scene = this.#camera = null;
559
546
  this.#hemiLight = this.#dirLight = this.#cameraLight = null;
560
547
  this.#shadowGen = null;
561
- this.#log("info", "Engine y recursos Babylon eliminados");
562
548
  }
563
549
 
564
550
  // Utility methods for loading gltf/glb
565
551
  async #getServerFileDataHeader(uri) {
566
- this.#log("debug", `HEAD ${uri}`);
552
+ console.log("PrefViewer: #getServerFileDataHeader()", uri);
567
553
  return new Promise((resolve) => {
568
554
  const xhr = new XMLHttpRequest();
569
555
  xhr.open("HEAD", uri, true);
@@ -572,15 +558,15 @@ class PrefViewer extends HTMLElement {
572
558
  if (xhr.status === 200) {
573
559
  const size = parseInt(xhr.getResponseHeader("Content-Length"));
574
560
  const timeStamp = new Date(xhr.getResponseHeader("Last-Modified")).toISOString();
575
- this.#log("debug", "HEAD ok", { size, timeStamp });
561
+ console.log("PrefViewer: HEAD ok", { size, timeStamp });
576
562
  resolve([size, timeStamp]);
577
563
  } else {
578
- this.#log("warn", "HEAD non-200", { status: xhr.status });
564
+ console.warn("PrefViewer: HEAD failed", xhr.status);
579
565
  resolve([0, null]);
580
566
  }
581
567
  };
582
568
  xhr.onerror = () => {
583
- this.#log("warn", "HEAD error");
569
+ console.warn("PrefViewer: HEAD network error");
584
570
  resolve([0, null]);
585
571
  };
586
572
  xhr.send();
@@ -589,12 +575,14 @@ class PrefViewer extends HTMLElement {
589
575
 
590
576
  #transformUrl(url) {
591
577
  return new Promise((resolve) => {
592
- resolve(url.replace(/^blob:[^/]+\//i, "").replace(/\\/g, "/"));
578
+ const transformed = url.replace(/^blob:[^/]+\//i, "").replace(/\\/g, "/");
579
+ console.log("PrefViewer: #transformUrl()", { in: url, out: transformed });
580
+ resolve(transformed);
593
581
  });
594
582
  }
595
583
 
596
584
  #decodeBase64(base64) {
597
- this.#log("debug", "Decodificando Base64…");
585
+ console.log("PrefViewer: #decodeBase64() START");
598
586
  const [, payload] = base64.split(",");
599
587
  const raw = payload || base64;
600
588
  let decoded = "";
@@ -604,73 +592,73 @@ class PrefViewer extends HTMLElement {
604
592
  try {
605
593
  decoded = atob(raw);
606
594
  } catch {
607
- this.#log("warn", "Base64 inválido (atob falló)");
595
+ console.warn("PrefViewer: base64 decode failed (not base64?)");
608
596
  return { blob, extension, size };
609
597
  }
610
598
  let isJson = false;
611
599
  try {
612
600
  JSON.parse(decoded);
613
601
  isJson = true;
614
- } catch {}
602
+ } catch { }
615
603
  extension = isJson ? ".gltf" : ".glb";
616
604
  const type = isJson ? "model/gltf+json" : "model/gltf-binary";
617
605
  const array = Uint8Array.from(decoded, (c) => c.charCodeAt(0));
618
606
  blob = new Blob([array], { type });
619
- this.#log("debug", "Base64 decodificado", { extension, size });
607
+ console.log("PrefViewer: #decodeBase64() END", { extension, size, type });
620
608
  return { blob, extension, size };
621
609
  }
622
610
 
623
611
  async #initStorage(db, table) {
612
+ console.log("PrefViewer: #initStorage()", { db, table });
624
613
  if (window.gltfDB && window.gltfDB.name === db && window.gltfDB.objectStoreNames.contains(table)) {
614
+ console.log("PrefViewer: existing DB connection reused");
625
615
  return true;
626
616
  }
627
617
  await initDb(db, table);
628
- this.#log("debug", "IndexedDB inicializado/abierto", { db, table });
618
+ console.log("PrefViewer: DB initialized");
629
619
  }
630
620
 
631
621
  // Methods for managing Asset Containers
632
622
  #setVisibilityOfWallAndFloorInModel(show) {
623
+ console.log("PrefViewer: #setVisibilityOfWallAndFloorInModel()", { incomingShow: show });
633
624
  if (!this.#data.containers.model.assetContainer || !this.#data.containers.model.visible) {
625
+ console.log("PrefViewer: no model assetContainer or not visible, skip visibility set");
634
626
  return false;
635
627
  }
636
628
  show = show !== undefined ? show : this.#data.containers.environment.visible;
637
629
  const prefixes = Object.values(this.#data.options.materials).map((material) => material.prefix);
638
- const meshes = this.#data.containers.model.assetContainer.meshes.filter((meshToFilter) =>
639
- prefixes.some((prefix) => meshToFilter.name.startsWith(prefix))
640
- );
641
- meshes.forEach((mesh) => mesh.setEnabled(show));
642
- this.#log("debug", "setVisibilityOfWallAndFloorInModel", { show, count: meshes.length });
630
+ console.log("PrefViewer: toggling meshes with prefixes", prefixes, "->", show);
631
+ this.#data.containers.model.assetContainer.meshes.filter((meshToFilter) => prefixes.some((prefix) => meshToFilter.name.startsWith(prefix))).forEach((mesh) => mesh.setEnabled(show));
643
632
  }
644
633
 
645
634
  #setOptionsMaterial(optionMaterial) {
635
+ console.log("PrefViewer: #setOptionsMaterial()", { optionMaterial });
646
636
  if (!optionMaterial || !optionMaterial.prefix || !optionMaterial.value) {
637
+ console.log("PrefViewer: optionMaterial incomplete, skipping");
647
638
  return false;
648
639
  }
649
640
 
650
- const material =
651
- this.#data.containers.materials.assetContainer?.materials.find((mat) => mat.name === optionMaterial.value) ||
652
- null;
641
+ const material = this.#data.containers.materials.assetContainer?.materials
642
+ .find((mat) => mat.name === optionMaterial.value) || null;
643
+
653
644
  if (!material) {
654
- this.#log("warn", "Material no encontrado en contenedor 'materials'", { name: optionMaterial.value });
645
+ console.warn("PrefViewer: material not found", optionMaterial.value);
655
646
  return false;
656
647
  }
657
648
 
649
+ const hadExplicitChange = !!optionMaterial.changed;
650
+
658
651
  const containers = [];
659
- if (
660
- this.#data.containers.model.assetContainer &&
661
- (this.#data.containers.model.changed || this.#data.containers.materials.changed || optionMaterial.changed)
662
- ) {
652
+ if (this.#data.containers.model.assetContainer &&
653
+ (this.#data.containers.model.changed || this.#data.containers.materials.changed || optionMaterial.changed)) {
663
654
  containers.push(this.#data.containers.model.assetContainer);
664
655
  }
665
- if (
666
- this.#data.containers.environment.assetContainer &&
667
- (this.#data.containers.environment.changed ||
668
- this.#data.containers.materials.changed ||
669
- optionMaterial.changed)
670
- ) {
656
+ if (this.#data.containers.environment.assetContainer &&
657
+ (this.#data.containers.environment.changed || this.#data.containers.materials.changed || optionMaterial.changed)) {
671
658
  containers.push(this.#data.containers.environment.assetContainer);
672
659
  }
673
660
  if (containers.length === 0) {
661
+ console.log("PrefViewer: no containers require material update");
674
662
  return false;
675
663
  }
676
664
 
@@ -684,120 +672,144 @@ class PrefViewer extends HTMLElement {
684
672
  })
685
673
  );
686
674
 
675
+ console.log("PrefViewer: material assignment result", {
676
+ prefix: optionMaterial.prefix, material: optionMaterial.value, someSetted
677
+ });
678
+
687
679
  if (someSetted) {
688
- optionMaterial.changed && (optionMaterial.changed.success = true);
689
- this.#log("debug", "Material aplicado", { prefix: optionMaterial.prefix, name: optionMaterial.value });
690
- } else {
691
- optionMaterial.value = optionMaterial.changed?.oldValue ?? optionMaterial.value;
680
+ // Asegura estructura si antes era `false`
681
+ if (!optionMaterial.changed) {
682
+ optionMaterial.changed = { oldValue: optionMaterial.value, success: false };
683
+ }
684
+ optionMaterial.changed.success = true;
685
+ } else if (hadExplicitChange) {
686
+ // Solo revertimos si el usuario pidió el cambio y falló
687
+ optionMaterial.value = optionMaterial.changed.oldValue;
692
688
  }
693
689
 
694
690
  return someSetted;
695
691
  }
696
692
 
697
693
  #setOptionsMaterials() {
694
+ console.log("PrefViewer: #setOptionsMaterials() START");
698
695
  let someSetted = false;
699
696
  Object.values(this.#data.options.materials).forEach((material) => {
700
697
  let settedMaterial = this.#setOptionsMaterial(material);
701
698
  someSetted = someSetted || settedMaterial;
702
699
  });
700
+ console.log("PrefViewer: #setOptionsMaterials() END", { someSetted });
703
701
  return someSetted;
704
702
  }
705
703
 
706
704
  #setOptionsCamera() {
707
- if (
708
- !this.#data.options.camera.value &&
709
- !this.#data.options.camera.changed &&
710
- !this.#data.containers.model.changed &&
711
- !this.#data.containers.environment.changed
712
- ) {
705
+ console.log("PrefViewer: #setOptionsCamera()", {
706
+ requested: this.#data.options.camera.value,
707
+ changed: this.#data.options.camera.changed,
708
+ modelChanged: this.#data.containers.model.changed,
709
+ envChanged: this.#data.containers.environment.changed
710
+ });
711
+
712
+ const camState = this.#data.options.camera;
713
+
714
+ if (!camState.value && !camState.changed && !this.#data.containers.model.changed && !this.#data.containers.environment.changed) {
713
715
  return false;
714
716
  }
715
717
 
718
+ const hadExplicitChange = !!camState.changed;
719
+
716
720
  let camera =
717
- this.#data.containers.model.assetContainer?.cameras.find(
718
- (thisCamera) => thisCamera.name === this.#data.options.camera.value
719
- ) ||
720
- this.#data.containers.environment.assetContainer?.cameras.find(
721
- (thisCamera) => thisCamera.name === this.#data.options.camera.value
722
- ) ||
721
+ this.#data.containers.model.assetContainer?.cameras.find(c => c.name === camState.value) ||
722
+ this.#data.containers.environment.assetContainer?.cameras.find(c => c.name === camState.value) ||
723
723
  null;
724
724
 
725
725
  if (!camera) {
726
- if (this.#data.options.camera.changed?.oldValue && this.#data.options.camera.changed?.oldValue !== this.#data.options.camera.value) {
726
+ // Si falló la cámara solicitada y hubo cambio explícito, intentamos volver a la anterior
727
+ if (hadExplicitChange && camState.changed.oldValue && camState.changed.oldValue !== camState.value) {
727
728
  camera =
728
- this.#data.containers.model.assetContainer?.cameras.find(
729
- (thisCamera) => thisCamera.name === this.#data.options.camera.changed.oldValue
730
- ) ||
731
- this.#data.containers.environment.assetContainer?.cameras.find(
732
- (thisCamera) => thisCamera.name === this.#data.options.camera.changed.oldValue
733
- ) ||
729
+ this.#data.containers.model.assetContainer?.cameras.find(c => c.name === camState.changed.oldValue) ||
730
+ this.#data.containers.environment.assetContainer?.cameras.find(c => c.name === camState.changed.oldValue) ||
734
731
  null;
735
- }
736
- if (camera) {
737
- camera.metadata = { locked: this.#data.options.camera.changed.oldLocked };
738
- this.#data.options.camera.value = this.#data.options.camera.changed.oldValue;
739
- this.#data.options.camera.locked = this.#data.options.camera.changed.oldLocked;
732
+
733
+ // Asegura estructura para escribir success
734
+ if (!camState.changed) camState.changed = { oldValue: camState.value, oldLocked: camState.locked, success: false };
735
+
736
+ if (camera) {
737
+ camera.metadata = { locked: camState.changed.oldLocked };
738
+ camState.value = camState.changed.oldValue;
739
+ camState.locked = camState.changed.oldLocked;
740
+ camState.changed.success = false;
741
+ } else {
742
+ // Fallback a la cámara por defecto del componente
743
+ camera = this.#camera;
744
+ camState.value = null;
745
+ camState.locked = this.#camera.metadata.locked;
746
+ camState.changed.success = false;
747
+ }
740
748
  } else {
749
+ // No hubo cambio explícito: usa fallback sin tocar changed
741
750
  camera = this.#camera;
742
- this.#data.options.camera.value = null;
743
- this.#data.options.camera.locked = this.#camera.metadata.locked;
751
+ camState.value = null;
752
+ camState.locked = this.#camera.metadata.locked;
744
753
  }
745
- this.#data.options.camera.changed && (this.#data.options.camera.changed.success = false);
746
754
  } else {
747
- camera.metadata = { locked: this.#data.options.camera.locked };
755
+ camera.metadata = { locked: camState.locked };
756
+ // Marca success si hubo cambio explícito
757
+ if (hadExplicitChange) {
758
+ if (!camState.changed) camState.changed = { oldValue: camState.value, oldLocked: camState.locked, success: false };
759
+ camState.changed.success = true;
760
+ }
748
761
  }
749
762
 
750
- if (!this.#data.options.camera.locked && this.#data.options.camera.value !== null) {
763
+ if (!camState.locked && camState.value !== null) {
751
764
  camera.attachControl(this.#canvas, true);
752
765
  }
753
766
  this.#scene.activeCamera = camera;
754
- this.#log("debug", "Cámara actualizada", {
755
- active: this.#scene?.activeCamera?.name ?? null,
756
- locked: this.#scene?.activeCamera?.metadata?.locked ?? null,
757
- });
767
+ console.log("PrefViewer: active camera set", { name: camera.name, locked: camera.metadata?.locked });
758
768
  return true;
759
769
  }
760
770
 
761
771
  #addContainer(container) {
772
+ console.log("PrefViewer: #addContainer()", { name: container.name, hasContainer: !!container.assetContainer, visible: container.visible, show: container.show });
762
773
  if (container.assetContainer && !container.visible && container.show) {
763
774
  container.assetContainer.addAllToScene();
764
775
  container.visible = true;
765
- this.#log("debug", `Añadido a escena: ${container.name}`);
776
+ console.log("PrefViewer: container added to scene", container.name);
766
777
  }
767
778
  }
768
779
 
769
780
  #removeContainer(container) {
781
+ console.log("PrefViewer: #removeContainer()", { name: container.name, hasContainer: !!container.assetContainer, visible: container.visible });
770
782
  if (container.assetContainer && container.visible) {
771
783
  container.assetContainer.removeAllFromScene();
772
784
  container.visible = false;
773
- this.#log("debug", `Eliminado de escena: ${container.name}`);
785
+ console.log("PrefViewer: container removed from scene", container.name);
774
786
  }
775
787
  }
776
788
 
777
789
  #replaceContainer(container, newAssetContainer) {
790
+ console.log("PrefViewer: #replaceContainer()", { name: container.name, hadContainer: !!container.assetContainer });
778
791
  // 1) quita y destruye el anterior si existía
779
792
  const old = container.assetContainer;
780
793
  if (old) {
781
- if (container.visible) {
782
- old.removeAllFromScene();
783
- }
784
- old.dispose();
794
+ if (container.visible) { old.removeAllFromScene(); }
795
+ old.dispose(); // <- importante
796
+ console.log("PrefViewer: old container disposed", container.name);
785
797
  }
786
798
 
787
799
  // 2) asigna el nuevo y prepara
788
800
  container.assetContainer = newAssetContainer;
789
801
 
790
- // Limitar luces por material (no usamos luces, poner 0 asegura shaders más simples)
791
- container.assetContainer.materials?.forEach((m) => {
802
+ // Opcional: limitar luces por material para ganar margen
803
+ container.assetContainer.materials?.forEach(m => {
792
804
  if ("maxSimultaneousLights" in m) {
793
- m.maxSimultaneousLights = 0; // [LIGHTS OFF]
805
+ m.maxSimultaneousLights = 2; // 2–3 suele ir bien
794
806
  }
795
807
  });
796
808
 
797
- // 3) sombras sólo si existe generador (con luces OFF es null)
798
- container.assetContainer.meshes.forEach((mesh) => {
799
- mesh.receiveShadows = !!this.#shadowGen;
800
- if (this.#shadowGen) this.#shadowGen.addShadowCaster(mesh, true);
809
+ // 3) sombras solo para los meshes que te interesen (mejor que todos)
810
+ container.assetContainer.meshes.forEach(mesh => {
811
+ mesh.receiveShadows = true;
812
+ this.#shadowGen.addShadowCaster(mesh, true);
801
813
  });
802
814
 
803
815
  // 4) añade a escena
@@ -805,46 +817,35 @@ class PrefViewer extends HTMLElement {
805
817
 
806
818
  // 5) fuerza recompilación con defines correctos del nuevo estado
807
819
  this.#scene.getEngine().releaseEffects();
808
-
809
- this.#log("debug", `Container reemplazado: ${container.name}`, {
810
- meshes: container.assetContainer.meshes?.length ?? 0,
811
- materials: container.assetContainer.materials?.length ?? 0,
812
- });
820
+ console.log("PrefViewer: container replaced and effects released", container.name);
813
821
  }
814
822
 
815
823
  async #loadAssetContainer(container) {
816
- const storage = container?.storage;
824
+ console.log("PrefViewer: #loadAssetContainer() START", { name: container?.name, storage: container?.storage });
825
+ let storage = container?.storage;
817
826
 
818
827
  if (!storage) {
819
- this.#log("debug", `Sin storage para "${container?.name}"`);
828
+ console.log("PrefViewer: no storage, skipping", container?.name);
820
829
  return false;
821
830
  }
822
831
 
823
- this.#timeStart(`load:${container.name}`);
824
832
  let source = storage.url || null;
825
833
 
826
834
  if (storage.db && storage.table && storage.id) {
827
- this.#log("info", `Cargando ${container.name} desde IndexedDB`, {
828
- db: storage.db,
829
- table: storage.table,
830
- id: storage.id,
831
- });
832
835
  await this.#initStorage(storage.db, storage.table);
833
836
  const object = await loadModel(storage.id, storage.table);
834
837
  source = object.data;
835
838
  if (object.timeStamp === container.timeStamp) {
836
- this.#log("debug", `${container.name}: sin cambios en IndexedDB`);
837
- this.#timeEnd(`load:${container.name}`);
839
+ console.log("PrefViewer: DB entry unchanged, skipping", container.name);
838
840
  return false;
839
841
  } else {
840
842
  container.changed = { timeStamp: object.timeStamp, size: object.size, success: false };
841
- this.#log("debug", `${container.name}: cambios detectados`, container.changed);
843
+ console.log("PrefViewer: DB entry changed", container.changed);
842
844
  }
843
845
  }
844
846
 
845
847
  if (!source) {
846
- this.#log("warn", `${container.name}: no hay source`);
847
- this.#timeEnd(`load:${container.name}`);
848
+ console.log("PrefViewer: no source after storage resolution", container.name);
848
849
  return false;
849
850
  }
850
851
 
@@ -857,23 +858,23 @@ class PrefViewer extends HTMLElement {
857
858
  });
858
859
  if (!container.changed) {
859
860
  if (container.timeStamp === null && container.size === size) {
860
- this.#log("debug", `${container.name}: Base64 sin cambios`);
861
- this.#timeEnd(`load:${container.name}`);
861
+ console.log("PrefViewer: same base64 size and null timestamp, skipping", container.name);
862
862
  return false;
863
863
  } else {
864
864
  container.changed = { timeStamp: null, size: size, success: false };
865
865
  }
866
866
  }
867
+ console.log("PrefViewer: prepared File from base64", { name: file.name, size: file.size, type: file.type });
867
868
  } else {
868
869
  const extMatch = source.match(/\.(gltf|glb)(\?|#|$)/i);
869
870
  extension = extMatch ? `.${extMatch[1].toLowerCase()}` : ".gltf";
870
871
  const [fileSize, fileTimeStamp] = await this.#getServerFileDataHeader(source);
871
872
  if (container.size === fileSize && container.timeStamp === fileTimeStamp) {
872
- this.#log("debug", `${container.name}: URL sin cambios`);
873
- this.#timeEnd(`load:${container.name}`);
873
+ console.log("PrefViewer: remote file unchanged, skipping", container.name);
874
874
  return false;
875
875
  } else {
876
876
  container.changed = { timeStamp: fileTimeStamp, size: fileSize, success: false };
877
+ console.log("PrefViewer: remote file changed", container.changed);
877
878
  }
878
879
  }
879
880
 
@@ -886,21 +887,13 @@ class PrefViewer extends HTMLElement {
886
887
  },
887
888
  },
888
889
  };
889
- this.#log("info", `LoadAssetContainerAsync ${container.name}`, { extension, changed: container.changed });
890
890
 
891
- try {
892
- const result = await LoadAssetContainerAsync(file || source, this.#scene, options);
893
- this.#timeEnd(`load:${container.name}`);
894
- return result;
895
- } catch (e) {
896
- this.#timeEnd(`load:${container.name}`);
897
- this.#log("error", `LoadAssetContainerAsync falló para ${container.name}`, e);
898
- throw e;
899
- }
891
+ console.log("PrefViewer: calling LoadAssetContainerAsync()", { extension });
892
+ return LoadAssetContainerAsync(file || source, this.#scene, options);
900
893
  }
901
894
 
902
895
  async #loadContainers(loadModel = true, loadEnvironment = true, loadMaterials = true) {
903
- this.#log("info", "loadContainers()", { loadModel, loadEnvironment, loadMaterials });
896
+ console.log("PrefViewer: #loadContainers()", { loadModel, loadEnvironment, loadMaterials });
904
897
  const promiseArray = [];
905
898
  promiseArray.push(loadModel ? this.#loadAssetContainer(this.#data.containers.model) : false);
906
899
  promiseArray.push(loadEnvironment ? this.#loadAssetContainer(this.#data.containers.environment) : false);
@@ -910,37 +903,33 @@ class PrefViewer extends HTMLElement {
910
903
 
911
904
  Promise.allSettled(promiseArray)
912
905
  .then(async (values) => {
906
+ console.log("PrefViewer: Promise.allSettled results", values);
913
907
  const modelContainer = values[0];
914
908
  const environmentContainer = values[1];
915
909
  const materialsContainer = values[2];
916
910
 
917
- this.#log(
918
- "debug",
919
- "Resultados Promise.allSettled",
920
- values.map((v) => ({ status: v.status, hasValue: !!v.value }))
921
- );
922
-
923
911
  if (modelContainer.status === "fulfilled" && modelContainer.value) {
912
+ console.log("PrefViewer: model container fulfilled, applying");
924
913
  this.#stripImportedLights(modelContainer.value);
925
914
  this.#replaceContainer(this.#data.containers.model, modelContainer.value);
926
915
  this.#storeChangedFlagsForContainer(this.#data.containers.model);
927
916
  } else {
928
- this.#data.containers.model.show
929
- ? this.#addContainer(this.#data.containers.model)
930
- : this.#removeContainer(this.#data.containers.model);
917
+ console.log("PrefViewer: model container not fulfilled or unchanged; ensuring visibility state");
918
+ this.#data.containers.model.show ? this.#addContainer(this.#data.containers.model) : this.#removeContainer(this.#data.containers.model);
931
919
  }
932
920
 
933
921
  if (environmentContainer.status === "fulfilled" && environmentContainer.value) {
922
+ console.log("PrefViewer: environment container fulfilled, applying");
934
923
  this.#stripImportedLights(environmentContainer.value);
935
924
  this.#replaceContainer(this.#data.containers.environment, environmentContainer.value);
936
925
  this.#storeChangedFlagsForContainer(this.#data.containers.environment);
937
926
  } else {
938
- this.#data.containers.environment.show
939
- ? this.#addContainer(this.#data.containers.environment)
940
- : this.#removeContainer(this.#data.containers.environment);
927
+ console.log("PrefViewer: environment container not fulfilled or unchanged; ensuring visibility state");
928
+ this.#data.containers.environment.show ? this.#addContainer(this.#data.containers.environment) : this.#removeContainer(this.#data.containers.environment);
941
929
  }
942
930
 
943
931
  if (materialsContainer.status === "fulfilled" && materialsContainer.value) {
932
+ console.log("PrefViewer: materials container fulfilled, applying");
944
933
  this.#stripImportedLights(materialsContainer.value);
945
934
  this.#replaceContainer(this.#data.containers.materials, materialsContainer.value);
946
935
  this.#storeChangedFlagsForContainer(this.#data.containers.materials);
@@ -951,11 +940,10 @@ class PrefViewer extends HTMLElement {
951
940
  this.#setVisibilityOfWallAndFloorInModel();
952
941
  this.#setStatusSceneLoaded();
953
942
  this.#resetChangedFlags();
954
- this.#log("info", "Escena cargada");
955
943
  })
956
944
  .catch((error) => {
957
945
  this.loaded = true;
958
- this.#log("error", "Failed to load containers", error);
946
+ console.error("PrefViewer: failed to load model", error);
959
947
  this.dispatchEvent(
960
948
  new CustomEvent("scene-error", {
961
949
  bubbles: true,
@@ -968,27 +956,29 @@ class PrefViewer extends HTMLElement {
968
956
  }
969
957
 
970
958
  #stripImportedLights(container) {
971
- const n = container?.lights?.length ?? 0;
972
- if (n) container.lights.slice().forEach((l) => l.dispose());
973
- this.#log("debug", `stripImportedLights(): ${n} → 0`);
959
+ console.log("PrefViewer: #stripImportedLights()", { lights: container?.lights?.length || 0 });
960
+ // El glTF puede traer KHR_lights_punctual: bórralas antes de añadir a la escena
961
+ if (container?.lights?.length) {
962
+ // Clonar para no mutar mientras iteras
963
+ container.lights.slice().forEach(l => l.dispose());
964
+ console.log("PrefViewer: stripped punctual lights from imported asset");
965
+ }
974
966
  }
975
967
 
976
968
  // Public Methods
977
969
  loadConfig(config) {
978
- this.#log("info", "loadConfig()", typeof config === "string" ? "[string]" : config);
970
+ console.log("PrefViewer: loadConfig()", config);
979
971
  config = typeof config === "string" ? JSON.parse(config) : config;
980
972
  if (!config) {
981
- this.#log("warn", "loadConfig() config vacío/nulo");
973
+ console.warn("PrefViewer: loadConfig() no config provided");
982
974
  return false;
983
975
  }
984
976
 
985
977
  // Containers
986
978
  this.#data.containers.model.storage = config.model?.storage || null;
987
- this.#data.containers.model.show =
988
- config.model?.visible !== undefined ? config.model.visible : this.#data.containers.model.show;
979
+ this.#data.containers.model.show = config.model?.visible !== undefined ? config.model.visible : this.#data.containers.model.show;
989
980
  this.#data.containers.environment.storage = config.scene?.storage || null;
990
- this.#data.containers.environment.show =
991
- config.scene?.visible !== undefined ? config.scene.visible : this.#data.containers.environment.show;
981
+ this.#data.containers.environment.show = config.scene?.visible !== undefined ? config.scene.visible : this.#data.containers.environment.show;
992
982
  this.#data.containers.materials.storage = config.materials?.storage || null;
993
983
 
994
984
  // Options
@@ -1001,7 +991,7 @@ class PrefViewer extends HTMLElement {
1001
991
  }
1002
992
 
1003
993
  setOptions(options) {
1004
- this.#log("info", "setOptions()", options);
994
+ console.log("PrefViewer: setOptions()", options);
1005
995
  if (!options) {
1006
996
  return false;
1007
997
  }
@@ -1023,61 +1013,63 @@ class PrefViewer extends HTMLElement {
1023
1013
  }
1024
1014
 
1025
1015
  loadModel(model) {
1026
- this.#log("info", "loadModel()", typeof model === "string" ? "[string]" : model);
1016
+ console.log("PrefViewer: loadModel()", model);
1027
1017
  model = typeof model === "string" ? JSON.parse(model) : model;
1028
1018
  if (!model) {
1029
- this.#log("warn", "loadModel() model vacío/nulo");
1019
+ console.warn("PrefViewer: loadModel() no model provided");
1030
1020
  return false;
1031
1021
  }
1032
1022
  this.#data.containers.model.storage = model.storage || null;
1033
- this.#data.containers.model.show =
1034
- model.visible !== undefined ? model.visible : this.#data.containers.model.show;
1023
+ this.#data.containers.model.show = model.visible !== undefined ? model.visible : this.#data.containers.model.show;
1035
1024
  this.initialized && this.#loadContainers(true, false, false);
1036
1025
  }
1037
1026
 
1038
1027
  loadScene(scene) {
1039
- this.#log("info", "loadScene()", typeof scene === "string" ? "[string]" : scene);
1028
+ console.log("PrefViewer: loadScene()", scene);
1040
1029
  scene = typeof scene === "string" ? JSON.parse(scene) : scene;
1041
1030
  if (!scene) {
1042
- this.#log("warn", "loadScene() scene vacío/nulo");
1031
+ console.warn("PrefViewer: loadScene() no scene provided");
1043
1032
  return false;
1044
1033
  }
1045
1034
  this.#data.containers.environment.storage = scene.storage || null;
1046
- this.#data.containers.environment.show =
1047
- scene.visible !== undefined ? scene.visible : this.#data.containers.environment.show;
1035
+ this.#data.containers.environment.show = scene.visible !== undefined ? scene.visible : this.#data.containers.environment.show;
1048
1036
  this.initialized && this.#loadContainers(false, true, false);
1049
1037
  }
1050
1038
 
1051
1039
  showModel() {
1040
+ console.log("PrefViewer: showModel()");
1052
1041
  this.#data.containers.model.show = true;
1053
1042
  this.#addContainer(this.#data.containers.model);
1054
1043
  }
1055
1044
 
1056
1045
  hideModel() {
1046
+ console.log("PrefViewer: hideModel()");
1057
1047
  this.#data.containers.model.show = false;
1058
1048
  this.#removeContainer(this.#data.containers.model);
1059
1049
  }
1060
1050
 
1061
1051
  showScene() {
1052
+ console.log("PrefViewer: showScene()");
1062
1053
  this.#data.containers.environment.show = true;
1063
1054
  this.#addContainer(this.#data.containers.environment);
1064
1055
  this.#setVisibilityOfWallAndFloorInModel();
1065
1056
  }
1066
1057
 
1067
1058
  hideScene() {
1059
+ console.log("PrefViewer: hideScene()");
1068
1060
  this.#data.containers.environment.show = false;
1069
1061
  this.#removeContainer(this.#data.containers.environment);
1070
1062
  this.#setVisibilityOfWallAndFloorInModel();
1071
1063
  }
1072
1064
 
1073
1065
  downloadModelGLB() {
1066
+ console.log("PrefViewer: downloadModelGLB()");
1074
1067
  const fileName = "model";
1075
- GLTF2Export.GLBAsync(this.#data.containers.model.assetContainer, fileName, {
1076
- exportWithoutWaitingForScene: true,
1077
- }).then((glb) => glb.downloadFiles());
1068
+ GLTF2Export.GLBAsync(this.#data.containers.model.assetContainer, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
1078
1069
  }
1079
1070
 
1080
1071
  downloadModelUSDZ() {
1072
+ console.log("PrefViewer: downloadModelUSDZ()");
1081
1073
  const fileName = "model";
1082
1074
  USDZExportAsync(this.#data.containers.model.assetContainer).then((response) => {
1083
1075
  if (response) {
@@ -1087,6 +1079,7 @@ class PrefViewer extends HTMLElement {
1087
1079
  }
1088
1080
 
1089
1081
  downloadModelAndSceneUSDZ() {
1082
+ console.log("PrefViewer: downloadModelAndSceneUSDZ()");
1090
1083
  const fileName = "scene";
1091
1084
  USDZExportAsync(this.#scene).then((response) => {
1092
1085
  if (response) {
@@ -1096,10 +1089,9 @@ class PrefViewer extends HTMLElement {
1096
1089
  }
1097
1090
 
1098
1091
  downloadModelAndSceneGLB() {
1092
+ console.log("PrefViewer: downloadModelAndSceneGLB()");
1099
1093
  const fileName = "scene";
1100
- GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) =>
1101
- glb.downloadFiles()
1102
- );
1094
+ GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
1103
1095
  }
1104
1096
  }
1105
1097