diff --git a/lib/tro_flow.py b/lib/tro_flow.py index 637cc03..8ee1f78 100644 --- a/lib/tro_flow.py +++ b/lib/tro_flow.py @@ -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 = "" # 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 "