R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
zhou yang
2026-10-04 19:24:45 +08:00
共同撰写人 Claude Opus 5.5
父节点 cd01640c40
当前提交 cf7cc9cf90
共修改 759 个文件,包含 76962 行新增和 5299 行删除
+123 -7
查看文件
@@ -15,7 +15,7 @@ u"""自建「PCI-E x4 金手指」封装 —— R2 刀片 `J1`(`PCIE-X4-EDGE-6
| 手指宽 | **0.70 mm**(净距 0.30) | 同上 |
| 手指长 | **4.30 mm** | 中文资料常写 4.0,KiCad 4.3,同量级 |
| 指尖到板边 | **1.30 mm** | multi-cb 给 PCIe 推荐 1.4;另一处给 0.6~1.5 窗口 ⇒ 1.3 在窗口内 |
| 缺口(key) | 11 与 12 号针之间,宽 **1.9**,深 **7.45**,口部两个 0.5×45° 导入角 | KiCad x1 / x4 两个封装完全一致 |
| 缺口(key) | 11 与 12 号针之间,宽 ~~1.9~~ **1.6**(2026-09-11 改 CEM 值),深 ~~7.45~~ **8.3**(2026-09-12 加深,见 `NOTCH_D` 旁注),口部两个 0.5×45° 导入角 | KiCad x1 / x4 两个封装都是 1.9(到 11/12 号指只剩 0.2);深度不跟 KiCad,按防呆键筋全高 8.00 + 0.3 余量定 |
| A1 / B31 短指 | 长 **3.2**,指尖再退 **1.10** | CEM 的 PRSNT# 语义:**最后接触**,插到底才认在位 |
| A 排 / B 排 | A = **底层**,B = **顶层** | 同 KiCad |
@@ -72,8 +72,41 @@ FW = 0.7 # 手指宽
LONG_L, SHORT_L = 4.3, 3.2
EDGE_CLR = 1.3 # 指尖到卡底边
SHORT_BACK = LONG_L - SHORT_L # 短指指尖再退 1.1
NOTCH_W, NOTCH_D = 1.9, 7.45 # 缺口宽 / 深(自板边算)
# ⛔ 2026-09-11 晚 用户拍板:缺口宽 1.9 → **1.6**(PCIe CEM 值)。1.9(KiCad 封装值)时缺口边到 11/12 号指铜边
# 只有 (3.0 − 1.9)/2 − 0.35 = 0.2 mm,DRC 报 4 条「板框到焊盘 < 0.3」(方案 §27.8);1.6 ⇒ 0.35。
# 代价:插槽防呆筋的配合余量少 0.15(CEM 的卡本来就是 1.6)。
# ⛔ 2026-09-12 晚 用户拍板:缺口深 7.45 → **8.3**。原因是 7.45 < 防呆键筋全高 8.00 ⇒ 卡往下插时
# 缺口封闭端先顶在键筋顶上,卡底停在 z=2.55、离槽底 2.000 差 0.55 ⇒ **卡是坐在一条塑料防呆筋上,
# 不是坐在槽底**(电气无碍:触点 z 6.18~7.12 被完全盖住,纯粹是受力点)。8.3 = 模型槽底 8.00 再留
# 0.3 余量,防实测键筋高于标称。⚠ 8.00 是厂商 STEP 标称值、不含公差,卡尺实测仍是权威口径。
# 净空已实测:加深段 x 39.2~40.8 / y 7.45~8.30 不碰任何铜(最近的是 x=41.5 的 GND 孔,孔边离
# 侧壁 0.395;x=38.5 的 EFUSE_EN/INT# 竖线 0.60;A11/A12/B11/B12 四指 0.350 —— 加深只往 y 长、
# x 一动不动,这三个数都不变)。连带:pcb_pour.notch_cut() 的内层避让顶端 7.95 → 8.8,要重铺内层。
# ⛔⛔ 2026-09-12 深夜 用户拍板:缺口宽 **1.6 → 1.9**(回到 CEM / KiCad 标准值)。
# ## 为什么必须回去:1.6 那版**插不进插槽**
# 厂商 STEP(SOFNG PCIE-64P11L)量出防呆键筋**根部宽 1.78**(竖壁 x −5.890/−4.110,
# 中心 −5.000 与 NOTCH_CX 独立吻合;顶上 0.5 是 45° 导入角,故顶端 1.78−1.0 = 0.78,
# 与文档记的「顶端 0.78」正好对上)。缺口 1.6 < 键筋 1.78 ⇒ 卡只能套住键筋顶端,
# 往下插到 **z=9.59** 就被撑住,离槽底 2.000 差 **7.59 mm** —— 不是勉强,是根本插不进。
# ## 1.9 才是标准值,0.2 的铜边距是 PCIe 几何定死的
# KiCad 官方 x1/x4 两个封装缺口都是 1.9;方案 §27.8 原话:「1.0 间距 / 0.7 指宽 / 1.9 缺口宽
# ⇒ 缺口边到 11、12 号指的铜边只有 0.2 mm,**任何 PCIe 卡都是这个数**」。
# ⇒ 2026-09-11 那次把它改成 1.6 是**基于错误信息的决定**:当时写作「CEM 的 1.6」(CEM 其实是 1.9),
# 且代价只估成「防呆筋配合余量少 0.15」,没意识到会导致插不进。
# ## 代价(用户已拍板接受)
# 刀片 DRC 会多 **4 条**「板框到焊盘 < 0.3」,全部集中在这一处。
# ⇒ **下单时必须向嘉立创确认「金手指缺口处铜到板边 0.2 mm 可做」**(§27.8 给的就是这条路)。
# (另一条路是把 A11/A12/B11/B12 四根指改窄到 0.50 换回 DRC 0 条 —— 那四根是
# EFUSE_EN / INT# / GND×2,都不走电流,但会偏离 CEM 标准指宽。用户选了保标准指宽。)
NOTCH_W, NOTCH_D = 1.9, 8.3 # 缺口宽 / 深(自板边算)
LEAD_IN = 0.5 # 缺口口部 45° 导入角
# 封装丝印那一行的 y。⛔ 2026-09-12 深夜由写死的 `yb + 1.6`(=7.20) 改成跟着缺口走:
# 丝印从 x 28.5 一路排到 ~44.1,**必然横穿缺口竖槽 x 39.20~40.80**,而缺口是铣穿的
# ⇒ 字会被切掉一段,偏偏这句是「别插进真 PCIe 插槽」的警告。7.45 时就已经重叠 0.75
# (不是加深引入的,加深只是把它恶化到 1.00)。抬到 NOTCH_D + 0.6 = 8.90 ⇒ 字带 8.40~9.40,
# 比缺口顶高 0.10;上方仍远低于 ZONES['finger'] 顶 12.0 与外层铜下沿 TOP_POUR_Y 11.5。
SILK_Y = round(NOTCH_D + 0.6, 3)
SILK_SIZE = 1.0
N_PER_SIDE = 32
SHORT_PINS = ('A1', 'B31') # PRSNT#:最后接触
GAP_AFTER = 11 # 11 号针之后空两格(缺口)
@@ -137,13 +170,17 @@ def selfcheck():
assert PIN_X['A11'] == -6.5 and PIN_X['A12'] == -3.5, u'11/12 之间必须空两格'
assert NOTCH_CX == -5.0, NOTCH_CX
ml, mr = notch_mouth()
assert round(mr - ml, 4) == 2.9, (ml, mr)
assert round(mr - ml, 4) == round(NOTCH_W + 2 * LEAD_IN, 4), (ml, mr)
# 缺口的直段(不含口部导入角)到相邻两指的铜边距。口部的 45° 导入角在 y=0~+0.5,
# 而指尖在 y=+1.3 才开始,两者在 Y 上错开 0.8 ⇒ 只需校核直段的 X 间距。
hw = NOTCH_W / 2.0
cl = round(NOTCH_CX - hw - (PIN_X['A11'] + FW / 2.0), 4)
cr = round((PIN_X['A12'] - FW / 2.0) - (NOTCH_CX + hw), 4)
assert cl == cr == 0.2, u'缺口到 11/12 号指的铜边距变了: %s / %s' % (cl, cr)
# ⛔ 门槛 2026-09-12 深夜由 0.3 放到 **0.2**:0.2 是 PCIe 几何本身定死的
# (1.0 间距 / 0.7 指宽 / 1.9 缺口 ⇒ (3.0−1.9)/2 − 0.35 = 0.2),**任何 PCIe 卡都是这个数**,
# KiCad 官方 x1/x4 封装同样是 0.2。0.3 是本项目自设的更严要求,而按它反推缺口只能 ≤1.70,
# 装不下厂商键筋 1.78 ⇒ 窗口为空。⇒ 放回标准值,DRC 那 4 条下单时与嘉立创确认。
assert cl == cr >= 0.2, u'缺口到 11/12 号指的铜边距 %s / %s < 0.2(PCIe 几何下限)' % (cl, cr)
assert EDGE_CLR - LEAD_IN >= 0.8, u'口部导入角会切进指尖'
for p in SHORT_PINS:
x, y, w, h = pad_geom(p)
@@ -155,6 +192,11 @@ def selfcheck():
assert abs((y - h / 2.0) - EDGE_CLR) < 1e-9, u'长指指尖不在 1.3'
# 缺口比手指深,否则插槽的防呆筋顶到板子
assert NOTCH_D > EDGE_CLR + LONG_L, u'缺口比手指还浅'
# ⛔ 丝印不许跨缺口(2026-09-12 深夜补)。原来只断言了「不许落进指区 y<=5.6」,
# 而丝印在 7.20、缺口顶 7.45 —— 两个数都过了指区那条断言,却**实际重叠 0.75**,
# 几何验证全程不报,是截图才看出来的。这条按「字带下沿 vs 缺口顶」判。
assert SILK_Y - SILK_SIZE / 2.0 >= NOTCH_D, \
u'丝印带下沿 %.2f 低于缺口顶 %.2f ⇒ 会被铣掉一段' % (SILK_Y - SILK_SIZE / 2.0, NOTCH_D)
return True
@@ -224,12 +266,19 @@ def body_js():
% (lay, mil(x1), mil(y1), mil(x2), mil(y2), mil(0.1)))
# 丝印:只准放在手指区**上方**(指区里一点丝印都不能有)
# create(layer, x, y, text, fontFamily, fontSize, lineWidth, ...)
js.append("await eda.pcb_PrimitiveString.create(3,%s,%s,'NOT PCIe - PhoneRail blade',"
"undefined,%s,%s);"
% (mil(-X_CENTER), mil(yb + 1.6), mil(1.0), mil(0.15)))
js.append(silk_js())
return ''.join(js)
SILK_TEXT = 'NOT PCIe - PhoneRail blade'
def silk_js():
u"""丝印那一行的 JS(`body_js` 与 `push_silk` 共用,别写两份)。"""
return ("await eda.pcb_PrimitiveString.create(3,%s,%s,'%s',undefined,%s,%s);"
% (mil(-X_CENTER), mil(SILK_Y), SILK_TEXT, mil(SILK_SIZE), mil(0.15)))
def push():
fp = find_fp(retries=2)
if not fp:
@@ -334,6 +383,65 @@ def verify(fp=None):
return bad
# 已知的两份封装:个人库原件 + **工程库副本**(PCB 上的 J1 引用的是后者,`getState_Footprint()` 读出来的)。
# ⚠ `lib_Footprint.search` 在 2026-09-11 晚的客户端上直接抛「o[r].match is not a function」⇒ 不靠搜索,按 uuid 开。
FP_COPIES = [('569096fb8fed4743b85c69f4b6581ead', '543725e8f0454017a5691e7e570aeef6'), # 个人库
('a1ad3deadadbf758', '05a65c22f14846b8b95360ef8d3c1e2e')] # 工程库(J1 实例指向它)
def push_notch(fp, lib):
u"""**只重画板框层(11)的缺口线**,焊盘 / 丝印一个不动(J1 实例的焊盘网络按脚号挂着,别去碰)。"""
segs = outline_segments()
body = ''.join("await eda.pcb_PrimitiveLine.create('',11,%s,%s,%s,%s,%s);"
% (mil(x1), mil(y1), mil(x2), mil(y2), mil(0.15)) for x1, y1, x2, y2 in segs)
r = call("await eda.lib_Footprint.openInEditor('%s','%s');"
"await new Promise(r=>setTimeout(r,2500));"
"const old=(await eda.pcb_PrimitiveLine.getAll()||[]).filter(l=>l.layer===11).map(l=>l.primitiveId);"
"if (old.length) await eda.pcb_PrimitiveLine.delete(old);"
"%s"
"await eda.pcb_Document.save();"
"const L=(await eda.pcb_PrimitiveLine.getAll()||[]).filter(l=>l.layer===11);"
"return {del: old.length, now: L.length, pads: (await eda.pcb_PrimitivePad.getAllPrimitiveId()).length,"
" xs: L.map(l=>[l.startX,l.endX]).flat()};" % (fp, lib, body), timeout=300)
xs = [v / MIL for v in r['xs']]
ok = r['now'] == len(segs) and r['pads'] == 64 and \
abs(min(xs) - notch_mouth()[0]) < 0.01 and abs(max(xs) - notch_mouth()[1]) < 0.01
print(u'%s @ %s:删 %d / 现 %d 段(应 %d),焊盘 %d,缺口 x %.3f~%.3f(应 %s)%s'
% (fp[:8], lib[:8], r['del'], r['now'], len(segs), r['pads'], min(xs), max(xs), notch_mouth(),
u' [OK]' if ok else u' [X]'))
return ok
def push_silk(fp, lib):
u"""**只重画丝印层(3/4)的字符串**,焊盘 / 板框一个不动 —— 同 `push_notch` 的道理:
`push()` 会把封装里所有图元删光重建,而 J1 实例的 64 个焊盘挂着网络,不能碰。
⚠ 编辑器自带的**封装名属性串**也在字符串图元里(`verify()` 里按 `t in (FP_NAME, None)`
跳过它)。这里删的时候同样要避开,否则封装名会被一起删掉。
"""
r = call("await eda.lib_Footprint.openInEditor('%s','%s');"
"await new Promise(r=>setTimeout(r,2500));"
"const all = await eda.pcb_PrimitiveString.getAll()||[];"
"const old = all.filter(s=>[3,4].includes(s.layer) && s.text!=='%s')"
".map(s=>s.primitiveId);"
"if (old.length) await eda.pcb_PrimitiveString.delete(old);"
"%s"
"await eda.pcb_Document.save();"
"const now = (await eda.pcb_PrimitiveString.getAll()||[])"
".filter(s=>[3,4].includes(s.layer)).map(s=>[s.x,s.y,s.text]);"
"return {del: old.length, now,"
" pads: (await eda.pcb_PrimitivePad.getAllPrimitiveId()).length,"
" lines: (await eda.pcb_PrimitiveLine.getAll()||[]).filter(l=>l.layer===11).length};"
% (fp, lib, FP_NAME, silk_js()), timeout=300)
got = [(round(x / MIL, 3), round(y / MIL, 3), t) for x, y, t in r['now'] if t != FP_NAME]
ok = (len(got) == 1 and abs(got[0][1] - SILK_Y) < 0.01 and got[0][2] == SILK_TEXT
and r['pads'] == 64 and r['lines'] == 4 + ARC_SEG)
print(u'%s @ %s:删 %d / 现 %s;焊盘 %d(应 64)、板框 %d 段(应 %d)%s'
% (fp[:8], lib[:8], r['del'], got, r['pads'], r['lines'], 4 + ARC_SEG,
u' [OK]' if ok else u' [X]'))
return ok
def bind():
u"""把封装挂到器件 PCIE-X4-EDGE-64P 上。"""
fp = find_fp()
@@ -365,6 +473,14 @@ if __name__ == '__main__':
print(u'自检过。X 中心 %.2f,缺口中心 %.2f,口部断点 %s' % (X_CENTER, NOTCH_CX, notch_mouth()))
for p in ('A1', 'B1', 'A11', 'A12', 'B31', 'A32'):
print(u' %-4s x=%7.3f y=%7.3f %.2fx%.2f' % ((p,) + pad_geom(p)))
print(u' 丝印 "%s" y=%.2f ⇒ 字带 %.2f~%.2f(缺口顶 %.2f)'
% (SILK_TEXT, SILK_Y, SILK_Y - SILK_SIZE / 2, SILK_Y + SILK_SIZE / 2, NOTCH_D))
if '--notch' in sys.argv: # 2026-09-11 晚:缺口改 1.6 用这条,只动板框层
oks = [push_notch(f, l) for f, l in FP_COPIES]
sys.exit(0 if all(oks) else 1)
if '--silk' in sys.argv: # 2026-09-12 深夜:丝印抬过缺口顶,只动丝印层
oks = [push_silk(f, l) for f, l in FP_COPIES]
sys.exit(0 if all(oks) else 1)
if '--push' in sys.argv:
fp = push()
verify(fp)