R2 刀片:金手指两侧开台阶(插不进插槽)+ 铺铜补齐并跟着台阶重画

⛔ 根因:禁布区 ≠ 板材。ZONES['finger'] 早写着「槽体 ~9mm 要让开」,但那只是
   「别放件」,板框从来没开台阶 —— outline_pts() 第一段就是 (r,0)→(BW-r,0),
   90 mm 宽的板材一直到 y=0。插槽塑料体只有 39 mm 且两端封闭 ⇒ 插不到底。

板框(用户拍板):舌头 36.0 mm(x 27~63)/ 台阶高 12.0 / 内凹角 R1.0,切掉 648 mm²(10.6%)。
放行核算:被切区里铜 0 / 孔 0 / 焊盘 0 / 器件 0(板上读回与缓存两种口径一致);
7 个定位孔最南 y=14.46 全在板材上;铣穿槽 y 56.86 不受影响。
代价:主板投影底边在舌头两侧悬空 1.0 mm,靠 7 个 M1.6 尼龙立柱压住,无害。

铺铜:抓板发现板上只有 11 块(L1/L4 的 GND 底填整层缺失,而记录写的是 25/25)。
补齐后按台阶重画成 31 块(L2 11 + L1 9 + L4 9 + L3 两岛),填充 31/31,DRC 间距 0 条。
+12V 岛改成阶梯形单环:舌头段仍下探 y=9.0 接 12V 三指 launch 孔,东段抬到 12.5,
断面 11.0 → 7.5 mm(1 oz 内层约 5.1 A,需求 1.5 A 峰值)。

