#!/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 # Stufe 2: Freerouting meldet manchmal fest verlegte 230-V-Verbindungen aus Stufe 1 als offen. # Akzeptieren, wenn nur 230-V-Netze offen sind - danach prüft KiCad die echte Konnektivität. bad = [l.split("'")[1] for l in r.stdout.splitlines() if l.strip().startswith("Net '")] if final and tag == 'lv' and bad and all(ismains(n) for n in bad) and os.path.exists(ses): print('lv: nur 230-V-Netze als offen gemeldet (fest aus Stufe 1):', bad) 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()))