db75a25ae1
Gefaellestrecke (GF_N): je Zeile eine Gruppe gleichartiger Bloecke (gleiche Hoehe/Laenge/Winkel/Separatoren/Scanner/Typ), IDs und Bezeichnung als Array statt einer einzigen Sammelzeile mit reiner Anzahl. VarioFoerderer (VF_N): je Compound-Block eine eigene Zeile mit vollstaendigen Gefaelle-/Angetrieben-Teilgruppen (Laengen, Winkel, Kurven, Foerderrichtung), da VF_N immer TYP "Streckengruppe" ist. Lose Strecke-Elemente ausserhalb eines VF_N-Blocks bleiben weiterhin in einer gruppierten IDs-Zeile. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
683 lines
26 KiB
Python
683 lines
26 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
export_sivas.py - Erzeugt Sivas-Export-CSV mit gruppierten Zeilen und Summierungszeilen.
|
|
|
|
Neues CSV-Format:
|
|
Nr;TeileArt;SivasNummer;Dispogruppe;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_sivas.py <export_raw.json> <data_dir> <output.csv>
|
|
|
|
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
|
|
|
|
from export_blockpatterns import load_patterns, matches_any
|
|
|
|
BLOCKPATTERNS = load_patterns()
|
|
|
|
|
|
def load_json(path):
|
|
"""Laedt JSON, tolerant gegenueber BricsCAD-Ausgaben.
|
|
|
|
AutoLISP schreibt export_raw.json (write-line) im ANSI-Codepage des
|
|
Systems, nicht in UTF-8 - Sonderzeichen (z.B. Gradzeichen) koennen
|
|
daher nicht als UTF-8 dekodierbar sein. Fallback auf cp1252.
|
|
"""
|
|
try:
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
except UnicodeDecodeError:
|
|
with open(path, "r", encoding="cp1252") as f:
|
|
return json.load(f)
|
|
|
|
|
|
def build_lookup(boegen, weichen):
|
|
"""Erzeugt ein Dict: SivasNr (als String) -> (typ, eintrag)"""
|
|
lookup = {}
|
|
for b in boegen:
|
|
lookup[str(b["Sivasnr"])] = ("bogen", b)
|
|
for w in weichen:
|
|
lookup[str(w["Sivasnr"])] = ("weiche", w)
|
|
return lookup
|
|
|
|
|
|
def classify_weiche(eintrag):
|
|
"""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 in ("Doppelweiche", "Dreiwegeweiche"):
|
|
return "doppelweiche"
|
|
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_mm(block):
|
|
"""Liest HOEHE aus Block-Attributen. Fallback: Z-Koordinate."""
|
|
attribs = block.get("attribs", {})
|
|
v = attribs.get("HOEHE") or str(int(block.get("z", 0) or 0)) or "2000"
|
|
return v
|
|
|
|
|
|
def get_id(block):
|
|
"""Liest das eindeutige ID-Attribut eines Blocks (von ssg-id-check-all vergeben)."""
|
|
return block.get("attribs", {}).get("ID", "")
|
|
|
|
|
|
def format_sivasnr(sivasnr):
|
|
"""
|
|
Wandelt einen "+"-separierten Sivasnr-Wert (Rohformat aus den JSON-Katalogen,
|
|
dort weiterhin benoetigt fuer Dateinamen-Referenzen) in eine reine
|
|
kommaseparierte Liste fuer die Ausgabe (CSV/Block-Attribut) um.
|
|
"""
|
|
return ", ".join(p.strip() for p in str(sivasnr).split("+") if p.strip())
|
|
|
|
|
|
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_gefaellestrecke_details(block):
|
|
"""Merkmale-Dict fuer eine einzelne ILS Gefaellestrecke (GF_N-Block)."""
|
|
attribs = block.get("attribs", {})
|
|
|
|
def mm_to_m(key):
|
|
try:
|
|
return str(round(float(attribs.get(key, "0")) / 1000.0, 2))
|
|
except (ValueError, TypeError):
|
|
return "0"
|
|
|
|
return {
|
|
"IDs": [get_id(block)],
|
|
"Bezeichnung": attribs.get("Bezeichnung", block.get("block_name", "")),
|
|
"Hoehe oben": mm_to_m("HOEHE_VON_mm"),
|
|
"Hoehe unten": mm_to_m("HOEHE_BIS_mm"),
|
|
"Laenge in Meter": attribs.get("L_GF_m", ""),
|
|
"Winkel": attribs.get("GF_WINKEL", ""),
|
|
"Anzahl der Separatoren": attribs.get("ANZAHL_SEPARATOR", "0"),
|
|
"Anzahl der Scanner": attribs.get("ANZAHL_SCANNER", "0"),
|
|
"Typ": attribs.get("TYP", "Gefaellestrecke"),
|
|
}
|
|
|
|
|
|
def gefaellestrecke_signature(block):
|
|
"""
|
|
Schluessel zur Gruppierung gleichartiger Gefaellestrecken: Bloecke mit
|
|
identischen Massen (Hoehe, Laenge, Winkel, Separatoren, Scanner, Typ)
|
|
landen in derselben Sivas-Zeile - nur IDs/Bezeichnung unterscheiden sich.
|
|
"""
|
|
attribs = block.get("attribs", {})
|
|
return (
|
|
attribs.get("HOEHE_VON_mm", "0"),
|
|
attribs.get("HOEHE_BIS_mm", "0"),
|
|
attribs.get("L_GF_m", ""),
|
|
attribs.get("GF_WINKEL", ""),
|
|
attribs.get("ANZAHL_SEPARATOR", "0"),
|
|
attribs.get("ANZAHL_SCANNER", "0"),
|
|
attribs.get("TYP", "Gefaellestrecke"),
|
|
)
|
|
|
|
|
|
def parse_float_list(csv_str):
|
|
"""Kommagetrennten Attribut-String (z.B. L_GF_m) in Liste von floats."""
|
|
out = []
|
|
for part in str(csv_str or "").split(","):
|
|
part = part.strip()
|
|
if not part:
|
|
continue
|
|
try:
|
|
out.append(float(part))
|
|
except ValueError:
|
|
pass
|
|
return out
|
|
|
|
|
|
def parse_str_list(csv_str):
|
|
"""Kommagetrennten Attribut-String (z.B. MOTORSEITE) in Liste von Strings."""
|
|
return [p.strip() for p in str(csv_str or "").split(",") if p.strip()]
|
|
|
|
|
|
def build_variofoerderer_details(block):
|
|
"""
|
|
Merkmale-Dict fuer eine VF_N-Streckengruppe (VarioFoerderer, ggf.
|
|
mehrsegmentig aus Gefaelle- und angetriebenen Foerderer-Teilen).
|
|
"""
|
|
attribs = block.get("attribs", {})
|
|
|
|
def to_int(key):
|
|
try:
|
|
return int(attribs.get(key, "0") or "0")
|
|
except (ValueError, TypeError):
|
|
return 0
|
|
|
|
return {
|
|
"IDs": [get_id(block)],
|
|
"Anzahl Foerdergruppen": to_int("ANZAHL_VF"),
|
|
"Anzahl Gefaellegruppen": to_int("ANZAHL_GF"),
|
|
"Hoehe in m": attribs.get("MONTAGEHOEHE_m", "0"),
|
|
"GruppenteileGefaelle": {
|
|
"AnzahlGeraden": to_int("ANZAHL_GF"),
|
|
"Laenge in Meter": parse_float_list(attribs.get("L_GF_m", "")),
|
|
"Winkel": parse_float_list(attribs.get("GF_WINKEL", "")),
|
|
"AnzahlKurvenLinks90": to_int("GF_Bogen_L_90"),
|
|
"AnzahlKurvenLinks60": to_int("GF_Bogen_L_60"),
|
|
"AnzahlKurvenLinks30": to_int("GF_Bogen_L_30"),
|
|
"AnzahlKurvenRechts90": to_int("GF_Bogen_R_90"),
|
|
"AnzahlKurvenRechts60": to_int("GF_Bogen_R_60"),
|
|
"AnzahlKurvenRechts30": to_int("GF_Bogen_R_30"),
|
|
},
|
|
"GruppenteileAngetrieben": {
|
|
"MotorSeite": parse_str_list(attribs.get("MOTORSEITE", "")),
|
|
"AnzahlGeraden": to_int("ANZAHL_VF"),
|
|
"Laenge in Meter": parse_float_list(attribs.get("L_VF_m", "")),
|
|
"Winkel": parse_float_list(attribs.get("VF_WINKEL", "")),
|
|
"Foerderrichtung": parse_str_list(attribs.get("ANTRIEBFAHRTRICHTUNG", "")),
|
|
"AnzahlKurvenAussen90": to_int("VF_Bogen_A_90"),
|
|
"AnzahlKurvenAussen60": to_int("VF_Bogen_A_60"),
|
|
"AnzahlKurvenAussen30": to_int("VF_Bogen_A_30"),
|
|
"AnzahlKurvenInnen90": to_int("VF_Bogen_I_90"),
|
|
"AnzahlKurvenInnen60": to_int("VF_Bogen_I_60"),
|
|
"AnzahlKurvenInnen30": to_int("VF_Bogen_I_30"),
|
|
},
|
|
"Anzahl der Separatoren": to_int("ANZAHL_SEPARATOR"),
|
|
"Anzahl der Scanner": to_int("ANZAHL_SCANNER"),
|
|
}
|
|
|
|
|
|
def build_kreisel_details(block):
|
|
"""Merkmale-Dict fuer einen ILS Kreisel."""
|
|
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"),
|
|
"Hoehe in m": hoehe_m,
|
|
"Drehrichtung": attribs.get("DREHRICHTUNG", "UZS"),
|
|
"Drehung": block.get("rotation", 0.0),
|
|
}
|
|
|
|
|
|
# --- Bekannte Bloecke ignorieren ---
|
|
SKIP_BLOCKS = set(BLOCKPATTERNS.get("pattern_ks_subblocks", ["K1", "K2", "K3", "K4", "KS_EIN", "KS_AUS"]))
|
|
|
|
# Sivas-Nummern und Bezeichnungen je ILS-Elementtyp
|
|
# Hinweis: "ils_kurve", "bt_beladung" und "bg_entladung" sind aktuell nur
|
|
# Typnamen-Platzhalter - die zugehoerigen Menuebefehle sind noch Dummies
|
|
# (siehe Lisp/README.md), es gibt noch keine Block-Erkennung dafuer.
|
|
ILS_SIVASNR = {
|
|
"kreisel": ("6200", "ILS 2.0 Kreisel"),
|
|
"eckrad": ("6210", "ILS 2.0 Eckrad"),
|
|
"zusatzmodul": ("6215", "ILS 2.0 Zusatzmodul"),
|
|
"ils_weiche": ("6216", "ILS 2.0 Weiche"),
|
|
"ils_kurve": ("", "ILS 2.0 Kurve"),
|
|
"strecke": ("6220", "ILS 2.0 Strecke"),
|
|
"gefaellestrecke": ("6220", "ILS 2.0 Gefaellestrecke"),
|
|
"bt_beladung": ("", "BT - Beladung"),
|
|
"bg_entladung": ("", "BG - Entladung"),
|
|
"automation": ("6269", "ILS 2.0 Automation und Pneumatik - Gesamtanlage"),
|
|
}
|
|
|
|
|
|
def process_blocks(blocks, lookup):
|
|
"""
|
|
Verarbeitet alle Bloecke.
|
|
|
|
Rueckgabe:
|
|
items - Liste der CSV-Zeilen als Dicts
|
|
counters - Zaehler fuer ILS-Automation-Summierungszeile
|
|
"""
|
|
# Gruppen: sivasnr_str -> {anzahl, hoehe_mm, ids}
|
|
bogen_groups = {} # sivasnr -> dict
|
|
weiche_groups = {} # sivasnr -> dict
|
|
# Gerade: artinr_str -> {anzahl, laenge_mm_total, hoehe_mm, ids}
|
|
gerade_groups = {}
|
|
kreisel_list = [] # Liste von blocks
|
|
eckrad_ids = [] # IDs aller Eckrad-Bloecke
|
|
strecke_ids = [] # IDs loser Strecke-Elemente (Vario/Staustrecke ausserhalb VF_N)
|
|
vario_list = [] # Liste von VarioFoerderer-Compound-Bloecken (VF_N), je einer Zeile
|
|
gefaelle_list = [] # Liste von Gefaellestrecke-Bloecken (GF_*), je einer Zeile
|
|
|
|
counters = {
|
|
"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,
|
|
"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,
|
|
}
|
|
|
|
for block in blocks:
|
|
bname = block.get("block_name", "")
|
|
|
|
if bname in SKIP_BLOCKS:
|
|
continue
|
|
|
|
# --- Omniflo Bogen oder Weiche (erkannt via Katalog) ---
|
|
if bname in lookup:
|
|
typ, eintrag = lookup[bname]
|
|
sivasnr = format_sivasnr(eintrag.get("Sivasnr", bname))
|
|
hoehe = get_hoehe_mm(block)
|
|
|
|
if typ == "bogen":
|
|
if sivasnr not in bogen_groups:
|
|
bogen_groups[sivasnr] = {"anzahl": 0, "hoehe_mm": hoehe, "ids": []}
|
|
bogen_groups[sivasnr]["anzahl"] += 1
|
|
bogen_groups[sivasnr]["ids"].append(get_id(block))
|
|
counters["anzahl_boegen"] += 1
|
|
|
|
elif typ == "weiche":
|
|
if sivasnr not in weiche_groups:
|
|
weiche_groups[sivasnr] = {"anzahl": 0, "hoehe_mm": hoehe, "ids": []}
|
|
weiche_groups[sivasnr]["anzahl"] += 1
|
|
weiche_groups[sivasnr]["ids"].append(get_id(block))
|
|
subtyp = classify_weiche(eintrag)
|
|
if subtyp == "einzelweiche":
|
|
counters["anzahl_einzelweichen"] += 1
|
|
elif subtyp in ("doppelweiche", "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 matches_any(bname, BLOCKPATTERNS.get("pattern_gerade", [])):
|
|
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, "ids": [],
|
|
}
|
|
gerade_groups[key]["anzahl"] += 1
|
|
gerade_groups[key]["laenge_mm"] += laenge
|
|
gerade_groups[key]["ids"].append(get_id(block))
|
|
counters["gesamtlaenge_ap110"] += laenge
|
|
continue
|
|
|
|
# --- ILS Eckrad (vor Kreisel pruefen: pattern_kreisel schliesst
|
|
# ECKRAD_* mit ein, pattern_eckrad ist die praezisere Teilmenge) ---
|
|
if matches_any(bname, BLOCKPATTERNS.get("pattern_eckrad", [])):
|
|
counters["anzahl_eckraeder"] += 1
|
|
eckrad_ids.append(get_id(block))
|
|
continue
|
|
|
|
# --- ILS Kreisel ---
|
|
if matches_any(bname, BLOCKPATTERNS.get("pattern_kreisel", [])):
|
|
kreisel_list.append(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 Compound-Block (VF_N) ---
|
|
# Bekommt immer eine eigene Detail-Zeile (vario_list) statt der
|
|
# generischen IDs-Gruppierung - siehe build_variofoerderer_details.
|
|
if matches_any(bname, BLOCKPATTERNS.get("pattern_variofoerderer", [])):
|
|
attribs = block.get("attribs", {})
|
|
counters["anzahl_variofoerderer"] += 1
|
|
vario_list.append(block)
|
|
try:
|
|
counters["laengen_variofoerderer"] += float(attribs.get("L_VF_m", "0") or "0")
|
|
except (ValueError, TypeError):
|
|
pass
|
|
try:
|
|
counters["anzahl_separatoren"] += int(attribs.get("ANZAHL_SEPARATOR", "0") or "0")
|
|
except (ValueError, TypeError):
|
|
pass
|
|
try:
|
|
counters["anzahl_scanner"] += int(attribs.get("ANZAHL_SCANNER", "0") or "0")
|
|
except (ValueError, TypeError):
|
|
pass
|
|
if counters["anzahl_streckengruppen"] == 0:
|
|
counters["anzahl_streckengruppen"] = 1
|
|
continue
|
|
|
|
# --- Gefaellestrecke Compound-Block (GF_N) ---
|
|
# Eigener Typ "ILS 2.0 Gefaellestrecke" - NICHT als Variofoerderer zaehlen.
|
|
if matches_any(bname, BLOCKPATTERNS.get("pattern_gefaellestrecke", [])):
|
|
attribs = block.get("attribs", {})
|
|
counters["anzahl_gefaellestrecken"] += 1
|
|
gefaelle_list.append(block)
|
|
try:
|
|
counters["laengen_gefaellestrecken"] += float(attribs.get("L_GF_m", "0") or "0")
|
|
except (ValueError, TypeError):
|
|
pass
|
|
try:
|
|
counters["anzahl_separatoren"] += int(attribs.get("ANZAHL_SEPARATOR", "0") or "0")
|
|
except (ValueError, TypeError):
|
|
pass
|
|
try:
|
|
counters["anzahl_scanner"] += int(attribs.get("ANZAHL_SCANNER", "0") or "0")
|
|
except (ValueError, TypeError):
|
|
pass
|
|
if counters["anzahl_streckengruppen"] == 0:
|
|
counters["anzahl_streckengruppen"] = 1
|
|
continue
|
|
|
|
# --- ILS Strecke-Module (Vario/Staustrecke, einzelne Komponenten in Modelspace) ---
|
|
if matches_any(bname, BLOCKPATTERNS.get("pattern_strecke_module", [])):
|
|
counters["anzahl_variofoerderer"] += 1
|
|
strecke_ids.append(get_id(block))
|
|
|
|
# --- 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": {"IDs": g["ids"]},
|
|
})
|
|
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": {"IDs": g["ids"]},
|
|
})
|
|
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": {"IDs": g["ids"]},
|
|
})
|
|
nr += 1
|
|
|
|
for block in kreisel_list:
|
|
sivasnr, teileart = ILS_SIVASNR["kreisel"]
|
|
details = build_kreisel_details(block)
|
|
details["ID"] = get_id(block)
|
|
items.append({
|
|
"nr": nr,
|
|
"teileart": teileart,
|
|
"sivasnr": sivasnr,
|
|
"sivasnr_quoted": True,
|
|
"dispgruppe": 20,
|
|
"anzahl": 1,
|
|
"laenge_mm": "",
|
|
"hoehe_m": "",
|
|
"details": details,
|
|
})
|
|
nr += 1
|
|
|
|
if counters["anzahl_eckraeder"] > 0:
|
|
sivasnr, teileart = ILS_SIVASNR["eckrad"]
|
|
items.append({
|
|
"nr": nr,
|
|
"teileart": teileart,
|
|
"sivasnr": sivasnr,
|
|
"sivasnr_quoted": True,
|
|
"dispgruppe": 20,
|
|
"anzahl": counters["anzahl_eckraeder"],
|
|
"laenge_mm": "",
|
|
"hoehe_m": "",
|
|
"details": {"IDs": eckrad_ids},
|
|
})
|
|
nr += 1
|
|
|
|
# Lose Strecke-Elemente (Vario/Staustrecke ausserhalb eines VF_N-Blocks)
|
|
# als eine gruppierte Zeile; VF_N-Compound-Bloecke bekommen unten je eine
|
|
# eigene Detail-Zeile.
|
|
if strecke_ids:
|
|
sivasnr, teileart = ILS_SIVASNR["strecke"]
|
|
items.append({
|
|
"nr": nr,
|
|
"teileart": teileart,
|
|
"sivasnr": sivasnr,
|
|
"sivasnr_quoted": True,
|
|
"dispgruppe": 20,
|
|
"anzahl": len(strecke_ids),
|
|
"laenge_mm": "",
|
|
"hoehe_m": "",
|
|
"details": {"IDs": strecke_ids},
|
|
})
|
|
nr += 1
|
|
|
|
# VarioFoerderer-Compound-Bloecke (VF_N) - je Block eine eigene Zeile mit
|
|
# vollstaendigen Gefaelle-/Angetrieben-Teilgruppen (immer TYP "Streckengruppe").
|
|
for block in vario_list:
|
|
sivasnr, teileart = ILS_SIVASNR["strecke"]
|
|
items.append({
|
|
"nr": nr,
|
|
"teileart": teileart,
|
|
"sivasnr": sivasnr,
|
|
"sivasnr_quoted": True,
|
|
"dispgruppe": 20,
|
|
"anzahl": 1,
|
|
"laenge_mm": "",
|
|
"hoehe_m": "",
|
|
"details": build_variofoerderer_details(block),
|
|
})
|
|
nr += 1
|
|
|
|
# Gleichartige Gefaellestrecken (identische Masse) zu einer Zeile
|
|
# zusammenfassen - nur IDs/Bezeichnung bleiben je Block unterschiedlich.
|
|
gefaelle_groups = {}
|
|
for block in gefaelle_list:
|
|
sig = gefaellestrecke_signature(block)
|
|
if sig not in gefaelle_groups:
|
|
gefaelle_groups[sig] = {"ids": [], "bezeichnungen": [], "block": block}
|
|
gefaelle_groups[sig]["ids"].append(get_id(block))
|
|
gefaelle_groups[sig]["bezeichnungen"].append(
|
|
block.get("attribs", {}).get("Bezeichnung", block.get("block_name", "")))
|
|
|
|
for group in gefaelle_groups.values():
|
|
sivasnr, teileart = ILS_SIVASNR["gefaellestrecke"]
|
|
details = build_gefaellestrecke_details(group["block"])
|
|
details["IDs"] = group["ids"]
|
|
details["Bezeichnung"] = group["bezeichnungen"]
|
|
items.append({
|
|
"nr": nr,
|
|
"teileart": teileart,
|
|
"sivasnr": sivasnr,
|
|
"sivasnr_quoted": True,
|
|
"dispgruppe": 20,
|
|
"anzahl": len(group["ids"]),
|
|
"laenge_mm": "",
|
|
"hoehe_m": "",
|
|
"details": details,
|
|
})
|
|
nr += 1
|
|
|
|
# ILS Automation Summierungszeile (nur wenn ILS-Elemente vorhanden)
|
|
has_ils = (counters["anzahl_kreisel"] > 0
|
|
or counters["anzahl_eckraeder"] > 0
|
|
or counters["anzahl_variofoerderer"] > 0
|
|
or counters["anzahl_gefaellestrecken"] > 0)
|
|
if has_ils:
|
|
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"],
|
|
}
|
|
auto_sivasnr, auto_teileart = ILS_SIVASNR["automation"]
|
|
items.append({
|
|
"nr": nr,
|
|
"teileart": auto_teileart,
|
|
"sivasnr": auto_sivasnr,
|
|
"sivasnr_quoted": True,
|
|
"dispgruppe": 20,
|
|
"anzahl": 1,
|
|
"laenge_mm": "",
|
|
"hoehe_m": "",
|
|
"details": automation_details,
|
|
})
|
|
|
|
return items, counters
|
|
|
|
|
|
def format_csv_line(item):
|
|
"""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_sivas.py <export_raw.json> <data_dir> <output.csv>")
|
|
sys.exit(1)
|
|
|
|
raw_json_path = sys.argv[1]
|
|
data_dir = sys.argv[2]
|
|
output_csv = sys.argv[3]
|
|
|
|
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_sivas] {len(blocks)} Bloecke geladen, "
|
|
f"{len(boegen)} Boegen, {len(weichen)} Weichen im Katalog.")
|
|
|
|
items, _ = process_blocks(blocks, lookup)
|
|
|
|
header = "Nr;TeileArt;SivasNummer;Dispogruppe;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_sivas] CSV geschrieben: {output_csv} ({len(items)} Zeilen)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|