# -*- coding: utf-8 -*- u"""P1-INPUT 的文字收拾(2026-09-06,跟在 `p1_r9f1.py` 换件之后)。 换完 R9A~R9E 截图才看出来两件事,一件是新的、一件是一直都在的: 1. **新的**:R9C/R9D/R9E 排在 y=300 的 x 860~1020 上,正好压住 Rs6/U13 那组说明文字 (x 850~1210 / y 261~300)。 2. **一直都在的**:`p1_add.py` 是「整页重画」的脚本,被推过好几次, 而它的清空那一步没把文字清干净 —— **66 个文字图元里只有 30 条是唯一的, 多出来的 36 个是同坐标同内容叠在一起的副本**(截图上表现为字发虚发粗)。 Rs6/U13 那一组还叠了**两代不同措辞**(x=850 的短版 + x=900 的长版)。 ⚠️ 这类东西 `verify_sch.py` / `port_audit.py` / `xpage.py` 一个都不报 —— 文字没有电气含义,三道检查都不看它。**只有截图能发现。** 做三件事: * **全页文字去重**:同 (x, y, 内容) 只留一个。 * **Rs6/U13 那一组**(两代共 7 条)整组删掉,在 R9 链**下方** (700, 206~245) 重建, 用措辞更全的那一代。x 700~1010 / y 206~245:离标题栏(x>455 且 y<192)还有 14, 离 GND 旗(1060,270)的标签在 x 上不重叠。 * **两条过期的说明**改掉:`F1 10A` → 12A;`R9 10R/5W` → R9A~R9E 5×2Ω。 用法: /c/Python314/python .claude/p1_text_fix.py [--push] """ import io import json import os import sys HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) from eda import run PAGE = '9a6096762e036e23' # 整组删掉的旧说明(两代措辞,按 (x, y) 认) DROP_XY = [(850, 261), (850, 274), (850, 287), (850, 300), (900, 269), (900, 282), (900, 295), # ⬇ 更早的一整代说明文字。判据是 `p1-base.json`(整页几何底稿)只有 9 条、 # `p1_add.py` 的 ADD_TEXT 再加 5 条 —— 下面这些两边都不在,是残留。 # 它们各自压着一处: (150, 760), (150, 780), (150, 795), (150, 810), # 旧【C】压在【A】上(页顶) (150, 360), (150, 380), # 旧【B】压在【C】上 (140, 120), (140, 140), # 旧【A】,还写着 F1 10A (520, 120), (520, 140), # 旧【A2】—— **印在标题栏里** (900, 800), # 旧【D】压着 (850,800) (150, 505)] # 旧注(内容已并进【B】标题) # ⚠️ (60,537) 上叠着两条**不同内容**:新的「已复核 SLUSEF4A…」和旧的「…待复核」。 # 去重按 (x, y, 内容) 只能各留一条,所以旧的那条要按内容单独点名。 DROP_TEXT = [u'R2/R3 分压与 EN 接法待按 LMR51430 datasheet 的 Vref / EN 耐压复核'] # 收拾完页上应该剩下的坐标(9 条底稿 + 1 条【D】标题 + 4 条新说明块)。 # 逐条对账,多一条少一条都停 —— 「删掉名单外的一切」这种写法一旦名单错了就没救了。 CANON_XY = [(60, 800), (60, 789), (40, 730), (60, 548), (60, 537), (60, 352), (60, 340), (60, 330), (60, 319), (850, 800)] # 在 R9 链下方重建(x 700 起,宽的一条到 1010,不碰 GND 旗) ADD_TEXT = [ (700, 245, 9, u'Rs6 5mΩ: 满板约 8A -> 40mV,INA226 满量程 81.92mV / 分辨率 0.5mA'), (700, 232, 9, u'U13 地址 A1=GND、A0=SCL -> 0x43(地址表见 §8.2,不是顺排)'), (700, 219, 9, u'体电容在 Rs6 上游;PCB 上 Rs6 放在体电容与五路分叉点之间,Kelvin 四线焊盘'), (700, 206, 9, u'R9A~R9E: 5 × 2Ω 2512 串联 = 10Ω/10W,高 0.8mm(原立式水泥电阻站起来超高度预算)'), ] # 就地改写的过期说明:(x, y) -> 新内容 REWRITE = { (60, 789): u'12V/10A -> F1 12A -> Q1 AO4407 反接保护,栅极 RC 缓启动 tau = R1*C1 = 10ms', (60, 319): u'泄放为突发额定:13.3V 下 1.33A / 17.7W,持续故障由固件关 VBUS 兜底', } # A4 可用区与标题栏(skill §8c / §12) X_MIN, X_MAX, Y_MIN, Y_MAX = 20, 1145, 10, 812 TB_X, TB_Y = 455, 192 def width(s, sz): u"""文字宽度 ≈ 字号 × (中日韩字数 + 0.5 × ASCII 字数)(skill §12 实测值)。""" cjk = sum(1 for c in s if ord(c) > 0x2e80) return sz * (cjk + 0.5 * (len(s) - cjk)) def selfcheck(): bad = [] for x, y, sz, s in ADD_TEXT: w = width(s, sz) if x + w > X_MAX: bad.append(u'(%d,%d) 右边界 %.0f 超出可用区 %d:%s' % (x, y, x + w, X_MAX, s[:24])) if y < Y_MIN or y > Y_MAX: bad.append(u'(%d,%d) 越出可用区' % (x, y)) if x > TB_X and y < TB_Y: bad.append(u'(%d,%d) 落进标题栏' % (x, y)) # R9 链的符号占 y 290~310,位号文字再往上 ⇒ 说明文字顶边不能高过 260 if y > 260: bad.append(u'(%d,%d) 会撞上 R9 链' % (x, y)) # GND 旗 (1060,270) 的标签大致占 x 1040~1080 / y 240~258 if y > 235 and x + w > 1040: bad.append(u'(%d,%d) 宽 %.0f 会压到 GND 旗的标签' % (x, y, w)) for (x, y), s in REWRITE.items(): if x + width(s, 9) > X_MAX: bad.append(u'改写后的 (%d,%d) 超出右边界' % (x, y)) return bad OPEN = ("await eda.dmt_EditorControl.openDocument('%s');" "await new Promise(r=>setTimeout(r,1500));" % PAGE) def call(body, timeout=180): r = run(OPEN + body, timeout=timeout) if not r.get('success'): print(u'失败:', json.dumps(r, ensure_ascii=False)[:600]) sys.exit(1) return r['result'] JS = u""" const cur = (await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name; if (cur !== 'P1-INPUT') return {err: '打错页了: ' + cur}; // 新说明块的坐标也进 DROP:那几条是「先删掉再按当前内容重建」, // 否则这个脚本**不幂等** —— 第二次跑会在同一坐标上再叠一层 // (2026-09-06 实测:27 → 18 而不是 14,而对账因为坐标在名单内还报了 OK)。 const DROP = new Set(%(drop)s); const DROPTXT = new Set(%(droptxt)s); const CANON = new Set(%(canon)s); const REW = %(rew)s; // [[x, y, 新内容], ...] const rewKey = new Set(REW.map(r => r[0] + ',' + r[1])); const all = []; for (const t of await eda.sch_PrimitiveText.getAll()) all.push({id: t.getState_PrimitiveId(), x: Math.round(+t.x), y: Math.round(+t.y), s: String(t.getState_Content ? t.getState_Content() : (t.content||'')), sz: t.getState_FontSize ? t.getState_FontSize() : 9}); // ① 整组删掉的 + ② 去重(同 x/y/内容只留第一个) const seen = new Set(); const kill = [], keep = []; for (const t of all) { const xy = t.x + ',' + t.y; if (DROP.has(xy) || DROPTXT.has(t.s)) { kill.push(t.id); continue; } const k = xy + '|' + t.s; if (seen.has(k)) { kill.push(t.id); continue; } seen.add(k); keep.push(t); } for (const id of kill) await eda.sch_PrimitiveText.delete(id); // ③ 就地改写:删掉留下来的那一条,按新内容重建(文字没有「改内容」的 setState) let rewrote = 0; for (const t of keep) { const k = t.x + ',' + t.y; if (!rewKey.has(k)) continue; const nw = REW.find(r => r[0] === t.x && r[1] === t.y); await eda.sch_PrimitiveText.delete(t.id); await eda.sch_PrimitiveText.create(t.x, t.y, nw[2], 0, null, null, t.sz); rewrote++; } // ④ 新的说明块 let added = 0; for (const a of %(add)s) { await eda.sch_PrimitiveText.create(a[0], a[1], a[3], 0, null, null, a[2]); added++; } // ⑤ 逐条对账:剩下的必须正好是 CANON + 新说明块,多一条少一条都报出来 const left = [], stray = []; for (const t of await eda.sch_PrimitiveText.getAll()) { const xy = Math.round(+t.x) + ',' + Math.round(+t.y); left.push(xy); if (!CANON.has(xy)) stray.push(xy); } const missing = [...CANON].filter(k => !left.includes(k)); const dupLeft = left.filter((k, i) => left.indexOf(k) !== i); return {before: all.length, killed: kill.length, rewrote, added, after: left.length, want: CANON.size, stray: [...new Set(stray)], missing, dup: [...new Set(dupLeft)]}; """ def main(): bad = selfcheck() print(u'P1-INPUT 文字收拾:去重 + Rs6/U13 说明块挪到 R9 链下方 + 两条过期说明改写') for x, y, sz, s in ADD_TEXT: print(u' 新块 (%4d,%3d) 宽 %4.0f -> x 到 %4.0f %s' % (x, y, width(s, sz), x + width(s, sz), s[:34])) for (x, y), s in sorted(REWRITE.items()): print(u' 改写 (%4d,%3d) %s' % (x, y, s[:44])) if bad: print(u'❌ 本地自检没过:') for b in bad: print(u' · ' + b) sys.exit(1) print(u' [OK] 本地自检:不出图框 / 不进标题栏 / 不撞 R9 链 / 不压 GND 旗标签') if '--push' not in sys.argv: print(u'\n加 --push 才真的改。') return canon = CANON_XY + [(x, y) for x, y, _sz, _s in ADD_TEXT] d = call(JS % dict( drop=json.dumps(['%d,%d' % xy for xy in DROP_XY] + ['%d,%d' % (x, y) for x, y, _sz, _s in ADD_TEXT]), droptxt=json.dumps(DROP_TEXT, ensure_ascii=False), canon=json.dumps(['%d,%d' % xy for xy in canon]), rew=json.dumps([[x, y, s] for (x, y), s in REWRITE.items()], ensure_ascii=False), add=json.dumps([[x, y, sz, s] for x, y, sz, s in ADD_TEXT], ensure_ascii=False))) if d.get('err'): sys.exit(u'❌ ' + d['err']) print(u' 文字 %d -> %d(删 %d:旧的一整代 + 重复副本;改写 %d;新增 %d)' % (d['before'], d['after'], d['killed'], d['rewrote'], d['added'])) ok = True if d.get('stray'): ok = False print(u' [!!] 名单外还剩 %d 处坐标:%s' % (len(d['stray']), ' '.join(d['stray']))) if d.get('missing'): ok = False print(u' [!!] 该留的没了 %d 处:%s' % (len(d['missing']), ' '.join(d['missing']))) if d.get('dup'): ok = False print(u' [!!] 还有 %d 处坐标叠着两条以上:%s' % (len(d['dup']), ' '.join(d['dup']))) if d['after'] != d['want']: ok = False print(u' [!!] 剩 %d 条,应该是 %d 条' % (d['after'], d['want'])) if ok: print(u' [OK] 剩下的 %d 条正好等于「底稿 9 + 【D】标题 1 + 新说明块 4」,且无同坐标叠加' % d['after']) print(u'\n⚠️ 文字改动三道几何检查一个都不报 —— 必须截图人眼看。') if not ok: sys.exit(1) if __name__ == '__main__': main()