#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ export_csv.py - Erzeugt CSV aus den von LISP gesammelten Block-Daten. Aufruf: python export_csv.py Argumente: export_raw.json - JSON-Datei mit allen INSERT-Entities aus der Zeichnung data_dir - Pfad zum data/-Verzeichnis (mit json/ Unterordner) output.csv - Ziel-CSV-Datei """ import json import sys import os def load_json(path): with open(path, "r", encoding="utf-8") as f: return json.load(f) def build_lookup(boegen, weichen): """Erzeugt ein Dict: SivasNr (als String) -> (typ, eintrag)""" lookup = {} for b in boegen: key = str(b["Sivasnr"]) lookup[key] = ("bogen", b) for w in weichen: key = str(w["Sivasnr"]) lookup[key] = ("weiche", w) return lookup def classify_weiche(eintrag): """Bestimmt den Weichen-Subtyp fuer die Omniflo Sum Zaehlung.""" wt = eintrag.get("WeichenTyp", "") profil = eintrag.get("ProfilTyp", "") if "WEICHENKOERPER" in profil.upper(): return "weichenkoerper" if wt == "Einzelweiche": return "einzelweiche" if wt == "Doppelweiche": return "doppelweiche" if wt == "Dreiwegeweiche": return "dreiwegeweiche" if wt == "Deltaweiche" or "DELTA" in profil.upper(): return "deltaweiche" if wt == "Dreifachweiche": return "deltaweiche" if wt == "Sternweiche" or "STAR" in profil.upper(): return "sternweiche" return "einzelweiche" def get_hoehe(block): """Liest HOEHE aus Block-Attributen. Fallback: Z-Koordinate.""" attribs = block.get("attribs", {}) return attribs.get("HOEHE") or str(int(block.get("z", 0) or 0)) or "2000" def get_drehung(block): """Liest DREHUNG aus Block-Attributen. Fallback: CAD-Rotation.""" attribs = block.get("attribs", {}) drehung_raw = attribs.get("DREHUNG") if drehung_raw is not None: try: return float(drehung_raw) except (ValueError, TypeError): pass return block.get("rotation", 0.0) def build_bogen_merkmale(block, eintrag): """Merkmale-JSON fuer einen Omniflo Bogen.""" return { "Kurvenwinkel": eintrag.get("KurvenWinkel", 0), "Radius": eintrag.get("Radius", 0), "Höhe": get_hoehe(block), "Drehung": get_drehung(block), "SivasNummer": str(eintrag.get("Sivasnr", "")) } def build_weiche_merkmale(block, eintrag): """Merkmale-JSON fuer eine Omniflo Weiche.""" wt = eintrag.get("WeichenTyp", "Einzelweiche") return { "Weichentyp": wt, "Richtung": str(eintrag.get("KurvenRichtung", "")), "Weichenwinkel": eintrag.get("KurvenWinkel", 0), "Höhe": get_hoehe(block), "Drehung": get_drehung(block), "Antrieb Kurve": eintrag.get("SivasnrTEF") is not None, "SivasNummer": str(eintrag.get("Sivasnr", "")) } def build_gerade_merkmale(block): """Merkmale-JSON fuer eine Omniflo Gerade (AP110).""" return { "Anzahl der Separatoren": "0", "Länge in Meter": "2", "Winkel": "0", "Anzahl der Scanner": 0, "Höhe oben": "2000", "Höhe unten": "2000", "Drehung": block.get("rotation", 0.0), "SivasNummer": "" } def build_kreisel_merkmale(block): """Merkmale-JSON fuer einen ILS Kreisel (liest Attribute aus Block).""" attribs = block.get("attribs", {}) abstand_mm = attribs.get("ABSTAND", "2300") try: abstand_m = str(round(float(abstand_mm) / 1000.0, 2)) except (ValueError, TypeError): abstand_m = "2.3" hoehe_mm = attribs.get("HOEHE", "0") try: hoehe_m = str(round(float(hoehe_mm) / 1000.0, 2)) except (ValueError, TypeError): hoehe_m = "0" return { "Abstand (Kreiselachse A - Kreiselachse) in Meter": abstand_m, "Anzahl der Separatoren": attribs.get("N_SEPARATOREN", "2"), "Kreiselart": attribs.get("KREISELART", "STANDARD"), "Anzahl der Scanner": attribs.get("N_SCANNER", "0"), "Anzahl der Rampen": attribs.get("N_RAMPEN", "0"), "Höhe in m": hoehe_m, "Drehrichtung": attribs.get("DREHRICHTUNG", "UZS"), "Drehung": block.get("rotation", 0.0), "Name": attribs.get("NAME", ""), } # --- Bekannte Blocknamen fuer nicht-Omniflo Elemente --- ## Kreisel-Bloecke werden ueber Praefix "KR_" erkannt (siehe process_blocks) VARIO_BLOCKS = { "AUS_Element_links", "EIN_Element_links", "Staustrecke_SP_1000_mm", "Staustrecke_Separator_SP_300_mm", "Vario_Spannstation_SP_500mm", "Vario_Motorstation_SP_500mm", } # Koordinatensystem-Bloecke (ignorieren) SKIP_BLOCKS = {"K1", "K2", "K3", "K4", "KS_EIN", "KS_AUS"} def process_blocks(blocks, lookup): """ Verarbeitet alle Bloecke und erzeugt: - items: Liste der CSV-Einzelzeilen - counters: Zaehler fuer Summierungszeilen """ items = [] counters = { # Omniflo Sum "anzahl_boegen": 0, "anzahl_weichenkoerper": 0, "anzahl_einzelweichen": 0, "anzahl_deltaweichen": 0, "anzahl_doppel_dreiwegeweichen": 0, "anzahl_sternweichen": 0, "gesamtlaenge_ap110": 0.0, "gesamtlaenge_ap60": 0.0, # ILS Automation "anzahl_kreisel": 0, "anzahl_kreisel_pinband": 0, "sep_kreisel_pin": 0, "sep_kreisel_ohne_pin": 0, "anzahl_verbindungskreisel": 0, "laengen_kreisel": 0.0, "anzahl_weichen_ils": 0, "anzahl_eckraeder": 0, "anzahl_gefaellestrecken": 0, "anzahl_variofoerderer": 0, "anzahl_streckengruppen": 0, "laengen_gefaellestrecken": 0.0, "anzahl_motoren_vario": 0, "laengen_variofoerderer": 0.0, "anzahl_etf": 0, "laengen_etf": 0.0, "anzahl_separatoren": 0, "anzahl_scanner": 0, "anzahl_btbeladung": 0, "anzahl_abwurfstationen": 0, } elem_nr = 0 bogen_count = 0 weiche_count = {} gerade_count = 0 kreisel_count = 0 for block in blocks: bname = block.get("block_name", "") # Koordinatensystem-Bloecke ueberspringen if bname in SKIP_BLOCKS: continue # Omniflo Bogen oder Weiche? if bname in lookup: typ, eintrag = lookup[bname] elem_nr += 1 shape_id = block.get("attribs", {}).get("ID", "0000") if typ == "bogen": bogen_count += 1 items.append({ "nr": elem_nr, "teileart": "Omniflo Kurve", "teileid": shape_id, "bezeichnung": f"OFBogen :{bogen_count}", "anzahl": 1, "merkmale": build_bogen_merkmale(block, eintrag) }) counters["anzahl_boegen"] += 1 elif typ == "weiche": subtyp = classify_weiche(eintrag) wt = eintrag.get("WeichenTyp", "Einzelweiche") weiche_count[wt] = weiche_count.get(wt, 0) + 1 items.append({ "nr": elem_nr, "teileart": "Omniflo Weiche", "teileid": shape_id, "bezeichnung": f"OFWeiche :{weiche_count[wt]}", "anzahl": 1, "merkmale": build_weiche_merkmale(block, eintrag) }) if subtyp == "einzelweiche": counters["anzahl_einzelweichen"] += 1 elif subtyp == "doppelweiche" or subtyp == "dreiwegeweiche": counters["anzahl_doppel_dreiwegeweichen"] += 1 elif subtyp == "deltaweiche": counters["anzahl_deltaweichen"] += 1 elif subtyp == "sternweiche": counters["anzahl_sternweichen"] += 1 elif subtyp == "weichenkoerper": counters["anzahl_weichenkoerper"] += 1 continue # Omniflo Gerade (AP110) if "AP110" in bname.upper() or "AP_110" in bname.upper(): elem_nr += 1 gerade_count += 1 items.append({ "nr": elem_nr, "teileart": "Omniflo Gerade", "teileid": block.get("attribs", {}).get("ID", "0000"), "bezeichnung": f"OFGerade :{gerade_count}", "anzahl": 1, "merkmale": build_gerade_merkmale(block) }) counters["gesamtlaenge_ap110"] += 2000.0 continue # ILS Kreisel (Compound-Bloecke mit Praefix KR_) if bname.startswith("KR_"): elem_nr += 1 kreisel_count += 1 items.append({ "nr": elem_nr, "teileart": "ILS 2.0 Kreisel", "teileid": block.get("attribs", {}).get("ID", "0000"), "bezeichnung": f"Kreisel :{kreisel_count}", "anzahl": 1, "merkmale": build_kreisel_merkmale(block) }) attribs = block.get("attribs", {}) counters["anzahl_kreisel"] += 1 n_sep = int(attribs.get("N_SEPARATOREN", "2") or "2") kreiselart = attribs.get("KREISELART", "STANDARD") if kreiselart == "PIN": counters["anzahl_kreisel_pinband"] += 1 counters["sep_kreisel_pin"] += n_sep else: counters["sep_kreisel_ohne_pin"] += n_sep abstand_mm = float(attribs.get("ABSTAND", "2300") or "2300") counters["laengen_kreisel"] += abstand_mm / 1000.0 counters["anzahl_separatoren"] += n_sep counters["anzahl_scanner"] += int(attribs.get("N_SCANNER", "0") or "0") if counters["anzahl_streckengruppen"] == 0: counters["anzahl_streckengruppen"] = 1 continue # VarioFoerderer-Bloecke is_vario = False for vb in VARIO_BLOCKS: if vb in bname: is_vario = True break if is_vario: counters["anzahl_variofoerderer"] += 1 continue return items, counters def build_ils_automation_row(elem_nr, counters): """Erzeugt die ILS 2.0 Automation Summierungszeile.""" merkmale = { "Anzahl Kreisel": counters["anzahl_kreisel"], "Anzahl Kreisel mit Pinband": counters["anzahl_kreisel_pinband"], "Anzahl aller Separatoren in Kreisel mit PIN": counters["sep_kreisel_pin"], "Anzahl aller Separatoren in Kreisel ohne PIN": counters["sep_kreisel_ohne_pin"], "Anzahl Verbindungskreisel": counters["anzahl_verbindungskreisel"], "Längen aller Kreisel": counters["laengen_kreisel"], "Anzahl der Weichen": counters["anzahl_weichen_ils"], "Anzahl der Eckräder": counters["anzahl_eckraeder"], "Anzahl der einzelnen Gefällestrecken": counters["anzahl_gefaellestrecken"], "Anzahl der einzelnen Varioförderer": counters["anzahl_variofoerderer"], "Anzahl der Streckengruppen": counters["anzahl_streckengruppen"], "Längen aller Gefällestrecken": counters["laengen_gefaellestrecken"], "Anzahl der Motoren Varioförderer": counters["anzahl_motoren_vario"], "Längen aller Varioförderer": counters["laengen_variofoerderer"], "Anzahl ETFs": counters["anzahl_etf"], "Längen aller ETFs": counters["laengen_etf"], "Anzahl aller Separatoren": counters["anzahl_separatoren"], "Anzahl aller Scanner": counters["anzahl_scanner"], "Anzahl aller BTBeladung": counters["anzahl_btbeladung"], "Anzahl aller Abwurfstationen": counters["anzahl_abwurfstationen"], } return { "nr": elem_nr, "teileart": "ILS 2.0 Automation", "teileid": "autogenerated_ils_automation_json", "bezeichnung": "Automation ILS structgen", "anzahl": 1, "merkmale": merkmale, } def build_omniflo_sum_row(elem_nr, counters): """Erzeugt die Omniflo Sum Summierungszeile.""" merkmale = { "Anzahl Bögen": counters["anzahl_boegen"], "Anzahl Weichengrundkörper": counters["anzahl_weichenkoerper"], "Anzahl Einwegweichen": counters["anzahl_einzelweichen"], "Anzahl Deltaweichen": counters["anzahl_deltaweichen"], "Anzahl Doppelweichen und Dreiwegeweichen": counters["anzahl_doppel_dreiwegeweichen"], "Anzahl Sternweichen": counters["anzahl_sternweichen"], "Gesamtlänge AP110": counters["gesamtlaenge_ap110"], "Gesamtlänge AP60": counters["gesamtlaenge_ap60"], } return { "nr": elem_nr, "teileart": "Omniflo Sum", "teileid": "autogenerated_of_json", "bezeichnung": "Omniflo sum", "anzahl": 1, "merkmale": merkmale, } def format_csv_line(item): """Formatiert eine CSV-Zeile im geforderten Format.""" merkmale_json = json.dumps(item["merkmale"], ensure_ascii=False) return '{nr};"{teileart}";"{teileid}";"{bezeichnung}";{anzahl};{merkmale}'.format( nr=item["nr"], teileart=item["teileart"], teileid=item["teileid"], bezeichnung=item["bezeichnung"], anzahl=item["anzahl"], merkmale=merkmale_json, ) def main(): if len(sys.argv) < 4: print("Aufruf: python export_csv.py ") sys.exit(1) raw_json_path = sys.argv[1] data_dir = sys.argv[2] output_csv = sys.argv[3] # Daten laden blocks = load_json(raw_json_path) boegen_path = os.path.join(data_dir, "json", "omniflo_boegen.json") weichen_path = os.path.join(data_dir, "json", "omniflo_weichen.json") boegen = load_json(boegen_path) if os.path.exists(boegen_path) else [] weichen = load_json(weichen_path) if os.path.exists(weichen_path) else [] lookup = build_lookup(boegen, weichen) print(f"[export_csv] {len(blocks)} Bloecke geladen, " f"{len(boegen)} Boegen, {len(weichen)} Weichen im Katalog.") # Bloecke verarbeiten items, counters = process_blocks(blocks, lookup) # Summierungszeilen anhaengen next_nr = (items[-1]["nr"] + 1) if items else 1 ils_row = build_ils_automation_row(next_nr, counters) items.append(ils_row) of_row = build_omniflo_sum_row(next_nr + 1, counters) items.append(of_row) # CSV schreiben header = "Elementnummer;TeileArt;TeileId;Bezeichnung;Anzahl;Merkmale" with open(output_csv, "w", encoding="utf-8") as f: f.write(header + "\n") for item in items: f.write(format_csv_line(item) + "\n") print(f"[export_csv] CSV geschrieben: {output_csv} ({len(items)} Zeilen)") if __name__ == "__main__": main()