Kreisel-Separatorliste (list_trackids) im CSV-Export ergaenzt
Kreisel bekommen wie VF_n/GF_n ein Merkmal mit der Reihenfolge aller Separator-/BTMT-Beladung-/BTMT-Entladung-TeileIds entlang der Bahn, sortiert nach Winkel um die Kreiselachse (Start bei 0 Grad in Drehrichtung DREHRICHTUNG) - nutzt dieselbe Umlauf-Logik, die bisher nur die "Nachbarn"-Spalte gespeist hat. Merkmal-Schluessel einheitlich auf "list_trackids" umbenannt (vorher "Separatorlist" bei VF/GF). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ from export_csv import ( # noqa: E402
|
||||
build_strecke_schleus_items,
|
||||
map_separator_kette_items,
|
||||
resolve_umlauf_und_sepliste_nachbarn,
|
||||
write_kreisel_separatorliste_merkmale,
|
||||
)
|
||||
|
||||
DURCHMESSER_MM = 800.0
|
||||
@@ -51,6 +52,17 @@ def _separator(teileid, x, y):
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
Separator, siehe KREISEL_UMLAUF_TEILEARTEN."""
|
||||
return {
|
||||
"teileart": teileart,
|
||||
"teileid": teileid,
|
||||
"_bbox": {"cx": x, "cy": y, "cz": 0.0, "dx": 200.0, "dy": 150.0},
|
||||
}
|
||||
|
||||
|
||||
def _zwei_beruehrende_kreisel():
|
||||
"""Zwei Kreisel, deren AN8-Enden sich exakt beruehren (End-an-End, wie im
|
||||
Mubea-Fall: 'Kreis/Kreis'-Beruehrung an der jeweiligen AN8-Kappe).
|
||||
@@ -280,3 +292,108 @@ class TestSeplisteNachbarnAufgeloest:
|
||||
assert sep_nachbarn == {as_item["teileid"], es_item["teileid"]}, (
|
||||
f'SEP-Zeile hat Nachbarn {sep_nachbarn!r}, erwartet '
|
||||
f'{{{as_item["teileid"]!r}, {es_item["teileid"]!r}}}')
|
||||
|
||||
|
||||
class TestKreiselUmlaufStartetBeiNullGrad:
|
||||
"""compute_kreisel_umlauf muss die Umlauf-Liste je Kreisel bei Winkel
|
||||
0 Grad (Kapsel-Achsrichtung, siehe _kreisel_capsule) beginnen lassen -
|
||||
Voraussetzung fuer eine reproduzierbare Separatorliste (Kundenwunsch:
|
||||
Start immer bei 0 Grad, nicht bei einem beliebigen Listenanfang)."""
|
||||
|
||||
def _kreisel_mit_vier_separatoren(self, drehrichtung):
|
||||
# Kreisel entlang +X (Achse von x=400 bis x=1400, siehe _kreisel).
|
||||
# Vier Separatoren rundherum bei ca. 10/100/190/280 Grad um die
|
||||
# Kreiselmitte (900, 0) - keiner exakt auf 0 Grad, damit der Test
|
||||
# echte Rotation der sortierten Liste beweist statt eines Zufalls-
|
||||
# treffers bei Index 0.
|
||||
k = _kreisel("0001", 0.0, 0.0, 0.0, abstand=1000.0)
|
||||
k["attribs"]["DREHRICHTUNG"] = drehrichtung
|
||||
# r=350mm < Kreiselradius 400mm, damit alle vier Punkte innerhalb der
|
||||
# Kreisel-AABB liegen (axis x in [400,1400] +- radius 400 -> y in
|
||||
# [-400,400]); r=700 (ausserhalb) faellt bei ~90/270 Grad aus der Box.
|
||||
cx, cy, r = 900.0, 0.0, 350.0
|
||||
import math
|
||||
seps = []
|
||||
for name, grad in (("S010", 10.0), ("S100", 100.0),
|
||||
("S190", 190.0), ("S280", 280.0)):
|
||||
rad = math.radians(grad)
|
||||
seps.append(_separator(name, cx + r * math.cos(rad), cy + r * math.sin(rad)))
|
||||
return [k] + seps
|
||||
|
||||
def test_start_bei_0_grad_uzs(self):
|
||||
# UZS negiert den Rohwinkel vor der Normierung (siehe compute_kreisel_
|
||||
# umlauf - UZS entspricht dem alten "absteigend nach rohem Winkel
|
||||
# sortieren", das ueber die Negation+aufsteigend-Sortierung erreicht
|
||||
# wird): 10/100/190/280 Grad werden dadurch zu (normiert) 350/260/
|
||||
# 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,
|
||||
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}"
|
||||
assert keys == ["S280", "S190", "S100", "S010"]
|
||||
|
||||
def test_start_bei_0_grad_guz(self):
|
||||
# GUZ (kein Vorzeichenwechsel): der Rohwinkel steigt hier bereits
|
||||
# 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,
|
||||
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}"
|
||||
assert keys == ["S010", "S100", "S190", "S280"]
|
||||
|
||||
|
||||
class TestKreiselListTrackidsMerkmal:
|
||||
"""write_kreisel_separatorliste_merkmale schreibt die bei 0 Grad
|
||||
beginnende Umlaufreihenfolge (Separatoren, AS/ES-Enden, Kreisel-Kreisel-
|
||||
Weichen) als Merkmal "list_trackids" auf den Kreisel selbst (analog zum
|
||||
gleichnamigen Merkmal bei VF_n/GF_n, siehe write_separatorliste_merkmale)."""
|
||||
|
||||
def test_freie_separatoren_in_umlaufreihenfolge(self):
|
||||
k = _kreisel("0001", 0.0, 0.0, 0.0, abstand=1000.0)
|
||||
k["merkmale"] = {}
|
||||
sep_a = _separator("0020", 300.0, 300.0) # ~63 Grad
|
||||
sep_b = _separator("0021", 1400.0, -50.0) # nahe 0 Grad (Ende Achse)
|
||||
items = [k, sep_a, sep_b]
|
||||
|
||||
kreisel_umlauf = compute_kreisel_umlauf(
|
||||
items, sep_kette={}, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM)
|
||||
write_kreisel_separatorliste_merkmale(
|
||||
items, kreisel_umlauf, sep_kette_by_key={}, weiche_by_key={})
|
||||
|
||||
kreisel_item = items[0]
|
||||
assert kreisel_item["merkmale"]["list_trackids"] == "0021, 0020"
|
||||
|
||||
def test_kreisel_ohne_umlauf_punkte_bekommt_kein_merkmal(self):
|
||||
k = _kreisel("0001", 0.0, 0.0, 0.0, abstand=1000.0)
|
||||
k["merkmale"] = {}
|
||||
items = [k]
|
||||
kreisel_umlauf = compute_kreisel_umlauf(
|
||||
items, sep_kette={}, tolerance_mm=0.0, durchmesser_mm=DURCHMESSER_MM)
|
||||
write_kreisel_separatorliste_merkmale(
|
||||
items, kreisel_umlauf, sep_kette_by_key={}, weiche_by_key={})
|
||||
assert "list_trackids" not in items[0]["merkmale"]
|
||||
|
||||
def test_btmt_beladung_und_entladung_zaehlen_als_freie_punkte(self):
|
||||
"""BTMT Beladung/Entladung sind Stationen AUF der Bahn/Track wie ein
|
||||
Separator - sie muessen im Kreisel-Umlauf UND in list_trackids
|
||||
des Kreisels auftauchen (Kundenwunsch), nicht nur echte Separatoren."""
|
||||
k = _kreisel("0001", 0.0, 0.0, 0.0, abstand=1000.0)
|
||||
k["merkmale"] = {}
|
||||
sep = _separator("0020", 300.0, 300.0)
|
||||
bel = _btmt("ILS 2.0 BTMT Beladung", "0030", 900.0, 400.0)
|
||||
ent = _btmt("ILS 2.0 BTMT Entladung", "0031", 1400.0, -50.0)
|
||||
items = [k, sep, bel, ent]
|
||||
|
||||
kreisel_umlauf = compute_kreisel_umlauf(
|
||||
items, sep_kette={}, 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}")
|
||||
|
||||
write_kreisel_separatorliste_merkmale(
|
||||
items, kreisel_umlauf, sep_kette_by_key={}, weiche_by_key={})
|
||||
assert k["merkmale"]["list_trackids"] == "0031, 0020, 0030"
|
||||
|
||||
Reference in New Issue
Block a user