初始导入:PhoneRail 五路手机直供电源板
原理图/PCB 已在 EasyEDA 里做到 12 页跨页 0 漏接,固件刚完成骨架包 A1。 这次是把已有的一切一次性纳入版本控制,没有改动任何设计内容。 纳入: - 设计与需求文档、PROGRESS.md、小米8 参考原理图 PDF 与追脚脚本 - .claude/ 下的 EasyEDA 桥接工具链与几何/网表缓存(eda-parts.json、 pcb-parts.json、pcb-pads.json、p2-ch1.json 等,是重画各页的唯一事实来源) - 117 张验证截图(本项目所有方向问题都是靠人眼看截图发现的,属于证据不是垃圾) - firmware/ 的 A1 骨架与 site/ 文档站源码 .gitignore 排除:构建产物(firmware/build、host/build、sdkconfig)、 site/node_modules、__pycache__、桥接日志 _bridge.log、脚本自标的 *.stale。 .gitattributes 默认 `* -text`:现有 .py/.json 大多是 CRLF,原样存, 不让 git 重写行尾。唯一例外是 *.sh 强制 LF —— 带 \r 的 shell 脚本 bash 跑不了。 Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QYuExfgyYa6DzRRcUso8M8
这个提交包含在:
@@ -0,0 +1,92 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""重新抓下板 PCB1 的 `pcb1-parts.json`(每个元件的**真实焊盘包围盒**,本地检查用)。
|
||||
|
||||
**从原理图导入变更之后必须跑一次**(skill §9m:包围盒不能在元件摆好之后重抓 ——
|
||||
这里已经把焊盘位置反转回元件本地坐标系,所以摆好之后再抓也等价)。
|
||||
|
||||
用法: /c/Python314/python .claude/pcb1_parts_dump.py
|
||||
"""
|
||||
import io, json, os, sys
|
||||
HERE = os.path.dirname(os.path.abspath(__file__)); sys.path.insert(0, HERE)
|
||||
from eda import run
|
||||
|
||||
PCB = '7e9415a16a909c51'
|
||||
OUT = os.path.join(HERE, 'pcb1-parts.json')
|
||||
|
||||
JS = u"""
|
||||
await eda.dmt_EditorControl.openDocument('__PCB__');
|
||||
await new Promise(r=>setTimeout(r,2000));
|
||||
const parts = [];
|
||||
for (const c of await eda.pcb_PrimitiveComponent.getAll()) {
|
||||
const d = c.designator || c.name;
|
||||
const x = c.x, y = c.y, rot = ((c.rotation % 360) + 360) % 360;
|
||||
const lay = (c.layer !== undefined) ? c.layer : 1;
|
||||
const th = -rot * Math.PI / 180, ct = Math.cos(th), st = Math.sin(th);
|
||||
let ps = [];
|
||||
try { ps = await eda.pcb_PrimitiveComponent.getAllPinsByPrimitiveId(c.primitiveId); } catch(e) {}
|
||||
let x0=null,y0=null,x1=null,y1=null;
|
||||
const pads = [];
|
||||
for (const p of ps || []) {
|
||||
let w = 0, h = 0, isPoly = false, pcx = p.x, pcy = p.y;
|
||||
try {
|
||||
const s = p.pad;
|
||||
if (s && s[0] === 'POLYGON') {
|
||||
// ⚠️ POLYGON 盘的 s[1] 是**点数组**(板坐标 mil),不是宽度。
|
||||
// 原来 `+s[1] || 0` 得到 NaN -> 0,尺寸就退回到 hole 那一位 ——
|
||||
// 于是 U1~U5 的散热盘(真身 2.03 x 4.32 mm)在 pcb1-parts.json 里
|
||||
// 被记成一个 0.12 mm 的点。见 dump_pads.py 文件头第 3 条。
|
||||
isPoly = true;
|
||||
const pts = (s[1] || []).filter(v => typeof v === 'number');
|
||||
const xs = pts.filter((_, i) => i % 2 === 0), ys = pts.filter((_, i) => i % 2 === 1);
|
||||
const ax = Math.min.apply(null, xs), bx = Math.max.apply(null, xs);
|
||||
const ay = Math.min.apply(null, ys), by = Math.max.apply(null, ys);
|
||||
w = bx - ax; h = by - ay; pcx = (ax + bx) / 2; pcy = (ay + by) / 2;
|
||||
} else {
|
||||
w = +s[1] || 0; h = +s[2] || 0;
|
||||
}
|
||||
} catch(e) {}
|
||||
const rx = pcx - x, ry = pcy - y;
|
||||
const px = rx * ct - ry * st, py0 = rx * st + ry * ct;
|
||||
const py = (lay === 2) ? -py0 : py0;
|
||||
// ⚠️ `p.pad` 里的 w/h 是**未旋转**的,焊盘自己还有一个 rotation。
|
||||
// QFN / SOIC 的侧边引脚就是把同一个长条焊盘转 90° 用的 ——
|
||||
// 不做这一步,U6(WQFN-16)的 bb 在 y 上会短 0.26 mm,
|
||||
// 而摆位检查、铺铜反焊盘全都吃这个 bb。
|
||||
// 2026-09-06 实测:C607 贴到 U6 北排脚旁边,本地检查说净距 0.358(过),
|
||||
// DRC 实测 0.096(3.8 mil,规则要 7.9)。**几何检查全绿、板子是错的。**
|
||||
// 这一套 API 里 getAllPinsByPrimitiveId 给的 rotation 是**度**
|
||||
// (pcb_PrimitivePad.getAll() 给的却是弧度,见 pcb_fanout.py 文件头坑 1)。
|
||||
// POLYGON 的点已经是板坐标,AABB 就是最终朝向,只要跟着**元件**的旋转转回本地。
|
||||
let pr = (((+p.rotation || 0) - rot) % 360 + 360) % 360;
|
||||
if (!isPoly && (pr === 90 || pr === 270)) { const t = w; w = h; h = t; }
|
||||
if (isPoly && (rot === 90 || rot === 270)) { const t = w; w = h; h = t; }
|
||||
let hw = 0, hh = 0;
|
||||
try {
|
||||
// 单铜层焊盘(layer 1/2)上的 hole 是假的(POLYGON 盘的 0.6xmin 占位值),
|
||||
// 不许让它顶掉真实焊盘尺寸 —— 见 dump_pads.py 文件头第 3 条。
|
||||
if (Array.isArray(p.hole) && p.layer !== 1 && p.layer !== 2) {
|
||||
if (String(p.hole[0]).toUpperCase() === 'ROUND') { hw = hh = +p.hole[1] || 0; }
|
||||
else { hw = +p.hole[1] || 0; hh = +p.hole[2] || hw; }
|
||||
}
|
||||
} catch(e) {}
|
||||
const ww = Math.max(w, hw), hhh = Math.max(h, hh);
|
||||
pads.push([p.padNumber, +px.toFixed(3), +py.toFixed(3), ww, hhh, p.net || null]);
|
||||
const a = px - ww/2, b = py - hhh/2, cc = px + ww/2, dd = py + hhh/2;
|
||||
x0 = (x0===null? a : Math.min(x0,a)); y0 = (y0===null? b : Math.min(y0,b));
|
||||
x1 = (x1===null? cc: Math.max(x1,cc)); y1 = (y1===null? dd: Math.max(y1,dd));
|
||||
}
|
||||
parts.push({d, x, y, rot, lay, fp: (c.footprint && c.footprint.name) || null,
|
||||
n: (ps||[]).length, bb: (x0===null ? null : [x0, y0, x1, y1]), pads});
|
||||
}
|
||||
return parts;
|
||||
""".replace('__PCB__', PCB)
|
||||
|
||||
r = run(JS, timeout=900)
|
||||
if not r.get('success'):
|
||||
sys.exit(json.dumps(r, ensure_ascii=False)[:600])
|
||||
parts = r['result']
|
||||
io.open(OUT, 'w', encoding='utf-8').write(json.dumps(parts, ensure_ascii=False))
|
||||
print(u'pcb1-parts.json %d 个元件(bb / pads 都是**元件本地坐标**,单位 mil)' % len(parts))
|
||||
nobb = [p['d'] for p in parts if not p['bb']]
|
||||
if nobb:
|
||||
print(u'⚠ 没有焊盘的元件 %d 个: %s' % (len(nobb), nobb[:12]))
|
||||
在新工单中引用
屏蔽一个用户