127 lines
5.2 KiB
JavaScript
127 lines
5.2 KiB
JavaScript
/* 拾取、悬停、隐藏与选中态 */
|
||||
|
|
/* ---- 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 = '<b>' + p.cn + '</b><br><small style="color:#fbbf24">' + p.name + '</small>';
|
|||
|
|
} else {
|
|||
|
|
n.innerHTML = '<b>#' + p.idx + ' ' + p.cn + '</b><br><small>' + p.name +
|
|||
|
|
' · ' + (p.printed ? '3D打印件' : '电机/已有件') +
|
|||
|
|
(dimsStr(p) ? '<br>' + dimsStr(p) : '') + '</small>';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
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 = '已隐藏:<b>#' + p.idx + ' ' + p.cn + '</b> <span id="unhideBtn" style="color:var(--ac);cursor:pointer;text-decoration:underline;margin-left:6px">撤销</span> <small style="color:var(--dim)">(可在左侧零件树勾选或点击“全选”恢复)</small>';
|
|||
|
|
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 = '<b>' + u.cn + '</b> · <span style="color:#f59e0b">' + u.name +
|
|||
|
|
'</span> · 2×CAN通信 + 2×24V供电 · <span style="color:var(--ok)">4芯高柔拖链电缆/T型分接</span>';
|
|||
|
|
requestRender();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
hud.innerHTML = '<b>#' + u.idx + ' ' + u.cn + '</b> · ' + (u.vol / 1000).toFixed(1) +
|
|||
|
|
' cm3 · ' + (u.printed ? '打印件' : '电机/已有件') +
|
|||
|
|
' <span style="margin-left:8px;color:var(--warn);font-size:11px">[右键单击或按H键隐藏此部件]</span>';
|
|||
|
|
const tr = tb.querySelector('[data-idx="' + u.idx + '"]');
|
|||
|
|
if (tr) { tr.style.background = '#22406b'; bomHlRow = tr; tr.scrollIntoView({block:'nearest'}); }
|
|||
|
|
}
|
|||
|
|
requestRender();
|
|||
|
|
});
|
|||
|
|
|