R2 底板改 4 层(D15):规则 / 几何 / 背面摆位 / 4 层布线与铺铜,已下发
- 规则:铜层 2→4,差分 0.28/0.18,间距 0.15,过孔 0.3/0.2(rules_base) - 几何:98×98 板框 + 4×M3,槽阵居中,L1 12V 大铜 + 指头(pcb_base) - 摆位:五插槽留 L1,其余 133 件翻到 L4;背面镜像规则实测;Hub 放走廊 2、 东移 2.5 mm;扇出圈 / 小件间距 1.5(去耦例外)/ 9 A 过孔阵禁区(place_base) - 叠层按用户决定改为 L1 槽面 / L2 整层 +3V3 / L3 整层 GND / L4 器件面 - 布线:D± 七对按构造布通(USB3 掉头、USB1/2 北去掉头、USB4/5 东绕、 J23 桥),+3V3 / GND / +12V 平面网每脚一孔,0.125 网格 + PathFinder 开关(route_base、remote_route.ps1);剩 37 个网待手工补 - 铺铜:4 层重写,入口链自南向北切段 + 断面校核(pour_base); 已下发布线 / 缝合孔 / 铜面轮廓,填铜未成功(待查) - 工具:plot_base / plot_routes / shot_base,board_check 通孔两层都算 Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_0168Bu6SEx3cXXCJLkdnE6zB
这个提交包含在:
@@ -0,0 +1,444 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""R2 刀片 **B2-CTRL** 页:PCA9555 + 侧键 MOS ×3 + 本槽 I²C 上拉。
|
||||
|
||||
/c/Python314/python .claude/r2/b2_ctrl.py # 只算 + 本地自检
|
||||
/c/Python314/python .claude/r2/b2_ctrl.py --push # 清页 + 重画(幂等)
|
||||
|
||||
## 这一页画什么
|
||||
|
||||
| 位号 | 器件 | 作用 |
|
||||
|---|---|---|
|
||||
| `U4` | PCA9555PW(0x20)| 3 个侧键 + `EDL_EN` + `VBUS_EN` 输出;4 路状态输入 |
|
||||
| `Q3/Q4/Q5` | 2N7002 | 侧键低边 MOS(R1 §13.3 原样:1k 串阻 + 100k 栅极下拉)|
|
||||
| `R40/R41` | 4.7k | 本槽 mux 分支的 I²C 上拉(每片刀片各自一套)|
|
||||
| `R42~R47` | 1k ×3 + 100k ×3 | 三个栅极的串阻与下拉 |
|
||||
| `C40` | 100nF | PCA9555 去耦 |
|
||||
|
||||
## ⚠️ 2026-09-08 改:**DAC 挪到底板**(推翻 D11 的「一片刀片一颗」)
|
||||
|
||||
原来这一页还有 `U4 DAC63204`(只用 CH0)。用户拍板改成**底板 2 颗 DAC63204 喂 5 路**
|
||||
—— 就是 R1 的 U6/U7 那套原样搬,固件 A4 驱动、NVM 预置 0.548V、
|
||||
§4.5 的三条外围硬要求、通道映射表全部现成,不引入任何新料。
|
||||
|
||||
**为什么敢让 `VDAC` 跨连接器**(设计文档 §4.4 的失效表已经算过):
|
||||
|
||||
| 故障 | Vd | Vout |
|
||||
|---|---|---|
|
||||
| 金手指断了 / 空槽 | 悬空 | **3.890 V** ✅ 正好是目标值 |
|
||||
| 对地短路 | 0 V | 4.010 V ✅ |
|
||||
| 短到 +3V3 | 3.3 V | 3.35 V ✅ 安全侧(手机关机)|
|
||||
|
||||
注入增益只有 **0.2007**,线上噪声和两板之间的地差到输出都**衰减 5 倍**;
|
||||
而 §4.5 要求的 `R8 1k + C12 10nF` 低通留在刀片上(Rinj 的 DAC 侧),
|
||||
正好把长线拾到的东西全滤掉 —— **B1-POWER 一个字节不用改**。
|
||||
|
||||
⇒ 位号顺移:PCA9555 从 `U5` 变 **`U4`**,B3-PHONE 的 TPS22918 / TS5A3157
|
||||
相应变成 `U5` / `U6`(方案 §7.2b 已同步)。
|
||||
|
||||
## 本页不放的四样(别在这里找)
|
||||
|
||||
- **DAC** -> 底板 `A3-CTRL`(2 × DAC63204,0x4A / 0x4B)
|
||||
- `EDL_EN` / `VBUS_EN` 的 100k 下拉 -> **B3-PHONE**(贴各自的负载,R1 R741/R701 就在那边)
|
||||
- `INT#` 的上拉 -> **底板 A3-CTRL**(五槽 wire-OR 共用一颗 10k,刀片上放会并成 2k)
|
||||
- `EFUSE_EN` -> **不经 PCA9555**,走金手指专用针直连底板 ESP32(方案 §7.3 的框)
|
||||
|
||||
## 几何约定
|
||||
|
||||
skill §6b/§6c:`create()` 的 rotation 语义同一个料号在不同页都可能相反,
|
||||
所以放完逐个 `getAllPins()` 读回、和 `EXPECT` 比对,对不上就翻 180 重试(最多 3 轮)。
|
||||
两脚件按**坐标集合**比(非极性件翻转只换脚号,无害)。
|
||||
"""
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
# ⚠ `.claude` 下有和 r2 同名的 R1 脚本(pcb_pour / pcb_rules / pcb_place)。
|
||||
# 只插父目录的话,**后面任何一个 `import pcb_rules` 都会拿到 R1 那一份**,
|
||||
# 而且不报错,只在调到 R2 专有的函数时才炸(2026-09-08 踩过)。
|
||||
# 规矩:两行都要写,HERE 后插(在 sys.path 最前)。
|
||||
sys.path.insert(0, os.path.dirname(HERE))
|
||||
sys.path.insert(0, HERE)
|
||||
from eda import run # noqa: E402
|
||||
|
||||
PAGE = '4f458d678c50f7a4' # BLADE / B2-CTRL
|
||||
PAGE_NAME = 'B2-CTRL'
|
||||
PARTS = json.load(io.open(os.path.join(os.path.dirname(HERE), 'eda-parts.json'),
|
||||
encoding='utf-8'))['parts']
|
||||
|
||||
PCA, MOS = 'C2688893', 'C8545'
|
||||
R4K7, R1K, R100K, C100N = 'C25900', 'C11702', 'C25741', 'C1525'
|
||||
|
||||
U4 = (420, 640) # PCA9555,rot 0:输出在左、输入与 I²C 在右
|
||||
PU = [(700, 'SDA'), (770, 'SCL')] # 两颗 4.7k 上拉的 x
|
||||
BX = [250, 550, 850] # 三路侧键 MOS 的中心 x
|
||||
KEYS = ['PWR', 'VUP', 'VDN']
|
||||
|
||||
# ── 引脚期望(相对中心的偏移,来自 R1 P3B-EXPAND / C4-SITE1 实测)──────────
|
||||
PCA_REL = dict([(str(i + 1), (-50, 55 - 10 * i)) for i in range(12)]
|
||||
+ [(str(13 + i), (50, -55 + 10 * i)) for i in range(12)])
|
||||
MOS_REL = {'1': (-20, 0), '2': (10, -20), '3': (10, 20)} # G / S / D
|
||||
|
||||
NC = {} # 本页没有有意不接的脚
|
||||
|
||||
ALLOW_SPLIT = set(['GND', '+3V3', 'SDA', 'SCL'] +
|
||||
['KEY_' + k for k in KEYS]) # 靠端口连起来的
|
||||
|
||||
|
||||
def call(js, timeout=180):
|
||||
r = run(js, timeout=timeout)
|
||||
if not r.get('success'):
|
||||
raise RuntimeError(json.dumps(r, ensure_ascii=False)[:900])
|
||||
return r['result']
|
||||
|
||||
|
||||
def plan():
|
||||
W, F, PT = [], [], []
|
||||
|
||||
def w(net, *pts):
|
||||
for a, b in zip(pts, pts[1:]):
|
||||
if a != b:
|
||||
W.append((a, b, net))
|
||||
|
||||
def gnd(x, y, rot):
|
||||
F.append(['Ground', 'GND', x, y, rot])
|
||||
|
||||
def pwr(x, y, rot):
|
||||
F.append(['Power', '+3V3', x, y, rot])
|
||||
|
||||
def port(d, net, x, y, rot):
|
||||
PT.append([d, net, x, y, rot])
|
||||
|
||||
# ── I²C 上拉(本槽 mux 分支各自一套;INA226 在 B1-POWER,也吃这两颗)──
|
||||
for x, net in PU:
|
||||
w(net, (x, 700), (x, 675))
|
||||
port('BI', net, x, 675, 0)
|
||||
w('+3V3', (x, 740), (x, 765))
|
||||
pwr(x, 765, 0)
|
||||
|
||||
# ── U4 PCA9555 ───────────────────────────────────────────────────────
|
||||
px, py = U4
|
||||
PL, PR = px - 50, px + 50
|
||||
|
||||
w('INT#', (PL, py + 55), (330, py + 55), (330, 745)) # 1 INT#
|
||||
port('OUT', 'INT#', 330, 745, 0)
|
||||
w('GND', (PL, py + 45), (300, py + 45)) # 2 A1
|
||||
w('GND', (PL, py + 35), (300, py + 35)) # 3 A2
|
||||
w('GND', (300, py + 35), (300, 725))
|
||||
gnd(300, 725, 180)
|
||||
OUTS = [('KEY_PWR', 25), ('KEY_VUP', 15), ('KEY_VDN', 5),
|
||||
('EDL_EN', -5), ('VBUS_EN', -15)] # IO0_0 ~ IO0_4
|
||||
for net, dy in OUTS:
|
||||
w(net, (PL, py + dy), (300, py + dy))
|
||||
port('OUT', net, 300, py + dy, 180)
|
||||
for dy in (-25, -35, -45, -55): # IO0_5~7 + VSS
|
||||
w('GND', (PL, py + dy), (320, py + dy))
|
||||
w('GND', (320, py - 25), (320, 555))
|
||||
gnd(320, 555, 0)
|
||||
|
||||
INS = [('EFUSE_FLT', -55), ('BUCK_PG', -45),
|
||||
('INA_ALERT', -35), ('EFUSE_PG', -25)] # IO1_0 ~ IO1_3
|
||||
for net, dy in INS:
|
||||
w(net, (PR, py + dy), (510, py + dy))
|
||||
port('IN', net, 510, py + dy, 180)
|
||||
for dy in (-15, -5, 5, 15, 25): # IO1_4~7 + A0
|
||||
w('GND', (PR, py + dy), (570, py + dy))
|
||||
w('GND', (570, py - 15), (570, 720))
|
||||
gnd(570, 720, 180)
|
||||
w('SCL', (PR, py + 35), (490, py + 35))
|
||||
port('BI', 'SCL', 490, py + 35, 0)
|
||||
w('SDA', (PR, py + 45), (490, py + 45))
|
||||
port('BI', 'SDA', 490, py + 45, 0)
|
||||
w('+3V3', (PR, py + 55), (520, py + 55)) # 24 VDD -> C40 下脚
|
||||
w('+3V3', (490, py + 55), (490, 730))
|
||||
pwr(490, 730, 0)
|
||||
w('GND', (520, 735), (520, 760))
|
||||
gnd(520, 760, 180)
|
||||
|
||||
# ── 侧键 MOS ×3(R1 §13.3:栅极 1k 串阻 + 100k 下拉,源极接地)────────
|
||||
for k, bx in enumerate(BX):
|
||||
g, kg = 'KEY_' + KEYS[k], 'KG_' + KEYS[k]
|
||||
w(kg, (bx - 20, 400), (bx - 60, 400)) # 栅极
|
||||
w(kg, (bx - 60, 420), (bx - 60, 400)) # 1k 下脚
|
||||
w(kg, (bx - 60, 380), (bx - 60, 400)) # 100k 上脚
|
||||
w(g, (bx - 60, 460), (bx - 60, 500)) # 1k 上脚 -> 端口
|
||||
port('IN', g, bx - 60, 500, 0)
|
||||
w('GND', (bx - 60, 340), (bx - 60, 315))
|
||||
gnd(bx - 60, 315, 0)
|
||||
w('GND', (bx + 10, 380), (bx + 10, 340)) # 源极
|
||||
gnd(bx + 10, 340, 0)
|
||||
w('KPAD_' + KEYS[k], (bx + 10, 420), (bx + 10, 465)) # 漏极 -> 中框焊盘
|
||||
port('OUT', 'KPAD_' + KEYS[k], bx + 10, 465, 0)
|
||||
|
||||
TX = [
|
||||
(45, 790, u'【B2-CTRL】刀片控制页:PCA9555 + 侧键 MOS ×3(DAC 已挪到底板)', 14),
|
||||
(45, 275, u'U4 PCA9555(A0/A1/A2 全接地 ⇒ 0x20):P0.0~P0.2 三个侧键 / P0.3 EDL_EN / '
|
||||
u'P0.4 VBUS_EN 为输出;P1.0~P1.3 收 EFUSE_FLT / BUCK_PG / INA_ALERT / EFUSE_PG。', 10),
|
||||
(45, 257, u'⚠ 备用脚 P0.5~P0.7 / P1.4~P1.7 直接接地:固件严禁把它们配成输出,'
|
||||
u'否则输出高电平直接对地短路(R1 §17.2 同款约束)。', 10),
|
||||
(45, 239, u'⚠ 复位安全(D9):PCA9555 上电为高阻输入 + 栅极 100k 下拉 ⇒ '
|
||||
u'三个侧键必然松开、VBUS 必然断开,不依赖任何软件动作。', 10),
|
||||
(45, 221, u'2026-09-08 改:DAC63204 挪到底板(2 颗喂 5 路,沿用 R1 的 U6/U7 与通道映射表)。'
|
||||
u'VDAC 从金手指进来,直接去 B1-POWER 的 R8/C12 低通 + Rinj。', 10),
|
||||
(45, 203, u'⚠ VDAC 跨连接器是安全的(设计文档 §4.4):断线 = Rinj 悬空 = 3.890V;对地短路 = 4.010V。'
|
||||
u'注入增益 0.2007,线上噪声与两板地差到输出衰减 5 倍。', 10),
|
||||
(45, 185, u'⚠ EDL_EN / VBUS_EN 的 100k 下拉在 B3-PHONE;INT# 的上拉在底板(五槽 wire-OR)。', 10),
|
||||
(45, 167, u'⚠ EFUSE_EN 不经 PCA9555,直连底板 ESP32(方案 §7.3)。', 10),
|
||||
(45, 149, u'4.7k I²C 上拉每片刀片自带;INA226 仍在 B1-POWER。', 10),
|
||||
]
|
||||
|
||||
# 文字越界自检(skill §12 的宽度经验值 + §8c 的标题栏范围)——
|
||||
# 这一条几何验证抓不到:文字压在标题栏上,六项检查全过,只有截图看得见。
|
||||
for x, y, s, size in TX:
|
||||
cjk = sum(1 for ch in s if ord(ch) > 0x2E7F)
|
||||
right = x + size * (cjk + 0.5 * (len(s) - cjk))
|
||||
limit = 455 if y < 192 else 1145
|
||||
assert right <= limit, u'文字右边界 %.0f 越过 %d(y=%d): %s' % (right, limit, y, s[:24])
|
||||
|
||||
# ── 下发前的几何断言(skill §3 / §6d / §8a-2b)────────────────────────
|
||||
for a, b, net in W:
|
||||
assert (a[0] == b[0]) != (a[1] == b[1]), u'%s 段不是横平竖直: %s->%s' % (net, a, b)
|
||||
for v in (a[0], a[1], b[0], b[1]):
|
||||
assert v % 5 == 0, u'%s 坐标 %d 不是 5 的倍数' % (net, v)
|
||||
for it in F + PT:
|
||||
assert it[2] % 5 == 0 and it[3] % 5 == 0, u'旗/端口 %s 坐标不是 5 的倍数' % (it[1],)
|
||||
assert 20 <= it[2] <= 1145 and 10 <= it[3] <= 812, u'旗/端口 %s 出框' % (it[1],)
|
||||
return W, F, PT, TX
|
||||
|
||||
|
||||
# ── 本地几何自检(skill §10b)─────────────────────────────────────────────
|
||||
def _on(p, a, b):
|
||||
if a[0] == b[0]:
|
||||
return p[0] == a[0] and min(a[1], b[1]) <= p[1] <= max(a[1], b[1])
|
||||
return p[1] == a[1] and min(a[0], b[0]) <= p[0] <= max(a[0], b[0])
|
||||
|
||||
|
||||
def netcheck(W, F, PT, pins):
|
||||
errs = []
|
||||
n = len(W)
|
||||
par = list(range(n))
|
||||
|
||||
def find(i):
|
||||
while par[i] != i:
|
||||
par[i] = par[par[i]]
|
||||
i = par[i]
|
||||
return i
|
||||
|
||||
for i in range(n):
|
||||
for j in range(i + 1, n):
|
||||
a1, a2, _ = W[i]
|
||||
b1, b2, _ = W[j]
|
||||
if _on(a1, b1, b2) or _on(a2, b1, b2) or _on(b1, a1, a2) or _on(b2, a1, a2):
|
||||
ri, rj = find(i), find(j)
|
||||
if ri != rj:
|
||||
par[ri] = rj
|
||||
grp = defaultdict(list)
|
||||
for i in range(n):
|
||||
grp[find(i)].append(i)
|
||||
pieces = defaultdict(int)
|
||||
for root, idx in grp.items():
|
||||
names = sorted(set(W[i][2] for i in idx))
|
||||
if len(names) > 1:
|
||||
errs.append(u'短路: ' + ' + '.join(names) + u' :: '
|
||||
+ ' | '.join('%s%s-%s' % (W[i][2], W[i][0], W[i][1]) for i in idx[:6]))
|
||||
pieces[names[0]] += 1
|
||||
for name, cnt in sorted(pieces.items()):
|
||||
if cnt > 1 and name not in ALLOW_SPLIT:
|
||||
errs.append(u'网络 %s 被拆成 %d 块(漏连)' % (name, cnt))
|
||||
|
||||
for d, pd in pins.items():
|
||||
for num, xy in pd.items():
|
||||
if num in NC.get(d, []):
|
||||
continue
|
||||
if not any(_on(tuple(xy), s[0], s[1]) for s in W):
|
||||
errs.append(u'悬空引脚: %s.%s @ %s' % (d, num, tuple(xy)))
|
||||
|
||||
# 旗/端口:必须落在导线**端点**,且不能是合并后母线的中段(skill §8a-2 / §8a-3)
|
||||
for kind, lst in ((u'旗', F), (u'端口', PT)):
|
||||
for it in lst:
|
||||
p = (it[2], it[3])
|
||||
through = any((s[0][0] == s[1][0] and p[0] == s[0][0]
|
||||
and min(s[0][1], s[1][1]) < p[1] < max(s[0][1], s[1][1]))
|
||||
or (s[0][1] == s[1][1] and p[1] == s[0][1]
|
||||
and min(s[0][0], s[1][0]) < p[0] < max(s[0][0], s[1][0]))
|
||||
for s in W)
|
||||
ends = [s for s in W if s[0] == p or s[1] == p]
|
||||
if through:
|
||||
errs.append(u'%s %s @ %s 压在导线中段' % (kind, it[1], p))
|
||||
elif not ends:
|
||||
errs.append(u'%s %s @ %s 不在任何导线端点上' % (kind, it[1], p))
|
||||
else:
|
||||
ax = set(('V' if s[0][0] == s[1][0] else 'H') for s in ends)
|
||||
if len(ends) > 1 and len(ax) == 1:
|
||||
errs.append(u'%s %s @ %s 两段共线,合并后会变成中段' % (kind, it[1], p))
|
||||
return errs
|
||||
|
||||
|
||||
def crossings(W):
|
||||
out = []
|
||||
for i in range(len(W)):
|
||||
for j in range(i + 1, len(W)):
|
||||
a1, a2, na = W[i]
|
||||
b1, b2, nb = W[j]
|
||||
if na == nb:
|
||||
continue
|
||||
ha, hb = a1[1] == a2[1], b1[1] == b2[1]
|
||||
if ha == hb:
|
||||
continue
|
||||
h, v = ((a1, a2), (b1, b2)) if ha else ((b1, b2), (a1, a2))
|
||||
y, x = h[0][1], v[0][0]
|
||||
if (min(h[0][0], h[1][0]) < x < max(h[0][0], h[1][0])
|
||||
and min(v[0][1], v[1][1]) < y < max(v[0][1], v[1][1])):
|
||||
out.append((na, nb, x, y))
|
||||
return out
|
||||
|
||||
|
||||
def placement():
|
||||
px, py = U4
|
||||
out = [['U4', PCA, px, py, 0], ['C40', C100N, 520, 715, 270],
|
||||
['R40', R4K7, PU[0][0], 720, 270], ['R41', R4K7, PU[1][0], 720, 270]]
|
||||
for k, bx in enumerate(BX):
|
||||
out += [['Q%d' % (k + 3), MOS, bx, 400, 0],
|
||||
['R%d' % (42 + 2 * k), R1K, bx - 60, 440, 270],
|
||||
['R%d' % (43 + 2 * k), R100K, bx - 60, 360, 270]]
|
||||
return out
|
||||
|
||||
|
||||
def expect(place):
|
||||
u"""期望引脚坐标。IC 逐脚;两脚件只比坐标集合(非极性件翻转只换脚号)。"""
|
||||
exp = {}
|
||||
for d, lcsc, x, y, rot in place:
|
||||
if lcsc == PCA:
|
||||
exp[d] = dict((k, (x + dx, y + dy)) for k, (dx, dy) in PCA_REL.items())
|
||||
elif lcsc == MOS:
|
||||
exp[d] = dict((k, (x + dx, y + dy)) for k, (dx, dy) in MOS_REL.items())
|
||||
else:
|
||||
exp[d] = {'?': set([(x, y + 20), (x, y - 20)])}
|
||||
return exp
|
||||
|
||||
|
||||
def main():
|
||||
push = '--push' in sys.argv
|
||||
place = placement()
|
||||
for it in place:
|
||||
assert it[1] in PARTS, u'eda-parts.json 里没有 %s' % it[1]
|
||||
exp = expect(place)
|
||||
|
||||
pins = {}
|
||||
for d, want in exp.items():
|
||||
if '?' in want:
|
||||
a, b = sorted(want['?'])
|
||||
pins[d] = {'1': a, '2': b}
|
||||
else:
|
||||
pins[d] = dict(want)
|
||||
|
||||
W, F, PT, TX = plan()
|
||||
errs = netcheck(W, F, PT, pins)
|
||||
print(u'元件 %d 导线 %d 旗 %d 端口 %d 文字 %d'
|
||||
% (len(place), len(W), len(F), len(PT), len(TX)))
|
||||
xs = crossings(W)
|
||||
print(u'交叉点(不相连,供人工确认): %d %s' % (len(xs), xs if xs else ''))
|
||||
if errs:
|
||||
print(u'本地自检 %d 条问题:' % len(errs))
|
||||
for e in errs:
|
||||
print(u' ! ' + e)
|
||||
raise SystemExit(1)
|
||||
print(u'本地自检通过(短路 0 / 漏连 0 / 悬空 0 / 旗端口全在端点)')
|
||||
if not push:
|
||||
return
|
||||
|
||||
n = call("""
|
||||
await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r=>setTimeout(r,1200));
|
||||
const nm=(await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name;
|
||||
if (nm!=='%s') throw new Error('page='+nm);
|
||||
let k=0;
|
||||
for (const c of await eda.sch_PrimitiveComponent.getAll()){
|
||||
if (c.getState_ComponentType()==='sheet') continue;
|
||||
await eda.sch_PrimitiveComponent.delete(c.getState_PrimitiveId()); k++;
|
||||
}
|
||||
for (const w of await eda.sch_PrimitiveWire.getAll()) await eda.sch_PrimitiveWire.delete(w.getState_PrimitiveId());
|
||||
for (const t of await eda.sch_PrimitiveText.getAll()) await eda.sch_PrimitiveText.delete(t.getState_PrimitiveId());
|
||||
return k;
|
||||
""" % (PAGE, PAGE_NAME))
|
||||
print(u'%s 已清空(删掉 %d 个图元)' % (PAGE_NAME, n))
|
||||
|
||||
put = [[d, PARTS[l]['uuid'], x, y, rot, PARTS[l]['lib']] for d, l, x, y, rot in place]
|
||||
for it in put:
|
||||
assert len(it) == 6 and it[5], u'placement 少了 libraryUuid: %s' % (it,) # skill §9c-2
|
||||
for i in range(0, len(put), 4):
|
||||
call("""
|
||||
for (const [d,uuid,x,y,rot,lib] of %s){
|
||||
await eda.sch_PrimitiveComponent.create({libraryUuid:lib, uuid}, x, y, undefined, rot, false, true, true);
|
||||
for (const c of await eda.sch_PrimitiveComponent.getAll()){
|
||||
if (c.getState_ComponentType()!=='part') continue;
|
||||
if (Math.round(c.getState_X())!==x || Math.round(c.getState_Y())!==y) continue;
|
||||
await c.toAsync().setState_Designator(d).done();
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
""" % json.dumps(put[i:i + 4]))
|
||||
print(u'已放置 %d 个元件' % len(put))
|
||||
|
||||
READ = """
|
||||
const out={};
|
||||
for (const c of await eda.sch_PrimitiveComponent.getAll()){
|
||||
if (c.getState_ComponentType()!=='part') continue;
|
||||
out[c.getState_Designator()] = (await c.getAllPins()).map(
|
||||
p=>[p.getState_PinNumber(), Math.round(p.getState_X()), Math.round(p.getState_Y())]);
|
||||
}
|
||||
return out;
|
||||
"""
|
||||
rots = dict((d, rot) for d, _l, _x, _y, rot in place)
|
||||
for rnd in range(4):
|
||||
got = call(READ)
|
||||
bad = []
|
||||
for d, want in exp.items():
|
||||
if d not in got:
|
||||
raise SystemExit(u'读不到元件 %s' % d)
|
||||
have = dict((p, (x, y)) for p, x, y in got[d])
|
||||
if '?' in want:
|
||||
if set(have.values()) != want['?']:
|
||||
bad.append((d, sorted(want['?']), sorted(have.values())))
|
||||
else:
|
||||
for p, xy in sorted(want.items()):
|
||||
if have.get(p) != xy:
|
||||
bad.append((d, p, xy, have.get(p)))
|
||||
break
|
||||
if not bad:
|
||||
print(u'引脚坐标逐个核对通过(%d 个元件)' % len(exp))
|
||||
break
|
||||
if rnd == 3:
|
||||
raise SystemExit(u'引脚坐标对不上:\n '
|
||||
+ '\n '.join(repr(b) for b in bad))
|
||||
names = sorted(set(b[0] for b in bad))
|
||||
print(u'第 %d 轮:翻转 %d 件重试 %s' % (rnd + 1, len(names), names))
|
||||
call("""
|
||||
for (const [d,rot] of %s){
|
||||
for (const c of await eda.sch_PrimitiveComponent.getAll()){
|
||||
if (c.getState_ComponentType()!=='part') continue;
|
||||
if (c.getState_Designator()!==d) continue;
|
||||
await c.toAsync().setState_Rotation(rot).done();
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
""" % json.dumps([[d, (rots[d] + 180 * (rnd + 1)) % 360] for d in names]))
|
||||
|
||||
def batch(items, size, js):
|
||||
for i in range(0, len(items), size):
|
||||
call(js % json.dumps(items[i:i + size], ensure_ascii=False))
|
||||
|
||||
batch([[a[0], a[1], b[0], b[1]] for a, b, _ in W], 40,
|
||||
"for (const s of %s) await eda.sch_PrimitiveWire.create(s); return 1;")
|
||||
batch(PT, 25, "for (const p of %s) await eda.sch_PrimitiveComponent.createNetPort(p[0],p[1],p[2],p[3],p[4],false); return 1;")
|
||||
batch(F, 25, "for (const f of %s) await eda.sch_PrimitiveComponent.createNetFlag(f[0],f[1],f[2],f[3],f[4],false); return 1;")
|
||||
batch([list(t) for t in TX], 12,
|
||||
"for (const t of %s) await eda.sch_PrimitiveText.create(t[0],t[1],t[2],0,null,null,t[3],false,false,false,0); return 1;")
|
||||
json.dump(NC, io.open(os.path.join(HERE, 'b2-nc.json'), 'w', encoding='utf-8'),
|
||||
ensure_ascii=False, indent=1)
|
||||
print(u'推送完成;接着跑 verify_sch.py --nets')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
在新工单中引用
屏蔽一个用户