文件
phonerail/.claude/r2/a4_hub.py
T

755 行
43 KiB
Python
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-8 -*-
u"""R2 底板 **A4-HUB** 页:USB2517I 七口 Hub + Hub 专用 3.3V LDO。
/c/Python314/python .claude/r2/a4_hub.py # 只算 + 本地自检
/c/Python314/python .claude/r2/a4_hub.py --push # 清页 + 重画(幂等,只有主代理跑)
M3 的第四页。**主体是 R1 载板 `C2-HUB` 整页原样搬过来**(`OFF = (0,0)`,块内坐标一个字
不动,连 U32 / J23 / Y1 / 去耦阵列的绝对引脚坐标都照抄 `_c2hub_live.json`),
在此之上做三处改动:
| # | 改动 | 依据 |
|---|---|---|
| 1 | **新增 `U31` AMS1117-3.3 + `C752`/`C753`/`C737`** —— R1 里这颗不在 C2-HUB 页上 | 契约 §1.1 / §2 |
| 2 | **放出第 6 个下行口**:`USBDN6_DM/DP`(53/54) → `USB6_DM/DP` 双向端口去 A3 | 方案 §8.2(D13)|
| 3 | `HUB_RST` 的保命上拉保留并写进页面文字;`UP_VBUS_DET` 端口 `BI`→`OUT`、`HUB_RST` 的第二个端口 `BI`→`IN` | 契约 §3 第 1 条 / §1.2 |
---
## ⭐ T6:`CFG_SEL[2:0]=110` 下七个下行口是不是默认全开?—— **是,全开**
逐条对 `kicad/datasheets/ic/USB2517.pdf`(Microchip **DS00001598C**,USB2517/USB2517I,
59 页,sha256 记在同名 `.json` 里;和 `eda-parts.json` 指名要用的 SMSC 2517 Rev 2.8
是同一份内容的 Microchip 重排版,**不是** USB251x**B** 的 00001692C):
| 手册位置 | 原文 | 结论 |
|---|---|---|
| **Table 5-2**(p.15~16)`CFG_SEL[2:0]=110` | 「Internal Default Configuration • Strap Options are Disabled」 | strap **一个都不采样**,`PRT_DIS_*` 失效 |
| **Table 7-1**(p.20)`0Ah PDS` Port Disable (Self) | 默认 **00h** | ⇒ 七个口**全部可用** |
| **Table 7-1**(p.20)`0Bh PDB` Port Disable (Bus) | 默认 **00h** | 同上(本板自供电,看 0Ah)|
| **7.2.1.11**(p.26)`PORT_DIS_SP` 位定义 | 「'0' = port is available, '1' = port is disabled」 | 00h = 一个都没禁 |
| **Table 7-1**(p.20)`09h NRD` Non-Removable | 默认 **00h** | 全部可移除;`NON_REM[1:0]` 是 strap,同样不采样 |
| **Table 7-1**(p.20)`06h CFG1` | 默认 **9Bh**,bit7 `SELF_BUS_PWR`=1 | 自供电(只有 `010` 那一档强制总线供电,`110` 不改)|
⇒ **第 6 个口不需要改任何 strap,直接把 `USBDN6_DP/DM` 引出来接线即可**。
方案 §8.2 表第 6 条写的「若需要禁用多余口,改的是 strap 而不是电路」在这里反过来了:
本来就全开,要做的是**连线**,不是改 strap。
⭐ **2026-09-10 R2 底板改回 `CFG_SEL[2:0]=000`(strap 使能)—— 推翻 §21.6b,仅底板;R1 载板仍是 110。**
理由:底板上口 1~3 的 D± 北去,Hub 侧 D−/D+ 左右顺序与槽上相反,原来靠「U 形掉头」构造对调,
占掉 U32 北面最挤的一块。strap 使能后 `PRT_SWPx` 上拉 = 该口 D+/D− 在芯片内对调,直走即可。
R1 当年放弃 strap 是因为载板上 51/49/47 三颗电阻「走线 0 解」;底板 U32 转了 270°,这三只脚
外侧只有去耦和可改道的布线挡着(2026-09-10 实测),摆位阶段与去耦一起求解。
CFG_SEL2(13) —— 纯输入 → 由 +3V3_HUB 端口改 **GND 端口**
CFG_SEL1(42) —— 复用 HS_IND 输出 → R712 10k 由上拉改 **下拉**(换那面旗)
CFG_SEL0(41) —— R711 10k 下拉,不动 ⇒ 0/0/0 = 000
PRT_SWP1~3(51/49/47) —— R724~R726 100k **上拉** = 口 1~3 D+/D− 对调;
⚠ 本页 1/2、3/4、6/7 脚的端口网名**同时对调**(1 脚 → USB1_DP …),
否则片内翻了、网表没翻,极性反而错(`to_strap_mode()` / `SWAP_ROWS`)
PRT_SWP4~6(33/31/17) —— R727~R729 100k **下拉** 钉住(口 4/5 手机、口 6 ESP32;I/O12 无内部下拉,§21.6a)
PRT_SWP7 / BOOST / GANG_EN —— 采到 1 无害(口 7 空着),不装(§21.6a 同一筛法)
NON_REM[1:0] —— R719/R720 各 10k 下拉 = 00,R1 原样(strap 使能后才真正起作用)
另:**R723 1 MΩ 跨 XTAL1–XTAL2**(USB2517 Hardware Design Checklist p9:「A 1 MΩ resistor connected
across XTAL1 and XTAL2 is required. Failure to place this resistor will result in unstable crystal
operation.」;数据手册 p52 Figure 9-1 也画着 1Meg)—— R1 起就漏了。
⚠️ **口 7 仍悬空**(`USBDN7_DP/DM` 55/56 不接):strap 使能后 PRT_DIS 采的是 D± 上拉,
悬空时被 Hub 内部 15k 下拉拉低 ⇒ 口 7 开着、空闲,无害。R714/R715 不装。
## 第 6 个口要不要串阻 / ESD?——**不要**,和 R1 的五个口一样直连
R1 的口 1~5(`_c2hub_live.json` 实测)是 `U32` 引脚 → 一段短线 → `USBn_DP/DM` 端口,
**中间一颗元件都没有**;设计文档也写着「不要在 D± 上串电阻或加 ESD 之外的任何东西」,
而 ESD 在手机原厂尾插小板上。第 6 个口的对端是**同一块板上的 ESP32-S3 原生 USB**
(IO19=D−/IO20=D+),不过连接器、不出板、走线 ≈25mm(方案 §6.3 第 6 对),
比五个手机口的处境好得多 ⇒ 照做,直连。
## U31:5V → 3.3V,只喂 U32
Vin = +5V(A2 的 U30,2A 轨) Vout = +3V3_HUB(**只**给 U32)
Hub 满配电流 ≈ 0.42 A(设计文档 §9.4)
耗散 P = 0.42 × (5.0 − 3.3) = **0.714 W**,SOT-223 封装
→ PCB 上必须给 SOT-223 的散热片(2 脚 tab)铺一块铜:
θJA 靠铜面从 ~150 °C/W 压到 ~60 °C/W ⇒ ΔT ≈ 43 K,环境 40 °C 下结温 ~83 °C,
对 125 °C 的上限有余量。**这条是 PCB 的约束,原理图上只能记着。**
压差裕量:AMS1117 手册 p.1「dropout guaranteed maximum 1.3 V」(@0.8 A),
这里只有 0.42 A、压差 1.7 V ⇒ 够。
输出电容 **必须是固态钽** `C737` T491A226K010AT 22µF/10V(`C116575`)——
AMS1117 手册 §Stability 原文:「The addition of **22µF solid tantalum** on the output
will ensure stability for all operating conditions.」**陶瓷不算数**(ESR 太低会让环路失稳)。
⚠️ 这颗**1 脚是正极**(钽的条纹侧),本脚本用显式引脚号断言 `1` 在上、`2` 在下,
放件时若方向反了 `Sheet.place()` 会自己翻 180 重试(skill §6b/§6c)。
输入侧 `C752` 10µF/50V 0805(`C440198`)+ `C753` 100nF 0402(`C1525`),都在 `+5V` 干线上。
⚠️ **契约 §3 第 3 条:`+3V3_HUB` 与 `+3V3` 是两条轨。** 本页从头到尾没有 `+3V3`,
也没有 `+12V`;两条轨只在 GND 上相遇(`build()` 里有断言)。
## HUB_RST 的保命处理(契约 §3 第 1 条,硬约束)
`R716` 10k 把 `RESET_N`(43) 上拉到 `+3V3_HUB`(R1 已有,本页原样保留、不许删,
`main()` 里逐脚断言它还在原位)。ESP32 复位 / 高阻 / 未烧固件时 `HUB_RST` 被这颗电阻
拉高 = **Hub 保持工作**。理由写进页面文字:D13 之后 ESP32 的控制台与烧录口就挂在这颗
Hub 后面,一旦「不驱动 = 复位」,误复位就等于自断控制台,而且下一次烧录也进不来
(退路是 A3 的 `J33` 5P UART 应急口,但那是退路,不是设计依据)。
## 几何约定(skill)
Y 轴向上(§1)、坐标全部 5 的倍数(§3/§8a-2b)、旗端口只落在导线端点(§8a/§8a-2)、
端口方向决定标签在左还是在右(§8b:`IN` 与 `BI`/`OUT` 相反)、
netport 符号只有 ~10 高,10 单位间距排一列不打架(§8b-2)、
标题栏 `x>455 且 y<192`(§8c)、文字宽度 ≈ 字号×(CJK + 0.5×ASCII)(§12)。
"""
import io
import json
import os
import sys
from collections import defaultdict
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from schcheck import Sheet, _on, call, two_pin, PARTS # noqa: E402
PAGE, NAME = '09d583ff95cd35d9', 'A4-HUB'
R2_PROJECT = '05a65c22f14846b8b95360ef8d3c1e2e'
SRC = os.path.join(HERE, '_c2hub_live.json') # R1 载板 C2-HUB 实测 dump
OFF = (0, 0) # 整页原样搬,一个字不动
# ── ⚠ 唯一一处必须挪的 R1 图元:VBUS_UP 分压那一小块 ────────────────────
# R1 载板 C2-HUB 上它坐在 y 45~135 / x 780~1060,搬到本页正好**落进 A4 标题栏**
# (skill §8c:标题栏是 x>455 且 y<192,比想象的大)—— 图上 R721 / R722 /
# UP_VBUS_DET 端口直接压在「PhoneRail-R2」和日期格上。
# 六项几何验证不看这个(它只管连接性),`text_clash` 也只查文字,
# **是靠人眼截图发现的**。整块平移 +380,落到 x 730~1070 / y 425~515 那条空带
# (上边是 U32 左列端口的 y≤465 @ x=760、下边是 +3V3_HUB 端口 y=525,都不撞)。
SHIFT_RECT = (770, 40, 1080, 140) # 源页坐标里的这一块
# ⚠ 2026-09-10 再右移 40(原 (0,380)):改动 4 在 U32 右列 47/49/51 行(y 475/495/515)加了 strap 电阻,
# 原位置的 `VBUS_UP` 端口 (800,500) 标签往左伸到 x≈725,正压在 49 脚那行的 R725 上(截图发现,
# 几何验证 / text_clash 都够不着)。右移后标签到 765 为止,R725 那一行的铜到 750 为止。
SHIFT_D = (40, 380)
def _in_rect(x, y, r=SHIFT_RECT):
return r[0] <= x <= r[2] and r[1] <= y <= r[3]
def shift_block(W, F, PT, place, exp):
u"""把 SHIFT_RECT 里的图元整块平移 SHIFT_D。跨界的导线一律当错误。"""
dx, dy = SHIFT_D
W2 = []
n = 0
for a, b, net in W:
ia, ib = _in_rect(*a), _in_rect(*b)
assert ia == ib, u'导线 %s %s-%s 跨过 SHIFT_RECT 边界,不能整块平移' % (net, a, b)
if ia:
W2.append(((a[0] + dx, a[1] + dy), (b[0] + dx, b[1] + dy), net))
n += 1
else:
W2.append((a, b, net))
for lst in (F, PT):
for it in lst:
if _in_rect(it[2], it[3]):
it[2] += dx
it[3] += dy
n += 1
moved = []
for it in place:
if _in_rect(it[2], it[3]):
it[2] += dx
it[3] += dy
moved.append(it[0])
n += 1
for d in moved:
exp[d] = dict((k, (v[0] + dx, v[1] + dy)) for k, v in exp[d].items())
assert sorted(moved) == ['R721', 'R722'], u'平移到的元件不是 R721/R722 而是 %s' % (moved,)
assert n, u'SHIFT_RECT 一个图元都没框到 —— 源页坐标变了?'
return W2
# ── 改动 3:端口方向按契约 §1.2 校正(键 = (网名, x, y) → 新 dir)───────────
# R1 这两个是随手写的 BI;契约里 `UP_VBUS_DET` 是 A4 发给 A3、`HUB_RST` 是 A3 发给 A4,
# 主代理要拿端口清单对账,方向必须对得上。**只改 dir,坐标一个字不动**。
# ⚠ 顺带:`IN` 与 `BI`/`OUT` 的标签方向正好相反(skill §8b),所以改 dir 会让标签换边 ——
# (1130,340) 是 rot=180,`BI` 时标签在左、`IN` 时在右,右边到 1145 只剩 15,
# 于是这一个连 rot 一起翻成 0(`PORT_ROT`),标签仍旧落在左边。
PORT_DIR = {
('UP_VBUS_DET', 930, 60): 'OUT', # A4 → A3(电脑在不在)
('HUB_RST', 1130, 340): 'IN', # A3 → A4;(760,435) 那个 R1 本来就是 IN
}
PORT_ROT = {('HUB_RST', 1130, 340): 0}
# ── 改动 2:第 6 个下行口(D13)──────────────────────────────────────────
# U32 右列 53=USBDN6_DM(680,535) / 54=USBDN6_DP(680,545)。
# 右侧端口列在 x=760,R1 已占 405/415/425/455/465/525/575/585/595/605/615/625/635/645/655,
# **535 / 545 正好是空的**,插进去即可,间距仍是 10(skill §8b-2)。
USB6 = [('USB6_DM', 535), ('USB6_DP', 545)]
# ── 改动 1:U31 AMS1117-3.3 块(新画)───────────────────────────────────
# 落在 R1 版图留下的空带 x 300~900 / y 190~330(`assert_clear()` 每次重算校验)。
# 引脚(契约 §4 实测,rot 0,相对中心):1 GND(-45,+10) 2 VOUT(-45,0) 3 VIN(-45,-10) 4 VOUT(+45,0)
# ⚠ GND 夹在 VIN 与 VOUT 中间、且在 VIN **上方**,所以「+5V 从左边平进、GND 往左再往下」
# 是唯一不产生交叉的走法:GND 的下行点必须**在 +5V 端口符号左边**
# (+5V 引脚 x=440,IN+rot0 的符号往左伸到 380;GND 下行点 x=350),差 30 净空。
U31_C = (700, 265)
U31_PINS = {'1': (655, 275), '2': (655, 265), '3': (655, 255), '4': (745, 265)}
C752_C, C753_C = (480, 235), (520, 235) # 输入 10µF/50V 0805 + 100nF 0402,跨距 ±20
# ⚠ 2026-09-08 主代理实测更正(原来写 (810,250) / rot270 / ±15,推送时 place() 四轮翻转全对不上):
# T491A226K010AT(C116575)**空页四个角度逐一放件读回**的结果是
# rot 0 → pin1 (0,+20) rot 90 → pin1 (-20,0) rot180 → pin1 (0,-20) rot270 → pin1 (+20,0)
# ⇒ 正极(1 脚)朝上要的是 **rot 0**,跨距是 **±20 不是 ±15**。
# `p1b-pins.json` 里那个 ±15 是别的封装的电容,skill §8d:引脚跨距因符号而异,必须读回。
C737_C = (810, 245) # 输出 22µF/10V 固态钽:rot0、±20 ⇒ 1脚(正)在 265、2脚在 225
NEW_PARTS = [
['U31', 'C6186', U31_C[0], U31_C[1], 0],
['C752', 'C440198', C752_C[0], C752_C[1], 270],
['C753', 'C1525', C753_C[0], C753_C[1], 270],
['C737', 'C116575', C737_C[0], C737_C[1], 0],
]
BLOCK = (300, 190, 900, 330) # 新块要占的净空矩形(含端口标签)
# ── 改动 4(2026-09-10,R2 底板):CFG_SEL 改回 000 + PRT_SWP + 晶振 1MΩ(理由见文件头)─────
# U32 引脚在本页的坐标:左列 x=320(33@340 / 31@360 / 17@500),右列 x=680(47@475 / 49@495 / 51@515)。
# 这六行原来都是悬空脚,两侧端口列(x=170 / x=760)对应的行也都空着(2026-09-10 从 `_c2hub_live.json` 查过)。
# 电阻横放 rot 0:1 脚 (x−20)、2 脚 (x+20)(同本页 R708 实测)。
STRAP_RIGHT = [('R724', 515, '51'), ('R725', 495, '49'), ('R726', 475, '47')] # 上拉 → +3V3_HUB 端口
STRAP_LEFT = [('R727', 340, '33'), ('R728', 360, '31'), ('R729', 500, '17')] # 下拉 → GND 端口
# 口 1~3 的 D± 在本页左列(x=320 脚 → x=170 端口,一脚一段直线,2026-09-10 查过):
# y 660 = 1 脚 USBDN1_DM、650 = 2 脚 DN1_DP、640 = 3 脚 DN2_DM、630 = 4 脚 DN2_DP、610 = 6 脚 DN3_DM、600 = 7 脚 DN3_DP
# PRT_SWP1~3 上拉之后这六只脚的 DP/DM 片内对调 ⇒ 端口网名跟着对调:{y: (原网名, 新网名)}
SWAP_ROWS = [(660, ('USB1_DM', 'USB1_DP')), (650, ('USB1_DP', 'USB1_DM')),
(640, ('USB2_DM', 'USB2_DP')), (630, ('USB2_DP', 'USB2_DM')),
(610, ('USB3_DM', 'USB3_DP')), (600, ('USB3_DP', 'USB3_DM'))]
R723_C = (1000, 800) # 1MΩ:Y1 正上方的空行。B 说明块最上一行 y=780(文字盒到 789),端口标签 794~806 不压字
STRAP_PARTS = ([[d, 'C25741', 720, y, 0] for d, y, _p in STRAP_RIGHT] +
[[d, 'C25741', 250, y, 0] for d, y, _p in STRAP_LEFT] +
[['R723', 'C26083', R723_C[0], R723_C[1], 0]])
def hpin(x, y, half=20):
u"""横放(rot 0)两脚件的期望:只比坐标集合(`schcheck.two_pin` 只管竖放)。"""
return {'?': set([(x - half, y), (x + half, y)])}
def to_strap_mode(W, F, PT):
u"""源页是 R1 的 110 接法 → 改成 000:13 脚端口改名 GND、R712 的上拉旗换地旗。**只改名字,坐标不动**。"""
n13 = [it for it in PT if (it[1], it[2], it[3]) == ('+3V3_HUB', 170, 540)]
assert len(n13) == 1, u'13 脚那个 +3V3_HUB 端口没找到(源页坐标变了?)'
n13[0][1] = 'GND'
f712 = [it for it in F if (it[0], it[1], it[2], it[3]) == ('Power', '+3V3_HUB', 970, 305)]
assert len(f712) == 1, u'R712 上拉那面 +3V3_HUB 旗没找到'
f712[0][:] = ['Ground', 'GND', 970, 305, 0] # Ground rot0:符号在下、引脚在上,导线从上方来(同 R711 那面)
W2, hit = [], 0
for a, b, net in W:
if set([a, b]) in (set([(170, 540), (320, 540)]), set([(970, 305), (970, 360)])):
assert net == '+3V3_HUB', u'%s-%s 原来不是 +3V3_HUB 而是 %s' % (a, b, net)
W2.append((a, b, 'GND'))
hit += 1
else:
W2.append((a, b, net))
assert hit == 2, u'要改名的两段导线只找到 %d 段(13 脚那段 + R712 那段)' % hit
# ⭐ 口 1~3 的 Hub 侧 DP/DM **网名对调**:PRT_SWPx=1 之后 USBDNx_DM 脚输出的是 D+。
# 不对调的话 1 脚还连着槽上的 D− —— 芯片里翻了一次、网表又原样,极性反而错。
# 对调后网表就是「Hub 西脚 ↔ 槽西针(D+)」,底板上直走、不交叉(这正是删 U 形掉头的前提)。
swap = dict(SWAP_ROWS)
got = 0
for it in PT:
if it[2] == 170 and it[3] in swap:
assert it[1] == swap[it[3]][0], u'y=%d 端口原来是 %s 不是 %s' % (it[3], it[1], swap[it[3]][0])
it[1] = swap[it[3]][1]
got += 1
assert got == len(swap), u'要对调的 D± 端口只找到 %d / %d 个' % (got, len(swap))
W3, got = [], 0
for a, b, net in W2:
y = a[1]
if a[1] == b[1] and y in swap and set([a[0], b[0]]) == set([170, 320]):
assert net == swap[y][0], u'y=%d 导线原来是 %s 不是 %s' % (y, net, swap[y][0])
W3.append((a, b, swap[y][1]))
got += 1
else:
W3.append((a, b, net))
assert got == len(swap), u'要对调的 D± 导线只找到 %d / %d 段' % (got, len(swap))
return W3
def add_straps(s):
u"""六颗 strap 电阻 + R723。脚 → 电阻 → 同名端口,同本页「每个芯片脚一段短线 + 一个端口」的画法。"""
for _d, y, pin in STRAP_RIGHT:
s.w('#SWP' + pin, (680, y), (700, y))
# 51 / 47 两行各一个 +3V3_HUB 端口;49 那一行(y=495)**不放端口**:平移过来的 VBUS 分压块的
# `VBUS_UP` 标签就在 y=500,放了必压。R725 右端用一段竖线接到 47 行那根线上 ——
# 47 行的线在 x=750 断开成两段,三根线在 (750,475) **端点**相交,不做中段 T 接。
s.w('+3V3_HUB', (740, 515), (760, 515))
s.port('BI', '+3V3_HUB', 760, 515, 0) # BI + rot0 ⇒ 符号在右,同这一列其他端口
s.w('+3V3_HUB', (740, 475), (750, 475), (760, 475))
s.port('BI', '+3V3_HUB', 760, 475, 0)
s.w('+3V3_HUB', (740, 495), (750, 495), (750, 475))
for _d, y, pin in STRAP_LEFT:
s.w('#SWP' + pin, (320, y), (270, y))
# 33 / 17 两行各一个 GND 端口;31 那一行(y=360)**不放端口**:R709/R710 分压的 `VBUS_DET`
# 输出端口 (90,360) 标签往右伸到 x≈170,放了必压。改成往上引 25 挂一面地旗
# (Ground rot180 = 符号在上、引脚在下,导线从下方来,skill §2);上方 y 365~415 只有 U32 的悬空脚。
for y in (340, 500):
s.w('GND', (230, y), (170, y))
s.port('BI', 'GND', 170, y, 180) # BI + rot180 ⇒ 符号在左,同这一列其他端口
s.w('GND', (230, 360), (230, 385))
s.gnd(230, 385, 180)
x, y = R723_C
s.w('XTAL1', (950, y), (x - 20, y))
s.port('BI', 'XTAL1', 950, y, 180)
s.w('XTAL2', (x + 20, y), (1050, y))
s.port('BI', 'XTAL2', 1050, y, 0)
# ── U32 上故意不接的脚(设计文档 §21.6 / §21.6b)+ J23 的 SBU ──────────────
# 53/54 已被改动 2 接走,所以**不在**这张表里。
NC = {
# 17/31/33/47/49/51 是 PRT_SWP6/5/4/3/2/1,2026-09-10 起接 strap 电阻(改动 4),不在表里
'U32': ['14', '15', '16', '18', '20', '21', '22', '23', '26', '27', '28',
'29', '30', '32', '34', '35', '36', '37', '38', '39',
'48', '50', '55', '56'],
'J23': ['A8', 'B8'], # SBU1/SBU2,USB2.0 用不上
}
# ── 靠同名旗/端口成网、导线上天然分块的网络(契约 §7 第 10 条)──────────────
# R1 这一页的画法就是「每个芯片脚一段短线 + 一个同名 netport」,所以页内网络天然多块。
# 五个下行口 / USB6 / UP_VBUS_DET / +5V **不在**表里 —— 它们只许有一块。
ALLOW_SPLIT = set([
'GND', '+3V3_HUB',
'VDD18', 'VDD18PLL', 'XTAL1', 'XTAL2', 'RBIAS',
'CFGSEL0', 'CFGSEL1', 'NONREM0', 'NONREM1',
'UP_DP', 'UP_DM', 'VBUS_UP', 'VBUS_DET', 'HUB_RST',
])
# ⚠ 可用的文字区只有三块(R1 版图 + 标题栏 x>455 且 y<192 挤剩下的,`text_clash`
# / `label_clash` 每次重算):
# A 标题 (45, 800)
# B 说明块 x 345~800 / y 690~780 —— R1 原来放说明的位置
# C U31 说明块 x 45~355 / y 195~260 —— x=60 的地线在 y 270~300,别越过 260
# D HUB_RST x 900~1145 / y 204~240 —— 就在 R716 底下;x=1130 的线从 y=250 起
# 左下 y<190 那一片**不能用**:J23 的本体和 12 根引脚就在 x 155~360 / y 45~180。
# y<665 而 x 345~680 那一片也不能用:U32 的符号本体在那里(引脚只到 y=660,
# 本体还要往上一点),所以 B 块最低只能到 y=690(R1 原来也是到这儿为止)。
TEXTS = [
(45, 800, u'【A4-HUB】板载 USB 2.0 七口 Hub + Hub 专用 3.3V —— 对电脑只有一根线(D13)', 14),
# B:Hub 配置 / T6 / 第 6 口 / HUB_RST
(345, 780, u'U32 USB2517I:CFG_SEL[2:0]=000 = 内部默认 + strap 使能(2026-09-10 底板改回)', 11),
(345, 762, u'CFG_SEL2(13) 接 GND;R711/R712 10k 下拉;NON_REM=00(R719/R720 下拉)', 11),
(345, 744, u'PRT_SWP1~3 经 R724~R726 上拉 = 口 1~3 片内对调;1~7 脚端口 DP/DM 名随之对调', 11),
(345, 726, u'PRT_SWP4~6 经 R727~R729 100k 下拉钉住;口 1~5 → A5 五槽,口 6 → ESP32', 11),
(345, 708, u'R723 1MΩ 跨 XTAL1–XTAL2:Microchip 检查清单写 required,不装晶振不稳', 11),
(345, 690, u'HUB_RST 固件只在启动自检脉冲一次;误复位 = 自断控制台 + 下次烧不进去', 11),
# C:U31 说明(就在 U31 块左边)
# ⚠ 2026-09-08 主代理改:原来这四行在 x=45,**被 R1 带来的地旗 (60,270) 压穿**
# —— `Ground rot=0` 的符号带 GND 标签往锚点**下方**伸到 y≈240,正好落在第 1/2 行上
# (渲染成「U32 满配」上盖一个 GND)。同 A2-AUX 的两处叠字,六项几何验证与
# `text_clash`(只查文字之间)都够不着,靠人眼截图发现。⇒ 整块挪到 x=100 / y 246~300、字号 10→9。
# x=110 那一版会被 x=350 的地竖线(y 195~275)划穿 —— 脚本自带的 `text_clash`
# 当场报了出来;x=200 那一版又压上 U32 左排引脚(y 330~660 一直伸到 x=320)。
# ⚠ 注意 `flag_clash` **不建模旗的标签占地**:`Ground rot=0` 的符号带 GND 字
# 从锚点往下伸约 38 个单位(本页实测 (60,270) 的旗压到 y≈240)。把这个盒子
# 连同 U32 的引脚包围盒 (310,330)-(690,670)、其余文字块一起当障碍全页扫一遍,
# **字号 10 的四行一个位置都放不下**,字号 9 才有 —— 这就是它比别的块小一号的原因。
(100, 300, u'【U31】+5V → +3V3_HUB 专用 LDO(AMS1117-3.3)', 9),
(100, 282, u'U32 满配约 0.42A,挂不进 U19 那条 0.5A 的 +3V3 轨', 9),
(100, 264, u'耗散 0.42A×1.7V=0.71W @SOT-223,tab 必须铺铜', 9),
(100, 246, u'C737 必须 22µF 固态钽(手册 Stability),陶瓷不算数', 9),
# D:HUB_RST 的保命上拉(就在 R716 底下)
(900, 240, u'⚠ R716 10k 是 HUB_RST 的保命上拉:', 10),
(900, 222, u'ESP32 复位 / 高阻时 Hub 保持工作 ——', 10),
(900, 204, u'控制台与烧录口就在它后面(D13)', 10),
]
# ══════════════════════════════════════════════════════════════════════════
def load(path, off):
u"""整页读回并平移。返回 (导线[(a,b,net)], 旗, 端口, place, expect, 悬空引脚数)。
与 `a2_aux.load()` 的两点不同:
1. 本页**整页搬**,没有 keep 过滤(U32 一颗芯片就把全页导线连成一片,切不开);
2. **允许悬空引脚** —— U32 有 30 个脚是故意不接的(见 NC),
`a2_aux` 那句 `assert hit` 在这里会当场炸掉。改成计数,由 `build()` 和 NC 对账。
"""
src = json.load(io.open(path, encoding='utf-8'))
dx, dy = off
pts = src['wires']
segs = [((int(round(pts[i][0])) + dx, int(round(pts[i][1])) + dy),
(int(round(pts[i + 1][0])) + dx, int(round(pts[i + 1][1])) + dy))
for i in range(0, len(pts), 2)]
for a, b in segs:
assert a != b, u'%s 有零长度导线 %s(skill §8a)' % (path, (a,))
assert (a[0] == b[0]) != (a[1] == b[1]), u'%s 有斜线 %s-%s' % (path, a, b)
n = len(segs)
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 = segs[i]
b1, b2 = segs[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
gname, gpins = defaultdict(set), defaultdict(set)
for lst in (src['flags'], src['ports']):
for f in lst:
xy = (int(round(f[2])) + dx, int(round(f[3])) + dy)
hit = [i for i in range(n) if _on(xy, segs[i][0], segs[i][1])]
assert hit, u'%s 的旗/端口 %s 不在任何导线上' % (path, f[1])
gname[find(hit[0])].add(f[1])
nfloat = 0
for p in src['parts']:
for q in p[9]:
xy = (int(round(q[2])) + dx, int(round(q[3])) + dy)
hit = [i for i in range(n) if _on(xy, segs[i][0], segs[i][1])]
if not hit: # 故意不接的脚,由 NC 兜底核对
nfloat += 1
continue
gpins[find(hit[0])].add('%s.%s' % (p[0], q[0]))
W, F, PT = [], [], []
for r_ in sorted(set(find(i) for i in range(n))):
names = gname.get(r_, set())
assert len(names) <= 1, u'组 %s 名字不唯一:%s' % (sorted(gpins[r_])[:3], sorted(names))
# 无名的短节点(J23 的两个 CC)沿用 R1 不起名,`#` 开头只给本地自检用、不下发
net = sorted(names)[0] if names else '#' + '+'.join(sorted(gpins[r_])[:2])
for i in range(n):
if find(i) == r_:
W.append((segs[i][0], segs[i][1], net))
for f in src['flags']:
xy = (int(round(f[2])) + dx, int(round(f[3])) + dy)
F.append([f[0].split('-')[0], f[1], xy[0], xy[1], f[4]])
hit_dir = 0
for p in src['ports']:
xy = (int(round(p[2])) + dx, int(round(p[3])) + dy)
d, rot = p[0].replace('Netport-', ''), p[4]
key = (p[1], xy[0], xy[1])
if key in PORT_DIR:
d = PORT_DIR[key]
rot = PORT_ROT.get(key, rot)
hit_dir += 1
PT.append([d, p[1], xy[0], xy[1], rot])
assert hit_dir == len(PORT_DIR), \
u'端口方向修正表只命中 %d/%d(坐标写错了)' % (hit_dir, len(PORT_DIR))
byname = {}
for k, v in PARTS.items():
byname.setdefault(v['name'], k)
place, exp = [], {}
for p in src['parts']:
d, nm = p[0], p[1]
base = nm.split('_C')[0] if '_C' in nm else nm
k = byname.get(nm) or byname.get(base)
assert k, u'eda-parts.json 里找不到器件 %r(%s)' % (nm, d)
assert not p[6], u'%s 是镜像件,Sheet.place 处理不了' % d
place.append([d, k, int(round(p[3])) + dx, int(round(p[4])) + dy, p[5]])
exp[d] = dict((str(q[0]), (int(round(q[2])) + dx, int(round(q[3])) + dy))
for q in p[9])
return W, F, PT, place, exp, nfloat
def assert_clear(W, F, PT, exp, rect):
u"""新块要占的矩形里不许有 R1 的任何图元 —— 坐标改错了当场终止,别等截图。"""
x0, y0, x1, y1 = rect
bad = []
for a, b, net in W:
for p in (a, b):
if x0 <= p[0] <= x1 and y0 <= p[1] <= y1:
bad.append(u'导线 %s 端点 %s' % (net, (p,)))
if a[0] == b[0] and x0 <= a[0] <= x1 \
and min(a[1], b[1]) < y1 and max(a[1], b[1]) > y0:
bad.append(u'竖线 %s x=%d 穿过' % (net, a[0]))
if a[1] == b[1] and y0 <= a[1] <= y1 \
and min(a[0], b[0]) < x1 and max(a[0], b[0]) > x0:
bad.append(u'横线 %s y=%d 穿过' % (net, a[1]))
for it in F + PT:
if x0 <= it[2] <= x1 and y0 <= it[3] <= y1:
bad.append(u'旗/端口 %s @(%d,%d)' % (it[1], it[2], it[3]))
for d, ps in exp.items():
for num, xy in ps.items():
if x0 <= xy[0] <= x1 and y0 <= xy[1] <= y1:
bad.append(u'引脚 %s.%s @%s' % (d, num, (xy,)))
assert not bad, u'新块矩形 %s 里有 R1 的东西:\n %s' % (rect, '\n '.join(sorted(set(bad))))
def add_u31(s):
u"""U31 AMS1117-3.3:+5V(IN 端口) → U31 → +3V3_HUB(Power 旗)。
走法(Y 轴向上):
+5V ─●──●──────── 3 VIN ← y=255,从左平进,全程无竖线
C752 C753 2 VOUT ──< +3V3_HUB ← 635,BI+rot180,符号往左
│ │ 1 GND ──┐
⏚─────┴────┴──────────┬────┘ ← GND 先往左到 x=350 再下行
350,195 y=215 地干线 ──● C737 下脚
2 脚和 4 脚都是 VOUT(4 脚是散热片),**两个都接**:4 脚往右接 C737 与电源旗,
2 脚被 1/3 脚夹在中间(同一个 x、上下各差 10)只能往左,走 y=255 与 y=275 之间那条
20 单位的走廊,末端放一个同名 `+3V3_HUB` netport ——
netport 符号只有 ~10 高,R1 这一页两列端口就是 10 单位间距排下来的(skill §8b-2)。
"""
p = U31_PINS
# 输入:+5V 干线(C752 / C753 的上脚落在这条线上,EasyEDA 会在引脚处截断,skill §8b)
# ⚠ 两颗电容必须待在 x<535 —— 535~635 是下面那个 `+3V3_HUB` 端口标签占的横向范围。
s.w('+5V', (440, 255), (C752_C[0], 255), (C753_C[0], 255), p['3'])
s.port('IN', '+5V', 440, 255, 0) # IN + rot0 ⇒ 符号在**左**(§8b),占 380~440
# 地:U31 GND 左出 → x=350 下行 → 地旗;y=215 是全块的地干线,T 在竖线中段
s.w('GND', p['1'], (350, 275), (350, 195))
s.gnd(350, 195, 0) # Ground rot0:符号在下、引脚在上,导线从上方来
s.w('GND', (350, 215), (C752_C[0], 215), (C753_C[0], 215),
(C737_C[0], 215), (C737_C[0], 225))
# 输出:4 脚(散热片)→ C737 → 电源旗;2 脚往左出一个同名端口
s.w('+3V3_HUB', (635, 265), p['2'])
s.port('BI', '+3V3_HUB', 635, 265, 180) # BI + rot180 ⇒ 符号在**左**,占 535~635
s.w('+3V3_HUB', p['4'], (C737_C[0], 265), (880, 265), (880, 320))
s.pwr('+3V3_HUB', 880, 320, 0) # Power rot0:符号在上、引脚在下,导线从下方来
def add_usb6(s):
u"""第 6 个下行口引到右侧端口列(D13)。串阻 / ESD 一律不加,同 R1 的口 1~5。"""
for net, y in USB6:
s.w(net, (680, y), (760, y))
s.port('BI', net, 760, y, 0) # BI + rot0 ⇒ 符号在右,占 760~854
def _text_boxes(s):
out = []
for x, y, txt, size in s.TX:
cjk = sum(1 for ch in txt if ord(ch) > 0x2E7F)
w = size * (cjk + 0.5 * (len(txt) - cjk))
out.append((x, y - size * 0.8, x + w, y + size * 0.8, txt))
return out
def text_clash(s, exp):
u"""文字压在导线 / 引脚 / 旗端口上(同 a1_input.py / a2_aux.py)。"""
errs = []
pts = []
for d, ps in exp.items():
if '?' in ps:
for xy in ps['?']:
pts.append((xy[0], xy[1], u'引脚 %s' % d))
else:
for num, xy in ps.items():
pts.append((xy[0], xy[1], u'引脚 %s.%s' % (d, num)))
for it in s.F + s.PT:
pts.append((it[2], it[3], u'旗/端口 %s' % it[1]))
for x0, y0, x1, y1, txt in _text_boxes(s):
for px, py, what in pts:
if x0 <= px <= x1 and y0 <= py <= y1:
errs.append(u'文字压住 %s @(%d,%d): %s' % (what, px, py, txt[:26]))
for a, b, net in s.W:
if a[1] == b[1]:
if y0 <= a[1] <= y1 and min(a[0], b[0]) <= x1 and max(a[0], b[0]) >= x0:
errs.append(u'文字压住导线 %s y=%d: %s' % (net, a[1], txt[:26]))
else:
if x0 <= a[0] <= x1 and min(a[1], b[1]) <= y1 and max(a[1], b[1]) >= y0:
errs.append(u'文字压住导线 %s x=%d: %s' % (net, a[0], txt[:26]))
return sorted(set(errs))
def label_clash(s):
u"""端口的符号+标签往锚点某一侧伸出去 ~40+6.7×字符(skill §8b),会压住页面文字。
契约 §7 第 8 条点名说这是本项目的常见事故,而 `flag_clash` 的「横向 <20」判据
根本够不着。这里把标签算成一个盒子,和**页面文字**逐个比,顺带查越框。
"""
errs = []
tb = _text_boxes(s)
for d, net, x, y, rot in s.PT:
left = (d == 'IN') == (int(rot) % 360 == 0) # §8b:IN 与 BI/OUT 正好相反
# 净空用 6.7/字符(§8b 说是偏保守的安全值);**越框**只能用实测的 5/字符
# (`PORTTEST` 8 字符实测 80 单位 = 40 + 5×8),否则 R1 那个贴着右边框的
# `NONREM0`(1070 + 40 + 5×7 = 1145,正好压线)会被误判成越框。
for k, tag in ((6.7, None), (5.0, 'frame')):
w = 40 + k * len(net)
bx0, bx1 = (x - w, x) if left else (x, x + w)
if tag == 'frame':
if bx0 < 20 or bx1 > 1145:
errs.append(u'端口 %s @(%d,%d,%s,rot%d) 的标签越框 %.0f~%.0f'
% (net, x, y, d, rot, bx0, bx1))
continue
for tx0, ty0, tx1, ty1, txt in tb:
if bx0 < tx1 and tx0 < bx1 and ty0 < y + 6 and y - 6 < ty1:
errs.append(u'端口 %s @(%d,%d) 的标签压住文字: %s' % (net, x, y, txt[:26]))
return sorted(set(errs))
def port_label_clash(s, place):
u"""端口标签(符号 + 字)与别的端口标签 / 两脚件本体叠在一起。
2026-09-10 改动 4 加 strap 时,`VBUS_UP` 标签压 R725、`VBUS_DET` 标签顶 GND 端口 ——
`text_clash` 只查页面文字、`flag_clash` 只看锚点距离,都够不着,是截图发现的。
标签按 40 + 5×字符(§8b 实测)、高 ±5 算盒子;两脚件本体按 rot0 ±(20,5) / rot90·270 ±(5,20)。
R1 原页可能本来就有擦边,所以只报告、不拦下发。"""
boxes = []
for d, net, x, y, rot in s.PT:
left = (d == 'IN') == (int(rot) % 360 == 0)
w = 40 + 5 * len(net)
boxes.append(((x - w, y - 5, x, y + 5) if left else (x, y - 5, x + w, y + 5), u'端口 %s@(%d,%d)' % (net, x, y)))
bodies = []
for d, _k, x, y, rot in place:
if d[0] in 'RC' and d[1:].isdigit():
bodies.append(((x - 20, y - 5, x + 20, y + 5) if int(rot) % 180 == 0 else (x - 5, y - 20, x + 5, y + 20), d))
hit = lambda a, b: a[0] < b[2] and b[0] < a[2] and a[1] < b[3] and b[1] < a[3] # noqa: E731
errs = []
for i in range(len(boxes)):
for j in range(i + 1, len(boxes)):
if hit(boxes[i][0], boxes[j][0]):
errs.append(u'%s 与 %s 的标签叠在一起' % (boxes[i][1], boxes[j][1]))
for b, d in bodies:
if hit(boxes[i][0], b):
errs.append(u'%s 的标签压在 %s 上' % (boxes[i][1], d))
return sorted(set(errs))
def flag_clash(s, gx=20, gy=10):
u"""两面旗/端口的符号叠在一起(同 a1_input.py / a2_aux.py)。"""
errs, items = [], [(it[1], it[2], it[3]) for it in s.F + s.PT]
for i in range(len(items)):
for j in range(i + 1, len(items)):
(n1, x1, y1), (n2, x2, y2) = items[i], items[j]
if abs(x1 - x2) < gx and abs(y1 - y2) < gy:
errs.append(u'旗/端口 %s @(%d,%d) 与 %s @(%d,%d) 挨太近'
% (n1, x1, y1, n2, x2, y2))
return sorted(set(errs))
def build():
W, F, PT, place, exp, nfloat = load(SRC, OFF)
W = shift_block(W, F, PT, place, exp)
# 53/54(第 6 口)和六个 strap 脚在源页上都还是悬空的
want_nc = sum(len(v) for v in NC.values()) + len(USB6) + len(STRAP_RIGHT) + len(STRAP_LEFT)
assert nfloat == want_nc, \
u'源页悬空引脚 %d 个,NC 表 + 第 6 口 + strap 六脚 = %d 个(对不上就是漏接或者表过期)' \
% (nfloat, want_nc)
W = to_strap_mode(W, F, PT)
assert_clear(W, F, PT, exp, BLOCK)
for _d, y, _p in STRAP_RIGHT:
assert_clear(W, F, PT, exp, (695, y - 8, 745, y + 8))
for _d, y, _p in STRAP_LEFT:
assert_clear(W, F, PT, exp, (225, y - 8, 275, y + 8))
assert_clear(W, F, PT, exp, (940, R723_C[1] - 8, 1060, R723_C[1] + 8))
assert_clear(W, F, PT, exp, (745, 470, 755, 500)) # R725 → 47 行那段竖线
assert_clear(W, F, PT, exp, (215, 365, 245, 410)) # R728 的地旗(符号朝上)
s = Sheet(PAGE, NAME)
s.W += W
s.F += F
s.PT += PT
add_u31(s)
add_usb6(s)
add_straps(s)
for t in TEXTS:
s.txt(t[0], t[1], t[2], t[3])
place = place + [list(x) for x in NEW_PARTS] + [list(x) for x in STRAP_PARTS]
exp = dict(exp)
for d, y, _p in STRAP_RIGHT:
exp[d] = hpin(720, y)
for d, y, _p in STRAP_LEFT:
exp[d] = hpin(250, y)
exp['R723'] = hpin(*R723_C)
exp['U31'] = dict(U31_PINS) # 4 脚件,逐脚断言
exp['C752'] = two_pin(C752_C[0], C752_C[1]) # 无极性,只比坐标集合
exp['C753'] = two_pin(C753_C[0], C753_C[1])
exp['C737'] = {'1': (C737_C[0], C737_C[1] + 20), # ⚠ 钽电容:1 脚是正极,必须在上
'2': (C737_C[0], C737_C[1] - 20)} # 跨距 ±20、rot0(本页实测,见 C737_C 处注释)
assert len(set(d for d, _k, _x, _y, _r in place)) == len(place), u'位号撞车'
# 契约 §2:A4 只许用 U31 / U32 / Y1 / J23 + R708~R730 + C737 / C740~C760
for d, _k, _x, _y, _r in place:
ok = d in ('U31', 'U32', 'Y1', 'J23') \
or (d[0] == 'R' and d[1:].isdigit() and 708 <= int(d[1:]) <= 730) \
or (d == 'C737') \
or (d[0] == 'C' and d[1:].isdigit() and 740 <= int(d[1:]) <= 760)
assert ok, u'位号 %s 不在契约 §2 给 A4 的区段里' % d
# 契约 §3 第 3 条 + 本页不许出现 +12V
for it in s.F + s.PT:
assert it[1] not in ('+3V3', '+12V'), u'本页不该出现 %s' % it[1]
return s, place, exp
def main():
push = '--push' in sys.argv
s, place, exp = build()
s.assert_grid()
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)
errs = s.netcheck(pins, NC, ALLOW_SPLIT)
errs += text_clash(s, exp) + label_clash(s) + flag_clash(s)
plc = port_label_clash(s, place)
print(u'端口标签叠压(只报告,下发前人工看)%d 条%s' % (len(plc), u':' if plc else u''))
for e in plc:
print(u' ' + e)
# 契约 §3 第 1 条的硬约束复核:HUB_RST 必须有一颗 10k 上拉到 +3V3_HUB
assert PARTS['C25744']['name'] == '0402WGF1002TCE', u'C25744 不再是 10k 电阻'
r716 = [it for it in place if it[0] == 'R716']
assert len(r716) == 1 and r716[0][1] == 'C25744', u'R716 不是 10k(HUB_RST 上拉没了)'
assert pins['R716']['1'] == (1130, 320) and pins['R716']['2'] == (1130, 280), \
u'R716(HUB_RST 上拉)位置变了,重新核对 §3 第 1 条'
# 第 6 个下行口确实接出去了
for net, y in USB6:
assert any(it[1] == net and it[0] == 'BI' for it in s.PT), u'%s 端口没画' % net
print(u'R1 C2-HUB 原样搬 %d 件 + 新增 %d 件(U31 块)+ %d 件(strap ×6 + R723 1MΩ)= %d 件'
% (len(place) - len(NEW_PARTS) - len(STRAP_PARTS), len(NEW_PARTS), len(STRAP_PARTS), len(place)))
s.report(len(place), errs)
if not push:
return
call("await eda.dmt_Project.openProject('%s'); await new Promise(r=>setTimeout(r,2500)); return 1;"
% R2_PROJECT)
s.clear()
s.place(place, exp)
s.push_geometry()
hide_strap_values()
print(u'接着跑 verify_sch.py --nets')
# ⚠ strap 电阻相邻两行只差 20 个单位:上一颗的阻值字(`Name` 属性 ={Value},在本体下方 15)
# 正好落在下一颗的位号(本体上方 5)上 —— 2026-09-10 截图发现,R724 的「100kΩ」和 R725 的位号都在 y=500。
# 行距由 U32 引脚定死,挪不开 ⇒ 这六颗**隐藏阻值字、只留位号**(阻值写在 B 块说明里)。R723 那一行没邻居,照常显示。
# `sch_PrimitiveAttribute.modify(id, {valueVisible:false})` 2026-09-10 在 R725 上实测:改完立刻读回为 false。
# 清页重画会把属性冲回默认,所以这一步必须跟着每次下发走。
def hide_strap_values():
ds = [d for d, _y, _p in STRAP_RIGHT + STRAP_LEFT]
r = call(u"""
await eda.dmt_EditorControl.openDocument('%s');
await new Promise(r=>setTimeout(r,1500));
const DS = %s, out = {};
for (const c of await eda.sch_PrimitiveComponent.getAll()) {
if (c.getState_ComponentType()!=='part' || !DS.includes(c.getState_Designator())) continue;
const nm = (await eda.sch_PrimitiveAttribute.getAll(c.getState_PrimitiveId())).find(a=>a.getState_Key()==='Name');
if (!nm) { out[c.getState_Designator()] = 'no Name'; continue; }
await eda.sch_PrimitiveAttribute.modify(nm.getState_PrimitiveId(), {valueVisible:false});
const back = await eda.sch_PrimitiveAttribute.get(nm.getState_PrimitiveId());
out[c.getState_Designator()] = back ? back.getState_ValueVisible() : 'gone';
}
return out;""" % (PAGE, json.dumps(ds)))
bad = [d for d in ds if r.get(d) is not False]
if bad:
raise SystemExit(u'!! 这几颗的阻值字没藏住:%s(读回 %s)—— 不存盘' % (bad, r))
call(u"return await eda.sch_Document.save();")
print(u'strap 电阻阻值字已隐藏并存盘:%s' % ' '.join(sorted(ds)))
if __name__ == '__main__':
main()