refactor(layout): src/ 收成顶层包 arm/,入口统一 python -m;提升承担构建的 scratch 脚本
单文件约束取消后,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 段逐字节一致。
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""查看器侧:数据打包、本地静态服务、headless 截图。
|
||||
"""
|
||||
@@ -0,0 +1,76 @@
|
||||
# -*- 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()
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Headless Edge screenshots of the viewer, for the visual self-check loop."""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import shutil
|
||||
import glob
|
||||
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
from arm.paths import WEB, DOCS
|
||||
SHOTS = DOCS + "/shots"
|
||||
HTML = WEB + "/机器人手臂3D查看器.html"
|
||||
CAND = [r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
|
||||
r"C:\Program Files\Microsoft\Edge\Application\msedge.exe"]
|
||||
EDGE = next((c for c in CAND if os.path.exists(c)), None)
|
||||
if not EDGE:
|
||||
EDGE = glob.glob(r"C:\Users\*\AppData\Local\Microsoft\Edge\*\msedge.exe")
|
||||
EDGE = EDGE[0] if EDGE else None
|
||||
if not EDGE:
|
||||
raise SystemExit("msedge not found")
|
||||
|
||||
VIEWS = {
|
||||
"01_iso": "?cam=iso",
|
||||
"zoom_user": "?cam=iso&az=-0.9&po=1.15&rd=470&tx=30&ty=320&tz=-80&xray=0",
|
||||
"user_harness_view": "?az=-1.85&po=1.32&rd=430&tx=0&ty=330&tz=-115&xray=0",
|
||||
"user_harness_zoom": "?az=-1.85&po=1.32&rd=280&tx=-5&ty=360&tz=-115&xray=0",
|
||||
"user_m1_rear": "?az=-1.25&po=1.35&rd=350&tx=-70&ty=570&tz=-128&xray=0",
|
||||
"user_m1_exact": "?az=-1.5708&po=1.5708&rd=260&tx=0&ty=610&tz=-128&xray=0",
|
||||
"user_m1_bypass_oblique": "?az=-1.0&po=1.15&rd=300&tx=-40&ty=580&tz=-140&xray=0",
|
||||
"user_smooth_harness": "?az=1.50&po=1.45&rd=340&tx=-15&ty=560&tz=-128&xray=0",
|
||||
"user_contour_harness": "?az=1.5708&po=1.50&rd=360&tx=-15&ty=320&tz=-128&xray=0",
|
||||
"02_front": "?cam=front",
|
||||
|
||||
# 工程图 / 装配向导 / 运动模拟三种模式。URL 参数(?view=&sheet=&theme=&guide=)就是
|
||||
# 为这几条截图加的:不把它们纳入基线,拆 CSS 与那 700 行工程图 JS 时等于没有关卡。
|
||||
"m1_sheet_front": "?view=sheet",
|
||||
"m2_sheet_joint": "?view=sheet&sheet=1",
|
||||
"m3_sheet_exploded": "?view=sheet&sheet=2",
|
||||
"m4_sheet_side": "?view=sheet&sheet=3",
|
||||
"m5_guide_step1": "?view=sheet&sheet=guide&guide=0",
|
||||
"m6_guide_step5": "?view=sheet&sheet=guide&guide=4",
|
||||
"m7_sheet_blueprint": "?view=sheet&theme=blueprint",
|
||||
"m8_sheet_dark": "?view=sheet&theme=dark",
|
||||
"m9_motion": "?view=motion",
|
||||
"03_side": "?cam=side",
|
||||
"04_top": "?cam=top",
|
||||
"05_exploded": "?cam=iso&expl=100",
|
||||
"06_wrist": "?cam=wrist&xray=0",
|
||||
"07_joint": "?cam=joint",
|
||||
"08_pose": "?cam=iso&j2=35&j3=60&j4=-25&j1=25",
|
||||
"09_sheet": "?sheet=1",
|
||||
"10_solid": "?cam=iso&xray=0",
|
||||
"11_tool_flange": "?cam=wrist&xray=0&j6=45",
|
||||
"12_elbow_flare": "?cam=elbow&xray=0",
|
||||
"13_forearm_side": "?cam=side&xray=0&rd=340&ty=245&tz=-135",
|
||||
"user_target": "?cam=side&az=-1.5708&po=1.57&xray=0&rd=340&tx=100&ty=245&tz=-135",
|
||||
"view_yz_pos_x": "?cam=side&az=0&po=1.57&xray=0&rd=340&tx=100&ty=245&tz=-135",
|
||||
"view_yz_neg_x": "?cam=side&az=3.14159&po=1.57&xray=0&rd=340&tx=100&ty=245&tz=-135",
|
||||
"view_iso_forearm": "?cam=side&az=-0.65&po=1.2&xray=0&rd=320&tx=100&ty=245&tz=-135",
|
||||
"user_view1": "?cam=wrist&az=0.55&po=1.4&rd=310&xray=0",
|
||||
"user_view2": "?cam=wrist&az=0.2&po=1.4&rd=290&xray=0",
|
||||
"user_p04": "?cam=joint&az=-1.5708&po=1.57&rd=380&xray=0",
|
||||
"user_p04_opp": "?cam=joint&az=1.5708&po=1.57&rd=380&xray=0",
|
||||
"user_elbow_rear": "?az=1.5708&po=1.5708&rd=280&tx=95&ty=315&tz=-128&xray=0",
|
||||
"user_p07_wrist": "?az=1.5708&po=1.5708&rd=240&tx=95&ty=235&tz=-128&xray=0",
|
||||
"user_elbow_front": "?az=-1.5708&po=1.5708&rd=250&tx=18.5&ty=310&tz=-128&xray=0",
|
||||
"user_p06_xz": "?az=1.5708&po=1.5708&rd=240&tx=18.5&ty=350&tz=-128&xray=0&hide=胸板,底脚,肩吊臂",
|
||||
"user_p06_yz": "?az=0&po=1.5708&rd=260&tx=18.5&ty=350&tz=-110&xray=0",
|
||||
"user_p06_iso": "?az=-0.65&po=1.25&rd=260&tx=-15&ty=350&tz=-128&xray=0",
|
||||
"user_p07_creases": "?az=-0.65&po=1.25&rd=260&tx=30&ty=270&tz=-130&xray=0",
|
||||
# 电机腔内壁(隐藏全部电机,从腔内看 R35/R30.15 两段同心圆弧是否连续)
|
||||
"bay_p06_inside": "?az=1.5708&po=1.5708&rd=250&tx=18.5&ty=345&tz=-128&xray=0&hide=J1,J2,J3,J4,J5,J6",
|
||||
"bay_p07_inside": "?az=1.5708&po=1.5708&rd=240&tx=18.5&ty=235&tz=-128&xray=0&hide=J1,J2,J3,J4,J5,J6",
|
||||
"bay_p08_inside": "?az=1.5708&po=1.5708&rd=210&tx=18.5&ty=175&tz=-128&xray=0&hide=J1,J2,J3,J4,J5,J6",
|
||||
"user_p04_side": "?az=1.5708&po=1.5708&rd=300&tx=18.5&ty=520&tz=-128&xray=0&hide=胸板,底脚,肩吊臂",
|
||||
"user_p04_bottom": "?az=0.5&po=2.0&rd=240&tx=18.5&ty=490&tz=-128&xray=0&hide=胸板,底脚,肩吊臂",
|
||||
"user_p04_bottom2": "?az=1.2&po=2.0&rd=260&tx=18.5&ty=490&tz=-128&xray=0&hide=胸板,底脚,肩吊臂",
|
||||
"user_shoulder_solid": "?cam=joint&az=2.1&po=1.35&rd=380&xray=0",
|
||||
"user_shoulder_xray": "?cam=joint&az=2.1&po=1.35&rd=380",
|
||||
"user_shoulder_top": "?cam=joint&az=-0.6&po=0.6&rd=360&xray=0",
|
||||
# P03 轻量化桁架版(孤立视图:只留 P03,隐藏其余零件/电机/螺钉/包络)
|
||||
"p03_light_iso": "?az=0.62&po=1.15&rd=300&tx=-70&ty=548&tz=-128&xray=0&hide=P01,P02,P04,P05,P06,P07,P08,P09,M1,M2,M3,M4,M5,M6,S0,S1,ENV",
|
||||
"p03_light_side": "?az=1.5708&po=1.5708&rd=320&tx=-70&ty=548&tz=-128&xray=0&hide=P01,P02,P04,P05,P06,P07,P08,P09,M1,M2,M3,M4,M5,M6,S0,S1,ENV",
|
||||
"p03_light_rear": "?az=0.0&po=1.45&rd=300&tx=-70&ty=548&tz=-128&xray=0&hide=P01,P02,P04,P05,P06,P07,P08,P09,M1,M2,M3,M4,M5,M6,S0,S1,ENV",
|
||||
"p03_light_join": "?az=0.62&po=1.15&rd=420&tx=18.5&ty=520&tz=-128&xray=0",
|
||||
"p03_light_xray": "?az=0.62&po=1.15&rd=300&tx=-70&ty=548&tz=-128&xray=1&hide=P01,P02,P04,P05,P06,P07,P08,P09,M1,M2,M3,M4,M5,M6,S0,S1,ENV",
|
||||
# 减重版(2026-10-02):P04 立柱槽 / P06 过渡块窗 / P07 顶板槽 的孤立视图
|
||||
# 俯视看朝上敞开的槽口,斜视看槽壁与留下来的抗弯腹板
|
||||
"hollow_p04_top": "?az=0.0&po=0.05&rd=250&tx=18.5&ty=585&tz=-128&xray=0&hide=P01,P02,P03,P05,P06,P07,P08,P09,M1,M2,M3,M4,M5,M6,S0,S1,ENV",
|
||||
"hollow_p04_iso": "?az=0.7&po=1.05&rd=280&tx=18.5&ty=585&tz=-128&xray=0&hide=P01,P02,P03,P05,P06,P07,P08,P09,M1,M2,M3,M4,M5,M6,S0,S1,ENV",
|
||||
"hollow_p07_top": "?az=0.0&po=0.05&rd=230&tx=18.5&ty=270&tz=-128&xray=0&hide=P01,P02,P03,P04,P05,P06,P08,P09,M1,M2,M3,M4,M5,M6,S0,S1,ENV",
|
||||
"hollow_p07_iso": "?az=0.7&po=1.05&rd=260&tx=18.5&ty=275&tz=-128&xray=0&hide=P01,P02,P03,P04,P05,P06,P08,P09,M1,M2,M3,M4,M5,M6,S0,S1,ENV",
|
||||
"hollow_p06_iso": "?az=0.7&po=1.05&rd=190&tx=18.5&ty=368&tz=-128&xray=0&hide=P01,P02,P03,P04,P05,P07,P08,P09,M1,M2,M3,M4,M5,M6,S0,S1,ENV",
|
||||
# P03↔立柱 安装接口(新版 4xM5 从腔体上方拧入):俯视看螺钉落位
|
||||
"p03_mount_top": "?az=0.9&po=1.30&rd=190&tx=-115&ty=476&tz=-128&xray=0&hide=P02,P04,P05,P06,P07,P08,P09,M1,M2,M3,M4,M5,M6,ENV",
|
||||
"p03_mount_iso": "?az=2.2&po=0.95&rd=230&tx=-115&ty=486&tz=-128&xray=0&hide=P02,P04,P05,P06,P07,P08,P09,M1,M2,M3,M4,M5,M6,ENV",
|
||||
"p03_mount_loose": "?az=0.9&po=0.42&rd=260&tx=-115&ty=500&tz=-128&xray=0&expl=60&hide=P02,P04,P05,P06,P07,P08,P09,M1,M2,M3,M4,M5,M6,ENV",
|
||||
"j3_前端压盖_装配": "?az=0.7&po=2.72&rd=190&tx=19.5&ty=396&tz=-128&xray=0&hide=立柱,底座,肩关节安装悬臂,肩叉,大臂骨架,小臂骨架,腕部转接座,末端工具法兰,M1,M2,M4,M5,M6,包络经纬网",
|
||||
"j3_前端压盖_全件": "?az=0.7&po=2.6&rd=300&tx=19.5&ty=420&tz=-128&xray=0",
|
||||
# 承压面下沉 2mm 留下的"悬空凸台"(2026-10-04 整改):沿电机轴线从尾端面方向看,
|
||||
# 四个视图分别对应 P04/P05/P06/P07 的后端。ax/po 沿轴向,rd 拉近到 R35 座圈。
|
||||
# 沿电机轴线从尾端面看过去:承压面(R35圆面) + 4xS02 沉孔,凸台消失后应是干净圆面
|
||||
"boss_p04": "?az=0.55&po=1.30&rd=150&tx=19.5&ty=490&tz=-158&xray=0&hide=P01,P02,P03,P05,P06,P07,P08,P09,M3,M4,M5,M6,ENV",
|
||||
"user_fork_fixed": "?az=0.55&po=1.30&rd=150&tx=19.5&ty=490&tz=-158&xray=0&hide=P01,P02,P03,P06,P07,P08,P09,M1,M4,M5,M6,ENV,S0,S1",
|
||||
"bay_p04_inside": "?az=0.55&po=2.10&rd=150&tx=19.5&ty=490&tz=-158&xray=0&hide=P01,P02,P03,P05,P06,P07,P08,P09,M1,M2,M3,M4,M5,M6,ENV,S0,S1",
|
||||
"bay_p06_inside": "?az=0.55&po=2.10&rd=150&tx=19.5&ty=332&tz=-158&xray=0&hide=P01,P02,P03,P04,P05,P07,P08,P09,M1,M2,M3,M4,M5,M6,ENV,S0,S1",
|
||||
"bay_p07_inside": "?az=0.55&po=2.10&rd=150&tx=19.5&ty=227&tz=-158&xray=0&hide=P01,P02,P03,P04,P05,P06,P08,P09,M1,M2,M3,M4,M5,M6,ENV,S0,S1",
|
||||
"p04_both_ends": "?az=1.5708&po=1.5708&rd=200&tx=19.5&ty=490&tz=-128&xray=0&hide=P01,P02,P03,P05,P06,P07,P08,P09,M1,M3,M4,M5,M6,ENV,S0,S1",
|
||||
"p04_m2_side": "?az=1.2&po=1.9&rd=240&tx=19.5&ty=490&tz=-128&xray=0&hide=P01,P02,P03,P05,P06,P07,P08,P09,M1,M3,M4,M5,M6,ENV,S0,S1",
|
||||
"p06_m4_side": "?az=1.2&po=1.9&rd=240&tx=19.5&ty=332&tz=-128&xray=0&hide=P01,P02,P03,P04,P05,P07,P08,P09,M1,M2,M3,M5,M6,ENV,S0,S1",
|
||||
"p07_m5_side": "?az=1.2&po=1.9&rd=240&tx=19.5&ty=227&tz=-128&xray=0&hide=P01,P02,P03,P04,P05,P06,P08,P09,M1,M2,M3,M4,M6,ENV,S0,S1",
|
||||
"p05_m3_side": "?az=1.2&po=1.9&rd=240&tx=19.5&ty=400&tz=-128&xray=0&hide=P01,P02,P03,P04,P06,P07,P08,P09,M1,M2,M4,M5,M6,ENV,S0,S1",
|
||||
"boss_p05": "?az=1.5708&po=1.5708&rd=190&tx=18.5&ty=392&tz=-128&xray=0&hide=P01,P02,P03,P04,P06,P07,P08,P09,M2,M4,M5,M6,ENV",
|
||||
"user_compare_view": "?cam=side&az=0&po=1.5708&rd=460&tx=19.5&ty=380&tz=-128&xray=0&hide=P01,P02,P03,P09,M1,M6,ENV",
|
||||
"user_p07_check": "?az=1.5708&po=1.5708&rd=260&tx=19.5&ty=220&tz=-128&xray=0&hide=P01,P02,P03,P04,P05,P06,P09,M1,M2,M3,M4,M5,M6,ENV",
|
||||
"boss_p06": "?az=1.5708&po=1.5708&rd=190&tx=18.5&ty=332&tz=-128&xray=0&hide=P01,P02,P03,P04,P05,P07,P08,P09,M1,M2,M3,M5,M6,ENV",
|
||||
"boss_p07": "?az=0&po=0.0&rd=210&tx=19.5&ty=215&tz=-128&xray=0&hide=P01,P02,P03,P04,P05,P06,P08,P09,M1,M2,M3,M4,M6,ENV",
|
||||
"svg_side": f"file:///{DOCS}/sheet_side.svg",
|
||||
"svg_joint": f"file:///{DOCS}/sheet_joint.svg",
|
||||
"svg_exploded": f"file:///{DOCS}/sheet_exploded.svg",
|
||||
"user_circle_check": "?az=0.8&po=1.95&rd=240&tx=19.5&ty=430&tz=-128&xray=0&hide=M1,M2,M3,M4,M5,M6,ENV",
|
||||
"user_exact_view": "?az=1.5708&po=1.92&rd=150&tx=19.5&ty=440&tz=-128&xray=0&hide=P01,P02,P03,M1,M2,M3,M4,M5,M6,ENV",
|
||||
"shot_p08_user": "?az=1.5708&po=1.92&rd=150&tx=19.5&ty=176&tz=-128&xray=0&hide=P01,P02,P03,P04,P05,P06,P07,P09,M1,M2,M3,M4,M5,M6,ENV",
|
||||
"p05_flank_view": "?az=0&po=1.5708&rd=200&tx=19.5&ty=440&tz=-135&xray=0&hide=P01,P02,P03,P07,P08,P09,M1,M4,M5,M6,ENV",
|
||||
"harness_full_iso": "?cam=iso&xray=0",
|
||||
"harness_overview": "?az=-0.7&po=1.2&rd=520&tx=0&ty=400&tz=-120&xray=0",
|
||||
"harness_xray_internal": "?az=-0.7&po=1.2&rd=520&tx=0&ty=400&tz=-120&alpha=0.35&xray=1",
|
||||
"harness_p07_window": "?az=-0.65&po=1.25&rd=260&tx=25&ty=275&tz=-130&xray=0",
|
||||
"harness_p07_xray": "?az=-0.65&po=1.25&rd=260&tx=25&ty=275&tz=-130&alpha=0.35&xray=1",
|
||||
"harness_p04_p05": "?az=0.8&po=1.35&rd=280&tx=25&ty=470&tz=-128&xray=0",
|
||||
"harness_p04_p05_xray": "?az=0.8&po=1.35&rd=280&tx=25&ty=470&tz=-128&alpha=0.35&xray=1",
|
||||
"harness_p06_window": "?az=-0.65&po=1.25&rd=240&tx=25&ty=355&tz=-128&xray=0",
|
||||
"harness_xray_full": "?cam=iso&xray=1",
|
||||
# 扎带拱桥核验:沿 ±Y 正视肩部,确认三个拱桥各自露出多少、线束是否压在桥上
|
||||
"tie_probe_negY": "?az=1.5708&po=1.5708&rd=460&tx=-30&ty=590&tz=-128&xray=0",
|
||||
"tie_probe_posY": "?az=-1.5708&po=1.5708&rd=460&tx=-30&ty=590&tz=-128&xray=0",
|
||||
"tie_after_negY": "?az=1.5708&po=1.5708&rd=300&tx=-25&ty=600&tz=-128&xray=0",
|
||||
"tie_after_oblique": "?az=2.05&po=1.30&rd=330&tx=-25&ty=600&tz=-128&xray=0",
|
||||
"tie_after_close": "?az=1.90&po=1.50&rd=180&tx=-25&ty=605&tz=-100&xray=0",
|
||||
# P06/P07 −X 侧线束走廊:从 −X、−Y 斜看(纯 −X 会被蓝色立柱挡住)
|
||||
"tie_after_p0607": "?az=2.35&po=1.5708&rd=300&tx=0&ty=290&tz=-102.5&xray=0",
|
||||
"tie_after_p0607_obl": "?az=2.60&po=1.35&rd=260&tx=-5&ty=290&tz=-110&xray=0",
|
||||
}
|
||||
os.makedirs(SHOTS, exist_ok=True)
|
||||
want = sys.argv[1:] or list(VIEWS)
|
||||
|
||||
def take_shot(nm):
|
||||
q = VIEWS[nm]
|
||||
out = "%s/%s.png" % (SHOTS, nm)
|
||||
import time, tempfile
|
||||
t_bust = int(time.time() * 1000)
|
||||
target = q if q.startswith("file://") else "file:///" + HTML.replace("\\", "/") + q + (
|
||||
f"&still=1&_t={t_bust}" if "?" in q else f"?still=1&_t={t_bust}")
|
||||
udd = tempfile.mkdtemp(prefix="eshot")
|
||||
cmd = [EDGE, "--headless=new", "--use-gl=angle", "--use-angle=d3d11",
|
||||
"--disable-cache", "--disable-application-cache", "--disk-cache-size=0",
|
||||
"--user-data-dir=" + udd,
|
||||
"--enable-gpu-rasterization", "--ignore-gpu-blocklist",
|
||||
"--hide-scrollbars", "--force-device-scale-factor=1",
|
||||
"--window-size=1500,860", "--virtual-time-budget=20000",
|
||||
"--screenshot=" + out, target]
|
||||
r = subprocess.run(cmd, capture_output=True, timeout=180)
|
||||
shutil.rmtree(udd, ignore_errors=True)
|
||||
ok = os.path.exists(out) and os.path.getsize(out) > 20000
|
||||
err = (r.stderr or b"").decode("utf-8", errors="ignore").strip()[:90].replace("\n", " ")
|
||||
sz = os.path.getsize(out) if os.path.exists(out) else 0
|
||||
print("%-18s %s %8s %s" % (nm, "OK " if ok else "FAIL", sz, err), flush=True)
|
||||
return ok
|
||||
|
||||
if __name__ == "__main__":
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
with ThreadPoolExecutor(max_workers=min(4, os.cpu_count() or 2)) as executor:
|
||||
results = list(executor.map(take_shot, want))
|
||||
|
||||
print("edge:", EDGE)
|
||||
print("all shots ok:", all(results))
|
||||
Reference in New Issue
Block a user