← Commit history

scaffold viewer: hover block descriptions (MCU, USB, contact fields, jumpers, dock)

Caleb ·2f6bd41401 ·2mo ago ·parent 3568fde
1 file changed +68
README.html+68
@@ -160,6 +160,74 @@ else {       g.onclick = () => { groundOn = !groundOn; viewer.setGroundVisible(groundOn); paint(); };       l.onclick = () => { viewer.setBottomLight(!viewer.getBottomLightState()); paint(); };       paint(); ctl.style.display = 'flex';+      // ---- block hover descriptions (scaffold) ----+      try {+        const scene = viewer.getScene();+        const tip = document.createElement('div');+        tip.style.cssText = 'position:absolute;display:none;z-index:20;pointer-events:none;' ++          'background:rgba(22,27,34,.95);border:1px solid rgba(240,246,252,.15);border-radius:8px;' ++          'padding:8px 11px;max-width:260px;color:#e6edf3;font-size:12.5px;line-height:1.45;' ++          "font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif";+        const tipT = document.createElement('div'); tipT.style.fontWeight = '600';+        const tipB = document.createElement('div'); tipB.style.color = '#9aa4b2'; tipB.style.marginTop = '2px';+        tip.appendChild(tipT); tip.appendChild(tipB);+        document.body.appendChild(tip);+        const root = scene.transformNodes.concat(scene.meshes).find(n => (n.name||'').indexOf('control-panel_scaffold') === 0);+        const NAME_RULES = [+          [/^(U2|Molecule_RP2350A_Mini|mini-D|MINI_)/, ['RP2350A Mini (user MCU)', '26 USER_IO with PWM, PIO and 4 ADC channels; USB 2.0 dual-role. Red = power LED, blue = status LED.']],+          [/^(WIRE_MC|MC\d)/, null],+          [/^JP\d/, ['Ground-bond jumpers', 'Large machine-pin jumpers bond each rail return to ground. Remove to float a supply.']],+          [/^CCJ/, ['CC jumper', 'Place when VBUS is broken out through machine pins without CC termination.']],+          [/^(D1$|D1_glow|D1_cone)/, ['Power LED (red)', 'Scaffold power indicator.']],+          [/^(ETT|ETS)/, ['ET60T blade dock', 'Power and signal blades that mate to the workcell each dock.']],+          [/^GHOST/, ['Control Panel: Workcell (ghost)', 'The workcell half, shown mated above the scaffold.']],+          [/^GNDSPACER/, ['Ground spacer', 'Return path to the base scaffold plate.']],+        ];+        const REGIONS = [+          [0.150, 0.192, -0.088, -0.046, ['RP2350A Mini (user MCU)', '26 USER_IO with PWM, PIO and 4 ADC channels; USB 2.0 dual-role.']],+          [0.125, 0.141, -0.068, -0.052, ['USB-C 2.0 (user)', 'Dual-role host/device, power delivery 3.0 A. VBUS sourced from PS2.']],+          [0.104, 0.141, -0.079, -0.056, ['USB power path', 'Protected VBUS switch and CC controller behind the USB-C port.']],+          [0.043, 0.101, -0.096, -0.079, ['Power contact field', 'PS1 / PS2 / PS3 rails and returns land on machine contacts MC1 to MC30.']],+          [0.101, 0.196, -0.096, -0.080, ['USER_IO contact field', 'MC31 to MC56: 26 RP2350 USER_IO lines for the project.']],+          [0.043, 0.103, -0.078, -0.046, ['Rail ground jumpers', 'Per-rail return-to-ground bonds; each rail is an explicit grounding choice.']],+        ];+        const inv = new window.BABYLON.Matrix();+        function describe(pick) {+          let n = pick.pickedMesh, names = [];+          while (n) { names.push(n.name || ''); n = n.parent; }+          for (const [re, txt] of NAME_RULES) {+            if (names.some(nm => re.test(nm))) { if (txt) return txt; break; }+          }+          if (root && pick.pickedPoint) {+            root.getWorldMatrix().invertToRef(inv);+            const p = window.BABYLON.Vector3.TransformCoordinates(pick.pickedPoint, inv);+            for (const [x0, x1, y0, y1, txt] of REGIONS) {+              if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1) return txt;+            }+            if (p.y < -0.098) return ['ET60T blade dock', 'Power and signal blades that mate to the workcell each dock.'];+            return ['Control Panel: Scaffold', 'User power and I/O dock board for the base scaffold.'];+          }+          return null;+        }+        let raf = 0;+        wrap.addEventListener('pointermove', (ev) => {+          if (raf) return;+          raf = requestAnimationFrame(() => {+            raf = 0;+            const pick = scene.pick(scene.pointerX, scene.pointerY, m => m.isEnabled() && (m.name||'') !== 'ground');+            const d = pick && pick.hit ? describe(pick) : null;+            if (d) {+              tipT.textContent = d[0]; tipB.textContent = d[1];+              tip.style.display = 'block';+              const r = wrap.getBoundingClientRect();+              tip.style.left = Math.min(ev.clientX - r.left + 14, r.width - 280) + 'px';+              tip.style.top = Math.max(ev.clientY - r.top - 10, 46) + 'px';+            } else tip.style.display = 'none';+          });+        });+        wrap.addEventListener('pointerleave', () => { tip.style.display = 'none'; });+      } catch (e) {}+       addEventListener('message', (e) => {         const d = e.data || {};         if (d.type !== 'cp-cam' || (d.board && d.board !== 'scaffold')) return;