diff --git a/Lisp/export.lsp b/Lisp/export.lsp index cc60bb1..7f12314 100644 --- a/Lisp/export.lsp +++ b/Lisp/export.lsp @@ -9,7 +9,11 @@ ;; --- String fuer JSON escapen (Anfuehrungszeichen/Backslash) --- +;; Konvertiert nicht-String-Werte (z.B. T, nil, Zahlen) defensiv in String. (defun csv:json-escape (s) + (if (not (= (type s) 'STR)) + (setq s (if s (vl-princ-to-string s) "")) + ) (setq s (vl-string-subst "\\\\" "\\" s)) (vl-string-subst "\\\"" "\"" s) ) diff --git a/cfg/attradd.cfg b/cfg/attradd.cfg index 21fea36..ea12980 100644 --- a/cfg/attradd.cfg +++ b/cfg/attradd.cfg @@ -64,3 +64,13 @@ SIVASNR_TEF = SivasnrTEF HOEHE = "" DREHUNG = "" + +[gerade] +ARTINR = Sivasnr +TEILEART = "Omniflo Gerade" +ID = "" +LAENGE = Laenge +LAYER = "A-2" +HOEHE = "" +DREHUNG = "" + diff --git a/lib/export_sivas.py b/lib/export_sivas.py index 6c78a6b..3242e12 100644 --- a/lib/export_sivas.py +++ b/lib/export_sivas.py @@ -1,10 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -export_csv.py - Erzeugt CSV aus den von LISP gesammelten Block-Daten. +export_sivas.py - Erzeugt Sivas-Export-CSV mit gruppierten Zeilen und Summierungszeilen. + +Neues CSV-Format: + Nr;TeileArt;SivasNummer;Dispgruppe;Anzahl;Laenge[mm];Hoehe[m];details + + - Omniflo Bogen/Weiche: gruppiert nach SivasNummer, Anzahl summiert + - Omniflo Gerade: gruppiert nach ARTINR-Attribut, Laenge[mm] summiert + - ILS 2.0 Kreisel: je Kreisel eine Zeile, details = JSON mit Parametern + - ILS 2.0 Automation: Summierungszeile (nur wenn ILS-Elemente vorhanden) Aufruf: - python export_csv.py + python export_sivas.py Argumente: export_raw.json - JSON-Datei mit allen INSERT-Entities aus der Zeichnung @@ -26,94 +34,49 @@ 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) + lookup[str(b["Sivasnr"])] = ("bogen", b) for w in weichen: - key = str(w["Sivasnr"]) - lookup[key] = ("weiche", w) + lookup[str(w["Sivasnr"])] = ("weiche", w) return lookup def classify_weiche(eintrag): - """Bestimmt den Weichen-Subtyp fuer die Omniflo Sum Zaehlung.""" + """Bestimmt den Weichen-Subtyp fuer die ILS-Automation-Zaehlung.""" wt = eintrag.get("WeichenTyp", "") profil = eintrag.get("ProfilTyp", "") if "WEICHENKOERPER" in profil.upper(): return "weichenkoerper" if wt == "Einzelweiche": return "einzelweiche" - if wt == "Doppelweiche": + if wt in ("Doppelweiche", "Dreiwegeweiche"): return "doppelweiche" - if wt == "Dreiwegeweiche": - return "dreiwegeweiche" - if wt == "Deltaweiche" or "DELTA" in profil.upper(): - return "deltaweiche" - if wt == "Dreifachweiche": + if wt in ("Deltaweiche", "Dreifachweiche") or "DELTA" in profil.upper(): return "deltaweiche" if wt == "Sternweiche" or "STAR" in profil.upper(): return "sternweiche" return "einzelweiche" -def get_hoehe(block): +def get_hoehe_mm(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" + v = attribs.get("HOEHE") or str(int(block.get("z", 0) or 0)) or "2000" + return v -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 format_hoehe_m(hoehe_mm_str): + """Konvertiert mm-String nach m mit deutschem Dezimaltrennzeichen.""" + try: + m = float(hoehe_mm_str) / 1000.0 + if m == int(m): + return str(int(m)) + return f"{m:.1f}".replace(".", ",") + except (ValueError, TypeError): + return "" -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).""" +def build_kreisel_details(block): + """Merkmale-Dict fuer einen ILS Kreisel.""" attribs = block.get("attribs", {}) abstand_mm = attribs.get("ABSTAND", "2300") try: @@ -131,15 +94,14 @@ def build_kreisel_merkmale(block): "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, + "Hoehe 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) +# --- Bekannte Bloecke ignorieren --- +SKIP_BLOCKS = {"K1", "K2", "K3", "K4", "KS_EIN", "KS_AUS"} VARIO_BLOCKS = { "AUS_Element_links", "EIN_Element_links", @@ -147,19 +109,23 @@ VARIO_BLOCKS = { "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 + Verarbeitet alle Bloecke. + + Rueckgabe: + items - Liste der CSV-Zeilen als Dicts + counters - Zaehler fuer ILS-Automation-Summierungszeile """ - items = [] + # Gruppen: sivasnr_str -> {anzahl, hoehe_mm} + bogen_groups = {} # sivasnr -> dict + weiche_groups = {} # sivasnr -> dict + # Gerade: artinr_str -> {anzahl, laenge_mm_total, hoehe_mm} + gerade_groups = {} + kreisel_list = [] # Liste von blocks + counters = { - # Omniflo Sum "anzahl_boegen": 0, "anzahl_weichenkoerper": 0, "anzahl_einzelweichen": 0, @@ -168,7 +134,6 @@ def process_blocks(blocks, lookup): "anzahl_sternweichen": 0, "gesamtlaenge_ap110": 0.0, "gesamtlaenge_ap60": 0.0, - # ILS Automation "anzahl_kreisel": 0, "anzahl_kreisel_pinband": 0, "sep_kreisel_pin": 0, @@ -191,54 +156,32 @@ def process_blocks(blocks, lookup): "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? + # --- Omniflo Bogen oder Weiche (erkannt via Katalog) --- if bname in lookup: typ, eintrag = lookup[bname] - elem_nr += 1 - shape_id = block.get("attribs", {}).get("ID", "0000") + sivasnr = str(eintrag.get("Sivasnr", bname)) + hoehe = get_hoehe_mm(block) 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) - }) + if sivasnr not in bogen_groups: + bogen_groups[sivasnr] = {"anzahl": 0, "hoehe_mm": hoehe} + bogen_groups[sivasnr]["anzahl"] += 1 counters["anzahl_boegen"] += 1 elif typ == "weiche": + if sivasnr not in weiche_groups: + weiche_groups[sivasnr] = {"anzahl": 0, "hoehe_mm": hoehe} + weiche_groups[sivasnr]["anzahl"] += 1 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": + elif subtyp in ("doppelweiche", "dreiwegeweiche"): counters["anzahl_doppel_dreiwegeweichen"] += 1 elif subtyp == "deltaweiche": counters["anzahl_deltaweichen"] += 1 @@ -246,36 +189,33 @@ def process_blocks(blocks, lookup): counters["anzahl_sternweichen"] += 1 elif subtyp == "weichenkoerper": counters["anzahl_weichenkoerper"] += 1 - continue - # Omniflo Gerade (AP110) + # --- 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 + attribs = block.get("attribs", {}) + artinr = attribs.get("ARTINR", "") + key = artinr if artinr else "AP110" + hoehe = get_hoehe_mm(block) + laenge = 2000.0 + laenge_attr = attribs.get("LAENGE") or attribs.get("A") + if laenge_attr: + try: + laenge = float(laenge_attr) + except (ValueError, TypeError): + pass + if key not in gerade_groups: + gerade_groups[key] = { + "anzahl": 0, "laenge_mm": 0.0, "hoehe_mm": hoehe, "artinr": artinr + } + gerade_groups[key]["anzahl"] += 1 + gerade_groups[key]["laenge_mm"] += laenge + counters["gesamtlaenge_ap110"] += laenge 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) - }) + # --- ILS Kreisel --- + if bname.startswith("KR_") or bname.startswith("KREISEL_"): + kreisel_list.append(block) attribs = block.get("attribs", {}) counters["anzahl_kreisel"] += 1 n_sep = int(attribs.get("N_SEPARATOREN", "2") or "2") @@ -293,98 +233,136 @@ def process_blocks(blocks, lookup): counters["anzahl_streckengruppen"] = 1 continue - # VarioFoerderer-Bloecke - is_vario = False + # --- VarioFoerderer --- for vb in VARIO_BLOCKS: if vb in bname: - is_vario = True + counters["anzahl_variofoerderer"] += 1 break - if is_vario: - counters["anzahl_variofoerderer"] += 1 - continue + + # --- Zeilen aufbauen --- + items = [] + nr = 1 + + for sivasnr, g in bogen_groups.items(): + items.append({ + "nr": nr, + "teileart": "Omniflo Bogen", + "sivasnr": sivasnr, + "sivasnr_quoted": False, + "dispgruppe": 10, + "anzahl": g["anzahl"], + "laenge_mm": "", + "hoehe_m": format_hoehe_m(g["hoehe_mm"]), + "details": {}, + }) + nr += 1 + + for sivasnr, g in weiche_groups.items(): + items.append({ + "nr": nr, + "teileart": "Omniflo Weiche", + "sivasnr": sivasnr, + "sivasnr_quoted": False, + "dispgruppe": 10, + "anzahl": g["anzahl"], + "laenge_mm": "", + "hoehe_m": format_hoehe_m(g["hoehe_mm"]), + "details": {}, + }) + nr += 1 + + for key, g in gerade_groups.items(): + items.append({ + "nr": nr, + "teileart": "Omniflo Gerade", + "sivasnr": g["artinr"], + "sivasnr_quoted": True, + "dispgruppe": 10, + "anzahl": g["anzahl"], + "laenge_mm": int(g["laenge_mm"]), + "hoehe_m": format_hoehe_m(g["hoehe_mm"]), + "details": {}, + }) + nr += 1 + + for block in kreisel_list: + items.append({ + "nr": nr, + "teileart": "ILS 2.0 Kreisel", + "sivasnr": "6200", + "sivasnr_quoted": True, + "dispgruppe": 20, + "anzahl": 1, + "laenge_mm": "", + "hoehe_m": "", + "details": build_kreisel_details(block), + }) + nr += 1 + + # ILS Automation Summierungszeile (nur wenn ILS-Elemente vorhanden) + if counters["anzahl_kreisel"] > 0 or counters["anzahl_variofoerderer"] > 0: + automation_details = { + "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"], + "Laengen aller Kreisel": counters["laengen_kreisel"], + "Anzahl der Weichen": counters["anzahl_weichen_ils"], + "Anzahl der Eckraeder": counters["anzahl_eckraeder"], + "Anzahl der einzelnen Gefaellestrecken": counters["anzahl_gefaellestrecken"], + "Anzahl der einzelnen Variofoerderer": counters["anzahl_variofoerderer"], + "Anzahl der Streckengruppen": counters["anzahl_streckengruppen"], + "Laengen aller Gefaellestrecken": counters["laengen_gefaellestrecken"], + "Anzahl der Motoren Variofoerderer": counters["anzahl_motoren_vario"], + "Laengen aller Variofoerderer": counters["laengen_variofoerderer"], + "Anzahl ETFs": counters["anzahl_etf"], + "Laengen 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"], + } + items.append({ + "nr": nr, + "teileart": "ILS 2.0 Automation", + "sivasnr": "6270", + "sivasnr_quoted": True, + "dispgruppe": 20, + "anzahl": 1, + "laenge_mm": "", + "hoehe_m": "", + "details": automation_details, + }) 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, + """Formatiert eine CSV-Zeile im neuen Sivas-Format.""" + details_json = json.dumps(item["details"], ensure_ascii=False) + sivasnr = f'"{item["sivasnr"]}"' if item["sivasnr_quoted"] else item["sivasnr"] + return ( + f'{item["nr"]}' + f';"{item["teileart"]}"' + f';{sivasnr}' + f';{item["dispgruppe"]}' + f';{item["anzahl"]}' + f';{item["laenge_mm"]}' + f';{item["hoehe_m"]}' + f';{details_json}' ) def main(): if len(sys.argv) < 4: - print("Aufruf: python export_csv.py ") + print("Aufruf: python export_sivas.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") @@ -394,29 +372,18 @@ def main(): lookup = build_lookup(boegen, weichen) - print(f"[export_csv] {len(blocks)} Bloecke geladen, " + print(f"[export_sivas] {len(blocks)} Bloecke geladen, " f"{len(boegen)} Boegen, {len(weichen)} Weichen im Katalog.") - # Bloecke verarbeiten - items, counters = process_blocks(blocks, lookup) + items, _ = 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" + header = "Nr;TeileArt;SivasNummer;Dispgruppe;Anzahl;Laenge[mm];Hoehe[m];details" 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)") + print(f"[export_sivas] CSV geschrieben: {output_csv} ({len(items)} Zeilen)") if __name__ == "__main__": diff --git a/lib/set_attributs.py b/lib/set_attributs.py index 7c4ec95..e638502 100644 --- a/lib/set_attributs.py +++ b/lib/set_attributs.py @@ -307,8 +307,13 @@ def process_all(data_dir, cfg_path, results_dir, single_nr=None, dry_run=False): # JSON-Daten laden boegen = load_json(os.path.join(data_dir, "json", "omniflo_boegen.json")) weichen = load_json(os.path.join(data_dir, "json", "omniflo_weichen.json")) + geraden_path = os.path.join(data_dir, "json", "omniflo_geraden.json") + geraden = load_json(geraden_path) if os.path.exists(geraden_path) else [] lookup = build_lookup(boegen, weichen) - print(f"[set_attributs] {len(boegen)} Boegen, {len(weichen)} Weichen geladen.") + for g in geraden: + lookup[str(g["Sivasnr"])] = ("gerade", g) + print(f"[set_attributs] {len(boegen)} Boegen, {len(weichen)} Weichen, " + f"{len(geraden)} Geraden geladen.") # Ausgabeverzeichnis out_dir = os.path.join(results_dir, "omniflo")