[FIX] Export: Schnittpunkte zwischen Kreiseln werden erkannt und Weichen entsprechend gezählt und als Teil mit Merkmalen im Export befüllt - nur Kreisel mit gleicher Höhe können einen Schnittpunkt für Weiche bilden - Funktion der Weichenzählung manuell aufrufbar; [FIX] Eckrad nun korrekt angelegt und exportiert

This commit is contained in:
2026-10-14 07:42:08 +02:00
parent 6044c35957
commit 8039251bb8
18 changed files with 1452 additions and 481 deletions
+21 -5
View File
@@ -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()
+79
View File
@@ -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-/
+38 -3
View File
@@ -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()
@@ -297,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):
@@ -414,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.
@@ -448,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,
@@ -826,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
@@ -986,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: