Merge remote-tracking branch 'origin/master'
Konflikt in menu/SSG_LIB.cui (generiert): Stand von origin/master uebernommen, nur der Menuepunkt "Testanlage test_system" (MM_00210) ergaenzt. SSG_LIB.mnu ohne Konflikt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+21
-5
@@ -40,6 +40,8 @@ from export_neighbors import (
|
||||
compute_sensor_zuordnung,
|
||||
compute_scanner_nearest_separator,
|
||||
compute_kreisel_touch_switches,
|
||||
lisp_touch_switches,
|
||||
load_lisp_kreisel_weichen,
|
||||
compute_sep_kette,
|
||||
compute_kreisel_umlauf,
|
||||
SENSOR_TEILEARTEN,
|
||||
@@ -952,7 +954,9 @@ def pruefe_ases_in_beiden_containern(items, dbg=None):
|
||||
return auffaellig
|
||||
|
||||
|
||||
def process_blocks(blocks, lookup, dbg=None):
|
||||
def process_blocks(blocks, lookup, dbg=None, lisp_weichen=None):
|
||||
# lisp_weichen = Inhalt von kreisel_weichen.json (load_lisp_kreisel_weichen)
|
||||
# oder None - dann werden die Kreisel-Beruehrpunkte hier selbst gerechnet.
|
||||
# x_origin_mm/y_origin_mm einmalig fuer diesen Exportlauf aufloesen (siehe
|
||||
# export_planquadrat.resolve_origins): automatisch aus den Bloecken, falls
|
||||
# nicht explizit in [Planquadrate] konfiguriert.
|
||||
@@ -1535,9 +1539,15 @@ def process_blocks(blocks, lookup, dbg=None):
|
||||
# compute_kreisel_umlauf (damit die Weiche als Verzweigungspunkt in
|
||||
# BEIDEN beruehrenden Kreisel-Umlaeufen auftaucht) als auch in
|
||||
# build_kreisel_weiche_items (das die tatsaechlichen Zeilen erzeugt).
|
||||
touch_switches = compute_kreisel_touch_switches(
|
||||
items, NEIGHBOR_TOLERANCE_MM, durchmesser_mm=KREISEL_DURCHMESSER_MM,
|
||||
element_box_mm=ELEMENT_BOX_MM, dbg=dbg)
|
||||
# Quelle ist der LISP-Export (kreisel_weichen.json: Kreisel UND Eckrad,
|
||||
# nur gleiche Hoehe, Kollisionen getrennt); fehlt die Datei, rechnet
|
||||
# compute_kreisel_touch_switches wie bisher (nur Kreisel, reine XY-Pruefung).
|
||||
if lisp_weichen is not None:
|
||||
touch_switches = lisp_touch_switches(lisp_weichen, element_box_mm=ELEMENT_BOX_MM, dbg=dbg)
|
||||
else:
|
||||
touch_switches = compute_kreisel_touch_switches(
|
||||
items, NEIGHBOR_TOLERANCE_MM, durchmesser_mm=KREISEL_DURCHMESSER_MM,
|
||||
element_box_mm=ELEMENT_BOX_MM, dbg=dbg)
|
||||
|
||||
# Umlauf-Reihenfolge an jedem Kreisel: freie Separatoren, BTMT-Stationen
|
||||
# UND die AS/ES-Enden andockender Ketten (alle zusammen KREISEL_UMLAUF_
|
||||
@@ -1693,7 +1703,13 @@ def main():
|
||||
dbg_file = None
|
||||
dbg = None
|
||||
|
||||
items = process_blocks(blocks, lookup, dbg=dbg)
|
||||
lisp_weichen = load_lisp_kreisel_weichen(raw_json_path)
|
||||
if lisp_weichen is not None:
|
||||
print(f"[export_csv] Kreisel-Weichen aus LISP: "
|
||||
f"{len(lisp_weichen['beruehrungen'])} Beruehrung(en), "
|
||||
f"{len(lisp_weichen['kollisionen'])} Kollision(en).")
|
||||
|
||||
items = process_blocks(blocks, lookup, dbg=dbg, lisp_weichen=lisp_weichen)
|
||||
|
||||
if dbg_file:
|
||||
dbg_file.close()
|
||||
|
||||
@@ -47,6 +47,7 @@ MIN_PARTNER) - Ergebnis ist die CSV-Spalte "Fehler" in export_csv.py.
|
||||
Nur von export_csv.py genutzt (EXPORTCSV), nicht von export_sivas.py.
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
|
||||
@@ -1091,6 +1092,84 @@ def compute_kreisel_touch_switches(items, tolerance_mm, durchmesser_mm=800.0,
|
||||
return switches
|
||||
|
||||
|
||||
# Dateiname der von LISP (csv:kreisel-weichen-schreiben, export.lsp) neben
|
||||
# export_raw.json geschriebenen Kreisel-Beruehrpunkte.
|
||||
LISP_KREISEL_WEICHEN_DATEI = "kreisel_weichen.json"
|
||||
|
||||
|
||||
def load_lisp_kreisel_weichen(raw_json_path):
|
||||
"""Liest kreisel_weichen.json aus dem Ordner von raw_json_path.
|
||||
|
||||
Die Datei schreibt der LISP-Export (KreiselInsert.lsp kreisel-bp-*):
|
||||
Kreisel UND Eckraeder, nur gleiche Hoehe, mehrere Punkte je Paar moeglich,
|
||||
Ueberschneidungen getrennt als "kollisionen". Rueckgabe: dict mit
|
||||
"beruehrungen"/"kollisionen" oder None, wenn die Datei fehlt/unlesbar ist
|
||||
(dann rechnet der Aufrufer selbst - compute_kreisel_touch_switches)."""
|
||||
pfad = os.path.join(os.path.dirname(os.path.abspath(raw_json_path)),
|
||||
LISP_KREISEL_WEICHEN_DATEI)
|
||||
if not os.path.exists(pfad):
|
||||
return None
|
||||
try:
|
||||
with open(pfad, encoding="utf-8", errors="replace") as f:
|
||||
daten = json.load(f)
|
||||
except (OSError, ValueError):
|
||||
return None
|
||||
if not isinstance(daten, dict):
|
||||
return None
|
||||
daten.setdefault("beruehrungen", [])
|
||||
daten.setdefault("kollisionen", [])
|
||||
return daten
|
||||
|
||||
|
||||
def lisp_touch_switches(lisp_daten, element_box_mm=None, dbg=None):
|
||||
"""Baut aus den LISP-Beruehrpunkten (load_lisp_kreisel_weichen) dieselben
|
||||
Switch-dicts wie compute_kreisel_touch_switches - damit laufen
|
||||
compute_kreisel_umlauf und build_kreisel_weiche_items unveraendert.
|
||||
|
||||
Partner-ID = ID-Attribut (= teileid), ohne ID der Handle. Mehrere Punkte
|
||||
eines Paares bekommen eindeutige Schluessel (WEICHE#a#b, WEICHE#a#b#2, ...).
|
||||
Die Box wird an achse_rot_grad (Radiale des beruehrenden Rades) ausgerichtet.
|
||||
Kollisionen werden NICHT zu Weichen, nur ins Debuglog geschrieben."""
|
||||
if element_box_mm is None:
|
||||
element_box_mm = load_element_box_mm()
|
||||
box_l, box_b, box_h = element_box_mm
|
||||
switches = []
|
||||
keys = set()
|
||||
if dbg:
|
||||
dbg("")
|
||||
dbg("=== Kreisel-Beruehrpunkte -> ILS-Weichen (aus LISP kreisel_weichen.json) ===")
|
||||
for b in lisp_daten.get("beruehrungen", []):
|
||||
id_a = b.get("a") or b.get("a_handle", "")
|
||||
id_b = b.get("b") or b.get("b_handle", "")
|
||||
key = f"WEICHE#{id_a}#{id_b}"
|
||||
n = 1
|
||||
while key in keys:
|
||||
n += 1
|
||||
key = f"WEICHE#{id_a}#{id_b}#{n}"
|
||||
keys.add(key)
|
||||
dx, dy, dz = _oriented_box_aabb(math.radians(float(b.get("achse_rot_grad", 0.0))),
|
||||
box_l, box_b, box_h)
|
||||
sw = {
|
||||
"key": key,
|
||||
"x": float(b.get("x", 0.0)), "y": float(b.get("y", 0.0)), "z": float(b.get("z", 0.0)),
|
||||
"dx": dx, "dy": dy, "dz": dz,
|
||||
"nachbarn": [id_a, id_b],
|
||||
"achse_von": b.get("achse_von", "") or id_a,
|
||||
"beruehrung": b.get("art", ""),
|
||||
}
|
||||
switches.append(sw)
|
||||
if dbg:
|
||||
dbg(f" {id_a} <-> {id_b}: {sw['beruehrung']} ({b.get('detail', '')}) "
|
||||
f"bei ({sw['x']:.0f}, {sw['y']:.0f}, {sw['z']:.0f})")
|
||||
if dbg:
|
||||
for k in lisp_daten.get("kollisionen", []):
|
||||
dbg(f" KOLLISION {k.get('a', '')} <-> {k.get('b', '')}: {k.get('art', '')} "
|
||||
f"({k.get('detail', '')}) bei ({float(k.get('x', 0)):.0f}, "
|
||||
f"{float(k.get('y', 0)):.0f})")
|
||||
dbg(f"Erzeugte ILS-Weichen: {len(switches)}")
|
||||
return switches
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Kreisel <-> Strecken-Enden: verfeinerte Kollisionsbox fuer compute_neighbor_
|
||||
# ids (die "Nachbarn"-Spalte einer GF/VF-Strecke soll nur an ihrem echten AS-/
|
||||
|
||||
+55
-17
@@ -63,6 +63,7 @@ from export_blockpatterns import (load_patterns, load_export_cfg, cfg_path_from_
|
||||
matches_any, csv_quote, safe_float, safe_int)
|
||||
from merkmalstamm import lade_merkmalstamm, bezeichnung_fuer
|
||||
import sivas_mapping
|
||||
from export_neighbors import load_lisp_kreisel_weichen
|
||||
|
||||
BLOCKPATTERNS = load_patterns()
|
||||
|
||||
@@ -113,17 +114,17 @@ def ja_nein(flag_1_0):
|
||||
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"
|
||||
def mm_zu_m(wert_mm):
|
||||
"""Wandelt ein mm-Attribut (z.B. Eckrad-HOEHE) in Meter um, gerundet wie
|
||||
3512 beim Kreisel ("2000" -> "2.0")."""
|
||||
return str(round(safe_float(wert_mm, 0.0) / 1000.0, 3))
|
||||
|
||||
|
||||
# --- 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,
|
||||
"mm_zu_m": mm_zu_m,
|
||||
}
|
||||
|
||||
|
||||
@@ -157,13 +158,15 @@ def build_gefaellestrecke_details(block, anzahl=1):
|
||||
|
||||
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.
|
||||
Schluessel zur Gruppierung gleichartiger Kreisel: Montagehoehe (Merkmal
|
||||
2952), 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.
|
||||
Die Hoehe wird numerisch verglichen ("4.820" == "4.82").
|
||||
"""
|
||||
attribs = block.get("attribs", {})
|
||||
return (
|
||||
round(safe_float(attribs.get("MONTAGEHOEHE_m", "0"), 0.0), 3),
|
||||
attribs.get("KREISELART", "STANDARD"),
|
||||
attribs.get("ABSTAND", "2300"),
|
||||
attribs.get("ANZAHL_SEPARATOR", attribs.get("N_SEPARATOREN", "2")),
|
||||
@@ -176,12 +179,13 @@ def kreisel_signature(block):
|
||||
|
||||
def eckrad_signature(block):
|
||||
"""
|
||||
Schluessel zur Gruppierung gleichartiger Eckraeder: Geruest fuer
|
||||
Einzelmodul/Geruestoption entscheiden, ob Eckraeder in derselben
|
||||
Sivas-Zeile summiert werden.
|
||||
Schluessel zur Gruppierung gleichartiger Eckraeder: Hoehe (Merkmal 2950)
|
||||
und Geruest fuer Einzelmodul/Geruestoption entscheiden, ob Eckraeder in
|
||||
derselben Sivas-Zeile summiert werden.
|
||||
"""
|
||||
attribs = block.get("attribs", {})
|
||||
return (
|
||||
mm_zu_m(attribs.get("HOEHE", "0")),
|
||||
attribs.get("GERUEST_EINZELMODUL", "0"),
|
||||
attribs.get("GERUEST_TYP", "Schoenenberger Geruest"),
|
||||
)
|
||||
@@ -294,6 +298,7 @@ def build_variofoerderer_details(block):
|
||||
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")
|
||||
SIVAS_MAPPING_WEICHEN = sivas_mapping.load_section_mapping("weichen")
|
||||
|
||||
|
||||
def build_kreisel_details(block):
|
||||
@@ -315,8 +320,8 @@ def build_kreisel_details(block):
|
||||
|
||||
|
||||
def build_eckrad_details(block):
|
||||
"""Merkmale-Dict fuer ein ILS Eckrad. Vollstaendig aus cfg/sivas.cfg
|
||||
[eckrad] (keine berechneten Werte)."""
|
||||
"""Merkmale-Dict fuer eine Gruppe gleichartiger ILS Eckraeder.
|
||||
Vollstaendig aus cfg/sivas.cfg [eckrad] (2950 per Transform mm_zu_m)."""
|
||||
attribs = block.get("attribs", {})
|
||||
return sivas_mapping.anwenden(SIVAS_MAPPING_ECKRAD, attribs, TRANSFORMS)
|
||||
|
||||
@@ -411,7 +416,9 @@ ILS_SIVASNR = {
|
||||
}
|
||||
|
||||
|
||||
def process_blocks(blocks):
|
||||
def process_blocks(blocks, anzahl_weichen_ils=0):
|
||||
# anzahl_weichen_ils = Anzahl Kreisel-Beruehrpunkte aus dem LISP-Export
|
||||
# (kreisel_weichen.json, siehe main) -> Merkmal 4403 der Automation-Zeile.
|
||||
"""
|
||||
Verarbeitet alle Bloecke.
|
||||
|
||||
@@ -445,7 +452,7 @@ def process_blocks(blocks):
|
||||
"sep_kreisel_ohne_pin": 0,
|
||||
"anzahl_verbindungskreisel": 0,
|
||||
"laengen_kreisel": 0.0,
|
||||
"anzahl_weichen_ils": 0,
|
||||
"anzahl_weichen_ils": anzahl_weichen_ils,
|
||||
"anzahl_eckraeder": 0,
|
||||
"anzahl_gefaellestrecken": 0,
|
||||
"anzahl_variofoerderer": 0,
|
||||
@@ -823,6 +830,30 @@ def process_blocks(blocks):
|
||||
})
|
||||
nr += 1
|
||||
|
||||
# ILS 2.0 Weiche (6216): EINE Summenzeile fuer alle Kreisel-Beruehrpunkte
|
||||
# (kreisel_weichen.json des LISP-Exports, Anzahl = anzahl_weichen_ils).
|
||||
# Ein Beruehrpunkt = eine Weichenkombination aus 1x aktiv (3371) + 1x
|
||||
# passiv (3372); die Zuordnung Merkmal -> Wert steht in sivas.cfg
|
||||
# [weichen], beide lesen ANZAHL_WEICHEN. Anzahl-Spalte 1 wie bei der
|
||||
# Automation-Zeile - die Gesamtanzahlen stecken in den Merkmalen.
|
||||
if counters["anzahl_weichen_ils"] > 0:
|
||||
weiche_sivasnr, weiche_teileart = ILS_SIVASNR["ils_weiche"]
|
||||
items.append({
|
||||
"nr": nr,
|
||||
"teileart": weiche_teileart,
|
||||
"sivasnr": weiche_sivasnr,
|
||||
"sivasnr_quoted": True,
|
||||
"bezeichnung": "",
|
||||
"dispgruppe": 20,
|
||||
"anzahl": 1,
|
||||
"laenge_mm": "",
|
||||
"details": sivas_mapping.anwenden(
|
||||
SIVAS_MAPPING_WEICHEN,
|
||||
{"ANZAHL_WEICHEN": str(counters["anzahl_weichen_ils"])},
|
||||
TRANSFORMS),
|
||||
})
|
||||
nr += 1
|
||||
|
||||
# ILS Automation Summierungszeile (nur wenn ILS-Elemente vorhanden)
|
||||
has_ils = (counters["anzahl_kreisel"] > 0
|
||||
or counters["anzahl_eckraeder"] > 0
|
||||
@@ -983,7 +1014,14 @@ def main():
|
||||
blocks = load_json(raw_json_path)
|
||||
print(f"[export_sivas] {len(blocks)} Bloecke geladen.")
|
||||
|
||||
items, _ = process_blocks(blocks)
|
||||
# Kreisel-Beruehrpunkte (= ILS-Weichen) zaehlt der LISP-Export
|
||||
# (kreisel_weichen.json neben der Raw-Datei); fehlt sie, bleibt 4403 = 0.
|
||||
lisp_weichen = load_lisp_kreisel_weichen(raw_json_path)
|
||||
anzahl_weichen = len(lisp_weichen["beruehrungen"]) if lisp_weichen else 0
|
||||
if lisp_weichen is not None:
|
||||
print(f"[export_sivas] Kreisel-Weichen aus LISP: {anzahl_weichen}")
|
||||
|
||||
items, _ = process_blocks(blocks, anzahl_weichen_ils=anzahl_weichen)
|
||||
|
||||
header = "Nr;TeileArt;SivasNummer;Bezeichnung;Dispogruppe;Anzahl;Laenge[mm];IDs;details"
|
||||
with open(output_csv, "w", encoding="utf-8") as f:
|
||||
|
||||
@@ -109,9 +109,7 @@ def anwenden(section_mapping, attribs, transforms):
|
||||
Rueckgabe: dict merkmal -> wert. Ohne Transform wird der Rohwert zu str()
|
||||
gewandelt (bisheriges Verhalten der build_*_details-Funktionen: alles
|
||||
Merkmalnummer-Werte sind Strings). Ein Transform liefert seinen
|
||||
Rueckgabewert UNVERAENDERT (z.B. build_eckrad_details erwartet fuer
|
||||
"Geruest fuer Einzelmodul" ein echtes bool, kein "True"/"False"-String -
|
||||
json.dumps schreibt dann true/false statt eines Strings).
|
||||
Rueckgabewert UNVERAENDERT (er ist selbst fuer den Typ verantwortlich).
|
||||
"""
|
||||
ergebnis = {}
|
||||
for merkmal, (attribut, alt_attribut, default, transform) in section_mapping.items():
|
||||
|
||||
Reference in New Issue
Block a user