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 穿透。
This commit is contained in:
cbb
2026-10-15 04:31:31 +08:00
parent 649bd69810
commit 531a293688
43 changed files with 1755 additions and 1397 deletions
+69
View File
@@ -0,0 +1,69 @@
/* 设计令牌、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%}
+28
View File
@@ -0,0 +1,28 @@
/* 右边缘可收起 BOM 抽屉 */
/* ---- edge BOM dock (collapsible drawer at the right edge) ---- */
#bomTab{position:fixed;right:0;top:64px;z-index:30;writing-mode:vertical-rl;text-orientation:mixed;
background:var(--pan2);color:var(--tx);border:1px solid var(--line);border-right:none;
border-radius:6px 0 0 6px;padding:12px 6px;font:inherit;cursor:pointer;letter-spacing:3px}
#bomTab:hover{border-color:var(--ac);color:#fff}
#bomTab b{color:var(--ac)}
#bomDock{position:fixed;right:0;top:46px;bottom:26px;width:min(430px,94vw);z-index:29;
transform:translateX(103%);transition:transform .22s ease;display:flex;flex-direction:column;
background:var(--pan);border-left:1px solid var(--ac);box-shadow:-10px 0 28px rgba(0,0,0,.5)}
#bomDock.open{transform:none}
#bomDock .dk-hd{display:flex;align-items:center;gap:8px;padding:9px 10px;flex:none;
border-bottom:1px solid var(--line)}
#bomDock .dk-hd b{font-size:13px}
#bomDock .dk-hd .q{color:var(--dim);font-size:11px;min-width:0;flex:0 1 auto;
overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#bomDock .dk-x{margin-left:auto}
#bomDock .dk-bd{overflow:auto;padding:8px 11px 16px;flex:1;min-height:0}
.dk-sec{margin:4px 0 5px;color:var(--ac);font-size:12px;letter-spacing:.4px}
.dk-note{color:var(--dim);font-size:11px;margin:1px 0 6px}
#screws td{white-space:normal}
#screws td:first-child{white-space:nowrap}
#screws tbody tr{cursor:pointer;transition:background .12s}
#screws tbody tr:hover{background:rgba(76,154,255,.14)}
#screws tbody tr.scrHl{background:#1d3f74;box-shadow:inset 3px 0 0 var(--warn)}
.hudlink{pointer-events:auto;cursor:pointer;color:var(--ac);text-decoration:underline;margin-left:8px}
@media (max-width:760px){#bomTab{top:auto;bottom:36px}#bomDock{top:46px;bottom:26px}}
+154
View File
@@ -0,0 +1,154 @@
/* 分步装配向导面板 */
/* 分步装配向导面板样式 */
#sheetGuideView {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: var(--bg);
display: flex;
flex-direction: column;
z-index: 15;
overflow: hidden;
}
.guide-header {
padding: 8px 14px;
background: var(--pan);
border-bottom: 1px solid var(--line);
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex: none;
}
.guide-steps-bar {
display: flex;
gap: 6px;
overflow-x: auto;
align-items: center;
flex: 1;
}
.guide-step-chip {
background: var(--pan2);
border: 1px solid var(--line);
border-radius: 20px;
padding: 4px 12px;
font-size: 11.5px;
color: var(--dim);
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 5px;
white-space: nowrap;
transition: all .15s;
}
.guide-step-chip:hover {
border-color: var(--warn);
color: var(--tx);
}
.guide-step-chip.active {
background: #78350f;
border-color: #f59e0b;
color: #fff;
font-weight: bold;
box-shadow: 0 0 10px rgba(245,158,11,.3);
}
.guide-body {
display: grid;
grid-template-columns: 360px 1fr;
flex: 1;
min-height: 0;
}
@media (max-width: 900px) {
.guide-body {
grid-template-columns: 1fr;
grid-template-rows: auto 1fr;
}
}
.guide-panel-left {
background: var(--pan);
border-right: 1px solid var(--line);
overflow-y: auto;
padding: 14px;
display: flex;
flex-direction: column;
gap: 12px;
}
.guide-panel-right {
position: relative;
overflow: hidden;
background: #0f1217;
display: flex;
align-items: center;
justify-content: center;
}
.guide-card {
background: var(--pan2);
border: 1px solid var(--line);
border-radius: 8px;
padding: 12px;
}
.guide-card-title {
font-size: 12px;
font-weight: bold;
color: var(--warn);
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 6px;
}
.guide-part-list {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.guide-part-badge {
background: rgba(255,255,255,.05);
border: 1px solid rgba(255,255,255,.1);
border-radius: 4px;
padding: 3px 7px;
font-size: 11px;
color: var(--tx);
cursor: pointer;
transition: all .15s;
}
.guide-part-badge:hover {
border-color: var(--ac);
background: rgba(76,154,255,.15);
color: #fff;
}
.guide-actions {
display: flex;
gap: 8px;
margin-top: auto;
padding-top: 10px;
}
.guide-btn {
flex: 1;
padding: 7px 0;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
transition: all .15s;
}
.guide-btn-nav {
background: var(--pan2);
border: 1px solid var(--line);
color: var(--tx);
}
.guide-btn-nav:hover {
border-color: var(--ac);
color: #fff;
}
.guide-btn-3d {
background: linear-gradient(135deg, #b45309, #d97706);
border: 1px solid #f59e0b;
color: #fff;
font-weight: bold;
}
.guide-btn-3d:hover {
background: linear-gradient(135deg, #d97706, #f59e0b);
}
small{color:var(--dim)}
+67
View File
@@ -0,0 +1,67 @@
/* IK 控制条与左栏结果面板 */
/* ===================== IK 控制条(两行:目标+姿态 / 模式+状态) ===================== */
#ikbar{grid-column:1/-1;display:flex;flex-direction:column;gap:4px;padding:5px 10px;
background:linear-gradient(180deg,#1c212b,#161922);border-bottom:1px solid var(--line)}
#ikbar .ikrow{display:flex;align-items:center;gap:6px;min-height:26px;
overflow-x:auto;overflow-y:hidden;white-space:nowrap}
#ikbar .ik-t{color:var(--ac);font-weight:700;letter-spacing:.8px;flex:none;font-size:11.5px;
border:1px solid var(--ac);border-radius:4px;padding:1px 6px}
#ikbar .lb{color:var(--dim);flex:none;font-size:12px}
#ikbar input[type=number]{width:68px;background:#0d1015;color:var(--tx);border:1px solid var(--line);
border-radius:4px;padding:3px 5px;font:inherit;font-variant-numeric:tabular-nums;flex:none}
#ikbar input[type=number]:focus{outline:none;border-color:var(--ac)}
#ikbar .vsep{width:1px;height:20px;background:var(--line);flex:none;margin:0 3px}
#ikbar .st{color:var(--dim);flex:none;font-size:11.5px;font-variant-numeric:tabular-nums}
#ikbar .sep{color:#3b4453;margin:0 5px}
#ikbar .hint{color:var(--dim);font-size:11px;flex:none}
#ikbar .good{color:var(--ok)} #ikbar .bad{color:#ff6b6b} #ikbar .hi{color:var(--warn)}
#ikSolve{background:#1d3f74;border-color:var(--ac);color:#fff;font-weight:600}
#ikSolve:hover{background:#24508f}
#ikPose.on{background:#3a2b63;border-color:#a06bff;color:#fff;font-weight:600}
#ikPoseBox{display:none;align-items:center;gap:6px}
#ikPoseBox input[type=number]{width:54px}
#ikMixBox{display:none;align-items:center;gap:6px;flex:none}
#ikMixBox input[type=range]{width:118px;flex:none;accent-color:var(--ac);cursor:pointer}
#ikMixBox .st{min-width:56px}
/* ===================== IK 结果面板(左栏顶部) ===================== */
#ikpanel{margin:0 0 10px;border:1px solid var(--line);border-radius:6px;background:#141821;padding:7px 8px}
/* 徽标必须可换行:CJK 的最小内容宽度=一个字,若徽标 nowrap+flex:none,
长状态文案会把标题压成竖排一字一行(实测 h3 缩到 12px) */
#ikpanel .ikh{display:flex;flex-wrap:wrap;align-items:center;gap:4px 6px;margin-bottom:5px}
#ikpanel .ikh h3{margin:0;font-size:12px;color:var(--ac);letter-spacing:.3px;
min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.badge{margin-left:auto;font-size:10.5px;padding:1px 7px;border-radius:9px;border:1px solid;
flex:0 1 auto;max-width:100%;text-align:center;line-height:1.35;text-wrap:balance}
.badge.wait{color:var(--dim);border-color:var(--line)}
.badge.goodb{color:#0c1a12;background:var(--ok);border-color:var(--ok)}
.badge.warnb{color:#241a06;background:var(--warn);border-color:var(--warn)}
.badge.badb{color:#2a0a0a;background:#ff6b6b;border-color:#ff6b6b}
#ikTab{font-size:11px}
#ikTab td.num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
#ikTab td.dim{color:var(--dim);white-space:nowrap}
#ikpanel .kv{margin-top:6px}
#ikpanel .kv i{white-space:nowrap}
#ikpanel .kv span,#ikpanel .kv b{overflow-wrap:anywhere;word-break:break-word;min-width:0}
#ikpanel .good{color:var(--ok)} #ikpanel .bad{color:#ff6b6b}
#ikpanel .kv .hi{color:var(--warn);font-variant-numeric:tabular-nums}
#ikAltN{font-size:10.5px;color:var(--dim);margin:7px 0 3px;line-height:1.45;white-space:normal}
.altrow{display:flex;align-items:center;gap:5px;padding:2px 4px;border-radius:4px;cursor:pointer;
font-size:11px;font-variant-numeric:tabular-nums;color:var(--dim)}
.altrow:hover{background:rgba(76,154,255,.16)}
.altrow .ai{color:#5b6478;width:10px;flex:none;text-align:right}
.altrow b{color:var(--ac);width:54px;flex:none;text-align:right;font-weight:500}
.altrow .aq{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#ikpanel details{margin-top:8px;border-top:1px solid var(--line);padding-top:6px}
#ikpanel details>summary{cursor:pointer;color:var(--ac);font-size:11.5px;outline:none}
#ikpanel details .doc{margin-top:5px;font-size:11px;color:var(--dim);line-height:1.7;white-space:normal;
-webkit-user-select:text;user-select:text}
#ikpanel details .doc b{color:var(--tx)}
#ikpanel details .doc code{color:var(--warn);font-family:Consolas,"Courier New",monospace;font-size:10.5px}
#ikpanel details .doc ol{margin:3px 0 3px 16px;padding:0}
#ikpanel details .doc li{margin:3px 0}
#ikpanel details .doc hr{border:none;border-top:1px solid var(--line);margin:6px 0}
#ikpanel .src{font-size:10.5px;color:var(--dim);margin-top:5px;white-space:normal;line-height:1.5}
#ikChkBox{margin-top:7px}
#ikChkBox summary{margin-bottom:2px}
+191
View File
@@ -0,0 +1,191 @@
/* 装配图工具栏、页签、主视口与画布 */
/* ==================== 交互式装配图查看器 (Interactive Assembly Drawing) ==================== */
#sheet {
display: none;
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
flex-direction: column;
background: #11141a;
overflow: hidden;
z-index: 10;
padding: 0;
}
body.sheet-mode #sheet {
display: flex !important;
}
/* 隐藏 3D 专属工具条按钮(当处于装配图时) */
body.sheet-mode #bAsm,
body.sheet-mode #bExp,
body.sheet-mode #exp,
body.sheet-mode #cam,
body.sheet-mode #bWire,
body.sheet-mode #bXRay,
body.sheet-mode #bScr,
body.sheet-mode #bHarn,
body.sheet-mode #bEnv,
body.sheet-mode #bFit {
display: none !important;
}
body.sheet-mode footer #footModel { display: none !important; }
body.sheet-mode footer #footSheet { display: inline !important; }
/* 顶部装配图工具栏 */
#sheetToolbar {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
background: var(--pan);
border-bottom: 1px solid var(--line);
flex: none;
min-height: 40px;
overflow-x: auto;
white-space: nowrap;
}
.sheet-tabs {
display: flex;
gap: 5px;
align-items: center;
}
.sheet-tab {
background: var(--pan2);
color: var(--tx);
border: 1px solid var(--line);
border-radius: 5px;
padding: 4px 10px;
font-size: 12px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 5px;
transition: all .15s ease;
}
.sheet-tab:hover {
border-color: var(--ac);
color: #fff;
}
.sheet-tab.on {
background: #1e3a66;
border-color: var(--ac);
color: #fff;
font-weight: 600;
box-shadow: 0 0 10px rgba(76,154,255,.25);
}
.sheet-tab.guide-tab {
background: rgba(245, 158, 11, 0.12);
border-color: rgba(245, 158, 11, 0.4);
color: #f59e0b;
}
.sheet-tab.guide-tab.on {
background: #78350f;
border-color: #f59e0b;
color: #fef3c7;
box-shadow: 0 0 10px rgba(245, 158, 11, 0.35);
}
.sheet-vsep {
width: 1px;
height: 20px;
background: var(--line);
margin: 0 4px;
flex: none;
}
.sheet-btn-group {
display: flex;
border: 1px solid var(--line);
border-radius: 5px;
overflow: hidden;
flex: none;
}
.sheet-btn-group .sheet-btn {
border: none;
border-radius: 0;
padding: 3px 8px;
font-size: 11.5px;
background: var(--pan2);
border-right: 1px solid var(--line);
cursor: pointer;
}
.sheet-btn-group .sheet-btn:last-child {
border-right: none;
}
.sheet-btn-group .sheet-btn.on {
background: var(--ac);
color: #fff;
font-weight: 600;
}
.sheet-layers {
display: flex;
gap: 8px;
align-items: center;
font-size: 11.5px;
color: var(--dim);
}
.sheet-layers label {
display: inline-flex;
align-items: center;
gap: 3px;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
}
.sheet-layers input[type=checkbox] {
accent-color: var(--ac);
cursor: pointer;
}
.sheet-space {
flex: 1;
min-width: 8px;
}
.sheet-btn-accent {
background: linear-gradient(135deg, #1d4ed8, #2563eb);
color: #fff;
border: 1px solid #60a5fa;
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 5px;
padding: 4px 10px;
border-radius: 5px;
cursor: pointer;
transition: all .15s;
}
.sheet-btn-accent:hover {
background: linear-gradient(135deg, #2563eb, #3b82f6);
box-shadow: 0 0 12px rgba(96,165,250,.4);
}
/* 主视口区域 */
#sheetViewport {
position: relative;
flex: 1;
min-height: 0;
overflow: hidden;
cursor: grab;
background: #0f1217;
transition: background 0.2s;
}
#sheetViewport.is-dragging {
cursor: grabbing !important;
}
/* 画布与缩放平移 */
#sheetCanvas {
position: absolute;
top: 0; left: 0;
transform-origin: 0 0;
will-change: transform;
}
#sheetSvgWrapper {
display: inline-block;
vertical-align: top;
box-shadow: 0 8px 32px rgba(0,0,0,.5);
border-radius: 6px;
overflow: hidden;
transition: box-shadow 0.2s;
}
#sheetSvgWrapper svg {
display: block;
border-radius: 6px;
}
+128
View File
@@ -0,0 +1,128 @@
/* 悬停 Tooltip 与 SVG 零件高亮 */
/* 悬停 Tooltip HUD */
.sheet-tooltip {
position: absolute;
z-index: 99;
background: rgba(15, 17, 21, 0.94);
backdrop-filter: blur(10px);
border: 1px solid var(--ac);
border-radius: 7px;
padding: 8px 12px;
font-size: 12px;
color: var(--tx);
box-shadow: 0 10px 28px rgba(0,0,0,.6);
pointer-events: none;
display: none;
max-width: 310px;
line-height: 1.5;
}
.sheet-tooltip.pinned {
pointer-events: auto;
border-color: #38bdf8;
box-shadow: 0 12px 36px rgba(0, 168, 255, 0.35);
}
.sheet-tooltip .st-title {
font-size: 13px;
font-weight: bold;
color: #fff;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 4px;
padding-bottom: 4px;
border-bottom: 1px solid rgba(255,255,255,.1);
}
.sheet-tooltip .st-tag {
font-size: 10px;
padding: 1px 5px;
border-radius: 3px;
font-weight: normal;
}
.st-tag.pr { background: rgba(61,220,151,.2); color: var(--ok); }
.st-tag.ex { background: rgba(255,180,84,.2); color: var(--warn); }
.sheet-tooltip .st-row {
display: flex;
justify-content: space-between;
gap: 12px;
font-size: 11px;
color: var(--dim);
margin: 2px 0;
}
.sheet-tooltip .st-row span:last-child {
color: var(--tx);
text-align: right;
}
.sheet-tooltip .st-btn {
margin-top: 6px;
width: 100%;
padding: 4px 0;
font-size: 11px;
background: #1d4ed8;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
transition: background .15s;
}
.sheet-tooltip .st-btn:hover {
background: #2563eb;
}
/* SVG 交互高亮样式 */
.sheet-part-path {
cursor: pointer;
transition: stroke .12s, stroke-width .12s, filter .12s;
}
.sheet-part-path.is-hovered {
stroke: #00d2ff !important;
stroke-width: 2.8px !important;
fill: rgba(0, 210, 255, 0.16) !important;
filter: drop-shadow(0 0 5px #00a8ff) !important;
}
.sheet-balloon {
cursor: pointer;
transition: transform .15s ease;
}
.sheet-balloon:hover .balloon-circle,
.sheet-balloon.is-hovered .balloon-circle {
fill: #0284c7 !important;
stroke: #ffffff !important;
stroke-width: 2px !important;
filter: drop-shadow(0 0 6px rgba(2,132,199,.8)) !important;
}
.sheet-balloon:hover .balloon-text,
.sheet-balloon.is-hovered .balloon-text {
fill: #ffffff !important;
font-weight: bold !important;
}
.sheet-leader.is-hovered {
stroke: #00d2ff !important;
stroke-width: 1.8px !important;
}
.sheet-legend-row {
cursor: pointer;
}
.sheet-legend-row:hover .legend-row-bg,
.sheet-legend-row.is-hovered .legend-row-bg {
fill: rgba(2, 132, 199, 0.3) !important;
}
.sheet-legend-row:hover text,
.sheet-legend-row.is-hovered text {
fill: #00d2ff !important;
font-weight: bold !important;
}
.joint-geom-part {
cursor: pointer;
transition: all .15s;
}
.joint-geom-part:hover, .joint-geom-part.is-hovered {
stroke: #00d2ff !important;
stroke-width: 2.2px !important;
fill: rgba(0, 210, 255, 0.15) !important;
}
+149
View File
@@ -0,0 +1,149 @@
/* 图纸主题(白图/蓝图/深色)、浮动工具条、缩放徽标、信息条 */
/* 视觉风格主题 */
#sheet.theme-paper #sheetViewport {
background: #cbd5e1;
}
#sheet.theme-paper #sheetSvgWrapper {
box-shadow: 0 10px 40px rgba(0,0,0,.35);
}
#sheet.theme-blueprint #sheetViewport {
background: #06182c;
background-image:
linear-gradient(rgba(14, 116, 144, 0.15) 1px, transparent 1px),
linear-gradient(90deg, rgba(14, 116, 144, 0.15) 1px, transparent 1px);
background-size: 24px 24px;
}
#sheet.theme-blueprint #sheetSvgWrapper {
box-shadow: 0 0 40px rgba(14, 165, 233, 0.25);
border: 1px solid rgba(56, 189, 248, 0.3);
}
#sheet.theme-blueprint .sheet-bg {
fill: #0a2240 !important;
}
#sheet.theme-blueprint .sheet-border-outer,
#sheet.theme-blueprint .sheet-border-inner,
#sheet.theme-blueprint .sheet-title-line,
#sheet.theme-blueprint .sheet-meta-line {
stroke: #38bdf8 !important;
}
#sheet.theme-blueprint .sheet-title-text {
fill: #f0f9ff !important;
}
#sheet.theme-blueprint .sheet-note-text,
#sheet.theme-blueprint .sheet-scale-text,
#sheet.theme-blueprint .sheet-ver-text {
fill: #bae6fd !important;
}
#sheet.theme-blueprint .sheet-axis,
#sheet.theme-blueprint .sheet-axis-label,
#sheet.theme-blueprint .sheet-dim-line,
#sheet.theme-blueprint .sheet-dim-ext,
#sheet.theme-blueprint .sheet-dim-text {
stroke: #7dd3fc !important;
fill: #7dd3fc !important;
}
#sheet.theme-dark #sheetViewport {
background: #090b0e;
}
#sheet.theme-dark .sheet-bg {
fill: #14171d !important;
}
#sheet.theme-dark .sheet-border-outer,
#sheet.theme-dark .sheet-border-inner,
#sheet.theme-dark .sheet-title-line,
#sheet.theme-dark .sheet-meta-line {
stroke: #374151 !important;
}
#sheet.theme-dark .sheet-title-text {
fill: #f3f4f6 !important;
}
#sheet.theme-dark .sheet-note-text,
#sheet.theme-dark .sheet-scale-text,
#sheet.theme-dark .sheet-ver-text {
fill: #9ca3af !important;
}
/* 浮动工具条 */
#sheetFloatBar {
position: absolute;
right: 14px;
top: 14px;
display: flex;
flex-direction: column;
gap: 5px;
z-index: 25;
}
.sfb-btn {
background: rgba(23, 26, 33, 0.88);
backdrop-filter: blur(8px);
color: var(--tx);
border: 1px solid rgba(255,255,255,.12);
border-radius: 6px;
padding: 6px 10px;
font-size: 11.5px;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0,0,0,.35);
display: flex;
align-items: center;
justify-content: center;
min-width: 36px;
transition: all .15s ease;
}
.sfb-btn:hover {
background: #1e3a66;
border-color: var(--ac);
color: #fff;
transform: translateY(-1px);
}
/* 缩放指示徽标 */
#sheetZoomBadge {
position: absolute;
right: 14px;
bottom: 12px;
background: rgba(15, 17, 21, 0.82);
backdrop-filter: blur(6px);
border: 1px solid var(--line);
color: var(--dim);
font-size: 11px;
padding: 3px 8px;
border-radius: 4px;
font-variant-numeric: tabular-nums;
cursor: pointer;
z-index: 20;
}
#sheetZoomBadge:hover {
color: var(--ac);
border-color: var(--ac);
}
/* 信息条 */
#sheetInfoBar {
position: absolute;
left: 14px;
bottom: 12px;
max-width: 70%;
background: rgba(15, 17, 21, 0.85);
backdrop-filter: blur(6px);
border: 1px solid var(--line);
border-radius: 5px;
padding: 4px 10px;
font-size: 11.5px;
color: var(--dim);
z-index: 20;
display: flex;
align-items: center;
gap: 6px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
#sheetInfoBar b {
color: var(--tx);
}
.sheet-info-sep {
color: var(--line);
}
+33
View File
@@ -0,0 +1,33 @@
/* 视图模式切换时的布局与显隐规则 */
/* ============ 视图模式:三维模型(静态) / 装配图 / 运动模拟 ============ */
/* 三维模型 与 装配图 下,全部运动学 UI 一律隐藏 */
body:not(.motion) #ikbar,
body:not(.motion) #ikpanel,
body:not(.motion) #joints,
body:not(.motion) #jointsHead,
body:not(.motion) #footIk{display:none!important}
/* 运动模拟只看运动:左栏去掉零件树与打印清单(其它模式保留) */
body.motion #treeBlock{display:none!important}
/* 运动模拟左栏紧凑化:目标一屏放下、不下拉(实测原需下拉 127px) */
#ikpanel{margin:0 0 6px;padding:5px 8px}
#ikpanel .ikh{margin-bottom:3px}
#ikpanel #ikTab th,#ikpanel #ikTab td{padding:1px 4px}
#ikAltN{margin:5px 0 2px}
.altrow{padding:1px 4px}
#joints .j-box{margin-bottom:4px}
#joints input[type=range]{height:15px}
/* 显式行定位:IK 控制条在「三维模型/装配图」下被移除后,
若不固定行号,网格自动排布会把页脚挤到 1fr 行并压扁(已实测) */
#app>header{grid-row:1}
#ikbar{grid-row:2}
#left{grid-row:3}
#stage{grid-row:3}
#app>footer{grid-row:4}
/* BOM 抽屉:运动模拟多一条两行 IK 控制条(实测 75px),需整体下移 */
#bomDock{top:46px} #bomTab{top:64px}
body.motion #bomDock{top:124px} body.motion #bomTab{top:140px}
@media (max-width:760px){
#bomTab,body.motion #bomTab{top:auto;bottom:36px}
#bomDock,body.motion #bomDock{top:86px;bottom:26px}
}