export_csv: AS/ES direkt aus ZZ_ASES_MARKER klassifizieren, Kreisel<->Strecke-Kollisionserzeugung entfernen

Stufe 2 (Python-Seite von "sepliste: Bauteil-IDs eintragen"): AS/ES-Kettenenden
sind jetzt ganz normale Items, direkt aus dem dauerhaften ZZ_ASES_MARKER
klassifiziert (TeileId/Position/Boundingbox vom Marker-Block selbst) - keine
Kollisionsberechnung mehr, die sie erfindet.

- lib/export_neighbors.py: compute_strecke_kreisel_schleus entfernt (suchte
  bisher per Kollisionstest den anschliessenden Kreisel und erzeugte dabei bei
  JEDEM Export eine neue TeileId fuer denselben physischen Punkt). AUSSCHLEUS_/
  EINSCHLEUS_TEILEART sind jetzt Teil von KREISEL_UMLAUF_TEILEARTEN - ein AS/ES
  bringt seine eigene Bounding-Box vom Marker mit und wird dadurch, wie jeder
  freie Separator, ueber die normale BBox-Ueberschneidung einem Kreisel
  zugeordnet; die gesonderte "naechster Kreisel per sep_kette"-Suche in
  compute_kreisel_umlauf (dessen sep_kette-Parameter damit entfaellt) ist
  ueberfluessig geworden. compute_sep_kette gibt je Eintrag zusaetzlich das
  Feld "id" weiter (Bauteil-TeileId, siehe vorheriger Commit).
- lib/export_csv.py: process_blocks klassifiziert ZZ_ASES_MARKER direkt (TYP-
  Attribut waehlt Ausschleus-/Einschleuselement); build_strecke_schleus_items
  entfaellt komplett. map_separator_kette_items -> map_sepliste_kette_items:
  ordnet jetzt AS/SEP/ES exakt per ID-Gleichheit zu (sepliste-Feld "id" gegen
  die TeileId der klassifizierten Items) statt per Koordinaten-Naehe - SEP
  faellt bei fehlendem "id"-Feld weiterhin auf den alten Naehe-Abgleich zurueck
  (Altbestand), AS/ES ohne "id" bleiben unaufgeloest.
- Neu: pruefe_ases_in_beiden_containern prueft am Ende von process_blocks, ob
  jede AS-/ES-Zeile sowohl in den TrackIds ihrer eigenen Strecke als auch in
  denen mindestens eines Kreisels auftaucht, und traegt bei Abweichung eine
  Warnung ein (Spalte "Warnungen") - Konsistenzpruefung fuer genau die beiden
  "Container", zu denen ein AS/ES gehoert.
- Aufgeraeumt (tote Routinen): _strecke_ends, _closest_pt_on_segment,
  _distinct_kreisel_ids (nur von den entfernten Funktionen genutzt), sowie der
  temporaere Diagnose-Filter DEBUG_HIGHLIGHT_TEILEIDS/dbg_highlight (an die
  jetzt behobene Koordinaten-Matching-Ambiguitaet gebunden, in seinem eigenen
  Kommentar bereits als Wegwerf-Code markiert). LISP-seitiger Dead-Code-Scan
  (export.lsp, ssg_ks_insert.lsp - die diese Woche bearbeiteten Module):
  keine Funde.

