107 lines
4.5 KiB
Python
107 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Zweistufiges Routing mit Freerouting.
|
|
1) nur 230-V-Netze, eingesperrt in MAINS_AREA; CT-Sekundärpins/-Stummel mit 4 mm Abstandsklasse
|
|
2) Kleinspannung, MAINS_AREA gesperrt (230-V-Bahnen + LV-Stummel bleiben fest)
|
|
-> ofensteuerung.kicad_pcb"""
|
|
import os, sys, shutil, subprocess, pcbnew
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, HERE)
|
|
from design import PROJECT, MAINS, MAINS_AREA, W, H
|
|
src = os.path.join(HERE, PROJECT + '_unrouted.kicad_pcb')
|
|
dst = os.path.join(HERE, PROJECT + '.kicad_pcb')
|
|
shutil.copy(os.path.join(HERE, PROJECT + '.kicad_pro'), src.replace('.kicad_pcb', '.kicad_pro'))
|
|
mm = pcbnew.FromMM
|
|
JAR = os.path.expanduser('~/tools/freerouting.jar')
|
|
passes = int(sys.argv[1]) if len(sys.argv) > 1 else 30
|
|
|
|
def freeroute(b, tag, mp):
|
|
dsn, ses = f'/tmp/ofen_{tag}.dsn', f'/tmp/ofen_{tag}.ses'
|
|
if os.path.exists(ses): os.remove(ses)
|
|
assert pcbnew.ExportSpecctraDSN(b, dsn)
|
|
for attempt in range(4):
|
|
if os.path.exists(ses): os.remove(ses)
|
|
r = subprocess.run(['java', '-jar', JAR, '-de', dsn, '-do', ses, '-mp', str(mp), '-l', 'en',
|
|
'--gui.enabled=false', '--usage_and_diagnostic_data.disable_analytics=true'],
|
|
capture_output=True, text=True, timeout=1800)
|
|
final = [l for l in r.stdout.splitlines() if 'Auto-routing stage completed' in l]
|
|
print(tag, attempt, final[-1][-120:] if final else r.stdout[-1500:])
|
|
if final and '(0 unrouted' in final[-1] and os.path.exists(ses):
|
|
return ses
|
|
mp += 20
|
|
raise SystemExit(tag + ': routing unvollständig\n' + r.stdout[-3000:] + r.stderr[-2000:])
|
|
|
|
def keepout(b, pts, name):
|
|
z = pcbnew.ZONE(b); z.SetIsRuleArea(True); z.SetDoNotAllowTracks(True); z.SetDoNotAllowVias(True)
|
|
z.SetDoNotAllowZoneFills(True); z.SetDoNotAllowPads(False); z.SetDoNotAllowFootprints(False); z.SetZoneName(name)
|
|
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 pts: ol.Append(mm(x), mm(y))
|
|
b.Add(z); return z
|
|
|
|
def strip_zones(b):
|
|
zs = [z for z in b.Zones() if not z.GetIsRuleArea()]
|
|
for z in zs: b.Remove(z)
|
|
return zs
|
|
|
|
ismains = lambda n: n.lstrip('/') in MAINS or n.startswith('unconnected-(K1-')
|
|
|
|
# ---------- Stufe 1: 230 V ----------
|
|
import json
|
|
p1 = '/tmp/ofen_p1.kicad_pcb'
|
|
shutil.copy(src, p1)
|
|
pro = json.load(open(os.path.join(HERE, PROJECT + '.kicad_pro')))
|
|
nsj = pro['net_settings']
|
|
d = dict([c for c in nsj['classes'] if c['name'] == 'Default'][0]); d.update(name='Iso', clearance=4.0, priority=0)
|
|
nsj['classes'].append(d)
|
|
nsj['netclass_patterns'].append({'netclass': 'Iso', 'pattern': 'ISO_*'})
|
|
json.dump(pro, open(p1.replace('.kicad_pcb', '.kicad_pro'), 'w'), indent=2)
|
|
b1 = pcbnew.LoadBoard(p1)
|
|
_zs1 = strip_zones(b1)
|
|
_ra1 = [z for z in b1.Zones() if z.GetZoneName() == 'NETZ_230V']
|
|
for z in _ra1:
|
|
b1.Remove(z) # reine "keine Fläche"-Zone wird im DSN als Sperrfläche exportiert
|
|
newnets = {}
|
|
def mknet(name):
|
|
if name not in newnets:
|
|
n = pcbnew.NETINFO_ITEM(b1, name); b1.Add(n); newnets[name] = n
|
|
return newnets[name]
|
|
for nm in ('ISO_VMID', 'ISO_CT_A', 'COIL_P5V', 'COIL_RELAY_C'):
|
|
mknet(nm)
|
|
for fp in list(b1.GetFootprints()):
|
|
for pd in list(fp.Pads()):
|
|
n = pd.GetNetname()
|
|
if not n or ismains(n):
|
|
continue
|
|
if fp.GetReference() == 'T1':
|
|
pd.SetNet(mknet('ISO_' + n.lstrip('/')))
|
|
elif fp.GetReference() == 'K1':
|
|
pd.SetNet(mknet('COIL_' + n.lstrip('/').replace('+', 'P')))
|
|
else:
|
|
pd.SetNet(b1.FindNet('')) # Hindernis ohne Netz
|
|
_tr1 = list(b1.GetTracks())
|
|
for t in _tr1:
|
|
b1.Remove(t) # LV-Stummel in Stufe 1 weglassen (würden die Sperrflächen verletzen)
|
|
b1.SynchronizeNetsAndNetClasses(True)
|
|
x1, y1 = MAINS_AREA[2]
|
|
keepout(b1, [(x1, 0), (W, 0), (W, H), (x1, H)], 'NUR_LV_R')
|
|
keepout(b1, [(0, y1), (x1, y1), (x1, H), (0, H)], 'NUR_LV_U')
|
|
ses1 = freeroute(b1, 'netz', passes)
|
|
|
|
# ---------- Stufe 2: Kleinspannung ----------
|
|
b = pcbnew.LoadBoard(src)
|
|
zones = strip_zones(b)
|
|
assert pcbnew.ImportSpecctraSES(b, ses1)
|
|
for t in b.GetTracks():
|
|
if ismains(t.GetNetname()):
|
|
t.SetLocked(True)
|
|
ko = keepout(b, MAINS_AREA, 'ROUTE_KEEPOUT')
|
|
ses2 = freeroute(b, 'lv', passes)
|
|
assert pcbnew.ImportSpecctraSES(b, ses2)
|
|
b.Remove(ko)
|
|
for z in zones: b.Add(z)
|
|
pcbnew.ZONE_FILLER(b).Fill(b.Zones())
|
|
b.BuildConnectivity()
|
|
print('unconnected after fill:', b.GetConnectivity().GetUnconnectedCount(True))
|
|
pcbnew.SaveBoard(dst, b)
|
|
print('saved', dst, 'tracks', len(b.GetTracks()))
|