1023 lines
44 KiB
Python
1023 lines
44 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;Bezeichnung;Dispogruppe;Anzahl;Laenge[mm];IDs;details
|
|
|
|
- Bezeichnung: Wert des Attributs "Bezeichnung" (Kreisel/Eckrad/VF/GF/Strecke).
|
|
Bei gruppierten Zeilen (mehrere Bloecke je Zeile) kommasepariert.
|
|
Bei Omniflo-Zeilen (Bogen/Weiche/Gerade) und der Automation-Summenzeile
|
|
bleibt das Feld leer, da es sich um Sammlungen gleichartiger Objekte handelt.
|
|
|
|
- IDs: kommaseparierte Liste der ID-Attribute aller Bloecke der Zeile
|
|
(eigene Spalte, nicht mehr Teil des details-JSON).
|
|
|
|
- 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
|
|
- ILS 2.0 Gefaellestrecke: gruppiert nach identischen Massen, Anzahl-Spalte
|
|
bleibt 1 - die reale Stueckzahl steht im Merkmal 4311 (siehe
|
|
build_gefaellestrecke_details)
|
|
- ILS 2.0 Zusatzmodul: BTMT-Beladung UND Abwurfstationen in EINER Zeile,
|
|
Anzahl bleibt 1, Stueckzahlen stehen in 3503/3504 (siehe process_blocks)
|
|
- ILS 2.0 Automation: Summierungszeile (nur wenn ILS-Elemente vorhanden)
|
|
|
|
details-JSON: Schluessel sind durchgaengig Sivas-Merkmalnummern (z.B.
|
|
"2952" fuer Kreiselhoehe) statt beschreibender Texte - siehe
|
|
cfg/Merkmalsstamm_in_Sivas.xlsx fuer die Bezeichnung je Nummer.
|
|
|
|
Aufruf:
|
|
python export_sivas.py <export_raw.json> <data_dir> <output.csv> [--mit-bezeichnung]
|
|
|
|
Argumente:
|
|
export_raw.json - JSON-Datei mit allen INSERT-Entities aus der Zeichnung
|
|
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)
|
|
--mit-bezeichnung - optional. Nur dann wird zusaetzlich zu output.csv
|
|
eine zweite Datei "<output>_mit_bezeichnung.csv"
|
|
geschrieben: gleicher Inhalt, aber jeder Merkmalnummer-
|
|
Key im details-JSON wird durch die zugehoerige Klartext-
|
|
Bezeichnung aus cfg/Merkmalsstamm_in_Sivas.xlsx ersetzt
|
|
(Wert bleibt unveraendert) - reine Lesehilfe zur
|
|
Pruefung, wird von Sivas selbst nicht eingelesen.
|
|
Schalter im Dialog CSV_EXPORT_KONFIG
|
|
("Sivas Export mit Bezeichner"), Standard aus.
|
|
|
|
Zusaetzlich wird IMMER "<output>_zusammengefasst.csv" geschrieben: gleiches
|
|
Format, aber Zeilen, die sich nur in Nr/Anzahl/IDs unterscheiden, sind zu
|
|
einer Zeile zusammengefasst (Anzahl addiert, IDs vereinigt, Nr neu) - siehe
|
|
items_zusammenfassen. output.csv selbst bleibt unveraendert.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
import os
|
|
|
|
from export_blockpatterns import (load_patterns, load_export_cfg, cfg_path_from_env,
|
|
matches_any, csv_quote, safe_float, safe_int)
|
|
from merkmalstamm import lade_merkmalstamm, bezeichnung_fuer
|
|
import sivas_mapping
|
|
|
|
BLOCKPATTERNS = load_patterns()
|
|
|
|
# Merkmalnummer fuer die Teilefarbe der Geruest-Baugruppen ([Sivas] in
|
|
# cfg/export.cfg; 108 = "Farbe" im Merkmalstamm).
|
|
MERKMAL_TEILEFARBE = load_export_cfg(cfg_path_from_env()).get(
|
|
"Sivas", "merkmal_teilefarbe", fallback="108").strip()
|
|
|
|
|
|
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 get_id(block):
|
|
"""Liest das eindeutige ID-Attribut eines Blocks (von ssg-id-check-all vergeben)."""
|
|
return block.get("attribs", {}).get("ID", "")
|
|
|
|
|
|
# --- 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
|
|
# deutsche Schreibweise mit Umlauten/Sonderzeichen). Tabelle kommt aus
|
|
# cfg/sivas.cfg [geruest_typ_anzeige], nicht ueber die uebliche ssg-text-
|
|
# Sprachdatei, da rein Sivas-seitig.
|
|
GERUEST_TYP_ANZEIGE = sivas_mapping.load_flat_mapping("geruest_typ_anzeige")
|
|
|
|
SEPARATOR_BEARBEITUNG_ANZEIGE = "ohne HARTCOAT HRC 50 ± 5"
|
|
|
|
|
|
def geruest_typ_anzeige(wert):
|
|
"""Wandelt den ASCII-Attributwert GERUEST_TYP in die Sivas-Anzeige um."""
|
|
return GERUEST_TYP_ANZEIGE.get(wert, wert)
|
|
|
|
|
|
def ja_nein(flag_1_0):
|
|
"""Wandelt ein "0"/"1"-Attribut in die Sivas-Anzeige "Ja"/"Nein" um."""
|
|
return "Ja" if flag_1_0 == "1" else "Nein"
|
|
|
|
|
|
def ist_eins(flag_1_0):
|
|
"""Wandelt ein "0"/"1"-Attribut in ein echtes bool um (True/False im
|
|
JSON, NICHT "Ja"/"Nein" wie ja_nein - siehe build_eckrad_details)."""
|
|
return flag_1_0 == "1"
|
|
|
|
|
|
# --- Transform-Namen, wie sie in cfg/sivas.cfg hinter "|" stehen koennen ---
|
|
TRANSFORMS = {
|
|
"ja_nein": ja_nein,
|
|
"geruest_typ_anzeige": geruest_typ_anzeige,
|
|
"ist_eins": ist_eins,
|
|
}
|
|
|
|
|
|
def streckengruppe_objekt_text(attribs):
|
|
"""Merkmal 4300 "Streckengruppe": "mehrere Objekte", wenn der Block ein
|
|
kombinierter Streckengruppe-Block (Gefaelle+Vario, TYP-Attribut
|
|
"Streckengruppe") ist, sonst "ein Objekt" (reine Gefaellestrecke)."""
|
|
typ = attribs.get("TYP", "Gefaellestrecke")
|
|
return "mehrere Objekte" if typ == "Streckengruppe" else "ein Objekt"
|
|
|
|
|
|
SIVAS_MAPPING_GEFAELLESTRECKE = sivas_mapping.load_section_mapping("gefaellestrecke")
|
|
|
|
|
|
def build_gefaellestrecke_details(block, anzahl=1):
|
|
"""Merkmale-Dict (Sivas-Merkmalnummern) fuer eine Gruppe gleichartiger
|
|
ILS Gefaellestrecken (GF_N-Bloecke). anzahl ist die Anzahl der in dieser
|
|
Zeile zusammengefassten, baugleichen Bloecke (4311) - anders als bei
|
|
Kreisel steht hier auf der Sivas-Hauptzeile selbst immer Anzahl=1 (siehe
|
|
process_blocks), die reale Stueckzahl steckt nur in 4311.
|
|
|
|
Die reine Attribut->Merkmal-Zuordnung kommt aus cfg/sivas.cfg
|
|
[gefaellestrecke]; 4300 (Streckengruppe-Text) und 4311 (Anzahl-Parameter)
|
|
sind berechnete/parametrisierte Werte und bleiben hier Code."""
|
|
attribs = block.get("attribs", {})
|
|
details = sivas_mapping.anwenden(SIVAS_MAPPING_GEFAELLESTRECKE, attribs, TRANSFORMS)
|
|
details["4300"] = streckengruppe_objekt_text(attribs)
|
|
details["4311"] = str(anzahl)
|
|
return details
|
|
|
|
|
|
def kreisel_signature(block):
|
|
"""
|
|
Schluessel zur Gruppierung gleichartiger Kreisel: Kreiselart, Abstand,
|
|
Anzahl der Separatoren, Anzahl der Scanner, Anzahl der Rampen sowie
|
|
Geruest fuer Einzelmodul/Geruestoption entscheiden, ob Kreisel in
|
|
derselben Sivas-Zeile summiert werden.
|
|
"""
|
|
attribs = block.get("attribs", {})
|
|
return (
|
|
attribs.get("KREISELART", "STANDARD"),
|
|
attribs.get("ABSTAND", "2300"),
|
|
attribs.get("ANZAHL_SEPARATOR", attribs.get("N_SEPARATOREN", "2")),
|
|
attribs.get("ANZAHL_SCANNER", attribs.get("N_SCANNER", "0")),
|
|
attribs.get("ANZAHL_RAMPEN", attribs.get("N_RAMPEN", "0")),
|
|
attribs.get("GERUEST_EINZELMODUL", "0"),
|
|
attribs.get("GERUEST_TYP", "Schoenenberger Geruest"),
|
|
)
|
|
|
|
|
|
def eckrad_signature(block):
|
|
"""
|
|
Schluessel zur Gruppierung gleichartiger Eckraeder: Geruest fuer
|
|
Einzelmodul/Geruestoption entscheiden, ob Eckraeder in derselben
|
|
Sivas-Zeile summiert werden.
|
|
"""
|
|
attribs = block.get("attribs", {})
|
|
return (
|
|
attribs.get("GERUEST_EINZELMODUL", "0"),
|
|
attribs.get("GERUEST_TYP", "Schoenenberger Geruest"),
|
|
)
|
|
|
|
|
|
def gefaellestrecke_signature(block):
|
|
"""
|
|
Schluessel zur Gruppierung gleichartiger Gefaellestrecken: Bloecke mit
|
|
identischen Massen (Hoehe, Laenge, Winkel, Separatoren, Scanner, Typ,
|
|
Geruest fuer Einzelmodul/Geruestoption) 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"),
|
|
attribs.get("GERUEST_EINZELMODUL", "0"),
|
|
attribs.get("GERUEST_TYP", "Schoenenberger Geruest"),
|
|
)
|
|
|
|
|
|
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()]
|
|
|
|
|
|
SIVAS_MAPPING_VARIOFOERDERER = sivas_mapping.load_section_mapping("variofoerderer")
|
|
|
|
|
|
def build_variofoerderer_details(block):
|
|
"""
|
|
Merkmale-Dict (Sivas-Merkmalnummern) fuer eine VF_N-Streckengruppe
|
|
(VarioFoerderer, ggf. mehrsegmentig aus Gefaelle- und angetriebenen
|
|
Foerderer-Teilen). Sivas kennt je Streckengruppe nur EIN Gefaelle-
|
|
Segment-Paar (001/002) und EIN Vario-Segment (001) - ANZAHL_GF/ANZAHL_VF
|
|
ueber diese Grenze hinaus werden hier (noch) nicht abgebildet.
|
|
|
|
Die Aufteilung der Separatoren/Scanner auf Gefaelle-Anteil (4337/4339)
|
|
vs. Vario-Anteil (4381/4383) ist NOCH NICHT umgesetzt (haengt an der noch
|
|
ausstehenden Neuzuordnung von Separator/Scanner zum jeweiligen
|
|
Streckenabschnitt) - bis dahin steht 4337/4339 fest auf "0" und der volle
|
|
Attributwert steht auf 4381/4383.
|
|
|
|
Die von der Segmentanzahl UNABHAENGIGEN Felder (2950, GF_Bogen_*/
|
|
VF_Bogen_*-Zaehler, 4381-4383, 3992/3993) kommen aus cfg/sivas.cfg
|
|
[variofoerderer]. Die Gefaelle-/Vario-Segmentpaare (4312-4392, 4353/
|
|
4354/4386, abhaengig von len(gf_laengen)/len(vf_laengen)) bleiben Code.
|
|
"""
|
|
attribs = block.get("attribs", {})
|
|
|
|
gf_laengen = parse_float_list(attribs.get("L_GF_m", ""))
|
|
gf_winkel = parse_float_list(attribs.get("GF_WINKEL", ""))
|
|
vf_laengen = parse_float_list(attribs.get("L_VF_m", ""))
|
|
vf_winkel = parse_float_list(attribs.get("VF_WINKEL", ""))
|
|
vf_richtung = parse_str_list(attribs.get("ANTRIEBFAHRTRICHTUNG", ""))
|
|
motorseiten = parse_str_list(attribs.get("MOTORSEITE", ""))
|
|
|
|
details = sivas_mapping.anwenden(SIVAS_MAPPING_VARIOFOERDERER, attribs, TRANSFORMS)
|
|
details["4300"] = "mehrere Objekte"
|
|
details["4311"] = "1"
|
|
|
|
if len(gf_laengen) >= 1:
|
|
details["4312"] = "Ja"
|
|
details["4318"] = gf_laengen[0]
|
|
details["4391"] = str(int(gf_winkel[0])) if gf_winkel else ""
|
|
else:
|
|
details["4312"] = "Nein"
|
|
|
|
if len(gf_laengen) >= 2:
|
|
details["4314"] = "Ja"
|
|
details["4319"] = gf_laengen[1]
|
|
details["4392"] = str(int(gf_winkel[1])) if len(gf_winkel) >= 2 else ""
|
|
else:
|
|
details["4314"] = "Nein"
|
|
|
|
details["4337"] = "0"
|
|
details["4338"] = SEPARATOR_BEARBEITUNG_ANZEIGE
|
|
details["4339"] = "0"
|
|
details["4340"] = "Ja" if vf_laengen else "Nein"
|
|
details["4341"] = sum(1 for seite in motorseiten if seite.lower() == "links")
|
|
details["4342"] = sum(1 for seite in motorseiten if seite.lower() == "rechts")
|
|
|
|
if vf_laengen:
|
|
details["4353"] = vf_laengen[0]
|
|
details["4354"] = vf_richtung[0].lower() if vf_richtung else ""
|
|
details["4386"] = str(int(vf_winkel[0])) if vf_winkel else ""
|
|
|
|
return {k: str(v) for k, v in details.items()}
|
|
|
|
|
|
SIVAS_MAPPING_KREISEL = sivas_mapping.load_section_mapping("kreisel")
|
|
SIVAS_MAPPING_ECKRAD = sivas_mapping.load_section_mapping("eckrad")
|
|
SIVAS_MAPPING_GERUEST = sivas_mapping.load_section_mapping("geruest")
|
|
|
|
|
|
def build_kreisel_details(block):
|
|
"""Merkmale-Dict (Sivas-Merkmalnummern) fuer eine Gruppe gleichartiger
|
|
ILS Kreisel. 3511 ("Kreisel - zu fertigende Menge") steht bewusst fest
|
|
auf "1" - die reale Stueckzahl der Gruppe steht auf der Sivas-Hauptzeile
|
|
selbst (Anzahl-Spalte, siehe kreisel_groups in process_blocks).
|
|
|
|
Die reine Attribut->Merkmal-Zuordnung kommt aus cfg/sivas.cfg [kreisel];
|
|
3510 (Kreiselart-Anzeigetext) und 3512 (Abstand mm->m, gerundet) sind
|
|
berechnete Werte und bleiben hier Code."""
|
|
attribs = block.get("attribs", {})
|
|
abstand_m = str(round(safe_float(attribs.get("ABSTAND", "2300"), 2300.0) / 1000.0, 2))
|
|
kreiselart = "Kreisel mit PINBAND" if attribs.get("KREISELART", "STANDARD") == "PIN" else "Kreisel"
|
|
details = sivas_mapping.anwenden(SIVAS_MAPPING_KREISEL, attribs, TRANSFORMS)
|
|
details["3510"] = kreiselart
|
|
details["3512"] = abstand_m
|
|
return details
|
|
|
|
|
|
def build_eckrad_details(block):
|
|
"""Merkmale-Dict fuer ein ILS Eckrad. Vollstaendig aus cfg/sivas.cfg
|
|
[eckrad] (keine berechneten Werte)."""
|
|
attribs = block.get("attribs", {})
|
|
return sivas_mapping.anwenden(SIVAS_MAPPING_ECKRAD, attribs, TRANSFORMS)
|
|
|
|
|
|
# def build_geruest_details(block):
|
|
#"""Merkmale-Dict fuer eine GR_-Geruest-Baugruppe (siehe pattern_geruest in
|
|
#cfg/export.cfg, GeruestInsert.lsp *geruest-attrib-defs*). ARTINR/
|
|
#BEZEICHNUNG/TEILEART/LAENGE wandern in feste Sivas-Spalten (sivasnr/
|
|
#bezeichnung/teileart/laenge_mm), FARBE bleibt als Rest-Attribut hier
|
|
#(cfg/sivas.cfg [geruest]). LAYER ist reine BricsCAD-Zeichnungsinformation
|
|
#ohne Sivas-Bedeutung und wird bewusst NICHT mit exportiert (anders als im
|
|
#CSV-Export export_csv.py, wo sie fuer die Zuordnung in der Zeichnung
|
|
#nuetzlich bleibt)."""
|
|
#attribs = block.get("attribs", {})
|
|
#return sivas_mapping.anwenden(SIVAS_MAPPING_GERUEST, attribs, TRANSFORMS)
|
|
|
|
def build_geruest_details(farbe):
|
|
"""Merkmale-Dict fuer EINE Artikelnummer einer GR_-Geruest-Baugruppe
|
|
(siehe pattern_geruest in cfg/export.cfg, GeruestInsert.lsp
|
|
*geruest-attrib-defs*). ARTINR/BEZEICHNUNG/TEILEART/LAENGE wandern in
|
|
feste Sivas-Spalten (sivasnr/bezeichnung/teileart/laenge_mm); hier bleibt
|
|
nur die Teilefarbe dieser Artikelnummer als Merkmal MERKMAL_TEILEFARBE -
|
|
ohne Farbe (Teil darf keine tragen) ein leeres Dict. LAYER ist reine
|
|
BricsCAD-Zeichnungsinformation ohne Sivas-Bedeutung und wird bewusst
|
|
NICHT mit exportiert (anders als im CSV-Export export_csv.py, wo sie fuer
|
|
die Zuordnung in der Zeichnung nuetzlich bleibt)."""
|
|
return {MERKMAL_TEILEFARBE: farbe} if farbe else {}
|
|
|
|
|
|
def geruest_artinr_farben(attribs):
|
|
"""(artinr, farbe)-Paare eines GR_-Blocks. TEILEFARBE ist eine
|
|
kommagetrennte Liste parallel zu ARTINR (GeruestInsert.lsp,
|
|
geruest-farbliste): je Artikelnummer die Baugruppenfarbe oder "".
|
|
Passt die Laenge nicht (Altbestand mit einem Einzelwert oder von Hand
|
|
geaendertes Attribut), wird gewarnt und ohne Farbe exportiert - eine
|
|
geratene Zuordnung waere schlimmer als keine. Leere Artikelnummern
|
|
werden verworfen."""
|
|
artinr_roh = attribs.get("ARTINR", "").split(",")
|
|
farben_roh = attribs.get("TEILEFARBE", "")
|
|
farben = [f.strip() for f in farben_roh.split(",")]
|
|
if farben_roh.strip() == "":
|
|
farben = [""] * len(artinr_roh)
|
|
elif len(farben) != len(artinr_roh):
|
|
print(f"WARNUNG: Geruest-Block ID {attribs.get('ID', '?')}: TEILEFARBE "
|
|
f"({len(farben)} Eintraege) passt nicht zu ARTINR "
|
|
f"({len(artinr_roh)} Eintraege) - ohne Farbe exportiert.",
|
|
file=sys.stderr)
|
|
farben = [""] * len(artinr_roh)
|
|
return [(a.strip(), f) for a, f in zip(artinr_roh, farben) if a.strip()]
|
|
|
|
|
|
# --- Bekannte Bloecke ignorieren ---
|
|
SKIP_BLOCKS = set(BLOCKPATTERNS.get("pattern_ks_subblocks", ["K1", "K2", "K3", "K4", "KS_EIN", "KS_AUS"]))
|
|
|
|
# Eigenstaendige Separator_SP/S-LP- und Scanner-Bloecke werden von der Lisp-
|
|
# Sammlung (csv:collect-export-blocks) zwar mit in export_raw.json geschrieben
|
|
# (export_csv.py gibt ihnen dort eigene TeileArt-Zeilen), aber HIER bewusst
|
|
# NICHT in eine eigene Sivas-Zeile uebernommen: ihre Stueckzahl wird bereits
|
|
# ueber die ANZAHL_SEPARATOR/ANZAHL_SCANNER-Attribute des zugehoerigen
|
|
# Kreisel/GF/VF-Blocks gezaehlt (siehe kreisel_signature/build_*_details
|
|
# oben) - ein zusaetzlicher eigener Eintrag wuerde doppelt zaehlen.
|
|
#
|
|
# Dieselbe Ueberlegung gilt fuer die ausgepackten VF_n/GF_n-Sub-Komponenten
|
|
# (Boegen, gerade Foerderer-/Gefaellestrecken-Segmente, Umlenk-/Motorstation -
|
|
# siehe csv:element-proxies-erzeugen, export.lsp; nur im Rohdaten-Modus
|
|
# *csv-export-alle-elemente*=T ueberhaupt in export_raw.json vorhanden) sowie
|
|
# fuer die gruppierten Foerdereinheit-/Gefaelleeinheit-Marker (ZZ_UNIT_MARKER,
|
|
# csv:ketten-verarbeiten, Default-Modus): ihre Stueckzahl steckt bereits
|
|
# in den GF_Bogen_*/VF_Bogen_*/ANZAHL_VF/ANZAHL_GF-Attributen des Wrappers
|
|
# (*strecke-attr-gruppe*, ssg_core.lsp) - ein eigener Sivas-Eintrag wuerde
|
|
# ebenso doppelt zaehlen. Muss VOR der pattern_strecke_module-Pruefung unten
|
|
# greifen, sonst faengt deren generisches "ILS 2.0 Strecke"-Sammeln diese
|
|
# Kopien zuerst ab.
|
|
SEP_SCANNER_PATTERNS = (
|
|
BLOCKPATTERNS.get("pattern_separator", []) + BLOCKPATTERNS.get("pattern_scanner", [])
|
|
+ BLOCKPATTERNS.get("pattern_nested_bogen", [])
|
|
+ BLOCKPATTERNS.get("pattern_nested_streckenelement", [])
|
|
+ BLOCKPATTERNS.get("pattern_nested_umlenkstation", [])
|
|
+ BLOCKPATTERNS.get("pattern_nested_motorstation", [])
|
|
+ BLOCKPATTERNS.get("pattern_unit_marker", [])
|
|
)
|
|
|
|
# Sivas-Nummern und Bezeichnungen je ILS-Elementtyp
|
|
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"),
|
|
"strecke": ("6220", "ILS 2.0 Strecke"),
|
|
"gefaellestrecke": ("6220", "ILS 2.0 Gefaellestrecke"),
|
|
"automation": ("6269", "ILS 2.0 Automation und Pneumatik - Gesamtanlage"),
|
|
}
|
|
|
|
|
|
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
|
|
"""
|
|
# Omniflo: artinr -> {teileart, anzahl, ids}
|
|
omniflo_groups = {}
|
|
# Gerade: artinr_str -> {anzahl, laenge_mm_total, ids}
|
|
gerade_groups = {}
|
|
kreisel_list = [] # Liste von blocks
|
|
eckrad_groups = {} # eckrad_signature(block) -> {"ids", "bezeichnungen", "block"}
|
|
strecke_ids = [] # IDs loser Strecke-Elemente (Vario/Staustrecke ausserhalb VF_N)
|
|
strecke_bezeichnungen = [] # Bezeichnung-Attribut loser Strecke-Elemente
|
|
vario_list = [] # Liste von VarioFoerderer-Compound-Bloecken (VF_N), je einer Zeile
|
|
gefaelle_list = [] # Liste von Gefaellestrecke-Bloecken (GF_*), je einer Zeile
|
|
btmt_beladung_ids = [] # IDs aller BTMT-Beladung*-Bloecke (eine gruppierte Zeile)
|
|
btmt_entladung_ids = [] # IDs aller SC_Entladung*-Bloecke (eine gruppierte Zeile)
|
|
geruest_list = [] # Liste von GR_-Geruest-Baugruppenbloecken, je einer Zeile
|
|
|
|
counters = {
|
|
"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,
|
|
"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,
|
|
"anzahl_geruest": 0,
|
|
}
|
|
|
|
for block in blocks:
|
|
bname = block.get("block_name", "")
|
|
|
|
if bname in SKIP_BLOCKS:
|
|
continue
|
|
|
|
# Separator/Scanner: siehe Kommentar bei SEP_SCANNER_PATTERNS oben.
|
|
if matches_any(bname, SEP_SCANNER_PATTERNS):
|
|
continue
|
|
|
|
# --- BTMT-Beladestation ---
|
|
# Anders als Separator/Scanner wird sie von keinem Carrier-Attribut
|
|
# mitgezaehlt, darum eigene (gruppierte) Zeile UND eigener Zaehler in
|
|
# der ILS-Automation-Summierungszeile (siehe unten).
|
|
if matches_any(bname, BLOCKPATTERNS.get("pattern_btmt_beladung", [])):
|
|
btmt_beladung_ids.append(get_id(block))
|
|
counters["anzahl_btbeladung"] += 1
|
|
continue
|
|
|
|
# --- BTMT-Entladestation (Abwurfstation) ---
|
|
if matches_any(bname, BLOCKPATTERNS.get("pattern_btmt_entladung", [])):
|
|
btmt_entladung_ids.append(get_id(block))
|
|
counters["anzahl_abwurfstationen"] += 1
|
|
continue
|
|
|
|
# --- Eingangs-/Ausgangssymbol (Materialfluss-Ein-/Ausgang) ---
|
|
# Bewusst NUR im CSV-Export als eigene Zeile (siehe export_csv.py) -
|
|
# hier ohne Sivas-Zeile und ohne eigenen Zaehler ueberspringen. Ein
|
|
# Eingangs-/Ausgangssymbol ist eine reine Fluss-Markierung ohne Sivas-
|
|
# Artikel; ein eigener Sivas-Eintrag wuerde die Stueckliste verfaelschen
|
|
# (Nutzeranforderung: Eingang/Ausgang nur im CSV-Export).
|
|
if matches_any(bname, BLOCKPATTERNS.get("pattern_eingang", [])) or \
|
|
matches_any(bname, BLOCKPATTERNS.get("pattern_ausgang", [])):
|
|
continue
|
|
|
|
# --- Geruest-Baugruppe ---
|
|
# Anders als BTMT/Separator je Block eine EIGENE Zeile statt gruppiert:
|
|
# ARTINR (Sivas-IDs) und Laenge/Farbe koennen von Block zu Block
|
|
# unterschiedlich sein (freie Basis-Variante+Anbauteile-Auswahl je
|
|
# GERUEST_*-Aufruf), eine Gruppierung wuerde das verschleiern - wie
|
|
# bei VF_N/Kreisel oben, die aus demselben Grund ebenfalls einzeln
|
|
# bleiben.
|
|
if matches_any(bname, BLOCKPATTERNS.get("pattern_geruest", [])):
|
|
geruest_list.append(block)
|
|
counters["anzahl_geruest"] += 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", "")
|
|
key = artinr if artinr else "AP110"
|
|
laenge_attr = attribs.get("LAENGE") or attribs.get("A")
|
|
laenge = safe_float(laenge_attr, 2000.0)
|
|
if key not in gerade_groups:
|
|
gerade_groups[key] = {
|
|
"anzahl": 0, "laenge_mm": 0.0,
|
|
"artinr": artinr, "ids": [],
|
|
}
|
|
gerade_groups[key]["anzahl"] += 1
|
|
gerade_groups[key]["laenge_mm"] += laenge
|
|
gerade_groups[key]["ids"].append(get_id(block))
|
|
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
|
|
# ECKRAD_* mit ein, pattern_eckrad ist die praezisere Teilmenge) ---
|
|
if matches_any(bname, BLOCKPATTERNS.get("pattern_eckrad", [])):
|
|
counters["anzahl_eckraeder"] += 1
|
|
sig = eckrad_signature(block)
|
|
if sig not in eckrad_groups:
|
|
eckrad_groups[sig] = {"ids": [], "bezeichnungen": [], "block": block}
|
|
eckrad_groups[sig]["ids"].append(get_id(block))
|
|
eckrad_groups[sig]["bezeichnungen"].append(
|
|
block.get("attribs", {}).get("Bezeichnung", ""))
|
|
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 = safe_int(attribs.get("ANZAHL_SEPARATOR", attribs.get("N_SEPARATOREN", "2")) or "2", 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 = safe_float(attribs.get("ABSTAND", "2300") or "2300", 2300.0)
|
|
counters["laengen_kreisel"] += abstand_mm / 1000.0
|
|
counters["anzahl_separatoren"] += n_sep
|
|
counters["anzahl_scanner"] += safe_int(attribs.get("ANZAHL_SCANNER", attribs.get("N_SCANNER", "0")) or "0")
|
|
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)
|
|
counters["laengen_variofoerderer"] += safe_float(attribs.get("L_VF_m", "0") or "0")
|
|
counters["anzahl_separatoren"] += safe_int(attribs.get("ANZAHL_SEPARATOR", "0") or "0")
|
|
counters["anzahl_scanner"] += safe_int(attribs.get("ANZAHL_SCANNER", "0") or "0")
|
|
counters["anzahl_motoren_vario"] += safe_int(attribs.get("ANZAHL_VF", "0") or "0")
|
|
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)
|
|
counters["laengen_gefaellestrecken"] += safe_float(attribs.get("L_GF_m", "0") or "0")
|
|
counters["anzahl_separatoren"] += safe_int(attribs.get("ANZAHL_SEPARATOR", "0") or "0")
|
|
counters["anzahl_scanner"] += safe_int(attribs.get("ANZAHL_SCANNER", "0") or "0")
|
|
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))
|
|
strecke_bezeichnungen.append(block.get("attribs", {}).get("Bezeichnung", ""))
|
|
|
|
# --- Zeilen aufbauen ---
|
|
items = []
|
|
nr = 1
|
|
|
|
for sivasnr, g in omniflo_groups.items():
|
|
items.append({
|
|
"nr": nr,
|
|
"teileart": g["teileart"],
|
|
"sivasnr": sivasnr,
|
|
"sivasnr_quoted": False,
|
|
"bezeichnung": "",
|
|
"dispgruppe": 10,
|
|
"anzahl": g["anzahl"],
|
|
"laenge_mm": "",
|
|
"ids": g["ids"],
|
|
"details": {},
|
|
})
|
|
nr += 1
|
|
|
|
for key, g in gerade_groups.items():
|
|
items.append({
|
|
"nr": nr,
|
|
"teileart": "Omniflo Gerade",
|
|
"sivasnr": g["artinr"],
|
|
"sivasnr_quoted": True,
|
|
"bezeichnung": "",
|
|
"dispgruppe": 10,
|
|
"anzahl": g["anzahl"],
|
|
"laenge_mm": int(g["laenge_mm"]),
|
|
"ids": g["ids"],
|
|
"details": {},
|
|
})
|
|
nr += 1
|
|
|
|
# Gleichartige Kreisel (gleiche Kreiselart und gleicher Abstand) zu einer
|
|
# Zeile zusammenfassen - nur IDs/Bezeichnung bleiben je Block unterschiedlich.
|
|
kreisel_groups = {}
|
|
for block in kreisel_list:
|
|
sig = kreisel_signature(block)
|
|
if sig not in kreisel_groups:
|
|
kreisel_groups[sig] = {"ids": [], "bezeichnungen": [], "block": block}
|
|
kreisel_groups[sig]["ids"].append(get_id(block))
|
|
kreisel_groups[sig]["bezeichnungen"].append(
|
|
block.get("attribs", {}).get("Bezeichnung", ""))
|
|
|
|
for group in kreisel_groups.values():
|
|
sivasnr, teileart = ILS_SIVASNR["kreisel"]
|
|
items.append({
|
|
"nr": nr,
|
|
"teileart": teileart,
|
|
"sivasnr": sivasnr,
|
|
"sivasnr_quoted": True,
|
|
"bezeichnung": ", ".join(b for b in group["bezeichnungen"] if b),
|
|
"dispgruppe": 20,
|
|
"anzahl": len(group["ids"]),
|
|
"laenge_mm": "",
|
|
"ids": group["ids"],
|
|
"details": build_kreisel_details(group["block"]),
|
|
})
|
|
nr += 1
|
|
|
|
# Gleichartige Eckraeder (gleiches Geruest fuer Einzelmodul/Geruestoption)
|
|
# zu einer Zeile zusammenfassen - nur IDs/Bezeichnung bleiben je Block
|
|
# unterschiedlich.
|
|
for group in eckrad_groups.values():
|
|
sivasnr, teileart = ILS_SIVASNR["eckrad"]
|
|
items.append({
|
|
"nr": nr,
|
|
"teileart": teileart,
|
|
"sivasnr": sivasnr,
|
|
"sivasnr_quoted": True,
|
|
"bezeichnung": ", ".join(b for b in group["bezeichnungen"] if b),
|
|
"dispgruppe": 20,
|
|
"anzahl": len(group["ids"]),
|
|
"laenge_mm": "",
|
|
"ids": group["ids"],
|
|
"details": build_eckrad_details(group["block"]),
|
|
})
|
|
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,
|
|
"bezeichnung": ", ".join(b for b in strecke_bezeichnungen if b),
|
|
"dispgruppe": 20,
|
|
"anzahl": len(strecke_ids),
|
|
"laenge_mm": "",
|
|
"ids": strecke_ids,
|
|
"details": {},
|
|
})
|
|
nr += 1
|
|
|
|
# ILS 2.0 Zusatzmodul: BTMT-Beladestationen UND BTMT-Entladestationen
|
|
# (Abwurfstationen) zusammen in EINER Zeile (Merkmale 3501-3504) statt wie
|
|
# bisher zwei separaten TeileArt-Zeilen ohne details - Anzahl-Spalte
|
|
# bleibt 1 (wie bei der Gefaellestrecke-Gruppe), die realen Stueckzahlen
|
|
# stehen in 3503/3504.
|
|
if btmt_beladung_ids or btmt_entladung_ids:
|
|
sivasnr, teileart = ILS_SIVASNR["zusatzmodul"]
|
|
items.append({
|
|
"nr": nr,
|
|
"teileart": teileart,
|
|
"sivasnr": sivasnr,
|
|
"sivasnr_quoted": True,
|
|
"bezeichnung": "",
|
|
"dispgruppe": 20,
|
|
"anzahl": 1,
|
|
"laenge_mm": "",
|
|
"ids": btmt_beladung_ids + btmt_entladung_ids,
|
|
"details": {
|
|
"3501": ja_nein("1" if btmt_beladung_ids else "0"),
|
|
"3503": str(len(btmt_beladung_ids)),
|
|
"3502": ja_nein("1" if btmt_entladung_ids else "0"),
|
|
"3504": str(len(btmt_entladung_ids)),
|
|
},
|
|
})
|
|
nr += 1
|
|
|
|
# Geruest-Baugruppen (GR_<id>_<timestamp>) - je Block eine eigene Zeile
|
|
# (siehe Kommentar bei geruest_list oben). Sivasnr kommt direkt aus dem
|
|
# ARTINR-Attribut - kann mehrere, kommagetrennte Sivas-IDs enthalten
|
|
# (Baugruppe aus mehreren Einzelteilen, siehe ssg-join in
|
|
# GeruestInsert.lsp) und dieselbe ID mehrfach (menge>1 je Einzelteil).
|
|
# Anders als bei den uebrigen Teilearten also KEIN fester Eintrag aus
|
|
# 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 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.
|
|
# Die Teilefarbe steht je Artikelnummer in TEILEFARBE (parallele Liste),
|
|
# gruppiert wird darum ueber (Artikelnummer, Farbe).
|
|
for block in geruest_list:
|
|
attribs = block.get("attribs", {})
|
|
artinr_groups = {}
|
|
for paar in geruest_artinr_farben(attribs):
|
|
artinr_groups.setdefault(paar, 0)
|
|
artinr_groups[paar] += 1
|
|
for (artinr, farbe), anzahl in artinr_groups.items():
|
|
items.append({
|
|
"nr": nr,
|
|
"teileart": attribs.get("TEILEART", "Geruest"),
|
|
"sivasnr": artinr,
|
|
"sivasnr_quoted": True,
|
|
"bezeichnung": attribs.get("BEZEICHNUNG", ""),
|
|
"dispgruppe": 20,
|
|
"anzahl": anzahl,
|
|
"laenge_mm": attribs.get("LAENGE", ""),
|
|
"ids": [get_id(block)],
|
|
"details": build_geruest_details(farbe),
|
|
})
|
|
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,
|
|
"bezeichnung": block.get("attribs", {}).get("Bezeichnung", ""),
|
|
"dispgruppe": 20,
|
|
"anzahl": 1,
|
|
"laenge_mm": "",
|
|
"ids": [get_id(block)],
|
|
"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"]
|
|
# Anders als bei Kreisel steht hier die reale Stueckzahl der Gruppe
|
|
# NICHT in der Anzahl-Spalte (die bleibt 1), sondern im Merkmal 4311 -
|
|
# siehe build_gefaellestrecke_details.
|
|
details = build_gefaellestrecke_details(group["block"], anzahl=len(group["ids"]))
|
|
items.append({
|
|
"nr": nr,
|
|
"teileart": teileart,
|
|
"sivasnr": sivasnr,
|
|
"sivasnr_quoted": True,
|
|
"bezeichnung": ", ".join(b for b in group["bezeichnungen"] if b),
|
|
"dispgruppe": 20,
|
|
"anzahl": 1,
|
|
"laenge_mm": "",
|
|
"ids": group["ids"],
|
|
"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
|
|
or counters["anzahl_btbeladung"] > 0
|
|
or counters["anzahl_abwurfstationen"] > 0)
|
|
if has_ils:
|
|
# 4407 "Anzahl der Streckengruppen" zaehlt NUR die kombinierten
|
|
# Gefaelle+Vario-Compound-Bloecke (VF_N, TYP "Streckengruppe") - das
|
|
# ist len(vario_list), NICHT counters["anzahl_variofoerderer"] (der
|
|
# zaehlt auch lose Strecke-Module ausserhalb eines VF_N mit).
|
|
# 4426 "Anzahl der einzelnen Vario-Foerderer" gibt es als eigenen
|
|
# Typ nicht mehr (nur noch Gefaellestrecke ODER Streckengruppe) und
|
|
# steht daher fest auf "0".
|
|
automation_details = {
|
|
"4000": "Ja",
|
|
"4400": str(counters["anzahl_kreisel"]),
|
|
"4413": str(counters["sep_kreisel_ohne_pin"]),
|
|
"4401": str(counters["anzahl_kreisel_pinband"]),
|
|
"4414": str(counters["sep_kreisel_pin"]),
|
|
"4402": str(counters["anzahl_verbindungskreisel"]),
|
|
"4419": f"{counters['laengen_kreisel']:.2f}",
|
|
"4403": str(counters["anzahl_weichen_ils"]),
|
|
"4404": str(counters["anzahl_eckraeder"]),
|
|
"4425": str(counters["anzahl_gefaellestrecken"]),
|
|
"4426": "0",
|
|
"4407": str(len(vario_list)),
|
|
"4418": f"{counters['laengen_gefaellestrecken']:.2f}",
|
|
"4408": str(counters["anzahl_motoren_vario"]),
|
|
"4420": str(counters["laengen_variofoerderer"]),
|
|
"4409": str(counters["anzahl_etf"]),
|
|
"4417": str(counters["laengen_etf"]),
|
|
"4410": str(counters["anzahl_separatoren"]),
|
|
"4411": str(counters["anzahl_scanner"]),
|
|
"4421": str(counters["anzahl_btbeladung"]),
|
|
"4422": str(counters["anzahl_abwurfstationen"]),
|
|
"4100": "Ja",
|
|
}
|
|
auto_sivasnr, auto_teileart = ILS_SIVASNR["automation"]
|
|
items.append({
|
|
"nr": nr,
|
|
"teileart": auto_teileart,
|
|
"sivasnr": auto_sivasnr,
|
|
"sivasnr_quoted": True,
|
|
"bezeichnung": "",
|
|
"dispgruppe": 20,
|
|
"anzahl": 1,
|
|
"laenge_mm": "",
|
|
"details": automation_details,
|
|
})
|
|
|
|
return items, counters
|
|
|
|
|
|
def format_csv_line(item, details=None):
|
|
"""Formatiert eine CSV-Zeile im neuen Sivas-Format.
|
|
|
|
details ueberschreibt item["details"] fuer die Ausgabe - genutzt von
|
|
details_mit_bezeichnung(), um dieselbe Zeile mit lesbaren Merkmal-Keys
|
|
in die zweite ("..._mit_bezeichnung.csv") Datei zu schreiben, ohne
|
|
item["details"] selbst zu veraendern.
|
|
"""
|
|
if details is None:
|
|
details = item["details"]
|
|
details_json = json.dumps(details, ensure_ascii=False)
|
|
sivasnr = f'"{csv_quote(item["sivasnr"])}"' if item["sivasnr_quoted"] else item["sivasnr"]
|
|
ids_str = ", ".join(str(i) for i in item.get("ids", []) if i)
|
|
return (
|
|
f'{item["nr"]}'
|
|
f';"{csv_quote(item["teileart"])}"'
|
|
f';{sivasnr}'
|
|
f';"{csv_quote(item["bezeichnung"])}"'
|
|
f';{item["dispgruppe"]}'
|
|
f';{item["anzahl"]}'
|
|
f';{item["laenge_mm"]}'
|
|
f';"{csv_quote(ids_str)}"'
|
|
f';{details_json}'
|
|
)
|
|
|
|
|
|
def details_mit_bezeichnung(details, merkmalstamm):
|
|
"""Kopie von details, bei der jeder Merkmalnummer-Key durch seine
|
|
Klartext-Bezeichnung aus dem Merkmalstamm ersetzt wird (Wert bleibt
|
|
unveraendert). Keys, die keine reine Zahl sind (aktuell nur beim
|
|
Eckrad, siehe build_eckrad_details - dort noch beschreibende
|
|
Klartext-Keys statt Merkmalnummern), bleiben unveraendert."""
|
|
neu = {}
|
|
for key, value in details.items():
|
|
if key.isdigit():
|
|
neu[bezeichnung_fuer(key, merkmalstamm)] = value
|
|
else:
|
|
neu[key] = value
|
|
return neu
|
|
|
|
|
|
def lesbarer_dateiname(output_csv):
|
|
"""Leitet den Dateinamen der Lesbarkeits-Kopie aus output_csv ab, z.B.
|
|
"500651_sivas.csv" -> "500651_sivas_mit_bezeichnung.csv"."""
|
|
stamm, endung = os.path.splitext(output_csv)
|
|
return f"{stamm}_mit_bezeichnung{endung}"
|
|
|
|
|
|
def zusammengefasster_dateiname(output_csv):
|
|
"""Leitet den Dateinamen der zusammengefassten Kopie aus output_csv ab,
|
|
z.B. "500651_sivas.csv" -> "500651_sivas_zusammengefasst.csv"."""
|
|
stamm, endung = os.path.splitext(output_csv)
|
|
return f"{stamm}_zusammengefasst{endung}"
|
|
|
|
|
|
def items_zusammenfassen(items):
|
|
"""Fasst Zeilen zusammen, die sich nur in Nr, Anzahl und IDs
|
|
unterscheiden: Anzahl wird addiert, die IDs werden (ohne Dubletten, in
|
|
Reihenfolge des Auftretens) vereinigt, Nr wird neu durchgezaehlt.
|
|
Reihenfolge der Gruppen = erstes Auftreten.
|
|
|
|
Betrifft in der Praxis vor allem die Geruest-Zeilen (je Block und
|
|
Artikelnummer eine Zeile, siehe process_blocks) - die uebrigen Teilearten
|
|
sind dort bereits gruppiert, und ihre Zeilen unterscheiden sich meist in
|
|
Bezeichnung/details. Die Laenge gehoert zum Schluessel: Saeulen mit
|
|
unterschiedlicher Laenge bleiben getrennte Zeilen.
|
|
|
|
Die Eingabe bleibt unveraendert (die Hauptdatei wird aus denselben
|
|
items geschrieben)."""
|
|
gruppen = {}
|
|
for item in items:
|
|
key = (
|
|
item["teileart"], str(item["sivasnr"]), item["sivasnr_quoted"],
|
|
item["bezeichnung"], item["dispgruppe"], str(item["laenge_mm"]),
|
|
json.dumps(item["details"], ensure_ascii=False, sort_keys=True),
|
|
)
|
|
g = gruppen.get(key)
|
|
if g is None:
|
|
g = dict(item, anzahl=0, ids=[])
|
|
gruppen[key] = g
|
|
g["anzahl"] += safe_int(item["anzahl"], 0)
|
|
for i in item.get("ids", []):
|
|
if i and i not in g["ids"]:
|
|
g["ids"].append(i)
|
|
ergebnis = list(gruppen.values())
|
|
for nr, g in enumerate(ergebnis, 1):
|
|
g["nr"] = nr
|
|
return ergebnis
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 4:
|
|
print("Aufruf: python export_sivas.py <export_raw.json> <data_dir> <output.csv> [--mit-bezeichnung]")
|
|
sys.exit(1)
|
|
|
|
raw_json_path = sys.argv[1]
|
|
# 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]
|
|
mit_bezeichnung = "--mit-bezeichnung" in sys.argv[4:]
|
|
|
|
blocks = load_json(raw_json_path)
|
|
print(f"[export_sivas] {len(blocks)} Bloecke geladen.")
|
|
|
|
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:
|
|
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)")
|
|
|
|
# Zusaetzliche Datei mit zusammengefassten identischen Zeilen (siehe
|
|
# items_zusammenfassen) - output_csv selbst bleibt unveraendert.
|
|
items_zus = items_zusammenfassen(items)
|
|
output_csv_zus = zusammengefasster_dateiname(output_csv)
|
|
with open(output_csv_zus, "w", encoding="utf-8") as f:
|
|
f.write(header + "\n")
|
|
for item in items_zus:
|
|
f.write(format_csv_line(item) + "\n")
|
|
print(f"[export_sivas] Zusammengefasste CSV geschrieben: {output_csv_zus} "
|
|
f"({len(items_zus)} Zeilen)")
|
|
|
|
# Zweite, lesbare Kopie fuer die manuelle Pruefung (Merkmalnummer +
|
|
# Bezeichnung aus cfg/Merkmalsstamm_in_Sivas.xlsx) - wird von Sivas
|
|
# selbst NICHT eingelesen, nur output_csv ist der eigentliche Import.
|
|
# Nur bei explizitem --mit-bezeichnung-Schalter (Dialog CSV_EXPORT_KONFIG).
|
|
if mit_bezeichnung:
|
|
merkmalstamm = lade_merkmalstamm()
|
|
output_csv_lesbar = lesbarer_dateiname(output_csv)
|
|
with open(output_csv_lesbar, "w", encoding="utf-8") as f:
|
|
f.write(header + "\n")
|
|
for item in items:
|
|
details = details_mit_bezeichnung(item["details"], merkmalstamm)
|
|
f.write(format_csv_line(item, details=details) + "\n")
|
|
print(f"[export_sivas] Lesbare Kopie geschrieben: {output_csv_lesbar}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|