/* 拾取、悬停、隐藏与选中态 */ /* ---- picking & hiding ---- */ const ray = new THREE.Raycaster(); let sel = null, hov = null; /* 拾取列表与 NDC 向量复用同一实例:旧版每次 pointermove 都 filter 出一个新数组 + 新建 Vector2(高频下纯垃圾)。可见性逐次重算,语义与 filter 完全一致 ——three r159 的 Raycaster 不检查 object.visible,只能靠这里过滤。 */ const pickList = [], ndcPick = new THREE.Vector2(); function pick(e) { const r = cv.getBoundingClientRect(); ndcPick.set(((e.clientX-r.left)/r.width)*2-1, -((e.clientY-r.top)/r.height)*2+1); ray.setFromCamera(ndcPick, cam); pickList.length = 0; for (const m of meshes) if (m.visible) pickList.push(m); if (window.isHarnessVisible && isHarnessVisible() && window.harnessMeshes) { for (const m of harnessMeshes) if (m.visible) pickList.push(m); } const hit = ray.intersectObjects(pickList, false)[0]; return hit ? hit.object : null; } /* BOM 表高亮行只记一行:旧版每次点选/ESC 都要把整张表(13 行零件 + 10 行螺钉) 的 style.background 全刷一遍,纯属多余 DOM 写入。 */ let bomHlRow = null; function bomHlClear() { if (bomHlRow) { bomHlRow.style.background = ''; bomHlRow = null; } } function dimsStr(p) { if (!p.dims) return ''; const f = d => String(Math.round(d * 10) / 10); return '外形 ' + f(p.dims[0]) + ' × ' + f(p.dims[1]) + ' × ' + f(p.dims[2]) + ' mm(X·Y·Z)'; } function hover(e) { const o = pick(e); if (o === hov) return; hov = o; const n = document.getElementById('note'); if (!o) { n.style.display = 'none'; return; } const p = o.userData; n.style.display = 'block'; if (p.harness) { n.innerHTML = '' + p.cn + '
' + p.name + ''; } else { n.innerHTML = '#' + p.idx + ' ' + p.cn + '
' + p.name + ' · ' + (p.printed ? '3D打印件' : '电机/已有件') + (dimsStr(p) ? '
' + dimsStr(p) : '') + '
'; } } function hidePart(m) { if (!m) return; const p = m.userData; if (m === sel) { m.material.emissive.setHex(0); m.material.emissiveIntensity = 0; sel = null; } m.visible = false; if (m.chk) m.chk.checked = false; const n = document.getElementById('note'); if (n) n.style.display = 'none'; const hud = document.getElementById('hud'); if (hud) { hud.innerHTML = '已隐藏:#' + p.idx + ' ' + p.cn + ' 撤销 (可在左侧零件树勾选或点击“全选”恢复)'; const ub = document.getElementById('unhideBtn'); if (ub) ub.onclick = () => { m.visible = true; if (m.chk) m.chk.checked = true; hud.textContent = ''; requestRender(); }; } requestRender(); } cv.addEventListener('contextmenu', e => { e.preventDefault(); if (panDist > 6) return; // ignore drag panning let target = sel; if (!target) target = pick(e); if (target) hidePart(target); }); window.addEventListener('keydown', e => { if (e.key === 'h' || e.key === 'H' || e.key === 'Delete') { if (sel) hidePart(sel); } else if (e.key === 'Escape') { clearScrewHl(); if (sel) { sel.material.emissive.setHex(0); sel.material.emissiveIntensity = 0; sel = null; const hud = document.getElementById('hud'); if (hud) hud.textContent = ''; bomHlClear(); requestRender(); } } }); cv.addEventListener('click', e => { const o = pick(e); if (o === sel) { if (sel) { sel.material.emissive.setHex(0); sel.material.emissiveIntensity = 0; sel = null; } bomHlClear(); clearScrewHl(); const hud = document.getElementById('hud'); if (hud) { hud.textContent = ''; delete hud.dataset.scrHl; } requestRender(); return; } if (sel && !sel.userData.screw && !sel.userData.harness) { sel.material.emissive.setHex(0); sel.material.emissiveIntensity = 0; } sel = o; if (sel && !sel.userData.screw && !sel.userData.harness) { sel.material.emissive.setHex(0x2f6bff); sel.material.emissiveIntensity = .55; } const tb = document.querySelector('#bom tbody'); bomHlClear(); const hud = document.getElementById('hud'); hud.textContent = ''; delete hud.dataset.scrHl; if (sel) { const u = sel.userData; if (u.screw) { highlightSpec(u.spec, false); requestRender(); return; } // 点中螺钉 -> 反查 BOM 行并定位 if (u.harness) { hud.innerHTML = '' + u.cn + ' · ' + u.name + ' · 2×CAN通信 + 2×24V供电 · 4芯高柔拖链电缆/T型分接'; requestRender(); return; } hud.innerHTML = '#' + u.idx + ' ' + u.cn + ' · ' + (u.vol / 1000).toFixed(1) + ' cm3 · ' + (u.printed ? '打印件' : '电机/已有件') + ' [右键单击或按H键隐藏此部件]'; const tr = tb.querySelector('[data-idx="' + u.idx + '"]'); if (tr) { tr.style.background = '#22406b'; bomHlRow = tr; tr.scrollIntoView({block:'nearest'}); } } requestRender(); });