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 段逐字节一致。
@@ -28,3 +28,10 @@ web/机器人手臂3D查看器.html
|
|||||||
/机器人手臂3D查看器.html
|
/机器人手臂3D查看器.html
|
||||||
webapp/data/
|
webapp/data/
|
||||||
dist/
|
dist/
|
||||||
|
data/cache/
|
||||||
|
|
||||||
|
# 这 3 张线束 x-ray 截图在同一份构建下就不逐字节复现(抗锯齿噪声),
|
||||||
|
# 每次跑关卡都会改写它们;参考像素已存进 tests/baselines/flaky/,不再入库。
|
||||||
|
docs/shots/harness_p04_p05_xray.png
|
||||||
|
docs/shots/harness_p07_xray.png
|
||||||
|
docs/shots/harness_xray_internal.png
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""6 自由度人形机械臂:CadQuery 参数化建模、行程/干涉扫描、制造文件与查看器数据。
|
||||||
|
|
||||||
|
入口一律从仓根以 `python -m arm.xxx` 运行:`python -m` 会把仓根放进 sys.path,
|
||||||
|
包内 import 因此直接可解析,不需要 pip install。
|
||||||
|
"""
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""行程与干涉扫描、强度与力矩核算、可达包络采样与网格注入。
|
||||||
|
"""
|
||||||
@@ -14,8 +14,15 @@ import math
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
from arm.analysis.sweep_v2 import CHAIN
|
||||||
from sweep_v2 import CHAIN, DOCS
|
from arm.paths import DOCS
|
||||||
|
|
||||||
|
# 这两个脚本的顶层就是"算完并写文件",被 import 时会静默重写 travel/mesh 派生产物
|
||||||
|
# (实测:一次 importlib 冒烟测试就把 docs/travel.json 重写了一遍)。这里改成直接炸。
|
||||||
|
if __name__ != "__main__":
|
||||||
|
raise RuntimeError(
|
||||||
|
"finalize_travel 是脚本:顶层代码一执行就重写产物,不能 import。"
|
||||||
|
"要用就跑 python -m arm.analysis.finalize_travel。")
|
||||||
|
|
||||||
d = json.load(open(DOCS + "/travel_detail.json", encoding="utf-8"))
|
d = json.load(open(DOCS + "/travel_detail.json", encoding="utf-8"))
|
||||||
|
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""生成可达外包络的 ENV 零件(经纬网单件)追加进 web/mesh_arm.json:
|
||||||
|
ENV_WL_包络经纬网 —— kind=line,LineSegments 渲染(经向 10°/条、纬向 8mm/环)
|
||||||
|
包络 = rmax(x) 母线绕 J1 轴(平行 X,过 (y=128, z=610))整周回转;grp=root 不随关节动。"""
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from arm.paths import WEB, CACHE
|
||||||
|
from arm import kinematics as HL
|
||||||
|
|
||||||
|
Y_AX = HL.Y_AXIS # 128
|
||||||
|
Z_AX = HL.Z_RL # 610
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
d = np.load(CACHE + "/ws_cloud.npz")
|
||||||
|
xs, rmax = d["xs"], d["rmax"]
|
||||||
|
ok = ~np.isnan(rmax)
|
||||||
|
xs, rmax = xs[ok], rmax[ok]
|
||||||
|
|
||||||
|
def grid(step_x, dphi_deg):
|
||||||
|
"""经纬参数网格 -> verts(NX*NP,3), segs(扁平索引对), tris。"""
|
||||||
|
xs_e = np.arange(xs.min(), xs.max() + 1e-9, step_x)
|
||||||
|
rm_e = np.interp(xs_e, xs, rmax)
|
||||||
|
nph = int(round(360.0 / dphi_deg))
|
||||||
|
phg = np.linspace(0.0, 2.0 * math.pi, nph, endpoint=False)
|
||||||
|
nx = len(xs_e)
|
||||||
|
V = np.empty((nx * nph, 3))
|
||||||
|
for i, x in enumerate(xs_e):
|
||||||
|
r = rm_e[i]
|
||||||
|
for j, ph in enumerate(phg):
|
||||||
|
V[i * nph + j] = (x, Y_AX + r * math.cos(ph), Z_AX + r * math.sin(ph))
|
||||||
|
segs = []
|
||||||
|
for i in range(nx - 1): # 经线(沿 x)
|
||||||
|
for j in range(nph):
|
||||||
|
segs += [i * nph + j, (i + 1) * nph + j]
|
||||||
|
for i in range(nx): # 纬线(沿 φ)
|
||||||
|
for j in range(nph):
|
||||||
|
j2 = (j + 1) % nph
|
||||||
|
segs += [i * nph + j, i * nph + j2]
|
||||||
|
tris = []
|
||||||
|
for i in range(nx - 1):
|
||||||
|
for j in range(nph):
|
||||||
|
j2 = (j + 1) % nph
|
||||||
|
a, b = i * nph + j, i * nph + j2
|
||||||
|
c, e = (i + 1) * nph + j, (i + 1) * nph + j2
|
||||||
|
tris += [a, c, b, b, c, e]
|
||||||
|
return V, segs, tris
|
||||||
|
|
||||||
|
# 经纬网单件:经向 10°一条、纬向 8mm一环(无需贴片面,线自带罩体感)
|
||||||
|
Vl, sl, _tl = grid(8.0, 10.0)
|
||||||
|
|
||||||
|
# 自证:包络尺寸应等于 ws_metrics 极值。
|
||||||
|
# 【2026-10-05c 修正】原来是 <1.0 的硬比较,但网格 x 是从 xs.min() 起按 step_x
|
||||||
|
# 对齐的(np.arange),最后一个采样点最多比真极值短一个步长 —— 一旦
|
||||||
|
# (x_fwd - xs.min()) 不是 step_x 的整数倍,断言就会假失败(实测差 6.46mm,
|
||||||
|
# 正好 < 8.0),导致包络注不进去、查看器里"可达包络"整个消失。改为
|
||||||
|
# "不得超出、且至多少一个步长"这个真正成立的判据。
|
||||||
|
step_x = 8.0
|
||||||
|
met = json.load(open(CACHE + "/ws_metrics.json", encoding="utf-8"))
|
||||||
|
assert Vl[:, 0].min() >= met["x_back"] - 1e-6, (Vl[:, 0].min(), met["x_back"])
|
||||||
|
assert Vl[:, 0].min() <= met["x_back"] + step_x + 1e-6, (Vl[:, 0].min(), met["x_back"])
|
||||||
|
assert Vl[:, 0].max() <= met["x_fwd"] + 1e-6, (Vl[:, 0].max(), met["x_fwd"])
|
||||||
|
assert Vl[:, 0].max() >= met["x_fwd"] - step_x - 1e-6, (Vl[:, 0].max(), met["x_fwd"])
|
||||||
|
rl = np.hypot(Vl[:, 1] - Y_AX, Vl[:, 2] - Z_AX)
|
||||||
|
# rl.max() 就是 rmax(x) 网格插值后的峰值,与 met["r_max"](原始点云极值)之间
|
||||||
|
# 只允许有插值/取整量级的差(实测 0.044mm),故给 1.0mm 容差。
|
||||||
|
assert abs(rl.max() - met["r_max"]) < 1.0, (rl.max(), met["r_max"])
|
||||||
|
assert np.all((Vl[:, 1] - Y_AX) ** 2 + (Vl[:, 2] - Z_AX) ** 2
|
||||||
|
<= (np.interp(Vl[:, 0], xs, rmax) + 1e-6) ** 2), "line"
|
||||||
|
print("包络经纬网: 顶点%d 线段%d(经10° x 纬8mm)" % (len(Vl), len(sl) // 2))
|
||||||
|
|
||||||
|
# ---- 追加进 mesh_arm.json ----
|
||||||
|
path = WEB + "/mesh_arm.json"
|
||||||
|
data = json.load(open(path, encoding="utf-8"))
|
||||||
|
data["parts"] = [p for p in data["parts"] if not p["name"].startswith("ENV_")]
|
||||||
|
mx = max(p["idx"] for p in data["parts"])
|
||||||
|
note_ws = ("可达外包络(运动学,docs/travel.json 滑块限位下的全组合 FK 包络):"
|
||||||
|
"r=rmax(x) 绕 J1 轴整周回转。高 %.0f~%.0f mm,前伸 %.0f / 后伸 %.0f mm,"
|
||||||
|
"离 J1 轴最远 %.0f mm;包络内含死区(肩下折不进),多关节同极位由控制器避碰。"
|
||||||
|
% (met["z_min"], met["z_max"], met["x_fwd"], met["x_back"], met["r_max"]))
|
||||||
|
line = dict(name="ENV_WL_包络经纬网", cn="包络经纬网", v=[list(p) for p in Vl],
|
||||||
|
s=sl, color=0x60a5fa, printed=False, vol=round(met["vol_dm3"] * 1e6, 1),
|
||||||
|
note=note_ws + " 经纬线:经向 10°/条,纬向 8mm/环。",
|
||||||
|
grp="root", jaw=0.0, ex=[0.0, 0.0, 0.0], idx=mx + 1,
|
||||||
|
kind="line")
|
||||||
|
data["parts"] += [line]
|
||||||
|
tmp = path + ".tmp"
|
||||||
|
with open(tmp, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, ensure_ascii=False, separators=(",", ":"))
|
||||||
|
os.replace(tmp, path)
|
||||||
|
print("已写入", path, "大小 %.1f MB" % (os.path.getsize(path) / 1e6))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -19,22 +19,19 @@ import math
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
try:
|
try:
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
import cadquery as cq
|
import cadquery as cq
|
||||||
from armcore import OUT, DOCS, solids_of, bb, mesh_of
|
from arm.paths import OUT, WEB, DOCS
|
||||||
|
from arm.cad.helpers import solids_of, bb, mesh_of
|
||||||
from OCP.BRepAlgoAPI import BRepAlgoAPI_Common
|
from OCP.BRepAlgoAPI import BRepAlgoAPI_Common
|
||||||
from OCP.BRepGProp import BRepGProp
|
from OCP.BRepGProp import BRepGProp
|
||||||
from OCP.GProp import GProp_GProps
|
from OCP.GProp import GProp_GProps
|
||||||
from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox
|
from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox
|
||||||
from OCP.gp import gp_Pnt, gp_Dir, gp_Ax2, gp_Vec
|
from OCP.gp import gp_Pnt, gp_Dir, gp_Ax2, gp_Vec
|
||||||
|
from arm import kinematics as HL
|
||||||
import human_layout as HL
|
|
||||||
|
|
||||||
RHO = {"PLA": 1.24e-3, "motor_g": 210.0} # g/mm3 for printed, g per module
|
RHO = {"PLA": 1.24e-3, "motor_g": 210.0} # g/mm3 for printed, g per module
|
||||||
# a slicer does not print solid: 3 perimeters + 30% infill lands near this fraction of
|
# a slicer does not print solid: 3 perimeters + 30% infill lands near this fraction of
|
||||||
# the CAD envelope, and using the solid mass would blame the part for plastic that is not there
|
# the CAD envelope, and using the solid mass would blame the part for plastic that is not there
|
||||||
@@ -159,7 +156,7 @@ def section_props(sh, normal, origin, bend=2, size=400.0):
|
|||||||
|
|
||||||
def arm_masses():
|
def arm_masses():
|
||||||
"""Everything distal to each joint, in grams, from the model itself."""
|
"""Everything distal to each joint, in grams, from the model itself."""
|
||||||
meta = json.load(io.open(OUT.replace("/out", "/web") + "/mesh_arm.json", encoding="utf-8"))
|
meta = json.load(io.open(WEB + "/mesh_arm.json", encoding="utf-8"))
|
||||||
TREE = ["root", "J1", "J2", "J3", "J4", "J5", "J6"]
|
TREE = ["root", "J1", "J2", "J3", "J4", "J5", "J6"]
|
||||||
tot = {}
|
tot = {}
|
||||||
for p in meta["parts"]:
|
for p in meta["parts"]:
|
||||||
@@ -202,7 +199,7 @@ def _mesh_vol_cg(v, t):
|
|||||||
|
|
||||||
def part_table(payload_kg=None):
|
def part_table(payload_kg=None):
|
||||||
"""[(name, mass_g, cg_xyz, grp)],含末端负载这个集中质量点。"""
|
"""[(name, mass_g, cg_xyz, grp)],含末端负载这个集中质量点。"""
|
||||||
meta = json.load(io.open(OUT.replace("/out", "/web") + "/mesh_arm.json", encoding="utf-8"))
|
meta = json.load(io.open(WEB + "/mesh_arm.json", encoding="utf-8"))
|
||||||
rows = []
|
rows = []
|
||||||
for p in meta["parts"]:
|
for p in meta["parts"]:
|
||||||
nm = p["name"]
|
nm = p["name"]
|
||||||
@@ -488,5 +485,10 @@ def main():
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
b = main()
|
b = main()
|
||||||
print("\n不合格截面 %d 处" % len(b))
|
print("\n不合格截面 %d 处" % len(b))
|
||||||
sys.stdout.flush() # os._exit 不刷缓冲,重定向到文件时否则输出全丢
|
|
||||||
|
# OCC/cadquery 的静态析构在解释器正常退出时必定段错误(实测:光是
|
||||||
|
# `python -c "import arm.cad.helpers"` 就返回 139,与本项目代码无关)。所以入口
|
||||||
|
# 脚本跑完后显式刷缓冲再 os._exit —— 否则全流程已经成功、exit code 却是 139,
|
||||||
|
# 会把上游脚本的 set -e 判成失败。只在 __main__ 出口生效, 被 import 时不触发。
|
||||||
|
sys.stdout.flush()
|
||||||
os._exit(0)
|
os._exit(0)
|
||||||
@@ -27,8 +27,7 @@
|
|||||||
就是臂撞躯干,是真约束。--no-stand 仅用于诊断:排除台架后重扫,给出"不含躯干约束"的
|
就是臂撞躯干,是真约束。--no-stand 仅用于诊断:排除台架后重扫,给出"不含躯干约束"的
|
||||||
自由空间行程,用来分辨某个限位是臂自身折叠产生的还是躯干挡的(台架位置本身是假设,
|
自由空间行程,用来分辨某个限位是臂自身折叠产生的还是躯干挡的(台架位置本身是假设,
|
||||||
改它等于改测试工装,不等于改设计)。
|
改它等于改测试工装,不等于改设计)。
|
||||||
|
运行:python -m arm.analysis.sweep_v2 [--workers 20] [--step 2] [--combo-step 6]
|
||||||
运行:python src/sweep_v2.py [--workers 20] [--step 2] [--combo-step 6]
|
|
||||||
[--no-combos] [--joints J2,J4] [--no-gjk] [--no-stand]
|
[--no-combos] [--joints J2,J4] [--no-gjk] [--no-stand]
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
@@ -38,20 +37,17 @@ import sys
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
try:
|
try:
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
|
from arm.cad import arm_build as AB
|
||||||
import arm_build as AB
|
|
||||||
import cadquery as cq
|
import cadquery as cq
|
||||||
import human_layout as HL
|
from arm import kinematics as HL
|
||||||
from armcore import place, bb, DOCS
|
from arm.paths import DOCS
|
||||||
|
from arm.cad.helpers import place, bb
|
||||||
VOL_MIN = 2.0 # mm3,超过即计为干涉
|
VOL_MIN = 2.0 # mm3,超过即计为干涉
|
||||||
CHAIN = ["root", "J1", "J2", "J3", "J4", "J5", "J6"]
|
CHAIN = ["root", "J1", "J2", "J3", "J4", "J5", "J6"]
|
||||||
MOUNT_PAIRS = {
|
MOUNT_PAIRS = {
|
||||||
@@ -175,7 +171,7 @@ def _do_simplex(s):
|
|||||||
def load_model(include_fixture=True):
|
def load_model(include_fixture=True):
|
||||||
"""include_fixture=False 时把 P01 立柱 / P02 底盘(固定测试台架,非本体)排除在
|
"""include_fixture=False 时把 P01 立柱 / P02 底盘(固定测试台架,非本体)排除在
|
||||||
碰撞配对之外——用来得到真机工作空间而不是台架工作空间。见 human_layout.FIXTURE_PREFIX。"""
|
碰撞配对之外——用来得到真机工作空间而不是台架工作空间。见 human_layout.FIXTURE_PREFIX。"""
|
||||||
import arm_model as AM
|
from arm import build_model as AM
|
||||||
JS = AM.build()
|
JS = AM.build()
|
||||||
parts = []
|
parts = []
|
||||||
for p in AB.PARTS:
|
for p in AB.PARTS:
|
||||||
@@ -721,3 +717,10 @@ def main():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
# OCC/cadquery 的静态析构在解释器正常退出时必定段错误(实测:光是
|
||||||
|
# `python -c "import arm.cad.helpers"` 就返回 139,与本项目代码无关)。所以入口
|
||||||
|
# 脚本跑完后显式刷缓冲再 os._exit —— 否则全流程已经成功、exit code 却是 139,
|
||||||
|
# 会把上游脚本的 set -e 判成失败。只在 __main__ 出口生效, 被 import 时不触发。
|
||||||
|
sys.stdout.flush()
|
||||||
|
os._exit(0)
|
||||||
@@ -29,17 +29,13 @@ import json
|
|||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
try:
|
try:
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
from arm import kinematics as HL
|
||||||
import human_layout as HL
|
from arm.paths import DOCS
|
||||||
from armcore import DOCS
|
from arm.analysis import strength_check as SC
|
||||||
import strength_check as SC
|
|
||||||
|
|
||||||
G = 9.81
|
G = 9.81
|
||||||
T_CONT = float(os.environ.get("ARMT_CONT", "2.5"))
|
T_CONT = float(os.environ.get("ARMT_CONT", "2.5"))
|
||||||
T_PEAK = float(os.environ.get("ARMT_PEAK", "8.0"))
|
T_PEAK = float(os.environ.get("ARMT_PEAK", "8.0"))
|
||||||
@@ -206,9 +202,12 @@ def main():
|
|||||||
w = min(ok, key=lambda t: mp[t])
|
w = min(ok, key=lambda t: mp[t])
|
||||||
print(" 整臂瓶颈 %s -> %.2f kg" % (w, mp[w]))
|
print(" 整臂瓶颈 %s -> %.2f kg" % (w, mp[w]))
|
||||||
print("\n已写出 docs/torque_balance.json + docs/torque_report.md")
|
print("\n已写出 docs/torque_balance.json + docs/torque_report.md")
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
# OCC/cadquery 的静态析构在解释器正常退出时必定段错误(实测:光是
|
||||||
|
# `python -c "import arm.cad.helpers"` 就返回 139,与本项目代码无关)。所以入口
|
||||||
|
# 脚本跑完后显式刷缓冲再 os._exit —— 否则全流程已经成功、exit code 却是 139,
|
||||||
|
# 会把上游脚本的 set -e 判成失败。只在 __main__ 出口生效, 被 import 时不触发。
|
||||||
|
sys.stdout.flush()
|
||||||
os._exit(0)
|
os._exit(0)
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""关节角组合可达空间(工具尖点)计算:FK 采样 + J1 旋转对称包络 + 体积估计"""
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from arm import kinematics as HL
|
||||||
|
from arm.paths import DOCS, CACHE
|
||||||
|
|
||||||
|
TRAVEL = {t["axis"]: (t["lo"], t["hi"]) for t in
|
||||||
|
json.load(open(DOCS + "/travel.json", encoding="utf-8"))}
|
||||||
|
|
||||||
|
TIP = (HL.X_ARM, HL.Y_AXIS, HL.Z_TIP) # P09 法兰底面中心(工具安装面)
|
||||||
|
J1_AX = (HL.Y_AXIS, HL.Z_RL) # J1 转轴: 过 Y=128, Z=610, 平行 X
|
||||||
|
|
||||||
|
|
||||||
|
def rot_about(p, k, ang):
|
||||||
|
"""绕轴 (p 点, k 方向) 旋转 ang(弧度, 可为数组M,);返回 Rm(M,3,3), t(M,3)。
|
||||||
|
p' = Rm @ (p_pt - p) + p,与 AB.R 的 gp_Trsf.SetRotation 右手定则一致。"""
|
||||||
|
k = np.asarray(k, dtype=float)
|
||||||
|
k = k / np.linalg.norm(k)
|
||||||
|
ang = np.atleast_1d(np.asarray(ang, dtype=float))
|
||||||
|
px, py, pz = p
|
||||||
|
K = np.array([[0.0, -k[2], k[1]], [k[2], 0.0, -k[0]], [-k[1], k[0], 0.0]])
|
||||||
|
K2 = K @ K
|
||||||
|
Rm = np.eye(3)[None, :, :] + np.sin(ang)[:, None, None] * K[None, :, :] \
|
||||||
|
+ (1.0 - np.cos(ang))[:, None, None] * K2[None, :, :]
|
||||||
|
pp = np.array([px, py, pz])
|
||||||
|
t = pp[None, :] - np.einsum("mij,j->mi", Rm, pp)
|
||||||
|
return Rm, t
|
||||||
|
|
||||||
|
|
||||||
|
def fk_batch(angles, tip=TIP):
|
||||||
|
"""angles: dict idx->弧度数组(M,);返回 (M,3) 世界坐标。链式 acc 与 sweep_v2 一致:
|
||||||
|
父关节变换在最外层 p' = R1∘R2∘…∘R5 (p),各 R_i 绕其零位世界轴(human_layout 唯一事实源)。"""
|
||||||
|
pts = np.tile(np.asarray(tip, dtype=float), (angles[1].shape[0], 1))
|
||||||
|
for i in range(5, 0, -1): # J6 跳过:尖点在 J6 轴上,其转角不改变尖点位置
|
||||||
|
rear, d = HL.JOINTS[i - 1][2], HL.JOINTS[i - 1][3]
|
||||||
|
Rm, t = rot_about(rear, d, angles[i])
|
||||||
|
pts = np.einsum("mij,mj->mi", Rm, pts) + t
|
||||||
|
return pts
|
||||||
|
|
||||||
|
|
||||||
|
def sweep(step=2.0):
|
||||||
|
j2 = np.radians(np.arange(TRAVEL["J2"][0], TRAVEL["J2"][1] + 1e-9, step))
|
||||||
|
j4 = np.radians(np.arange(TRAVEL["J4"][0], TRAVEL["J4"][1] + 1e-9, step))
|
||||||
|
j5 = np.radians(np.arange(TRAVEL["J5"][0], TRAVEL["J5"][1] + 1e-9, step))
|
||||||
|
g2, g4, g5 = np.meshgrid(j2, j4, j5, indexing="ij")
|
||||||
|
M = g2.size
|
||||||
|
angles = {1: np.zeros(M), 2: g2.ravel(), 3: np.zeros(M),
|
||||||
|
4: g4.ravel(), 5: g5.ravel()}
|
||||||
|
pts = fk_batch(angles)
|
||||||
|
# 自证1:零位尖点必须回到 (18.5,128,93)
|
||||||
|
z0 = fk_batch({i: np.zeros(1) for i in range(1, 6)})
|
||||||
|
assert np.allclose(z0[0], TIP, atol=1e-9), z0[0]
|
||||||
|
# 自证2:J1/J6 旋转不改变 (x, r) 与极值位置——J1 取中值扫一遍,r 分布应与 J1=0 相同
|
||||||
|
for phi in (0.5, 1.2, 2.4):
|
||||||
|
v1 = np.full(M, phi)
|
||||||
|
p1 = fk_batch({1: v1, 2: g2.ravel(), 3: np.zeros(M),
|
||||||
|
4: g4.ravel(), 5: g5.ravel()})
|
||||||
|
r1 = np.hypot(p1[:, 1] - J1_AX[0], p1[:, 2] - J1_AX[1])
|
||||||
|
r0 = np.hypot(pts[:, 1] - J1_AX[0], pts[:, 2] - J1_AX[1])
|
||||||
|
assert np.allclose(np.sort(r0), np.sort(r1), atol=1e-9), "J1 对称性被破坏"
|
||||||
|
# 自证3:J3 旋转(大臂自转)不改变尖点位置——关节轴过尖点所在纵线
|
||||||
|
print("J3 对称性: 尖点 x=%.1f 与 J3 轴 x=%.1f 一致=%s, y=%.1f 与轴 y=%.1f 一致=%s"
|
||||||
|
% (TIP[0], HL.X_ARM, abs(TIP[0] - HL.X_ARM) < 1e-9,
|
||||||
|
TIP[1], HL.Y_AXIS, abs(TIP[1] - HL.Y_AXIS) < 1e-9))
|
||||||
|
print("J6 含于轴: 尖点 (%.1f,%.1f) vs 轴 (%.1f,%.1f)"
|
||||||
|
% (TIP[0], TIP[1], HL.JOINTS[5][2][0], HL.JOINTS[5][2][1]))
|
||||||
|
return pts
|
||||||
|
|
||||||
|
|
||||||
|
def envelope(pts, grid=1.0):
|
||||||
|
"""(x, r) 半平面栅格化占用 + 体积(绕 J1 轴回转)。"""
|
||||||
|
x = pts[:, 0]
|
||||||
|
r = np.hypot(pts[:, 1] - J1_AX[0], pts[:, 2] - J1_AX[1])
|
||||||
|
x0, x1 = math.floor(x.min()), math.ceil(x.max())
|
||||||
|
r0, r1 = 0.0, math.ceil(r.max())
|
||||||
|
nx = int(round((x1 - x0) / grid)) + 1
|
||||||
|
nr = int(round((r1 - r0) / grid)) + 1
|
||||||
|
xi = np.clip(((x - x0) / grid).round().astype(int), 0, nx - 1)
|
||||||
|
ri = np.clip((r / grid).round().astype(int), 0, nr - 1)
|
||||||
|
occ = np.zeros((nx, nr), dtype=bool)
|
||||||
|
occ[xi, ri] = True
|
||||||
|
# 每个 x 列只保留 r <= 该列可达最大 r(回转体沿 r 连续填充)
|
||||||
|
for i in range(nx):
|
||||||
|
j = np.nonzero(occ[i])[0]
|
||||||
|
if j.size:
|
||||||
|
occ[i, : j.max() + 1] = True
|
||||||
|
rr = r0 + np.arange(nr) * grid
|
||||||
|
vol = float((occ * (2.0 * math.pi * rr)[None, :] * grid * grid).sum())
|
||||||
|
return dict(occ=occ, x0=x0, grid=grid, rm_axes=rr, vol_mm3=vol,
|
||||||
|
nx=nx, nr=nr)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
pts = sweep()
|
||||||
|
x, y, z = pts[:, 0], pts[:, 1], pts[:, 2]
|
||||||
|
r = np.hypot(y - J1_AX[0], z - J1_AX[1])
|
||||||
|
env = envelope(pts)
|
||||||
|
# 每个 x 的 r 包络(侧视/俯视剖面用)
|
||||||
|
xs = env["x0"] + np.arange(env["nx"]) * env["grid"]
|
||||||
|
rmax_x, rmin_x = [], []
|
||||||
|
for i in range(env["nx"]):
|
||||||
|
j = np.nonzero(env["occ"][i])[0]
|
||||||
|
if j.size:
|
||||||
|
rmax_x.append(env["rm_axes"][j.max()])
|
||||||
|
rmin_x.append(env["rm_axes"][j.min()])
|
||||||
|
else:
|
||||||
|
rmax_x.append(np.nan)
|
||||||
|
rmin_x.append(np.nan)
|
||||||
|
os.makedirs(CACHE, exist_ok=True)
|
||||||
|
np.savez_compressed(CACHE + "/ws_cloud.npz",
|
||||||
|
pts=pts, xs=np.array(xs),
|
||||||
|
rmax=np.array(rmax_x), rmin=np.array(rmin_x))
|
||||||
|
met = dict(
|
||||||
|
z_max=float(z.max()), z_min=float(z.min()),
|
||||||
|
x_fwd=float(x.max()), x_back=float(x.min()),
|
||||||
|
y_lo=float(y.min()), y_hi=float(y.max()),
|
||||||
|
r_max=float(r.max()), r_min=float(r.min()),
|
||||||
|
reach_from_j1_axis=float(r.max()),
|
||||||
|
spread=float(z.max() - z.min()),
|
||||||
|
vol_dm3=env["vol_mm3"] / 1e6,
|
||||||
|
tip=TIP, j1_axis=list(J1_AX),
|
||||||
|
limits={k: list(v) for k, v in TRAVEL.items()},
|
||||||
|
)
|
||||||
|
# 自证4:极值点姿态可解释(打印取极值的角度组合索引)
|
||||||
|
imax = int(np.argmax(z)); imin = int(np.argmin(z))
|
||||||
|
ifwd = int(np.argmax(x)); iback = int(np.argmin(x))
|
||||||
|
met["pose_extreme"] = dict(
|
||||||
|
zmax_idx=imax, zmin_idx=imin, xfwd_idx=ifwd, xback_idx=iback)
|
||||||
|
print(json.dumps({k: v for k, v in met.items()
|
||||||
|
if k not in ("limits", "pose_extreme")},
|
||||||
|
ensure_ascii=False, indent=1))
|
||||||
|
json.dump(met, open(CACHE + "/ws_metrics.json", "w", encoding="utf-8"),
|
||||||
|
ensure_ascii=False, indent=1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -10,17 +10,17 @@ import math
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
import cadquery as cq
|
import cadquery as cq
|
||||||
import arm_build as AB
|
from arm.cad import arm_build as AB
|
||||||
from armcore import (M, C, cyl, box, ring, place, trs, bb, bbox_str, solids_of, OUT, WEB, DOCS)
|
from arm.paths import OUT, WEB, DOCS
|
||||||
from arm_build import (Joint, add, bx, Ycyl, Ybox, Yplate, prism, dogbone, sector,
|
from arm.constants import M, C
|
||||||
|
from arm.cad.helpers import cyl, box, ring, place, trs, bb, bbox_str, solids_of
|
||||||
|
from arm.cad.arm_build import (Joint, add, bx, Ycyl, Ybox, Yplate, prism, dogbone, sector,
|
||||||
FLANGE_Z, COVER_Z, HUB_FACE_Z, CLAMP_SPAN, HUB_M2_CLOCK,
|
FLANGE_Z, COVER_Z, HUB_FACE_Z, CLAMP_SPAN, HUB_M2_CLOCK,
|
||||||
BORE_R, CB_R, PCB_W, D2R, T, R, PARTS)
|
BORE_R, CB_R, PCB_W, D2R, T, R, PARTS)
|
||||||
import human_layout as HL
|
from arm import kinematics as HL
|
||||||
from OCP.BRepAlgoAPI import BRepAlgoAPI_Common
|
from OCP.BRepAlgoAPI import BRepAlgoAPI_Common
|
||||||
from human_layout import (HUB, PLATE_T, R_MOT, Z_TIP, Z_WR, Z_EI, Z_SH, Z_RL, Z_R1,
|
from arm.kinematics import (HUB, PLATE_T, R_MOT, Z_TIP, Z_WR, Z_EI, Z_SH, Z_RL, Z_R1,
|
||||||
Z_R3, Z_HUB3, Z_EL, Z_R6, Z_HUB6,
|
Z_R3, Z_HUB3, Z_EL, Z_R6, Z_HUB6,
|
||||||
Z_R4, Z_CAP, Z_M6, Z_CHEST0, Z_CHEST1, X_J1_HUB, X_ARM, L1,
|
Z_R4, Z_CAP, Z_M6, Z_CHEST0, Z_CHEST1, X_J1_HUB, X_ARM, L1,
|
||||||
X_FORK_GAP, X_BAY_EXT,
|
X_FORK_GAP, X_BAY_EXT,
|
||||||
@@ -33,24 +33,23 @@ AB.VERSION = VERSION
|
|||||||
AB.ITER = ITER
|
AB.ITER = ITER
|
||||||
|
|
||||||
# ------------------------------------------------ 逐件模块(几何已拆出)
|
# ------------------------------------------------ 逐件模块(几何已拆出)
|
||||||
from parts import PART_MODULES
|
from arm.cad.parts import PART_MODULES
|
||||||
from part_tools import (
|
from arm.cad.part_tools import (
|
||||||
BOSS, CHAMFER_EDGE, CLAMP_BACKOFF, CLAMP_BCD, CLAMP_CLOCK, CLAMP_CLOCK_LOCAL, CLAMP_CLR,
|
BOSS, CHAMFER_EDGE, CLAMP_BACKOFF, CLAMP_BCD, CLAMP_CLOCK, CLAMP_CLOCK_LOCAL, CLAMP_CLR,
|
||||||
CLAMP_ROOT, CLAMP_T, CLEARANCE_GAP, DISC_R, FENCE_OPEN_CUT, FILLET_LARGE, FILLET_MEDIUM,
|
CLAMP_ROOT, CLAMP_T, CLEARANCE_GAP, DISC_R, FENCE_OPEN_CUT, FILLET_LARGE, FILLET_MEDIUM,
|
||||||
FILLET_SMALL, FILLET_TINY, FILLET_XLARGE, HOLLOW_FILLET, HOLLOW_WALL, SLEEVE_R, XB, Xcyl,
|
FILLET_SMALL, FILLET_TINY, FILLET_XLARGE, HOLLOW_FILLET, HOLLOW_WALL, SLEEVE_R, XB, Xcyl,
|
||||||
Xplate, Y_MID, cut_motor_fence, hollow_channel, hollow_tent_window, hub_m2_holes,
|
Xplate, Y_MID, cut_motor_fence, hollow_channel, hollow_tent_window, hub_m2_holes,
|
||||||
make_direct_hub, make_pitch_to_roll_bracket, motor_front_clamp,
|
make_direct_hub, make_pitch_to_roll_bracket, motor_front_clamp,
|
||||||
)
|
)
|
||||||
from parts.p01_column import X_COL, Y_COL, Z_LOCK, p_chest, split_p01_poka_yoke
|
from arm.cad.parts.p01_column import X_COL, Y_COL, Z_LOCK, p_chest, split_p01_poka_yoke
|
||||||
from parts.p02_base import make_p02_poka_yoke, p_base_foot
|
from arm.cad.parts.p02_base import make_p02_poka_yoke, p_base_foot
|
||||||
from parts.p03_girdle import p_girdle
|
from arm.cad.parts.p03_girdle import p_girdle
|
||||||
from parts.p04_fork import P04_CH, p_fork
|
from arm.cad.parts.p04_fork import P04_CH, p_fork
|
||||||
from parts.p05_roll_base import p_shoulder_roll_base
|
from arm.cad.parts.p05_roll_base import p_shoulder_roll_base
|
||||||
from parts.p06_humerus import P06_HOLLOW_WIN, P06_WIN_XZ, p_humerus_bone
|
from arm.cad.parts.p06_humerus import P06_HOLLOW_WIN, P06_WIN_XZ, p_humerus_bone
|
||||||
from parts.p07_forearm import P07_CH, p_forearm_bone
|
from arm.cad.parts.p07_forearm import P07_CH, p_forearm_bone
|
||||||
from parts.p08_wrist import p_wrist_bracket
|
from arm.cad.parts.p08_wrist import p_wrist_bracket
|
||||||
from parts.p09_flange import p_tool_flange
|
from arm.cad.parts.p09_flange import p_tool_flange
|
||||||
|
|
||||||
# 兼容旧工具:scratch/ 的诊断脚本按 arm_model.<符号> 取用件构造函数与开关
|
# 兼容旧工具:scratch/ 的诊断脚本按 arm_model.<符号> 取用件构造函数与开关
|
||||||
|
|
||||||
def build():
|
def build():
|
||||||
@@ -80,9 +79,7 @@ def build():
|
|||||||
for t in ("J1", "J2", "J3", "J4", "J5", "J6"):
|
for t in ("J1", "J2", "J3", "J4", "J5", "J6"):
|
||||||
AB.GRP[motor_of[t]] = par[t]
|
AB.GRP[motor_of[t]] = par[t]
|
||||||
AB.JAW = {}
|
AB.JAW = {}
|
||||||
|
# 逐件模块:几何、件号/配色/说明、装配帧归属都在 arm/cad/parts/ 里,
|
||||||
|
|
||||||
# 逐件模块:几何、件号/配色/说明、装配帧归属都在 src/parts/ 里,
|
|
||||||
# 这里只按 BOM 顺序收集 specs(),并把件号登记到装配帧图上。
|
# 这里只按 BOM 顺序收集 specs(),并把件号登记到装配帧图上。
|
||||||
parts_to_build = []
|
parts_to_build = []
|
||||||
for _m in PART_MODULES:
|
for _m in PART_MODULES:
|
||||||
@@ -177,39 +174,26 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
# 紧固件(螺钉)实例注入:把 docs/screws.json 的 72 颗螺钉按真实孔位还原成三维实例,
|
# 紧固件(螺钉)实例注入:把 docs/screws.json 的 72 颗螺钉按真实孔位还原成三维实例,
|
||||||
# 追加进 web/mesh_arm.json 的 parts(查看器可点 BOM 螺丝行高亮定位;纯数据,不动 CAD)
|
# 追加进 web/mesh_arm.json 的 parts(查看器可点 BOM 螺丝行高亮定位;纯数据,不动 CAD)
|
||||||
try:
|
from arm.dataio import screw_mesh
|
||||||
import screw_mesh
|
|
||||||
screw_mesh.main()
|
screw_mesh.main()
|
||||||
except Exception as e:
|
|
||||||
print("螺钉实例注入失败(不影响主流程,可手动跑 src/screw_mesh.py):", e, flush=True)
|
|
||||||
|
|
||||||
cl = AB.collide()
|
cl = AB.collide()
|
||||||
|
from arm.viewer.build_viewer import rebuild_viewer
|
||||||
try:
|
|
||||||
from build_viewer import rebuild_viewer
|
|
||||||
rebuild_viewer()
|
rebuild_viewer()
|
||||||
except Exception as e:
|
|
||||||
print("rebuild_viewer error:", e, flush=True)
|
|
||||||
|
|
||||||
# 可达包络自动注入:AB.export 刚重写过 mesh_arm.json(包络件会被冲掉),
|
# 可达包络自动注入:AB.export 刚重写过 mesh_arm.json(包络件会被冲掉),
|
||||||
# 这里重跑 FK 采样 + 包络网格注入,再重打包一次查看器(约 3~4s,纯数据无 CAD 计算)
|
# 这里重跑 FK 采样 + 包络网格注入,再重打包一次查看器(约 3~4s,纯数据无 CAD 计算)
|
||||||
try:
|
from arm.analysis import ws_sweep, gen_env_mesh
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
||||||
"..", "scratch"))
|
|
||||||
import ws_sweep
|
|
||||||
import gen_env_mesh
|
|
||||||
t_env = time.time()
|
t_env = time.time()
|
||||||
ws_sweep.main()
|
ws_sweep.main()
|
||||||
gen_env_mesh.main()
|
gen_env_mesh.main()
|
||||||
from build_viewer import rebuild_viewer as _rbv
|
rebuild_viewer()
|
||||||
_rbv()
|
|
||||||
print(f"-> 可达包络已自动注入查看器(+{time.time() - t_env:.1f}s)✓\n", flush=True)
|
print(f"-> 可达包络已自动注入查看器(+{time.time() - t_env:.1f}s)✓\n", flush=True)
|
||||||
except Exception as e:
|
|
||||||
print("可达包络自动注入失败(不影响主流程,修复后手动跑 scratch/gen_env_mesh.py + src/build_viewer.py):",
|
|
||||||
e, flush=True)
|
|
||||||
|
|
||||||
print("=" * 80, flush=True)
|
print("=" * 80, flush=True)
|
||||||
print("✅ 机器人手臂编译、导出与查看器发布全流程完成!", flush=True)
|
print("✅ 机器人手臂编译、导出与查看器发布全流程完成!", flush=True)
|
||||||
print("=" * 80, flush=True)
|
print("=" * 80, flush=True)
|
||||||
|
|
||||||
|
# OCC/cadquery 的静态析构在解释器正常退出时必定段错误(实测:光是
|
||||||
|
# `python -c "import arm.cad.helpers"` 就返回 139,与本项目代码无关)。所以入口
|
||||||
|
# 脚本跑完后显式刷缓冲再 os._exit —— 否则全流程已经成功、exit code 却是 139,
|
||||||
|
# 会把上游脚本的 set -e 判成失败。只在 __main__ 出口生效, 被 import 时不触发。
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
os._exit(0)
|
os._exit(0)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""几何与装配层。
|
||||||
|
|
||||||
|
helpers 放置/基础体/网格化等无状态 helper
|
||||||
|
arm_build Joint 装配模型、零件登记(add)、STEP/STL/BOM/mesh 导出
|
||||||
|
part_tools 跨件共用刀法(减重腔、压盖、转接座、圆角常量、螺钉孔位事实源)
|
||||||
|
parts/ 每个打印件一个模块:几何 + 件号/配色/说明 + 装配帧归属
|
||||||
|
"""
|
||||||
@@ -21,10 +21,10 @@ try:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
import cadquery as cq
|
import cadquery as cq
|
||||||
from armcore import (M, C, V, trs, place, bb, bbox_str, cyl, ring, box, holes,
|
from arm.paths import OUT, WEB, DOCS, SRC_STEP, HALF_STEP, CLAMP_STEP
|
||||||
solids_of, mesh_of, MOTOR_TOL, OUT, WEB, DOCS, SRC_STEP, HALF_STEP, CLAMP_STEP)
|
from arm.constants import M, C, MOTOR_TOL
|
||||||
|
from arm.cad.helpers import V, trs, place, bb, bbox_str, cyl, ring, box, holes, solids_of, mesh_of
|
||||||
from OCP.gp import gp_Trsf, gp_Vec, gp_Pnt, gp_Dir, gp_Ax1
|
from OCP.gp import gp_Trsf, gp_Vec, gp_Pnt, gp_Dir, gp_Ax1
|
||||||
from OCP.BRepAlgoAPI import BRepAlgoAPI_Common
|
from OCP.BRepAlgoAPI import BRepAlgoAPI_Common
|
||||||
from OCP.GProp import GProp_GProps
|
from OCP.GProp import GProp_GProps
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Shared constants + CAD/placement helpers for the robot arm build.
|
"""CAD 放置与几何 helper:刚体变换、求交/包围盒、基础体、网格化与 STEP 读取。
|
||||||
|
|
||||||
All motor interface numbers are MEASURED from
|
实测接口数值在 arm/constants.py,仓库路径在 arm/paths.py——本模块不再持有它们。
|
||||||
G:/Robot_v2/Model/Solidworks/电机.STEP and 配件/make_v9.py (see docs/interfaces.md).
|
|
||||||
"""
|
"""
|
||||||
import math
|
import math
|
||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
import cadquery as cq
|
import cadquery as cq
|
||||||
from OCP.gp import gp_Trsf, gp_Vec, gp_XYZ, gp_Mat, gp_Pnt, gp_Dir, gp_Ax1, gp_Ax2, gp_Quaternion
|
from OCP.gp import gp_Trsf, gp_Vec, gp_XYZ, gp_Mat, gp_Pnt, gp_Dir, gp_Ax1, gp_Ax2, gp_Quaternion
|
||||||
@@ -21,68 +18,7 @@ from OCP.TopoDS import TopoDS
|
|||||||
|
|
||||||
V = cq.Vector
|
V = cq.Vector
|
||||||
|
|
||||||
# 路径全部由本文件位置推出,仓库可以整体搬迁;三个环境变量是临时覆盖入口。
|
|
||||||
_HERE = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/")
|
|
||||||
WORKSPACE = os.environ.get("ARM_WORKSPACE") or os.path.dirname(_HERE)
|
|
||||||
ROOT = os.environ.get("ARM_ROOT") or os.path.dirname(WORKSPACE)
|
|
||||||
MODEL = os.environ.get("ARM_MODEL_ROOT") or (ROOT + r"/Model/Solidworks")
|
|
||||||
SRC_STEP = MODEL + r"/电机.STEP"
|
|
||||||
HALF_STEP = MODEL + r"/配件/端面齿半联轴器_相同件x2.STEP"
|
|
||||||
CLAMP_STEP = MODEL + r"/配件/端面齿联轴器_抱箍_两片装配.STEP"
|
|
||||||
OUT = WORKSPACE + r"/out"
|
|
||||||
WEB = WORKSPACE + r"/web"
|
|
||||||
DOCS = WORKSPACE + r"/docs"
|
|
||||||
|
|
||||||
# ---------------- MEASURED: motor module (local frame: origin = PCBA rear face centre, +Z = output) ----
|
|
||||||
M = dict(
|
|
||||||
pcba_w=63.6, # square PCBA across flats
|
|
||||||
pcba_t=9.0, # z 0 -> 9.0
|
|
||||||
rearplate_d=60.0, # z 5.5 -> 10.5
|
|
||||||
hous_d=60.0, # housing OD, z 10.5 -> 52.5
|
|
||||||
hous_len=42.0,
|
|
||||||
cover_d=60.30, # top cover OD (R30.15), z 52.5 -> 56.0
|
|
||||||
cover_z=56.0, # cover front face = module total length
|
|
||||||
flange_z=54.942, # 2nd-stage output flange face
|
|
||||||
neck_d=36.0, # female neck bore in the cover
|
|
||||||
neck_depth=1.0,
|
|
||||||
centre_d=6.5, # centre bore in the flange
|
|
||||||
centre_depth=4.0,
|
|
||||||
m2_bcd=29.0,
|
|
||||||
m2_clock=(8.05, 128.07, 248.06),
|
|
||||||
m2_tap_d=1.85, # M2 pilot for self-tapping in printed material
|
|
||||||
m2_depth=6.0,
|
|
||||||
m2_clear_d=2.75,
|
|
||||||
m2_cb_d=5.2,
|
|
||||||
)
|
|
||||||
# ---------------- MEASURED: face-spline coupling (existing, printed by the user) ------------------------
|
|
||||||
C = dict(
|
|
||||||
n_teeth=12, tooth_r0=19.0, tooth_r1=24.5, tooth_s=2.5,
|
|
||||||
flange_t=4.50, part_z=5.560, # mirrored stack
|
|
||||||
hub_r=27.0,
|
|
||||||
gap=9.00, # cover-face to cover-face when closed
|
|
||||||
clamp_od_r=30.0, clamp_len=7.80,
|
|
||||||
lug_r1=37.0, lug_t=4.50,
|
|
||||||
bolt_r=32.50, bolt_d=3.80,
|
|
||||||
z0=1.66, z1=9.46, # clamp envelope in the flange frame (motor cover face = 1.060)
|
|
||||||
)
|
|
||||||
# axial stations, motor local frame:
|
|
||||||
COVER_Z = M["cover_z"] # 56.0
|
|
||||||
HUB_FACE_Z = COVER_Z + C["gap"] # 65.0 = mating face of the driven hub
|
|
||||||
CLAMP_R = C["clamp_od_r"] + 0.6 # radial keep-out around the joint axis
|
|
||||||
LUG_R = C["lug_r1"] + 1.0 # radial keep-out where the bolt lugs are
|
|
||||||
# ---------------- clearances / print rules -------------------------------------------------------------
|
|
||||||
CLR = dict(bore_slip=0.20, pilot=0.10, hole_3=3.4, hole_27=2.8, wall_min=2.0)
|
|
||||||
FIT = dict(motor_bore_d=M["hous_d"] + 2 * 0.15, cover_cb_d=M["cover_d"] + 2 * 0.35)
|
|
||||||
|
|
||||||
# 电机【轴向长度】公差(用户 2026-10-07 要求:余量偏大稍微减去 1mm,由 2mm 改为 1mm)。
|
|
||||||
# 这条链是"后端板螺钉 + 前端压盖螺钉"两个刚性夹持面,螺钉头一旦坐死在沉孔台阶上
|
|
||||||
# 就再也不能进给,所以公差没法靠"拧紧"消化,只能靠两件事吃掉(见 arm_build.Joint.pocket
|
|
||||||
# 与 motor_front_clamp):
|
|
||||||
# 长出来的 0~+1mm -> 后端板 4xM3 螺钉头下垫 0~1mm 垫片圈(螺钉被垫高 -> 电机被拉回)
|
|
||||||
# 短下去的 0~-1mm -> 电机尾端面与后端板承压面之间垫 0~1mm 垫片(承压面已按 MOTOR_TOL
|
|
||||||
# 让空,见 Joint.pocket 的 REAR_SEAT)
|
|
||||||
# 两边都不动压盖/凸台的几何,关节轴向长度不变。
|
|
||||||
MOTOR_TOL = 1.0
|
|
||||||
|
|
||||||
# ---------------- geometry helpers --------------------------------------------------------------------
|
# ---------------- geometry helpers --------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""跨件共用的刀法与统一配方:X 向基础体、减重腔 helper、电机前端压盖、止口 hub、pitch->roll 转接座骨架。"""
|
"""跨件共用的刀法与统一配方:X 向基础体、减重腔 helper、电机前端压盖、止口 hub、pitch->roll 转接座骨架。"""
|
||||||
import arm_build as AB
|
from arm.cad import arm_build as AB
|
||||||
import cadquery as cq
|
import cadquery as cq
|
||||||
import human_layout as HL
|
from arm import kinematics as HL
|
||||||
import math
|
import math
|
||||||
from armcore import cyl, place
|
from arm.cad.helpers import cyl, place
|
||||||
from arm_build import COVER_Z, R, T, Ycyl, bx, prism
|
from arm.cad.arm_build import COVER_Z, R, T, Ycyl, bx, prism
|
||||||
from human_layout import X_ARM, Y_AXIS, Y_COVER
|
from arm.kinematics import X_ARM, Y_AXIS, Y_COVER
|
||||||
|
|
||||||
|
|
||||||
Y_MID = Y_AXIS
|
Y_MID = Y_AXIS
|
||||||
|
|
||||||
@@ -3,6 +3,6 @@
|
|||||||
|
|
||||||
arm_model.build() 只按 PART_MODULES 顺序收集 specs();改哪件就开哪个文件。
|
arm_model.build() 只按 PART_MODULES 顺序收集 specs();改哪件就开哪个文件。
|
||||||
"""
|
"""
|
||||||
from parts import p01_column, p02_base, p03_girdle, p04_fork, p05_roll_base, p06_humerus, p07_forearm, p08_wrist, p09_flange
|
from arm.cad.parts import p01_column, p02_base, p03_girdle, p04_fork, p05_roll_base, p06_humerus, p07_forearm, p08_wrist, p09_flange
|
||||||
|
|
||||||
PART_MODULES = [p01_column, p02_base, p03_girdle, p04_fork, p05_roll_base, p06_humerus, p07_forearm, p08_wrist, p09_flange]
|
PART_MODULES = [p01_column, p02_base, p03_girdle, p04_fork, p05_roll_base, p06_humerus, p07_forearm, p08_wrist, p09_flange]
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""P01 立柱三段(A/B/C):躯干承力柱 + 阶梯榫卯与防反装对锁孔。"""
|
"""P01 立柱三段(A/B/C):躯干承力柱 + 阶梯榫卯与防反装对锁孔。"""
|
||||||
import cadquery as cq
|
import cadquery as cq
|
||||||
from armcore import cyl
|
from arm.cad.helpers import cyl
|
||||||
from arm_build import Ycyl, bx
|
from arm.cad.arm_build import Ycyl, bx
|
||||||
from human_layout import Y_AXIS, Z_CHEST1
|
from arm.kinematics import Y_AXIS, Z_CHEST1
|
||||||
from part_tools import Xcyl
|
from arm.cad.part_tools import Xcyl
|
||||||
|
|
||||||
|
|
||||||
# ========== 躯干部分 ==========
|
# ========== 躯干部分 ==========
|
||||||
# ========== 躯干/测试台架部分 (后置防倾翻高刚性机架) ==========
|
# ========== 躯干/测试台架部分 (后置防倾翻高刚性机架) ==========
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""P02 底座三段(中核心 / 前向力臂 / 后向力臂):后置防倾翻机架与防呆母榫。"""
|
"""P02 底座三段(中核心 / 前向力臂 / 后向力臂):后置防倾翻机架与防呆母榫。"""
|
||||||
import cadquery as cq
|
import cadquery as cq
|
||||||
from armcore import cyl
|
from arm.cad.helpers import cyl
|
||||||
from parts.p01_column import X_COL, Y_COL
|
from arm.cad.parts.p01_column import X_COL, Y_COL
|
||||||
|
|
||||||
|
|
||||||
def make_p02_poka_yoke():
|
def make_p02_poka_yoke():
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
"""P03 肩吊臂:板式桁架 + 侧开式电机托架 + M1 后置法兰。"""
|
"""P03 肩吊臂:板式桁架 + 侧开式电机托架 + M1 后置法兰。"""
|
||||||
import cadquery as cq
|
import cadquery as cq
|
||||||
import math
|
import math
|
||||||
from armcore import cyl
|
from arm.cad.helpers import cyl
|
||||||
from arm_build import Yplate, bx
|
from arm.cad.arm_build import Yplate, bx
|
||||||
from human_layout import X_BAY_EXT, Y_AXIS, Z_CHEST1, Z_R1, Z_RL
|
from arm.kinematics import X_BAY_EXT, Y_AXIS, Z_CHEST1, Z_R1, Z_RL
|
||||||
from part_tools import Xcyl, make_surface_tie_bridge
|
from arm.cad.part_tools import Xcyl, make_surface_tie_bridge
|
||||||
from parts.p01_column import X_COL, Y_COL, Z_LOCK
|
from arm.cad.parts.p01_column import X_COL, Y_COL, Z_LOCK
|
||||||
|
|
||||||
|
|
||||||
def p_girdle(j1):
|
def p_girdle(j1):
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""P04 肩叉:J1 从动杯体、M2 前端压盖、立柱 U 形减重槽、Hermite 弧面前脸。"""
|
"""P04 肩叉:J1 从动杯体、M2 前端压盖、立柱 U 形减重槽、Hermite 弧面前脸。"""
|
||||||
import arm_build as AB
|
from arm.cad import arm_build as AB
|
||||||
import cadquery as cq
|
import cadquery as cq
|
||||||
import math
|
import math
|
||||||
from armcore import cyl
|
from arm.cad.helpers import cyl
|
||||||
from arm_build import COVER_Z, Ycyl, bx
|
from arm.cad.arm_build import COVER_Z, Ycyl, bx
|
||||||
from human_layout import X_ARM, X_FORK_GAP, Y_AXIS, Y_COVER, Y_REAR, Z_RL, Z_SH
|
from arm.kinematics import X_ARM, X_FORK_GAP, Y_AXIS, Y_COVER, Y_REAR, Z_RL, Z_SH
|
||||||
from OCP.BRepAlgoAPI import BRepAlgoAPI_Common
|
from OCP.BRepAlgoAPI import BRepAlgoAPI_Common
|
||||||
from part_tools import (Xplate, clamp_hole_drills, cut_motor_fence, hollow_channel,
|
from arm.cad.part_tools import (Xplate, clamp_hole_drills, cut_motor_fence, hollow_channel,
|
||||||
hub_m2_holes, make_surface_tie_bridge, motor_front_clamp)
|
hub_m2_holes, make_surface_tie_bridge, motor_front_clamp)
|
||||||
|
|
||||||
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""P05 大臂旋转座:J2 俯仰 -> J3 滚转(复用 part_tools 的 pitch->roll 骨架)。"""
|
"""P05 大臂旋转座:J2 俯仰 -> J3 滚转(复用 part_tools 的 pitch->roll 骨架)。"""
|
||||||
import cadquery as cq
|
import cadquery as cq
|
||||||
from human_layout import Z_R3, Z_SH
|
from arm.kinematics import Z_R3, Z_SH
|
||||||
from part_tools import make_pitch_to_roll_bracket
|
from arm.cad.part_tools import make_pitch_to_roll_bracket
|
||||||
|
|
||||||
|
|
||||||
def p_shoulder_roll_base(j2, j3, half=None):
|
def p_shoulder_roll_base(j2, j3, half=None):
|
||||||
"""P05_大臂旋转座_螺旋体30%_墙4底5_竖打(深度圆角优化版)
|
"""P05_大臂旋转座_螺旋体30%_墙4底5_竖打(深度圆角优化版)
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""P06 大臂骨架:J3 滚转 -> J4 俯仰的 Loft 空心梁。"""
|
"""P06 大臂骨架:J3 滚转 -> J4 俯仰的 Loft 空心梁。"""
|
||||||
import arm_build as AB
|
from arm.cad import arm_build as AB
|
||||||
import cadquery as cq
|
import cadquery as cq
|
||||||
from armcore import cyl
|
from arm.cad.helpers import cyl
|
||||||
from arm_build import COVER_Z, Ycyl, bx
|
from arm.cad.arm_build import COVER_Z, Ycyl, bx
|
||||||
from human_layout import X_ARM, Y_AXIS, Y_COVER, Y_REAR, Z_EL, Z_R3
|
from arm.kinematics import X_ARM, Y_AXIS, Y_COVER, Y_REAR, Z_EL, Z_R3
|
||||||
from part_tools import (
|
from arm.cad.part_tools import (
|
||||||
CLAMP_BACKOFF, CLAMP_CLR, CLAMP_T, cut_motor_fence, hub_m2_holes,
|
CLAMP_BACKOFF, CLAMP_CLR, CLAMP_T, cut_motor_fence, hub_m2_holes,
|
||||||
make_surface_tie_bridge_neg_x, motor_front_clamp,
|
make_surface_tie_bridge_neg_x, motor_front_clamp,
|
||||||
)
|
)
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""P07 小臂骨架:J4 -> J5 的 L 型结构 + 顶板减重槽。"""
|
"""P07 小臂骨架:J4 -> J5 的 L 型结构 + 顶板减重槽。"""
|
||||||
import arm_build as AB
|
from arm.cad import arm_build as AB
|
||||||
import cadquery as cq
|
import cadquery as cq
|
||||||
import human_layout as HL
|
from arm import kinematics as HL
|
||||||
import math
|
import math
|
||||||
from armcore import cyl
|
from arm.cad.helpers import cyl
|
||||||
from arm_build import Ycyl, bx
|
from arm.cad.arm_build import Ycyl, bx
|
||||||
from human_layout import PLATE_T, X_ARM, Y_COVER, Y_HUB, Y_REAR, Z_EL, Z_WR
|
from arm.kinematics import PLATE_T, X_ARM, Y_COVER, Y_HUB, Y_REAR, Z_EL, Z_WR
|
||||||
from part_tools import (
|
from arm.cad.part_tools import (
|
||||||
FILLET_SMALL, cut_motor_fence, hollow_channel, hub_m2_holes,
|
FILLET_SMALL, cut_motor_fence, hollow_channel, hub_m2_holes,
|
||||||
make_surface_tie_bridge_neg_x, motor_front_clamp,
|
make_surface_tie_bridge_neg_x, motor_front_clamp,
|
||||||
)
|
)
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""P08 腕偏转座:J5 俯仰 -> J6 滚转(复用 part_tools 的 pitch->roll 骨架)。"""
|
"""P08 腕偏转座:J5 俯仰 -> J6 滚转(复用 part_tools 的 pitch->roll 骨架)。"""
|
||||||
from human_layout import Z_R6, Z_WR
|
from arm.kinematics import Z_R6, Z_WR
|
||||||
from part_tools import make_pitch_to_roll_bracket
|
from arm.cad.part_tools import make_pitch_to_roll_bracket
|
||||||
|
|
||||||
|
|
||||||
def p_wrist_bracket(j5, j6):
|
def p_wrist_bracket(j5, j6):
|
||||||
"""P08_腕偏转座_螺旋体25%_墙4底5_竖打(深度圆角优化版)
|
"""P08_腕偏转座_螺旋体25%_墙4底5_竖打(深度圆角优化版)
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""P09 工具法兰:末端法兰盘与 J6 直锁 hub。"""
|
"""P09 工具法兰:末端法兰盘与 J6 直锁 hub。"""
|
||||||
import math
|
import math
|
||||||
from armcore import cyl
|
from arm.cad.helpers import cyl
|
||||||
from arm_build import D2R
|
from arm.cad.arm_build import D2R
|
||||||
from human_layout import PLATE_T, X_ARM, Y_AXIS, Z_HUB6
|
from arm.kinematics import PLATE_T, X_ARM, Y_AXIS, Z_HUB6
|
||||||
from part_tools import CHAMFER_EDGE, CLAMP_BACKOFF, FILLET_SMALL, FILLET_TINY, make_direct_hub
|
from arm.cad.part_tools import CHAMFER_EDGE, CLAMP_BACKOFF, FILLET_SMALL, FILLET_TINY, make_direct_hub
|
||||||
|
|
||||||
|
|
||||||
def p_tool_flange(j6):
|
def p_tool_flange(j6):
|
||||||
"""P09_工具法兰_螺旋体30%_墙4底5_竖打 深度圆角优化版
|
"""P09_工具法兰_螺旋体30%_墙4底5_竖打 深度圆角优化版
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""实测接口数值:电机模组、端面齿联轴器、配合公差与电机轴向公差。
|
||||||
|
|
||||||
|
全部 MEASURED 自 G:/Robot_v2/Model/Solidworks/电机.STEP 与配件(见 docs/interfaces.md)。
|
||||||
|
改之前先确认量的是同一个基准面。"""
|
||||||
|
|
||||||
|
# 注:本段由 armcore.py 原样搬来,行内容与注释未作改动。
|
||||||
|
|
||||||
|
# ---------------- MEASURED: motor module (local frame: origin = PCBA rear face centre, +Z = output) ----
|
||||||
|
M = dict(
|
||||||
|
pcba_w=63.6, # square PCBA across flats
|
||||||
|
pcba_t=9.0, # z 0 -> 9.0
|
||||||
|
rearplate_d=60.0, # z 5.5 -> 10.5
|
||||||
|
hous_d=60.0, # housing OD, z 10.5 -> 52.5
|
||||||
|
hous_len=42.0,
|
||||||
|
cover_d=60.30, # top cover OD (R30.15), z 52.5 -> 56.0
|
||||||
|
cover_z=56.0, # cover front face = module total length
|
||||||
|
flange_z=54.942, # 2nd-stage output flange face
|
||||||
|
neck_d=36.0, # female neck bore in the cover
|
||||||
|
neck_depth=1.0,
|
||||||
|
centre_d=6.5, # centre bore in the flange
|
||||||
|
centre_depth=4.0,
|
||||||
|
m2_bcd=29.0,
|
||||||
|
m2_clock=(8.05, 128.07, 248.06),
|
||||||
|
m2_tap_d=1.85, # M2 pilot for self-tapping in printed material
|
||||||
|
m2_depth=6.0,
|
||||||
|
m2_clear_d=2.75,
|
||||||
|
m2_cb_d=5.2,
|
||||||
|
)
|
||||||
|
# ---------------- MEASURED: face-spline coupling (existing, printed by the user) ------------------------
|
||||||
|
C = dict(
|
||||||
|
n_teeth=12, tooth_r0=19.0, tooth_r1=24.5, tooth_s=2.5,
|
||||||
|
flange_t=4.50, part_z=5.560, # mirrored stack
|
||||||
|
hub_r=27.0,
|
||||||
|
gap=9.00, # cover-face to cover-face when closed
|
||||||
|
clamp_od_r=30.0, clamp_len=7.80,
|
||||||
|
lug_r1=37.0, lug_t=4.50,
|
||||||
|
bolt_r=32.50, bolt_d=3.80,
|
||||||
|
z0=1.66, z1=9.46, # clamp envelope in the flange frame (motor cover face = 1.060)
|
||||||
|
)
|
||||||
|
# axial stations, motor local frame:
|
||||||
|
COVER_Z = M["cover_z"] # 56.0
|
||||||
|
HUB_FACE_Z = COVER_Z + C["gap"] # 65.0 = mating face of the driven hub
|
||||||
|
CLAMP_R = C["clamp_od_r"] + 0.6 # radial keep-out around the joint axis
|
||||||
|
LUG_R = C["lug_r1"] + 1.0 # radial keep-out where the bolt lugs are
|
||||||
|
# ---------------- clearances / print rules -------------------------------------------------------------
|
||||||
|
CLR = dict(bore_slip=0.20, pilot=0.10, hole_3=3.4, hole_27=2.8, wall_min=2.0)
|
||||||
|
FIT = dict(motor_bore_d=M["hous_d"] + 2 * 0.15, cover_cb_d=M["cover_d"] + 2 * 0.35)
|
||||||
|
|
||||||
|
# 电机【轴向长度】公差(用户 2026-10-07 要求:余量偏大稍微减去 1mm,由 2mm 改为 1mm)。
|
||||||
|
# 这条链是"后端板螺钉 + 前端压盖螺钉"两个刚性夹持面,螺钉头一旦坐死在沉孔台阶上
|
||||||
|
# 就再也不能进给,所以公差没法靠"拧紧"消化,只能靠两件事吃掉(见 arm_build.Joint.pocket
|
||||||
|
# 与 motor_front_clamp):
|
||||||
|
# 长出来的 0~+1mm -> 后端板 4xM3 螺钉头下垫 0~1mm 垫片圈(螺钉被垫高 -> 电机被拉回)
|
||||||
|
# 短下去的 0~-1mm -> 电机尾端面与后端板承压面之间垫 0~1mm 垫片(承压面已按 MOTOR_TOL
|
||||||
|
# 让空,见 Joint.pocket 的 REAR_SEAT)
|
||||||
|
# 两边都不动压盖/凸台的几何,关节轴向长度不变。
|
||||||
|
MOTOR_TOL = 1.0
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""数据注入与工程图:螺钉实例注入 mesh、四张工程图 SVG 生成。
|
||||||
|
"""
|
||||||
@@ -17,8 +17,15 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
from arm.paths import WEB, DOCS
|
||||||
from armcore import WEB, DOCS
|
|
||||||
|
# 这两个脚本的顶层就是"算完并写文件",被 import 时会静默重写 travel/mesh 派生产物
|
||||||
|
# (实测:一次 importlib 冒烟测试就把 docs/travel.json 重写了一遍)。这里改成直接炸。
|
||||||
|
if __name__ != "__main__":
|
||||||
|
raise RuntimeError(
|
||||||
|
"draw_sheet 是脚本:顶层代码一执行就重写产物,不能 import。"
|
||||||
|
"要用就跑 python -m arm.dataio.draw_sheet。")
|
||||||
|
|
||||||
D = json.load(io.open(WEB + "/mesh_arm.json", encoding="utf-8"))
|
D = json.load(io.open(WEB + "/mesh_arm.json", encoding="utf-8"))
|
||||||
P, LAY, MS = D["parts"], D["layout"], D["measured"]
|
P, LAY, MS = D["parts"], D["layout"], D["measured"]
|
||||||
MT, CT = MS["M"], MS["C"]
|
MT, CT = MS["M"], MS["C"]
|
||||||
@@ -329,4 +336,3 @@ for n, s in zip(("sheet_front", "sheet_joint", "sheet_exploded", "sheet_side"),
|
|||||||
io.open(WEB + "/sheets.jsdata", "w", encoding="utf-8").write(json.dumps(sheets, ensure_ascii=False))
|
io.open(WEB + "/sheets.jsdata", "w", encoding="utf-8").write(json.dumps(sheets, ensure_ascii=False))
|
||||||
print("sheets: %d (%d KB) -> docs/sheet_{front,joint,exploded,side}.svg" % (
|
print("sheets: %d (%d KB) -> docs/sheet_{front,joint,exploded,side}.svg" % (
|
||||||
len(sheets), sum(len(s) for s in sheets) // 1024))
|
len(sheets), sum(len(s) for s in sheets) // 1024))
|
||||||
os._exit(0)
|
|
||||||
@@ -38,9 +38,8 @@ docs/screws.json 是紧固件清单的唯一事实源(规格 / 长度 / 数量
|
|||||||
|
|
||||||
长度处理:S08/S10 螺杆按真实长度会伸到底座底面(z=0)以下悬空(工作台/预埋螺母未建模),
|
长度处理:S08/S10 螺杆按真实长度会伸到底座底面(z=0)以下悬空(工作台/预埋螺母未建模),
|
||||||
这里按可见段截断到 z=0,头部与孔位仍按 BOM 规格,避免出现"悬空螺杆"。
|
这里按可见段截断到 z=0,头部与孔位仍按 BOM 规格,避免出现"悬空螺杆"。
|
||||||
|
运行:python -m arm.dataio.screw_mesh -> 注入网格(纯数据,秒级,不动 CAD)
|
||||||
运行:python src/screw_mesh.py -> 注入网格(纯数据,秒级,不动 CAD)
|
再 python -m arm.viewer.build_viewer -> 重新打包单文件查看器
|
||||||
再 python src/build_viewer.py -> 重新打包单文件查看器
|
|
||||||
arm_model.py 主流程末尾已自动调用本模块,重跑完整构建不会丢失螺钉。
|
arm_model.py 主流程末尾已自动调用本模块,重跑完整构建不会丢失螺钉。
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
@@ -52,14 +51,9 @@ try:
|
|||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
from arm.paths import WEB, DOCS
|
||||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
from arm import kinematics as HL
|
||||||
_ROOT = os.path.dirname(_HERE)
|
from arm.cad.part_tools import CLAMP_BCD, CLAMP_CLOCK
|
||||||
sys.path.insert(0, _HERE)
|
|
||||||
|
|
||||||
import human_layout as HL # noqa: E402
|
|
||||||
from part_tools import CLAMP_BCD, CLAMP_CLOCK # noqa: E402 压盖孔位唯一事实源
|
|
||||||
|
|
||||||
X_COL = -115.0 # 立柱中心(与 parts/p01_column.py 的 X_COL 一致)
|
X_COL = -115.0 # 立柱中心(与 parts/p01_column.py 的 X_COL 一致)
|
||||||
Y_COL = HL.Y_AXIS # 128.0
|
Y_COL = HL.Y_AXIS # 128.0
|
||||||
GRP_PAR = {"J2": "J1", "J3": "J2", "J4": "J3", "J5": "J4", "J6": "J5"}
|
GRP_PAR = {"J2": "J1", "J3": "J2", "J4": "J3", "J5": "J4", "J6": "J5"}
|
||||||
@@ -293,11 +287,11 @@ def instances(m2_bcd, m2_clock):
|
|||||||
|
|
||||||
# ---------------------------------------------------------------- 注入
|
# ---------------------------------------------------------------- 注入
|
||||||
def main():
|
def main():
|
||||||
path = os.path.join(_ROOT, "web", "mesh_arm.json")
|
path = WEB + "/mesh_arm.json"
|
||||||
print("读取 %s ..." % path, flush=True)
|
print("读取 %s ..." % path, flush=True)
|
||||||
d = json.load(open(path, encoding="utf-8"))
|
d = json.load(open(path, encoding="utf-8"))
|
||||||
M = d["measured"]["M"]
|
M = d["measured"]["M"]
|
||||||
specs = json.load(open(os.path.join(_ROOT, "docs", "screws.json"), encoding="utf-8"))
|
specs = json.load(open(DOCS + "/screws.json", encoding="utf-8"))
|
||||||
byid = {s["id"]: s for s in specs}
|
byid = {s["id"]: s for s in specs}
|
||||||
|
|
||||||
d["parts"] = [p for p in d["parts"] if not p.get("screw")] # 幂等:先清旧螺钉
|
d["parts"] = [p for p in d["parts"] if not p.get("screw")] # 幂等:先清旧螺钉
|
||||||
@@ -344,3 +338,10 @@ def main():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
# OCC/cadquery 的静态析构在解释器正常退出时必定段错误(实测:光是
|
||||||
|
# `python -c "import arm.cad.helpers"` 就返回 139,与本项目代码无关)。所以入口
|
||||||
|
# 脚本跑完后显式刷缓冲再 os._exit —— 否则全流程已经成功、exit code 却是 139,
|
||||||
|
# 会把上游脚本的 set -e 判成失败。只在 __main__ 出口生效, 被 import 时不触发。
|
||||||
|
sys.stdout.flush()
|
||||||
|
os._exit(0)
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""仓库内路径的唯一来源。
|
||||||
|
|
||||||
|
原先这些常量是硬编码的 G:/Robot_v2 绝对路径,仓库换盘符即全断;现在由本文件
|
||||||
|
位置推出。REPO 的父目录就是原来的 ROOT(=G:/Robot_v2),所以外部 STEP 输入的解析
|
||||||
|
结果与从前逐字相同。三个 ARM_* 环境变量是临时覆盖入口。
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
_HERE = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/") # .../arm
|
||||||
|
REPO = os.environ.get("ARM_WORKSPACE") or os.path.dirname(_HERE)
|
||||||
|
ROOT = os.environ.get("ARM_ROOT") or os.path.dirname(REPO)
|
||||||
|
MODEL = os.environ.get("ARM_MODEL_ROOT") or (ROOT + r"/Model/Solidworks")
|
||||||
|
SRC_STEP = MODEL + r"/电机.STEP"
|
||||||
|
HALF_STEP = MODEL + r"/配件/端面齿半联轴器_相同件x2.STEP"
|
||||||
|
CLAMP_STEP = MODEL + r"/配件/端面齿联轴器_抱箍_两片装配.STEP"
|
||||||
|
OUT = REPO + r"/out"
|
||||||
|
WEB = REPO + r"/web"
|
||||||
|
DOCS = REPO + r"/docs"
|
||||||
|
# 可达包络 FK 采样的缓存(ws_cloud.npz / ws_metrics.json)。原先躺在 gitignore 的
|
||||||
|
# scratch/ 里,构建却依赖它——缺了新克隆就跑不出 ENV 件。
|
||||||
|
CACHE = REPO + r"/data/cache"
|
||||||
|
# 查看器模板。S4 拆成 webapp/index.html + js/** 后这条即作废。
|
||||||
|
TPL = REPO + r"/src/viewer_tpl.html"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""查看器侧:数据打包、本地静态服务、headless 截图。
|
||||||
|
"""
|
||||||
@@ -9,9 +9,7 @@ try:
|
|||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
from arm.paths import WEB, DOCS, REPO, TPL
|
||||||
from armcore import WEB, DOCS, WORKSPACE
|
|
||||||
SRC = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@@ -36,7 +34,7 @@ def rebuild_viewer():
|
|||||||
DOCS + "/screws.json") else "[]"
|
DOCS + "/screws.json") else "[]"
|
||||||
ik_solver = io.open(WEB + "/arm-ik-solver.js", encoding="utf-8").read()
|
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()
|
ik_glue = io.open(WEB + "/ik-glue.js", encoding="utf-8").read()
|
||||||
tpl = io.open(SRC + "/viewer_tpl.html", encoding="utf-8").read()
|
tpl = io.open(TPL, encoding="utf-8").read()
|
||||||
print(f" 完成 ({len(data)/1024:.0f} KB) ✓", flush=True)
|
print(f" 完成 ({len(data)/1024:.0f} KB) ✓", flush=True)
|
||||||
|
|
||||||
print(" - 打包生成全内置单文件 HTML...", end="", flush=True)
|
print(" - 打包生成全内置单文件 HTML...", end="", flush=True)
|
||||||
@@ -56,7 +54,7 @@ def rebuild_viewer():
|
|||||||
# 于是只写成功第一份、另外两份静默停在旧版本 —— 表现为"代码改了但网页没变"。
|
# 于是只写成功第一份、另外两份静默停在旧版本 —— 表现为"代码改了但网页没变"。
|
||||||
# 实测就是这个坑:18:52 那次 dst 是新的,web/index.html 和根目录那份还停在 18:12。
|
# 实测就是这个坑:18:52 那次 dst 是新的,web/index.html 和根目录那份还停在 18:12。
|
||||||
ok, failed = True, []
|
ok, failed = True, []
|
||||||
for path in (dst, WEB + "/index.html", WORKSPACE + "/机器人手臂3D查看器.html"):
|
for path in (dst, WEB + "/index.html", REPO + "/机器人手臂3D查看器.html"):
|
||||||
try:
|
try:
|
||||||
io.open(path, "w", encoding="utf-8").write(out)
|
io.open(path, "w", encoding="utf-8").write(out)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -68,12 +66,11 @@ def rebuild_viewer():
|
|||||||
print("!! 以下副本写入失败(多半是浏览器正打开着该文件,Windows 独占锁):", flush=True)
|
print("!! 以下副本写入失败(多半是浏览器正打开着该文件,Windows 独占锁):", flush=True)
|
||||||
for f in failed:
|
for f in failed:
|
||||||
print(" - %s" % f, flush=True)
|
print(" - %s" % f, flush=True)
|
||||||
print(" 关掉对应页面后重跑 python src/build_viewer.py 即可。\n", flush=True)
|
print(" 关掉对应页面后重跑 python -m arm.viewer.build_viewer 即可。\n", flush=True)
|
||||||
else:
|
else:
|
||||||
print(f"-> 3D查看器已更新至: {dst}、{WEB}/index.html 与 根目录/机器人手臂3D查看器.html\n",
|
print(f"-> 3D查看器已更新至: {dst}、{WEB}/index.html 与 根目录/机器人手臂3D查看器.html\n",
|
||||||
flush=True)
|
flush=True)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
rebuild_viewer()
|
rebuild_viewer()
|
||||||
os._exit(0)
|
|
||||||
|
|
||||||
@@ -11,9 +11,7 @@ try:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
from arm.paths import WEB, DOCS
|
||||||
from armcore import ROOT, WORKSPACE, WEB, DOCS
|
|
||||||
|
|
||||||
SHOTS = DOCS + "/shots"
|
SHOTS = DOCS + "/shots"
|
||||||
HTML = WEB + "/机器人手臂3D查看器.html"
|
HTML = WEB + "/机器人手臂3D查看器.html"
|
||||||
CAND = [r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
|
CAND = [r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
|
||||||
@@ -36,6 +34,18 @@ VIEWS = {
|
|||||||
"user_smooth_harness": "?az=1.50&po=1.45&rd=340&tx=-15&ty=560&tz=-128&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",
|
"user_contour_harness": "?az=1.5708&po=1.50&rd=360&tx=-15&ty=320&tz=-128&xray=0",
|
||||||
"02_front": "?cam=front",
|
"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",
|
"03_side": "?cam=side",
|
||||||
"04_top": "?cam=top",
|
"04_top": "?cam=top",
|
||||||
"05_exploded": "?cam=iso&expl=100",
|
"05_exploded": "?cam=iso&expl=100",
|
||||||
@@ -168,4 +178,3 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
print("edge:", EDGE)
|
print("edge:", EDGE)
|
||||||
print("all shots ok:", all(results))
|
print("all shots ok:", all(results))
|
||||||
os._exit(0)
|
|
||||||
|
Before Width: | Height: | Size: 294 KiB |
|
Before Width: | Height: | Size: 363 KiB |
|
Before Width: | Height: | Size: 296 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 184 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 129 KiB |
@@ -2933,6 +2933,21 @@ requestAnimationFrame(loop);
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (q.get('harn') === '0') { const b = document.getElementById('bHarn'); if (b && b.classList.contains('on')) b.onclick(); }
|
if (q.get('harn') === '0') { const b = document.getElementById('bHarn'); if (b && b.classList.contains('on')) b.onclick(); }
|
||||||
|
/* 视图模式与工程图状态也要能用 URL 驱动:否则工程图/装配向导这两种模式(约占一半
|
||||||
|
CSS 与 700 行 JS)没法用 headless 截图回归,改动只能靠人眼抽查。 */
|
||||||
|
if (q.has('view')) {
|
||||||
|
const t = document.querySelector('.tab[data-view="' + q.get('view') + '"]');
|
||||||
|
if (t) t.click();
|
||||||
|
}
|
||||||
|
if (q.has('sheet')) {
|
||||||
|
const b = document.querySelector('#sheetTabs .sheet-tab[data-sheet="' + q.get('sheet') + '"]');
|
||||||
|
if (b) b.click();
|
||||||
|
}
|
||||||
|
if (q.has('theme')) {
|
||||||
|
const b = document.querySelector('.sheet-btn[data-theme="' + q.get('theme') + '"]');
|
||||||
|
if (b) b.click();
|
||||||
|
}
|
||||||
|
if (q.has('guide')) { const g = +q.get('guide'); if (isFinite(g)) renderGuideStep(g); }
|
||||||
if (q.has('j6')) { const el = document.getElementById('sJ6');
|
if (q.has('j6')) { const el = document.getElementById('sJ6');
|
||||||
if (el) { el.value = q.get('j6'); el.dispatchEvent(new Event('input')); } }
|
if (el) { el.value = q.get('j6'); el.dispatchEvent(new Event('input')); } }
|
||||||
if (q.has('hide')) {
|
if (q.has('hide')) {
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""核验脚本与基线。跑法:从仓根 `python -m tests.test_harness_collision`
|
||||||
|
(`-m` 把仓根放进 sys.path,脚本因此能 import arm.*;直接 python tests/xxx.py 会找不到包)。
|
||||||
|
"""
|
||||||
|
After Width: | Height: | Size: 294 KiB |
|
After Width: | Height: | Size: 363 KiB |
|
After Width: | Height: | Size: 296 KiB |
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"note": "实测同一构建两轮:mean<=0.00174, 单像素最大差 12, 无像素差>8。阈值取约 10x 余量;超过即视为真实渲染回归。",
|
||||||
|
"mean_max": 0.02,
|
||||||
|
"pixel_max": 32,
|
||||||
|
"views": {
|
||||||
|
"harness_xray_internal": {
|
||||||
|
"mean": 0.00087,
|
||||||
|
"mx": 6
|
||||||
|
},
|
||||||
|
"harness_p07_xray": {
|
||||||
|
"mean": 0.00097,
|
||||||
|
"mx": 5
|
||||||
|
},
|
||||||
|
"harness_p04_p05_xray": {
|
||||||
|
"mean": 0.00174,
|
||||||
|
"mx": 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
af5c9d74bddf7d795bd38f4371fa61ec *docs/shots/01_iso.png
|
|
||||||
c315879cfd09f35efa4ca4ecc1564b65 *docs/shots/02_front.png
|
|
||||||
b3157f4453cf96b930f065e134a2d18b *docs/shots/03_side.png
|
|
||||||
068fc4bc3eec481ff7d7b1d4da2aad88 *docs/shots/04_top.png
|
|
||||||
fbb95ea7a9229195d80c6f487021ae2b *docs/shots/05_exploded.png
|
|
||||||
cbea750a1b52c5e8d061a9e3f4c981fe *docs/shots/06_wrist.png
|
|
||||||
bb3dc393e4cd8f2c399c343aba0b3d35 *docs/shots/07_joint.png
|
|
||||||
72e86ae7743ff7416e247d19556dc657 *docs/shots/08_pose.png
|
|
||||||
af5c9d74bddf7d795bd38f4371fa61ec *docs/shots/09_sheet.png
|
|
||||||
af5c9d74bddf7d795bd38f4371fa61ec *docs/shots/10_solid.png
|
|
||||||
e97dcf60224e2ad968bddf02ea680e57 *docs/shots/11_grip_open.png
|
|
||||||
8daa2255af050a225609cebbc490d3b9 *docs/shots/11_tool_flange.png
|
|
||||||
002a1f4a2b65744515f5b54364fcc2a8 *docs/shots/12_elbow_flare.png
|
|
||||||
b2f1454a6009a8b44ddd41be9efdaff5 *docs/shots/13_forearm_side.png
|
|
||||||
053a9be5edad6b577bbe902c428aa135 *docs/shots/H2_前后圆角_34.png
|
|
||||||
6d6feec7d65ea08e1175013fa3691869 *docs/shots/H2_前后圆角_侧视.png
|
|
||||||
f17fd52c9f2eee907da3c76a80cd8073 *docs/shots/H2_前后圆角_正面.png
|
|
||||||
9519d95093d835f72dfc008c4febc1a3 *docs/shots/H_正式_34视角.png
|
|
||||||
cf61473cd470b5111b678f2fa01f8bcc *docs/shots/H_正式_侧视.png
|
|
||||||
2385a6f605834163eb821a56cfa5be6a *docs/shots/H_正式_整机.png
|
|
||||||
001e85eca69fc3f1503d41b0a7366706 *docs/shots/P04_H落地_复核.png
|
|
||||||
351e5b573126d75bc5baf01b55199e5b *docs/shots/P04_前后圆角_复核.png
|
|
||||||
bac1bfb89650c343b33b680c410259e9 *docs/shots/P04_前脸轮廓_改前改后对比.png
|
|
||||||
f54e549cd743495e78e45744f3283d47 *docs/shots/P04_尖角_改后.png
|
|
||||||
3b906449664648c1555e9aadeff83bca *docs/shots/P04_顶部_改前.png
|
|
||||||
ff4ab3362882cb99895c94801896afe7 *docs/shots/P04_顶部_改后.png
|
|
||||||
ae0c77ebf8a51a2541cfb903bd34f86c *docs/shots/P05_斜棱R3圆角_实施完成对比.png
|
|
||||||
aa18505c1201d1490584ced82d16f465 *docs/shots/P05_斜棱圆角_评估对比.png
|
|
||||||
60a8a174ce455f0539503ac1aa3d58f0 *docs/shots/P07_立柱底端圆角_对比.png
|
|
||||||
baf85ba3b5c37d6ffb9c0deb6b8eb7d0 *docs/shots/arm_overview_notch_exact.png
|
|
||||||
07ec81c2fcef975b2b53577f00f5176c *docs/shots/bay_inner_wall.png
|
|
||||||
e39999d2ef28536f24d7f6178fa9a15c *docs/shots/bay_p04_inside.png
|
|
||||||
959e4bf08e174d90a1fe03a75a042070 *docs/shots/bay_p06_inside.png
|
|
||||||
f9d8d58365b4ca693525af5540d39d32 *docs/shots/bay_p07_inside.png
|
|
||||||
1b07f37e5fed9d82886b9518a3839860 *docs/shots/bay_p08_inside.png
|
|
||||||
d8955c69a48a064dc395c7c18b9db49c *docs/shots/bay_section_polar.png
|
|
||||||
90b79bb61cacea2323ca19790d4158b2 *docs/shots/boss_p04.png
|
|
||||||
dcc54259caf06f796e3dec84496abb12 *docs/shots/boss_p05.png
|
|
||||||
9f2c134297f2c7a555a6eaca2e74e4de *docs/shots/boss_p06.png
|
|
||||||
b789564315ca1ad7d6d9d18c96972cba *docs/shots/boss_p07.png
|
|
||||||
8fdee57313c9ea826dcd881d9c4655c8 *docs/shots/compare_harn_off.png
|
|
||||||
b8b89700a7a6a38b80aa1ef0d8966195 *docs/shots/compare_harn_on.png
|
|
||||||
1d0cccff978809bb5e1f357d0f45a28a *docs/shots/crop_circle_detail.png
|
|
||||||
43857f773d225ed4c6425c27f93afc2c *docs/shots/crop_p04_bottom.png
|
|
||||||
9b2f02d28cc0f40c913ca13daad4c4f4 *docs/shots/crop_p04_bottom2.png
|
|
||||||
baa4492159c3e4d7eab0a45f1965e640 *docs/shots/crop_p04_bottom_v2.png
|
|
||||||
f00fbb4fad4c0127cb403d28f91ed557 *docs/shots/crop_p04_side.png
|
|
||||||
5214b01e26e27d8033c54122c215b77b *docs/shots/crop_p04_side_v2.png
|
|
||||||
10a610706648c4d3e2dac056eaef6f6d *docs/shots/crop_side_p05.png
|
|
||||||
7c6c053e0875a0dda616fde588985bd3 *docs/shots/crop_side_p08.png
|
|
||||||
8bbdcba793f44ccaf9df47a8ac316c60 *docs/shots/crop_user_circle_p05.png
|
|
||||||
3259a8c112f1d351a1fed11973a02c95 *docs/shots/crop_user_circle_p08.png
|
|
||||||
a618bec8aa67bb2cc743e923207df914 *docs/shots/crop_user_creases.png
|
|
||||||
5994293cc45e59afdb907c52e4e2dd01 *docs/shots/crop_user_creases_fixed.png
|
|
||||||
1f3b3d68bbcf563ba50f79ab74494117 *docs/shots/crop_user_feedback.png
|
|
||||||
5768c8a960267428b10362662d78fd4e *docs/shots/crop_user_p06_yz_fixed.png
|
|
||||||
c2b2873686ec704024bdbb178f8d19f4 *docs/shots/fence_all_sections.png
|
|
||||||
efcb304e1e6d17f93e8fb0802ed429ac *docs/shots/fence_j2_after.png
|
|
||||||
5a907286d59d7f0584bf0a86cee50e01 *docs/shots/fence_j4_after.png
|
|
||||||
5a0556bda22250c7f633df0db6603154 *docs/shots/fence_j5_after.png
|
|
||||||
dd21d8f29d1a0267866b898c75ae6d4f *docs/shots/fence_j6_after.png
|
|
||||||
3123606bf71f402a9714aad63aa7f179 *docs/shots/full_arm_compare.png
|
|
||||||
93893ff628702b83137e27fdcf751f60 *docs/shots/harness_3d_viewer.png
|
|
||||||
af5c9d74bddf7d795bd38f4371fa61ec *docs/shots/harness_full_iso.png
|
|
||||||
2c8ecd1eee5204b6cf6c4af3b3c5f62e *docs/shots/harness_full_view.png
|
|
||||||
b8b89700a7a6a38b80aa1ef0d8966195 *docs/shots/harness_inner_front.png
|
|
||||||
4087f9fa75d2ef805457b1e32689785d *docs/shots/harness_inner_side.png
|
|
||||||
ae26c478855955748222592591ee6a03 *docs/shots/harness_notch_fixed.png
|
|
||||||
8896124b0c45d9796e983f026134cff6 *docs/shots/harness_overview.png
|
|
||||||
7a40f9f434e51299deb7ab9a59f8fc81 *docs/shots/harness_overview_xray.png
|
|
||||||
7cbf2c699501bff3c7ebb4beb7cd34e2 *docs/shots/harness_p04_p05.png
|
|
||||||
7b7335a79480d04dc3862f51a6377396 *docs/shots/harness_p04_p05_xray.png
|
|
||||||
687e2184dd3e149939ce6d4214ffc698 *docs/shots/harness_p06_window.png
|
|
||||||
9fca41ada43f36a338cce31797152c3c *docs/shots/harness_p07_window.png
|
|
||||||
c8a688690b418e68993d06e7b60e2c05 *docs/shots/harness_p07_xray.png
|
|
||||||
b8b89700a7a6a38b80aa1ef0d8966195 *docs/shots/harness_toggled_off.png
|
|
||||||
84fb87bb5ce6cef73866401642ab0f9d *docs/shots/harness_xray_full.png
|
|
||||||
7d07f7728a5cb0bbb71c787e2b44aeb1 *docs/shots/harness_xray_internal.png
|
|
||||||
152a3bbf2ebd3531fbde77a1c201c9a0 *docs/shots/harness_xray_motor.png
|
|
||||||
54d1ba097fdf4213811c1360204fa677 *docs/shots/harness_zoom_elbow.png
|
|
||||||
09c5d9d5c507a182d4dea5e4cfa238dc *docs/shots/harness_zoom_shoulder.png
|
|
||||||
7fe0cb0d52c1f7d97bf1c8fd47b22e0c *docs/shots/hollow_p04_iso.png
|
|
||||||
c55a40d4bc4b4e73a672326fa02d573c *docs/shots/hollow_p04_top.png
|
|
||||||
8379d580ff746c859587e29e34700d7c *docs/shots/hollow_p06_iso.png
|
|
||||||
1464e622ec8090b6052ab82f7bcd6b22 *docs/shots/hollow_p07_iso.png
|
|
||||||
f3b85ea576a6aa99a61953c4ff2a5ff4 *docs/shots/hollow_p07_top.png
|
|
||||||
e169359e86a61f7ab80d4adea0231436 *docs/shots/j3_前端压盖_全件.png
|
|
||||||
83325d3ca1b9e56fd2c70661052fd063 *docs/shots/j3_前端压盖_装配.png
|
|
||||||
8766806e981e84aaafd880af49e6bb0f *docs/shots/j4_neg80_view.png
|
|
||||||
7797c4490780e1d164a7784fc062ba32 *docs/shots/j4_pos80_view.png
|
|
||||||
26f3fd913018e16e99af2edd861342b8 *docs/shots/j4_side_neg80.png
|
|
||||||
9f411a6efadfa2fd38aa929beaaed0f0 *docs/shots/j4_side_pos80.png
|
|
||||||
cfd861e3c300b6b91f84f40bb9753d51 *docs/shots/lingxi_6DUUEF.png
|
|
||||||
82e86303cb609fa371600c0fb5f9b85a *docs/shots/lingxi_6DUUEG.png
|
|
||||||
e6616300fa1a80f4316673f733b43167 *docs/shots/lingxi_6DUUEJ.png
|
|
||||||
515d312c6cf3d2b420d19eda7a380fbe *docs/shots/m2_front_notch_exact.png
|
|
||||||
f7ad1083e20c91ef1dcf2eeb37e0f908 *docs/shots/m2_front_view.png
|
|
||||||
c84927682b860cfe7d9c2ec2ffc21305 *docs/shots/m2_iso_notch_exact.png
|
|
||||||
9bb20af2e14c64203e814423e65efc75 *docs/shots/m2_notch_zoom.png
|
|
||||||
369479ad05476793ce824fa6a58bca98 *docs/shots/m2_side_view.png
|
|
||||||
38536cef76f4e29825705a5d3bf8bfbc *docs/shots/m3_front_assembled_after.png
|
|
||||||
1c221106e26cdf4d85dd6d432483704f *docs/shots/m3_front_iso_after.png
|
|
||||||
a9182da4a194b3316ba710e5e22b4299 *docs/shots/m3_front_nomotor_after.png
|
|
||||||
88eadfa42ec551a96b52a58468bbe5ac *docs/shots/m3_front_open_after.png
|
|
||||||
e8acef55ca4305e09dbda01de7d8af51 *docs/shots/m3_front_side_after.png
|
|
||||||
c22cef65ca2a74b1332ecaba6b9a484a *docs/shots/p03_light_iso.png
|
|
||||||
ebdeb9a180e95afd38f5a42d12743579 *docs/shots/p03_light_join.png
|
|
||||||
5671bcc5a150d4899d186abd3658ef99 *docs/shots/p03_light_rear.png
|
|
||||||
2a9e076fa310b0c78b1a0e8375e8aae8 *docs/shots/p03_light_side.png
|
|
||||||
0aa155c87743426668114d876478582c *docs/shots/p03_light_xray.png
|
|
||||||
d0c82d07ab6055c1b67c03c126fe267a *docs/shots/p03_mount_iso.png
|
|
||||||
21db2e9ca69192cce0be4467b4701028 *docs/shots/p03_mount_loose.png
|
|
||||||
78f62639acb79e30fa88cc5fda87cfea *docs/shots/p03_mount_top.png
|
|
||||||
ae0305c092707e0c73944e1d889d076a *docs/shots/p04_both_ends.png
|
|
||||||
f241acda8fdae460eb38f5a054e841f9 *docs/shots/p04_m2_side.png
|
|
||||||
f9a0e5fda588e5345e32d112c3dbc116 *docs/shots/p04_top_after.png
|
|
||||||
2b07a995cfadea3396cbf1b9bcdbe3fb *docs/shots/p04_top_iso.png
|
|
||||||
8191b972269eab6e51b40b5771e7d3d5 *docs/shots/p05_after_groove_removed.png
|
|
||||||
fcbf993fb18f6e7007cb66fea153cdf2 *docs/shots/p05_corner_zoom.png
|
|
||||||
3d261042388c8b2924848d8a29f71931 *docs/shots/p05_flank_view.png
|
|
||||||
c7bbe31a5e94574fbaaf495622f999ff *docs/shots/p05_focus.png
|
|
||||||
9fcdb197a69d94949660b42032db1fda *docs/shots/p05_inner_corner_1.png
|
|
||||||
665709a81624ee17b8565df8ed1947da *docs/shots/p05_inner_corner_2.png
|
|
||||||
597ffa69b9817823c1958822482ae9d1 *docs/shots/p05_inner_corner_side.png
|
|
||||||
2efeeb47868cb57fab926aebe980bd5b *docs/shots/p05_iso_clean.png
|
|
||||||
9e930e7d033b8b4dfe8a486feb9b8eff *docs/shots/p05_iso_target.png
|
|
||||||
515b204e42d75d98afe0600f8b8b419d *docs/shots/p05_m3_side.png
|
|
||||||
bfe508bc1f44430bb3a6d43811962226 *docs/shots/p05_side.png
|
|
||||||
0e6931d83143d767ba37fa76d12aeb41 *docs/shots/p05_side_target.png
|
|
||||||
38794de6a818f31efade797afff934f3 *docs/shots/p05_zoom_v3.png
|
|
||||||
3b82ff785a29a5b1a4233404f62e3106 *docs/shots/p06_iso_v3.png
|
|
||||||
bc99a68fa6ebcc21c173188a13805834 *docs/shots/p06_iso_v4.png
|
|
||||||
4c6014a754e233411f6e89e4112d6cde *docs/shots/p06_m4_side.png
|
|
||||||
cf5939f305762bd064cdfae373c130d4 *docs/shots/p07_fillet_zoom.png
|
|
||||||
aa3cb2e1e7bff6e3ca4af66bd6c70854 *docs/shots/p07_m5_side.png
|
|
||||||
c6a061335ea757064ecf51195b89f808 *docs/shots/preview_p06_closeup.png
|
|
||||||
c712e7385b2094bc349d06f3ff1973eb *docs/shots/preview_p06_front.png
|
|
||||||
966427d5bba294035b3e6f15b0984e60 *docs/shots/preview_p06_iso.png
|
|
||||||
16e9638ec903aef101594f6cd0224a40 *docs/shots/preview_p06_yz_side.png
|
|
||||||
7635f1d057b06f014c1683261a7237b8 *docs/shots/shot_p08_user.png
|
|
||||||
20b4517e84e161abce960994380a92c6 *docs/shots/shot_service_loop_bent.png
|
|
||||||
468704c6678e540f54d3165768713991 *docs/shots/shot_service_loop_zero.png
|
|
||||||
d6fba250cab75489c4ccd44b245ce02d *docs/shots/shot_service_loop_zoom.png
|
|
||||||
977bb781966f2b94894a2fb99cc4ed69 *docs/shots/side_full_arm.png
|
|
||||||
cd787443c8c8c4be47d24f3e4c0b0e33 *docs/shots/side_full_arm_v4.png
|
|
||||||
5de5466396a959ce13f494da0d50298d *docs/shots/side_v3.png
|
|
||||||
aed480014d24fcd45bc3eafd30b0376c *docs/shots/side_zoom_p05.png
|
|
||||||
aed480014d24fcd45bc3eafd30b0376c *docs/shots/side_zoom_p05_v4.png
|
|
||||||
4e3374649995691b3556f3c86fca5904 *docs/shots/side_zoom_p08.png
|
|
||||||
4e3374649995691b3556f3c86fca5904 *docs/shots/side_zoom_p08_v4.png
|
|
||||||
f1e5bd79e6921b39d0785e4208ef5cca *docs/shots/svg_exploded.png
|
|
||||||
57ac93ac3d2be849772d51ba8300a989 *docs/shots/svg_front.png
|
|
||||||
eadcf7d6ac9853950496a559dbd8de72 *docs/shots/svg_joint.png
|
|
||||||
4a9a34e534496cb2c3be499ea2029d87 *docs/shots/svg_side.png
|
|
||||||
2c8ecd1eee5204b6cf6c4af3b3c5f62e *docs/shots/test.png
|
|
||||||
9324b57f4bfb601a6cd6606f74c0c856 *docs/shots/test_p05_fillet_after.png
|
|
||||||
9e0c65c8e53adab6305cc1b03dd3c038 *docs/shots/test_p05_view.png
|
|
||||||
a9b48aa3507d451d8e9159699cfeb6be *docs/shots/test_side_0.png
|
|
||||||
96a62a6d55b26a30a5001952a8541ed8 *docs/shots/test_side_neg_y.png
|
|
||||||
7458cdfb12232a2566142ddc6bb76424 *docs/shots/test_side_pi.png
|
|
||||||
96635ca94ec00783d84c9a3f54fb91c0 *docs/shots/test_side_pos_y.png
|
|
||||||
4434ed29679c4fdba76782b40d71c925 *docs/shots/test_side_x_neg.png
|
|
||||||
02a5cd63659b58b6c4f64782ca9f61e4 *docs/shots/test_side_x_pos.png
|
|
||||||
67a351369c1bb2e1491eb2dadc5efc00 *docs/shots/test_target_corner.png
|
|
||||||
b7aeadaf3603105f3a47780490ad0342 *docs/shots/test_user_view.png
|
|
||||||
d3bf901601ff221ecd1b09dc93dd2ccc *docs/shots/tie_after_close.png
|
|
||||||
7c2be3a8488997de588e3760f966fd7d *docs/shots/tie_after_negY.png
|
|
||||||
0b3e26c6e2fd664f91764d26b8b1ed0c *docs/shots/tie_after_oblique.png
|
|
||||||
c39cee9deabedb4dc8fad8ec76b439e5 *docs/shots/tie_after_p0607.png
|
|
||||||
b51a4e5424297c9012cc0562bbeec296 *docs/shots/tie_after_p0607_obl.png
|
|
||||||
70772a99b8c5d81619127eed7bc1dab5 *docs/shots/tie_probe_negY.png
|
|
||||||
a3bf9bd7fb796283bbb9767cc790167a *docs/shots/tie_probe_posY.png
|
|
||||||
1e1d29129833b35e43b925b25eae4b8f *docs/shots/user_circle_check.png
|
|
||||||
309cc9ac3238e747104e6ceee7bd68bc *docs/shots/user_compare_view.png
|
|
||||||
d25a25539d00674d608dca7234e331e0 *docs/shots/user_contour_harness.png
|
|
||||||
3ed51a9cbad23f7c9c7eb79ac5471eb1 *docs/shots/user_elbow_front.png
|
|
||||||
85d13ea862263aa055924df2beca98fb *docs/shots/user_elbow_rear.png
|
|
||||||
9c2bc97ed4ca490e624695c5e4086f8c *docs/shots/user_exact_clean.png
|
|
||||||
6e1c039c236d28a4bec7e7b589fb2ed3 *docs/shots/user_exact_view.png
|
|
||||||
6a86499dd36b2a0a1ac50ae28ea138e4 *docs/shots/user_fork_fixed.png
|
|
||||||
527d30d68065fd9e59020f8234998c61 *docs/shots/user_harness_full.png
|
|
||||||
d35c720c416b645b0560296f1062d878 *docs/shots/user_harness_notches.png
|
|
||||||
1389d052730b4bb09eb0a17cf9c49354 *docs/shots/user_harness_view.png
|
|
||||||
eebfbfbea1375770fbc333f83f64e959 *docs/shots/user_harness_zoom.png
|
|
||||||
ffe701c93ce7984d125cff99a10ed319 *docs/shots/user_iso_after.png
|
|
||||||
d3234ff06df60213391dab75589d65ab *docs/shots/user_m1_bypass_oblique.png
|
|
||||||
99188cb1c040a2c6494322a9845ac6cb *docs/shots/user_m1_exact.png
|
|
||||||
606e209a6d3dc64ee3a59d1fc22339f3 *docs/shots/user_m1_rear.png
|
|
||||||
d34d5d15434bc5a980e34e5511cc49a4 *docs/shots/user_p04.png
|
|
||||||
51d318f3584444ea78f2078d9ef3eddd *docs/shots/user_p04_bottom.png
|
|
||||||
b31cd81ca26198896378bc38c4393b9d *docs/shots/user_p04_bottom2.png
|
|
||||||
22ace55c6f60a6322f770b2b0a0a504c *docs/shots/user_p04_iso.png
|
|
||||||
6ea6b98ad978b993868bd302ad1dd8c6 *docs/shots/user_p04_opp.png
|
|
||||||
2c7f53c7bed7d25175948f09960ed634 *docs/shots/user_p04_side.png
|
|
||||||
20e8583cae4d1705bcecdb750ac90b3b *docs/shots/user_p06_fixed.png
|
|
||||||
2b1117068f7dc20cb24054cfea6d3453 *docs/shots/user_p06_front.png
|
|
||||||
fe0b30e16a673beb200224a0e6607faa *docs/shots/user_p06_iso.png
|
|
||||||
291d238a586ded723221e2f9ed49e7e3 *docs/shots/user_p06_j4_pitch_neg.png
|
|
||||||
4798f251b423398acb0975ecdff88aa6 *docs/shots/user_p06_j4_pitch_pos.png
|
|
||||||
5326a55ce2be7e15e78272cbdf5c6574 *docs/shots/user_p06_p07_match.png
|
|
||||||
c8448c391e21b12831c09e82fab9da24 *docs/shots/user_p06_side.png
|
|
||||||
729025864df7708c900e110ec175f0da *docs/shots/user_p06_xz.png
|
|
||||||
095da56acdc9b512709786df26691481 *docs/shots/user_p06_yz.png
|
|
||||||
152eb1db302b62238f3051b1f48fcf45 *docs/shots/user_p07_check.png
|
|
||||||
abf7113f27b6443541e3a0859d77b3fe *docs/shots/user_p07_creases.png
|
|
||||||
a17303b12669be77a97dc18f2463c809 *docs/shots/user_p07_wrist.png
|
|
||||||
a60f07bb05bba7a58bdd29e5202c478f *docs/shots/user_shoulder_solid.png
|
|
||||||
86aab5a8eb32216b4f712c730cc67068 *docs/shots/user_shoulder_top.png
|
|
||||||
a60f07bb05bba7a58bdd29e5202c478f *docs/shots/user_shoulder_xray.png
|
|
||||||
227a85ecdb2fd21b1d042f84bf3aa94c *docs/shots/user_side_full_v5.png
|
|
||||||
02cfa0aa5681fec061f73b9d69fef41c *docs/shots/user_smooth_harness.png
|
|
||||||
87a8889ba4031cdf2e378fca7e1b475c *docs/shots/user_target.png
|
|
||||||
22f62024f51ee1827f662d8754019f57 *docs/shots/user_view1.png
|
|
||||||
e96c715833f9321b9fd1501bc4bdf87a *docs/shots/user_view2.png
|
|
||||||
8513509bd468d4253fff41a769fba112 *docs/shots/user_view_after.png
|
|
||||||
f800bcb2c675c540da91ea4128626558 *docs/shots/verify_front.png
|
|
||||||
04dcb70b0420dd9b626970e84c6c6009 *docs/shots/verify_front_no_p04.png
|
|
||||||
f800bcb2c675c540da91ea4128626558 *docs/shots/verify_front_xray.png
|
|
||||||
6d08599e6af8098899be6f4e04cd587f *docs/shots/verify_rear.png
|
|
||||||
467521f6aed235e4b7e018291d7a9273 *docs/shots/view_a.png
|
|
||||||
400e33d3f92bdc5d35030bc0560793f2 *docs/shots/view_b.png
|
|
||||||
2e70d37bc8bfe39ec1f55956b8c90c31 *docs/shots/view_c.png
|
|
||||||
9ac5686881ce53db4220af7599bae200 *docs/shots/view_d.png
|
|
||||||
64a199b2c65e7f2d0ad5e2a80c5caef8 *docs/shots/view_iso_forearm.png
|
|
||||||
22ae02822c50527ff3aa0b341337add5 *docs/shots/view_iso_zoom.png
|
|
||||||
1d8277f0b43938423f2ca53b34df0c56 *docs/shots/view_yz_neg_x.png
|
|
||||||
d51cde4a5e2450c529d1c8897e16cf29 *docs/shots/view_yz_pos_x.png
|
|
||||||
d1719a9f6fb775fd63cef27c5ff17f54 *docs/shots/zoom_user.png
|
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
af5c9d74bddf7d795bd38f4371fa61ec docs/shots/01_iso.png
|
||||||
|
d1719a9f6fb775fd63cef27c5ff17f54 docs/shots/zoom_user.png
|
||||||
|
1389d052730b4bb09eb0a17cf9c49354 docs/shots/user_harness_view.png
|
||||||
|
eebfbfbea1375770fbc333f83f64e959 docs/shots/user_harness_zoom.png
|
||||||
|
606e209a6d3dc64ee3a59d1fc22339f3 docs/shots/user_m1_rear.png
|
||||||
|
99188cb1c040a2c6494322a9845ac6cb docs/shots/user_m1_exact.png
|
||||||
|
d3234ff06df60213391dab75589d65ab docs/shots/user_m1_bypass_oblique.png
|
||||||
|
02cfa0aa5681fec061f73b9d69fef41c docs/shots/user_smooth_harness.png
|
||||||
|
d25a25539d00674d608dca7234e331e0 docs/shots/user_contour_harness.png
|
||||||
|
c315879cfd09f35efa4ca4ecc1564b65 docs/shots/02_front.png
|
||||||
|
30ab6da268de109cd087b93b8b175cf9 docs/shots/m1_sheet_front.png
|
||||||
|
45050caba87c801ab0c29ac939e51e43 docs/shots/m2_sheet_joint.png
|
||||||
|
615621995924b8bcaf67d79a020cd8d2 docs/shots/m3_sheet_exploded.png
|
||||||
|
2b1950dda519fac21d5e71d4a9c1f990 docs/shots/m4_sheet_side.png
|
||||||
|
86098e0fada6f54d9ea3d285b3e0ed6e docs/shots/m5_guide_step1.png
|
||||||
|
8366cefeca8b935f9fef31bfd2454047 docs/shots/m6_guide_step5.png
|
||||||
|
40f4663eef6aa61f36e47e1fb1b1ab07 docs/shots/m7_sheet_blueprint.png
|
||||||
|
2702f107e6152f05a5016b916f38cb89 docs/shots/m8_sheet_dark.png
|
||||||
|
c84473f41febb608aea0ec2ec12ef317 docs/shots/m9_motion.png
|
||||||
|
b3157f4453cf96b930f065e134a2d18b docs/shots/03_side.png
|
||||||
|
068fc4bc3eec481ff7d7b1d4da2aad88 docs/shots/04_top.png
|
||||||
|
fbb95ea7a9229195d80c6f487021ae2b docs/shots/05_exploded.png
|
||||||
|
cbea750a1b52c5e8d061a9e3f4c981fe docs/shots/06_wrist.png
|
||||||
|
bb3dc393e4cd8f2c399c343aba0b3d35 docs/shots/07_joint.png
|
||||||
|
72e86ae7743ff7416e247d19556dc657 docs/shots/08_pose.png
|
||||||
|
af5c9d74bddf7d795bd38f4371fa61ec docs/shots/09_sheet.png
|
||||||
|
af5c9d74bddf7d795bd38f4371fa61ec docs/shots/10_solid.png
|
||||||
|
8daa2255af050a225609cebbc490d3b9 docs/shots/11_tool_flange.png
|
||||||
|
002a1f4a2b65744515f5b54364fcc2a8 docs/shots/12_elbow_flare.png
|
||||||
|
b2f1454a6009a8b44ddd41be9efdaff5 docs/shots/13_forearm_side.png
|
||||||
|
87a8889ba4031cdf2e378fca7e1b475c docs/shots/user_target.png
|
||||||
|
d51cde4a5e2450c529d1c8897e16cf29 docs/shots/view_yz_pos_x.png
|
||||||
|
1d8277f0b43938423f2ca53b34df0c56 docs/shots/view_yz_neg_x.png
|
||||||
|
64a199b2c65e7f2d0ad5e2a80c5caef8 docs/shots/view_iso_forearm.png
|
||||||
|
22f62024f51ee1827f662d8754019f57 docs/shots/user_view1.png
|
||||||
|
e96c715833f9321b9fd1501bc4bdf87a docs/shots/user_view2.png
|
||||||
|
d34d5d15434bc5a980e34e5511cc49a4 docs/shots/user_p04.png
|
||||||
|
6ea6b98ad978b993868bd302ad1dd8c6 docs/shots/user_p04_opp.png
|
||||||
|
85d13ea862263aa055924df2beca98fb docs/shots/user_elbow_rear.png
|
||||||
|
a17303b12669be77a97dc18f2463c809 docs/shots/user_p07_wrist.png
|
||||||
|
3ed51a9cbad23f7c9c7eb79ac5471eb1 docs/shots/user_elbow_front.png
|
||||||
|
729025864df7708c900e110ec175f0da docs/shots/user_p06_xz.png
|
||||||
|
095da56acdc9b512709786df26691481 docs/shots/user_p06_yz.png
|
||||||
|
fe0b30e16a673beb200224a0e6607faa docs/shots/user_p06_iso.png
|
||||||
|
abf7113f27b6443541e3a0859d77b3fe docs/shots/user_p07_creases.png
|
||||||
|
959e4bf08e174d90a1fe03a75a042070 docs/shots/bay_p06_inside.png
|
||||||
|
f9d8d58365b4ca693525af5540d39d32 docs/shots/bay_p07_inside.png
|
||||||
|
1b07f37e5fed9d82886b9518a3839860 docs/shots/bay_p08_inside.png
|
||||||
|
2c7f53c7bed7d25175948f09960ed634 docs/shots/user_p04_side.png
|
||||||
|
51d318f3584444ea78f2078d9ef3eddd docs/shots/user_p04_bottom.png
|
||||||
|
b31cd81ca26198896378bc38c4393b9d docs/shots/user_p04_bottom2.png
|
||||||
|
a60f07bb05bba7a58bdd29e5202c478f docs/shots/user_shoulder_solid.png
|
||||||
|
a60f07bb05bba7a58bdd29e5202c478f docs/shots/user_shoulder_xray.png
|
||||||
|
86aab5a8eb32216b4f712c730cc67068 docs/shots/user_shoulder_top.png
|
||||||
|
c22cef65ca2a74b1332ecaba6b9a484a docs/shots/p03_light_iso.png
|
||||||
|
2a9e076fa310b0c78b1a0e8375e8aae8 docs/shots/p03_light_side.png
|
||||||
|
5671bcc5a150d4899d186abd3658ef99 docs/shots/p03_light_rear.png
|
||||||
|
ebdeb9a180e95afd38f5a42d12743579 docs/shots/p03_light_join.png
|
||||||
|
0aa155c87743426668114d876478582c docs/shots/p03_light_xray.png
|
||||||
|
c55a40d4bc4b4e73a672326fa02d573c docs/shots/hollow_p04_top.png
|
||||||
|
7fe0cb0d52c1f7d97bf1c8fd47b22e0c docs/shots/hollow_p04_iso.png
|
||||||
|
f3b85ea576a6aa99a61953c4ff2a5ff4 docs/shots/hollow_p07_top.png
|
||||||
|
1464e622ec8090b6052ab82f7bcd6b22 docs/shots/hollow_p07_iso.png
|
||||||
|
8379d580ff746c859587e29e34700d7c docs/shots/hollow_p06_iso.png
|
||||||
|
78f62639acb79e30fa88cc5fda87cfea docs/shots/p03_mount_top.png
|
||||||
|
d0c82d07ab6055c1b67c03c126fe267a docs/shots/p03_mount_iso.png
|
||||||
|
21db2e9ca69192cce0be4467b4701028 docs/shots/p03_mount_loose.png
|
||||||
|
83325d3ca1b9e56fd2c70661052fd063 docs/shots/j3_前端压盖_装配.png
|
||||||
|
e169359e86a61f7ab80d4adea0231436 docs/shots/j3_前端压盖_全件.png
|
||||||
|
90b79bb61cacea2323ca19790d4158b2 docs/shots/boss_p04.png
|
||||||
|
6a86499dd36b2a0a1ac50ae28ea138e4 docs/shots/user_fork_fixed.png
|
||||||
|
e39999d2ef28536f24d7f6178fa9a15c docs/shots/bay_p04_inside.png
|
||||||
|
959e4bf08e174d90a1fe03a75a042070 docs/shots/bay_p06_inside.png
|
||||||
|
f9d8d58365b4ca693525af5540d39d32 docs/shots/bay_p07_inside.png
|
||||||
|
ae0305c092707e0c73944e1d889d076a docs/shots/p04_both_ends.png
|
||||||
|
f241acda8fdae460eb38f5a054e841f9 docs/shots/p04_m2_side.png
|
||||||
|
4c6014a754e233411f6e89e4112d6cde docs/shots/p06_m4_side.png
|
||||||
|
aa3cb2e1e7bff6e3ca4af66bd6c70854 docs/shots/p07_m5_side.png
|
||||||
|
515b204e42d75d98afe0600f8b8b419d docs/shots/p05_m3_side.png
|
||||||
|
dcc54259caf06f796e3dec84496abb12 docs/shots/boss_p05.png
|
||||||
|
309cc9ac3238e747104e6ceee7bd68bc docs/shots/user_compare_view.png
|
||||||
|
152eb1db302b62238f3051b1f48fcf45 docs/shots/user_p07_check.png
|
||||||
|
9f2c134297f2c7a555a6eaca2e74e4de docs/shots/boss_p06.png
|
||||||
|
b789564315ca1ad7d6d9d18c96972cba docs/shots/boss_p07.png
|
||||||
|
4a9a34e534496cb2c3be499ea2029d87 docs/shots/svg_side.png
|
||||||
|
eadcf7d6ac9853950496a559dbd8de72 docs/shots/svg_joint.png
|
||||||
|
f1e5bd79e6921b39d0785e4208ef5cca docs/shots/svg_exploded.png
|
||||||
|
1e1d29129833b35e43b925b25eae4b8f docs/shots/user_circle_check.png
|
||||||
|
6e1c039c236d28a4bec7e7b589fb2ed3 docs/shots/user_exact_view.png
|
||||||
|
7635f1d057b06f014c1683261a7237b8 docs/shots/shot_p08_user.png
|
||||||
|
3d261042388c8b2924848d8a29f71931 docs/shots/p05_flank_view.png
|
||||||
|
af5c9d74bddf7d795bd38f4371fa61ec docs/shots/harness_full_iso.png
|
||||||
|
8896124b0c45d9796e983f026134cff6 docs/shots/harness_overview.png
|
||||||
|
9fca41ada43f36a338cce31797152c3c docs/shots/harness_p07_window.png
|
||||||
|
7cbf2c699501bff3c7ebb4beb7cd34e2 docs/shots/harness_p04_p05.png
|
||||||
|
687e2184dd3e149939ce6d4214ffc698 docs/shots/harness_p06_window.png
|
||||||
|
84fb87bb5ce6cef73866401642ab0f9d docs/shots/harness_xray_full.png
|
||||||
|
70772a99b8c5d81619127eed7bc1dab5 docs/shots/tie_probe_negY.png
|
||||||
|
a3bf9bd7fb796283bbb9767cc790167a docs/shots/tie_probe_posY.png
|
||||||
|
7c2be3a8488997de588e3760f966fd7d docs/shots/tie_after_negY.png
|
||||||
|
0b3e26c6e2fd664f91764d26b8b1ed0c docs/shots/tie_after_oblique.png
|
||||||
|
d3bf901601ff221ecd1b09dc93dd2ccc docs/shots/tie_after_close.png
|
||||||
|
c39cee9deabedb4dc8fad8ec76b439e5 docs/shots/tie_after_p0607.png
|
||||||
|
b51a4e5424297c9012cc0562bbeec296 docs/shots/tie_after_p0607_obl.png
|
||||||
@@ -44,7 +44,16 @@ def main():
|
|||||||
print("【线束三维物理干涉检测】全通道、外侧走线与电机全固态零碰撞验证")
|
print("【线束三维物理干涉检测】全通道、外侧走线与电机全固态零碰撞验证")
|
||||||
print("=" * 80)
|
print("=" * 80)
|
||||||
|
|
||||||
with open('src/viewer_tpl.html', 'r', encoding='utf-8') as f:
|
# 线束通道的锚点坐标写在查看器源码里(现在是模板,拆分后是 webapp/js/harness.js)。
|
||||||
|
# 扫候选文件而不是写死一个路径,前端拆分时这条检查不用跟着改;一个都找不到就直接失败,
|
||||||
|
# 绝不能"没匹配到 = 没有通道 = 零干涉"地假通过。
|
||||||
|
from arm.paths import REPO
|
||||||
|
cands = [REPO + "/src/viewer_tpl.html", REPO + "/webapp/js/harness.js"]
|
||||||
|
found = [c for c in cands if os.path.exists(c)]
|
||||||
|
if not found:
|
||||||
|
print("找不到线束源码,检查项无法执行:", cands, flush=True)
|
||||||
|
sys.exit(2)
|
||||||
|
with open(found[0], 'r', encoding='utf-8') as f:
|
||||||
tpl = f.read()
|
tpl = f.read()
|
||||||
|
|
||||||
tube_matches = re.findall(r'makeHarnTube\(\[\s*([\s\S]*?)\s*\]\s*,\s*[\d\.]+\s*,\s*\w+\s*,\s*\'([^\']+)\'', tpl)
|
tube_matches = re.findall(r'makeHarnTube\(\[\s*([\s\S]*?)\s*\]\s*,\s*[\d\.]+\s*,\s*\w+\s*,\s*\'([^\']+)\'', tpl)
|
||||||
@@ -100,7 +109,8 @@ def main():
|
|||||||
if total_hits == 0:
|
if total_hits == 0:
|
||||||
print("🎉 恭喜!线束系统已实现 100% 结构腔体内规范走线与零碰撞干涉!", flush=True)
|
print("🎉 恭喜!线束系统已实现 100% 结构腔体内规范走线与零碰撞干涉!", flush=True)
|
||||||
print("=" * 80, flush=True)
|
print("=" * 80, flush=True)
|
||||||
import os
|
sys.stdout.flush()
|
||||||
|
# 同上:OCC 析构会段错误,所以用 os._exit 带走正确退出码
|
||||||
os._exit(0 if total_hits == 0 else 1)
|
os._exit(0 if total_hits == 0 else 1)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""重构期的不变量关卡。
|
||||||
|
|
||||||
|
python -m tests.verify # 数据哈希 + IK 数值(秒级,每步都跑)
|
||||||
|
python -m tests.verify --shots # 再加全套截图比对(约 3 分钟,前端改动后跑)
|
||||||
|
|
||||||
|
基线在 tests/baselines/ 下,随仓库走。产物按**文件名**匹配,不写死目录:
|
||||||
|
mesh 从 web/ 搬到 webapp/data/ 这类移动不该让关卡失效,也不该让我以为它通过了。
|
||||||
|
任何一项对不上就退出码 1。
|
||||||
|
"""
|
||||||
|
import hashlib
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
REPO = os.path.dirname(HERE)
|
||||||
|
BASE = os.path.join(HERE, "baselines")
|
||||||
|
|
||||||
|
# 每个产物允许出现的目录,按优先级;找到第一个即取。
|
||||||
|
CANDIDATES = {
|
||||||
|
"mesh_arm.json": ["webapp/data", "web"],
|
||||||
|
"sheets.jsdata": ["webapp/data", "web"],
|
||||||
|
"travel.json": ["docs", "webapp/data"],
|
||||||
|
"travel_detail.json": ["docs", "webapp/data"],
|
||||||
|
"collide.json": ["docs", "webapp/data"],
|
||||||
|
"screws.json": ["docs"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def md5(path):
|
||||||
|
h = hashlib.md5()
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
for chunk in iter(lambda: f.read(1 << 20), b""):
|
||||||
|
h.update(chunk)
|
||||||
|
return h.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def read_list(name):
|
||||||
|
"""基线文件是 'hash filename';md5sum 的二进制模式会在文件名前加 '*',剥掉。"""
|
||||||
|
out = {}
|
||||||
|
for ln in io.open(os.path.join(BASE, name), encoding="utf-8"):
|
||||||
|
ln = ln.strip()
|
||||||
|
if ln:
|
||||||
|
h, f = ln.split()
|
||||||
|
out[f.lstrip("*")] = h
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def locate(fname, dirs):
|
||||||
|
for d in dirs:
|
||||||
|
p = os.path.join(REPO, d, fname)
|
||||||
|
if os.path.exists(p):
|
||||||
|
return p
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def check_data():
|
||||||
|
base = read_list("data_by_name.md5")
|
||||||
|
bad, rows = 0, []
|
||||||
|
for fname, dirs in CANDIDATES.items():
|
||||||
|
want = base.get(fname)
|
||||||
|
if want is None:
|
||||||
|
rows.append(("SKIP", fname, "基线里没有这项"))
|
||||||
|
continue
|
||||||
|
p = locate(fname, dirs)
|
||||||
|
if p is None:
|
||||||
|
bad += 1
|
||||||
|
rows.append(("MISS", fname, "找不到文件(试过的目录: %s)" % ", ".join(dirs)))
|
||||||
|
continue
|
||||||
|
got = md5(p)
|
||||||
|
ok = got == want
|
||||||
|
if not ok:
|
||||||
|
bad += 1
|
||||||
|
rows.append(("OK " if ok else "DIFF", fname,
|
||||||
|
os.path.relpath(p, REPO).replace("\\", "/") + " " + got[:12] +
|
||||||
|
("" if ok else " 基线 " + want[:12])))
|
||||||
|
for st, f, msg in rows:
|
||||||
|
print(" [%s] %-20s %s" % (st, f, msg))
|
||||||
|
return bad
|
||||||
|
|
||||||
|
|
||||||
|
def check_ik():
|
||||||
|
r = subprocess.run(["node", os.path.join(HERE, "ik_baseline.mjs"),
|
||||||
|
"--compare", os.path.join(BASE, "ik_exact.json")],
|
||||||
|
capture_output=True, text=True, cwd=REPO,
|
||||||
|
encoding="utf-8", errors="replace")
|
||||||
|
out = r.stdout or ""
|
||||||
|
for ln in out.splitlines():
|
||||||
|
print(" " + ln)
|
||||||
|
if r.returncode != 0:
|
||||||
|
print(" node 退出码 %d: %s" % (r.returncode, (r.stderr or "").strip()[:300]))
|
||||||
|
# 没有 "== exact" 这一行就说明探针根本没跑出比对结果(编码炸了、node 不在、路径错…),
|
||||||
|
# 绝不能因为"没报差异"就算通过。
|
||||||
|
if "== exact" not in out:
|
||||||
|
print(" [MISS] ik_baseline 没有输出比对结果,判 FAIL")
|
||||||
|
return 1
|
||||||
|
return 0 if r.returncode == 0 else 1
|
||||||
|
|
||||||
|
|
||||||
|
def check_shots():
|
||||||
|
"""两层判定,依据是"同一构建连跑两轮"的实测(scratch/_gate/flaky_scan.py):
|
||||||
|
104 张逐字节可复现 -> md5 必须相同;
|
||||||
|
3 张线束 x-ray 不可复现(mean<=0.0017、无像素差>8,纯抗锯齿噪声)
|
||||||
|
-> 与 tests/baselines/flaky/ 里的参考像素比,超阈值才算回归。
|
||||||
|
把不可复现的也要求 md5 相同,等于给每一步埋假 FAIL,最后只会被人肉忽略。"""
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
base = read_list("shots_stable.md5")
|
||||||
|
th = json.load(io.open(os.path.join(BASE, "flaky", "thresholds.json"), encoding="utf-8"))
|
||||||
|
r = subprocess.run([sys.executable, "-m", "arm.viewer.shot"],
|
||||||
|
capture_output=True, text=True, cwd=REPO,
|
||||||
|
encoding="utf-8", errors="replace")
|
||||||
|
out = r.stdout or ""
|
||||||
|
if "all shots ok" not in out and "OK " not in out:
|
||||||
|
print(" [MISS] shot.py 没有正常输出(退出码 %s),判 FAIL" % r.returncode)
|
||||||
|
return 999
|
||||||
|
bad = [l for l in out.splitlines() if "FAIL" in l]
|
||||||
|
if bad:
|
||||||
|
print(" [MISS] 截图失败 %d 个: %s" % (len(bad), bad[:3]))
|
||||||
|
|
||||||
|
fails = len(bad)
|
||||||
|
for fname, want in sorted(base.items()):
|
||||||
|
p = os.path.join(REPO, fname.replace("/", os.sep))
|
||||||
|
if not os.path.exists(p):
|
||||||
|
print(" [MISS] %s" % fname)
|
||||||
|
fails += 1
|
||||||
|
continue
|
||||||
|
if md5(p) != want:
|
||||||
|
print(" [DIFF] %-40s" % fname)
|
||||||
|
fails += 1
|
||||||
|
print(" 逐字节比对: %d 张稳定视图,%d 张不一致" % (len(base), fails - len(bad)))
|
||||||
|
|
||||||
|
tol_fails = 0
|
||||||
|
for name, ref in sorted(th["views"].items()):
|
||||||
|
p = os.path.join(REPO, "docs", "shots", name + ".png")
|
||||||
|
q = os.path.join(BASE, "flaky", name + ".png")
|
||||||
|
if not (os.path.exists(p) and os.path.exists(q)):
|
||||||
|
print(" [MISS] 容差比对缺文件: %s" % name)
|
||||||
|
tol_fails += 1
|
||||||
|
continue
|
||||||
|
d = np.abs(np.asarray(Image.open(p).convert("RGB"), dtype=np.int16)
|
||||||
|
- np.asarray(Image.open(q).convert("RGB"), dtype=np.int16))
|
||||||
|
mean, mx = float(d.mean()), int(d.max())
|
||||||
|
over = mean > th["mean_max"] or mx > th["pixel_max"]
|
||||||
|
tol_fails += 1 if over else 0
|
||||||
|
print(" [%s] %-34s mean=%.5f (<= %.2f) 最大单像素差=%d (<= %d)"
|
||||||
|
% ("DIFF" if over else "OK ", name, mean, th["mean_max"], mx, th["pixel_max"]))
|
||||||
|
print(" 容差比对: %d 张噪声视图,%d 张超阈值" % (len(th["views"]), tol_fails))
|
||||||
|
return fails + tol_fails
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argv = sys.argv[1:]
|
||||||
|
fails = 0
|
||||||
|
print("== 数据产物哈希 ==")
|
||||||
|
fails += check_data()
|
||||||
|
print("== IK 数值基线 ==")
|
||||||
|
fails += check_ik()
|
||||||
|
if "--shots" in argv:
|
||||||
|
print("== 截图逐字节比对 ==")
|
||||||
|
fails += check_shots()
|
||||||
|
print("RESULT:", "PASS" if fails == 0 else "FAIL(%d 项不一致)" % fails)
|
||||||
|
return 1 if fails else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 196 KiB |