TRO-Fluss: Weichenerkennung ueber CSV-Nachbarn statt Distanzschwelle
_find_switch_separators() ermittelt Weichen-Separatoren jetzt topologisch (Ausschleuselement unter den CSV-Nachbarn, gleiche Kreisel-Bahn) statt ueber eine Distanzschwelle - die Fallback-Schwelle paarte an Mubea unbeteiligte Separatoren faelschlich mit dem naechsten freien Ausschleuselement, u.a. 20 PinStore_Auto-Speicherlinien wurden so faelschlich als 1Sep1Swi erkannt. Materialfluss: _sole_kreisel_neighbour() als Fallback in _apply_transfers() fuer AS/ES-Elemente ohne eigenes Merkmal "Kreisel" (Mubea), aufloesbar wenn die Strecke genau einen Kreisel-Nachbarn hat. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e4bfad4dcc
commit
058c60e23d
+58
-89
@@ -153,24 +153,6 @@ from tro_overrides import (
|
||||
# einem Separator) wird ein Speicherblock (PinStore_Auto) angenommen.
|
||||
MIN_STORE_LANES = 2
|
||||
|
||||
# Toleranz (mm), innerhalb derer ein Separator als "sitzt an einem
|
||||
# Ausschleuselement" gilt (siehe _find_switch_separators, Regel 1). Kalibriert
|
||||
# an hundmM05_1.csv: die tatsaechlich am Uebergang montierten Separatoren
|
||||
# liegen 250-350mm vom Element entfernt, die naechsten unbeteiligten (auf der
|
||||
# eigenen Streckenmitte montierten) Separatoren erst ab ca. 885mm - die
|
||||
# Grenze liegt bewusst in der Mitte dieser Luecke.
|
||||
SWITCH_PROXIMITY_MM = 500.0
|
||||
|
||||
# Obergrenze (mm) fuer Regel 2 in _find_switch_separators (naechster freier
|
||||
# Separator ohne Treffer aus Regel 1). Grosszuegiger als SWITCH_PROXIMITY_MM,
|
||||
# aber bewusst NICHT unbegrenzt: an HundM05 (Review 2026-09-18) lagen die noch
|
||||
# echten Weichen bis 1139mm vom Ausschleuselement entfernt - das deckt dieser
|
||||
# Wert. Ohne diese Grenze hat sich Regel 2 an einer groesseren Anlage (Mubea,
|
||||
# 500573) als Fehlerquelle erwiesen: uebrig gebliebene, tatsaechlich
|
||||
# unbeteiligte Separatoren wurden dem naechsten noch freien Ausschleuselement
|
||||
# zugeordnet, auch wenn das 5-9 Meter entfernt lag.
|
||||
SWITCH_FALLBACK_MM = 1500.0
|
||||
|
||||
# Mindest-Hoehenverlust (mm) auf dem der Reihe nach vorangehenden TrackIds-
|
||||
# Segment, damit ein Separator auf einer "Strecke" trotzdem als 1Sep statt
|
||||
# Vario gilt (siehe _incoming_drop). Muss nur "horizontal" gefuehrte, aber
|
||||
@@ -330,7 +312,7 @@ def _default_resolve_host(sep: Element, findings: list[str], graph: Graph) -> st
|
||||
|
||||
|
||||
def _find_switch_separators(
|
||||
separators: list[Element], elements: list[Element], tol_mm: float
|
||||
separators: list[Element], elements: list[Element]
|
||||
) -> dict[str, tuple[Element, float]]:
|
||||
"""Separator-Id -> (Ausschleuselement der Weiche, Abstand mm).
|
||||
|
||||
@@ -345,80 +327,67 @@ def _find_switch_separators(
|
||||
zusaetzliches Bauteil des TROs (siehe derive_tros/scanners_for) - viele
|
||||
Separatoren tragen einen Scanner, ohne eine Weiche zu sein.
|
||||
|
||||
Zwei Durchlaeufe, um trotzdem jedem Ausschleuselement genau einen
|
||||
Separator zuzuordnen:
|
||||
Frueher ueber eine reine Distanzschwelle ermittelt (<=500mm, mit einer
|
||||
grosszuegigeren Fallback-Schwelle bis 1500mm fuer weiter entfernte
|
||||
Faelle). Das erwies sich als nicht plausibel genug in beide Richtungen
|
||||
(Review 2026-09-25): die Fallback-Schwelle paarte an Mubea wiederholt
|
||||
voellig unbeteiligte Separatoren mit dem naechstgelegenen noch freien
|
||||
Ausschleuselement, ohne echten Bezug zwischen beiden - vor Ort geprueft,
|
||||
keine Weiche an der Stelle. Eine reine Nahdistanz ohne Fallback haette
|
||||
umgekehrt an HundM05 alle 5 echten Weichen verfehlt, die dort
|
||||
gleichmaessig bei 1013-1016mm liegen (Review 2026-09-18) - Distanz allein
|
||||
ist also weder an Mubea noch an HundM05 ein verlaessliches Kriterium.
|
||||
|
||||
1. Geometrische Naehe (Distanz <= tol_mm). HundM05 hat an seinen
|
||||
Kreisel<->Strecke-Uebergaengen kein eigenes "ILS Weiche"-Bauteil
|
||||
(anders als ein Kreisel<->Kreisel-Beruehrpunkt, siehe Mubea) - das von
|
||||
dxfmakros synthetisierte Ausschleuselement (Kreisel -> Strecke, am
|
||||
Streckenanfang) uebernimmt dort faktisch die Rolle der Weiche. Sitzt
|
||||
ein Separator direkt daneben, gehoert er zu dieser Weiche - unabhaengig
|
||||
davon, ob seine CSV-Spalte "Zuordnung" auf die Kreisel-Bahn oder auf
|
||||
die Strecke zeigt (in den Daten kommt beides vor, siehe derive_tros).
|
||||
2. Naechstgelegener freier Separator je noch unbelegtem Ausschleuselement,
|
||||
jedes Paar nur einmal vergeben (kleinste Distanz zuerst, hoechstens
|
||||
SWITCH_FALLBACK_MM) - unabhaengig davon, ob Regel 1 auf derselben Bahn
|
||||
bereits einen Treffer hatte. Grund fuer den zweiten Durchlauf (HundM05,
|
||||
Review 2026-09-18): an einem Kreisel mit 5 Abzweigen lagen die
|
||||
tatsaechlichen Schalt-Separatoren pro Abzweig unterschiedlich weit vom
|
||||
Uebergabeelement entfernt (418-1139mm in der damaligen Registrierung)
|
||||
- eine einzelne mm-Schwelle (Regel 1) trifft nicht alle.
|
||||
SWITCH_FALLBACK_MM begrenzt das trotzdem: ohne Obergrenze hat Regel 2
|
||||
an einer groesseren Anlage (Mubea) uebrig gebliebene, tatsaechlich
|
||||
unbeteiligte Separatoren dem naechsten noch freien Ausschleuselement
|
||||
zugeordnet, auch wenn das 5-9 Meter entfernt lag - siehe
|
||||
SWITCH_FALLBACK_MM.
|
||||
Stattdessen wird die im CSV-Export bereits vorhandene topologische
|
||||
Nachbarschaft verwendet (Spalte "Nachbarn", Element.neighbours): dxfmakros
|
||||
traegt dort den tatsaechlich anliegenden Nachbarn ein (CAD-Topologie),
|
||||
nicht nur den geometrisch naechsten - an HundM05 steht das echte
|
||||
Ausschleuselement trotz 1013-1016mm Abstand direkt in den Nachbarn des
|
||||
Schalt-Separators, an Mubea NICHT in den Nachbarn der zu Unrecht per
|
||||
Fallback getroffenen Separatoren. Ein Separator ist eine Weiche, wenn:
|
||||
|
||||
Frueher lief Regel 2 nur auf einer Bahn, die Regel 1 bereits bestaetigt
|
||||
hatte (eine Bahn ohne jeden nahen Treffer hat vermutlich gar keine
|
||||
Weiche). Diese Bahn-Sperre wurde entfernt (Review 2026-09-23): bei
|
||||
einer neueren HundM05-Revision lagen ALLE 5 echten Schalt-Separatoren
|
||||
gleichmaessig bei ca. 1013-1016mm - ausserhalb von Regel 1 (500mm), aber
|
||||
innerhalb von SWITCH_FALLBACK_MM. Ohne einen einzigen Regel-1-Treffer
|
||||
blieb die Bahn-Sperre fuer jede Bahn zu, Regel 2 kam nie zum Zug, und
|
||||
keine der 5 Weichen wurde erkannt. SWITCH_FALLBACK_MM bleibt die
|
||||
einzige Schranke gegen Fehlzuordnungen (siehe Mubea-Fall oben) - sollte
|
||||
sich das an einer weiteren Anlage als zu grosszuegig erweisen, braucht
|
||||
es dort eine gezieltere Loesung statt der Bahn-Sperre.
|
||||
1. ein Ausschleuselement unter seinen Nachbarn steht, UND
|
||||
2. dieses Ausschleuselement zur selben Kreisel-Bahn gehoert wie der
|
||||
Separator (gleiche "Zuordnung", z. B. beide auf Kreisel "0002" -
|
||||
Element.track_ids des betreffenden Kreisels enthaelt das
|
||||
Ausschleuselement). Das ist nicht nur eine Absicherung gegen einen
|
||||
ungenau exportierten "Nachbarn"-Eintrag, sondern noetig: ein
|
||||
Ausschleuselement markiert nicht nur den Kreisel->Strecke-Uebergang,
|
||||
sondern an einem PinStore_Auto-Speicherblock auch den Ablauf jeder
|
||||
einzelnen Schwerkraftlinie zum gemeinsamen Sammler - jeder
|
||||
Linien-Separator hat dort ein Ausschleuselement als CSV-Nachbarn, ohne
|
||||
damit eine echte Weiche zu sein (Review 2026-09-25, Mubea: 20
|
||||
Speicherlinien faelschlich als 1Sep1Swi einsortiert, weil ihre
|
||||
Kreisel-Zuordnung fehlt und Kriterium 2 dafuer faelschlich nicht griff).
|
||||
Ist die Kreisel-Zuordnung des Separators nicht aufloesbar (er sitzt gar
|
||||
nicht auf einer Kreisel-Bahn), ist er deshalb KEIN Weichenkandidat -
|
||||
Kriterium 1 allein reicht nicht.
|
||||
|
||||
Die Distanz im Rueckgabewert ist nur noch Beiwerk fuer die Doku ("XXXmm
|
||||
von Ausschleuselement YYYY entfernt"), kein Kriterium mehr.
|
||||
"""
|
||||
transfers = [e for e in elements if e.kind == "Ausschleus"]
|
||||
transfers = {e.teile_id: e for e in elements if e.kind == "Ausschleus"}
|
||||
kreisel_by_id = {e.teile_id: e for e in elements if e.kind == "Kreisel"}
|
||||
result: dict[str, tuple[Element, float]] = {}
|
||||
|
||||
for sep in separators:
|
||||
if sep.x is None or sep.y is None:
|
||||
sep_kreisel_id = sep.assignment.split("-", 1)[0] if sep.assignment else ""
|
||||
kreisel = kreisel_by_id.get(sep_kreisel_id)
|
||||
if kreisel is None:
|
||||
continue
|
||||
best: tuple[float, Element] | None = None
|
||||
for transfer in transfers:
|
||||
if transfer.x is None or transfer.y is None:
|
||||
for base, _lane in sep.neighbours:
|
||||
transfer = transfers.get(base)
|
||||
if transfer is None:
|
||||
continue
|
||||
d = math.hypot(transfer.x - sep.x, transfer.y - sep.y)
|
||||
if d <= tol_mm and (best is None or d < best[0]):
|
||||
best = (d, transfer)
|
||||
if best is not None:
|
||||
result[sep.teile_id] = (best[1], best[0])
|
||||
|
||||
used_transfers = {hit[0].teile_id for hit in result.values()}
|
||||
remaining_seps = [s for s in separators if s.teile_id not in result
|
||||
and s.x is not None and s.y is not None]
|
||||
|
||||
candidates: list[tuple[float, Element, Element]] = []
|
||||
for sep in remaining_seps:
|
||||
for transfer in transfers:
|
||||
if transfer.teile_id in used_transfers or transfer.x is None or transfer.y is None:
|
||||
if transfer.teile_id not in kreisel.track_ids:
|
||||
continue
|
||||
d = math.hypot(transfer.x - sep.x, transfer.y - sep.y)
|
||||
if d <= SWITCH_FALLBACK_MM:
|
||||
candidates.append((d, sep, transfer))
|
||||
candidates.sort(key=lambda c: c[0])
|
||||
|
||||
claimed_seps: set[str] = set()
|
||||
for d, sep, transfer in candidates:
|
||||
if sep.teile_id in claimed_seps or transfer.teile_id in used_transfers:
|
||||
continue
|
||||
result[sep.teile_id] = (transfer, d)
|
||||
claimed_seps.add(sep.teile_id)
|
||||
used_transfers.add(transfer.teile_id)
|
||||
d = (
|
||||
math.hypot(transfer.x - sep.x, transfer.y - sep.y)
|
||||
if None not in (sep.x, sep.y, transfer.x, transfer.y)
|
||||
else 0.0
|
||||
)
|
||||
result[sep.teile_id] = (transfer, d)
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
@@ -572,7 +541,7 @@ def derive_tros(
|
||||
# unten (3b) ihre eigene 1Sep1Swi-Uebergabestelle und duerfen nicht zugleich
|
||||
# als gewoehnlicher Separator ihres Zuordnung-Hosts (Kreisel-Bahn oder
|
||||
# Strecke - beides kommt vor) mitgezaehlt werden.
|
||||
switch_hits = _find_switch_separators(switch_candidates, elements, SWITCH_PROXIMITY_MM)
|
||||
switch_hits = _find_switch_separators(switch_candidates, elements)
|
||||
|
||||
# Separator -> Host-Knoten
|
||||
sep_host: dict[str, str] = {}
|
||||
@@ -1933,10 +1902,10 @@ def render_doc(
|
||||
add("| Gefaellestrecke, einzelne Linie | `1Sep` |")
|
||||
add("| Strecke (angetrieben) | `Vario` (je Streckensegment) |")
|
||||
add("| Kreisel-Bahn | `1Sep` |")
|
||||
add(f"| Separator <= {SWITCH_PROXIMITY_MM:.0f}mm (oder naechstgelegen bis "
|
||||
f"{SWITCH_FALLBACK_MM:.0f}mm) von einem Ausschleuselement "
|
||||
"| `1Sep1Swi` (Element uebernimmt die fehlende Weiche; ein Einschleuselement "
|
||||
"zaehlt nicht - passiver Merge ohne Richtungsentscheidung) |")
|
||||
add("| Separator mit Ausschleuselement unter seinen CSV-Nachbarn (gleiche "
|
||||
"Kreisel-Bahn) | `1Sep1Swi` (Element uebernimmt die fehlende Weiche; "
|
||||
"ein Einschleuselement zaehlt nicht - passiver Merge ohne "
|
||||
"Richtungsentscheidung) |")
|
||||
add("| zusaetzlich: 2 bzw. 3 abgehende Wege | Aufwertung zu `1Sep1Swi` / `1Sep2Swi` |")
|
||||
add("")
|
||||
add("Ein **Scanner** am Separator aendert den Typ nicht. In der Referenzanlage "
|
||||
|
||||
Reference in New Issue
Block a user