#!/usr/bin/env python3 """Add nominal STEP-derived metal bounding projections on Dwgs.User only. Not silkscreen, a courtyard, or a case cutout. See db9-mechanical-notes.md for source attribution, selected face subsets and the user-confirmed male/female external-geometry equivalence. No module height or case-wall thickness assumed. """ import json from pathlib import Path import uuid import pcbnew as p ROOT=Path(__file__).resolve().parent PCB=ROOT/"serial-carrier.kicad_pcb" NS=uuid.UUID("ba7274a8-c35d-46bc-908d-cd86708fc6c4") def main(): lock=ROOT/"~serial-carrier.kicad_pcb.lck" if lock.exists(): raise SystemExit("Close PCB Editor before refreshing the CAD envelopes") before=PCB.read_bytes() project=ROOT/"serial-carrier.kicad_pro" project_before=project.read_bytes() board=p.LoadBoard(str(PCB)) u2=next(f for f in board.GetFootprints() if f.GetReference()=="U2") assert u2.GetOrientationDegrees()==0, "Review transform for rotated module" x0,y0=p.ToMM(u2.GetPosition().x),p.ToMM(u2.GetPosition().y) data=json.loads((ROOT/"reference/db9-cad/measurements.json").read_text()) owned={str(uuid.uuid5(NS,str(i))) for i in range(64)} for drawing in list(board.GetDrawings()): if drawing.m_Uuid.AsString() in owned: board.Delete(drawing) count=0 def point(x,y): # STEP/Eagle axes +Y toward connector; KiCad local Y is downward. return p.VECTOR2I(p.FromMM(x0+x-1.905),p.FromMM(y0+2.667-y)) def add(item): nonlocal count assert count<64 item.SetLayer(p.Dwgs_User) item.SetUuid(p.KIID(str(uuid.uuid5(NS,str(count))))) board.Add(item) count+=1 for feature in ("front_flange_exposed_faces", "shell_outer_including_root_and_lip", "left_front_hex_post", "right_front_hex_post"): xmin,ymin,_,xmax,ymax,_=data[feature]["bounds"] rect=p.PCB_SHAPE(board) rect.SetShape(p.SHAPE_T_RECT) rect.SetStart(point(xmin,ymax)); rect.SetEnd(point(xmax,ymin)) rect.SetWidth(p.FromMM(0.1)) add(rect) for axis in data["front_lock_axes"]: x=axis["location"][0] # These mark front-facing Y axes, not holes through the carrier PCB. line=p.PCB_SHAPE(board) line.SetShape(p.SHAPE_T_SEGMENT) line.SetStart(point(x,26.907)); line.SetEnd(point(x,32.707)) line.SetWidth(p.FromMM(0.08)) add(line) label=p.PCB_TEXT(board) label.SetText("CAD METAL ENVELOPES - NOT CASE CUTOUT") label.SetPosition(p.VECTOR2I(p.FromMM(x0+13.97),p.FromMM(y0-17.67))) label.SetTextSize(p.VECTOR2I(p.FromMM(0.65),p.FromMM(0.65))) label.SetTextThickness(p.FromMM(0.1)) add(label) for drawing in board.GetDrawings(): if not isinstance(drawing,p.PCB_TEXT): continue value=drawing.GetText() if value.startswith("DE-9 OUT /"): drawing.SetPosition(p.VECTOR2I(p.FromMM(x0+13.97),p.FromMM(y0-20.67))) elif value=="82 x 80 mm / 2 copper layers": drawing.SetPosition(p.VECTOR2I(p.FromMM(141),p.FromMM(91))) elif value.startswith("Nominal RS-232 metal verified;"): drawing.SetPosition(p.VECTOR2I(p.FromMM(141),p.FromMM(93.5))) temp=ROOT/"serial-carrier-envelope-tmp.kicad_pcb" try: assert p.SaveBoard(str(temp),board,True) if lock.exists() or PCB.read_bytes()!=before: raise RuntimeError("PCB changed or reopened; refusing overwrite") assert project.read_bytes()==project_before temp.replace(PCB) finally: temp.unlink(missing_ok=True) print(f"Added {count} nominal CAD projection graphics on Dwgs.User; no copper/project changes") if __name__=="__main__": main()