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

65 lines
2.9 KiB
JavaScript

/* URL 参数驱动的视图状态(供截图与分享复现) */
/* ---- url params so views can be scripted / shared: ?expl=60&j1=20&cam=iso&xray=0 ---- */
(function(){
const q = new URLSearchParams(location.search);
if (q.has('expl')) { exp.value = q.get('expl'); setExpl(+exp.value/100);
if (+exp.value) tog(false, true); }
for (const id of JNAMES) {
const k = id.toLowerCase();
if (q.has(k)) { const el = document.getElementById('s'+id);
el.value = q.get(k); el.dispatchEvent(new Event('input')); }
}
if (q.has('cam')) { const s = document.getElementById('cam'); s.value = q.get('cam'); s.dispatchEvent(new Event('change')); }
if (q.has('az')) az = +q.get('az');
if (q.has('po')) po = +q.get('po');
if (q.has('rd')) rd = +q.get('rd');
if (q.has('tx')) tx = +q.get('tx');
if (q.has('ty')) ty = +q.get('ty');
if (q.has('tz')) tz = +q.get('tz');
if (q.get('env') === '1') { const b = document.getElementById('bEnv');
if (b && !b.classList.contains('on')) b.onclick(); }
if (q.get('xray') === '0') { const b = document.getElementById('bXRay'); if (b && b.classList.contains('on')) b.onclick(); }
if (q.get('xray') === '1') { const b = document.getElementById('bXRay'); if (b && !b.classList.contains('on')) b.onclick(); }
if (q.get('wire') === '1') { const b = document.getElementById('bWire'); if (b && !b.classList.contains('on')) b.onclick(); }
if (q.has('alpha')) {
const a = +q.get('alpha');
meshes.forEach(m => {
if (m.userData.printed) {
m.material.transparent = true;
m.material.opacity = a;
m.material.depthWrite = a > 0.8;
}
});
}
if (q.get('harn') === '0') { const b = document.getElementById('bHarn'); if (b && b.classList.contains('on')) b.onclick(); }
/* 视图模式与工程图状态也要能用 URL 驱动:否则工程图/装配向导这两种模式(约占一半
CSS 与 700 行 JS)没法用 headless 截图回归,改动只能靠人眼抽查。 */
if (q.has('view')) {
const t = document.querySelector('.tab[data-view="' + q.get('view') + '"]');
if (t) t.click();
}
if (q.has('sheet')) {
const b = document.querySelector('#sheetTabs .sheet-tab[data-sheet="' + q.get('sheet') + '"]');
if (b) b.click();
}
if (q.has('theme')) {
const b = document.querySelector('.sheet-btn[data-theme="' + q.get('theme') + '"]');
if (b) b.click();
}
if (q.has('guide')) { const g = +q.get('guide'); if (isFinite(g)) renderGuideStep(g); }
if (q.has('j6')) { const el = document.getElementById('sJ6');
if (el) { el.value = q.get('j6'); el.dispatchEvent(new Event('input')); } }
if (q.has('hide')) {
const h = q.get('hide').split(',');
meshes.forEach(m => {
const nm = (m.userData && m.userData.name) || '';
const cn = (m.userData && m.userData.cn) || '';
if (h.some(k => nm.includes(k) || cn.includes(k))) {
m.visible = false;
if (m.chk) m.chk.checked = false;
}
});
}
requestRender();
})();