tro_flow: Weichen-Separator nach Laufrichtung, Weichen-Slots mit Bauteil

Steuernder Separator einer ILS Weiche (_find_weiche_separators) ist der
letzte freie Separator in Laufrichtung VOR der Weiche, gemessen auf dem
Kreisel-Umlauf aus Lage und Drehrichtung (kreisel_junctions.separators_before)
- nicht mehr der CSV-Nachbar. Ein Nachbar hinter der Weiche steuert sie nie;
Hoechstabstand weiche_vorlauf_max in cfg/parameter.ini [tro_flow]. Mubea
(Export 2026-10-16): 2Sep2Swi = 0005 (Ausschleus 0061) + 0036 (Weiche 0130)
statt 0005 + 0012; 0012 bleibt 1Sep, Kreisel1 wieder angebunden. Dematic,
HundM05, showroom, test_system unveraendert.

TRO-JSON: DerivedTro.switches (optional) nennt die Weichen-Bauteile in
Slot-Reihenfolge (switch<i> gehoert bei 2Sep-Typen zu separator<i>);
derive_slots traegt sie in die switch<n>-Slots, merge_hw_layout bindet das
Ventil dort. e_planning.ini: Rollen ventil -> valve (Slot-Rolle der SCL),
neue Regel Ausschleus_Weiche.valve fuer Ausschleusventile an Kreisel-Weichen.
Mubea: MB0715, MB0724, MB0627, MB0671 statt TODO(E-Planung).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-16 13:42:16 +02:00
parent 39b445a7c4
commit d414bd6cf3
8 changed files with 297 additions and 46 deletions
+73 -15
View File
@@ -104,8 +104,10 @@ ausschliesslich derive_tros(), Schritt fuer Schritt:
Beruehrpunkten derselben Umlenkung
(_kreisel_knoten, lib/kreisel_junctions.py)
3c' Separator an einer ILS Weiche, die kein 1Sep1Swi
Weichenknoten aus 3c ist
(_find_weiche_separators)
Weichenknoten aus 3c ist: letzter freier
Separator in Laufrichtung davor (Lage +
Drehrichtung, nicht TrackIds/Nachbarn;
_find_weiche_separators)
3d 3b-TRO und 3c'-TRO direkt hintereinander 2Sep2Swi
in der TrackIds-Kette eines Kreisels
4 1Sep mit 2 bzw. 3 abgehenden Flusskanten 1Sep1Swi / 1Sep2Swi
@@ -178,7 +180,7 @@ from material_flow import (
read_elements,
resolve_input,
)
from kreisel_junctions import Junction, find_junctions
from kreisel_junctions import Junction, find_junctions, separators_before
from model import Analysis, DerivedTro, LayoutPart, TroDocument
from parameter import get_float, get_int
from tro_ids import (
@@ -211,6 +213,7 @@ from tro_catalog import (
_P = "tro_flow"
MIN_STORE_LANES = get_int(_P, "min_store_lanes")
GEFAELLE_DROP_MM = get_float(_P, "gefaelle_drop_mm")
WEICHE_VORLAUF_MAX = get_float(_P, "weiche_vorlauf_max")
COORD_SCALE = get_float(_P, "coord_scale")
COORD_PFEIL_LAENGE = get_int(_P, "coord_pfeil_laenge")
COORD_MAX_LUECKE = get_int(_P, "coord_max_luecke")
@@ -661,12 +664,29 @@ def _find_weiche_separators(
erzeugt sie z. B. am Beruehrpunkt zweier Kreisel (Merkmal "Erzeugt aus":
"Kreisel-Beruehrpunkt"; Mubea 0125 zwischen Kreisel2 und Kreisel1).
Steuernder Separator ist der Separator unter den CSV-Nachbarn der Weiche,
der einen Scanner traegt (Scanner-Merkmal "Naechster Separator"). Mubea
0125: Nachbarn 0040 und 0036, nur 0036 hat einen Scanner (0041) - vor Ort
bestaetigt (Review 2026-08-12 und 2026-10-01). Ist nur ein Separator
benachbart, gilt dieser. Bleibt es mehrdeutig, wird nichts geraten,
sondern ein Finding geschrieben.
Steuernder Separator ist der Separator, der in Laufrichtung unmittelbar
VOR der Weiche liegt - der Traeger haelt dort, wird gelesen und die
Weiche entscheidet. Liegt die Weiche auf Kreiseln mit Drehrichtung
(kreisel_junctions.separators_before), gilt:
1. Je Kreisel, auf dem die Weiche liegt, zaehlt nur der letzte Separator
vor ihr - bestimmt aus Lage und Drehrichtung, nicht aus TrackIds
oder der CSV-Spalte "Nachbarn". Ein Separator NACH der Weiche steuert
sie nie, auch wenn er ihr CSV-Nachbar ist (Mubea-Export 2026-10-16:
Nachbarn der Weiche 0130 sind 0061/0012, 0012 liegt auf Kreisel2
hinter ihr; gesteuert wird sie von 0036 auf Kreisel1 davor).
2. Er muss frei sein (in separators, also nicht schon Steuer-Separator
eines Ausschleuselements oder eigener VF-Kettenseparator) und
hoechstens weiche_vorlauf_max (cfg/parameter.ini [tro_flow]) Laufweg
vor der Weiche liegen.
3. Bleiben mehrere (je Kreisel einer): zuerst ein CSV-Nachbar, dann einer
mit Scanner (Scanner-Merkmal "Naechster Separator"), dann der mit dem
kuerzesten Laufweg.
Ohne Kreisel-Geometrie (Weiche auf keinem Umlauf, Drehrichtung fehlt)
gilt die alte Regel: der Separator unter den CSV-Nachbarn mit Scanner,
sonst der einzige benachbarte. Bleibt es mehrdeutig, wird nichts
geraten, sondern ein Finding geschrieben.
Grenze: einer Weiche wird hoechstens EIN Separator zugeordnet. Weichen
mit mehreren Separatoren (2Sep1Swi, 3Sep2Swi, ...) ermittelt vorher
@@ -674,15 +694,16 @@ def _find_weiche_separators(
ILS Weichen an, die dort keinen Knoten bilden.
Parameter:
separators: zu pruefende Separatoren (in derive_tros ohne die
separators: Separatoren, die steuern duerfen (in derive_tros ohne die
Ausschleus-Weichen-Separatoren aus switch_hits).
elements: alle Elemente des CSV-Exports (ILS Weichen und Scanner).
findings: Fundliste; je mehrdeutiger Weiche wird ein Eintrag
angehaengt.
elements: alle Elemente des CSV-Exports (ILS Weichen, Kreisel,
Separatoren, Scanner).
findings: Fundliste; je verworfenem Nachbarn, je Zuordnung ausserhalb
der Nachbarn und je nicht zuordenbarer Weiche ein Eintrag.
Rueckgabe:
Separator-Id -> ILS Weiche (Element.kind "Weiche"). Wird in
derive_tros Schritt 3c' zu 1Sep1Swi.
derive_tros Schritt 3c' zu 1Sep1Swi (oder in 3d Teil eines 2Sep2Swi).
"""
by_id = {s.teile_id: s for s in separators}
mit_scanner = {
@@ -691,7 +712,37 @@ def _find_weiche_separators(
}
result: dict[str, Element] = {}
for weiche in (e for e in elements if e.kind == "Weiche"):
kandidaten = sorted({base for base, _lane in weiche.neighbours if base in by_id})
nachbarn = sorted({base for base, _lane in weiche.neighbours if base in by_id})
vorher, nachher = (separators_before(elements, weiche.x, weiche.y)
if None not in (weiche.x, weiche.y) else ([], []))
if vorher:
for sid in (n for n in nachbarn if n in nachher):
findings.append(
f"ILS Weiche {weiche.teile_id}: CSV-Nachbar {sid} liegt in "
f"Laufrichtung hinter der Weiche - steuert sie nicht")
frei = [u for u in vorher
if u.separator in by_id and u.distance <= WEICHE_VORLAUF_MAX]
if not frei:
davor = ", ".join(f"{u.separator} auf {u.kreisel} ({u.distance:.0f}mm)"
for u in vorher)
findings.append(
f"ILS Weiche {weiche.teile_id}: kein freier Separator in "
f"Laufrichtung davor (letzte davor: {davor}; hoechstens "
f"{WEICHE_VORLAUF_MAX:.0f}mm) - keinem TRO zugeordnet")
continue
frei.sort(key=lambda u: (u.separator not in nachbarn,
u.separator not in mit_scanner, u.distance))
u = frei[0]
if u.separator not in nachbarn:
findings.append(
f"ILS Weiche {weiche.teile_id}: gesteuert von Separator "
f"{u.separator} ({u.distance:.0f}mm in Laufrichtung davor auf "
f"Kreisel {u.kreisel}), obwohl kein CSV-Nachbar (Nachbarn: "
f"{', '.join(b for b, _l in weiche.neighbours) or '-'})")
result[u.separator] = weiche
continue
kandidaten = nachbarn
gewaehlt = [s for s in kandidaten if s in mit_scanner]
if len(gewaehlt) != 1:
gewaehlt = kandidaten if len(kandidaten) == 1 else []
@@ -1282,6 +1333,7 @@ def derive_tros(
hosts=[strecke_id] if strecke_id in graph.nodes else [transfer.teile_id],
separators=[sep_id],
scanners=own_scanners,
switches=[transfer.teile_id],
reason=(
f"{distance:.0f}mm von {art} {transfer.teile_id} entfernt - "
f"kein eigenes Weichen-Bauteil im Layout, das {art} "
@@ -1324,6 +1376,7 @@ def derive_tros(
as_switch_tro[as_id] = tro
weichen = weichen_je_tro[id(tro)]
weichen.append(as_id)
tro.switches.append(as_id)
if strecke_id in graph.nodes and strecke_id not in tro.hosts:
tro.hosts.append(strecke_id)
tro.label = (f"Separator {steuer} an Ausschleuselementen "
@@ -1362,6 +1415,8 @@ def derive_tros(
hosts=hosts or [j.sep_kreisel[s] for s in j.separators],
separators=list(j.separators),
scanners=scanners_for(j.separators),
# nicht exportierte Beruehrpunkte (W<a>_<b>) sind kein Layout-Bauteil
switches=[c.weiche_id if c.exported else "" for c in j.contacts],
reason=(f"Separator{'en' if len(j.separators) > 1 else ''} {vor} "
f"in Laufrichtung unmittelbar vor "
f"{'den Weichen' if len(weichen) > 1 else 'der Weiche'} "
@@ -1385,6 +1440,7 @@ def derive_tros(
hosts=[host] if host is not None else [weiche.teile_id],
separators=[sep_id],
scanners=sorted(sep_scanners.get(sep_id, [])),
switches=[weiche.teile_id],
reason=f"steuert die ILS Weiche {weiche.teile_id} (echtes Weichen-Bauteil)",
confidence="hoch",
)
@@ -1416,6 +1472,8 @@ def derive_tros(
hosts=list(as_tro.hosts),
separators=as_tro.separators + w_tro.separators,
scanners=sorted(as_tro.scanners + w_tro.scanners),
# switch1 = Ausschleuselement (separator1), switch2 = Weiche
switches=as_tro.switches + w_tro.switches,
reason=(f"Ausschleuselement {as_id} (Separator "
f"{', '.join(as_tro.separators)}) und ILS Weiche {w_id} "
f"(Separator {', '.join(w_tro.separators)}) liegen in "