#!/usr/bin/env python3 """Platzierte Platine (ohne Leiterbahnen) aus design.py erzeugen -> ofensteuerung_unrouted.kicad_pcb""" import os, sys, json import pcbnew HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) from design import PARTS, PROJECT, W, H, MAINS, MAINS_AREA REFPOS = {'A1': (81.4, 20.0)} FPDIR = '/usr/share/kicad/footprints' mm = pcbnew.FromMM def V(x, y): return pcbnew.VECTOR2I(mm(x), mm(y)) symuuids = json.load(open(os.path.join(HERE, 'symuuids.json'))) board = pcbnew.LoadBoard(os.path.join(HERE, PROJECT + '_template.kicad_pcb')) if False else pcbnew.CreateEmptyBoard() board.SetFileName(os.path.join(HERE, PROJECT + '.kicad_pcb')) ds = board.GetDesignSettings() ds.SetCopperLayerCount(2) ns = ds.m_NetSettings dnc = ns.GetDefaultNetclass() dnc.SetTrackWidth(mm(0.4)); dnc.SetClearance(mm(0.25)); dnc.SetViaDiameter(mm(0.8)); dnc.SetViaDrill(mm(0.4)) pnc = pcbnew.NETCLASS('Power') pnc.SetTrackWidth(mm(0.8)); pnc.SetClearance(mm(0.3)); pnc.SetViaDiameter(mm(1.0)); pnc.SetViaDrill(mm(0.5)); pnc.SetPriority(0) ns.SetNetclass('Power', pnc) pnc.SetTrackWidth(mm(1.0)) mnc = pcbnew.NETCLASS('Mains') mnc.SetTrackWidth(mm(2.0)); mnc.SetClearance(mm(2.5)); mnc.SetViaDiameter(mm(2.2)); mnc.SetViaDrill(mm(1.0)); mnc.SetPriority(0) ns.SetNetclass('Mains', mnc) for pat in ('GND', '+5V', '+3V3'): ns.SetNetclassPatternAssignment(pat, 'Power') for n in MAINS: ns.SetNetclassPatternAssignment('/' + n, 'Mains') ns.SetNetclassPatternAssignment('unconnected-(K1-*', 'Mains') ds.m_TrackMinWidth = mm(0.25); ds.m_MinClearance = mm(0.2); ds.m_CopperEdgeClearance = mm(0.4) nets = {} POWER = ('GND', '+5V', '+3V3') def net(name): name = name if (name in POWER or name.startswith('unconnected-')) else '/' + name if name not in nets: n = pcbnew.NETINFO_ITEM(board, name); board.Add(n); nets[name] = n return nets[name] for p in PARTS: if p.get('board') is False or not p['fp']: continue lib, name = p['fp'].split(':') libpath = os.path.join(HERE, lib + '.pretty') if lib == PROJECT else os.path.join(FPDIR, lib + '.pretty') fp = pcbnew.FootprintLoad(libpath, name) if fp is None: raise SystemExit('fp missing ' + p['fp']) fp.SetFPID(pcbnew.LIB_ID(lib, name)) fp.SetReference(p['ref']); fp.SetValue(p['val']) fp.SetPath(pcbnew.KIID_PATH('/' + symuuids[p['ref']])) x, y, rot = p['pcb'] fp.SetPosition(V(x, y)) fp.SetOrientationDegrees(rot) board.Add(fp) for pad in fp.Pads(): n = p['pins'].get(pad.GetNumber()) if n: pad.SetNet(net(n)) elif pad.GetNumber() and pad.GetNumber() in p['pins']: nm = p.get('ncname', {}).get(pad.GetNumber(), '') pad.SetNet(net(f"unconnected-({p['ref']}-{nm + '-' if nm else ''}Pad{pad.GetNumber()})")) for k in ('Description', 'Bezugsquelle'): v = p.get({'Description': 'desc', 'Bezugsquelle': 'mpn'}[k], '') if k == 'Description' or v: try: fld = fp.GetField(k) if fp.HasField(k) else None if fld is None: fld = pcbnew.PCB_FIELD(fp, fp.GetNextFieldId() if hasattr(fp,'GetNextFieldId') else -1, k); fp.Add(fld) fld.SetText(v); fld.SetVisible(False); fld.SetLayer(pcbnew.F_Fab) except Exception as e: print('field', k, e) # Referenz klein, Value auf Fab fp.Reference().SetTextSize(V(0.9, 0.9)); fp.Reference().SetTextThickness(mm(0.14)) fp.Value().SetLayer(pcbnew.F_Fab) if p['fp'].split(':')[0] in ('Resistor_THT', 'Diode_THT') or p['fp'].startswith('Capacitor_THT:C_Disc'): pads = list(fp.Pads()) a, b2 = pads[0].GetPosition(), pads[1].GetPosition() fp.Reference().SetPosition(pcbnew.VECTOR2I((a.x + b2.x) // 2, (a.y + b2.y) // 2)) fp.Reference().SetTextAngleDegrees(rot if rot in (0, 90) else rot - 180) fp.Reference().SetTextSize(V(0.8, 0.8)); fp.Reference().SetTextThickness(mm(0.13)) if p['ref'] in REFPOS: fp.Reference().SetPosition(V(*REFPOS[p['ref']])); fp.Reference().SetTextAngleDegrees(0) if p['ref'] in ('J10',): fp.Reference().SetVisible(False) if p['fp'].startswith('Connector_Phoenix'): npins = len(p['pins']); sgn = 1 if rot == 0 else -1 fp.Reference().SetPosition(V(x + sgn * 5.08 * (npins - 1) + sgn * 2.6, y + (-2.0 if rot == 180 else 2.0))) fp.Reference().SetTextAngleDegrees(0) # Platinenumriss mit abgerundeten Ecken def seg(a, b): s = pcbnew.PCB_SHAPE(board); s.SetShape(pcbnew.SHAPE_T_SEGMENT); s.SetStart(V(*a)); s.SetEnd(V(*b)) s.SetLayer(pcbnew.Edge_Cuts); s.SetWidth(mm(0.1)); board.Add(s) def arc(c, start, ang): s = pcbnew.PCB_SHAPE(board); s.SetShape(pcbnew.SHAPE_T_ARC); s.SetCenter(V(*c)); s.SetStart(V(*start)) s.SetArcAngleAndEnd(pcbnew.EDA_ANGLE(ang, pcbnew.DEGREES_T)); s.SetLayer(pcbnew.Edge_Cuts); s.SetWidth(mm(0.1)); board.Add(s) r = 2.0 seg((r, 0), (W - r, 0)); seg((W, r), (W, H - r)); seg((W - r, H), (r, H)); seg((0, H - r), (0, r)) arc((W - r, r), (W - r, 0), 90); arc((W - r, H - r), (W, H - r), 90); arc((r, H - r), (r, H), 90); arc((r, r), (0, r), 90) # GND-Flächen beidseitig def zone(layer, netname, pts, keepout=False): z = pcbnew.ZONE(board) z.SetLayer(layer) if keepout: z.SetIsRuleArea(True); z.SetDoNotAllowTracks(True); z.SetDoNotAllowVias(True); z.SetDoNotAllowZoneFills(True) z.SetDoNotAllowPads(False); z.SetDoNotAllowFootprints(False) ls = pcbnew.LSET(); ls.AddLayer(pcbnew.F_Cu); ls.AddLayer(pcbnew.B_Cu); z.SetLayerSet(ls) else: z.SetNet(net(netname)); z.SetMinThickness(mm(0.25)); z.SetLocalClearance(mm(0.4)) z.SetPadConnection(pcbnew.ZONE_CONNECTION_THERMAL); z.SetThermalReliefGap(mm(0.5)); z.SetThermalReliefSpokeWidth(mm(0.6)) z.SetAssignedPriority(0) ol = z.Outline(); ol.NewOutline() for (x, y) in pts: ol.Append(mm(x), mm(y)) board.Add(z) return z for L in (pcbnew.F_Cu, pcbnew.B_Cu): zone(L, 'GND', [(0.5, 0.5), (W - 0.5, 0.5), (W - 0.5, H - 0.5), (0.5, H - 0.5)]) # Antennen-Freifläche unter dem ESP8266-Modul (Antenne zeigt zum oberen Rand) zone(pcbnew.F_Cu, None, [(68.6, 1.0), (94.2, 1.0), (94.2, 13.0), (68.6, 13.0)], keepout=True) # 230-V-Bereich: keine GND-Fläche (beide Lagen) z = pcbnew.ZONE(board); z.SetIsRuleArea(True); z.SetDoNotAllowZoneFills(True); z.SetDoNotAllowTracks(False); z.SetDoNotAllowVias(False) z.SetDoNotAllowPads(False); z.SetDoNotAllowFootprints(False); z.SetZoneName('NETZ_230V') ls = pcbnew.LSET(); ls.AddLayer(pcbnew.F_Cu); ls.AddLayer(pcbnew.B_Cu); z.SetLayerSet(ls) ol = z.Outline(); ol.NewOutline() for (x, y) in MAINS_AREA: ol.Append(mm(x), mm(y)) board.Add(z) # Kleinspannungs-Stummel aus dem 230-V-Bereich (CT-Sekundärseite, Relaisspule), gesperrt def padpos(ref, num): for fp in board.GetFootprints(): if fp.GetReference() == ref: for pd in fp.Pads(): if pd.GetNumber() == num: p = pd.GetPosition(); return (pcbnew.ToMM(p.x), pcbnew.ToMM(p.y)) raise KeyError((ref, num)) def track(netname, pts, width): for a, b in zip(pts, pts[1:]): t = pcbnew.PCB_TRACK(board); t.SetStart(V(*a)); t.SetEnd(V(*b)); t.SetWidth(mm(width)); t.SetLayer(pcbnew.F_Cu) t.SetNet(net(netname)); t.SetLocked(True); board.Add(t) YO = MAINS_AREA[2][1] + 1.0 vm = padpos('T1', '2'); ca = padpos('T1', '1'); k5 = padpos('K1', '5'); k2 = padpos('K1', '2') track('VMID', [vm, (vm[0] - 2.0, vm[1] + 2.0), (vm[0] - 2.0, YO)], 0.4) track('CT_A', [ca, (ca[0] + 2.0, ca[1] + 2.0), (ca[0] + 2.0, YO)], 0.4) track('+5V', [k5, (k5[0] - 1.5, k5[1] + 1.5), (k5[0] - 1.5, YO)], 1.0) track('RELAY_C', [k2, (k2[0] + 1.5, k2[1] + 1.5), (k2[0] + 1.5, YO)], 0.6) # Trennlinie 230 V auf dem Bestückungsdruck for a, b in (((MAINS_AREA[1][0], 1.5), MAINS_AREA[2]), (MAINS_AREA[2], (1.5, MAINS_AREA[2][1]))): s = pcbnew.PCB_SHAPE(board); s.SetShape(pcbnew.SHAPE_T_SEGMENT); s.SetStart(V(*a)); s.SetEnd(V(*b)) s.SetLayer(pcbnew.F_SilkS); s.SetWidth(mm(0.3)); board.Add(s) # Randstreifen: keine Leiterbahnen näher als 0,8 mm am Rand for pts in ([(0, 0), (W, 0), (W, 0.8), (0, 0.8)], [(0, H - 0.8), (W, H - 0.8), (W, H), (0, H)], [(0, 0), (0.8, 0), (0.8, H), (0, H)], [(W - 0.8, 0), (W, 0), (W, H), (W - 0.8, H)]): z = zone(pcbnew.F_Cu, None, pts, keepout=True); z.SetDoNotAllowZoneFills(False) # Beschriftung def text(t, x, y, size=1.2, layer='F.SilkS', rot=0, bold=False): layer = {'F.SilkS': pcbnew.F_SilkS, 'B.SilkS': pcbnew.B_SilkS}[layer] s = pcbnew.PCB_TEXT(board); s.SetText(t) if layer == pcbnew.B_SilkS: s.SetMirrored(True) s.SetPosition(V(x, y)); s.SetLayer(layer) s.SetTextSize(V(size, size)); s.SetTextThickness(mm(size * (0.2 if bold else 0.15))); s.SetTextAngleDegrees(rot) board.Add(s) from design import SILK for args in SILK: text(*args) ns.RecomputeEffectiveNetclasses() if hasattr(ns,'RecomputeEffectiveNetclasses') else None board.SynchronizeNetsAndNetClasses(True) pcbnew.SaveBoard(os.path.join(HERE, PROJECT + '_unrouted.kicad_pcb'), board) print('saved', len(board.GetFootprints()), 'footprints')