Weichenerkennung: nur Ausschleuselement zaehlt, Regel 2 mit Obergrenze

Zwei Korrekturen zu c788647, beide durch Review gefunden (HundM05):

1. Ein Einschleuselement (Wiedereinschleusen in den Kreisel) ist keine
   Weiche - das ist ein passiver Merge ohne Richtungsentscheidung, nur das
   Ausschleusen (Verlassen der Bahn) legt die Fahrtrichtung fest.
   _find_switch_separators() beruecksichtigt jetzt ausschliesslich
   Ausschleuselemente; ein Separator an einem Einschleuselement (z. B.
   HundM05 Separator 0018) wird nicht mehr als eigener 1Sep1Swi
   herausgeloest, sondern zaehlt normal zum Vario-TRO seines Streckensegments.
   HundM05.csv: 6 -> 5 automatisch erkannte Weichen (deckt sich jetzt mit den
   5 Ausschleuselementen im Layout).

2. Beim Pruefen an Mubea (500573) fiel auf, dass Regel 2 (naechster freier
   Separator, bisher ohne Obergrenze) dort mehrere Separatoren dem naechsten
   noch freien Ausschleus-/Einschleuselement zugeordnet hatte, obwohl das
   3.5-9m entfernt lag - eindeutig keine Weiche. Neue Konstante
   SWITCH_FALLBACK_MM (1500mm) begrenzt Regel 2: grosszuegig genug fuer die
   bei HundM05 beobachtete Kalibrierungs-Drift (bis 1139mm), zu eng fuer
   Mubeas Fehlzuordnungen. mubea.csv: 9 -> 4 automatisch erkannte Weichen
   (die beiden bestaetigten Handkorrekturen in tro_overrides.ini bleiben
   unveraendert).

