Files
Robot_Arm/src/shot.py
T

135 lines
8.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from armcore import ROOT, WORKSPACE, 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",
"02_front": "?cam=front",
"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",
"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",
"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",
"svg_front": f"file:///{DOCS}/sheet_front.svg",
}
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)
# 不预先删除旧图:Edge 的 --screenshot 本来就会覆盖,而 os.remove 在带
# safe-delete 垫片的环境里会把"删到回收站"的失败抛出来,直接打断整轮渲染。
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}")
# 每次用独立 profile:共用默认 profile 时并发实例会互相抢锁,表现为随机 FAIL。
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("%-12s %s %8s %s" % (nm, "OK " if ok else "FAIL", sz, err), flush=True)
return ok
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))
os._exit(0)