两条判据上的坑:
- 阶梯形多边形不能按包围盒判越界(+12V 岛会误报)⇒ 改成逐边 0.25 mm 采样。
- pcb_pour.inner_poly()/outer_poly() 是死函数,plan() 只用 gnd_tiles()。

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0168Bu6SEx3cXXCJLkdnE6zB
这个提交包含在:
zhou yang
2026-09-12 12:36:36 +08:00
共同撰写人 Claude Opus 5
父节点 312089326e
当前提交 dc4f9b9d1c
共修改 4 个文件,包含 304 行新增和 53 行删除
+105 -34
查看文件
@@ -11,16 +11,23 @@ u"""R2 刀片 PCB(M4):板框 + 分区参考 + 摆位常量。
**板左下角 = (0, 0),x 向右、y 向上**(EasyEDA PCB 里 y 向上为正,见方案 §22.5)。
这正好是方案 §3.1 那张分区图的坐标,图里的数可以直接抄。
⛔ **2026-09-11 用户拍板:98×76 → 90×68**(方案 §34)。
⛔⛔ **2026-09-11 晚 用户拍板:主板转 180°,两条排线槽挪到顶部**(方案 §35)。
接口边(J600 / J1401 / 侧键)朝**上**,服务带(C60 + 两条槽 + J3)在板子**顶部**,
手机远端边贴着金手指区上沿(y 11.0)。与手机绑定的一切(七个孔 / 两条槽 / J4 / J5 / K1~K4)
一律经 `phone_xy()` 从本体坐标 (u, v) 推,**不许写死、不许自己算旋转**。
```
y 76 ┬──────────────────────────────────┬──────────────┐
│ │ 功率列 26mm │
│ 小米8 主板 67.09 × 44.66 │ Buck/L/Cin │
y 29 ┼──── 接口边 ───────────────────────┤ eFuse/Rs │
│ 服务带 18mm C60/TP/EDL/BM24/K1~4 │ INA/PCA9555 │
y 11 ┼──────────────────────────────────┴──────────────┤
│ 金手指区 11mm(槽体 ~9mm 要让开,这一带不许放件) │
y 0 ┴───────────────── 板边(卡边)─────────────────────┘
x 0 3 ──── 手机主板 ──── 70.09 72 ─ 功率列 ─ 98
y 68.00 ┬──────────────────────────────────┬─────────────┐
│ 服务带 12.34 尾插槽 / C60 / 电池槽 │ 功率列 19.4 │
y 55.66 ┼──── 接口边(朝上)─────────────────┤ U3/Rs1 (顶) │
│ │ eFuse │
│ 小米8 主板 67.09 × 44.66(转 180)│ L / Buck │
│ │ Cin / F1(底)│
y 11.00 ┼──────────────────────────────────┴─────────────┤
│ 金手指区 11mm(槽体 ~9mm 要让开,这一带不许放件) │
y 0.00 ┴───────────────── 板边(卡边)──────────────────┘
x 0 1.5 ─── 手机主板 ─── 68.59 70.59 ─ 功率列 ─ 90
```
## 板框的底边是**断开**的
@@ -55,8 +62,20 @@ PCB = '3b9c4e39c8d164db' # BLADE-PCB
MIL = fp_edge.MIL
# ── 板框(mm,出处 方案 §3.1)─────────────────────────────────────────
BW, BH = 98.0, 76.0
BW, BH = 90.0, 68.0 # 2026-09-11 由 98×76 压缩(用户拍板)
CORNER_R = 2.0
# ── 金手指两侧的台阶(2026-09-12 用户指出「金手指两侧没有开槽,插不进插槽」)──────────
# PCI-E 卡在金手指那一段是「凸」字形:只有舌头伸进插槽,两侧板材必须收到槽体顶面以上。
# 刀片原来是一整块 90×68 的圆角矩形,底边一条直线 ⇒ 塑料体(39 mm,两端封闭)会顶住
# x<27 / x>63 的板材,**插不到底**。
# 舌头 36.0 宽(用户拍板,比金手指 33.70 两侧各多 1.15);台阶高 12.0(槽体 ~9,余 3)。
# 内凹角必须倒圆角:外形铣刀下不去直角,取 R1.0。
# ⚠ 台阶 12.0 > 手机主板落点 y0 = 11.0 ⇒ 主板投影底边在舌头两侧**悬空 1.0 mm**
# (左 x 1.50~27.00 / 右 x 63.00~68.59)。7 个定位孔最南 y=14.46 全在板材上、
# 主板靠 M1.6 尼龙立柱固定而不是靠边缘托住 ⇒ 无害,但 selfcheck 的分区断言要按舌头口径判。
TONGUE_X0, TONGUE_X1 = 27.0, 63.0
STEP_H = 12.0
STEP_R = 1.0 # 台阶内凹角(铣刀半径,嘉立创外形最小 R0.5,取 1.0 稳)
CORNER_SEG = 8 # 每个 R2 圆角用几段折线
OUTLINE_W = 0.15
@@ -66,8 +85,26 @@ J1_X, J1_Y, J1_ROT = BW / 2.0, 0.0, 0
# ── 小米8 主板的落点 + 定位孔(板级事实,摆位/布线/铺铜都从这里取)────────
# 主板 67.09 × 44.66,长边水平、**接口边(v=0,J600/J1401/侧键都在上面)朝下**。
PHONE_ORG = (3.0, 29.0)
# ⛔ 2026-09-11 晚:**主板转 180°**(接口边朝上,方案 §35)。`PHONE_ORG` 仍是手机投影矩形的
# **左下角**(板坐标),但本体 (u, v) → 板 (x, y) 要经 `phone_xy()`:u 从右往左、v 从上往下。
# y 的取法:手机远端边贴金手指区上沿 11.0(原来 C60 也是贴这条线放的,同一条禁布规则);
# 接口边 = 11 + 44.66 = 55.66,上面 12.34 的服务带放 C60(本体 11)+ 两条槽 + J3。
# x 不变(1.5):H6/H7(u≈64.7)转过来落在左边,螺钉头离板边 2.2 − 1.6 = 0.6 以上。
# ⚠ SIM 卡托在 u 大的那一端(mi8_mech 注释:轮廓 70.3 含卡托 3.3)⇒ 转过来在**左边**,
# 装着卡托的话会伸出板左沿 ~1.8 mm(机箱侧壁要让)。
PHONE_ORG = (1.5, 11.0)
PHONE_WH = (67.09, 44.66)
PHONE_ROT = 180
IF_Y = round(PHONE_ORG[1] + PHONE_WH[1], 3) # 接口边(v=0)在板上的 y:55.66
def phone_xy(u, v):
u"""主板本体坐标 (u, v) → 刀片板坐标 (x, y)。**唯一的旋转实现**,别处不许自己算。
PHONE_ROT = 180:本体绕投影中心转半圈 ⇒ x = 右沿 − u,y = 上沿 − v(接口边 v=0 在上)。"""
if PHONE_ROT == 180:
return (round(PHONE_ORG[0] + PHONE_WH[0] - u, 3), round(PHONE_ORG[1] + PHONE_WH[1] - v, 3))
return (round(PHONE_ORG[0] + u, 3), round(PHONE_ORG[1] + v, 3))
# ⛔ **主板定位孔**。整个 §3.2 的高度预算 / §21.1 的槽距结论都建立在
# 「螺丝把主板压在刀片上、螺柱间隙 1.0 mm」(R1 §22.3e 的中框式焊盘)之上 ——
@@ -87,8 +124,7 @@ def phone_holes():
u"""{名字: (x, y)},刀片坐标。"""
m = json.load(io.open(os.path.join(os.path.dirname(HERE), 'mi8_mech.json'),
encoding='utf-8'))
return {k: (round(PHONE_ORG[0] + u, 3), round(PHONE_ORG[1] + v, 3))
for k, (u, v) in m['holes'].items()}
return {k: phone_xy(u, v) for k, (u, v) in m['holes'].items()}
# ── 排线穿板槽(2026-09-09 用户拍板:屏蔽窗去掉、器件全到 B 面、给两条排线开槽)──
@@ -109,7 +145,11 @@ def phone_holes():
# 按 53.58 定,尾插车道西沿 47.6 正好让开 H4 螺钉头 (44.9 + 2.2),与 R1 自洽。
J600_U = 23.77 # 电池座中心(两份数据一致;§22.3b 标 ±2,槽放宽到 12)
J1401_U = 53.58 # 尾插座本体中心(§22.3b,±0.3)
SLOT_Y = (24.8, 27.8) # 槽的 y 区间:接口边外侧 1.2~4.2,中心离边 2.7(R1 同)
# 槽的 y 区间:接口边外侧 1.2~4.2,中心离边 2.7(R1 同)。**跟着接口边走**。
# ⛔ 2026-09-11 晚主板转 180°:接口边朝上 ⇒ 槽在接口边**以北**(顶部服务带里),
# 排线翻过接口边 → 穿槽 → 在 B 面 U 形折回、往 **−y** 走到 J5 / J4。
SLOT_Y = (round(IF_Y + 1.2, 3), round(IF_Y + 4.2, 3))
SLOT_RIB = 2.0 # 槽顶到板边留的肋(顶边导轨也咬在这条肋上)
SLOT_W = {'BAT': 12.0, 'TAIL': 20.0} # 排线 8 / 18,各留余量
FLEX_W = {'BAT': 8.0, 'TAIL': 18.0}
SLOT_R = 1.0 # 槽内圆角(铣刀半径,嘉立创内槽最小 R0.5)
@@ -117,7 +157,7 @@ SLOT_R = 1.0 # 槽内圆角(铣刀半径,嘉立创内槽
def flex_x():
u"""两条排线的中心 x(刀片坐标)。"""
return {'BAT': round(PHONE_ORG[0] + J600_U, 3), 'TAIL': round(PHONE_ORG[0] + J1401_U, 3)}
return {'BAT': phone_xy(J600_U, 0.0)[0], 'TAIL': phone_xy(J1401_U, 0.0)[0]}
def flex_slots():
@@ -143,23 +183,26 @@ def slot_check():
dy = max(w[1] - hy, 0.0, hy - w[3])
if math.hypot(dx, dy) < HOLE_KEEP:
bad.append(u'槽 %s 离螺钉孔 %s 只有 %.2f < %.1f' % (k, hn, math.hypot(dx, dy), HOLE_KEEP))
if w[3] > PHONE_ORG[1]:
bad.append(u'槽 %s 顶 %.1f 进了手机投影(接口边 %.1f)' % (k, w[3], PHONE_ORG[1]))
if w[1] < ZONES['finger'][3] + 1.0:
bad.append(u'槽 %s 底 %.1f 压到金手指区' % (k, w[1]))
if w[1] < IF_Y:
bad.append(u'槽 %s 底 %.1f 进了手机投影(接口边 %.1f)' % (k, w[1], IF_Y))
if w[3] > BH - SLOT_RIB:
bad.append(u'槽 %s 顶 %.1f 离板边不到 %.1f 的肋' % (k, w[3], SLOT_RIB))
s = flex_slots()
rib = s['TAIL'][0] - s['BAT'][2]
a, b = sorted(s.values())
rib = b[0] - a[2]
if rib < 3.0:
bad.append(u'两槽之间只剩 %.1f mm 肋(要 >= 3)' % rib)
return bad
# 分区(只是参考矩形,画在文档层,摆位脚本从这里取)
PHONE_X1 = round(PHONE_ORG[0] + PHONE_WH[0], 3) # 手机右沿 68.59
POWER_X0 = round(PHONE_X1 + 2.0, 3) # 功率列西沿 70.59(离手机 2 mm)
ZONES = {
'finger': (0.0, 0.0, BW, 11.0), # 金手指区:不许放件(槽体 ~9mm)
'service': (0.0, 11.0, 72.0, 29.0), # 服务带:C60 / TP / EDL / BM24 / K1~K4
'phone': (3.0, 29.0, 3 + 67.09, 29.0 + 44.66), # 小米8 主板投影
'power': (72.0, 11.0, BW, BH), # 功率列
'finger': (TONGUE_X0, 0.0, TONGUE_X1, STEP_H), # 舌头(唯一伸进插槽的那块):不许放件
'service': (0.0, IF_Y, POWER_X0, BH), # 服务带(2026-09-11 晚起在顶部):C60 / 两条排线槽 / J3
'phone': (PHONE_ORG[0], PHONE_ORG[1], PHONE_X1, round(PHONE_ORG[1] + PHONE_WH[1], 3)),
'power': (POWER_X0, STEP_H, BW, BH), # 功率列
}
@@ -173,26 +216,37 @@ def corner(cx, cy, a0, a1):
def outline_pts():
u"""板框点列(逆时针,从底边左端 (r,0) 起,回到 (r,0) 闭合)。
u"""板框点列(逆时针,从舌头左下角起,闭合)。**凸字形**:底边只剩舌头那一段。
⚠ 相邻重合点必须去掉:圆角的首点和前一条直边的终点是同一个点,留着就会生成
一条**零长度线段**,编辑器会静默把它吃掉(下发 41 段读回 37 段),
于是「读回条数 != 期望」变成一个没完没了的假警报。这正是 CLAUDE.md 里
原理图那条「零长度导线」坑的 PCB 版。
⛔ 第一段必须仍是底边 `(TONGUE_X0, 0) -> (TONGUE_X1, 0)`:`segments()` 和
`outline_polygon()` 都按这个前提把防呆缺口插进去。
"""
r = CORNER_R
pts = [(r, 0.0), (BW - r, 0.0)]
pts += corner(BW - r, r, 270, 360) # 右下
r, ri, hs = CORNER_R, STEP_R, STEP_H
pts = [(TONGUE_X0, 0.0), (TONGUE_X1, 0.0)] # 舌头底边(缺口在中间断开)
pts += [(TONGUE_X1, hs - ri)]
pts += corner(TONGUE_X1 + ri, hs - ri, 180, 90) # 右侧台阶内凹角
pts += [(BW - r, hs)]
pts += corner(BW - r, hs + r, 270, 360) # 右下外角
pts += [(BW, BH - r)]
pts += corner(BW - r, BH - r, 0, 90) # 右上
pts += corner(BW - r, BH - r, 0, 90) # 右上
pts += [(r, BH)]
pts += corner(r, BH - r, 90, 180) # 左上
pts += [(0.0, r)]
pts += corner(r, r, 180, 270) # 左下,收回 (r,0)
pts += corner(r, BH - r, 90, 180) # 左上
pts += [(0.0, hs + r)]
pts += corner(r, hs + r, 180, 270) # 左下外角
pts += [(TONGUE_X0 - ri, hs)]
pts += corner(TONGUE_X0 - ri, hs - ri, 90, 0) # 左侧台阶内凹角
pts += [(TONGUE_X0, 0.0)]
out = [pts[0]]
for q in pts[1:]:
if abs(q[0] - out[-1][0]) > 1e-9 or abs(q[1] - out[-1][1]) > 1e-9:
out.append(q)
if abs(out[0][0] - out[-1][0]) > 1e-9 or abs(out[0][1] - out[-1][1]) > 1e-9:
out.append(out[0])
assert abs(out[0][0] - out[-1][0]) < 1e-9 and abs(out[0][1] - out[-1][1]) < 1e-9, u'板框点列没有闭合'
return out
@@ -283,7 +337,7 @@ def rrect_pts(x0, y0, x1, y1, r, seg=6):
def selfcheck():
bl, br = notch_break()
assert round(br - bl, 4) == 2.9, (bl, br)
assert round(br - bl, 4) == round(fp_edge.NOTCH_W + 2 * fp_edge.LEAD_IN, 4), (bl, br)
# 金手指必须整个落在板内,且不侵占服务带
for pin in ('A1', 'A32'):
x, y, w, h = fp_edge.pad_geom(pin)
@@ -294,10 +348,27 @@ def selfcheck():
assert fp_edge.NOTCH_D < ZONES['finger'][3], u'缺口比金手指区还深'
# 分区不许互相重叠(除了 finger/service 只共边)
z = ZONES
assert z['service'][3] == z['phone'][1] == 29.0
assert z['service'][1] == z['phone'][3] == IF_Y
# ⚠ 台阶之后 finger 区只剩舌头那一块(x TONGUE_X0~TONGUE_X1)。手机投影 y0=11.0
# 低于台阶 12.0,但它在舌头正上方那一段是有板材的;两侧那 1 mm 是**有意的悬空**
# (定位孔最南 14.46 全在板材上,主板靠尼龙立柱固定)⇒ 只判「不压舌头」。
# 主板投影底边在舌头两侧的**悬空量**(有意为之,见 STEP_H 处的说明):只判别超标。
_hang = max(0.0, STEP_H - z['phone'][1])
assert _hang <= 1.5, u'主板投影底边悬空 %.2f mm 过多(台阶 %.1f vs 主板 y0 %.2f)' % (
_hang, STEP_H, z['phone'][1])
assert min(v[1] for v in phone_holes().values()) > STEP_H, u'有定位孔落进台阶切掉的区域'
assert fp_edge.EDGE_CLR + fp_edge.LONG_L < STEP_H, u'金手指比台阶还高'
assert z['phone'][2] <= z['power'][0], u'手机投影压进功率列'
assert z['service'][2] <= z['power'][0], u'服务带压进功率列'
assert z['phone'][3] <= BH, u'手机投影超出板顶'
# 台阶:金手指与防呆缺口必须整个落在舌头里,内凹角要铣得出来
_fx0 = J1_X + fp_edge.PIN_X['A1'] - fp_edge.FW / 2
_fx1 = J1_X + fp_edge.PIN_X['A32'] + fp_edge.FW / 2
assert TONGUE_X0 < _fx0 and _fx1 < TONGUE_X1, u'金手指 %.2f~%.2f 超出舌头 %.1f~%.1f' % (_fx0, _fx1, TONGUE_X0, TONGUE_X1)
_bl, _br = notch_break()
assert TONGUE_X0 < _bl < _br < TONGUE_X1, u'防呆缺口超出舌头'
assert STEP_R >= 0.5, u'台阶内凹角 R%.1f 铣刀下不去' % STEP_R
assert TONGUE_X1 - TONGUE_X0 < BW, u'舌头没窄于板宽,等于没开台阶'
bad = slot_check()
assert not bad, u'排线槽校核不过: %s' % bad
return True