Beide Projekte weiterhin ohne Fehler, keine nicht verbundenen TROs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Samer Ayadi
2026-09-21 10:46:30 +02:00
co-authored by Claude Sonnet 5
parent c7886477ff
commit fa96a6e666
+57 -46
View File
@@ -113,14 +113,24 @@ 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 Aus-/
# Einschleuselement" gilt (siehe _find_switch_separators). Kalibriert an
# hundmM05_1.csv: die tatsaechlich am Uebergang montierten Separatoren liegen
# 250-350mm vom Element entfernt, die naechsten unbeteiligten (auf der
# 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
GENERATED_MARKER = "<!-- erzeugt von lib/tro_flow.py -->"
# Zeichnungsmassstab fuer --use-cords: Graphviz rechnet in Punkt, die Anlage in
@@ -273,43 +283,46 @@ 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
) -> dict[str, tuple[Element, float]]:
"""Separator-Id -> (Aus-/Einschleuselement der Weiche, Abstand mm).
"""Separator-Id -> (Ausschleuselement der Weiche, Abstand mm).
Ein Separator ist eine Weiche, wenn ihm ein ILS Aus- oder Einschleuselement
Ein Separator ist eine Weiche, wenn ihm ein ILS Ausschleuselement
zugeordnet werden kann - das Element ist die Weiche (siehe unten), der
Separator nur der Steuerpunkt davor. Ein eventueller Scanner am selben
Separator ist dabei kein Kriterium, sondern nur ein zusaetzliches Bauteil
des TROs (siehe derive_tros/scanners_for) - viele Separatoren tragen einen
Scanner, ohne eine Weiche zu sein.
Separator nur der Steuerpunkt davor. NICHT das Einschleuselement: das
Wiedereinschleusen ist ein passiver Merge ohne Richtungsentscheidung, nur
das Ausschleusen (Verlassen der Kreisel-Bahn) ist eine Weichenstelle - vor
Ort bestaetigt (HundM05, Review 2026-09-21): ein Separator an einem
Einschleuselement ist keine eigene Uebergabestelle. Ein eventueller
Scanner am selben Separator ist ebenfalls kein Kriterium, sondern nur ein
zusaetzliches Bauteil des TROs (siehe derive_tros/scanners_for) - viele
Separatoren tragen einen Scanner, ohne eine Weiche zu sein.
Zwei Durchlaeufe, um trotzdem jedem Aus-/Einschleuselement genau einen
Zwei Durchlaeufe, um trotzdem jedem Ausschleuselement genau einen
Separator zuzuordnen:
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) bzw. Einschleuselement (Strecke -> Kreisel, am
Streckenende) uebernimmt dort faktisch die Rolle der Weiche. Sitzt ein
Separator direkt daneben, gehoert er zu dieser Weiche - unabhaengig
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 Aus-/
Einschleuselement, jedes Paar nur einmal vergeben (kleinste Distanz
zuerst), auf derselben Bahn und mit derselben Rolle (Aus- oder
Einschleusung) wie ein bereits durch Regel 1 bestaetigter Treffer -
verhindert, dass ein Separator versehentlich mit dem naeher liegenden
Einschleuselement einer anderen Strecke verkoppelt wird. 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
aktuellen Registrierung) - eine einzelne mm-Schwelle (Regel 1) trifft
nicht alle. Durch die eindeutige Vergabe (jedes Element nur einmal)
bleibt ein Separator ohne eigenes Uebergabeelement (z. B. ein
gewoehnlicher Zwischenseparator auf der Bahn) trotzdem unbelegt, auch
ohne feste mm-Grenze.
2. Naechstgelegener freier Separator je noch unbelegtem Ausschleuselement,
jedes Paar nur einmal vergeben (kleinste Distanz zuerst, hoechstens
SWITCH_FALLBACK_MM), und nur auf einer Bahn, die bereits durch Regel 1
einen bestaetigten Treffer hat (eine Bahn ohne jeden nahen Treffer hat
vermutlich gar keine Weiche, z. B. Kreisel1 Bahn R bei HundM05). 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.
"""
transfers = [e for e in elements if e.kind in ("Ausschleus", "Einschleus")]
transfers = [e for e in elements if e.kind == "Ausschleus"]
result: dict[str, tuple[Element, float]] = {}
for sep in separators:
@@ -325,28 +338,24 @@ def _find_switch_separators(
if best is not None:
result[sep.teile_id] = (best[1], best[0])
# Rolle je Kreisel-Anschluss aus einem bereits bestaetigten Treffer lesen -
# keine Annahme, welche Bahn (-L/-R) Aus- bzw. Einschleusung uebernimmt.
bahn_role: dict[str, str] = {}
for sep in separators:
hit = result.get(sep.teile_id)
if hit and sep.assignment:
bahn_role.setdefault(sep.assignment, hit[0].kind)
# Bahnen mit mindestens einem durch Regel 1 bestaetigten Treffer - nur
# dort darf Regel 2 ueberhaupt suchen (siehe Docstring).
switch_bahnen = {sep.assignment for sep in separators
if sep.teile_id in result and sep.assignment}
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
and bahn_role.get(s.assignment or "") is not None]
and s.assignment in switch_bahnen]
candidates: list[tuple[float, Element, Element]] = []
for sep in remaining_seps:
role = bahn_role[sep.assignment or ""]
for transfer in transfers:
if (transfer.kind != role or transfer.teile_id in used_transfers
or transfer.x is None or transfer.y is None):
if transfer.teile_id in used_transfers or transfer.x is None or transfer.y is None:
continue
d = math.hypot(transfer.x - sep.x, transfer.y - sep.y)
candidates.append((d, sep, transfer))
if d <= SWITCH_FALLBACK_MM:
candidates.append((d, sep, transfer))
candidates.sort(key=lambda c: c[0])
claimed_seps: set[str] = set()
@@ -388,7 +397,7 @@ def derive_tros(
(e for e in elements if e.kind == "Scanner"), key=lambda e: e.teile_id
)
# Separatoren an einem Aus-/Einschleuselement (siehe _find_switch_separators)
# Separatoren an einem Ausschleuselement (siehe _find_switch_separators)
# werden VOR der normalen Host-Aufloesung herausgenommen: sie bilden gleich
# unten (3b) ihre eigene 1Sep1Swi-Uebergabestelle und duerfen nicht zugleich
# als gewoehnlicher Separator ihres Zuordnung-Hosts (Kreisel-Bahn oder
@@ -520,7 +529,7 @@ def derive_tros(
)
)
# 3b) Separatoren an einem Aus-/Einschleuselement -> eigene 1Sep1Swi-
# 3b) Separatoren an einem Ausschleuselement -> eigene 1Sep1Swi-
# Uebergabestelle. Das Element uebernimmt die Rolle der (hier fehlenden)
# Weiche; siehe _find_switch_separators. Host bewusst die Strecke (nicht
# die Kreisel-Bahn): beide Elementarten tragen "Strecke" oder "Foerderer"
@@ -532,7 +541,7 @@ def derive_tros(
strecke_id = str(_merkmal(transfer.merkmale, "Strecke", "Förderer") or "")
ende = str(_merkmal(transfer.merkmale, "Streckenende") or "")
own_scanners = sorted(sep_scanners.get(sep_id, []))
art = "Ausschleuselement" if transfer.kind == "Ausschleus" else "Einschleuselement"
art = "Ausschleuselement"
tros.append(
Tro(
tro_id="",
@@ -1391,8 +1400,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 von einem Aus-/Einschleuselement "
"| `1Sep1Swi` (Element uebernimmt die fehlende Weiche) |")
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("| zusaetzlich: 2 bzw. 3 abgehende Wege | Aufwertung zu `1Sep1Swi` / `1Sep2Swi` |")
add("")
add("Ein **Scanner** am Separator aendert den Typ nicht. In der Referenzanlage "