state / harness / camera / pick / ui / sheets / render / urlparams。 经典脚本的顶层 const/let 落在同一个全局词法作用域,所以切片本身不改变任何行为—— 这一步先让"文件边界"与"模块语义"两件事分离:翻 ESM 时若出问题,责任清楚在 import/export,而不是切片切错了行。 切片踩到并修掉的:urlparams.js 被切掉了 IIFE 的收尾 })();(我按 len-2 收边界), node --check 当场报 Unexpected end of input。 截图基线分级也在这一步收敛:工程图/装配向导视图带 feGaussianBlur 发光滤镜, 同一构建两轮就有 ±1 抖动(先前扫描那两轮碰巧逐字节相同,才漏进"稳定"清单, 于是随机假 FAIL)。把 8 张工程图/向导视图连同 3 张线束 x-ray、boss_p07、 p03_mount_loose 一起转像素容差(存参考像素,阈值 mean<=0.02、单像素<=32, 约实测 20x 余量),其余 92 张仍要求逐字节相同。 关卡:92 张逐字节一致 + 13 张在噪声阈内;数据哈希与 IK exact 段一致。
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();
|
||
});
|
||
|