R2 底板:补 6 条自由丝印(插槽 NOT PCIe 警告 + J1/J23 接口标注)

板上 pcb_PrimitiveString 原本 0 条 —— 方案 §5.2 要求的插槽警告、J1/J23 的接口标注
一直只写在原理图说明文字里,从没落到 PCB。

已补并读回核对 6/6:
- PWR 20V  L4 (12.500, 1.800)    J1 与 J23 同一颗 USB-C 座,插错要能看出来
- USB      L4 (84.250, 1.800)    J23 是 Hub 上行口
- NOT PCIe - PhoneRail blade only  L3 (49.000, 79.480)
- NOT PCIe ×3  L3 (36.000,18.520) / (62.000,38.840) / (45.000,59.160)
  完整句只放得下一条,其余槽间放短标 ⇒ 五个槽每个都邻着一条。

三条坑(已写进 R2 §39.5):
- ⛔ 丝印压铜不算冲突(铜上有阻焊)。第一版把压铜也当冲突,六个候选位全军覆没,
  看着像「板上没地方放字」。真正要避的只有 焊盘 / 过孔 / 位号 / 安装孔 / 板框外。
- ⚠ alignMode 要显式传 5(CENTER),不传默认不是居中,字会整体偏出去;
  位号 Attribute 用的 3 是另一套口径。
- ⚠ 核对不能拿文案当字典 key:NOT PCIe 有三条,同名互相覆盖 ⇒
  「6 条都建上了」却报成「4/6 对上」。按 文案+坐标+层 配对。

