diff --git a/lib/tro_flow.py b/lib/tro_flow.py index f2b2458..637cc03 100644 --- a/lib/tro_flow.py +++ b/lib/tro_flow.py @@ -275,7 +275,15 @@ def _find_switch_separators( ) -> dict[str, tuple[Element, float]]: """Separator-Id -> (Aus-/Einschleuselement der Weiche, Abstand mm). - Zwei Regeln, angewandt in dieser Reihenfolge: + Ein Separator ist eine Weiche, wenn ihm ein ILS Aus- oder Einschleuselement + 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. + + Zwei Durchlaeufe, um trotzdem jedem Aus-/Einschleuselement genau einen + Separator zuzuordnen: 1. Geometrische Naehe (Distanz <= tol_mm). HundM05 hat an seinen Kreisel<->Strecke-Uebergaengen kein eigenes "ILS Weiche"-Bauteil @@ -286,21 +294,20 @@ def _find_switch_separators( 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. Separator mit eigenem Scanner (Scanner-Merkmal "Naechster Separator"), - auf noch unbelegten Uebergabestellen der SELBEN Rolle (Aus- oder - Einschleusung) und dem SELBEN Kreisel-Anschluss wie ein bereits durch - Regel 1 bestaetigter Treffer auf dieser Bahn - keine feste mm-Grenze, - stattdessen die naechstgelegene freie Uebergabestelle dieser Rolle. - Grund (HundM05, Review 2026-09-18): an einem Kreisel mit 5 Abzweigen - liegen 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. - Ein Scanner direkt am Separator ist hier aber der CAD-belegte - Lesepunkt vor der Weichenentscheidung (Barcode -> Richtung), also ein - verlaesslicherer Marker als der reine Abstand. Nach Rolle (Aus- vs. - Einschleusung) gefiltert, damit z. B. ein Separator auf der - Ausschleus-Bahn nicht versehentlich mit dem naeher liegenden - Einschleuselement einer anderen Strecke verkoppelt wird. + 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. """ transfers = [e for e in elements if e.kind in ("Ausschleus", "Einschleus")] result: dict[str, tuple[Element, float]] = {} @@ -326,31 +333,29 @@ def _find_switch_separators( if hit and sep.assignment: bahn_role.setdefault(sep.assignment, hit[0].kind) - scanner_targets = { - str(_merkmal(e.merkmale, "Naechster Separator") or "").strip() - for e in elements if e.kind == "Scanner" - } 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] - for sep in separators: - if sep.teile_id in result or sep.teile_id not in scanner_targets: - continue - if sep.x is None or sep.y is None: - continue - role = bahn_role.get(sep.assignment or "") - if role is None: - continue - best: tuple[float, Element] | None = None + 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): continue d = math.hypot(transfer.x - sep.x, transfer.y - sep.y) - if 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.add(best[1].teile_id) + 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) return result