Files
Ofensteuerung/pcb/gen_pcb.py
T

179 lines
9.0 KiB
Python

#!/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')