Verifiziert an den echten HundM05-Daten (results/export_raw.json, 45 Bloecke):
TeileId-Bereich luecken- und duplikatfrei 1-45 (vorher 1-55 mit Luecke 24-33 +
Zusatz-IDs 46-55), alle 10 AS/ES-IDs korrekt in ihrer Strecke UND im Kreisel
06 TrackIds, keine Warnungen der neuen Pruefung. Zwei Testdateien auf die neue
Architektur umgestellt (test_export_kreisel_umlauf.py: AS/ES als Item statt
Kollisions-Erzeugung, neue Klasse TestAsEsImKreiselUmlauf fuer die BBox-
Teilnahme; test_export_separatorliste.py: id-basierte Zuordnung, Klasse
TestMarkerIdWiederverwendung durch TestMapSeplisteKetteItemsFallback ersetzt
- die alte testete inzwischen entfernte Mint-Logik). Volle Suite: 224 bestanden
(4 vorbestehende, unabhaengige Fixture-Failures unveraendert).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Samer Ayadi
2026-09-22 14:48:45 +02:00
co-authored by Claude Sonnet 5
parent 719ffc2385
commit ab3958e0e4
5 changed files with 543 additions and 793 deletions
+85 -55
View File
@@ -22,8 +22,7 @@ from export_neighbors import ( # noqa: E402
compute_kreisel_umlauf,
)
from export_csv import ( # noqa: E402
build_strecke_schleus_items,
map_separator_kette_items,
map_sepliste_kette_items,
resolve_umlauf_und_sepliste_nachbarn,
write_kreisel_separatorliste_merkmale,
write_separatorliste_merkmale,
@@ -53,6 +52,20 @@ def _separator(teileid, x, y):
}
def _ases(teileart, teileid, x, y, zuordnung):
"""AS-/ES-Item, wie es process_blocks direkt aus einem ZZ_ASES_MARKER
klassifiziert: eigene Bounding-Box vom Marker-Rechteck, "_zuordnung" =
ZUORDNUNG-Attribut (die eigene Strecke), teilnahmefaehig am Kreisel-
Umlauf ueber KREISEL_UMLAUF_TEILEARTEN wie ein freier Separator."""
return {
"teileart": teileart,
"teileid": teileid,
"_zuordnung": zuordnung,
"merkmale": {},
"_bbox": {"cx": x, "cy": y, "cz": 0.0, "dx": 200.0, "dy": 200.0},
}
def _btmt(teileart, teileid, x, y):
"""BTMT-Beladung/-Entladung-Stationsblock ("ILS 2.0 BTMT Beladung"/
"ILS 2.0 BTMT Entladung") - frei auf der Bahn platziert wie ein
@@ -105,7 +118,7 @@ class TestKreiselKreiselWeicheImUmlauf:
items = _zwei_beruehrende_kreisel()
switches = compute_kreisel_touch_switches(items, tolerance_mm=0.0,
durchmesser_mm=DURCHMESSER_MM)
umlauf = compute_kreisel_umlauf(items, sep_kette={}, tolerance_mm=0.0,
umlauf = compute_kreisel_umlauf(items, tolerance_mm=0.0,
durchmesser_mm=DURCHMESSER_MM,
touch_switches=switches)
weiche_key = switches[0]["key"]
@@ -130,7 +143,7 @@ class TestKreiselKreiselWeicheImUmlauf:
assert len(switches) == 1
weiche_key = switches[0]["key"]
umlauf = compute_kreisel_umlauf(items, sep_kette={}, tolerance_mm=50.0,
umlauf = compute_kreisel_umlauf(items, tolerance_mm=50.0,
durchmesser_mm=DURCHMESSER_MM,
touch_switches=switches)
@@ -166,7 +179,7 @@ class TestKreiselBoundsRotationsUnabhaengig:
k = self._langer_kreisel_90grad()
sep = _separator("9001", 0.0, 10000.0) # liegt mitten auf der Achse
items = [k, sep]
umlauf = compute_kreisel_umlauf(items, sep_kette={}, tolerance_mm=200.0,
umlauf = compute_kreisel_umlauf(items, tolerance_mm=200.0,
durchmesser_mm=DURCHMESSER_MM)
keys = [p[0] for p in umlauf["0002"]]
assert "9001" in keys, "Separator auf der gedrehten Kreiselachse wurde nicht erkannt"
@@ -179,7 +192,7 @@ class TestKreiselBoundsRotationsUnabhaengig:
k = self._langer_kreisel_90grad()
sep = _separator("0065", 958.0, 5100.0)
items = [k, sep]
umlauf = compute_kreisel_umlauf(items, sep_kette={}, tolerance_mm=200.0,
umlauf = compute_kreisel_umlauf(items, tolerance_mm=200.0,
durchmesser_mm=DURCHMESSER_MM)
keys = [p[0] for p in umlauf["0002"]]
assert "0065" not in keys, (
@@ -191,26 +204,24 @@ class TestSeplisteNachbarnAufgeloest:
"""Regressionstest fuer einen dritten realen Mubea-Befund: die meisten
Separatoren blieben trotz sepliste-XDATA komplett ohne "Nachbarn".
Architektur (nach dem Umbau "Proxy liefert Zeilen, sepliste nur
Reihenfolge"): die in einer Kette verpackten Separatoren liegen bereits als
echte (Proxy-)Separator_SP-Items vor (der LISP-Export fuegt je verpacktem
Symbol eine temporaere Kopie ein). map_separator_kette_items erzeugt daher
KEINE neuen Zeilen mehr, sondern ordnet jedem sepliste-SEP das vorhandene
Separator-Item zu (Match ueber Wrapper-ID + INSERT-Punkt) und traegt es in
sep_kette_by_key ein; die AS/ES-Zeilen kommen weiter aus
build_strecke_schleus_items. resolve_umlauf_und_sepliste_nachbarn setzt
dann die Nachbarn als ECHTE TeileIds (nicht die rohen sep_kette-Schluessel,
Architektur: SEP-Eintraege liegen bereits als echte (Proxy-)Separator_SP-
Items vor (der LISP-Export fuegt je verpacktem Symbol eine temporaere
Kopie ein), AS-/ES-Eintraege als eigene Items, direkt aus dem dauerhaften
ZZ_ASES_MARKER klassifiziert (siehe export_csv.py process_blocks) - beide
tragen bereits ihre TeileId als sepliste-Feld "id". map_sepliste_kette_
items erzeugt daher KEINE neuen Zeilen, sondern ordnet jedem sepliste-
Eintrag sein vorhandenes Item exakt per ID zu und traegt es in
sep_kette_by_key ein. resolve_umlauf_und_sepliste_nachbarn setzt dann die
Nachbarn als ECHTE TeileIds (nicht die rohen sep_kette-Schluessel,
frueherer Bug)."""
def _sepliste_kette(self):
"""Ein Kreisel + eine Gefaellestrecke mit sepliste AS(1) -> SEP(2) ->
ES(3) PLUS das bereits vorhandene Proxy-Separator-Item fuer den SEP
(wie es der freie-Separator-Pfad in process_blocks erzeugt: TeileId,
Merkmal "Zuordnung" = Wrapper-ID, INSERT-Punkt _x/_y an der sepliste-
SEP-Koordinate). "nachbarn" auf der Strecke simuliert das Ergebnis von
compute_neighbor_ids (BBox-Ueberschneidung Strecke <-> Kreisel) - die
Vorbedingung, unter der compute_strecke_kreisel_schleus die AS/ES-
Schleuselemente erzeugt."""
ES(3), PLUS die drei bereits vorhandenen Items, die diese Eintraege
vertreten (AS/ES ueber den ZZ_ASES_MARKER, SEP ueber den freien-
Separator-Pfad in process_blocks) - jeder sepliste-Eintrag traegt
bereits die TeileId seines Items als Feld "id" (siehe
csv:asmark-ids-setzen/csv:sepliste-ids-setzen, Lisp/export.lsp)."""
k = _kreisel("0001", 0.0, 0.0, 0.0, abstand=1000.0)
strecke = {
"teileart": "ILS 2.0 Gefaellestrecke",
@@ -220,13 +231,14 @@ class TestSeplisteNachbarnAufgeloest:
"_bbox": {"cx": 5000.0, "cy": 0.0, "cz": 0.0, "dx": 8000.0, "dy": 300.0},
"k1": "", "k2": "",
"sepliste": [
{"typ": "AS", "lfdnr": 1, "x": 1500.0, "y": 0.0},
{"typ": "SEP", "lfdnr": 2, "x": 5000.0, "y": 0.0},
{"typ": "ES", "lfdnr": 3, "x": 8500.0, "y": 0.0},
{"typ": "AS", "lfdnr": 1, "x": 1500.0, "y": 0.0, "id": "0056"},
{"typ": "SEP", "lfdnr": 2, "x": 5000.0, "y": 0.0, "id": "0055"},
{"typ": "ES", "lfdnr": 3, "x": 8500.0, "y": 0.0, "id": "0057"},
],
}
# Proxy-Separator-Item (bereits vorhanden), Zuordnung = Wrapper-ID 0010,
# INSERT-Punkt an der sepliste-SEP-Koordinate (5000, 0).
# SEP: Proxy-Separator-Item (INSERT-Punkt an der sepliste-SEP-
# Koordinate - nur als Fallback-Feld gefuehrt, die Zuordnung selbst
# laeuft exakt per ID).
proxy_sep = {
"teileart": "ILS 2.0 Separator",
"teileid": "0055",
@@ -235,7 +247,12 @@ class TestSeplisteNachbarnAufgeloest:
"_bbox": {"cx": 5000.0, "cy": 0.0, "cz": 0.0, "dx": 150.0, "dy": 210.0},
"nachbarn": "",
}
return [k, strecke, proxy_sep]
# AS/ES: eigene Items wie aus dem ZZ_ASES_MARKER klassifiziert.
as_item = _ases("ILS Ausschleuselement", "0056", 1500.0, 0.0, "0010")
as_item["nachbarn"] = ""
es_item = _ases("ILS Einschleuselement", "0057", 8500.0, 0.0, "0010")
es_item["nachbarn"] = ""
return [k, strecke, proxy_sep, as_item, es_item]
def test_keine_rohen_sep_kette_schluessel_in_nachbarn(self):
from export_neighbors import compute_sep_kette
@@ -246,20 +263,15 @@ class TestSeplisteNachbarnAufgeloest:
touch_switches = []
weiche_by_key = {}
kreisel_umlauf = compute_kreisel_umlauf(
items, sep_kette, tolerance_mm=50.0, durchmesser_mm=DURCHMESSER_MM,
items, tolerance_mm=50.0, durchmesser_mm=DURCHMESSER_MM,
touch_switches=touch_switches)
sep_kette_by_key = {}
build_strecke_schleus_items(
items, None, 100, sep_kette=sep_kette,
sep_kette_by_key=sep_kette_by_key)
map_separator_kette_items(
items, sep_kette, sep_kette_by_key=sep_kette_by_key)
sep_kette_by_key = map_sepliste_kette_items(items, sep_kette)
resolve_umlauf_und_sepliste_nachbarn(
items, kreisel_umlauf, sep_kette, sep_kette_by_key, weiche_by_key)
# AS/ES (neu erzeugt) + der bereits vorhandene Proxy-Separator.
# AS/ES + der bereits vorhandene Proxy-Separator.
sepliste_items = [it for it in items
if it.get("teileart") in
("ILS 2.0 Separator", "ILS Ausschleuselement", "ILS Einschleuselement")]
@@ -282,14 +294,9 @@ class TestSeplisteNachbarnAufgeloest:
items = self._sepliste_kette()
sep_kette = compute_sep_kette(items)
kreisel_umlauf = compute_kreisel_umlauf(
items, sep_kette, tolerance_mm=50.0, durchmesser_mm=DURCHMESSER_MM,
items, tolerance_mm=50.0, durchmesser_mm=DURCHMESSER_MM,
touch_switches=[])
sep_kette_by_key = {}
build_strecke_schleus_items(
items, None, 100, sep_kette=sep_kette,
sep_kette_by_key=sep_kette_by_key)
map_separator_kette_items(
items, sep_kette, sep_kette_by_key=sep_kette_by_key)
sep_kette_by_key = map_sepliste_kette_items(items, sep_kette)
resolve_umlauf_und_sepliste_nachbarn(
items, kreisel_umlauf, sep_kette, sep_kette_by_key, {})
@@ -299,6 +306,8 @@ class TestSeplisteNachbarnAufgeloest:
# Der SEP-Schluessel muss auf das vorhandene Proxy-Separator-Item zeigen.
assert sep_item["teileid"] == "0055", "sepliste-SEP nicht auf Proxy-Separator gemappt"
assert as_item["teileid"] == "0056", "sepliste-AS nicht auf sein Item gemappt"
assert es_item["teileid"] == "0057", "sepliste-ES nicht auf sein Item gemappt"
sep_nachbarn = {n.strip() for n in sep_item["nachbarn"].split(",") if n.strip()}
assert sep_nachbarn == {as_item["teileid"], es_item["teileid"]}, (
@@ -313,12 +322,7 @@ class TestSeplisteNachbarnAufgeloest:
from export_neighbors import compute_sep_kette
items = self._sepliste_kette()
sep_kette = compute_sep_kette(items)
sep_kette_by_key = {}
build_strecke_schleus_items(
items, None, 100, sep_kette=sep_kette,
sep_kette_by_key=sep_kette_by_key)
map_separator_kette_items(
items, sep_kette, sep_kette_by_key=sep_kette_by_key)
sep_kette_by_key = map_sepliste_kette_items(items, sep_kette)
write_separatorliste_merkmale(items, sep_kette_by_key)
strecke = next(it for it in items if it.get("teileid") == "0010")
@@ -363,7 +367,7 @@ class TestKreiselUmlaufStartetBeiNullGrad:
# 170/80 Grad - der kleinste (und damit Listenanfang) ist S280 (80
# Grad), NICHT S010 (350 Grad, liegt kurz VOR dem vollen Umlauf).
items = self._kreisel_mit_vier_separatoren("UZS")
umlauf = compute_kreisel_umlauf(items, sep_kette={}, tolerance_mm=0.0,
umlauf = compute_kreisel_umlauf(items, tolerance_mm=0.0,
durchmesser_mm=DURCHMESSER_MM)
keys = [p[0] for p in umlauf["0001"]]
assert keys[0] == "S280", f"Umlauf startet nicht bei 0 Grad (UZS): {keys}"
@@ -374,7 +378,7 @@ class TestKreiselUmlaufStartetBeiNullGrad:
# monoton mit 10/100/190/280 Grad - der Umlauf startet beim kleinsten
# (S010) und laeuft aufsteigend weiter.
items = self._kreisel_mit_vier_separatoren("GUZ")
umlauf = compute_kreisel_umlauf(items, sep_kette={}, tolerance_mm=0.0,
umlauf = compute_kreisel_umlauf(items, tolerance_mm=0.0,
durchmesser_mm=DURCHMESSER_MM)
keys = [p[0] for p in umlauf["0001"]]
assert keys[0] == "S010", f"Umlauf startet nicht bei 0 Grad (GUZ): {keys}"
@@ -396,7 +400,7 @@ class TestKreiselTrackidsSpalte:
items = [k, sep_a, sep_b]
kreisel_umlauf = compute_kreisel_umlauf(
items, sep_kette={}, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM)
items, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM)
write_kreisel_separatorliste_merkmale(
items, kreisel_umlauf, sep_kette_by_key={}, weiche_by_key={})
@@ -408,7 +412,7 @@ class TestKreiselTrackidsSpalte:
k["merkmale"] = {}
items = [k]
kreisel_umlauf = compute_kreisel_umlauf(
items, sep_kette={}, tolerance_mm=0.0, durchmesser_mm=DURCHMESSER_MM)
items, tolerance_mm=0.0, durchmesser_mm=DURCHMESSER_MM)
write_kreisel_separatorliste_merkmale(
items, kreisel_umlauf, sep_kette_by_key={}, weiche_by_key={})
assert "trackids" not in items[0]
@@ -425,7 +429,7 @@ class TestKreiselTrackidsSpalte:
items = [k, sep, bel, ent]
kreisel_umlauf = compute_kreisel_umlauf(
items, sep_kette={}, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM)
items, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM)
keys = [p[0] for p in kreisel_umlauf["0001"]]
assert set(keys) == {"0020", "0030", "0031"}, (
f"BTMT Beladung/Entladung fehlen im Kreisel-Umlauf: {keys}")
@@ -447,7 +451,7 @@ class TestKreiselTrackidsSpalte:
items = [k, sep, ein, aus]
kreisel_umlauf = compute_kreisel_umlauf(
items, sep_kette={}, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM)
items, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM)
keys = [p[0] for p in kreisel_umlauf["0001"]]
assert set(keys) == {"0020", "0040", "0041"}, (
f"Eingang/Ausgang fehlen im Kreisel-Umlauf: {keys}")
@@ -455,3 +459,29 @@ class TestKreiselTrackidsSpalte:
write_kreisel_separatorliste_merkmale(
items, kreisel_umlauf, sep_kette_by_key={}, weiche_by_key={})
assert k["trackids"] == "0041, 0020, 0040"
class TestAsEsImKreiselUmlauf:
"""Neu (Marker-Architektur): AS/ES sind jetzt ein ganz normaler
KREISEL_UMLAUF_TEILEARTEN-Eintrag - ihre eigene Bounding-Box (vom
ZZ_ASES_MARKER) entscheidet, ob sie im Umlauf eines Kreisels auftauchen,
genau wie ein freier Separator. Keine gesonderte Naeherungslogik mehr."""
def test_as_in_reichweite_taucht_im_umlauf_auf(self):
k = _kreisel("0001", 0.0, 0.0, 0.0, abstand=1000.0)
as_item = _ases("ILS Ausschleuselement", "0056", 300.0, 300.0, "0010")
items = [k, as_item]
umlauf = compute_kreisel_umlauf(
items, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM)
keys = [p[0] for p in umlauf["0001"]]
assert "0056" in keys, "AS-Item mit eigener BBox wurde nicht im Umlauf erkannt"
def test_es_ausserhalb_der_reichweite_fehlt_im_umlauf(self):
k = _kreisel("0001", 0.0, 0.0, 0.0, abstand=1000.0)
es_item = _ases("ILS Einschleuselement", "0057", 20000.0, 0.0, "0010")
items = [k, es_item]
umlauf = compute_kreisel_umlauf(
items, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM)
keys = [p[0] for p in umlauf["0001"]]
assert "0057" not in keys, (
"ES-Item weit ausserhalb der Kreiselbox wurde faelschlich erkannt")
+73 -97
View File
@@ -1,15 +1,19 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test_export_separatorliste.py - Merkmal "Separatorlist" einer Strecke.
test_export_separatorliste.py - "TrackIds"-Spalte einer Strecke (VF_n/GF_n).
Laeuft komplett ohne BricsCAD (reine Python-Logik, siehe lib/export_csv.py).
Eine Strecke (VF_n/GF_n) traegt in ihrer "sepliste"-XDATA nur x/y je AS-/SEP-/
ES-Eintrag (Rohkoordinaten aus der Zeichnung, kein TeileId). write_separator-
liste_merkmale() soll daraus, ueber die bereits von map_separator_kette_items
per x/y-Abgleich aufgeloesten sep_kette_by_key-Eintraege, das Merkmal
"Separatorlist" der Strecke fuellen: die TeileIds ihrer SEP-Eintraege (NICHT
AS/ES) in Baureihenfolge, kommagetrennt (z.B. "0052, 0053, 0054").
Eine Strecke (VF_n/GF_n) traegt in ihrer "sepliste"-XDATA je AS-/SEP-/ES-
Eintrag x/y sowie (seit csv:asmark-ids-setzen/csv:sepliste-ids-setzen,
Lisp/export.lsp) ein Feld "id" - die TeileId des Bauteils, das diesen
Eintrag in der Zeichnung vertritt (AS/ES ueber den dauerhaften
ZZ_ASES_MARKER, SEP ueber die beim Export ausgepackte temporaere
Separator_SP-Kopie). write_separatorliste_merkmale() soll daraus, ueber die
bereits von map_sepliste_kette_items per exaktem ID-Abgleich aufgeloesten
sep_kette_by_key-Eintraege, die Spalte "TrackIds" der Strecke fuellen: die
TeileIds von AS-Ende, SEP-Eintraegen und ES-Ende in Baureihenfolge,
kommagetrennt (z.B. "0056, 0052, 0053, 0057").
"""
import os
@@ -17,11 +21,9 @@ import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
from export_neighbors import compute_sep_kette, compute_kreisel_umlauf # noqa: E402
from export_neighbors import compute_sep_kette # noqa: E402
from export_csv import ( # noqa: E402
build_strecke_schleus_items,
map_separator_kette_items,
max_numeric_teileid,
map_sepliste_kette_items,
write_separatorliste_merkmale,
)
@@ -40,13 +42,27 @@ def _kreisel(teileid, x0, y0, rotation, abstand=1000.0):
}
def _ases(teileart, teileid, x, y, zuordnung):
"""AS-/ES-Item, wie es process_blocks direkt aus einem ZZ_ASES_MARKER
klassifiziert - siehe test_export_kreisel_umlauf.py fuer dasselbe
Fixture-Muster."""
return {
"teileart": teileart,
"teileid": teileid,
"_zuordnung": zuordnung,
"merkmale": {},
"nachbarn": "",
"_bbox": {"cx": x, "cy": y, "cz": 0.0, "dx": 200.0, "dy": 200.0},
}
def _strecke_mit_zwei_separatoren():
"""Ein Kreisel + eine Gefaellestrecke mit sepliste AS(1) -> SEP(2) ->
SEP(3) -> ES(4), PLUS die zwei bereits vorhandenen Proxy-Separator-Items
(wie sie der freie-Separator-Pfad in process_blocks erzeugt: TeileId,
Merkmal "Zuordnung" = Wrapper-ID, INSERT-Punkt _x/_y an der jeweiligen
sepliste-SEP-Koordinate) - siehe TestSeplisteNachbarnAufgeloest in
test_export_kreisel_umlauf.py fuer dasselbe Fixture-Muster."""
SEP(3) -> ES(4), PLUS die vier bereits vorhandenen Items, die diese
Eintraege vertreten (zwei Proxy-Separator-Items wie sie der freie-
Separator-Pfad in process_blocks erzeugt, sowie das AS-/ES-Item wie aus
dem ZZ_ASES_MARKER klassifiziert) - jeder sepliste-Eintrag traegt bereits
die TeileId seines Items als Feld "id"."""
k = _kreisel("0001", 0.0, 0.0, 0.0, abstand=1000.0)
strecke = {
"teileart": "ILS 2.0 Gefaellestrecke",
@@ -57,10 +73,10 @@ def _strecke_mit_zwei_separatoren():
"k1": "", "k2": "",
"merkmale": {"Anzahl_Separator": "2", "Anzahl_Scanner": "0"},
"sepliste": [
{"typ": "AS", "lfdnr": 1, "x": 1500.0, "y": 0.0},
{"typ": "SEP", "lfdnr": 2, "x": 4000.0, "y": 0.0},
{"typ": "SEP", "lfdnr": 3, "x": 6000.0, "y": 0.0},
{"typ": "ES", "lfdnr": 4, "x": 8500.0, "y": 0.0},
{"typ": "AS", "lfdnr": 1, "x": 1500.0, "y": 0.0, "id": "0056"},
{"typ": "SEP", "lfdnr": 2, "x": 4000.0, "y": 0.0, "id": "0052"},
{"typ": "SEP", "lfdnr": 3, "x": 6000.0, "y": 0.0, "id": "0053"},
{"typ": "ES", "lfdnr": 4, "x": 8500.0, "y": 0.0, "id": "0057"},
],
}
proxy_sep_1 = {
@@ -79,7 +95,9 @@ def _strecke_mit_zwei_separatoren():
"_bbox": {"cx": 6000.0, "cy": 0.0, "cz": 0.0, "dx": 150.0, "dy": 210.0},
"nachbarn": "",
}
return [k, strecke, proxy_sep_1, proxy_sep_2]
as_item = _ases("ILS Ausschleuselement", "0056", 1500.0, 0.0, "0010")
es_item = _ases("ILS Einschleuselement", "0057", 8500.0, 0.0, "0010")
return [k, strecke, proxy_sep_1, proxy_sep_2, as_item, es_item]
class TestSeparatorlisteMerkmal:
@@ -87,53 +105,36 @@ class TestSeparatorlisteMerkmal:
def test_separatorlist_in_baureihenfolge(self):
items = _strecke_mit_zwei_separatoren()
sep_kette = compute_sep_kette(items)
compute_kreisel_umlauf( # AS/ES muessen als Punkte existieren, sonst
items, sep_kette, tolerance_mm=50.0, # unveraendert fuer diesen Test
durchmesser_mm=DURCHMESSER_MM, touch_switches=[])
sep_kette_by_key = {}
build_strecke_schleus_items(
items, None, 100, sep_kette=sep_kette,
sep_kette_by_key=sep_kette_by_key)
map_separator_kette_items(
items, sep_kette, sep_kette_by_key=sep_kette_by_key)
sep_kette_by_key = map_sepliste_kette_items(items, sep_kette)
write_separatorliste_merkmale(items, sep_kette_by_key)
# write_separatorliste_merkmale fuellt seit dem Umbau das Top-Level-Feld
# write_separatorliste_merkmale fuellt das Top-Level-Feld
# item["trackids"] (CSV-Spalte "TrackIds") mit AS-Ende + Separatoren +
# ES-Ende in Baureihenfolge - NICHT mehr das Merkmal "Separatorlist" mit
# nur den SEP-Eintraegen. AS/ES bekommen ueber build_strecke_schleus_items
# eigene TeileIds (hier 0054/0055), die SEP-Proxies sind 0052/0053.
# ES-Ende in Baureihenfolge. AS/ES tragen ihre eigene TeileId (0056/
# 0057), die SEP-Proxies 0052/0053 - alle exakt per sepliste-Feld
# "id" aufgeloest, kein Minten mehr noetig.
strecke = next(it for it in items if it.get("teileid") == "0010")
assert strecke.get("trackids") == "0054, 0052, 0053, 0055", (
assert strecke.get("trackids") == "0056, 0052, 0053, 0057", (
f'trackids = {strecke.get("trackids")!r}, '
f'erwartet "0054, 0052, 0053, 0055" (AS, SEP, SEP, ES in Baureihenfolge)')
f'erwartet "0056, 0052, 0053, 0057" (AS, SEP, SEP, ES in Baureihenfolge)')
def test_vertauschte_proxy_reihenfolge_in_items_aendert_nichts(self):
"""Die Reihenfolge in "items" (bzw. in welcher Reihenfolge die Proxy-
Separatoren im Export auftauchen) darf die Separatorlist-Reihenfolge
nicht beeinflussen - massgeblich ist allein die lfdnr aus der
sepliste, nicht die Item-Liste."""
Separatoren im Export auftauchen) darf die TrackIds-Reihenfolge nicht
beeinflussen - massgeblich ist allein die lfdnr aus der sepliste,
nicht die Item-Liste."""
items = _strecke_mit_zwei_separatoren()
k, strecke, proxy_1, proxy_2 = items
items = [k, strecke, proxy_2, proxy_1] # vertauscht
k, strecke, proxy_1, proxy_2, as_item, es_item = items
items = [k, strecke, proxy_2, proxy_1, es_item, as_item] # vertauscht
sep_kette = compute_sep_kette(items)
compute_kreisel_umlauf(
items, sep_kette, tolerance_mm=50.0,
durchmesser_mm=DURCHMESSER_MM, touch_switches=[])
sep_kette_by_key = {}
build_strecke_schleus_items(
items, None, 100, sep_kette=sep_kette,
sep_kette_by_key=sep_kette_by_key)
map_separator_kette_items(
items, sep_kette, sep_kette_by_key=sep_kette_by_key)
sep_kette_by_key = map_sepliste_kette_items(items, sep_kette)
write_separatorliste_merkmale(items, sep_kette_by_key)
strecke = next(it for it in items if it.get("teileid") == "0010")
assert strecke.get("trackids") == "0054, 0052, 0053, 0055"
assert strecke.get("trackids") == "0056, 0052, 0053, 0057"
def test_strecke_ohne_sepliste_bleibt_unveraendert(self):
"""Altbestand ohne "sepliste"-XDATA darf kein leeres/kaputtes
@@ -148,57 +149,32 @@ class TestSeparatorlisteMerkmal:
assert "Separatorlist" not in strecke["merkmale"]
class TestMarkerIdWiederverwendung:
"""build_strecke_schleus_items darf die ID eines dauerhaften
ZZ_ASES_MARKER (siehe csv:asmark-aktualisieren, Lisp/export.lsp; im
sepliste-Eintrag als Feld "id" gespiegelt) nicht ignorieren und
stattdessen eine neue erfinden - genau das erzeugte die vom Nutzer
gemeldete TeileId-Luecke (Marker-ID blieb ungenutzt, gleichzeitig
entstand fuer denselben Punkt eine zusaetzliche, hoehere ID)."""
class TestMapSeplisteKetteItemsFallback:
"""map_sepliste_kette_items faellt fuer SEP-Eintraege OHNE "id"-Feld
(Altbestand-Export vor der Marker-Funktion) weiterhin auf den
Naehe-Abgleich zurueck - AS/ES ohne "id" bleiben dagegen unaufgeloest
(fuer sie existiert seit dem Wegfall der Kreisel<->Strecke-Kollisions-
erzeugung kein Item mehr, das sie vertreten koennte)."""
def test_marker_id_wiederverwendet_statt_neu_gemintet(self):
def test_sep_ohne_id_faellt_auf_naehe_zurueck(self):
items = _strecke_mit_zwei_separatoren()
strecke = next(it for it in items if it.get("teileid") == "0010")
# AS = sepliste[0] (lfdnr=1), ES = sepliste[-1] (lfdnr=4) - siehe
# ssg-sepliste-sammeln (Lisp/ssg_ks_insert.lsp): AS immer zuerst,
# ES immer zuletzt. Bewusst NIEDRIGER als die Proxy-Separator-IDs
# 0052/0053 gewaehlt - genau wie im real beobachteten HundM05-Export
# (Marker 0024-0033, Separator-Proxies 0034-0045).
strecke["sepliste"][0]["id"] = "0024"
strecke["sepliste"][-1]["id"] = "0033"
for entry in strecke["sepliste"]:
if entry["typ"] == "SEP":
entry["id"] = ""
sep_kette = compute_sep_kette(items)
sep_kette_by_key = {}
build_strecke_schleus_items(
items, None, 100, sep_kette=sep_kette,
sep_kette_by_key=sep_kette_by_key)
sep_kette_by_key = map_sepliste_kette_items(items, sep_kette)
as_item = sep_kette_by_key["0010#1"]
es_item = sep_kette_by_key["0010#4"]
assert as_item["teileid"] == "0024", (
f'AS-Zeile hat teileid={as_item["teileid"]!r}, erwartet die '
f'wiederverwendete Marker-ID "0024"')
assert es_item["teileid"] == "0033", (
f'ES-Zeile hat teileid={es_item["teileid"]!r}, erwartet die '
f'wiederverwendete Marker-ID "0033"')
assert sep_kette_by_key["0010#2"]["teileid"] == "0052"
assert sep_kette_by_key["0010#3"]["teileid"] == "0053"
# Kein spaeterer Mint in DIESEM Lauf darf eine wiederverwendete
# Marker-ID erneut vergeben - der laufende Zaehler muss mindestens
# auf die hoechste wiederverwendete ID hochgezogen worden sein.
assert max_numeric_teileid(items) >= 33
def test_strecke_ohne_marker_id_mintet_wie_bisher(self):
"""Fehlt das "id"-Feld (Altbestand-Wrapper vor der Marker-Funktion,
oder Marker-Zuweisung in Lisp fehlgeschlagen), bleibt das Mint-
Verhalten unveraendert - dieselben TeileIds wie vor diesem Fix."""
def test_as_ohne_id_bleibt_unaufgeloest(self):
items = _strecke_mit_zwei_separatoren()
sep_kette = compute_sep_kette(items)
sep_kette_by_key = {}
build_strecke_schleus_items(
items, None, 100, sep_kette=sep_kette,
sep_kette_by_key=sep_kette_by_key)
strecke = next(it for it in items if it.get("teileid") == "0010")
strecke["sepliste"][0]["id"] = ""
as_item = sep_kette_by_key["0010#1"]
es_item = sep_kette_by_key["0010#4"]
assert as_item["teileid"] == "0054"
assert es_item["teileid"] == "0055"
sep_kette = compute_sep_kette(items)
sep_kette_by_key = map_sepliste_kette_items(items, sep_kette)
assert "0010#1" not in sep_kette_by_key