单文件约束取消后,Python 侧没必要再靠 16 处 sys.path.insert 维持扁平模块互认。
现在按职责分包:
arm/paths.py 路径唯一来源(含新增 CACHE、TPL)
arm/constants.py 实测接口数值(原 armcore 的 M/C/CLR/FIT/MOTOR_TOL 段)
arm/cad/{helpers,arm_build,part_tools,parts/} 几何与装配
arm/kinematics.py(原 human_layout.py,唯一运动学事实源)
arm/analysis/{sweep_v2,strength_check,torque_balance,finalize_travel,ws_sweep,gen_env_mesh}
arm/dataio/{screw_mesh,draw_sheet} arm/viewer/{build_viewer,shot}
arm/build_model.py(原 arm_model.py)
顺带修掉的既有缺陷(都是实测撞出来的,不是预防性改动):
- arm_model 第二次 sys.path 跳到 gitignore 的 scratch/ 去 import 构建必需的包络脚本:
ws_sweep/gen_env_mesh 提升进 arm/analysis/,缓存挪到 data/cache/(gitignore,可重生成)。
- finalize_travel / draw_sheet 顶层就是"算完写文件",一次 importlib 冒烟测试就把
docs/travel.json 重写了一遍;现在被 import 直接抛错。
- strength_check 用 OUT.replace("/out","/web") 靠字符串形状推路径,改成直接用 WEB。
- build_viewer 里 WORKSPACE 使用点漏改导致 NameError —— 已由构建关卡抓到并修掉。
os._exit 的处置与计划里的假设相反:实测光是 `python -c "import arm.cad.helpers"`
就会在解释器退出时段错误(exit 139,OCC 静态析构),全流程跑完也一样。所以入口
脚本尾部保留 flush + os._exit,并把原因写进注释;被 import 时不生效。
关卡(tests/):
- verify.py 一条命令跑三层:产物 md5、IK 数值 exact 段、截图。
- 截图基线重做并分级:同一构建连跑两轮实测 102 张逐字节可复现(要求 md5 相同),
3 张线束 x-ray 不可复现(mean<=0.0017、无像素差>8)改为与参考像素比容差;
这 3 张不再入库,免得每次跑关卡都脏 git。
- 新增 ?view=sheet / ?sheet=N / ?theme / ?guide=N 四个 URL 参数:工程图与装配向导
占了约一半 CSS 和 700 行 JS,原先没有任何截图覆盖,拆它们等于没有关卡。
- 关卡灵敏度已验:改坏 IK 基线一个数 -> FAIL;换旧版求解器 -> 5 处 exact 差异。
实测:python -m arm.build_model 退出码 0,全部产物 md5 与基线一致,
105 视图截图全过,IK exact 段逐字节一致。
77 lines
3.6 KiB
Python
77 lines
3.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Assemble the single-file viewer: three.js (inline, no CDN) + mesh data + 2D sheets."""
|
|
import io
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
try:
|
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
except Exception:
|
|
pass
|
|
from arm.paths import WEB, DOCS, REPO, TPL
|
|
|
|
import time
|
|
|
|
def rebuild_viewer():
|
|
print("=" * 80, flush=True)
|
|
print("【第五阶段 5/5】打包单文件三维交互查看器 (机器人手臂3D查看器.html)...", flush=True)
|
|
print("=" * 80, flush=True)
|
|
t0 = time.time()
|
|
print(" - 读取 Three.js 离线 3D 渲染内核...", end="", flush=True)
|
|
three = io.open(WEB + "/three_inline.js", encoding="utf-8").read()
|
|
assert len(three) > 400000, len(three)
|
|
print(f" 完成 ({len(three)/1024:.0f} KB) ✓", flush=True)
|
|
|
|
print(" - 读取几何网格与关节运动学数据...", end="", flush=True)
|
|
data = io.open(WEB + "/mesh_arm.json", encoding="utf-8").read()
|
|
sheets = io.open(WEB + "/sheets.jsdata", encoding="utf-8").read()
|
|
trv = io.open(DOCS + "/travel.json", encoding="utf-8").read() if os.path.exists(
|
|
DOCS + "/travel.json") else "[]"
|
|
col = io.open(DOCS + "/collide.json", encoding="utf-8").read() if os.path.exists(
|
|
DOCS + "/collide.json") else '{"caps":[],"parts":{}}'
|
|
screws = io.open(DOCS + "/screws.json", encoding="utf-8").read() if os.path.exists(
|
|
DOCS + "/screws.json") else "[]"
|
|
ik_solver = io.open(WEB + "/arm-ik-solver.js", encoding="utf-8").read()
|
|
ik_glue = io.open(WEB + "/ik-glue.js", encoding="utf-8").read()
|
|
tpl = io.open(TPL, encoding="utf-8").read()
|
|
print(f" 完成 ({len(data)/1024:.0f} KB) ✓", flush=True)
|
|
|
|
print(" - 打包生成全内置单文件 HTML...", end="", flush=True)
|
|
out = (tpl.replace("/*__THREE__*/", three)
|
|
.replace("/*__DATA__*/", data)
|
|
.replace("/*__SHEETS__*/", sheets)
|
|
.replace("/*__TRAVEL__*/", trv)
|
|
.replace("/*__COLLIDE__*/", col)
|
|
.replace("/*__SCREWS__*/", screws)
|
|
.replace("/*__IKSOLVER__*/", ik_solver)
|
|
.replace("/*__IKGLUE__*/", ik_glue))
|
|
assert "/*__" not in out, [l for l in out.split("\n") if "/*__" in l][:3]
|
|
dst = WEB + "/机器人手臂3D查看器.html"
|
|
# 【2026-10-06】三个副本逐个写、逐个报错,不要一把梭。
|
|
# 原来三行连写,任一份被浏览器独占(Windows 上打开中的 html 是独占锁)就抛异常,
|
|
# 而调用方 arm_model.py 把整个 rebuild_viewer() 包在 try/except 里,
|
|
# 于是只写成功第一份、另外两份静默停在旧版本 —— 表现为"代码改了但网页没变"。
|
|
# 实测就是这个坑:18:52 那次 dst 是新的,web/index.html 和根目录那份还停在 18:12。
|
|
ok, failed = True, []
|
|
for path in (dst, WEB + "/index.html", REPO + "/机器人手臂3D查看器.html"):
|
|
try:
|
|
io.open(path, "w", encoding="utf-8").write(out)
|
|
except Exception as e:
|
|
ok = False
|
|
failed.append("%s (%s)" % (path, e))
|
|
sz_mb = os.path.getsize(dst) / 1e6
|
|
print(f" 完成 ({sz_mb:.2f} MB, 耗时: {time.time()-t0:.2f}s) ✓", flush=True)
|
|
if failed:
|
|
print("!! 以下副本写入失败(多半是浏览器正打开着该文件,Windows 独占锁):", flush=True)
|
|
for f in failed:
|
|
print(" - %s" % f, flush=True)
|
|
print(" 关掉对应页面后重跑 python -m arm.viewer.build_viewer 即可。\n", flush=True)
|
|
else:
|
|
print(f"-> 3D查看器已更新至: {dst}、{WEB}/index.html 与 根目录/机器人手臂3D查看器.html\n",
|
|
flush=True)
|
|
|
|
if __name__ == "__main__":
|
|
rebuild_viewer()
|
|
|