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:
@@ -3,11 +3,11 @@
|
||||
|
||||
Requires system Python + pcbnew. Refuses to overwrite an existing PCB unless
|
||||
--overwrite is explicit. This is a draft generator, not a manufacturing tool.
|
||||
Does not modify the schematic or footprint libraries. KiCad SaveBoard also saves
|
||||
PCB design settings into the project; close the project before regeneration.
|
||||
Does not modify the schematic, footprint libraries or project settings.
|
||||
SaveBoard(..., True) deliberately preserves the user's existing project rules.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
@@ -18,10 +18,10 @@ ROOT = Path(__file__).resolve().parent
|
||||
PCB = ROOT / "serial-carrier.kicad_pcb"
|
||||
LIBS = Path("/usr/share/kicad/footprints")
|
||||
ORIGIN = (100, 100)
|
||||
OUTLINE = [(0,0), (82,0), (82,80), (36,80), (36,61), (0,61)]
|
||||
OUTLINE = [(36,0), (82,0), (82,80), (0,80), (0,19), (36,19)]
|
||||
PLACEMENT = {
|
||||
"U1": ("Carrier:HW678_2x22_Provisional", 8, 4),
|
||||
"U2": ("Carrier:MAX3243_Reference_Provisional", 43, 30),
|
||||
"U1": ("Carrier:HW678_2x22_Provisional", 8, 23),
|
||||
"U2": ("Carrier:MAX3243_Reference_Provisional", 43, 26.670),
|
||||
"DS1": ("Carrier:OLED_26mm_I2C_Provisional", 43, 36),
|
||||
"R1": ("Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal", 43, 34),
|
||||
"R2": ("Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P7.62mm_Horizontal", 54, 34),
|
||||
@@ -30,9 +30,9 @@ PLACEMENT = {
|
||||
"SW2": ("Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x", 55, 68),
|
||||
"SW3": ("Button_Switch_THT:SW_TH_Tactile_Omron_B3F-100x", 67, 68),
|
||||
}
|
||||
MOUNTS = [(3.5,3.5), (3.5,56), (78,4), (78,76)]
|
||||
MOUNTS = [(3.5,22.5), (3.5,75), (78,4), (78,76)]
|
||||
# Deliberate allocation, NOT a measured antenna envelope or an RF guarantee.
|
||||
RF_RESERVE = [(10,0), (31,0), (31,13), (10,13)]
|
||||
RF_RESERVE = [(10,19), (31,19), (31,32), (10,32)]
|
||||
|
||||
def mm(v):
|
||||
return p.FromMM(v)
|
||||
@@ -87,20 +87,6 @@ def copy_schematic_fields(fp, component):
|
||||
fp.GetField(name).SetVisible(False)
|
||||
|
||||
|
||||
def configure_draft_netclasses():
|
||||
# SaveBoard creates the project defaults. Align future interactive routing
|
||||
# with the draft's physical minimum widths and its explicit power routing.
|
||||
path=ROOT/"serial-carrier.kicad_pro"
|
||||
project=json.loads(path.read_text())
|
||||
settings=project["net_settings"]
|
||||
default=next(c for c in settings["classes"] if c["name"]=="Default")
|
||||
default.update(clearance=0.25, track_width=0.25, via_diameter=0.7, via_drill=0.3)
|
||||
power=dict(default, name="Power", priority=0, track_width=0.5, diff_pair_width=0.25)
|
||||
settings["classes"]=[c for c in settings["classes"] if c["name"]!="Power"]+[power]
|
||||
patterns=[v for v in settings.get("netclass_patterns",[]) if v["pattern"] not in ("/+3V3","/GND")]
|
||||
settings["netclass_patterns"]=patterns+[{"netclass":"Power","pattern":name} for name in ("/+3V3","/GND")]
|
||||
path.write_text(json.dumps(project,indent=2)+"\n")
|
||||
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description=__doc__)
|
||||
@@ -108,8 +94,8 @@ def main():
|
||||
args=parser.parse_args()
|
||||
if PCB.exists() and not args.overwrite:
|
||||
parser.error("PCB exists; preserve manual edits or explicitly use --overwrite")
|
||||
if any(ROOT.glob("~serial-carrier.*.lck")):
|
||||
parser.error("Project is open in KiCad; close it before regenerating PCB/settings")
|
||||
if ROOT.joinpath("~serial-carrier.kicad_pcb.lck").exists():
|
||||
parser.error("PCB is open in KiCad; close it before regenerating")
|
||||
netfile=ROOT / "validation/pcb-source-netlist.xml"
|
||||
subprocess.run(["kicad-cli","sch","export","netlist","--format","kicadxml","-o",str(netfile),str(ROOT/"serial-carrier.kicad_sch")],check=True)
|
||||
source=ET.parse(netfile).getroot()
|
||||
@@ -126,11 +112,11 @@ def main():
|
||||
settings.m_HoleClearance=mm(0.25)
|
||||
settings.m_TrackMinWidth=mm(0.25)
|
||||
settings.m_ViasMinSize=mm(0.7)
|
||||
# SaveBoard persists these proposed draft rules into the project settings.
|
||||
# Proposed in-memory draft rules only; never overwrite the user's project settings.
|
||||
title=board.GetTitleBlock()
|
||||
title.SetTitle("Serial Swiss Army Knife - 82x80 mm carrier draft")
|
||||
title.SetRevision("A0 DRAFT")
|
||||
title.SetComment(0,"NOT FOR FABRICATION - male module / USB / antenna geometry unverified")
|
||||
title.SetRevision("A1 DRAFT")
|
||||
title.SetComment(0,"NOT FOR FABRICATION - USB / antenna geometry pending; verify module pinout / assembly")
|
||||
pad_nets={}
|
||||
for source_net in source.findall("nets/net"):
|
||||
net=p.NETINFO_ITEM(board,source_net.get("name"))
|
||||
@@ -155,9 +141,9 @@ def main():
|
||||
quiet_footprint(fp)
|
||||
copy_schematic_fields(fp,comp)
|
||||
if ref=="U1":
|
||||
fp.Reference().SetPosition(xy(20.7,25))
|
||||
fp.Reference().SetPosition(xy(20.7,44))
|
||||
elif ref=="U2":
|
||||
fp.Reference().SetPosition(xy(56.97,18))
|
||||
fp.Reference().SetPosition(xy(56.97,14.670))
|
||||
elif ref=="DS1":
|
||||
fp.Reference().SetPosition(xy(56,49))
|
||||
elif ref.startswith("SW"):
|
||||
@@ -199,23 +185,22 @@ def main():
|
||||
reserve.SetDoNotAllowFootprints(False) # the module itself spans this region
|
||||
polygon(reserve,RF_RESERVE)
|
||||
board.Add(reserve)
|
||||
text(board,"RF RESERVE",20.5,7,p.Dwgs_User,1.2)
|
||||
text(board,"VERIFY ANTENNA",20.5,10,p.Dwgs_User,1)
|
||||
text(board,"ESP32 HW678",20.7,29)
|
||||
text(board,"USB ACCESS - VERIFY",18,64,p.Dwgs_User,1)
|
||||
text(board,"DE-9 OUT / VERIFY MALE",56.97,1.5,p.Dwgs_User,1)
|
||||
text(board,"MAX3243 REF",56.97,21)
|
||||
text(board,"RF RESERVE",20.5,26,p.Dwgs_User,1.2)
|
||||
text(board,"VERIFY ANTENNA",20.5,29,p.Dwgs_User,1)
|
||||
text(board,"ESP32 HW678",20.7,48)
|
||||
text(board,"USB ACCESS - VERIFY",18,83,p.Dwgs_User,1)
|
||||
text(board,"DE-9 OUT / NOMINAL MALE",56.97,-1.830,p.Dwgs_User,1)
|
||||
text(board,"MAX3243 REF",56.97,17.670)
|
||||
text(board,"26mm OLED",56,52)
|
||||
for label,x in zip(("BACK","SELECT","NEXT"),(46.25,58.25,70.25)):
|
||||
text(board,label,x,75, size=0.9)
|
||||
text(board,"DRAFT - NOT FOR FAB",56,78,size=0.9)
|
||||
text(board,"82 x 80 mm / 2 copper layers",41,-5,p.Dwgs_User,1.5)
|
||||
text(board,"Carrier envelope only; module overhang and connector clearance not qualified",41,-2.5,p.Dwgs_User,0.9)
|
||||
text(board,"Nominal RS-232 metal verified; ESP32 USB / antenna / assembly clearance pending",41,-2.5,p.Dwgs_User,0.9)
|
||||
board.BuildConnectivity()
|
||||
p.SaveBoard(str(PCB),board)
|
||||
configure_draft_netclasses()
|
||||
assert p.SaveBoard(str(PCB),board,True)
|
||||
print("Created",PCB,"with 9 circuit footprints, 4 proposed M3 mounts, 2 copper layers.")
|
||||
print("Placement draft only. Board-only U2 footprint is a female-CAD reference, not male qualification.")
|
||||
print("Placement draft only. U2 nominal male external metal dimensions user-confirmed; pin map / assembly still require qualification.")
|
||||
|
||||
if __name__=="__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user