kicad:EasyEDA→KiCad 工程(Board1/Board2)、库、脚本与评审文档(IC 引脚、风扇、热、EDL 供电、布局规则)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
zhou yang
2026-10-04 19:24:42 +08:00
共同撰写人 Claude Opus 5.5
父节点 1d842cf45f
当前提交 bfe094a5d7
共修改 173 个文件,包含 401864 行新增和 0 行删除
+225
查看文件
@@ -0,0 +1,225 @@
"""Guarded Hub strap pulldown ECO and reviewed USB/LED NC markers."""
import argparse
import hashlib
import json
import math
import shutil
import uuid
import xml.etree.ElementTree as ET
import pcbnew as pcb
from bootstrap_pcb import native_net_names, xy
from check_hardware import Board
from optimize_pcb_layout import Layout, box_mm, expand, point, place_silkscreen
from schematic_maintenance import ROOT, child, children, parse, properties
from hub_strap_checks import STRAPS
OUT = ROOT / 'out/hub-straps-r1'
CLI = 'D:/KiCad/bin/kicad-cli.exe'
NC = {1: {'J22': ['A8', 'B8', 'A4B9', 'B4A9']},
2: {'J23': ['A8', 'B8'], 'U32': ['14', '16', '18', '32']}}
SHEETS = [ROOT / 'PhoneRail-Board1/P3-CONTROL.kicad_sch',
ROOT / 'PhoneRail-Board2/C2-HUB.kicad_sch']
PCB = ROOT / 'PhoneRail-Board2/PhoneRail-Board2.kicad_pcb'
def uid(key):
return str(uuid.uuid5(uuid.NAMESPACE_URL, 'PhoneRail/hub-straps-r1/' + key))
def pin_position(root, instance, number):
assert child(instance, 'mirror') is None, 'Mirror requires explicit geometry review'
lib = next(s for s in children(child(root, 'lib_symbols'), 'symbol')
if s[1] == child(instance, 'lib_id')[1])
pin = next(p for u in children(lib, 'symbol') for p in children(u, 'pin')
if child(p, 'number')[1] == str(number))
x, y, angle = map(float, child(instance, 'at')[1:])
px, py = map(float, child(pin, 'at')[1:3])
a = math.radians(angle)
return round(x + px*math.cos(a)-py*math.sin(a), 5), round(y-px*math.sin(a)-py*math.cos(a), 5)
def label(name, x, y, key, global_label=False, angle=0):
kind = 'global_label' if global_label else 'label'
shape = '(shape input)' if global_label else ''
return (f'({kind} "{name}" {shape} (at {x} {y} {angle}) '
f'(effects (font (size 1 1)) (justify {"left" if angle == 0 else "right"} bottom)) (uuid "{uid(key)}"))')
def layout_notes(source):
root = parse(source)
for note in sorted(children(root, 'text'), key=lambda n: n.start, reverse=True):
source = source[:note.start] + source[note.end:]
notes = [
'CONFIG: CFG_SEL[2:0] = 000; internal defaults + straps.',
'ROM 06h = 9Bh: self-powered operation.',
'PRTPWR / OCS open by existing architecture.',
'5V short-circuit / port OC response: REVIEW OPEN.',
'R900-R909: 100k explicit strap-low (Fig 7-3).',
'PRT_SWP[7:1] = 0: normal D+/D- polarity.',
'BOOST[1:0] = 00; GANG_EN = 0.',
'Ports 6/7: R714/R715 10k to +3V3_HUB.',
'Port-disable / enumeration: verify on prototype.',
'RBIAS: 12.0k 1%; Y1: 24MHz.',
'VDD18/VDD18PLL: >=1uF, ESR <0.1 ohm.',
'VBUS_DET: R709/R710 = 100k/100k.',
'UP_VBUS_DET: R721/R722 = 10k/20k.',
'NC: USB2-only SBU and four unused LED outputs.',
]
additions = []
for index, text in enumerate(notes):
additions.append(f'(text "{text}" (at 310 {45+index*7} 0) '
'(effects (font (size 1.27 1.27)) (justify left bottom)) '
f'(uuid "{uid("review-note-"+str(index))}"))')
additions.append('(text "R900-R909: USB2517 strap-low pulldowns, 100k each" (at 25.4 216 0) '
'(effects (font (size 1.27 1.27)) (justify left bottom)) '
f'(uuid "{uid("bank-note")}"))')
root = parse(source)
return source[:root.end-1] + '\n'.join(additions) + '\n' + source[root.end-1:]
def edit_sheets():
for number, path in enumerate(SHEETS, 1):
source = path.read_text(encoding='utf-8')
root = parse(source)
instances = {properties(s)['Reference'][2]: s for s in children(root, 'symbol')}
additions = []
for ref, pins in NC[number].items():
for pin in pins:
x, y = pin_position(root, instances[ref], pin)
additions.append(f'(no_connect (at {x} {y}) (uuid "{uid(ref+pin)}"))')
if number == 2:
template_source = (ROOT / 'PhoneRail-Board2/C4-SITE1.kicad_sch').read_text(encoding='utf-8')
template_root = parse(template_source)
template = next(s for s in children(template_root, 'symbol') if properties(s)['Reference'][2] == 'R741')
props = {k: p[2] for k, p in properties(template).items()}
lib_id = child(template, 'lib_id')[1]
library = child(root, 'lib_symbols')
if not any(s[1] == lib_id for s in children(library, 'symbol')):
lib = next(s for s in children(child(template_root, 'lib_symbols'), 'symbol') if s[1] == lib_id)
source = source[:library.end-1] + template_source[lib.start:lib.end] + '\n' + source[library.end-1:]
root = parse(source)
sheet_path = child(child(child(instances['R711'], 'instances'), 'project'), 'path')[1]
for index, (refnum, pin) in enumerate(STRAPS.items()):
ref, name = f'R{refnum}', f'STRAP_{pin}'
x, y = 25.4 + index*30.48, 236.22
fields = []
for key, value in {**props, 'Reference': ref}.items():
visible = key in ('Reference', 'Value')
fx, fy = (x+2.54, y+(2.54 if key == 'Value' else 0)) if visible else (x, y)
fields.append(f'(property {json.dumps(key)} {json.dumps(value, ensure_ascii=False)} '
f'(at {fx} {fy} 270) (effects (font (size 1.27 1.27)) '
f'(justify left bottom) {"" if visible else "(hide yes)"}))')
additions.append(f'(symbol (lib_id "{lib_id}") (at {x} {y} 90) (unit 1) '
'(exclude_from_sim no) (in_bom yes) (on_board yes) (dnp no) '
f'(uuid "{uid(ref)}") ' + ' '.join(fields) +
f' (instances (project "PhoneRail-Board2" (path "{sheet_path}" '
f'(reference "{ref}") (unit 1)))))')
# The existing resistor symbol has pins at local x = +/-5.08.
new_instance = parse(additions[-1])
rx1, ry1 = pin_position(root, new_instance, 1)
rx2, ry2 = pin_position(root, new_instance, 2)
additions += [label('GND', rx1, ry1, ref+'gnd', True),
label(name, rx2, ry2, ref+'net')]
hx, hy = pin_position(root, instances['U32'], pin)
additions.append(label(name, hx, hy, 'hub'+str(pin), angle=180 if hx < 128 else 0))
additions.append('(text "R900-R909: 100k strap-low; USB2517 DS00001598C Fig 7-3." '
'(at 25.4 216 0) (effects (font (size 1.27 1.27)) (justify left bottom)) '
f'(uuid "{uid("note")}"))')
source = source[:root.end-1] + '\n' + '\n'.join(additions) + '\n' + source[root.end-1:]
if number == 2:
source = layout_notes(source)
path.write_text(source, encoding='utf-8')
def sync_pcb(source):
board = pcb.LoadBoard(str(PCB))
names = native_net_names(source)
nets = {n.GetNetname(): n for n in board.GetNetsByName().values()}
for name in names.values():
if name not in nets:
net = pcb.NETINFO_ITEM(board, name)
board.Add(net)
nets[name] = net
existing = {f.GetReference(): f for f in board.GetFootprints()}
components = {c.attrib['ref']: c for c in ET.parse(OUT / 'after/Board2.xml').findall('components/comp')}
root_uuid = child(parse(source.path.read_text(encoding='utf-8')), 'uuid')[1]
layout = Layout.__new__(Layout)
layout.number, layout.board, layout.parts = 2, board, existing
layout.done, layout.placements = set(existing), {}
layout.obstacles = [(r, expand(box_mm(f), .35),
-1 if any(p.GetAttribute() in (pcb.PAD_ATTRIB_PTH, pcb.PAD_ATTRIB_NPTH)
for p in f.Pads()) else f.GetLayer()) for r, f in existing.items()]
for z in board.Zones():
if z.GetIsRuleArea() and z.GetDoNotAllowFootprints():
layout.obstacles.append((z.GetZoneName(), expand(box_mm(z), .25),
-1 if z.GetLayerSet().Contains(pcb.F_Cu) else pcb.B_Cu))
center = point(existing['U32'])
for refnum, pin in STRAPS.items():
ref = f'R{refnum}'
c = components[ref]
fp = pcb.FootprintLoad(str(ROOT / 'lib/PhoneRail.pretty'), 'R0402')
fp.SetFPIDAsString('PhoneRail:R0402')
fp.SetReference(ref)
fp.SetValue(c.findtext('value'))
fp.SetSheetname('C2-HUB')
fp.SetSheetfile('C2-HUB.kicad_sch')
for field in c.findall('fields/field'):
if field.get('name') not in {'Reference', 'Value', 'Footprint'}:
fp.SetField(field.get('name'), field.text or '')
path = pcb.KIID_PATH()
for key in [root_uuid, *c.find('sheetpath').get('tstamps').strip('/').split('/'), c.findtext('tstamps')]:
path.push_back(pcb.KIID(key))
fp.SetPath(path)
board.Add(fp)
existing[ref] = fp
pad = next(p for p in existing['U32'].Pads() if p.GetNumber() == str(pin))
px, py = point(pad)
dx, dy = px-center[0], py-center[1]
norm = math.hypot(dx, dy)
layout.put(ref, (px+dx/norm*2, py+dy/norm*2), rotation=(0,90,180,270), radius=12,
reason=f'100k pull-down for U32.{pin}; DS00001598C Fig 7-3')
fp.Reference().SetLayer(pcb.B_Fab)
fp.SetField('ReferenceOnFabOnly', 'yes')
for field in fp.GetFields():
field.SetVisible(field.GetName() == 'Reference')
field.SetMirrored(True)
for ref, fp in existing.items():
for pad in fp.Pads():
endpoint = (ref, pad.GetNumber())
if endpoint in source.pins:
pad.SetNet(nets[names[source.pins[endpoint]]])
place_silkscreen(board, {'C745', 'C747', 'C748', 'R709', 'R714', 'R719'})
pcb.SaveBoard(str(PCB), board)
(OUT / 'placement.json').write_text(json.dumps(layout.placements, indent=2) + '\n')
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument('--apply', action='store_true')
args = ap.parse_args()
if not args.apply:
raise SystemExit('Use --apply once; validate with verify_erc_disposition.py afterwards')
before = OUT / 'before'
if before.exists():
raise RuntimeError('Existing ECO baseline; refusing to repeat mutations')
before.mkdir(parents=True)
for path in [*SHEETS, PCB]:
shutil.copy2(path, before / path.name)
for number in (1, 2):
Board(number).compile(CLI, before)
edit_sheets()
after = OUT / 'after'
after.mkdir()
for number in (1, 2):
board = Board(number)
board.compile(CLI, after)
if number == 2:
sync_pcb(board)
hashes = {str(p.relative_to(ROOT)): hashlib.sha256(p.read_bytes()).hexdigest() for p in [*SHEETS, PCB]}
(OUT / 'applied.json').write_text(json.dumps(hashes, indent=2) + '\n')
if __name__ == '__main__':
main()