Integrate A1 carrier PCB CAD updates

- Add pinned STEP-derived DE-9 mechanical measurements and provenance
- Refresh placement, routing, silkscreen, and nominal CAD envelopes
- Document provisional dimensions, assumptions, and validation status
This commit is contained in:
2026-09-21 12:31:24 +02:00
parent 06df47c934
commit c0f7a1ad51
13 changed files with 5825 additions and 3966 deletions
+11 -7
View File
@@ -109,10 +109,10 @@ def boundary(radius):
edge = 0.5 + radius + MARGIN
mask = np.broadcast_to((X < 100 + edge) | (X > 182 - edge) |
(Y < 100 + edge) | (Y > 180 - edge), (NY, NX)).copy()
mask |= (X < 136 + edge) & (Y > 161 - edge)
mask |= (X < 136 + edge) & (Y < 119 + edge)
# Expand the RF reservation by copper radius plus numerical safety margin.
r = radius + MARGIN
mask |= (X >= 110 - r) & (X <= 131 + r) & (Y <= 113 + r)
mask |= (X >= 110 - r) & (X <= 131 + r) & (Y >= 119 - r) & (Y <= 132 + r)
return mask
@@ -275,13 +275,15 @@ def main():
if ROOT.joinpath('~serial-carrier.kicad_pcb.lck').exists():
parser.error('PCB editor lock exists; close the PCB before routing')
original_bytes = PCB.read_bytes()
project = ROOT / 'serial-carrier.kicad_pro'
project_before = project.read_bytes()
board = p.LoadBoard(str(PCB))
before = identity(board)
if board.GetCopperLayerCount() != 2 or len(board.GetFootprints()) != 13:
parser.error('unexpected board structure; review router assumptions')
if len(board.Zones()) != 1 or not board.Zones()[0].GetIsRuleArea():
parser.error('unexpected zones; router supports only the original RF rule area')
outline = [(100, 100), (182, 100), (182, 180), (136, 180), (136, 161), (100, 161)]
outline = [(136, 100), (182, 100), (182, 180), (100, 180), (100, 119), (136, 119)]
expected_edges = {frozenset((a, b)) for a, b in zip(outline, outline[1:] + outline[:1])}
actual_edges = {frozenset(((p.ToMM(e.GetStart().x), p.ToMM(e.GetStart().y)),
(p.ToMM(e.GetEnd().x), p.ToMM(e.GetEnd().y))))
@@ -292,7 +294,7 @@ def main():
contour = reserve.Outline().COutline(0)
rf_points = {(p.ToMM(contour.CPoint(i).x), p.ToMM(contour.CPoint(i).y))
for i in range(contour.PointCount())}
if (rf_points != {(110, 100), (131, 100), (131, 113), (110, 113)} or
if (rf_points != {(110, 119), (131, 119), (131, 132), (110, 132)} or
not all(reserve.IsOnLayer(layer) for layer in LAYERS) or
not all((reserve.GetDoNotAllowTracks(), reserve.GetDoNotAllowVias(),
reserve.GetDoNotAllowZoneFills()))):
@@ -313,8 +315,9 @@ def main():
if len(netpads) > 1:
name = netpads[0].GetNetname()
for length, a, b in pairs_for_net(netpads):
# Short local links first, then the long signal fanout; ground last.
priority = (name == '/GND', length)
# Reserve short ground escapes before signal fanout can box them in.
# Long ground links still run last, preserving space for signal paths.
priority = (name == '/GND' and length > 15, length)
work.append((priority, net, name, a, b))
work.sort(key=lambda w: (w[0], w[2]))
start_time = time.monotonic()
@@ -354,7 +357,8 @@ def main():
raise RuntimeError('PCB changed or editor opened during routing; refusing overwrite')
# Check a serialized candidate before replacing the user's board.
candidate = ROOT / 'validation/pcb-routing-candidate.kicad_pcb'
p.SaveBoard(str(candidate), board)
assert p.SaveBoard(str(candidate), board, True)
assert project.read_bytes() == project_before, 'project settings changed'
assert identity(p.LoadBoard(str(candidate))) == before, 'saved identity mismatch'
candidate.replace(PCB)
ROOT.joinpath('validation/pcb-routing-run.json').write_text(json.dumps(report, indent=2)+'\n')