Files
cbb f713355351 refactor(viewer): app.js 按职责切成 8 个普通脚本(纯切片,零语义改动)
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 段一致。
2026-10-15 04:45:56 +08:00

127 lines
5.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 拾取、悬停、隐藏与选中态 */
/* ---- 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();
});