文件
phonerail/.claude/c4_site.py
zhou yang和Claude Opus 5 efcba30a0c 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
2026-09-10 21:02:10 +08:00

421 行
23 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-8 -*-
u"""载板 C4-SITE1 ~ C4-SITE5:每台手机一页。
一页里三块:
【A】电池排线 4 焊盘直焊座 J8n(P+/P−/TEMP/ID,2026-09-06 取代 TP8n1/2)+ TEMP 10k / ID 68k + §6.3 电容组(六颗)
【B】四根侧键顶针(PWR / VUP / VDN + J604 三键公共地)+ 三颗按键 MOS(2N7002 + 1k 栅阻 + 100k 下拉)
—— 2026-09-06 随 74HC595 从下板 P2 搬来(§26.23),位号沿用 Q%d03~05 / R%d20~25
【C】EDL:1.25mm 2P 座 + TS5A3157 模拟开关 + 100k 下拉 + 去耦
用法: /c/Python314/python .claude/c4_site.py [--push] [--only N]
所有引脚坐标都是**读回来再算线**的(skill §6b:create 的 rotation 与
setState_Rotation 差 180 度,电容跨距也各不相同)。EXPECT 里断言不过就直接终止。
"""
import json
import time
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from eda import run
SCH = '302e115180aaa8fc' # phonerail-carrier
LIB = '0819f05c4eef4c71ace90d822a990e87'
LIB_POGO = '543725e8f0454017a5691e7e570aeef6' # 个人库,顶针自建封装版在这里
P = {
# 2026-09-06 换矮封装(用户拍板):红宝石 6.3TPV2200M10X10.5,C3828985,SMD D10×L10.5,
# 液态铝电解 ESR 60mΩ@100kHz(§6.2 要 40~100,非聚合物)。立创库里这颗器件挂的是直插封装,
# 所以在个人库里建了「6.3TPV2200M10X10.5 SMD」:符号复制自立创,封装 = CAP-SMD_BD10.0-L10.3-W10.3-LS11.2-FD_H10.5
# 的副本(焊盘 1 = +,渲染图核过)。旧件 C49256788(10×16 插件)留作历史。
'C2200U': '4bb2e55e90d9441eb935a4b4af5beeed', # C3828985 2200uF/6.3V SMD 10x10.5(个人库 LIB_POGO)
'C2200U_OLD': '1477290f70a341ce8e1943e3cd679351', # C49256788 2200uF/10V 插件(作废)
'C100U': 'a110bb2c7fd046cdb078d7f64c0f9f04', # C15008 100uF/6.3V 1206
'C22U': '0de9d28bbcd34ca3bf2a4d78faaa50bb', # C12891 22uF/25V 1206
'C100N': '87bb635d0b2f489a9f60e7cd225beb3c', # C1525 100nF 0402
'TPRED': '17b5ed105c5b4fe180eee3ffb4702963', # C9900007423 红色测试点(2026-09-06 起不用)
'TPBLK': 'cf8b789614c04ee491fd17b89f81a8ab', # C9900007426 黑色测试点(2026-09-06 起不用)
# 2026-09-06 用户拍板:电池排线在保护板焊点处剪断,4 个焊盘直焊载板(设计文档 §13.5 末尾)。
# 个人库自建器件 MI8-BAT-FPC-4P(fp_batfpc.py):1=P+ 2=P− 3=TEMP 4=ID;保护板上的 NTC(10k) / ID(68k) 用固定电阻复制。
'BATFPC': '22b0c3130f0b4ff6baab529761dab3e4', # MI8-BAT-FPC-4P(个人库 LIB_POGO)
'R10K': 'c3b9baa5ef2e4070a4c0f9e9cd04fe6e', # C25744 10k 0402 R78n TEMP
'R68K': '94441cf3b58f4f16af17200285ebaf08', # C36871 68k 0402 R79n ID
# ⚠ 2026-09-04 改用**个人库自建封装版**:立创原封装 SMD-1_BD2.0-H3.6 的焊盘是 Ø2.5,
# 而小米8 四个侧键触点最近的一对(J601↔J604)只隔 2.217 mm —— 两个焊盘会叠在一起短路。
# 顶针法兰本身只有 Ø2.0,所以是立创把焊盘开大了。自建封装 POGO-SMD1P_D1.9 缩到 Ø1.9
# (净间距 0.317 mm,过 0.2 mm 规则)。见 .claude/pcb2_place.py 文件头。
'POGO': '1c8e0ea1e66142a8860d6812b1dbe356', # YTC1P-2510-01 D1.9(个人库,LIB_POGO)
'POGO_LCSC': '43b0bd715d444e1fab4580cb95b3686d', # 作废:C5221288 原件(焊盘 Ø2.5)
'CONN2P': '6461e6e5749d421bb997627984866425', # C225104 A1251WV-S-2P
'SW': '5acafd8eb5a641fcaf05190902930635', # C353034 TS5A3157DBVR
'R100K': 'acc294b6661948399f609b41c77ed19f', # C25741 100k 0402
'NMOS': 'f8007837564a4f1fa0e6b57b81dccb0d', # C8545 2N7002(按键 MOS,从下板搬来)
'R1K': '65abbad5fa864933b552e0f016ece0db', # C11702 1k 0402(栅极串阻)
}
# ---- 版面常量 ----
Y_VO, Y_GND = 720, 540 # 【A】两条母线
Y_CAP = 630 # 电容中心
CAP_X = [250, 350, 450, 550, 650, 750]
X_RAIL0, X_RAIL1 = 90, 940 # 2026-09-06 母线西端 150→90:给 J8n 的四条竖走廊让地方
J8 = (225, 630) # 排线直焊座:符号 4 脚朝**左**(实测 pin 在 cx−20)、1 在最上,放在第一颗电容左边
# 四脚各自的竖走廊:1(P+) 往上接 VO 母线,走最远的 100(它的竖线在所有横线之上,不会被穿);
# 往下走的 2/3/4 按「越低的越靠近引脚」排:2→130 落 GND 母线,3→170 进 R78n,4→190 进 R79n。
COL = {'1': 100, '2': 130, '3': 170, '4': 190}
R78, R79 = (170, 575), (190, 575) # TEMP / ID 电阻(竖放),下端直接落在 GND 母线上
# 【B】三个按键列(PWR / VUP / VDN),每列:顶针在上、按键 MOS 在下、K 端口从最底下进来。
# 列内相对顶针中心 x_p:MOS (x_p-25, 360) → G(x_p-45,360) S(x_p-15,340) D(x_p-15,380);
# 栅阻 R%20/22/24 (x_p-45, 330) 竖放;下拉 R%21/23/25 (x_p-75, 330) 竖放;
# K 端口 (x_p-45, 250);POGO 端口挂在漏极竖线旁的横支线末端 (x_p+5, 410)(给 PCB 网络起名)。
# 列距 120:一列占 x_p-85 ~ x_p+85(POGO 标签),最东一列的标签 515 < 【C】那根 x=530 的竖线。
KEY_X = [190, 310, 430]
GND_POGO_X = 80 # 第四根顶针(J604 公共地)
Y_POGO = 440
J6, U4 = (660, 400), (880, 400) # 【C】
R74, C76 = (975, 370), (1060, 370)
CAPS = [('C2200U', u'2200µF/6.3V'), ('C100U', u'100µF'), ('C100U', u'100µF'),
('C22U', u'22µF'), ('C22U', u'22µF'), ('C100N', u'100nF')]
def call(js, timeout=180):
r = run(js, timeout=timeout)
if not r.get('success'):
raise RuntimeError(json.dumps(r, ensure_ascii=False)[:800])
return r['result']
def names(n):
base = 801 + (n - 1) * 10
return {
'caps': ['C%d' % (base + i) for i in range(6)],
'j8': 'J8%d' % n, 'r78': 'R78%d' % n, 'r79': 'R79%d' % n,
'pogo': ['J%d' % (41 + (n - 1) * 4 + i) for i in range(4)],
'j6': 'J6%d' % n, 'u4': 'U4%d' % n, 'r74': 'R74%d' % n, 'c76': 'C76%d' % n,
'q': ['Q%d0%d' % (n, k) for k in (3, 4, 5)], # PWR / VUP / VDN
'rg': ['R%d2%d' % (n, k) for k in (0, 2, 4)], # 1k 栅阻
'rp': ['R%d2%d' % (n, k) for k in (1, 3, 5)], # 100k 下拉
}
def placement(n):
d = names(n)
out = [[d['j8'], P['BATFPC'], J8[0], J8[1], 0, LIB_POGO],
[d['r78'], P['R10K'], R78[0], R78[1], 270, LIB],
[d['r79'], P['R68K'], R79[0], R79[1], 270, LIB]]
for i, (key, _v) in enumerate(CAPS):
# C80x1 是电解,pin1 = 正极,必须朝上接 VOn_P。
# 实测这颗 2200µF(C49256788)在 create() 里传 270 得到 pin1 在**下**,
# 和 skill §6 那张表相反 —— 所以传 90。下面 EXPECT 会再断言一次。
rot = 90 if key == 'C2200U' else 270
out.append([d['caps'][i], P[key], CAP_X[i], Y_CAP, rot, LIB_POGO if key == 'C2200U' else LIB])
for i, x in enumerate(KEY_X):
out.append([d['pogo'][i], P['POGO'], x, Y_POGO, 0, LIB_POGO])
out.append([d['q'][i], P['NMOS'], x - 25, 360, 0, LIB])
out.append([d['rg'][i], P['R1K'], x - 45, 330, 90, LIB])
out.append([d['rp'][i], P['R100K'], x - 75, 330, 90, LIB])
out.append([d['pogo'][3], P['POGO'], GND_POGO_X, Y_POGO, 0, LIB_POGO])
out += [[d['j6'], P['CONN2P'], J6[0], J6[1], 0, LIB],
[d['u4'], P['SW'], U4[0], U4[1], 0, LIB],
[d['r74'], P['R100K'], R74[0], R74[1], 270, LIB],
[d['c76'], P['C100N'], C76[0], C76[1], 270, LIB]]
# ⚠️ 每条必须是 6 项。少了 lib 那一项 -> `create({libraryUuid: undefined, uuid})`
# **永远不 resolve、也不抛错**,表现成「桥接 30 秒超时」,极难往这里想。
for it in out:
assert len(it) == 6 and it[5], u'placement 少了 libraryUuid: %s' % (it,)
return out
def wiring(n, pins):
u"""pins: {位号: {引脚号: (x, y)}},全部是读回来的真实坐标。"""
d = names(n)
W, F, PT, TX = [], [], [], []
def w(*p):
for i in range(0, len(p) - 2, 2):
W.append([p[i], p[i + 1], p[i + 2], p[i + 3]])
# ---------- 【A】P+/P- 与电容组 ----------
w(X_RAIL0, Y_VO, X_RAIL1, Y_VO)
PT.append(['BI', 'VO%d_P' % n, X_RAIL1, Y_VO, 0])
w(X_RAIL0, Y_GND, 900, Y_GND, 900, 510)
F.append(['Ground', 'GND', 900, 510, 0])
# 排线直焊座 J8n:4 脚朝左、1 在最上(build_page 里断言过)。每脚先横走到自己的竖走廊再上下落(COL 的注释)。
jp = pins[d['j8']]
for k in ('1', '2', '3', '4'):
px, py = jp[k]
assert px > max(COL.values()), u'%s.%s 引脚 x=%d 太靠左(走廊到 %d)' % (d['j8'], k, px, max(COL.values()))
w(px, py, COL[k], py)
w(COL['1'], jp['1'][1], COL['1'], Y_VO)
w(COL['2'], jp['2'][1], COL['2'], Y_GND)
for k, rn in (('3', d['r78']), ('4', d['r79'])):
rp = pins[rn]
rtop = max(rp.values(), key=lambda v: v[1])
rbot = min(rp.values(), key=lambda v: v[1])
assert rtop[0] == COL[k], u'%s 不在走廊 x=%d 上(实测 %s)' % (rn, COL[k], rtop)
w(COL[k], jp[k][1], COL[k], rtop[1])
w(rbot[0], rbot[1], rbot[0], Y_GND) # 直接落在 GND 母线上(T 型连接)
for i in range(6):
pm = pins[d['caps'][i]]
top = max(pm.values(), key=lambda v: v[1])
bot = min(pm.values(), key=lambda v: v[1])
if i == 0: # 电解:pin1 = 正极,必须是接 VOn_P 的那一头(换了符号 rot 语义可能变,读回来断言)
assert pm['1'][1] > pm['2'][1], u'%s 的 pin1(+) 不在上面: %s' % (d['caps'][0], pm)
w(top[0], top[1], top[0], Y_VO)
w(bot[0], bot[1], bot[0], Y_GND)
# ---------- 【B】侧键顶针 + 按键 MOS ----------
for i, key in enumerate(('PWR', 'VUP', 'VDN')):
px, py = pins[d['pogo'][i]]['1']
q = pins[d['q'][i]]
g, sr, dr = q['1'], q['2'], q['3'] # 2N7002:1=G 2=S 3=D(下面 build_page 里断言过)
assert px == dr[0] == sr[0] and dr[1] < py, (d['q'][i], q, px, py)
# 漏极 -> 顶针一根直线;POGO 端口挂在旁边一根 20 长的横支线末端(给 PCB 网络起名)。
# ⚠️ 不能把端口放在直线的中点:两段共线导线会被 EasyEDA 合并成一段,端口就落在中段 =
# 真断线(skill §8a-2,2026-09-06 第一版就是这么被 port_audit 抓到 15 处的)。
mid = dr[1] + 30
w(dr[0], dr[1], px, py)
w(dr[0], mid, dr[0] + 20, mid)
PT.append(['BI', 'POGO%d_%s' % (n, key), dr[0] + 20, mid, 0])
# 源极 -> 地
w(sr[0], sr[1], sr[0], 300)
F.append(['Ground', 'GND', sr[0], 300, 0])
# 栅极 <- 1k <- K 端口(从底下进来)
rg = pins[d['rg'][i]]
gt = max(rg.values(), key=lambda v: v[1])
gb = min(rg.values(), key=lambda v: v[1])
assert gt[0] == g[0] and gt[1] < g[1], (d['rg'][i], rg, g)
w(g[0], g[1], gt[0], gt[1])
w(gb[0], gb[1], gb[0], 250)
PT.append(['IN', 'K%d_%s' % (n, key), gb[0], 250, 180])
# 栅极 100k 下拉(复位期间 / 排针没插时不误按)
rp = pins[d['rp'][i]]
pt = max(rp.values(), key=lambda v: v[1])
pb = min(rp.values(), key=lambda v: v[1])
assert pt[0] < g[0] and pt[1] < g[1], (d['rp'][i], rp, g)
w(g[0], g[1], pt[0], g[1], pt[0], pt[1])
w(pb[0], pb[1], pb[0], 300)
F.append(['Ground', 'GND', pb[0], 300, 0])
px, py = pins[d['pogo'][3]]['1'] # J604 三键公共地
w(px, py, px, 400)
F.append(['Ground', 'GND', px, 400, 0])
# ---------- 【C】EDL ----------
j, u = pins[d['j6']], pins[d['u4']]
no, gnd, com = u['1'], u['2'], u['4']
inn, vp = u['6'], u['5']
s1, s2, t3, t4 = j['1'], j['2'], j['3'], j['4']
# 模拟开关的两个通路端分别在符号两侧,只能一上一下绕,两条走廊错开且互不交叉
w(no[0], no[1], no[0], 490, 530, 490, 530, s1[1], s1[0], s1[1])
w(com[0], com[1], com[0], 300, 560, 300, 560, s2[1], s2[0], s2[1])
w(gnd[0], gnd[1], 825, gnd[1], 825, 350)
F.append(['Ground', 'GND', 825, 350, 0])
# 1.25mm 座的两个固定脚接地(和 C3-USB 上的 J17 一致)
w(t3[0], t3[1], 600, t3[1], 600, 445)
F.append(['Ground', 'GND', 600, 445, 180])
w(t4[0], t4[1], 600, t4[1], 600, 355)
F.append(['Ground', 'GND', 600, 355, 0])
# V+ 往下走,避开 IN 那条横线
w(vp[0], vp[1], 945, vp[1], 945, 340)
F.append(['Power', '+3V3_HUB', 945, 340, 180])
# IN <- EDL_ENn,中途接 100k 下拉
w(inn[0], inn[1], 1000, inn[1])
PT.append(['BI', 'EDL_EN%d' % n, 1000, inn[1], 0])
rp = pins[d['r74']]
rtop = max(rp.values(), key=lambda v: v[1])
rbot = min(rp.values(), key=lambda v: v[1])
w(rtop[0], inn[1], rtop[0], rtop[1])
w(rbot[0], rbot[1], rbot[0], 320)
F.append(['Ground', 'GND', rbot[0], 320, 0])
cp = pins[d['c76']]
ctop = max(cp.values(), key=lambda v: v[1])
cbot = min(cp.values(), key=lambda v: v[1])
w(ctop[0], ctop[1], ctop[0], 420)
F.append(['Power', '+3V3_HUB', ctop[0], 420, 0])
w(cbot[0], cbot[1], cbot[0], 320)
F.append(['Ground', 'GND', cbot[0], 320, 0])
TX += [
(45, 806, u'【CH%d 手机位】P+/P− 焊盘 + 电容组 + 侧键顶针 + EDL' % n, 13),
(45, 788, u'【A】%s:原厂电池排线在保护板焊点处剪断,4 个焊盘直焊(1=P+ 2=P− 3=TEMP 4=ID,间距 2.0)。保护板整块不要(§13.5 末尾)。'
% names(n)['j8'], 10),
(45, 772, u' %s 10k 代替保护板上的 NTC(模拟 25 °C),%s 68k 照抄保护板上的 ID 电阻,都对 P−。'
% (names(n)['r78'], names(n)['r79']), 10),
(45, 522, u'【B】四根顶针顶主板反面的 J601~J604(§13.3),J604 是三键公共地,直接接本路的电源返回地。按键 = MOS 把触点对地短接(主板上已串 1k)。', 10),
(45, 508, u' %s 2N7002 由 C6-KEYS 的 74HC595 驱动(K%d_PWR/VUP/VDN),栅极 1k 串阻 + 100k 下拉:复位期间 / 排针没插时不会误按(2026-09-06 从下板搬来,§26.23)。'
% ('/'.join(names(n)['q']), n), 10),
(45, 494, u' 顶针 YTC1P-2510-01(C5221288):自由高 3.5 / 行程 0.8 / 压到底 2.7 mm;配 3.0mm 立柱压缩 0.5mm(62% 行程,约 62g),屏蔽罩净空 1.6mm。', 10),
(45, 480, u' 冠径只有 Ø0.9 球头,±1mm 落点公差靠首件实测校正(§22.3c / §22.3e)。', 10),
(45, 205, u'【C】%s 是 1.25mm 2P 座,两根线焊到主板正面的 TP1204 / TP1205(§22.4)。'
% names(n)['j6'], 10),
(45, 189, u' %s 导通即短接两点 → 手机进 9008。串了 1.8k,短路电流只有 1mA。'
% names(n)['u4'], 10),
(45, 173, u' %s 100k 下拉紧挨开关放:ESP32 复位期间 74HC595 输出高阻,'
% names(n)['r74'], 10),
(45, 157, u' 靠它保证手机不会被误拉进 9008(§8.1 复位安全)。', 10),
]
# 所有导线必须是水平或垂直、且非零长度 —— 斜线会被 create() 直接拒掉,
# 零长度会变成 skill §8a 那种「看着连上、重画就断」的隐形漏接。
# 另外:旗/端口的坐标会被吸附到 5 的倍数,导线端点不会。两边差 2 个单位,
# 旗就落在导线中段 = 真断线,而 verify_sch 和 DRC 都看不出来
# (C1-CONN 的 GND 旗写成 y=362 就是这么断的,只有 port_audit 抓得到)。
for a, b, c, e in W:
assert (a == c) != (b == e), u'CH%d 有一段不是横平竖直:%s' % (n, (a, b, c, e))
for v in (a, b, c, e):
assert v % 5 == 0, u'CH%d 导线坐标 %d 不是 5 的倍数:%s' % (n, v, (a, b, c, e))
for kind, lst in (('flag', F), ('port', PT)):
for it in lst:
assert it[2] % 5 == 0 and it[3] % 5 == 0, u'CH%d 的 %s %s 坐标 (%d,%d) 不是 5 的倍数' % (n, kind, it[1], it[2], it[3])
return W, F, PT, TX
def build_page(n, push):
d = names(n)
pname = 'C4-SITE%d' % n
if not push:
return
page = call("""
const info = await eda.dmt_Schematic.getSchematicInfo('%s');
for (const p of (info.page||[])) if (p.name==='%s') return p.uuid;
const u = await eda.dmt_Schematic.createSchematicPage('%s');
await eda.dmt_Schematic.modifySchematicPageName(u,'%s');
return u;
""" % (SCH, pname.lower(), SCH, pname))
call("""
await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r=>setTimeout(r,900));
const n=(await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name;
if (n!=='%s') throw new Error('page='+n);
for (const c of await eda.sch_PrimitiveComponent.getAll()){
if (c.getState_ComponentType()==='sheet') continue;
await eda.sch_PrimitiveComponent.delete(c.getState_PrimitiveId());
}
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 1;
""" % (page, pname))
place = placement(n)
# 分两段:创建走「甩出去 + 轮询」绕开桥接的 30 秒单次上限(16 个件逐个联网取
# 库时会撑爆),认领位号留在正常的 await 请求里(快,且要读回全部元件比对坐标)。
# ⚠️ 这个模式只对「慢」有效。2026-09-04 曾经卡在 done=0 不动,真因是
# `placement()` 里那两个测试点漏了 libraryUuid —— `create({libraryUuid: undefined})`
# **永远不 resolve 也不报错**,轮询看起来和卡死一样。见 skill §9c-2 与上面的断言。
call("""
globalThis.__pl = {done: 0, err: null, n: %d};
(async () => {
for (const [d, uuid, x, y, rot, lib] of %s) {
try { await eda.sch_PrimitiveComponent.create({libraryUuid: lib, uuid}, x, y, undefined, rot, false, true, true); }
catch (e) { globalThis.__pl.err = d + ': ' + String(e).slice(0, 150); }
globalThis.__pl.done++;
}
})();
return 'fired';
""" % (len(place), json.dumps(place)))
for _ in range(150):
st = call("return globalThis.__pl;")
if st['err']:
raise SystemExit(u'%s 放置失败: %s' % (pname, st['err']))
if st['done'] >= st['n']:
break
time.sleep(2)
else:
raise SystemExit(u'%s 放置超时,只完成 %d/%d' % (pname, st['done'], st['n']))
# 按坐标认领位号(本页每个件的 (x,y) 都是唯一的)
named = call("""
const P = %s;
let k = 0;
const cs = (await eda.sch_PrimitiveComponent.getAll()).filter(c => c.getState_ComponentType() === 'part');
for (const [d, uuid, x, y] of P) {
for (const c of cs) {
if (Math.round(c.getState_X()) !== x || Math.round(c.getState_Y()) !== y) continue;
await c.toAsync().setState_Designator(d).done();
k++; break;
}
}
return k;
""" % json.dumps([[d, u, x, y] for d, u, x, y, r, l in place]))
if named != len(place):
raise SystemExit(u'%s 只认领到 %d/%d 个位号' % (pname, named, len(place)))
raw = call("""
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(), (p.getState_PinName?p.getState_PinName():''),
Math.round(p.getState_X()), Math.round(p.getState_Y())]);
}
return out;
""")
pins = {k: {p: (x, y) for p, _n, x, y in v} for k, v in raw.items()}
pnames = {k: {p: _n for p, _n, x, y in v} for k, v in raw.items()}
bad = []
jp = pins[d['j8']]
if sorted(jp) != ['1', '2', '3', '4']:
bad.append(u'%s 引脚号 %s 不是 1..4' % (d['j8'], sorted(jp)))
elif not (jp['1'][1] > jp['2'][1] > jp['3'][1] > jp['4'][1]) or len({v[0] for v in jp.values()}) != 1 or jp['1'][0] >= J8[0]:
bad.append(u'%s 引脚要 4 脚朝左、1 在最上,实测 %s' % (d['j8'], jp))
# 电解电容 pin1 = 正极,必须在上(接 VOn_P)
c801 = pins[d['caps'][0]]
if c801['1'][1] <= c801['2'][1]:
bad.append(u'%s 是电解电容,pin1(正极) 必须在上,实测 %s' % (d['caps'][0], c801))
# 模拟开关引脚名要对上 NO/GND/NC/COM/V+/IN
want = {'1': 'NO', '2': 'GND', '3': 'NC', '4': 'COM', '5': 'V+', '6': 'IN'}
got = pnames[d['u4']]
for k, v in want.items():
if got.get(k, '').upper().replace('VCC', 'V+') != v:
bad.append(u'%s.%s 引脚名期望 %s 实测 %s' % (d['u4'], k, v, got.get(k)))
for qd in d['q']:
q = pins[qd]
if not (sorted(q) == ['1', '2', '3'] and q['1'][0] < q['2'][0] == q['3'][0] and q['2'][1] < q['1'][1] < q['3'][1]):
bad.append(u'%s 2N7002 引脚应是 1=G(左) 2=S(右下) 3=D(右上),实测 %s' % (qd, q))
if bad:
raise SystemExit(pname + u' 引脚核对失败:\n ' + '\n '.join(bad))
W, F, PT, TX = wiring(n, pins)
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(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], 15,
"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;")
print(u'%s: 元件 %d 导线 %d 旗 %d 端口 %d 文字 %d (%s)'
% (pname, len(place), len(W), len(F), len(PT), len(TX), page))
return page
def main():
push = '--push' in sys.argv
only = None
if '--only' in sys.argv:
only = int(sys.argv[sys.argv.index('--only') + 1])
sites = [only] if only else [1, 2, 3, 4, 5]
if not push:
for n in sites:
print(u'C4-SITE%d 元件 %d 个' % (n, len(placement(n))))
print(u'(未推送,加 --push)')
return
nc = {}
for n in sites:
build_page(n, True)
nc['U4%d' % n] = ['3'] # 模拟开关的 NC 端不用
ncf = os.path.join(HERE, 'c4-nets-nc.json')
old = json.load(open(ncf, encoding='utf-8')) if os.path.exists(ncf) else {}
old.update(nc)
json.dump(old, open(ncf, 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
if __name__ == '__main__':
main()