← Commit history

workcell viewer: per-component hover descriptions (user-oriented; CM5 kept system-level), Descriptions toggle

Caleb ·3db4294478 ·2mo ago ·parent 1a9cd96
1 file changed +132
README.html+132
@@ -106,6 +106,138 @@ else {       g.onclick = () => { groundOn = !groundOn; viewer.setGroundVisible(groundOn); paint(); };       l.onclick = () => { viewer.setBottomLight(!viewer.getBottomLightState()); paint(); };       paint(); ctl.style.display = 'flex';+      // ---- block hover descriptions (workcell) ----+      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,.96);border:1px solid rgba(240,246,252,.15);border-radius:8px;' ++          'padding:8px 11px;max-width:300px;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);+        let tipsOn = true;+        const dBtn = document.createElement('button');+        dBtn.type = 'button'; dBtn.textContent = 'Descriptions'; dBtn.className = 'on';+        ctl.appendChild(dBtn);+        dBtn.onclick = () => { tipsOn = !tipsOn; dBtn.classList.toggle('on', tipsOn); if (!tipsOn) tip.style.display = 'none'; };++        const HDR = {+          pwr: 'Supply inputs (PS1\u2013PS4)',+          meter: 'Rail metering',+          usb: 'USB ports',+          wifi: 'M.2 Wi-Fi slot',+          cm5: 'Workcell controller',+          ring: 'ADOM LED ring',+          sens: 'Sensing',+          probe: 'PiProbe (N1)',+          dock: 'ET60T dock',+          led: 'Status LEDs',+          jmp: 'Ground bonds',+          board: 'Control Panel: Workcell',+        };+        const T = {+          pwr: [HDR.pwr, 'Bench supplies land on WAGO terminals: PS1 0\u201360 V / 12 A (variable), PS2 5 V / 10 A (switched), PS3 user rail, PS4 internal 5 V. Every rail is metered.'],+          meter: [HDR.meter, 'INA228 20-bit monitors \u2014 one per rail \u2014 feed live V / A / W to the Control Panel Dashboard.'],+          ps2sw: [HDR.pwr, 'TPS22990 load switch \u2014 the PS2 rail enable, 3 ms controlled ramp. Switching PS2 off also drops USB VBUS.'],+          usba: [HDR.usb, 'USB-A 3.0 SuperSpeed host, power delivery; VBUS from PS2. Its USB 2.0 lanes are muxed with Bluetooth \u2014 mutually exclusive.'],+          usbc: [HDR.usb, 'USB-C 3.0 SuperSpeed host (G-Switch GT-USB-7052), power delivery 3.0 A; VBUS from PS2.'],+          mux: [HDR.usb, 'TMUXHS221 high-speed mux \u2014 routes the shared USB 2.0 lane to Bluetooth or USB-A.'],+          efuse: [HDR.usb, 'TPS25940 smart eFuse \u2014 protected VBUS path (5 A limit, back-feed block).'],+          cc: [HDR.usb, 'TUSB321 Type-C CC controller.'],+          esd: [HDR.usb, 'USB ESD protection.'],+          wifi: [HDR.wifi, 'M.2 Key-E slot for the AX210-class module: Wi-Fi 6E (2.4 / 5 / 6 GHz, 160 MHz, 2\u00d72 MIMO to ~2.4 Gbps) + Bluetooth 5.3. Wi-Fi on PCIe, Bluetooth on USB.'],+          cm5: [HDR.cm5, 'Raspberry Pi CM5 (system side) \u2014 runs metering, switching and the Control Panel Dashboard. No user wiring lands here.'],+          sd: [HDR.cm5, 'micro-SD \u2014 CM5 storage.'],+          net: [HDR.cm5, 'RJ45 \u2014 workcell network uplink (system side).'],+          ring: [HDR.ring, '18 blue LEDs (0402, C130724) around the logo \u2014 verification for vision-system reliability. The chase animation mirrors the real behavior.'],+          bme: [HDR.sens, 'BME690 environmental sensor slot \u2014 temperature \u221240..+85 \u00b0C, 0\u2013100 %RH, 300\u20131100 hPa, gas/IAQ.'],+          bmi: [HDR.sens, 'BMI270 inertial sensor slot \u2014 accel \u00b12\u201316 g, gyro \u00b1125\u20132000 \u00b0/s.'],+          mic: [HDR.sens, 'MEMS microphone module (N3) \u2014 IM72D128 on I2S, acoustic capture for the dashboard.'],+          sj: [HDR.sens, 'Sensor jumper \u2014 drops in to connect a sensor slot.'],+          probe: [HDR.probe, 'Onboard debug probe \u2014 flashes and halts the scaffold\u2019s user MCU over SWD (stock openocd / picotool).'],+          dock: [HDR.dock, 'Delivers PS1\u2013PS3 + returns, USB 2.0, SWD/UART debug and MCU boot/reset across the blade dock to the scaffold.'],+          ledR: [HDR.led, 'Red status indicator (0402, C130719).'],+          ledG: [HDR.led, 'Green status indicator (0402, C130723).'],+          jmp: [HDR.jmp, 'Ground-bond jumper.'],+          mcL: [HDR.dock, 'Large machine contact \u2014 ~20.8 A (80% derated), mates 14 AWG solid core.'],+          mc: [HDR.board, 'Machine contact \u2014 medium ~6.4 A, solderless press-fit.'],+          sw: [HDR.board, 'SW1 \u2014 service button (SKRPACE010).'],+          ghost: ['Control Panel: Scaffold (ghost)', 'The scaffold half shown mated below \u2014 user power + I/O dock.'],+          board: [HDR.board, 'CM5 carrier and smart power-distribution hub \u2014 the system half of the control panel.'],+        };+        const NAME_RULES = [+          [/^BMEH/, 'bme'], [/^BMIH/, 'bmi'],+          [/^N3$/, 'mic'], [/^N1$/, 'probe'],+          [/^SJ\d/, 'sj'],+          [/^(D2|D23)($|_glow|_cone)/, 'ledR'],+          [/^(D3|D4)($|_glow|_cone)/, 'ledG'],+          [/^(D\d+)($|_glow|_cone)/, 'ring'],+          [/^(U1|U2|U3|U4)$/, 'meter'],+          [/^U7$/, 'ps2sw'], [/^U13$/, 'mux'], [/^U17$/, 'efuse'], [/^U18$/, 'cc'],+          [/^(U8|U9|U10|U11|U19|U20|U22|U23|U24)$/, 'esd'],+          [/^J9$/, 'usba'], [/^J10$/, 'usbc'], [/^J8$/, 'wifi'],+          [/^(J4|J7)$/, 'cm5'], [/^J5$/, 'sd'], [/^J6$/, 'net'],+          [/^(J1|J3)$/, 'pwr'],+          [/^SW1$/, 'sw'],+          [/^(JP\d|JMP|Adom_Jumper|GNDSPACER)/, 'jmp'],+          [/^MpinContact_Large/, 'mcL'],+          [/^(WIRE_MC|MC\d|MpinContact)/, 'mc'],+          [/^(ETT|ETS)/, 'dock'],+          [/^GHOST/, 'ghost'],+        ];+        const REGIONS = [+          [0.163, 0.184, -0.105, -0.087, 'wifi'],+          [0.138, 0.159, -0.126, -0.111, 'usba'],+          [0.159, 0.177, -0.126, -0.111, 'usbc'],+          [0.105, 0.162, -0.093, -0.070, 'cm5'],+          [0.060, 0.0755, -0.1015, -0.086, 'ring'],+          [0.094, 0.108, -0.092, -0.077, 'mic'],+          [0.018, 0.051, -0.087, -0.071, 'meter'],+          [0.040, 0.090, -0.076, -0.050, 'pwr'],+          [0.246, 0.267, -0.175, -0.157, 'probe'],+        ];+        const inv = new window.BABYLON.Matrix();+        const root = scene.transformNodes.concat(scene.meshes).find(n => (n.name||'').indexOf('control-panel_workcell') === 0);+        function describe(pick) {+          let n = pick.pickedMesh; const names = [];+          while (n) { names.push(n.name || ''); n = n.parent; }+          for (const [re, key] of NAME_RULES) {+            if (names.some(nm => re.test(nm))) return T[key];+          }+          if (root && pick.pickedPoint) {+            root.getWorldMatrix().invertToRef(inv);+            const p = window.BABYLON.Vector3.TransformCoordinates(pick.pickedPoint, inv);+            for (const [x0, x1, y0, y1, key] of REGIONS) {+              if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1) return T[key];+            }+            if (p.y < -0.106) return T.dock;+            return T.board;+          }+          return null;+        }+        let raf = 0;+        wrap.addEventListener('pointermove', (ev) => {+          if (!tipsOn || raf) return;+          raf = requestAnimationFrame(() => {+            raf = 0;+            if (!tipsOn) return;+            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 - 310) + '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 !== 'workcell')) return;