← Commit history

Sandbox rescue: start the render loop, add a fallback light, frame the camera. The wiki CSP leaves all three silently undone.

John Lauer ·4544ff6f93 ·2mo ago ·parent eda7909
1 file changed +74
viewer/index.html+74
@@ -1489,6 +1489,80 @@ async function loadCurrentGlb(force) {     viewer.frameModel();     setView('isometric');     for (const ms of [50, 200, 600, 1500]) setTimeout(applyGroundTransparency, ms);++    // ── Sandbox rescue ────────────────────────────────────────────────────+    // The wiki serves this page under a CSP that sandboxes it to a null+    // origin (`sandbox allow-scripts ...; default-src 'none'; img-src data:+    // https:; media-src data: https:`). Three things break there, and all+    // three are silent — no console error, the GLB reports "loaded", and you+    // get a blank canvas:+    //+    //   1. NO RENDER LOOP. engine.activeRenderLoops is empty, so scene.render+    //      is never called. getFps() still reports 60, which is what makes+    //      this so easy to misdiagnose. Measure frames with+    //      onAfterRenderObservable instead.+    //   2. NO LIGHTS. environmentSpecular.env fetches fine (200) but Babylon+    //      turns it into a blob: URL, which media-src refuses, so+    //      scene.environmentTexture stays null and every PBR material renders+    //      black. Adom3DViewer.BABYLON exports no light class, so the only+    //      way back is Scene.createDefaultLight().+    //   3. NO FRAMING. frameModel() leaves the camera at radius ~2500 on a+    //      ~107 mm board, so even once it renders the board is a speck.+    //+    // Each guard is a no-op when the viewer is served normally.+    function sandboxRenderRescue() {+      if (!window.viewer) return;+      let engine, scene, B;+      try {+        engine = viewer.getEngine();+        scene = viewer.getScene();+        B = window.Adom3DViewer.BABYLON;+      } catch (e) { return; }+      if (!engine || !scene || !B) return;++      if (!engine.activeRenderLoops || engine.activeRenderLoops.length === 0) {+        engine.runRenderLoop(() => scene.render());+      }++      if (scene.lights.length === 0 && !scene.environmentTexture && scene.createDefaultLight) {+        scene.createDefaultLight(true);+        const l = scene.lights[0];+        if (l) {+          l.direction = new B.Vector3(-0.3, -0.5, 1);+          l.intensity = 2.2;+          l.diffuse = new B.Color3(1, 1, 1);+          l.groundColor = new B.Color3(0.45, 0.47, 0.5);+          l.specular = new B.Color3(0.6, 0.6, 0.6);+        }+        scene.ambientColor = new B.Color3(0.35, 0.35, 0.35);+        for (const m of scene.materials) {+          if (m.ambientColor) m.ambientColor = new B.Color3(1, 1, 1);+          if (m.environmentIntensity !== undefined) m.environmentIntensity = 0;+        }+      }++      const cam = scene.activeCamera;+      if (!cam || cam.radius === undefined) return;+      let mn = [Infinity, Infinity, Infinity], mx = [-Infinity, -Infinity, -Infinity];+      for (const m of scene.meshes) {+        if (!m.getTotalVertices || !m.getTotalVertices()) continue;+        m.computeWorldMatrix(true);+        const b = m.getBoundingInfo().boundingBox, a = b.minimumWorld, d = b.maximumWorld;+        mn = [Math.min(mn[0], a.x), Math.min(mn[1], a.y), Math.min(mn[2], a.z)];+        mx = [Math.max(mx[0], d.x), Math.max(mx[1], d.y), Math.max(mx[2], d.z)];+      }+      if (!isFinite(mn[0])) return;+      const diag = Math.hypot(mx[0] - mn[0], mx[1] - mn[1], mx[2] - mn[2]);+      if (!(diag > 0) || cam.radius <= diag * 3) return;   // already framed+      const ctr = [(mn[0] + mx[0]) / 2, (mn[1] + mx[1]) / 2, (mn[2] + mx[2]) / 2];+      const r = diag * 1.15;+      cam.upperRadiusLimit = Math.max(cam.upperRadiusLimit || 0, r * 4);+      cam.upVector = new B.Vector3(0, 0, 1);+      cam.setPosition(new B.Vector3(ctr[0] + r * 0.55, ctr[1] - r * 0.62, ctr[2] + r * 0.55));+      cam.setTarget(new B.Vector3(ctr[0], ctr[1], ctr[2]));+    }+    for (const ms of [80, 400, 1200, 2500, 5000]) setTimeout(sandboxRenderRescue, ms);+     // BULLETPROOF enforcement — the viewer's goHome()/frameModel()/setView()     // re-init the EnvironmentHelper and reset the ground alpha back to 1     // (opaque). The init-time retry loop above can't catch those LATER calls