Files
Robot_Arm/webapp/styles/base.css
T
cbb 531a293688 refactor(viewer): 单文件查看器拆成 webapp/ 多文件,走本地 http;Worker 换成真文件
前端不再是"一个 16MB HTML + 一个 2955 行模板"。现在:
  webapp/index.html      343 行 DOM 骨架,零内联数据
  webapp/styles/*.css    811 行 CSS 按段切成 8 个文件
  webapp/js/app.js       原内联主脚本逐字搬出(仍是普通脚本,S5 才改 ESM)
  webapp/js/ik-solver.js / ik-glue.js / vendor/three.min.js(原 web/ 三个文件)
  webapp/workers/ik.worker.js  真 Worker 文件
  webapp/generated/00_data.js  生成物(网格/工程图/行程/碰撞/螺钉)
  dist/机器人手臂3D查看器.html  分发用单文件,由 build_viewer 内联上面这些文件产出

入口:python -m arm.viewer.serve 后开 http://127.0.0.1:8123/
(serve 带 no-store,改完刷新即新构建,不再需要 Ctrl+F5;显式 MIME,
 .js 被 Windows 注册表判成 text/plain 会让模块静默不执行)

删掉 src/viewer_tpl.html 与 web/ 目录(旧产物移入 backups/pre_split_20261015/)。
工程图不再维护第二份 sheets.jsdata —— 直接读 draw_sheet 写出的 4 个真 SVG。

实测中修掉的问题:
- serve 的 / 原先"内部映射"到 webapp/index.html,导致文档基址是 /、相对路径的
  8 个 CSS 与 4 个 JS 全部 404(整页无样式无脚本)。改成 302 跳转。
- file:// 下 new Worker('workers/…') 构造阶段不抛错、错误以异步 onerror 到达,
  所以"构造失败才试 Blob 兜底"永远不会触发;改为按协议选路。两条路径都已实测
  backend=worker(http 走文件、file:// 走 Blob)。
- Worker 握手 3s 在 headless 并发下会误判"没起来"(截图里红徽标即此),放宽到 10s,
  并把后端状态暴露成 window.__IK_BACKEND / __IK_BACKEND_LOG 供测试直读。
- 截图竞态:4 并发 × 每页 14MB 数据时,虚拟时间预算会在页面加载完成前到期,
  拍到半初始化画面(且能"稳定地坏",旧基线里 m9_motion 就是这么混进来的)。
  并发降到 2、预算 40s、serve 加 HTTP/1.1+大缓冲+字节缓存,并用"同一构建两轮"
  重新界定稳定性:99 张逐字节一致、6 张属噪声改走像素容差(mean<=0.02、单像素<=32)。
- 关卡自身两处假通过已修:node 输出被 GBK 解码炸掉时不再算 PASS;
  线束干涉测试读不到通道源码时会 exit 2,而不是"零通道=零穿透=通过"。

关卡结果:python -m arm.build_model 退出码 0;10 项数据哈希与基线一致;
IK exact 段逐字节一致;99 张截图逐字节一致 + 6 张在噪声阈内;线束干涉测试 1800 点 0 穿透。
2026-10-15 04:31:31 +08:00

70 lines
4.5 KiB
CSS

/* 设计令牌、reset、整体栅格与左栏/树/页脚 */
:root{--bg:#0f1115;--pan:#171a21;--pan2:#1f242e;--tx:#e6e8ee;--dim:#8b93a7;--ac:#4c9aff;--ok:#3ddc97;--warn:#ffb454;--line:#2b323e}
*{box-sizing:border-box}
html,body{margin:0;height:100%;background:var(--bg);color:var(--tx);
font:13px/1.55 "Microsoft YaHei","Segoe UI",system-ui,sans-serif;overflow:hidden;
-webkit-user-select:none;user-select:none}
#app{display:grid;grid-template-columns:322px minmax(430px,1fr);grid-template-rows:46px auto 1fr 26px;
height:100%;min-width:0;overflow-x:auto}
header{grid-column:1/-1;display:flex;align-items:center;gap:8px;padding:0 12px;flex-wrap:nowrap;
overflow-x:auto;overflow-y:hidden;background:var(--pan);border-bottom:1px solid var(--line)}
header .q{white-space:nowrap;flex:none;color:var(--dim);font-size:12px}
header b{font-size:14px;white-space:nowrap;flex:none}
header .sp{flex:1 1 auto;min-width:6px}
button,select{background:var(--pan2);color:var(--tx);border:1px solid var(--line);
border-radius:5px;padding:4px 9px;font:inherit;cursor:pointer;white-space:nowrap;flex:none}
button:hover{border-color:var(--ac);color:#fff}
button.on{background:#22406b;border-color:var(--ac)}
#bHarn.on{background:#5c3b1e;border-color:#f59e0b;color:#fbbf24}
aside{background:var(--pan);overflow:auto;padding:8px;min-width:0}
#left{border-right:1px solid var(--line)}
#app.l{grid-template-columns:322px minmax(430px,1fr)}
#stage{position:relative;overflow:hidden;min-width:0;
background:radial-gradient(120% 120% at 50% 0%,#1b2028 0%,#0d0f13 70%)}
canvas{display:block;width:100%;height:100%}
footer{grid-column:1/-1;background:var(--pan);border-top:1px solid var(--line);
display:flex;align-items:center;gap:14px;padding:0 12px;color:var(--dim);font-size:12px;
white-space:nowrap;overflow:hidden}
@media (max-width:1150px){
#app{grid-template-columns:300px minmax(400px,1fr)}
}
@media (max-width:760px){
#app{grid-template-columns:minmax(0,1fr)}
#left{display:none}
#app.l #left{display:block}
}
h3{font-size:12px;margin:10px 0 6px;color:var(--ac);letter-spacing:.4px;-webkit-user-select:none;user-select:none}
.row{display:flex;align-items:center;gap:6px;margin:3px 0}
.row label{flex:1;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-user-select:none;user-select:none}
.row .q{color:var(--dim);font-size:11px;-webkit-user-select:none;user-select:none}
.chip{display:inline-block;width:9px;height:9px;border-radius:2px;margin-right:5px;flex:none}
.pr{color:var(--ok)}.ex{color:var(--warn)}.mo{color:#9aa4b8}
table{width:100%;border-collapse:collapse;font-size:11.5px;-webkit-user-select:text;user-select:text}
th,td{border-bottom:1px solid var(--line);padding:3px 4px;text-align:left;vertical-align:top}
th{color:var(--dim);font-weight:400;position:sticky;top:0;background:var(--pan);-webkit-user-select:none;user-select:none}
.j-box{margin-bottom:7px;padding:3px 4px 1px;border-radius:4px;transition:background .15s}
.j-box:hover{background:rgba(255,255,255,.03)}
.j-box label{cursor:default;font-weight:500}
.j-val{font-size:11px;color:var(--ac);cursor:pointer;padding:1px 5px;border-radius:3px;
background:rgba(76,154,255,.1);font-variant-numeric:tabular-nums;transition:all .15s;
-webkit-user-select:none;user-select:none}
.j-val:hover{background:var(--ac);color:#fff}
input[type=range]{width:100%;height:18px;accent-color:var(--ac);cursor:pointer;display:block;margin:1px 0}
label em{font-style:normal;color:var(--mu);font-size:10.5px;margin-left:4px}
label em.ok{color:#5ddc90}
.gr{font-size:11px;color:var(--dim);margin:2px 0 4px;-webkit-user-select:none;user-select:none}
#hud{position:absolute;left:10px;top:8px;pointer-events:none;font-size:11.5px;color:var(--dim);
background:rgba(15,17,21,.72);border:1px solid var(--line);border-radius:6px;padding:6px 9px;max-width:60%}
#hud b{color:var(--tx)}
#hud:empty{display:none}
#note{position:absolute;right:10px;bottom:10px;max-width:330px;background:rgba(15,17,21,.88);
border:1px solid var(--ac);border-radius:7px;padding:8px 10px;font-size:12px;display:none;
-webkit-user-select:text;user-select:text}
.kv{display:grid;grid-template-columns:auto 1fr;gap:2px 8px;font-size:11.5px;-webkit-user-select:text;user-select:text}
.kv i{color:var(--dim);font-style:normal;-webkit-user-select:none;user-select:none}
.tab{padding:4px 9px;-webkit-user-select:none;user-select:none}
.bad{color:#ff6b6b}.good{color:var(--ok)}
svg{display:block;width:100%;height:auto;background:#fff;border-radius:6px}
#sheet{display:none;padding:10px;overflow:auto;height:100%}