Add KiCad module carrier PCB draft
Introduce an editable KiCad 10 schematic and routed two-layer 82 × 80 mm layout with local provisional footprints, validation tooling, and component research. Keep the RS-232 male-module mapping and mechanical clearances explicitly provisional pending hardware verification.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read-only PCB checks and fresh KiCad reports; does not regenerate or save PCB.
|
||||
|
||||
Requires KiCad's pcbnew binding in system Python. The known U2 footprint parity
|
||||
warning is reported explicitly, not hidden or added to KiCad's exclusions.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as ET
|
||||
import pcbnew as p
|
||||
|
||||
ROOT=Path(__file__).resolve().parent
|
||||
PCB=ROOT/"serial-carrier.kicad_pcb"
|
||||
OUT=ROOT/"validation"
|
||||
|
||||
def run(*args):
|
||||
subprocess.run(args,check=True,cwd=ROOT)
|
||||
|
||||
run("kicad-cli","sch","export","netlist","--format","kicadxml","-o",str(OUT/"pcb-source-netlist.xml"),str(ROOT/"serial-carrier.kicad_sch"))
|
||||
run("kicad-cli","pcb","drc","--format","json","--all-track-errors","--severity-all","--schematic-parity","-o",str(OUT/"pcb-parity-drc.json"),str(PCB))
|
||||
board=p.LoadBoard(str(PCB))
|
||||
source=ET.parse(OUT/"pcb-source-netlist.xml").getroot()
|
||||
comps={c.get("ref"):c for c in source.findall("components/comp")}
|
||||
footprints={f.GetReference():f for f in board.GetFootprints()}
|
||||
assert set(footprints)==set(comps)|{"H1","H2","H3","H4"}
|
||||
assert board.GetCopperLayerCount()==2
|
||||
assert p.ToMM(board.GetDesignSettings().GetBoardThickness())==1.6
|
||||
outline=[d for d in board.GetDrawings() if d.GetLayer()==p.Edge_Cuts]
|
||||
assert len(outline)==6
|
||||
points={(round(p.ToMM(v.x)-100,5),round(p.ToMM(v.y)-100,5)) for d in outline for v in (d.GetStart(),d.GetEnd())}
|
||||
assert points=={(0,0),(82,0),(82,80),(36,80),(36,61),(0,61)}
|
||||
assert max(x for x,y in points)-min(x for x,y in points)==82
|
||||
assert max(y for x,y in points)-min(y for x,y in points)==80
|
||||
padnets={}
|
||||
for net in source.findall("nets/net"):
|
||||
for node in net.findall("node"):
|
||||
padnets[(node.get("ref"),node.get("pin"))]=net.get("name")
|
||||
seen=set()
|
||||
for ref,comp in comps.items():
|
||||
fp=footprints[ref]
|
||||
assert fp.GetValue()==comp.findtext("value"),ref
|
||||
assert fp.GetPath().AsString().endswith("/"+comp.findtext("tstamps")),ref
|
||||
wanted=comp.findtext("footprint")
|
||||
if ref=="U2":
|
||||
assert not wanted
|
||||
assert fp.GetFPIDAsString()=="Carrier:MAX3243_Reference_Provisional"
|
||||
else:
|
||||
assert fp.GetFPIDAsString()==wanted,ref
|
||||
for pad in fp.Pads():
|
||||
if not pad.GetNumber():
|
||||
continue
|
||||
key=ref,pad.GetNumber()
|
||||
assert pad.GetNetname()==padnets[key],(key,pad.GetNetname(),padnets[key])
|
||||
seen.add(key)
|
||||
assert seen==set(padnets),"Missing or extra electrical pads"
|
||||
for ref in ("H1","H2","H3","H4"):
|
||||
assert footprints[ref].IsBoardOnly()
|
||||
pads=list(footprints[ref].Pads())
|
||||
assert len(pads)==1 and pads[0].GetDrillSize().x==p.FromMM(3.2)
|
||||
assert pads[0].GetAttribute()==p.PAD_ATTRIB_NPTH
|
||||
tracks=list(board.GetTracks())
|
||||
assert tracks,"Draft unexpectedly unrouted"
|
||||
for track in tracks:
|
||||
if isinstance(track,p.PCB_VIA):
|
||||
assert track.GetDrillValue()==p.FromMM(0.3)
|
||||
assert track.GetWidth(p.F_Cu)==p.FromMM(0.7)
|
||||
else:
|
||||
assert track.GetLayer() in (p.F_Cu,p.B_Cu)
|
||||
expected=0.5 if track.GetNetname() in ("/+3V3","/GND") else 0.25
|
||||
assert track.GetWidth()==p.FromMM(expected)
|
||||
report=json.loads((OUT/"pcb-parity-drc.json").read_text())
|
||||
assert not report["violations"],report["violations"]
|
||||
assert not report["unconnected_items"],report["unconnected_items"]
|
||||
parity=report["schematic_parity"]
|
||||
assert len(parity)==1 and parity[0]["type"]=="footprint_symbol_mismatch",parity
|
||||
assert len(parity[0]["items"])==1 and parity[0]["items"][0]["uuid"]==footprints["U2"].m_Uuid.AsString()
|
||||
summary={
|
||||
"carrier_bounds_mm":[82,80],
|
||||
"copper_layers":2,
|
||||
"draft_thickness_mm":1.6,
|
||||
"electrical_footprints":len(comps),
|
||||
"proposed_M3_mounts":4,
|
||||
"physical_DRC_violations":0,
|
||||
"unconnected_items":0,
|
||||
"schematic_pad_nets_match":True,
|
||||
"known_parity_warning":"U2 provisional female-reference footprint assigned on PCB only; schematic footprint intentionally blank",
|
||||
"ignored_DRC_checks":report["ignored_checks"],
|
||||
"not_validated":["male module pin map/fit", "assembly envelope and heights", "header/socket fit", "RF performance and antenna clearance", "power/return-path integrity", "USB power path", "OLED pull-ups", "fabrication process", "hardware operation"],
|
||||
}
|
||||
(OUT/"pcb-validation-summary.json").write_text(json.dumps(summary,indent=2)+"\n")
|
||||
print("PCB draft verified: 82 x 80 mm; 2 copper layers; 0 physical DRC violations; 0 unrouted.")
|
||||
print("All schematic pad nets match. One expected U2 footprint parity warning remains visible.")
|
||||
print("Not fabrication-ready; see pcb-draft-notes.md and ignored checks in the DRC report.")
|
||||
Reference in New Issue
Block a user