47724fcfe2
write_separatorliste_merkmale liess bisher nur "SEP"-Eintraege der sepliste in die "TrackIds"-Spalte einfliessen; die AS-/ES-Ausschleus- /Einschleuselemente an Anfang und Ende der Kette fehlten dadurch, obwohl sie beim Kreisel (compute_kreisel_umlauf) bereits Teil von dessen TrackIds sind. Jetzt werden alle sepliste-Eintraege (AS, SEP, ES) in Baureihenfolge aufgeloest. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
425 lines
20 KiB
Python
425 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
test_export_kreisel_umlauf.py - Kreisel-Kreisel-Weiche im Umlauf.
|
|
Laeuft komplett ohne BricsCAD (reine Python-Geometrie, siehe lib/export_neighbors.py).
|
|
|
|
Regressionstest fuer einen realen Mubea-Befund: zwei sich beruehrende Kreisel
|
|
erzeugen eine "ILS Weiche" am Beruehrpunkt (compute_kreisel_touch_switches).
|
|
Diese Weiche ist der Verzweigungspunkt zwischen beiden Kreiseln - ein
|
|
Separator direkt davor muss sie als Nachbarn bekommen, nicht (wie vor dem
|
|
Fix) einen Punkt vom jeweils ANDEREN Kreisel-Ende, weil die Weiche in
|
|
compute_kreisel_umlauf schlicht fehlte.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
|
|
|
|
from export_neighbors import ( # noqa: E402
|
|
compute_kreisel_touch_switches,
|
|
compute_kreisel_umlauf,
|
|
)
|
|
from export_csv import ( # noqa: E402
|
|
build_strecke_schleus_items,
|
|
map_separator_kette_items,
|
|
resolve_umlauf_und_sepliste_nachbarn,
|
|
write_kreisel_separatorliste_merkmale,
|
|
write_separatorliste_merkmale,
|
|
)
|
|
|
|
DURCHMESSER_MM = 800.0
|
|
RADIUS_MM = DURCHMESSER_MM / 2.0
|
|
|
|
|
|
def _kreisel(teileid, x0, y0, rotation, abstand=1000.0):
|
|
return {
|
|
"teileart": "ILS 2.0 Kreisel",
|
|
"teileid": teileid,
|
|
"x": x0, "y": y0, "z": 0.0,
|
|
"rotation": rotation,
|
|
"attribs": {"ABSTAND": str(abstand), "DREHRICHTUNG": "UZS"},
|
|
"_bbox": {"cx": x0, "cy": y0, "cz": 0.0,
|
|
"dx": abstand + DURCHMESSER_MM, "dy": DURCHMESSER_MM},
|
|
}
|
|
|
|
|
|
def _separator(teileid, x, y):
|
|
return {
|
|
"teileart": "ILS 2.0 Separator",
|
|
"teileid": teileid,
|
|
"_bbox": {"cx": x, "cy": y, "cz": 0.0, "dx": 150.0, "dy": 100.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
|
|
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).
|
|
|
|
Kreisel 1 liegt entlang +X ab x=0 (AN8-Mitte bei radius=400, SP8-Mitte bei
|
|
radius+abstand=1400). Kreisel 2 liegt entlang -X ab x=800 (AN8-Mitte bei
|
|
800-400=400) - beide AN8-Kappen beruehren sich bei x=400 (Kreis/Kreis).
|
|
"""
|
|
k1 = _kreisel("0001", 0.0, 0.0, 0.0)
|
|
k2 = _kreisel("0002", 800.0, 0.0, 180.0)
|
|
return [k1, k2]
|
|
|
|
|
|
class TestKreiselKreiselWeicheImUmlauf:
|
|
|
|
def test_touch_switch_erkannt(self):
|
|
items = _zwei_beruehrende_kreisel()
|
|
switches = compute_kreisel_touch_switches(items, tolerance_mm=0.0,
|
|
durchmesser_mm=DURCHMESSER_MM)
|
|
assert len(switches) == 1
|
|
sw = switches[0]
|
|
assert sorted(sw["nachbarn"]) == ["0001", "0002"]
|
|
assert sw["key"] == "WEICHE#0001#0002"
|
|
|
|
def test_weiche_in_beiden_umlaeufen(self):
|
|
"""Die Weiche muss als Punkt in BEIDEN Kreisel-Umlaeufen auftauchen -
|
|
sie ist der gemeinsame Verzweigungspunkt (K1->K2 und K2->K1)."""
|
|
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,
|
|
durchmesser_mm=DURCHMESSER_MM,
|
|
touch_switches=switches)
|
|
weiche_key = switches[0]["key"]
|
|
for tid in ("0001", "0002"):
|
|
keys = [p[0] for p in umlauf[tid]]
|
|
assert weiche_key in keys, f"Weiche fehlt im Umlauf von Kreisel {tid}"
|
|
|
|
def test_separator_bekommt_weiche_als_nachbar_nicht_anderen_kreisel(self):
|
|
"""Realer Mubea-Befund: ein Separator, der unmittelbar VOR der
|
|
Kreisel-Kreisel-Weiche liegt (auf der AN8-Seite von Kreisel 1), muss
|
|
die Weiche als Umlauf-Nachbarn bekommen - nicht einen Punkt, der
|
|
eigentlich zu Kreisel 2 gehoert (das war der Bug: die Weiche fehlte
|
|
in punkte_je_kreisel, wodurch beide Separatoren am Beruehrpunkt auf
|
|
denselben (falschen) Nachbarn verwiesen)."""
|
|
items = _zwei_beruehrende_kreisel()
|
|
# Separator dicht vor der Beruehrung (x=390), gehoert zu Kreisel 1.
|
|
sep = _separator("0036", 390.0, 0.0)
|
|
items.append(sep)
|
|
|
|
switches = compute_kreisel_touch_switches(items, tolerance_mm=0.0,
|
|
durchmesser_mm=DURCHMESSER_MM)
|
|
assert len(switches) == 1
|
|
weiche_key = switches[0]["key"]
|
|
|
|
umlauf = compute_kreisel_umlauf(items, sep_kette={}, tolerance_mm=50.0,
|
|
durchmesser_mm=DURCHMESSER_MM,
|
|
touch_switches=switches)
|
|
|
|
punkte_k1 = {p[0]: (p[1], p[2]) for p in umlauf["0001"]}
|
|
assert "0036" in punkte_k1, "Separator 0036 fehlt im Umlauf von Kreisel 1"
|
|
vorgaenger, nachfolger = punkte_k1["0036"]
|
|
assert weiche_key in (vorgaenger, nachfolger), (
|
|
f"Separator 0036 sollte die Kreisel-Kreisel-Weiche ({weiche_key}) "
|
|
f"als Umlauf-Nachbarn haben, hat aber vorgaenger={vorgaenger!r}, "
|
|
f"nachfolger={nachfolger!r}")
|
|
|
|
|
|
class TestKreiselBoundsRotationsUnabhaengig:
|
|
"""Regressionstest fuer einen zweiten realen Mubea-Befund: ein um 90 Grad
|
|
gedrehter, sehr langer Kreisel (Achse entlang Welt-Y statt Welt-X) bekam
|
|
in compute_kreisel_umlauf eine falsch orientierte Matching-Box (schmal in
|
|
Y, ueberbreit in X statt umgekehrt) - kreisel_bounds baute die Box bisher
|
|
IMMER mit dx=durchmesser+achs_len/dy=durchmesser, unabhaengig von der
|
|
tatsaechlichen Drehung. Echte anliegende Separatoren (weit entfernt in X,
|
|
aber auf der Kreiselachse in Y) fielen dadurch durch, waehrend entfernte,
|
|
zufaellig in der ueberbreiten X-Box liegende Separatoren faelschlich als
|
|
Kreisel-Nachbarn erkannt wurden (Mubea Kreisel 0002, 20m lang, Drehung 90)."""
|
|
|
|
def _langer_kreisel_90grad(self):
|
|
# Kreisel 0002 aus dem Mubea-Datensatz: x=0, y=4529.5, Drehung 90,
|
|
# ABSTAND=20020mm -> Achse verlaeuft entlang Welt-Y von y~4930 bis
|
|
# y~24950, nicht entlang Welt-X.
|
|
return _kreisel("0002", 0.0, 4529.5, 90.0, abstand=20020.0)
|
|
|
|
def test_separator_auf_der_gedrehten_achse_wird_erkannt(self):
|
|
"""Ein Separator, der WIRKLICH auf der (gedrehten) Kreiselachse liegt
|
|
(x nahe 0, y zwischen AN8- und SP8-Mitte), muss erkannt werden."""
|
|
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,
|
|
durchmesser_mm=DURCHMESSER_MM)
|
|
keys = [p[0] for p in umlauf["0002"]]
|
|
assert "9001" in keys, "Separator auf der gedrehten Kreiselachse wurde nicht erkannt"
|
|
|
|
def test_separator_weit_neben_der_achse_wird_nicht_erkannt(self):
|
|
"""Ein Separator, der (wie im Mubea-Fall Separator 0065/0066) 958mm
|
|
seitlich der schmalen Kreiselbreite (durchmesser=800mm, Radius 400mm)
|
|
liegt, gehoert NICHT zum Kreisel - er darf nicht ueber eine faelschlich
|
|
breite X-Box eingefangen werden."""
|
|
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,
|
|
durchmesser_mm=DURCHMESSER_MM)
|
|
keys = [p[0] for p in umlauf["0002"]]
|
|
assert "0065" not in keys, (
|
|
"Separator 958mm neben der Kreiselachse wurde faelschlich als "
|
|
"Kreisel-Nachbar erkannt (rotationsabhaengiger BBox-Bug)")
|
|
|
|
|
|
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,
|
|
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."""
|
|
k = _kreisel("0001", 0.0, 0.0, 0.0, abstand=1000.0)
|
|
strecke = {
|
|
"teileart": "ILS 2.0 Gefaellestrecke",
|
|
"teileid": "0010",
|
|
"x": 5000.0, "y": 0.0, "z": 0.0,
|
|
"nachbarn": "0001",
|
|
"_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},
|
|
],
|
|
}
|
|
# Proxy-Separator-Item (bereits vorhanden), Zuordnung = Wrapper-ID 0010,
|
|
# INSERT-Punkt an der sepliste-SEP-Koordinate (5000, 0).
|
|
proxy_sep = {
|
|
"teileart": "ILS 2.0 Separator",
|
|
"teileid": "0055",
|
|
"merkmale": {"Zuordnung": "0010"},
|
|
"_x": 5000.0, "_y": 0.0,
|
|
"_bbox": {"cx": 5000.0, "cy": 0.0, "cz": 0.0, "dx": 150.0, "dy": 210.0},
|
|
"nachbarn": "",
|
|
}
|
|
return [k, strecke, proxy_sep]
|
|
|
|
def test_keine_rohen_sep_kette_schluessel_in_nachbarn(self):
|
|
from export_neighbors import compute_sep_kette
|
|
items = self._sepliste_kette()
|
|
sep_kette = compute_sep_kette(items)
|
|
assert set(sep_kette) == {"0010#1", "0010#2", "0010#3"}
|
|
|
|
touch_switches = []
|
|
weiche_by_key = {}
|
|
kreisel_umlauf = compute_kreisel_umlauf(
|
|
items, sep_kette, 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)
|
|
|
|
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.
|
|
sepliste_items = [it for it in items
|
|
if it.get("teileart") in
|
|
("ILS 2.0 Separator", "ILS Ausschleuselement", "ILS Einschleuselement")]
|
|
assert len(sepliste_items) == 3, "AS/SEP/ES nicht alle vorhanden"
|
|
|
|
for it in sepliste_items:
|
|
for nachbar in it["nachbarn"].split(","):
|
|
nachbar = nachbar.strip()
|
|
if not nachbar:
|
|
continue
|
|
assert "#" not in nachbar, (
|
|
f'{it["teileart"]} {it["teileid"]}: "Nachbarn" enthaelt einen '
|
|
f'unaufgeloesten sep_kette-Schluessel ({nachbar!r}) statt '
|
|
f'einer TeileId')
|
|
|
|
def test_sep_zeile_hat_as_und_es_als_nachbarn(self):
|
|
"""Der Proxy-Separator (SEP-Position) muss nach der Aufloesung sowohl die
|
|
TeileId der AS- als auch der ES-Zeile als Nachbarn tragen."""
|
|
from export_neighbors import compute_sep_kette
|
|
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,
|
|
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)
|
|
resolve_umlauf_und_sepliste_nachbarn(
|
|
items, kreisel_umlauf, sep_kette, sep_kette_by_key, {})
|
|
|
|
as_item = sep_kette_by_key["0010#1"]
|
|
sep_item = sep_kette_by_key["0010#2"]
|
|
es_item = sep_kette_by_key["0010#3"]
|
|
|
|
# Der SEP-Schluessel muss auf das vorhandene Proxy-Separator-Item zeigen.
|
|
assert sep_item["teileid"] == "0055", "sepliste-SEP nicht auf Proxy-Separator gemappt"
|
|
|
|
sep_nachbarn = {n.strip() for n in sep_item["nachbarn"].split(",") if n.strip()}
|
|
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}}}')
|
|
|
|
def test_trackids_enthaelt_as_sep_und_es(self):
|
|
"""Die "TrackIds"-Spalte einer Strecke muss AS- UND ES-Ende mit
|
|
enthalten, nicht nur die dazwischenliegenden Separatoren - sonst
|
|
fehlen genau die Kettenenden, die beim Kreisel (compute_kreisel_
|
|
umlauf) bereits Teil von dessen TrackIds sind."""
|
|
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)
|
|
write_separatorliste_merkmale(items, sep_kette_by_key)
|
|
|
|
strecke = next(it for it in items if it.get("teileid") == "0010")
|
|
as_id = sep_kette_by_key["0010#1"]["teileid"]
|
|
sep_id = sep_kette_by_key["0010#2"]["teileid"]
|
|
es_id = sep_kette_by_key["0010#3"]["teileid"]
|
|
assert sep_id == "0055", "sepliste-SEP nicht auf Proxy-Separator gemappt"
|
|
assert strecke["trackids"] == f"{as_id}, {sep_id}, {es_id}"
|
|
|
|
|
|
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 TestKreiselTrackidsSpalte:
|
|
"""write_kreisel_separatorliste_merkmale schreibt die bei 0 Grad
|
|
beginnende Umlaufreihenfolge (Separatoren, AS/ES-Enden, Kreisel-Kreisel-
|
|
Weichen) in die eigene CSV-Spalte "TrackIds" (item["trackids"]) auf den
|
|
Kreisel selbst (analog zum gleichnamigen Feld 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["trackids"] == "0021, 0020"
|
|
|
|
def test_kreisel_ohne_umlauf_punkte_bekommt_keine_trackids(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 "trackids" not in items[0]
|
|
|
|
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 der TrackIds-Spalte
|
|
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["trackids"] == "0031, 0020, 0030"
|