[CHANGE] Sivas Export schaut nun nur noch auf die kommaseparierte Liste im Attribut ARTINR und fügt die Artikelnummern in die Export-Liste ein; [ADD] Menü und dwg für Omniflo Ladeschleifen

This commit is contained in:
Simon Hensch
2026-09-28 13:53:46 +02:00
parent 5a13595289
commit dabf9e4f8a
18 changed files with 663 additions and 385 deletions
+49 -188
View File
@@ -14,7 +14,8 @@ Neues CSV-Format:
- IDs: kommaseparierte Liste der ID-Attribute aller Bloecke der Zeile
(eigene Spalte, nicht mehr Teil des details-JSON).
- Omniflo Bogen/Weiche: gruppiert nach SivasNummer, Anzahl summiert
- Omniflo (TEILEART "Omniflo ..."): je Nummer aus dem ARTINR-Attribut
(kommagetrennt) eine Zeile, Anzahl summiert; TEF-Teile als "Omniflo TEF"
- Omniflo Gerade: gruppiert nach ARTINR-Attribut, Laenge[mm] summiert
- ILS 2.0 Kreisel: gruppiert nach Kreiselart+Abstand+Anzahl Separatoren/
Scanner/Rampen, Anzahl summiert
@@ -34,7 +35,8 @@ Aufruf:
Argumente:
export_raw.json - JSON-Datei mit allen INSERT-Entities aus der Zeichnung
data_dir - Pfad zum data/-Verzeichnis (mit json/ Unterordner)
data_dir - Pfad zum data/-Verzeichnis (derzeit ungenutzt, bleibt
fuer den Aufrufer export.lsp erhalten)
output.csv - Ziel-CSV-Datei (reine Merkmalnummern, fuer den
Sivas-Import)
@@ -71,47 +73,11 @@ def load_json(path):
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_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())
# --- Sivas-Merkmalnummern: Anzeigetexte, die nicht 1:1 aus dem Attribut
# uebernommen werden koennen (Attribute bleiben ASCII, siehe CLAUDE.md-
# Konvention "Alle Texte in Dateien ohne Umlaute" - Sivas erwartet aber die
@@ -218,42 +184,6 @@ def gefaellestrecke_signature(block):
)
def expand_tef_components(sivasnr_parts, sivasnr_tef):
"""Ersetzt "0_BG*"-Platzhalter-Teile in sivasnr_parts durch die dahinter
liegenden echten TEF-Sivasnummern (Katalogfeld SivasnrTEF).
"0_BG*"-Werte im Sivasnr-Rohwert (z.B. "834372404+0_BG071090") sind KEINE
echten Sivasnummern, sondern reine Blocknamen-Platzhalter fuer eine TEF-
Baugruppe aus mehreren realen Teilen - "0_BG071090" steht z.B. fuer die
drei echten Teile "0_B10071"+"0_B10090"+"0_B10090" (siehe SivasnrTEF im
Katalog, omniflo_weichen.json/omniflo_boegen.json). Ohne diese Aufloesung
wuerde "0_BG071090" selbst als (nicht existente) Sivasnummer gezaehlt und
die echten Teile (u.a. 0_B10071) fehlten in der Summierung.
Reale TEF-Einzelteile, die bereits direkt im Sivasnr-Rohwert stehen
(Praefix "0_B", aber nicht "0_BG", z.B. "821104026+0_B10071"), sind schon
echte Sivasnummern und werden unveraendert uebernommen.
Bei mehreren "0_BG*"-Platzhaltern im selben Rohwert (TEF beidseitig) wird
SivasnrTEF zu gleichen Teilen aufgeteilt - der Katalog listet die Teile
je Seite hintereinander in SivasnrTEF.
"""
bg_count = sum(1 for p in sivasnr_parts if p.startswith("0_BG"))
if bg_count == 0:
return list(sivasnr_parts)
tef_parts = [p.strip() for p in str(sivasnr_tef or "").split("+") if p.strip()]
chunk_size = len(tef_parts) // bg_count if bg_count else 0
expanded = []
tef_index = 0
for part in sivasnr_parts:
if part.startswith("0_BG"):
expanded.extend(tef_parts[tef_index:tef_index + chunk_size])
tef_index += chunk_size
else:
expanded.append(part)
return expanded
def parse_float_list(csv_str):
"""Kommagetrennten Attribut-String (z.B. L_GF_m) in Liste von floats."""
out = []
@@ -443,17 +373,21 @@ ILS_SIVASNR = {
}
def process_blocks(blocks, lookup):
def process_blocks(blocks):
"""
Verarbeitet alle Bloecke.
Omniflo-Elemente werden ueber das TEILEART-Attribut ("Omniflo ...")
erkannt, ihre Sivas-Nummern kommen - wie beim Geruest - ausschliesslich
aus dem ARTINR-Attribut (kommagetrennt, TEF-Teile dort bereits
aufgeloest). Ein Katalog-Lookup ueber den Blocknamen findet nicht statt.
Rueckgabe:
items - Liste der CSV-Zeilen als Dicts
counters - Zaehler fuer ILS-Automation-Summierungszeile
"""
# Gruppen: sivasnr_str -> {anzahl, ids}
bogen_groups = {} # sivasnr -> dict
weiche_groups = {} # sivasnr -> dict
# Omniflo: artinr -> {teileart, anzahl, ids}
omniflo_groups = {}
# Gerade: artinr_str -> {anzahl, laenge_mm_total, ids}
gerade_groups = {}
kreisel_list = [] # Liste von blocks
@@ -467,14 +401,6 @@ def process_blocks(blocks, lookup):
geruest_list = [] # Liste von GR_-Geruest-Baugruppenbloecken, 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,
@@ -544,76 +470,9 @@ def process_blocks(blocks, lookup):
counters["anzahl_geruest"] += 1
continue
# --- Omniflo Bogen oder Weiche (erkannt via Katalog) ---
if bname in lookup:
typ, eintrag = lookup[bname]
sivasnr_raw = eintrag.get("Sivasnr", bname)
sivasnr_parts = [p.strip() for p in str(sivasnr_raw).split("+")]
has_tef = any(p.startswith("0_B") for p in sivasnr_parts)
block_id = get_id(block)
if typ == "bogen":
if has_tef and len(sivasnr_parts) > 1:
# Multi-Komponenten: jede Komponente separat gruppieren
# ("0_BG*"-Platzhalter werden ueber SivasnrTEF in ihre
# echten Einzelteile aufgeloest, siehe expand_tef_components)
expanded_parts = expand_tef_components(sivasnr_parts, eintrag.get("SivasnrTEF"))
for i, comp_sivasnr in enumerate(expanded_parts):
comp_id = f"{block_id}.t{i+1}"
if comp_sivasnr not in bogen_groups:
bogen_groups[comp_sivasnr] = {"anzahl": 0, "ids": []}
bogen_groups[comp_sivasnr]["anzahl"] += 1
bogen_groups[comp_sivasnr]["ids"].append(comp_id)
else:
# Normal: alle als eine Komponente
sivasnr = format_sivasnr(sivasnr_raw)
if sivasnr not in bogen_groups:
bogen_groups[sivasnr] = {"anzahl": 0, "ids": []}
bogen_groups[sivasnr]["anzahl"] += 1
bogen_groups[sivasnr]["ids"].append(block_id)
counters["anzahl_boegen"] += 1
elif typ == "weiche":
subtyp = classify_weiche(eintrag)
if has_tef and len(sivasnr_parts) > 1:
# Multi-Komponenten: jede Komponente separat gruppieren
# ("0_BG*"-Platzhalter werden ueber SivasnrTEF in ihre
# echten Einzelteile aufgeloest, siehe expand_tef_components)
expanded_parts = expand_tef_components(sivasnr_parts, eintrag.get("SivasnrTEF"))
for i, comp_sivasnr in enumerate(expanded_parts):
comp_id = f"{block_id}.t{i+1}"
if comp_sivasnr.startswith("0_B"):
# TEF-Komponente (Bogen) in bogen_groups
if comp_sivasnr not in bogen_groups:
bogen_groups[comp_sivasnr] = {"anzahl": 0, "ids": []}
bogen_groups[comp_sivasnr]["anzahl"] += 1
bogen_groups[comp_sivasnr]["ids"].append(comp_id)
else:
# Weichen-Komponente in weiche_groups
if comp_sivasnr not in weiche_groups:
weiche_groups[comp_sivasnr] = {"anzahl": 0, "ids": []}
weiche_groups[comp_sivasnr]["anzahl"] += 1
weiche_groups[comp_sivasnr]["ids"].append(comp_id)
else:
# Normal: alle als eine Komponente
sivasnr = format_sivasnr(sivasnr_raw)
if sivasnr not in weiche_groups:
weiche_groups[sivasnr] = {"anzahl": 0, "ids": []}
weiche_groups[sivasnr]["anzahl"] += 1
weiche_groups[sivasnr]["ids"].append(block_id)
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) ---
# VOR dem allgemeinen Omniflo-Zweig: Geraden tragen ggf. ebenfalls
# TEILEART "Omniflo ...", brauchen aber die Laengensumme.
if matches_any(bname, BLOCKPATTERNS.get("pattern_gerade", [])):
attribs = block.get("attribs", {})
artinr = attribs.get("ARTINR", "")
@@ -628,7 +487,33 @@ def process_blocks(blocks, lookup):
gerade_groups[key]["anzahl"] += 1
gerade_groups[key]["laenge_mm"] += laenge
gerade_groups[key]["ids"].append(get_id(block))
counters["gesamtlaenge_ap110"] += laenge
continue
# --- Omniflo-Element (Bogen, Weiche, Ladeschleife, TEF, ...) ---
# Erkennung ueber TEILEART, Sivas-Nummern aus ARTINR - wie beim
# Geruest eine Zeile je Nummer, hier aber ueber ALLE Bloecke
# gruppiert. Die DWGs fuehren TEF-Teile bereits aufgeloest im ARTINR
# (z.B. "834372002, 0_B10090, 0_B10090"). TEF-Teile ("0_B*") laufen
# unter "Omniflo TEF", damit dasselbe Teil aus Bogen und Weiche EINE
# Zeile bleibt.
attribs = block.get("attribs", {})
teileart = attribs.get("TEILEART", "")
if teileart.startswith("Omniflo"):
artinr_list = [a.strip() for a in attribs.get("ARTINR", "").split(",") if a.strip()]
block_id = get_id(block)
if not artinr_list:
print(f"[export_sivas] WARNUNG: Omniflo-Block {bname} (ID {block_id}) "
f"ohne ARTINR - nicht exportiert.", file=sys.stderr)
continue
for i, artinr in enumerate(artinr_list):
comp_id = f"{block_id}.t{i+1}" if len(artinr_list) > 1 else block_id
if artinr not in omniflo_groups:
omniflo_groups[artinr] = {
"teileart": "Omniflo TEF" if artinr.startswith("0_B") else teileart,
"anzahl": 0, "ids": [],
}
omniflo_groups[artinr]["anzahl"] += 1
omniflo_groups[artinr]["ids"].append(comp_id)
continue
# --- ILS Eckrad (vor Kreisel pruefen: pattern_kreisel schliesst
@@ -695,27 +580,10 @@ def process_blocks(blocks, lookup):
items = []
nr = 1
for sivasnr, g in bogen_groups.items():
teileart = "TEF Bogen" if str(sivasnr).startswith("0_B") else "Omniflo Bogen"
for sivasnr, g in omniflo_groups.items():
items.append({
"nr": nr,
"teileart": teileart,
"sivasnr": sivasnr,
"sivasnr_quoted": False,
"bezeichnung": "",
"dispgruppe": 10,
"anzahl": g["anzahl"],
"laenge_mm": "",
"ids": g["ids"],
"details": {},
})
nr += 1
for sivasnr, g in weiche_groups.items():
teileart = "TEF Weiche" if str(sivasnr).startswith("0_B") else "Omniflo Weiche"
items.append({
"nr": nr,
"teileart": teileart,
"teileart": g["teileart"],
"sivasnr": sivasnr,
"sivasnr_quoted": False,
"bezeichnung": "",
@@ -842,7 +710,7 @@ def process_blocks(blocks, lookup):
# ILS_SIVASNR - JEDE Artikelnummer bekommt ihre EIGENE Sivas-Zeile (Sivas
# kennt nur eine Sivasnr je Zeile), gleiche Artikelnummern INNERHALB
# derselben Baugruppe werden dabei zu einer Zeile mit anzahl>1
# zusammengefasst (wie bogen_groups/weiche_groups oben). Die volle
# zusammengefasst (wie omniflo_groups oben). Die volle
# LAENGE der Baugruppe steht auf JEDER so entstandenen Zeile - Sivas hat
# kein Konzept einer "gemeinsamen" Laenge ueber mehrere Zeilen hinweg.
for block in geruest_list:
@@ -1026,22 +894,15 @@ def main():
sys.exit(1)
raw_json_path = sys.argv[1]
data_dir = sys.argv[2]
# sys.argv[2] (data_dir) wird derzeit nicht gebraucht - Omniflo-Nummern
# kommen aus ARTINR, nicht mehr aus dem Katalog. Argument bleibt fuer
# den Aufrufer (export.lsp) bestehen.
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")
print(f"[export_sivas] {len(blocks)} Bloecke geladen.")
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)
items, _ = process_blocks(blocks)
header = "Nr;TeileArt;SivasNummer;Bezeichnung;Dispogruppe;Anzahl;Laenge[mm];IDs;details"
with open(output_csv, "w", encoding="utf-8") as f: