CSV-Export: Nachbarschaft/bbox fuer Kreisel-Kreisel-Weichen, sepliste-Ketten und Separatoren repariert
Kreisel-Kreisel-Weichen fehlten bisher im Kreisel-Umlauf (compute_kreisel_umlauf), wodurch angrenzende Separatoren auf falsche/fehlende Nachbarn zeigten. Die kreisel_bounds-Matching-Box ignorierte ausserdem die tatsaechliche Kreiseldrehung (faelschlich als achsparallel angenommen), was bei gedrehten langen Kreiseln (Mubea Kreisel 0002) zu falschen Nachbarschaften fuehrte. Die sepliste-XDATA (AS/SEP/ES-Reihenfolge je VF_n/GF_n) wurde nie ans Python-Item durchgereicht - die gesamte Ketten-Nachbarschaftserkennung lief dadurch ins Leere. Nach dem Beheben entstand eine Doppelzaehlung: die in einer Kette verpackten Separatoren existieren bereits als echte Proxy-INSERTs (csv:sep-proxies-erzeugen); build_separator_kette_items erzeugte zusaetzlich eigene Zeilen aus der sepliste. Umgebaut zu map_separator_kette_items: ordnet die sepliste-Reihenfolge den vorhandenen Proxy-Items zu (Match ueber Wrapper-ID + INSERT-Punkt) statt neue Zeilen zu erzeugen - keine Doppelzaehlung mehr, Nachbarn korrekt gesetzt. Ausserdem: vla-getboundingbox scheiterte in einem realen Export bei ALLEN Bloecken (fehlende bbox ueberall), der Fehler wurde bisher still verschluckt. REGENALL vor der Export-Schleife behebt das; eine neue Diagnose (exp-bbox-fail) meldet kuenftige Ausfaelle statt sie zu verschlucken. Freie/eingebettete Separatoren ohne bbox bekommen zusaetzlich einen Fallback aus einer konfigurierbaren Symbol-Groesse (cfg/export.cfg [Boundingbox] separator_box_*). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+268
-101
@@ -104,6 +104,7 @@ danach.
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -115,6 +116,8 @@ from export_neighbors import (
|
||||
load_omniflo_ks_tolerance_mm,
|
||||
load_kreisel_durchmesser_mm,
|
||||
load_element_box_mm,
|
||||
load_separator_box_mm,
|
||||
separator_bbox,
|
||||
load_collision_debug_target,
|
||||
compute_neighbor_ids,
|
||||
compute_neighbor_errors,
|
||||
@@ -136,6 +139,7 @@ NEIGHBOR_OMNIFLO_CELL_SIZE_MM = load_omniflo_cell_size_mm()
|
||||
NEIGHBOR_OMNIFLO_KS_TOLERANCE_MM = load_omniflo_ks_tolerance_mm()
|
||||
KREISEL_DURCHMESSER_MM = load_kreisel_durchmesser_mm()
|
||||
ELEMENT_BOX_MM = load_element_box_mm()
|
||||
SEPARATOR_BOX_MM = load_separator_box_mm()
|
||||
NEIGHBOR_DEBUG_TARGET = load_collision_debug_target()
|
||||
|
||||
|
||||
@@ -433,6 +437,31 @@ def bbox_columns(block):
|
||||
}
|
||||
|
||||
|
||||
def _fallback_separator_bbox(cols, block, separator_box_mm=None):
|
||||
"""Ergaenzt eine fehlende Bounding-Box (cols["_bbox"] is None) durch die
|
||||
feste Separator-Symbol-Box aus cfg [Boundingbox] separator_box_* an der
|
||||
realen INSERT-Position/-Drehung des Blocks. Wird fuer frei platzierte
|
||||
Separator_SP-INSERTs gebraucht, deren vla-getboundingbox beim Export
|
||||
scheiterte (z.B. eingefrorener Layer) - ohne "_bbox" fielen sie sonst aus
|
||||
der Nachbarschafts-/Zuordnungserkennung. Hat der Block schon eine bbox,
|
||||
bleibt cols unveraendert.
|
||||
|
||||
Rueckgabe: (ggf. ergaenztes) cols-dict."""
|
||||
if cols.get("_bbox"):
|
||||
return cols
|
||||
if separator_box_mm is None:
|
||||
separator_box_mm = SEPARATOR_BOX_MM
|
||||
bbox = separator_bbox(block.get("x", 0.0) or 0.0, block.get("y", 0.0) or 0.0,
|
||||
block.get("rotation", 0.0) or 0.0,
|
||||
separator_box_mm=separator_box_mm,
|
||||
z=block.get("z", 0.0) or 0.0)
|
||||
cols = dict(cols)
|
||||
cols["position"] = f'{bbox["cx"]:.2f}, {bbox["cy"]:.2f}, {bbox["cz"]:.2f}'
|
||||
cols["boundingbox"] = f'{bbox["dx"]:.2f}, {bbox["dy"]:.2f}, {bbox["dz"]:.2f}'
|
||||
cols["_bbox"] = bbox
|
||||
return cols
|
||||
|
||||
|
||||
def planquadrat_column(block, planquadrat_cfg):
|
||||
"""Berechnet das Planquadrat aus der Bounding-Box des Blocks, falls
|
||||
vorhanden (siehe export_planquadrat.py), sonst aus dem Einfuegepunkt.
|
||||
@@ -483,32 +512,39 @@ def max_numeric_teileid(items):
|
||||
return max_id
|
||||
|
||||
|
||||
def build_kreisel_weiche_items(items, planquadrat_cfg, start_elem_nr, dbg=None):
|
||||
def build_kreisel_weiche_items(items, planquadrat_cfg, start_elem_nr, switches, dbg=None):
|
||||
"""Haengt fuer jeden Kreisel-Beruehrpunkt eine "ILS Weiche"-Zeile an items
|
||||
an (siehe export_neighbors.compute_kreisel_touch_switches).
|
||||
|
||||
switches = Rueckgabe von compute_kreisel_touch_switches, bereits VOR dem
|
||||
Aufruf berechnet (der Aufrufer speist dieselbe Liste auch in
|
||||
compute_kreisel_umlauf ein, damit die Weiche im Kreisel-Umlauf auftaucht -
|
||||
siehe process_blocks). Jeder switches-Eintrag traegt seinen stabilen
|
||||
"key" (sw["key"]) - hier wird KEIN neuer Schluessel erzeugt.
|
||||
|
||||
TeileId zaehlt aus der hoechsten bereits enthaltenen numerischen ID hoch
|
||||
(max_numeric_teileid), 4-stellig aufgefuellt wie die Kreisel-IDs. Anzahl
|
||||
ist wie bei allen Zeilen 1; Nachbarn = die beiden Kreisel-IDs; K1-K4 und
|
||||
Insertpoint bleiben leer; Position = Beruehrkoordinaten; Boundingbox = die
|
||||
feste, an der Kreiselachse ausgerichtete Element-Box (element_box_mm,
|
||||
cfg/export.cfg [Boundingbox]).
|
||||
ist wie bei allen Zeilen 1; Nachbarn = die beiden Kreisel-IDs (durch den
|
||||
Kreisel-Umlauf ggf. bereits um die eigentlichen Umlauf-Nachbarn ergaenzt/
|
||||
ersetzt, siehe process_blocks); K1-K4 und Insertpoint bleiben leer;
|
||||
Position = Beruehrkoordinaten; Boundingbox = die feste, an der
|
||||
Kreiselachse ausgerichtete Element-Box (element_box_mm, cfg/export.cfg
|
||||
[Boundingbox]).
|
||||
|
||||
Rueckgabe: fortgeschriebene Elementnummer (start_elem_nr + Anzahl Weichen).
|
||||
Rueckgabe: (fortgeschriebene Elementnummer, dict switches-"key" -> erzeugtes
|
||||
Item) - die Key-Zuordnung braucht process_blocks, um nach dem Kreisel-
|
||||
Umlauf die tatsaechliche "Nachbarn"-Spalte der Weiche zu setzen.
|
||||
"""
|
||||
switches = compute_kreisel_touch_switches(
|
||||
items, NEIGHBOR_TOLERANCE_MM, durchmesser_mm=KREISEL_DURCHMESSER_MM,
|
||||
element_box_mm=ELEMENT_BOX_MM, dbg=dbg)
|
||||
|
||||
naechste_id = max_numeric_teileid(items)
|
||||
elem_nr = start_elem_nr
|
||||
weiche_by_key = {}
|
||||
for n, sw in enumerate(switches, start=1):
|
||||
elem_nr += 1
|
||||
naechste_id += 1
|
||||
teileid = f"{naechste_id:04d}"
|
||||
planquadrat = (compute_planquadrat(sw["x"], sw["y"], planquadrat_cfg)
|
||||
if planquadrat_cfg is not None else "")
|
||||
items.append({
|
||||
item = {
|
||||
"nr": elem_nr,
|
||||
"teileart": WEICHE_TEILEART,
|
||||
"teileid": teileid,
|
||||
@@ -527,8 +563,10 @@ def build_kreisel_weiche_items(items, planquadrat_cfg, start_elem_nr, dbg=None):
|
||||
"Achse von Kreisel": sw["achse_von"],
|
||||
"Beruehrung": sw["beruehrung"],
|
||||
},
|
||||
})
|
||||
return elem_nr
|
||||
}
|
||||
items.append(item)
|
||||
weiche_by_key[sw["key"]] = item
|
||||
return elem_nr, weiche_by_key
|
||||
|
||||
|
||||
def _strecke_typ_feld(teileart):
|
||||
@@ -539,7 +577,8 @@ 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, dbg=None):
|
||||
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
|
||||
@@ -553,18 +592,29 @@ def build_strecke_schleus_items(items, planquadrat_cfg, start_elem_nr, sep_kette
|
||||
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), enthaelt "Nachbarn" NUR noch die IDs des
|
||||
ketteninternen Vorgaengers/Nachfolgers (Separator oder je anderem
|
||||
Kreisel-Ende) statt Kreisel-ID + Strecken-ID; die Kreisel-/Foerderer-
|
||||
Zugehoerigkeit steht dann 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.
|
||||
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).
|
||||
|
||||
Rueckgabe: fortgeschriebene Elementnummer.
|
||||
"""
|
||||
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)
|
||||
@@ -595,14 +645,18 @@ def build_strecke_schleus_items(items, planquadrat_cfg, start_elem_nr, sep_kette
|
||||
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
|
||||
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).
|
||||
# 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).
|
||||
lfdnr = sepliste[0]["lfdnr"] if sp["ende_label"] == "Anfang" else sepliste[-1]["lfdnr"]
|
||||
key = f"{sp['strecke_id']}#{lfdnr}"
|
||||
eintrag = sep_kette.get(key, {})
|
||||
nachbarn = [n for n in (eintrag.get("vorgaenger"), eintrag.get("nachfolger")) if n]
|
||||
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:
|
||||
@@ -611,7 +665,7 @@ def build_strecke_schleus_items(items, planquadrat_cfg, start_elem_nr, sep_kette
|
||||
merkmale["Strecke"] = sp["strecke_id"]
|
||||
merkmale["Streckenende"] = sp["ende_label"]
|
||||
|
||||
items.append({
|
||||
item = {
|
||||
"nr": elem_nr,
|
||||
"teileart": teileart,
|
||||
"teileid": teileid,
|
||||
@@ -624,63 +678,145 @@ def build_strecke_schleus_items(items, planquadrat_cfg, start_elem_nr, sep_kette
|
||||
"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 build_separator_kette_items(items, planquadrat_cfg, start_elem_nr, sep_kette, dbg=None):
|
||||
"""Haengt fuer jeden in einer VF_n/GF_n-Kette eingebetteten Separator
|
||||
("Staustrecke_Separator_SP_300_mm", ueber die neue "sepliste"-XDATA
|
||||
bekannt - siehe ssg-sepliste-xdata-schreiben/compute_sep_kette) eine
|
||||
eigene "ILS 2.0 Separator"-Zeile an. Bisher wurden diese internen
|
||||
Ketten-Separatoren nur gezaehlt (ANZAHL_SEPARATOR), nicht einzeln
|
||||
exportiert - anders als frei platzierte Separator_SP-Symbole.
|
||||
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.
|
||||
|
||||
Nachbarn = ketteninterner Vorgaenger/Nachfolger (Separator oder AS/ES),
|
||||
analog zur AS/ES-Nachbarn-Spalte in build_strecke_schleus_items.
|
||||
Merkmale bekommen "Zuordnung" = Strecken-TeileId (wie bei frei
|
||||
platzierten Separatoren ueber compute_sensor_zuordnung).
|
||||
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.
|
||||
|
||||
Rueckgabe: fortgeschriebene Elementnummer.
|
||||
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).
|
||||
"""
|
||||
strecke_by_id = {it.get("teileid", ""): it for it in items
|
||||
if it.get("teileart", "") in GF_VF_TEILEARTEN}
|
||||
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).
|
||||
sep_items_by_zuordnung = {}
|
||||
for it in items:
|
||||
if it.get("teileart", "") != "ILS 2.0 Separator":
|
||||
continue
|
||||
zuordnung = it.get("merkmale", {}).get("Zuordnung", "")
|
||||
if zuordnung:
|
||||
sep_items_by_zuordnung.setdefault(zuordnung, []).append(it)
|
||||
|
||||
naechste_id = max_numeric_teileid(items)
|
||||
elem_nr = start_elem_nr
|
||||
zaehler = 0
|
||||
for key, eintrag in sep_kette.items():
|
||||
if eintrag.get("typ") != "SEP":
|
||||
continue
|
||||
elem_nr += 1
|
||||
naechste_id += 1
|
||||
zaehler += 1
|
||||
teileid = f"{naechste_id:04d}"
|
||||
planquadrat = (compute_planquadrat(eintrag["x"], eintrag["y"], planquadrat_cfg)
|
||||
if planquadrat_cfg is not None else "")
|
||||
nachbarn = [n for n in (eintrag.get("vorgaenger"), eintrag.get("nachfolger")) if n]
|
||||
strecke = strecke_by_id.get(eintrag["strecke_id"])
|
||||
merkmale = {
|
||||
"Erzeugt aus": "VF/GF-Sepliste",
|
||||
"Zuordnung": eintrag["strecke_id"],
|
||||
}
|
||||
if strecke:
|
||||
merkmale[_strecke_typ_feld(strecke.get("teileart", ""))] = eintrag["strecke_id"]
|
||||
items.append({
|
||||
"nr": elem_nr,
|
||||
"teileart": "ILS 2.0 Separator",
|
||||
"teileid": teileid,
|
||||
"bezeichnung": f"Separator :{zaehler}",
|
||||
"planquadrat": planquadrat,
|
||||
"position": f'{eintrag["x"]:.2f}, {eintrag["y"]:.2f}, 0.00',
|
||||
"boundingbox": "",
|
||||
"insertpoint": "",
|
||||
"k1": "", "k2": "", "k3": "", "k4": "",
|
||||
"nachbarn": ", ".join(nachbarn),
|
||||
"fehler": "",
|
||||
"merkmale": merkmale,
|
||||
})
|
||||
return elem_nr
|
||||
kandidaten = sep_items_by_zuordnung.get(eintrag["strecke_id"], [])
|
||||
if not kandidaten:
|
||||
if dbg:
|
||||
dbg(f" sepliste-SEP {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)
|
||||
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
|
||||
if dbg:
|
||||
dbg(f" sepliste-SEP {key} -> Separator-Item TeileId "
|
||||
f"{best.get('teileid', '')!r}")
|
||||
return sep_kette_by_key
|
||||
|
||||
|
||||
def resolve_umlauf_und_sepliste_nachbarn(items, kreisel_umlauf, sep_kette,
|
||||
sep_kette_by_key, weiche_by_key):
|
||||
"""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).
|
||||
|
||||
Muss NACH allen drei Aufrufen laufen (ihre 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).
|
||||
weiche_by_key = dict switches-"key" -> erzeugtes Item (von
|
||||
build_kreisel_weiche_items).
|
||||
"""
|
||||
# Gemeinsamer Lookup fuer BEIDE Schluesselarten, die in kreisel_umlauf und
|
||||
# sep_kette als punkt_key/vorgaenger/nachfolger auftreten koennen: freie
|
||||
# Separatoren (TeileId), Kreisel-Kreisel-Weichen (weiche_by_key) UND
|
||||
# sepliste-Zeilen (sep_kette_by_key, Schluessel "<strecke_id>#<lfdnr>").
|
||||
# Ein Umlauf-Punkt kann naemlich ein AS/ES-Ende sein (sep_kette-Schluessel)
|
||||
# und ein sep_kette-Vorgaenger/Nachfolger kann das freie Kreiselende gar
|
||||
# nicht sein (das loest ausschliesslich compute_kreisel_umlauf auf) - erst
|
||||
# der gemeinsame Lookup deckt alle Kombinationen ab.
|
||||
separator_by_id = {it.get("teileid", ""): it for it in items
|
||||
if it.get("teileart", "") == "ILS 2.0 Separator"}
|
||||
alle_schluessel = {**sep_kette_by_key, **weiche_by_key, **separator_by_id}
|
||||
|
||||
# Freie Separatoren, Weichen UND die AS/ES-Enden andockender Ketten
|
||||
# bekommen ihre "Nachbarn"-Spalte aus dem Kreisel-Umlauf.
|
||||
if kreisel_umlauf:
|
||||
for punkte in kreisel_umlauf.values():
|
||||
for punkt_key, vorgaenger, nachfolger in punkte:
|
||||
item = alle_schluessel.get(punkt_key)
|
||||
if item is None:
|
||||
continue
|
||||
nachbarn = []
|
||||
for nachbar_key in (vorgaenger, nachfolger):
|
||||
if not nachbar_key:
|
||||
continue
|
||||
nachbar_item = alle_schluessel.get(nachbar_key)
|
||||
if nachbar_item is not None:
|
||||
nachbarn.append(nachbar_item.get("teileid", ""))
|
||||
if nachbarn:
|
||||
item["nachbarn"] = ", ".join(nachbarn)
|
||||
|
||||
# Ketteninterne sepliste-Nachbarn (SEP <-> SEP, SEP <-> AS/ES) - das
|
||||
# jeweilige Kettenende (vorgaenger/nachfolger ist None, z.B. das AS-Ende
|
||||
# ohne SEP davor) bleibt hier unveraendert, es wird stattdessen oben vom
|
||||
# Kreisel-Umlauf gesetzt (compute_sep_kette dokumentiert diese Aufteilung).
|
||||
for key, eintrag in sep_kette.items():
|
||||
item = sep_kette_by_key.get(key)
|
||||
if item is None:
|
||||
continue
|
||||
nachbarn = []
|
||||
for nachbar_key in (eintrag.get("vorgaenger"), eintrag.get("nachfolger")):
|
||||
if not nachbar_key:
|
||||
continue
|
||||
nachbar_item = alle_schluessel.get(nachbar_key)
|
||||
if nachbar_item is not None:
|
||||
nachbarn.append(nachbar_item.get("teileid", ""))
|
||||
if nachbarn:
|
||||
item["nachbarn"] = ", ".join(nachbarn)
|
||||
|
||||
|
||||
def process_blocks(blocks, lookup, dbg=None):
|
||||
@@ -792,6 +928,13 @@ def process_blocks(blocks, lookup, dbg=None):
|
||||
# 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-
|
||||
# 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
|
||||
# uebernommen).
|
||||
"sepliste": block.get("sepliste"),
|
||||
**bbox_columns(block),
|
||||
})
|
||||
continue
|
||||
@@ -811,6 +954,8 @@ def process_blocks(blocks, lookup, dbg=None):
|
||||
# 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),
|
||||
})
|
||||
continue
|
||||
@@ -883,6 +1028,14 @@ def process_blocks(blocks, lookup, dbg=None):
|
||||
if matches_any(bname, BLOCKPATTERNS.get("pattern_separator", [])):
|
||||
elem_nr += 1
|
||||
separator_count += 1
|
||||
cols = bbox_columns(block)
|
||||
# Fehlende Bounding-Box (vla-getboundingbox scheiterte, z.B.
|
||||
# eingefrorener Layer) durch die feste Separator-Symbol-Box aus
|
||||
# cfg [Boundingbox] separator_box_* ersetzen - sonst nimmt der
|
||||
# Separator an der Nachbarschafts-/Umlauf-/Zuordnungserkennung
|
||||
# nicht teil (die brauchen "_bbox"). Position/Drehung des realen
|
||||
# INSERTs sind ja bekannt.
|
||||
cols = _fallback_separator_bbox(cols, block)
|
||||
items.append({
|
||||
"nr": elem_nr,
|
||||
"teileart": "ILS 2.0 Separator",
|
||||
@@ -894,7 +1047,12 @@ def process_blocks(blocks, lookup, dbg=None):
|
||||
# Zuordnung, die compute_sensor_zuordnung unten nicht
|
||||
# ueberschreiben darf - siehe build_sensor_merkmale.
|
||||
"_zuordnung_fix": bool(block.get("zuordnung_fix")),
|
||||
**bbox_columns(block),
|
||||
# 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).
|
||||
"_x": block.get("x", 0.0), "_y": block.get("y", 0.0),
|
||||
**cols,
|
||||
})
|
||||
continue
|
||||
|
||||
@@ -1020,37 +1178,46 @@ def process_blocks(blocks, lookup, dbg=None):
|
||||
# Strecke eine sepliste traegt (reiner Altbestand ohne neue XDATA).
|
||||
sep_kette = compute_sep_kette(items, dbg=dbg)
|
||||
|
||||
# Kreisel-Kreisel-Weichen VOR dem Umlauf berechnen (reine Geometrie,
|
||||
# braucht keine TeileIds) - dieselbe Liste geht sowohl in
|
||||
# compute_kreisel_umlauf (damit die Weiche als Verzweigungspunkt in
|
||||
# BEIDEN beruehrenden Kreisel-Umlaeufen auftaucht) als auch in
|
||||
# build_kreisel_weiche_items (das die tatsaechlichen Zeilen erzeugt).
|
||||
touch_switches = compute_kreisel_touch_switches(
|
||||
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) UND die AS/ES-Enden andockender
|
||||
# Ketten (aus sep_kette), sortiert nach Winkel um die Kreiselachse (siehe
|
||||
# compute_kreisel_umlauf - DREHRICHTUNG-Attribut bestimmt den Umlaufsinn).
|
||||
# Freie Separator-Zeilen bekommen ihre "Nachbarn"-Spalte hier aus dem
|
||||
# Umlauf statt leer zu bleiben; die AS/ES-Nachbarn selbst setzt weiterhin
|
||||
# build_strecke_schleus_items (ketteninterner Vorgaenger/Nachfolger).
|
||||
# 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).
|
||||
kreisel_umlauf = compute_kreisel_umlauf(
|
||||
items, sep_kette, NEIGHBOR_TOLERANCE_MM,
|
||||
durchmesser_mm=KREISEL_DURCHMESSER_MM, dbg=dbg)
|
||||
if kreisel_umlauf:
|
||||
separator_by_id = {it.get("teileid", ""): it for it in items
|
||||
if it.get("teileart", "") == "ILS 2.0 Separator"}
|
||||
for punkte in kreisel_umlauf.values():
|
||||
for punkt_key, vorgaenger, nachfolger in punkte:
|
||||
item = separator_by_id.get(punkt_key)
|
||||
if item is None:
|
||||
continue
|
||||
nachbarn = [n for n in (vorgaenger, nachfolger) if n]
|
||||
item["nachbarn"] = ", ".join(nachbarn)
|
||||
durchmesser_mm=KREISEL_DURCHMESSER_MM, touch_switches=touch_switches, dbg=dbg)
|
||||
|
||||
# Nach der Kollisions-/Nachbarschaftserkennung: an jedem Kreisel-
|
||||
# Beruehrpunkt eine ILS-Weiche anhaengen (eigene Zeilen am Listenende -
|
||||
# sie nehmen an der Nachbarschaftserkennung selbst nicht teil, ihre
|
||||
# Nachbarn sind fest die beiden beruehrenden Kreisel). Danach an jedem
|
||||
# Strecken-Ende mit anschliessendem Kreisel ein Aus-/Einschleuselement,
|
||||
# gefolgt von je einer Zeile pro in einer Kette eingebettetem Separator -
|
||||
# die TeileId-Zaehlung laeuft ueber alle drei hinweg weiter.
|
||||
elem_nr = build_kreisel_weiche_items(items, planquadrat_cfg, elem_nr, dbg=dbg)
|
||||
elem_nr = build_strecke_schleus_items(items, planquadrat_cfg, elem_nr, sep_kette=sep_kette, dbg=dbg)
|
||||
build_separator_kette_items(items, planquadrat_cfg, elem_nr, sep_kette, 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.
|
||||
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)
|
||||
|
||||
resolve_umlauf_und_sepliste_nachbarn(
|
||||
items, kreisel_umlauf, sep_kette, sep_kette_by_key, weiche_by_key)
|
||||
|
||||
# Nach dem Sammelheimen aller Zeilen: sortiert nach TeileId ausgeben, damit
|
||||
# Elementnummer (erste Spalte) und TeileId nach Moeglichkeit uebereinstimmen
|
||||
|
||||
+107
-14
@@ -121,6 +121,56 @@ def load_element_box_mm(cfg_path=None):
|
||||
parser.getfloat("Boundingbox", "element_box_hoehe_mm", fallback=WEICHE_BOX_HOEHE_MM),
|
||||
)
|
||||
|
||||
|
||||
# Fallback-Masse (mm) der festen Separator-Symbol-Bounding-Box, falls
|
||||
# cfg/export.cfg [Boundingbox] separator_box_* nicht gesetzt/lesbar ist (siehe
|
||||
# load_separator_box_mm). LAENGE laengs des Symbols (bei Drehung 0 entlang X),
|
||||
# BREITE quer, HOEHE = Z. Gemessenes Separator_SP-Symbol (~210 x 150 x 14 mm).
|
||||
SEPARATOR_BOX_LAENGE_MM = 210.0
|
||||
SEPARATOR_BOX_BREITE_MM = 150.0
|
||||
SEPARATOR_BOX_HOEHE_MM = 14.0
|
||||
|
||||
|
||||
def load_separator_box_mm(cfg_path=None):
|
||||
"""Feste Bounding-Box-Masse (Laenge, Breite, Hoehe in mm) eines Separator-
|
||||
Symbols aus cfg/export.cfg [Boundingbox] separator_box_laenge_mm /
|
||||
_breite_mm / _hoehe_mm. Gebraucht fuer die in einer VF_n/GF_n-Kette
|
||||
eingebetteten Separatoren (sepliste-XDATA), die kein reales INSERT mehr
|
||||
sind und darum von vla-getboundingbox keine Box bekommen. Fallback auf
|
||||
SEPARATOR_BOX_*_MM, falls die Sektion/Schluessel fehlen.
|
||||
Rueckgabe: (laenge, breite, hoehe)."""
|
||||
if cfg_path is None:
|
||||
cfg_path = cfg_path_from_env()
|
||||
parser = load_export_cfg(cfg_path)
|
||||
return (
|
||||
parser.getfloat("Boundingbox", "separator_box_laenge_mm", fallback=SEPARATOR_BOX_LAENGE_MM),
|
||||
parser.getfloat("Boundingbox", "separator_box_breite_mm", fallback=SEPARATOR_BOX_BREITE_MM),
|
||||
parser.getfloat("Boundingbox", "separator_box_hoehe_mm", fallback=SEPARATOR_BOX_HOEHE_MM),
|
||||
)
|
||||
|
||||
|
||||
def separator_bbox(x, y, rotation_grad, separator_box_mm=None, z=0.0):
|
||||
"""Achsparallele Welt-Bounding-Box (_bbox-dict) eines Separator-Symbols an
|
||||
Weltposition (x, y) mit Drehung rotation_grad. Die feste Symbol-Box
|
||||
(separator_box_mm = (laenge, breite, hoehe)) wird um rotation_grad gedreht
|
||||
und ihre achsparallele Welt-Huelle zurueckgegeben - fuer eingebettete
|
||||
sepliste-Separatoren, die keine echte Zeichnungsgeometrie haben.
|
||||
|
||||
Rueckgabe: dict {"cx","cy","cz","dx","dy","dz"} wie csv:get-bbox / die
|
||||
"_bbox" der realen Bloecke (siehe export_csv.bbox_columns)."""
|
||||
if separator_box_mm is None:
|
||||
separator_box_mm = load_separator_box_mm()
|
||||
laenge, breite, hoehe = separator_box_mm
|
||||
# Laenge liegt bei Drehung 0 entlang X, Breite entlang Y (anders als
|
||||
# _oriented_box_aabb, das die Laengsseite QUER zur Achse legt - hier ist
|
||||
# es die reine Symbol-AABB, konsistent mit der gemessenen Referenz).
|
||||
rot = math.radians(rotation_grad or 0.0)
|
||||
c, s = abs(math.cos(rot)), abs(math.sin(rot))
|
||||
dx = laenge * c + breite * s
|
||||
dy = laenge * s + breite * c
|
||||
return {"cx": x, "cy": y, "cz": z, "dx": dx, "dy": dy, "dz": hoehe}
|
||||
|
||||
|
||||
# Mindestanzahl Partner (Nachbarn) je TeileArt fuer die Fehlerspalte "Fehler"
|
||||
# (siehe compute_neighbor_errors): Kreisel/Eckrad und Omniflo-Elemente
|
||||
# brauchen mindestens einen Partner, Gefaellestrecke/Foerderer/Strecke-Modul
|
||||
@@ -930,7 +980,13 @@ def compute_kreisel_touch_switches(items, tolerance_mm, durchmesser_mm=800.0,
|
||||
|
||||
Rueckgabe: Liste von dicts (in items-Reihenfolge der Paare), je erzeugte
|
||||
Weiche:
|
||||
{"x","y","z": Beruehrpunkt (mm),
|
||||
{"key": "WEICHE#<teileid_a>#<teileid_b>" - stabiler Schluessel, ueber den
|
||||
compute_kreisel_umlauf die Weiche in BEIDE beruehrenden Kreisel-
|
||||
Umlaeufe einhaengt (die Weiche gehoert als Verzweigungspunkt zu
|
||||
beiden, anders als ein Separator oder ein AS/ES-Ende) UND
|
||||
build_kreisel_weiche_items die zugehoerige TeileId nach dem Umlauf-
|
||||
Aufbau wiederfindet,
|
||||
"x","y","z": Beruehrpunkt (mm),
|
||||
"dx","dy","dz": Welt-AABB der ausgerichteten Box (mm),
|
||||
"nachbarn": [teileid_a, teileid_b],
|
||||
"achse_von": teileid des orientierungsgebenden Kreisels,
|
||||
@@ -987,6 +1043,7 @@ def compute_kreisel_touch_switches(items, tolerance_mm, durchmesser_mm=800.0,
|
||||
|
||||
dx, dy, dz = _oriented_box_aabb(achse_rot, box_l, box_b, box_h)
|
||||
switches.append({
|
||||
"key": f"WEICHE#{id_a}#{id_b}",
|
||||
"x": tx, "y": ty, "z": tz,
|
||||
"dx": dx, "dy": dy, "dz": dz,
|
||||
"nachbarn": [id_a, id_b],
|
||||
@@ -1285,7 +1342,7 @@ def compute_sep_kette(items, dbg=None):
|
||||
|
||||
Jeder Sepliste-Eintrag bekommt eine (innerhalb dieser Kette eindeutige)
|
||||
Schluessel-ID "<strecke_teileid>#<lfdnr>" - das ist die ID, mit der
|
||||
build_strecke_schleus_items/build_separator_kette_items die spaeteren
|
||||
build_strecke_schleus_items/map_separator_kette_items die spaeteren
|
||||
globalen TeileIds verknuepft (siehe export_csv.py). Vorgaenger/Nachfolger
|
||||
sind schlicht der vorherige/naechste Eintrag in der Sepliste (die bereits
|
||||
in Baureihenfolge vorliegt) - kein Rateverfahren noetig.
|
||||
@@ -1320,13 +1377,23 @@ def compute_sep_kette(items, dbg=None):
|
||||
return result
|
||||
|
||||
|
||||
def compute_kreisel_umlauf(items, sep_kette, tolerance_mm, durchmesser_mm=800.0, dbg=None):
|
||||
def compute_kreisel_umlauf(items, sep_kette, tolerance_mm, durchmesser_mm=800.0,
|
||||
touch_switches=None, dbg=None):
|
||||
"""Bringt fuer jeden echten Kreisel (KREISEL_SPLIT_TEILEART) alle dort
|
||||
liegenden Punkte - frei platzierte Separatoren (per compute_sensor_
|
||||
zuordnung bereits einer Kreiselhaelfte zugeordnet) UND die AS/ES-Enden
|
||||
zuordnung bereits einer Kreiselhaelfte zugeordnet), die AS/ES-Enden
|
||||
aller andockenden VF_n/GF_n-Ketten (aus sep_kette, per BBox-Naehe wie
|
||||
compute_strecke_kreisel_schleus zugeordnet) - in eine geschlossene
|
||||
Umlauf-Reihenfolge (zirkulaer, siehe DREHRICHTUNG-Attribut).
|
||||
compute_strecke_kreisel_schleus zugeordnet) UND die Kreisel-Kreisel-
|
||||
Weichen (touch_switches, siehe compute_kreisel_touch_switches) - in eine
|
||||
geschlossene Umlauf-Reihenfolge (zirkulaer, siehe DREHRICHTUNG-Attribut).
|
||||
|
||||
Eine Kreisel-Kreisel-Weiche liegt (anders als ein Separator oder ein
|
||||
AS/ES-Ende) auf GENAU EINEM Punkt, gehoert aber zu ZWEI Kreiseln - man
|
||||
kann von Kreisel A zu Kreisel B UND umgekehrt wechseln. Sie wird darum
|
||||
in punkte_je_kreisel BEIDER beruehrender Kreisel eingetragen (gleiche
|
||||
Weltposition, gleicher "key" - siehe touch_switches), damit sie im
|
||||
Umlauf jedes der beiden Kreisel zwischen den jeweils angrenzenden
|
||||
Separatoren/AS-ES-Enden auftaucht statt spurlos zu fehlen.
|
||||
|
||||
Winkel wird um den Kapsel-Mittelpunkt (Mitte AN8-/SP8-Achse, siehe
|
||||
_kreisel_capsule) gemessen, 0 Grad = Kapsel-Achsrichtung. UZS
|
||||
@@ -1336,12 +1403,15 @@ def compute_kreisel_umlauf(items, sep_kette, tolerance_mm, durchmesser_mm=800.0,
|
||||
|
||||
items = dieselbe Liste wie bei compute_neighbor_ids/compute_sensor_
|
||||
zuordnung. sep_kette = Rueckgabe von compute_sep_kette (fuer die AS/ES-
|
||||
Punkte: x/y + strecke_id).
|
||||
Punkte: x/y + strecke_id). touch_switches = Rueckgabe von
|
||||
compute_kreisel_touch_switches (None/leer = wie bisher ohne Weichen im
|
||||
Umlauf).
|
||||
|
||||
Rueckgabe: dict {kreisel_teileid: [(punkt_key, vorgaenger_key,
|
||||
nachfolger_key), ...]} in Umlaufreihenfolge - punkt_key ist entweder eine
|
||||
Separator-TeileId (freie Separatoren) oder ein sep_kette-Schluessel
|
||||
(AS/ES-Enden). Kreisel ohne zugeordnete Punkte liefern eine leere Liste.
|
||||
nachfolger_key), ...]} in Umlaufreihenfolge - punkt_key ist eine
|
||||
Separator-TeileId (freie Separatoren), ein sep_kette-Schluessel (AS/ES-
|
||||
Enden) oder ein touch_switches-"key" (Kreisel-Kreisel-Weiche). Kreisel
|
||||
ohne zugeordnete Punkte liefern eine leere Liste.
|
||||
"""
|
||||
half_tol = tolerance_mm / 2.0
|
||||
kreisel = {}
|
||||
@@ -1355,13 +1425,27 @@ def compute_kreisel_umlauf(items, sep_kette, tolerance_mm, durchmesser_mm=800.0,
|
||||
# bestimmbar, wie compute_sensor_zuordnung es fuer die "Zuordnung"-Spalte
|
||||
# tut - hier direkt gegen die volle Kreisel-BBox statt der Haelften, weil
|
||||
# nur "gehoert zu diesem Kreisel" gebraucht wird, nicht links/rechts).
|
||||
#
|
||||
# Achsparallele Welt-AABB der Kapsel (a/b + Radius je Achse) statt einer
|
||||
# Box mit fest "dx=durchmesser+achs_len, dy=durchmesser" - letzteres
|
||||
# nimmt STILLSCHWEIGEND an, die Kreiselachse liegt entlang Welt-X. Bei
|
||||
# einem um 90 Grad gedrehten Kreisel (Achse entlang Welt-Y, z.B. ein
|
||||
# 20m-Kreisel laengs der Halle) drehte sich damit auch die Box um 90 Grad
|
||||
# gegen die tatsaechliche Geometrie - schmal in Y (dort, wo der Kreisel in
|
||||
# Wahrheit 20m lang ist) und ueberbreit in X (dort, wo er nur
|
||||
# durchmesser_mm misst). Ergebnis: entfernte Punkte wurden faelschlich
|
||||
# als "auf dem Kreisel" erkannt, waehrend tatsaechlich anliegende
|
||||
# Separatoren durchfielen (realer Bug-Fall: Mubea, Kreisel 0002).
|
||||
kreisel_bounds = {}
|
||||
for tid, (cap, _dr) in kreisel.items():
|
||||
acx = (cap["a"][0] + cap["b"][0]) / 2.0
|
||||
acy = (cap["a"][1] + cap["b"][1]) / 2.0
|
||||
achs_len = math.hypot(cap["b"][0] - cap["a"][0], cap["b"][1] - cap["a"][1])
|
||||
r = cap["r"]
|
||||
ax, ay = cap["a"]
|
||||
bx, by = cap["b"]
|
||||
minx, maxx = min(ax, bx) - r, max(ax, bx) + r
|
||||
miny, maxy = min(ay, by) - r, max(ay, by) + r
|
||||
kreisel_bounds[tid] = _bounds(
|
||||
{"cx": acx, "cy": acy, "dx": durchmesser_mm + achs_len, "dy": durchmesser_mm},
|
||||
{"cx": (minx + maxx) / 2.0, "cy": (miny + maxy) / 2.0,
|
||||
"dx": maxx - minx, "dy": maxy - miny},
|
||||
half_tol)
|
||||
|
||||
punkte_je_kreisel = {tid: [] for tid in kreisel}
|
||||
@@ -1395,6 +1479,15 @@ def compute_kreisel_umlauf(items, sep_kette, tolerance_mm, durchmesser_mm=800.0,
|
||||
if best_tid is not None:
|
||||
punkte_je_kreisel[best_tid].append((key, eintrag["x"], eintrag["y"]))
|
||||
|
||||
# Kreisel-Kreisel-Weichen: die beiden beruehrenden Kreisel-IDs stehen
|
||||
# bereits in sw["nachbarn"] (siehe compute_kreisel_touch_switches) - kein
|
||||
# Naeherungsverfahren wie bei Separator/AS/ES noetig, die Weiche wird in
|
||||
# BEIDE Umlaeufe eingetragen.
|
||||
for sw in (touch_switches or []):
|
||||
for tid in sw.get("nachbarn", []):
|
||||
if tid in punkte_je_kreisel:
|
||||
punkte_je_kreisel[tid].append((sw["key"], sw["x"], sw["y"]))
|
||||
|
||||
result = {}
|
||||
for tid, punkte in punkte_je_kreisel.items():
|
||||
cap, drehrichtung = kreisel[tid]
|
||||
|
||||
Reference in New Issue
Block a user