新工具 silk_str.py:自带落点搜索(0.25 步长 + 偏移上限)、幂等下发、--verify 只读核对。

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0168Bu6SEx3cXXCJLkdnE6zB
这个提交包含在:
zhou yang
2026-09-12 12:48:15 +08:00
共同撰写人 Claude Opus 5
父节点 dc4f9b9d1c
当前提交 22d9f31e9b
共修改 4 个文件,包含 294 行新增和 0 行删除
+50
查看文件
@@ -0,0 +1,50 @@
[
{
"text": "PWR 20V",
"layer": 4,
"x": 12.5,
"y": 1.8,
"fs": 1.2,
"lw": 0.15
},
{
"text": "USB",
"layer": 4,
"x": 84.25,
"y": 1.8,
"fs": 1.2,
"lw": 0.15
},
{
"text": "NOT PCIe - PhoneRail blade only",
"layer": 3,
"x": 49.0,
"y": 79.48,
"fs": 1.2,
"lw": 0.15
},
{
"text": "NOT PCIe",
"layer": 3,
"x": 36.0,
"y": 18.52,
"fs": 1.2,
"lw": 0.15
},
{
"text": "NOT PCIe",
"layer": 3,
"x": 62.0,
"y": 38.84,
"fs": 1.2,
"lw": 0.15
},
{
"text": "NOT PCIe",
"layer": 3,
"x": 45.0,
"y": 59.16,
"fs": 1.2,
"lw": 0.15
}
]
+195
查看文件
@@ -0,0 +1,195 @@
# -*- coding: utf-8 -*-
u"""底板的**自由丝印**(`pcb_PrimitiveString`):接口标注 + 插槽警告。
/c/Python314/python .claude/r2/silk_str.py # 抓板 + 找落点 + 报告(不下发)
/c/Python314/python .claude/r2/silk_str.py --push # 下发(幂等:先删同文案的,再建)+ 读回核对
/c/Python314/python .claude/r2/silk_str.py --verify # 只读回核对
## 要标什么(方案 §5.2 / A1B-PD 的 note / PROGRESS 待办)
| 文案 | 层 | 为什么 |
|---|---|---|
| `PWR 20V` | 4(底层,J1 在底面)| J1 与 J23 是**同一颗 USB-C 座**,插错要能看出来 |
| `USB` | 4 | 同上,J23 是 Hub 上行口 |
| `NOT PCIe - PhoneRail blade only` | 3(顶层,插槽在顶面)| 槽是 PCI-E x4 座,但针位是自定义的;插真 PCIe 卡会烧 |
| `NOT PCIe` ×3 | 3 | 五个槽每个都要邻着一条提示,完整句只放得下一条 |
## 判据:丝印**只避** 焊盘 / 过孔 / 位号 / 安装孔 / 板框外
⛔ **压铜是常态**,不算冲突 —— 铜上有阻焊,油墨正常印在阻焊上。第一版把「压铜」也当冲突,
于是六个候选位全军覆没,看着像「板上没地方放字」。
⚠ `alignMode` 要显式传 **5 = CENTER**(枚举 `EPCB_PrimitiveStringAlignMode`)。
不传的话默认不是居中,坐标含义和这里按中心算的框对不上,字会整体偏出去。
(位号 `Attribute` 用的 3 是另一套口径,别照抄。)
"""
import io
import json
import math
import os
import sys
sys.stdout.reconfigure(encoding='utf-8')
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.dirname(HERE))
sys.path.insert(0, HERE)
from eda import run # noqa: E402
import pcb_base as B # noqa: E402
import fp_edge # noqa: E402
MIL = fp_edge.MIL
FS, LW = 1.2, 0.15 # 字高 / 线宽(嘉立创丝印下限 0.8 / 0.15)
CHAR_W, EDGE, CLR = 0.62, 0.5, 0.10
ALIGN_CENTER = 5
PLAN = os.path.join(HERE, '_base_silk_str.json')
# (文案, 层, 目标 x, 目标 y, 允许偏移 dx, dy)
SILK = [
(u'PWR 20V', 4, 12.5, 1.8, 4.0, 1.0), # J1(底面)南侧
(u'USB', 4, 84.0, 1.8, 4.0, 1.0), # J23(底面)南侧
(u'NOT PCIe - PhoneRail blade only', 3, 49.0, 79.48, 8.0, 3.0), # 槽4~槽5 之间
(u'NOT PCIe', 3, 36.0, 18.52, 10.0, 3.0), # 槽1~槽2
(u'NOT PCIe', 3, 62.0, 38.84, 10.0, 3.0), # 槽2~槽3
(u'NOT PCIe', 3, 49.0, 59.16, 16.0, 4.0), # 槽3~槽4
]
READ = u"""
await eda.dmt_EditorControl.openDocument('%s');
await new Promise(r=>setTimeout(r,2500));
const M = 0.0254;
const V = (await eda.pcb_PrimitiveVia.getAll()||[]).map(v=>[v.x*M, v.y*M, v.diameter*M]);
const A = (await eda.pcb_PrimitiveAttribute.getAll()||[]).filter(a=>a.key==='Designator' && a.valueVisible)
.map(a=>({ref:a.value, lay:a.layer, x:a.x*M, y:a.y*M, rot:((a.rotation%%360)+360)%%360, fs:a.fontSize*M}));
const S = (await eda.pcb_PrimitiveString.getAll()||[])
.map(s=>({lay:s.layer, x:s.x*M, y:s.y*M, t:(s.text!==undefined?s.text:s.string), id:s.primitiveId}));
return {vias:V, attrs:A, strs:S};
""" % B.PCB
def tbox(t, x, y, fs=FS):
h = fs
w = CHAR_W * h * len(t)
return (x - w / 2. - CLR, y - h / 2. - CLR, x + w / 2. + CLR, y + h / 2. + CLR)
def _ov(p, q):
return not (p[2] <= q[0] or q[2] <= p[0] or p[3] <= q[1] or q[3] <= p[1])
def clean(t, lay, x, y, pads, vias, attrs):
bb = tbox(t, x, y)
if bb[0] < EDGE or bb[1] < EDGE or bb[2] > B.BW - EDGE or bb[3] > B.BH - EDGE:
return False
want = (1, 12) if lay == 3 else (2, 12)
for p in pads:
if p[6] in want and _ov(bb, (p[2] - p[4] / 2., p[3] - p[5] / 2.,
p[2] + p[4] / 2., p[3] + p[5] / 2.)):
return False
for (vx, vy, vd) in vias:
if _ov(bb, (vx - vd / 2., vy - vd / 2., vx + vd / 2., vy + vd / 2.)):
return False
for a in attrs:
if a['lay'] != lay:
continue
h = a['fs']
w = CHAR_W * h * len(a['ref'])
if a['rot'] in (90, 270):
w, h = h, w
if _ov(bb, (a['x'] - w / 2., a['y'] - h / 2., a['x'] + w / 2., a['y'] + h / 2.)):
return False
r = B.HOLE_D / 2. + 0.6
for (hx, hy) in B.MOUNT_HOLES.values():
if _ov(bb, (hx - r, hy - r, hx + r, hy + r)):
return False
return True
def fit(t, lay, x0, y0, dx, dy, pads, vias, attrs):
r = 0.0
while r <= max(dx, dy):
for sy in (0, 1, -1):
for sx in (0, 1, -1):
x = x0 + sx * r
y = y0 + sy * r * (dy / max(dx, 1e-9))
if abs(x - x0) > dx or abs(y - y0) > dy:
continue
if clean(t, lay, x, y, pads, vias, attrs):
return round(x, 3), round(y, 3)
r += 0.25
return None
def main():
r = run(READ, timeout=600)
d = r.get('result')
assert d, json.dumps(r, ensure_ascii=False)[:400]
pads = json.load(io.open(os.path.join(HERE, '_base_pads.json'), encoding='utf-8'))
print(u'板上:过孔 %d / 可见位号 %d / 自由丝印 %d'
% (len(d['vias']), len(d['attrs']), len(d['strs'])))
plan, miss = [], []
for (t, lay, x0, y0, dx, dy) in SILK:
g = fit(t, lay, x0, y0, dx, dy, pads, d['vias'], d['attrs'])
if g:
plan.append({'text': t, 'layer': lay, 'x': g[0], 'y': g[1], 'fs': FS, 'lw': LW})
print(u' %-34s L%d (%7.3f, %7.3f) 偏离目标 %.2f'
% (t, lay, g[0], g[1], math.hypot(g[0] - x0, g[1] - y0)))
else:
miss.append(t)
print(u' %-34s L%d ⛔ 找不到干净落点' % (t, lay))
io.open(PLAN, 'w', encoding='utf-8').write(json.dumps(plan, ensure_ascii=False, indent=1))
if miss:
sys.exit(u'!! %d 条没落点,不下发' % len(miss))
if '--verify' in sys.argv:
verify(plan)
return
if '--push' not in sys.argv:
print(u'(只算;加 --push 下发)')
return
# 幂等:先删掉板上文案相同的,再建
kill = [s['id'] for s in d['strs'] if s['t'] in [p['text'] for p in plan]]
rows = [[p['layer'], p['x'] * MIL, p['y'] * MIL, p['text'], p['fs'] * MIL, p['lw'] * MIL]
for p in plan]
res = run(u"""
await eda.dmt_EditorControl.openDocument('%s');
await new Promise(r=>setTimeout(r,2500));
const KILL = %s, R = %s;
if (KILL.length) { try { await eda.pcb_PrimitiveString.delete(KILL); } catch (e) {} }
let n = 0;
for (const [lay, x, y, t, fs, lw] of R) {
const o = await eda.pcb_PrimitiveString.create(lay, x, y, t, undefined, fs, lw, %d, 0);
if (o) n++;
}
await eda.pcb_Document.save();
return {deleted: KILL.length, made: n,
now: (await eda.pcb_PrimitiveString.getAll()||[]).length};
""" % (B.PCB, json.dumps(kill), json.dumps(rows, ensure_ascii=False), ALIGN_CENTER), timeout=600)
print(u'下发:%s' % json.dumps(res.get('result'), ensure_ascii=False))
verify(plan)
def verify(plan):
u"""读回逐条核对。
⛔ **不能用文案当字典 key** —— `NOT PCIe` 有三条,同名会互相覆盖,
于是「6 条都建上了」却报成「4/6 对上」。按**文案 + 坐标**配对,配上一条就消费掉一条。"""
back = run(READ, timeout=600).get('result') or {}
pool = list(back.get('strs', []))
bad = []
for q in plan:
hit = None
for k, s in enumerate(pool):
if s['t'] == q['text'] and abs(s['x'] - q['x']) <= 0.05 and abs(s['y'] - q['y']) <= 0.05 and s['lay'] == q['layer']:
hit = k
break
if hit is None:
bad.append(u'%s @ (%.3f, %.3f) L%d' % (q['text'], q['x'], q['y'], q['layer']))
else:
pool.pop(hit)
print(u'读回核对:%d / %d 条对上;板上自由丝印共 %d 条%s'
% (len(plan) - len(bad), len(plan), len(back.get('strs', [])),
(u';没对上:%s' % u' / '.join(bad)) if bad else u''))
return not bad
if __name__ == '__main__':
main()
+23
查看文件
@@ -87,6 +87,29 @@
可行性先算过:现字高下 163 个里 161 个有解 ⇒ **不用缩字号**。 可行性先算过:现字高下 163 个里 161 个有解 ⇒ **不用缩字号**。
### 自由丝印(`silk_str.py`,同日补)
板上 `pcb_PrimitiveString` 原本 **0 条** —— 方案 §5.2 要求的插槽警告、J1/J23 的接口标注,
一直只写在原理图的说明文字里,**从没落到 PCB**。已补 6 条并读回核对:
| 文案 | 层 | 坐标 |
|---|---|---|
| `PWR 20V` | 4(底面,J1)| (12.500, 1.800) |
| `USB` | 4(底面,J23)| (84.250, 1.800) |
| `NOT PCIe - PhoneRail blade only` | 3(顶面,插槽)| (49.000, 79.480) |
| `NOT PCIe` ×3 | 3 | (36.000, 18.520) / (62.000, 38.840) / (45.000, 59.160) |
完整句只放得下一条,其余槽间放短标 ⇒ **五个槽每个都邻着一条**。字高 1.2 / 线宽 0.15。
**三条坑**:
1. ⛔ **丝印压铜不算冲突**(铜上有阻焊,油墨正常印在上面)。第一版把压铜也当冲突,
六个候选位全军覆没,看着像「板上没地方放字」。真正要避的只有
**焊盘 / 过孔 / 位号 / 安装孔 / 板框外**。
2. ⚠ **`alignMode` 要显式传 5(CENTER)**,不传默认不是居中,字会整体偏出去。
位号 `Attribute` 用的 3 是另一套口径,别照抄。
3. ⚠ **核对不能拿文案当字典 key**:`NOT PCIe` 有三条,同名互相覆盖 ⇒
「6 条都建上了」却报成「4/6 对上」。按**文案 + 坐标 + 层**配对,配上一条消费一条。
## ⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐ 2026-09-12(收尾):按用户最新布局**重布风扇窗口并已下发** —— 窝内只差 J27.4 ## ⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐ 2026-09-12(收尾):按用户最新布局**重布风扇窗口并已下发** —— 窝内只差 J27.4
用户:「将底板按照现在我的布局重新布线」→ 选「风扇窗口整体重布(拆掉那 43 段重来)」+「GND 回流孔一起补」。 用户:「将底板按照现在我的布局重新布线」→ 选「风扇窗口整体重布(拆掉那 43 段重来)」+「GND 回流孔一起补」。
+26
查看文件
@@ -3326,6 +3326,32 @@ FAN_PWM_L (10.30, 83.90, 22.50, 87.30) 盖住 J27.4 与 Q6.3
工具:**`silk_base.py`**(`--push` 走 `pcb_PrimitiveAttribute.modify(id, {x, y, rotation})`, 工具:**`silk_base.py`**(`--push` 走 `pcb_PrimitiveAttribute.modify(id, {x, y, rotation})`,
下发后读回逐个核对),快照 `_base_silk.json`、方案 `_base_silk_plan.json`。 下发后读回逐个核对),快照 `_base_silk.json`、方案 `_base_silk_plan.json`。
### 39.5 自由丝印(`silk_str.py`,2026-09-12 补)
板上原本 `pcb_PrimitiveString` 是 **0 条** —— 方案 §5.2 要求的插槽警告、以及 J1/J23 的接口标注
一直只写在原理图的说明文字里,**没有落到 PCB**。已补 6 条:
| 文案 | 层 | 坐标 | 为什么 |
|---|---|---|---|
| `PWR 20V` | 4 | (12.500, 1.800) | J1 与 J23 **同一颗 USB-C 座**,插错要能看出来 |
| `USB` | 4 | (84.250, 1.800) | J23 是 Hub 上行口 |
| `NOT PCIe - PhoneRail blade only` | 3 | (49.000, 79.480) | 槽是 PCI-E x4 座但针位自定义,插真卡会烧 |
| `NOT PCIe` ×3 | 3 | (36.000, 18.520) / (62.000, 38.840) / (45.000, 59.160) | 完整句只放得下一条,其余槽间放短标 ⇒ **五个槽每个都邻着一条** |
字高 1.2 / 线宽 0.15(嘉立创丝印下限 0.8 / 0.15)。J1/J23 在底面 ⇒ 走底层丝印 layer 4;
插槽在顶面 ⇒ layer 3。
**两条判据上的坑**:
1. ⛔ **丝印压铜不算冲突** —— 铜上有阻焊,油墨正常印在阻焊上。第一版把「压铜」也当冲突,
于是六个候选位全军覆没,看着像「板上没地方放字」。真正要避的只有
**焊盘 / 过孔 / 位号 / 安装孔 / 板框外**。
2. ⚠ **`alignMode` 必须显式传 5(CENTER)**。不传的话默认不是居中,坐标含义和按中心算的
文字框对不上,字会整体偏出去。位号 `Attribute` 用的 3 是另一套口径,别照抄。
`silk_str.py` 自带落点搜索(0.25 mm 步长、限定偏移上限)+ 幂等下发(先删同文案再建)+ 读回核对,
落点存 `_base_silk_str.json`。
## 40. 刀片金手指两侧开台阶(2026-09-12 用户指出「插不进插槽」)+ 铺铜补齐 ## 40. 刀片金手指两侧开台阶(2026-09-12 用户指出「插不进插槽」)+ 铺铜补齐
用户:「解决刀片板与插槽干涉的问题,完善刀片板铺铜」→「金手指两侧没有开槽,应该会插不进插槽」。 用户:「解决刀片板与插槽干涉的问题,完善刀片板铺铜」→「金手指两侧没有开槽,应该会插不进插槽」。