export_csv: AS/ES direkt aus ZZ_ASES_MARKER klassifizieren, Kreisel<->Strecke-Kollisionserzeugung entfernen
Stufe 2 (Python-Seite von "sepliste: Bauteil-IDs eintragen"): AS/ES-Kettenenden sind jetzt ganz normale Items, direkt aus dem dauerhaften ZZ_ASES_MARKER klassifiziert (TeileId/Position/Boundingbox vom Marker-Block selbst) - keine Kollisionsberechnung mehr, die sie erfindet. - lib/export_neighbors.py: compute_strecke_kreisel_schleus entfernt (suchte bisher per Kollisionstest den anschliessenden Kreisel und erzeugte dabei bei JEDEM Export eine neue TeileId fuer denselben physischen Punkt). AUSSCHLEUS_/ EINSCHLEUS_TEILEART sind jetzt Teil von KREISEL_UMLAUF_TEILEARTEN - ein AS/ES bringt seine eigene Bounding-Box vom Marker mit und wird dadurch, wie jeder freie Separator, ueber die normale BBox-Ueberschneidung einem Kreisel zugeordnet; die gesonderte "naechster Kreisel per sep_kette"-Suche in compute_kreisel_umlauf (dessen sep_kette-Parameter damit entfaellt) ist ueberfluessig geworden. compute_sep_kette gibt je Eintrag zusaetzlich das Feld "id" weiter (Bauteil-TeileId, siehe vorheriger Commit). - lib/export_csv.py: process_blocks klassifiziert ZZ_ASES_MARKER direkt (TYP- Attribut waehlt Ausschleus-/Einschleuselement); build_strecke_schleus_items entfaellt komplett. map_separator_kette_items -> map_sepliste_kette_items: ordnet jetzt AS/SEP/ES exakt per ID-Gleichheit zu (sepliste-Feld "id" gegen die TeileId der klassifizierten Items) statt per Koordinaten-Naehe - SEP faellt bei fehlendem "id"-Feld weiterhin auf den alten Naehe-Abgleich zurueck (Altbestand), AS/ES ohne "id" bleiben unaufgeloest. - Neu: pruefe_ases_in_beiden_containern prueft am Ende von process_blocks, ob jede AS-/ES-Zeile sowohl in den TrackIds ihrer eigenen Strecke als auch in denen mindestens eines Kreisels auftaucht, und traegt bei Abweichung eine Warnung ein (Spalte "Warnungen") - Konsistenzpruefung fuer genau die beiden "Container", zu denen ein AS/ES gehoert. - Aufgeraeumt (tote Routinen): _strecke_ends, _closest_pt_on_segment, _distinct_kreisel_ids (nur von den entfernten Funktionen genutzt), sowie der temporaere Diagnose-Filter DEBUG_HIGHLIGHT_TEILEIDS/dbg_highlight (an die jetzt behobene Koordinaten-Matching-Ambiguitaet gebunden, in seinem eigenen Kommentar bereits als Wegwerf-Code markiert). LISP-seitiger Dead-Code-Scan (export.lsp, ssg_ks_insert.lsp - die diese Woche bearbeiteten Module): keine Funde. Verifiziert an den echten HundM05-Daten (results/export_raw.json, 45 Bloecke): TeileId-Bereich luecken- und duplikatfrei 1-45 (vorher 1-55 mit Luecke 24-33 + Zusatz-IDs 46-55), alle 10 AS/ES-IDs korrekt in ihrer Strecke UND im Kreisel 06 TrackIds, keine Warnungen der neuen Pruefung. Zwei Testdateien auf die neue Architektur umgestellt (test_export_kreisel_umlauf.py: AS/ES als Item statt Kollisions-Erzeugung, neue Klasse TestAsEsImKreiselUmlauf fuer die BBox- Teilnahme; test_export_separatorliste.py: id-basierte Zuordnung, Klasse TestMarkerIdWiederverwendung durch TestMapSeplisteKetteItemsFallback ersetzt - die alte testete inzwischen entfernte Mint-Logik). Volle Suite: 224 bestanden (4 vorbestehende, unabhaengige Fixture-Failures unveraendert). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
719ffc2385
commit
ab3958e0e4
+248
-298
@@ -41,14 +41,15 @@ from export_neighbors import (
|
||||
compute_sensor_zuordnung,
|
||||
compute_scanner_nearest_separator,
|
||||
compute_kreisel_touch_switches,
|
||||
compute_strecke_kreisel_schleus,
|
||||
compute_sep_kette,
|
||||
compute_kreisel_umlauf,
|
||||
dbg_highlight,
|
||||
SENSOR_TEILEARTEN,
|
||||
WEICHE_TEILEART,
|
||||
GF_VF_TEILEARTEN,
|
||||
KREISEL_SPLIT_TEILEART,
|
||||
KREISEL_UMLAUF_TEILEARTEN,
|
||||
AUSSCHLEUS_TEILEART,
|
||||
EINSCHLEUS_TEILEART,
|
||||
)
|
||||
|
||||
BLOCKPATTERNS = load_patterns()
|
||||
@@ -534,174 +535,44 @@ def _strecke_typ_feld(teileart):
|
||||
return "Strecke" if teileart == "ILS 2.0 Gefaellestrecke" else "Förderer"
|
||||
|
||||
|
||||
def build_strecke_schleus_items(items, planquadrat_cfg, start_elem_nr, sep_kette=None,
|
||||
sep_kette_by_key=None, dbg=None):
|
||||
"""Haengt fuer jedes Strecken-Ende mit anschliessendem Kreisel eine
|
||||
"ILS Ausschleuselement"- (Anfang) bzw. "ILS Einschleuselement"-Zeile
|
||||
(Ende) an items an (siehe export_neighbors.compute_strecke_kreisel_schleus
|
||||
fuer Position/Boundingbox - reine Geometrie, unveraendert).
|
||||
def map_sepliste_kette_items(items, sep_kette, dbg=None):
|
||||
"""Ordnet jeden sepliste-Eintrag (AS/SEP/ES, siehe compute_sep_kette)
|
||||
einem bereits klassifizierten Item zu und liefert sie unter ihrem
|
||||
sep_kette-Schluessel zurueck - fuer die Nachbar-Aufloesung in
|
||||
resolve_umlauf_und_sepliste_nachbarn sowie die TrackIds-Spalte der
|
||||
besitzenden Strecke (siehe write_separatorliste_merkmale).
|
||||
|
||||
Muss NACH build_kreisel_weiche_items laufen, damit die TeileId-Zaehlung
|
||||
(max_numeric_teileid) auch die bereits erzeugten Weichen einschliesst.
|
||||
Anzahl 1; K1-K4/Insertpoint leer; Boundingbox = die statische, an der
|
||||
Kreiselachse ausgerichtete Default-Box.
|
||||
ERZEUGT KEINE neuen Zeilen: AS/ES liegen bereits als eigene Items vor
|
||||
(direkt aus dem dauerhaften ZZ_ASES_MARKER klassifiziert, siehe
|
||||
process_blocks), SEP als die beim Export ausgepackte temporaere
|
||||
Separator_SP-Kopie (csv:sep-proxies-erzeugen, export.lsp). Beide Wege
|
||||
wuerden sonst dieselben physischen Bauteile DOPPELT zaehlen.
|
||||
|
||||
sep_kette = Rueckgabe von export_neighbors.compute_sep_kette (keys
|
||||
"<strecke_id>#<lfdnr>" -> Eintrag mit typ/vorgaenger/nachfolger). Traegt
|
||||
die zugehoerige Strecke eine "sepliste" (neue LISP-XDATA, siehe
|
||||
ssg-sepliste-xdata-schreiben), bleibt "Nachbarn" hier zunaechst LEER und
|
||||
der sep_kette-Schluessel dieser Zeile wird stattdessen in
|
||||
sep_kette_by_key (dict, vom Aufrufer bereitgestellt und HIER befuellt)
|
||||
eingetragen - process_blocks loest daraus NACH der Registrierung ALLER
|
||||
SEP/AS/ES-Schluessel (auch der von map_separator_kette_items) die
|
||||
tatsaechlichen TeileIds auf (eigener Block am Ende von process_blocks,
|
||||
direkt nach beiden Aufrufen). Ein sep_kette-Schluessel ist naemlich KEINE TeileId,
|
||||
sondern nur waehrend des Baus eindeutig -
|
||||
ihn direkt in die CSV zu schreiben waere ein Bug (zeigte frueher auf
|
||||
"<strecke_id>#<lfdnr>" statt auf den eigentlichen Nachbarn). Die
|
||||
Kreisel-/Foerderer-Zugehoerigkeit steht bei sepliste-Zeilen stattdessen
|
||||
in den Merkmalen ("Kreisel" + "Förderer"/"Strecke" je Typ, analog zum
|
||||
"Zuordnung"-Feld der Separatoren). Fehlt die sepliste (Altbestand-Wrapper
|
||||
ohne die neue XDATA), bleibt der bisherige Nachbarn-Inhalt
|
||||
[kreisel_id, strecke_id] unveraendert erhalten (sofort aufgeloest, kein
|
||||
sep_kette-Schluessel involviert).
|
||||
Zuordnung: EXAKTER ID-Abgleich. Seit csv:sepliste-ids-setzen/
|
||||
csv:asmark-ids-setzen (Lisp/export.lsp) traegt praktisch jeder sepliste-
|
||||
Eintrag bereits die TeileId des ihn vertretenden Items als Feld "id" -
|
||||
das Item mit GENAU dieser teileid IST der gesuchte Partner. Kein Rate-/
|
||||
Naeheverfahren mehr noetig, keine der frueheren Ambiguitaeten (ein
|
||||
einzelner fehlerhafter sepliste-Koordinatenwert konnte vorher per
|
||||
Naeherungssuche einen FALSCHEN Nachbarn waehlen und einen anderen
|
||||
Separator ganz ohne Zuordnung lassen - siehe Memory
|
||||
sepliste-lfdnr2-koordinaten-bug.md).
|
||||
|
||||
Rueckgabe: fortgeschriebene Elementnummer.
|
||||
Fallback (kein "id"-Feld - Altbestand-Export ohne Marker/Kopie-ID, oder
|
||||
deren Zuweisung ist in Lisp fehlgeschlagen): NUR fuer SEP-Eintraege
|
||||
weiterhin per naechstem INSERT-Punkt gegen die per Zuordnung
|
||||
vorgefilterten Separator-Items. AS/ES ohne "id" bleiben unaufgeloest -
|
||||
fuer sie existiert seit dem Wegfall der Kreisel<->Strecke-Kollisions-
|
||||
erzeugung (frueher compute_strecke_kreisel_schleus/
|
||||
build_strecke_schleus_items) ohnehin kein Item mehr, das sie vertreten
|
||||
koennte.
|
||||
|
||||
Rueckgabe: sep_kette_by_key (neues dict).
|
||||
"""
|
||||
if sep_kette is None:
|
||||
sep_kette = {}
|
||||
if sep_kette_by_key is None:
|
||||
sep_kette_by_key = {}
|
||||
schleus, warnings = compute_strecke_kreisel_schleus(
|
||||
items, durchmesser_mm=KREISEL_DURCHMESSER_MM,
|
||||
element_box_mm=ELEMENT_BOX_MM, dbg=dbg)
|
||||
sep_kette_by_key = {}
|
||||
by_teileid = {it.get("teileid", ""): it for it in items if it.get("teileid", "")}
|
||||
|
||||
strecke_by_id = {it.get("teileid", ""): it for it in items
|
||||
if it.get("teileart", "") in GF_VF_TEILEARTEN}
|
||||
|
||||
# Warnungen (>2 Kreisel-BBox-Beruehrungen je Strecke) auf die Strecken-Zeile
|
||||
# schreiben (Spalte "Warnungen") und in die Konsole ausgeben.
|
||||
if warnings:
|
||||
for sid, text in warnings.items():
|
||||
if sid in strecke_by_id:
|
||||
strecke_by_id[sid]["warnung"] = text
|
||||
print(f"[export_csv] WARNUNG: Strecke {sid} {text}")
|
||||
|
||||
naechste_id = max_numeric_teileid(items)
|
||||
elem_nr = start_elem_nr
|
||||
zaehler = {}
|
||||
for sp in schleus:
|
||||
elem_nr += 1
|
||||
teileart = sp["teileart"]
|
||||
zaehler[teileart] = zaehler.get(teileart, 0) + 1
|
||||
planquadrat = (compute_planquadrat(sp["x"], sp["y"], planquadrat_cfg)
|
||||
if planquadrat_cfg is not None else "")
|
||||
|
||||
strecke = strecke_by_id.get(sp["strecke_id"])
|
||||
sepliste = strecke.get("sepliste") if strecke else None
|
||||
merkmale = {"Erzeugt aus": "Kreisel-Strecke-Kollision"}
|
||||
sep_kette_key = None
|
||||
marker_id = ""
|
||||
if sepliste:
|
||||
# Neue XDATA vorhanden: Nachbarn nur noch aus der Ketten-internen
|
||||
# Vorgaenger/Nachfolger-Beziehung (AS/ES ist immer das erste bzw.
|
||||
# letzte Sepliste-Element seiner Strecke). Die Keys (vorgaenger/
|
||||
# nachfolger) sind sep_kette-interne Schluessel, KEINE fertigen
|
||||
# TeileIds - erst nach dem Bau ALLER SEP/AS/ES-Zeilen dieser Kette
|
||||
# aufloesbar (siehe process_blocks, Abschnitt nach beiden Aufrufen,
|
||||
# analog zum Kreisel-Umlauf/Weichen-Muster).
|
||||
sepliste_ende = sepliste[0] if sp["ende_label"] == "Anfang" else sepliste[-1]
|
||||
lfdnr = sepliste_ende["lfdnr"]
|
||||
# ZZ_ASES_MARKER (csv:asmark-aktualisieren, Lisp/export.lsp) traegt
|
||||
# bereits eine STABILE, ueber Exportlaeufe hinweg gleichbleibende
|
||||
# ID fuer genau dieses Kettenende (schon 4-stellig formatiert,
|
||||
# ssg-id-format) - im sepliste-Eintrag als Feld "id" gespiegelt.
|
||||
# Diese ID unten WIEDERVERWENDEN statt eine neue zu minten: der
|
||||
# Marker bekommt in process_blocks bewusst KEINE eigene CSV-Zeile
|
||||
# (siehe pattern_ases_marker), seine ID bliebe sonst fuer immer
|
||||
# ungenutzt, waehrend hier bei jedem Exportlauf zusaetzlich eine
|
||||
# neue, hoehere ID fuer denselben physischen Punkt erfunden wuerde.
|
||||
marker_id = str(sepliste_ende.get("id", "")).strip()
|
||||
sep_kette_key = f"{sp['strecke_id']}#{lfdnr}"
|
||||
nachbarn = [] # in process_blocks nachgetragen
|
||||
merkmale["Kreisel"] = sp["kreisel_id"]
|
||||
merkmale[_strecke_typ_feld(strecke.get("teileart", ""))] = sp["strecke_id"]
|
||||
else:
|
||||
nachbarn = [sp["kreisel_id"], sp["strecke_id"]]
|
||||
merkmale["Kreisel"] = sp["kreisel_id"]
|
||||
merkmale["Strecke"] = sp["strecke_id"]
|
||||
merkmale["Streckenende"] = sp["ende_label"]
|
||||
|
||||
if marker_id:
|
||||
# Marker-ID uebernehmen (schon 4-stellig formatiert - NICHT
|
||||
# erneut formatieren) und den laufenden Zaehler ggf. hochziehen,
|
||||
# damit ein SPAETERER Mint in dieser Schleife (Altbestand-Wrapper
|
||||
# ohne Marker/sepliste) nie mit einer bereits wiederverwendeten
|
||||
# Marker-ID kollidiert - unabhaengig davon, in welcher Reihenfolge
|
||||
# Lisp Marker- vs. Separator-Proxy-IDs vergeben hat.
|
||||
teileid = marker_id
|
||||
if marker_id.isdigit():
|
||||
naechste_id = max(naechste_id, int(marker_id))
|
||||
else:
|
||||
# Altbestand-Wrapper ohne Marker-ID (oder Marker-ID-Zuweisung in
|
||||
# Lisp fehlgeschlagen) - unveraendertes bisheriges Verhalten.
|
||||
naechste_id += 1
|
||||
teileid = f"{naechste_id:04d}"
|
||||
|
||||
item = {
|
||||
"nr": elem_nr,
|
||||
"teileart": teileart,
|
||||
"teileid": teileid,
|
||||
"bezeichnung": f"{teileart} :{zaehler[teileart]}",
|
||||
"planquadrat": planquadrat,
|
||||
"position": f'{sp["x"]:.2f}, {sp["y"]:.2f}, {sp["z"]:.2f}',
|
||||
"boundingbox": f'{sp["dx"]:.2f}, {sp["dy"]:.2f}, {sp["dz"]:.2f}',
|
||||
"insertpoint": "",
|
||||
"k1": "", "k2": "", "k3": "", "k4": "",
|
||||
"nachbarn": ", ".join(nachbarn),
|
||||
"fehler": "",
|
||||
"merkmale": merkmale,
|
||||
}
|
||||
items.append(item)
|
||||
if sep_kette_key is not None:
|
||||
sep_kette_by_key[sep_kette_key] = item
|
||||
return elem_nr
|
||||
|
||||
|
||||
def map_separator_kette_items(items, sep_kette, sep_kette_by_key=None, dbg=None):
|
||||
"""Ordnet jeden in einer VF_n/GF_n-Kette eingebetteten Separator
|
||||
(sepliste-Eintrag typ "SEP", siehe compute_sep_kette) dem BEREITS
|
||||
vorhandenen Separator-Item zu und traegt es unter seinem sep_kette-
|
||||
Schluessel in sep_kette_by_key ein - fuer die Nachbar-Aufloesung in
|
||||
resolve_umlauf_und_sepliste_nachbarn.
|
||||
|
||||
WICHTIG - erzeugt KEINE neuen Zeilen (frueher tat das
|
||||
build_separator_kette_items): die in einer Kette verpackten Separatoren
|
||||
liegen bereits als ECHTE Separator_SP-INSERTs in der Zeichnung - der
|
||||
LISP-Export fuegt fuer jedes verpackte Symbol eine temporaere Kopie ein
|
||||
(csv:sep-proxies-erzeugen, export.lsp), die den normalen freien-Separator-
|
||||
Pfad in process_blocks durchlaeuft (mit echter vla-getboundingbox-Box, ID
|
||||
und fest gesetzter ZUORDNUNG = Wrapper-ID ueber "zuordnung_fix"). Beide
|
||||
Wege wuerden sonst dieselben physischen Separatoren DOPPELT zaehlen (26
|
||||
Proxy-INSERTs + 26 sepliste-Zeilen = 64 statt 38). Die sepliste liefert
|
||||
hier nur noch die REIHENFOLGE (Vorgaenger/Nachfolger), nicht die Zeilen.
|
||||
|
||||
Zuordnung sepliste-SEP -> vorhandenes Separator-Item: ueber die
|
||||
Wrapper-ID (Item-Merkmal "Zuordnung" == sep_kette-Eintrag "strecke_id",
|
||||
fixiert durch den LISP-Proxy) UND den naechstgelegenen INSERT-Punkt
|
||||
(Item-Felder "_x"/"_y" vs. sepliste-Koordinate x/y - beide sind der rohe
|
||||
Sub-INSERT-Einfuegepunkt aus derselben Bauphase, daher deckungsgleich;
|
||||
verifiziert 26/26 <300mm). Reicht der Wrapper mehrere Separatoren, trennt
|
||||
der INSERT-Punkt sie; bei nur einem ist die Wrapper-ID schon eindeutig.
|
||||
|
||||
Ein sepliste-SEP ohne passendes Item (sollte nicht vorkommen, da der Proxy
|
||||
jeden verpackten Separator einfuegt) wird uebersprungen (dbg-Hinweis).
|
||||
"""
|
||||
if sep_kette_by_key is None:
|
||||
sep_kette_by_key = {}
|
||||
# Vorhandene Separator-Items nach Wrapper-Zuordnung gruppieren (nur die mit
|
||||
# fixierter Zuordnung = die Proxy-Kopien verpackter Ketten-Separatoren;
|
||||
# frei platzierte Separatoren tragen keine sepliste und stoeren nicht).
|
||||
# Kandidatenpool nur noch fuer den Koordinaten-Fallback (SEP ohne "id").
|
||||
sep_items_by_zuordnung = {}
|
||||
for it in items:
|
||||
if it.get("teileart", "") != "ILS 2.0 Separator":
|
||||
@@ -711,96 +582,68 @@ def map_separator_kette_items(items, sep_kette, sep_kette_by_key=None, dbg=None)
|
||||
sep_items_by_zuordnung.setdefault(zuordnung, []).append(it)
|
||||
|
||||
if dbg:
|
||||
dbg("=== map_separator_kette_items: Kandidatenpools je Zuordnung ===")
|
||||
for zuordnung, kandidaten in sep_items_by_zuordnung.items():
|
||||
dbg(f" Zuordnung {zuordnung!r}: "
|
||||
f"{[k.get('teileid', '') for k in kandidaten]}")
|
||||
dbg("")
|
||||
dbg("=== map_separator_kette_items: sepliste-SEP -> Separator-Item ===")
|
||||
|
||||
# id(item) -> Liste der sep_kette-Keys, die genau dieses Item als
|
||||
# naechsten Kandidaten gewaehlt haben - Diagnose der NICHT-exklusiven
|
||||
# Zuordnung (jeder Key sucht unabhaengig im vollen Pool, siehe Docstring):
|
||||
# waehlen zwei Keys denselben Kandidaten, bleibt ein anderer Kandidat im
|
||||
# selben Pool ohne jeden sep_kette-Eintrag und damit ohne Nachbarn/TrackIds.
|
||||
gewaehlt_von = {}
|
||||
# Zuordnungen, fuer die es ueberhaupt sepliste-SEP-Schluessel gibt. Nur in
|
||||
# DIESEN Pools ist ein nicht gewaehlter Kandidat auffaellig; frei auf einem
|
||||
# Kreisel platzierte Separatoren tragen keine sepliste und bekommen ihre
|
||||
# Nachbarn aus dem Kreisel-Umlauf (siehe resolve_umlauf_und_sepliste_nachbarn).
|
||||
zuordnungen_mit_sepliste = set()
|
||||
dbg("=== map_sepliste_kette_items: sepliste-Eintrag -> Item ===")
|
||||
|
||||
per_id = 0
|
||||
per_fallback = 0
|
||||
ohne_treffer = 0
|
||||
for key, eintrag in sep_kette.items():
|
||||
marker_id = eintrag.get("id", "")
|
||||
if marker_id:
|
||||
item = by_teileid.get(marker_id)
|
||||
if item is not None:
|
||||
sep_kette_by_key[key] = item
|
||||
per_id += 1
|
||||
if dbg:
|
||||
dbg(f" {key} (typ={eintrag.get('typ')!r}) -> Item per ID "
|
||||
f"{marker_id!r} (TeileId {item.get('teileid', '')!r})")
|
||||
continue
|
||||
if dbg:
|
||||
dbg(f" {key}: id={marker_id!r} gesetzt, aber KEIN Item mit "
|
||||
f"dieser TeileId gefunden")
|
||||
|
||||
# Fallback nur fuer SEP ohne (aufloesbares) "id"-Feld (Altbestand).
|
||||
if eintrag.get("typ") != "SEP":
|
||||
ohne_treffer += 1
|
||||
continue
|
||||
zuordnungen_mit_sepliste.add(eintrag["strecke_id"])
|
||||
kandidaten = sep_items_by_zuordnung.get(eintrag["strecke_id"], [])
|
||||
if not kandidaten:
|
||||
ohne_treffer += 1
|
||||
if dbg:
|
||||
dbg(f" sepliste-SEP {key}: kein Separator-Item mit Zuordnung "
|
||||
dbg(f" {key}: kein Separator-Item mit Zuordnung "
|
||||
f"{eintrag['strecke_id']!r} gefunden - uebersprungen")
|
||||
continue
|
||||
# Naechster INSERT-Punkt zur sepliste-Koordinate.
|
||||
sx, sy = eintrag.get("x", 0.0), eintrag.get("y", 0.0)
|
||||
entfernungen = sorted(
|
||||
((math.hypot(it.get("_x", 0.0) - sx, it.get("_y", 0.0) - sy), it)
|
||||
for it in kandidaten),
|
||||
key=lambda p: p[0])
|
||||
best_d, best = entfernungen[0]
|
||||
best = min(kandidaten,
|
||||
key=lambda it: math.hypot(it.get("_x", 0.0) - sx, it.get("_y", 0.0) - sy))
|
||||
sep_kette_by_key[key] = best
|
||||
gewaehlt_von.setdefault(id(best), []).append(key)
|
||||
per_fallback += 1
|
||||
if dbg:
|
||||
rangliste = ", ".join(f"{it.get('teileid', '')!r}={d:.0f}mm"
|
||||
for d, it in entfernungen)
|
||||
dbg(f" sepliste-SEP {key} (x={sx:.0f},y={sy:.0f}) -> Separator-Item "
|
||||
f"TeileId {best.get('teileid', '')!r} (d={best_d:.0f}mm); "
|
||||
f"Kandidaten sortiert: [{rangliste}]"
|
||||
f"{dbg_highlight(best.get('teileid', ''))}")
|
||||
dbg(f" {key} (x={sx:.0f},y={sy:.0f}) -> Naehe-Fallback auf "
|
||||
f"Separator-Item TeileId {best.get('teileid', '')!r}")
|
||||
|
||||
if dbg:
|
||||
dbg("")
|
||||
mehrfach = {iid: keys for iid, keys in gewaehlt_von.items() if len(keys) > 1}
|
||||
if mehrfach:
|
||||
dbg(" WARNUNG: folgende Separator-Items wurden von MEHREREN "
|
||||
"sepliste-Keys unabhaengig als naechster Kandidat gewaehlt "
|
||||
"(nicht-exklusive Zuordnung):")
|
||||
for keys in mehrfach.values():
|
||||
teileid = sep_kette_by_key[keys[0]].get("teileid", "")
|
||||
dbg(f" TeileId {teileid!r} gewaehlt von Keys: {keys}"
|
||||
f"{dbg_highlight(teileid)}")
|
||||
gewaehlte_ids = set(gewaehlt_von.keys())
|
||||
for zuordnung, kandidaten in sep_items_by_zuordnung.items():
|
||||
if zuordnung not in zuordnungen_mit_sepliste:
|
||||
continue
|
||||
for it in kandidaten:
|
||||
if id(it) not in gewaehlte_ids:
|
||||
teileid = it.get("teileid", "")
|
||||
dbg(f" Separator-Item TeileId {teileid!r} (Zuordnung "
|
||||
f"{zuordnung!r}) wurde von KEINEM sepliste-Key gewaehlt "
|
||||
f"- bleibt ohne sep_kette-Eintrag, damit ohne Nachbarn/"
|
||||
f"TrackIds{dbg_highlight(teileid)}")
|
||||
dbg(f" Zuordnung: {per_id} per ID, {per_fallback} per Naehe-Fallback, "
|
||||
f"{ohne_treffer} ohne Treffer")
|
||||
return sep_kette_by_key
|
||||
|
||||
|
||||
def resolve_umlauf_und_sepliste_nachbarn(items, kreisel_umlauf, sep_kette,
|
||||
sep_kette_by_key, weiche_by_key, dbg=None):
|
||||
"""Loest die "Nachbarn"-Spalte aller Zeilen auf, die build_kreisel_weiche_items/
|
||||
build_strecke_schleus_items/map_separator_kette_items zunaechst leer
|
||||
gelassen (bzw. deren Item nur registriert) haben, weil ihr(e) Nachbar(n)
|
||||
beim eigenen Bau/der Registrierung noch nicht als TeileId feststand(en)
|
||||
(Weiche vs. Separator im Kreisel-Umlauf, oder eine sepliste-Zeile, deren
|
||||
Nachfolger erst danach registriert wird).
|
||||
"""Loest die "Nachbarn"-Spalte aller Zeilen auf, die build_kreisel_weiche_items
|
||||
zunaechst leer gelassen bzw. map_sepliste_kette_items nur registriert hat,
|
||||
weil ihr(e) Nachbar(n) beim eigenen Bau/der Registrierung noch nicht als
|
||||
TeileId feststand(en) (Weiche vs. Separator im Kreisel-Umlauf, oder eine
|
||||
sepliste-Zeile, deren Nachfolger erst danach registriert wird).
|
||||
|
||||
Muss NACH allen drei Aufrufen laufen (ihre TeileIds stehen erst dann
|
||||
fest). items wird IN-PLACE veraendert (item["nachbarn"]).
|
||||
Muss NACH build_kreisel_weiche_items UND map_sepliste_kette_items laufen
|
||||
(deren TeileIds stehen erst dann fest). items wird IN-PLACE veraendert
|
||||
(item["nachbarn"]).
|
||||
|
||||
kreisel_umlauf = Rueckgabe von export_neighbors.compute_kreisel_umlauf.
|
||||
sep_kette = Rueckgabe von export_neighbors.compute_sep_kette.
|
||||
sep_kette_by_key = dict sep_kette-Schluessel -> Item (befuellt von
|
||||
build_strecke_schleus_items UND
|
||||
map_separator_kette_items - DIESELBE dict fuer beide,
|
||||
siehe process_blocks: ein sep_kette-Nachbar kann in der
|
||||
jeweils anderen Funktion registriert worden sein).
|
||||
sep_kette_by_key = Rueckgabe von map_sepliste_kette_items (dict
|
||||
sep_kette-Schluessel -> Item).
|
||||
weiche_by_key = dict switches-"key" -> erzeugtes Item (von
|
||||
build_kreisel_weiche_items).
|
||||
"""
|
||||
@@ -837,8 +680,7 @@ def resolve_umlauf_und_sepliste_nachbarn(items, kreisel_umlauf, sep_kette,
|
||||
if item is None:
|
||||
if dbg:
|
||||
dbg(f" Kreisel {kreisel_tid} Punkt {punkt_key!r}: "
|
||||
f"KEIN Item in alle_schluessel gefunden - "
|
||||
f"uebersprungen{dbg_highlight(punkt_key)}")
|
||||
f"KEIN Item in alle_schluessel gefunden - uebersprungen")
|
||||
continue
|
||||
nachbarn = []
|
||||
for nachbar_key in (vorgaenger, nachfolger):
|
||||
@@ -849,20 +691,17 @@ def resolve_umlauf_und_sepliste_nachbarn(items, kreisel_umlauf, sep_kette,
|
||||
nachbarn.append(nachbar_item.get("teileid", ""))
|
||||
elif dbg:
|
||||
dbg(f" Kreisel {kreisel_tid} Punkt {punkt_key!r}: "
|
||||
f"Nachbar-Key {nachbar_key!r} nicht in alle_schluessel "
|
||||
f"aufloesbar{dbg_highlight(item.get('teileid', ''))}")
|
||||
f"Nachbar-Key {nachbar_key!r} nicht in alle_schluessel aufloesbar")
|
||||
teileid = item.get("teileid", "")
|
||||
if nachbarn:
|
||||
item["nachbarn"] = ", ".join(nachbarn)
|
||||
if dbg:
|
||||
dbg(f" Kreisel {kreisel_tid} Punkt {punkt_key!r} "
|
||||
f"(TeileId {teileid!r}): nachbarn={nachbarn}"
|
||||
f"{dbg_highlight(teileid)}")
|
||||
f"(TeileId {teileid!r}): nachbarn={nachbarn}")
|
||||
elif dbg:
|
||||
dbg(f" Kreisel {kreisel_tid} Punkt {punkt_key!r} "
|
||||
f"(TeileId {teileid!r}): KEINE Nachbarn aufgeloest "
|
||||
f"(vorgaenger={vorgaenger!r}, nachfolger={nachfolger!r})"
|
||||
f"{dbg_highlight(teileid)}")
|
||||
f"(vorgaenger={vorgaenger!r}, nachfolger={nachfolger!r})")
|
||||
|
||||
if dbg:
|
||||
dbg("")
|
||||
@@ -878,7 +717,7 @@ def resolve_umlauf_und_sepliste_nachbarn(items, kreisel_umlauf, sep_kette,
|
||||
if dbg:
|
||||
dbg(f" sepliste-Key {key!r} (typ={eintrag.get('typ')!r}): "
|
||||
f"KEIN Item in sep_kette_by_key - uebersprungen "
|
||||
f"(siehe map_separator_kette_items){dbg_highlight(key)}")
|
||||
f"(siehe map_sepliste_kette_items)")
|
||||
continue
|
||||
nachbarn = []
|
||||
for nachbar_key in (eintrag.get("vorgaenger"), eintrag.get("nachfolger")):
|
||||
@@ -894,8 +733,7 @@ def resolve_umlauf_und_sepliste_nachbarn(items, kreisel_umlauf, sep_kette,
|
||||
dbg(f" sepliste-Key {key!r} (TeileId {teileid!r}): "
|
||||
f"vorgaenger={eintrag.get('vorgaenger')!r} "
|
||||
f"nachfolger={eintrag.get('nachfolger')!r} -> "
|
||||
f"nachbarn={nachbarn} item.nachbarn={item.get('nachbarn', '')!r}"
|
||||
f"{dbg_highlight(teileid)}")
|
||||
f"nachbarn={nachbarn} item.nachbarn={item.get('nachbarn', '')!r}")
|
||||
|
||||
|
||||
def write_separatorliste_merkmale(items, sep_kette_by_key, dbg=None):
|
||||
@@ -909,18 +747,18 @@ def write_separatorliste_merkmale(items, sep_kette_by_key, dbg=None):
|
||||
Baureihenfolge - siehe compute_sep_kette) statt sep_kette selbst: dessen
|
||||
dict-Reihenfolge ist nicht garantiert, die sepliste-Liste dagegen schon.
|
||||
AS/ES zaehlen HIER mit (anders als beim "Separatorlist"-Merkmal frueher) -
|
||||
sie sind die Schleus-Enden der Kette (siehe build_strecke_schleus_items)
|
||||
und bekommen dort eine eigene TeileId ("ILS Ausschleuselement"/"ILS
|
||||
Einschleuselement"), analog zu den AS/ES-Enden, die beim Kreisel bereits
|
||||
ueber compute_kreisel_umlauf in dessen TrackIds auftauchen - ohne sie
|
||||
fehlten hier die Kettenenden, obwohl sie beim Kreisel schon drin sind.
|
||||
sie sind eigene Items ("ILS Ausschleuselement"/"ILS Einschleuselement",
|
||||
direkt aus dem dauerhaften ZZ_ASES_MARKER klassifiziert, siehe
|
||||
process_blocks), analog zu den AS/ES-Enden, die beim Kreisel bereits ueber
|
||||
compute_kreisel_umlauf in dessen TrackIds auftauchen - ohne sie fehlten
|
||||
hier die Kettenenden, obwohl sie beim Kreisel schon drin sind.
|
||||
|
||||
Der eigentliche Separator wurde bereits von map_separator_kette_items ueber
|
||||
die sepliste-Koordinate (x/y) auf sein reales Separator-Item (TeileId)
|
||||
aufgeloest, AS/ES von build_strecke_schleus_items - beide unter demselben
|
||||
"<strecke_id>#<lfdnr>"-Schluessel in sep_kette_by_key eingetragen; hier
|
||||
wird dieser Schluessel nur noch je Strecke eingesammelt. Muss darum NACH
|
||||
map_separator_kette_items/build_strecke_schleus_items laufen.
|
||||
Sowohl der Separator als auch AS/ES wurden bereits von
|
||||
map_sepliste_kette_items ueber die sepliste-ID (Feld "id") auf ihr reales
|
||||
Item (TeileId) aufgeloest, unter demselben "<strecke_id>#<lfdnr>"-
|
||||
Schluessel in sep_kette_by_key eingetragen; hier wird dieser Schluessel
|
||||
nur noch je Strecke eingesammelt. Muss darum NACH map_sepliste_kette_items
|
||||
laufen.
|
||||
"""
|
||||
for item in items:
|
||||
if item.get("teileart", "") not in GF_VF_TEILEARTEN:
|
||||
@@ -960,8 +798,8 @@ def write_kreisel_separatorliste_merkmale(items, kreisel_umlauf, sep_kette_by_ke
|
||||
Schluessel oder Weichen-"key") auf die TeileId des jeweils zugehoerigen
|
||||
Items aufgeloest. Derselbe Lookup wie in
|
||||
resolve_umlauf_und_sepliste_nachbarn, daher muss diese Funktion NACH
|
||||
map_separator_kette_items/build_kreisel_weiche_items/
|
||||
build_strecke_schleus_items laufen (deren TeileIds stehen erst dann fest).
|
||||
map_sepliste_kette_items/build_kreisel_weiche_items laufen (deren
|
||||
TeileIds stehen erst dann fest).
|
||||
"""
|
||||
freipunkt_by_id = {it.get("teileid", ""): it for it in items
|
||||
if it.get("teileart", "") in KREISEL_UMLAUF_TEILEARTEN}
|
||||
@@ -987,6 +825,67 @@ def write_kreisel_separatorliste_merkmale(items, kreisel_umlauf, sep_kette_by_ke
|
||||
kreisel_item["trackids"] = ", ".join(teileids)
|
||||
|
||||
|
||||
def pruefe_ases_in_beiden_containern(items, dbg=None):
|
||||
"""Prueft, ob jede AS-/ES-Zeile (AUSSCHLEUS_TEILEART/EINSCHLEUS_TEILEART)
|
||||
in BEIDEN Containern auftaucht, zu denen sie gehoert:
|
||||
- in den TrackIds ihrer EIGENEN Strecke (VF_n/GF_n, ueber das
|
||||
ZUORDNUNG-Attribut des Markers - siehe Item-Feld "_zuordnung")
|
||||
- in den TrackIds MINDESTENS eines Kreisels (der beruehrte Carrier,
|
||||
geometrisch ueber compute_kreisel_umlauf/KREISEL_UMLAUF_TEILEARTEN
|
||||
ermittelt - nur "ILS 2.0 Kreisel", Eckrad nimmt am Umlauf nicht teil,
|
||||
siehe compute_kreisel_umlauf/KREISEL_SPLIT_TEILEART)
|
||||
|
||||
Muss NACH write_separatorliste_merkmale UND
|
||||
write_kreisel_separatorliste_merkmale laufen (beide TrackIds-Spalten
|
||||
muessen bereits stehen).
|
||||
|
||||
Ein AS/ES, das in einem der beiden Container fehlt, zeigt einen echten
|
||||
Fehler in der Zuordnungskette an (z.B. ZUORDNUNG-Attribut leer/falsch,
|
||||
Kollisionstoleranz zu knapp, Kreisel zu weit weg, fehlende Bounding-Box am
|
||||
Marker) - kein Normalfall. Traegt in diesem Fall eine Warnung in
|
||||
item["warnung"] ein (erscheint in der CSV-Spalte "Warnungen") und gibt sie
|
||||
zusaetzlich auf der Konsole/im Debuglog aus.
|
||||
|
||||
Rueckgabe: Liste der betroffenen TeileIds (fuer Tests/Diagnose), leer bei
|
||||
vollstaendiger Zuordnung.
|
||||
"""
|
||||
strecke_by_id = {it.get("teileid", ""): it for it in items
|
||||
if it.get("teileart", "") in GF_VF_TEILEARTEN}
|
||||
kreisel_trackid_sets = [
|
||||
{t.strip() for t in (it.get("trackids", "") or "").split(",") if t.strip()}
|
||||
for it in items if it.get("teileart", "") == KREISEL_SPLIT_TEILEART
|
||||
]
|
||||
|
||||
auffaellig = []
|
||||
for it in items:
|
||||
if it.get("teileart", "") not in (AUSSCHLEUS_TEILEART, EINSCHLEUS_TEILEART):
|
||||
continue
|
||||
teileid = it.get("teileid", "")
|
||||
fehlend = []
|
||||
|
||||
strecke = strecke_by_id.get(it.get("_zuordnung", ""))
|
||||
if strecke is None:
|
||||
fehlend.append("eigene Strecke (ZUORDNUNG unaufloesbar)")
|
||||
else:
|
||||
strecke_ids = {t.strip() for t in (strecke.get("trackids", "") or "").split(",") if t.strip()}
|
||||
if teileid not in strecke_ids:
|
||||
fehlend.append("eigene Strecke")
|
||||
|
||||
if not any(teileid in ids for ids in kreisel_trackid_sets):
|
||||
fehlend.append("Kreisel")
|
||||
|
||||
if fehlend:
|
||||
auffaellig.append(teileid)
|
||||
warnung = f"nicht in TrackIds von: {', '.join(fehlend)}"
|
||||
bisherige = it.get("warnung", "")
|
||||
it["warnung"] = f"{bisherige}; {warnung}" if bisherige else warnung
|
||||
meldung = f"[export_csv] WARNUNG: {it.get('teileart', '')} {teileid}: {warnung}"
|
||||
print(meldung)
|
||||
if dbg:
|
||||
dbg(f" {meldung}")
|
||||
return auffaellig
|
||||
|
||||
|
||||
def process_blocks(blocks, lookup, dbg=None):
|
||||
# x_origin_mm/y_origin_mm einmalig fuer diesen Exportlauf aufloesen (siehe
|
||||
# export_planquadrat.resolve_origins): automatisch aus den Bloecken, falls
|
||||
@@ -1010,6 +909,8 @@ def process_blocks(blocks, lookup, dbg=None):
|
||||
eingang_count = 0
|
||||
ausgang_count = 0
|
||||
geruest_count = 0
|
||||
ausschleus_count = 0
|
||||
einschleus_count = 0
|
||||
|
||||
# Zaehlung fuer die Omniflo-Sum-Zeile (nur Bogen/Weiche/Gerade)
|
||||
cnt_wk = cnt_einzel = cnt_delta = cnt_doppel = cnt_stern = 0
|
||||
@@ -1025,15 +926,54 @@ def process_blocks(blocks, lookup, dbg=None):
|
||||
|
||||
# ZZ_ASES_MARKER (siehe cfg/export.cfg pattern_ases_marker sowie
|
||||
# csv:asmark-aktualisieren in Lisp/export.lsp): der persistente
|
||||
# Marker, den Lisp an jedes AS-/ES-Kettenende setzt, damit dessen ID
|
||||
# ueber Exportlaeufe hinweg stabil bleibt. Bekommt hier bewusst KEINE
|
||||
# eigene CSV-Zeile - die sichtbare AS/ES-Zeile fuer denselben
|
||||
# physischen Punkt liefert weiterhin build_strecke_schleus_items
|
||||
# (reine Geometrie); die Marker-ID wird dort ueber das sepliste-Feld
|
||||
# "id" wiederverwendet (siehe dort). Eine eigene Zeile hier waere
|
||||
# doppelt gezaehlt und haette ohnehin keine sinnvolle Geometrie/
|
||||
# Boundingbox aus dem kleinen Marker-Rechteck selbst.
|
||||
# Marker, den Lisp an jedes AS-/ES-Kettenende setzt. Er IST die
|
||||
# AS-/ES-Zeile - TeileId, Position und Boundingbox kommen direkt vom
|
||||
# Marker-Block selbst (echtes INSERT, echte vla-getboundingbox-Box),
|
||||
# kein synthetischer Bau-Schritt mehr noetig (fruehere Version:
|
||||
# compute_strecke_kreisel_schleus/build_strecke_schleus_items suchten
|
||||
# den anschliessenden Kreisel per Kollisionstest und erfanden dabei
|
||||
# eine eigene, bei jedem Export neue TeileId). "Kreisel"-Merkmal und
|
||||
# ID-basierte sep_kette-Zuordnung tragen unten zwei kurze Nachlaeufe
|
||||
# in process_blocks nach, sobald alle Items klassifiziert sind (die
|
||||
# eigene Kette bzw. der beruehrte Kreisel koennen an DIESER Stelle
|
||||
# noch unklassifiziert sein).
|
||||
if matches_any(bname, BLOCKPATTERNS.get("pattern_ases_marker", [])):
|
||||
attribs = block.get("attribs", {})
|
||||
typ = attribs.get("TYP", "")
|
||||
teileart = {"AS": AUSSCHLEUS_TEILEART, "ES": EINSCHLEUS_TEILEART}.get(typ)
|
||||
if teileart is None:
|
||||
# Sollte nicht vorkommen (TYP wird von csv:asmark-aktualisieren
|
||||
# immer gesetzt) - defensiv ueberspringen statt abzustuerzen.
|
||||
print(f"[export_csv] WARNUNG: {bname} ohne gueltiges TYP-Attribut "
|
||||
f"(TYP={typ!r}) - uebersprungen")
|
||||
continue
|
||||
elem_nr += 1
|
||||
if typ == "AS":
|
||||
ausschleus_count += 1
|
||||
zaehler_n = ausschleus_count
|
||||
else:
|
||||
einschleus_count += 1
|
||||
zaehler_n = einschleus_count
|
||||
items.append({
|
||||
"nr": elem_nr,
|
||||
"teileart": teileart,
|
||||
"teileid": attribs.get("ID") or format_sensor_id("ASES", elem_nr),
|
||||
"bezeichnung": f"{teileart} :{zaehler_n}",
|
||||
"planquadrat": planquadrat_column(block, planquadrat_cfg),
|
||||
"merkmale": {
|
||||
"Streckenende": "Anfang" if typ == "AS" else "Ende",
|
||||
"Element": attribs.get("ELEMENT", ""),
|
||||
"ARTINR": attribs.get("ARTINR", ""),
|
||||
},
|
||||
# Private Felder (nicht in der CSV) fuer die beiden Nachlaeufe
|
||||
# weiter unten in process_blocks: "_zuordnung" = ID des
|
||||
# eigenen VF_n/GF_n-Wrappers (fuer das "Förderer"/"Strecke"-
|
||||
# Merkmal), "_lfdnr" wird hier nicht gebraucht (die sep_kette-
|
||||
# Zuordnung laeuft exakt ueber die TeileId, siehe
|
||||
# map_sepliste_kette_items).
|
||||
"_zuordnung": attribs.get("ZUORDNUNG", ""),
|
||||
**bbox_columns(block),
|
||||
})
|
||||
continue
|
||||
|
||||
# Omniflo Bogen oder Weiche
|
||||
@@ -1108,12 +1048,8 @@ def process_blocks(blocks, lookup, dbg=None):
|
||||
"bezeichnung": bezeichnung_oder_fallback(block, f"VarioFoerderer :{vf_count}"),
|
||||
"planquadrat": planquadrat_column(block, planquadrat_cfg),
|
||||
"merkmale": build_variofoerderer_merkmale(block),
|
||||
# x/y (Block-Einfuegepunkt = Kettenanfang) fuer die Anfang/Ende-
|
||||
# Bestimmung der Ein-/Ausschleuselemente (compute_strecke_kreisel_schleus)
|
||||
"x": block.get("x", 0.0),
|
||||
"y": block.get("y", 0.0),
|
||||
# Sepliste-XDATA (AS/SEP/ES-Reihenfolge) durchreichen - Quelle
|
||||
# fuer compute_sep_kette/map_separator_kette_items (Ketten-
|
||||
# fuer compute_sep_kette/map_sepliste_kette_items (Ketten-
|
||||
# interne Separatoren + AS/ES-Nachbarn). Ohne diese Weitergabe
|
||||
# liefe die gesamte sepliste-basierte Nachbarverkettung ins
|
||||
# Leere (das Feld stand im JSON, wurde aber nicht ans Item
|
||||
@@ -1134,10 +1070,6 @@ def process_blocks(blocks, lookup, dbg=None):
|
||||
"bezeichnung": bezeichnung_oder_fallback(block, f"Gefaellestrecke :{gf_count}"),
|
||||
"planquadrat": planquadrat_column(block, planquadrat_cfg),
|
||||
"merkmale": build_variofoerderer_merkmale(block),
|
||||
# x/y (Block-Einfuegepunkt = Kettenanfang) fuer die Anfang/Ende-
|
||||
# Bestimmung der Ein-/Ausschleuselemente (compute_strecke_kreisel_schleus)
|
||||
"x": block.get("x", 0.0),
|
||||
"y": block.get("y", 0.0),
|
||||
# Sepliste-XDATA durchreichen (siehe VF_N oben).
|
||||
"sepliste": block.get("sepliste"),
|
||||
**bbox_columns(block),
|
||||
@@ -1232,9 +1164,9 @@ def process_blocks(blocks, lookup, dbg=None):
|
||||
# ueberschreiben darf - siehe build_sensor_merkmale.
|
||||
"_zuordnung_fix": bool(block.get("zuordnung_fix")),
|
||||
# Roher INSERT-Punkt (nicht der bbox-Mittelpunkt) - dient
|
||||
# map_separator_kette_items zum Zuordnen dieses (Proxy-)
|
||||
# Separators zu seinem sepliste-Eintrag (Match ueber
|
||||
# Wrapper-ID + naechster INSERT-Punkt, siehe dort).
|
||||
# map_sepliste_kette_items als Koordinaten-FALLBACK, falls der
|
||||
# sepliste-Eintrag (noch) kein "id"-Feld traegt (Altbestand;
|
||||
# der Normalfall loest exakt per ID auf, siehe dort).
|
||||
"_x": block.get("x", 0.0), "_y": block.get("y", 0.0),
|
||||
**cols,
|
||||
})
|
||||
@@ -1433,34 +1365,46 @@ def process_blocks(blocks, lookup, dbg=None):
|
||||
items, NEIGHBOR_TOLERANCE_MM, durchmesser_mm=KREISEL_DURCHMESSER_MM,
|
||||
element_box_mm=ELEMENT_BOX_MM, dbg=dbg)
|
||||
|
||||
# Umlauf-Reihenfolge an jedem Kreisel: freie Separatoren (per Zuordnung
|
||||
# oben bereits einem Kreisel zugeordnet), die AS/ES-Enden andockender
|
||||
# Ketten (aus sep_kette) UND die Kreisel-Kreisel-Weichen (touch_switches),
|
||||
# sortiert nach Winkel um die Kreiselachse (siehe compute_kreisel_umlauf -
|
||||
# DREHRICHTUNG-Attribut bestimmt den Umlaufsinn).
|
||||
# Umlauf-Reihenfolge an jedem Kreisel: freie Separatoren, BTMT-Stationen
|
||||
# UND die AS/ES-Enden andockender Ketten (alle zusammen KREISEL_UMLAUF_
|
||||
# TEILEARTEN, einheitlich per eigener Bounding-Box zugeordnet - ein AS/ES
|
||||
# bringt seine Box vom ZZ_ASES_MARKER direkt mit) sowie die Kreisel-
|
||||
# Kreisel-Weichen (touch_switches), sortiert nach Winkel um die
|
||||
# Kreiselachse (siehe compute_kreisel_umlauf - DREHRICHTUNG-Attribut
|
||||
# bestimmt den Umlaufsinn).
|
||||
kreisel_umlauf = compute_kreisel_umlauf(
|
||||
items, sep_kette, NEIGHBOR_TOLERANCE_MM,
|
||||
items, NEIGHBOR_TOLERANCE_MM,
|
||||
durchmesser_mm=KREISEL_DURCHMESSER_MM, touch_switches=touch_switches, dbg=dbg)
|
||||
|
||||
# Neue Zeilen, deren "Nachbarn"-Spalte aus einem sep_kette-Schluessel
|
||||
# aufgeloest werden muss, erzeugen: Kreisel-Kreisel-Weichen, dann Aus-/
|
||||
# Einschleuselemente (AS/ES). Ein sep_kette-Schluessel kann auf eine Zeile
|
||||
# zeigen, die ERST DANACH registriert wird, darum erst NACH allen
|
||||
# Registrierungen aufloesen (siehe unten) statt inline - frueherer Bug
|
||||
# schrieb sonst den rohen Schluessel ("0026#3") statt der TeileId.
|
||||
# aufgeloest werden muss, erzeugen: nur noch die Kreisel-Kreisel-Weichen
|
||||
# (AS/ES sind bereits als eigene Items klassifiziert, siehe process_blocks
|
||||
# oben). Ein sep_kette-Schluessel kann auf eine Zeile zeigen, die ERST
|
||||
# DANACH registriert wird, darum erst NACH allen Registrierungen aufloesen
|
||||
# (siehe unten) statt inline - frueherer Bug schrieb sonst den rohen
|
||||
# Schluessel ("0026#3") statt der TeileId.
|
||||
elem_nr, weiche_by_key = build_kreisel_weiche_items(
|
||||
items, planquadrat_cfg, elem_nr, touch_switches, dbg=dbg)
|
||||
sep_kette_by_key = {}
|
||||
elem_nr = build_strecke_schleus_items(
|
||||
items, planquadrat_cfg, elem_nr, sep_kette=sep_kette,
|
||||
sep_kette_by_key=sep_kette_by_key, dbg=dbg)
|
||||
# Die in einer Kette verpackten Separatoren erzeugen KEINE eigenen Zeilen -
|
||||
# sie liegen bereits als (Proxy-)Separator_SP-Items vor (freier-Separator-
|
||||
# Pfad oben). map_separator_kette_items ordnet ihnen nur ihren sepliste-
|
||||
# Schluessel zu, damit die Nachbar-Reihenfolge (AS -> SEP -> ES) auf die
|
||||
# vorhandenen Items angewandt werden kann (kein Doppel-Export).
|
||||
map_separator_kette_items(
|
||||
items, sep_kette, sep_kette_by_key=sep_kette_by_key, dbg=dbg)
|
||||
|
||||
# "Förderer"/"Strecke"-Merkmal an jedem AS/ES nachtragen: die eigene Kette
|
||||
# (ZUORDNUNG-Attribut des Markers, siehe "_zuordnung") kann bei der
|
||||
# Klassifikation des Markers selbst noch unklassifiziert gewesen sein -
|
||||
# der FeldNAME haengt von deren TeileArt ab (VF -> "Förderer",
|
||||
# GF -> "Strecke", siehe _strecke_typ_feld), der WERT ist einfach die
|
||||
# ZUORDNUNG selbst (keine Geometrie mehr noetig).
|
||||
strecke_by_id = {it.get("teileid", ""): it for it in items
|
||||
if it.get("teileart", "") in GF_VF_TEILEARTEN}
|
||||
for it in items:
|
||||
if it.get("teileart", "") not in (AUSSCHLEUS_TEILEART, EINSCHLEUS_TEILEART):
|
||||
continue
|
||||
strecke = strecke_by_id.get(it.get("_zuordnung", ""))
|
||||
if strecke is not None:
|
||||
it["merkmale"][_strecke_typ_feld(strecke.get("teileart", ""))] = \
|
||||
strecke.get("teileid", "")
|
||||
|
||||
# sep_kette-Schluessel (AS/SEP/ES) auf ihre bereits vorhandenen Items
|
||||
# aufloesen - exakt per ID, siehe map_sepliste_kette_items.
|
||||
sep_kette_by_key = map_sepliste_kette_items(items, sep_kette, dbg=dbg)
|
||||
|
||||
# Spalte "TrackIds" je Strecke (Reihenfolge der SEP-TeileIds aus der
|
||||
# sepliste, x/y bereits oben auf das reale Separator-Item aufgeloest).
|
||||
@@ -1477,6 +1421,12 @@ def process_blocks(blocks, lookup, dbg=None):
|
||||
write_kreisel_separatorliste_merkmale(
|
||||
items, kreisel_umlauf, sep_kette_by_key, weiche_by_key, dbg=dbg)
|
||||
|
||||
# Konsistenzpruefung: jedes AS/ES muss jetzt sowohl in den TrackIds seiner
|
||||
# eigenen Strecke als auch in denen eines Kreisels auftauchen (siehe
|
||||
# Docstring) - Abweichungen deuten auf einen echten Fehler in der
|
||||
# Zuordnungskette hin und werden ueber "Warnungen" sichtbar gemacht.
|
||||
pruefe_ases_in_beiden_containern(items, dbg=dbg)
|
||||
|
||||
# Nach dem Sammelheimen aller Zeilen: sortiert nach TeileId ausgeben, damit
|
||||
# Elementnummer (erste Spalte) und TeileId nach Moeglichkeit uebereinstimmen
|
||||
# (siehe format_csv_line/Elementnummer-Neuvergabe unten). Numerische IDs
|
||||
|
||||
Reference in New Issue
Block a user