[FIX] Export: ZUORDNUNG-Attribut vor BBox-Suche bei Separator/Scanner
Separatoren an einer Kreisel-Kreisel-Weiche ragen mit Toleranz in die Box beider Kreisel; compute_kreisel_umlauf/compute_sensor_zuordnung nahmen den ersten Treffer (test_system: Separator 0018 an Kreisel 0006 statt 0005, 0015 an 0003 statt 0002). Das ZUORDNUNG-Attribut aus count_sep_scan hat jetzt Vorrang, sofern es einen vorhandenen Carrier nennt; die Haelfte L/R wird weiter per BBox bestimmt, ohne Attribut bleibt die BBox-Suche. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -255,6 +255,14 @@ def build_sensor_merkmale(block):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _sensor_zuordnung_attr(block):
|
||||||
|
"""Carrier-ID (Kreisel/GF/VF-TeileId) eines Separator-/Scanner-Blocks aus
|
||||||
|
der Zeichnung: zuordnung_fix (verpackte Kopie, siehe build_sensor_
|
||||||
|
merkmale) vor dem ZUORDNUNG-Attribut. "" wenn nichts gesetzt ist."""
|
||||||
|
return (block.get("zuordnung_fix")
|
||||||
|
or block.get("attribs", {}).get("ZUORDNUNG", "") or "").strip()
|
||||||
|
|
||||||
|
|
||||||
def build_element_merkmale(block):
|
def build_element_merkmale(block):
|
||||||
"""Merkmale-Dict fuer eine ausgepackte VF_n/GF_n-Sub-Komponente (Bogen/
|
"""Merkmale-Dict fuer eine ausgepackte VF_n/GF_n-Sub-Komponente (Bogen/
|
||||||
Streckenelement/Umlenkstation/Motorstation) - siehe csv:element-proxies-
|
Streckenelement/Umlenkstation/Motorstation) - siehe csv:element-proxies-
|
||||||
@@ -1341,6 +1349,11 @@ def process_blocks(blocks, lookup, dbg=None, lisp_weichen=None):
|
|||||||
# Zuordnung, die compute_sensor_zuordnung unten nicht
|
# Zuordnung, die compute_sensor_zuordnung unten nicht
|
||||||
# ueberschreiben darf - siehe build_sensor_merkmale.
|
# ueberschreiben darf - siehe build_sensor_merkmale.
|
||||||
"_zuordnung_fix": bool(block.get("zuordnung_fix")),
|
"_zuordnung_fix": bool(block.get("zuordnung_fix")),
|
||||||
|
# Privates Feld: Carrier-ID aus der Zeichnung (zuordnung_fix
|
||||||
|
# bzw. ZUORDNUNG-Attribut, von cs-zuordnung-lauf in
|
||||||
|
# Lisp/count_sep_scan.lsp gepflegt) - hat in compute_sensor_
|
||||||
|
# zuordnung/compute_kreisel_umlauf Vorrang vor der BBox-Suche.
|
||||||
|
"_zuordnung_attr": _sensor_zuordnung_attr(block),
|
||||||
# Roher INSERT-Punkt (nicht der bbox-Mittelpunkt) - dient
|
# Roher INSERT-Punkt (nicht der bbox-Mittelpunkt) - dient
|
||||||
# map_sepliste_kette_items als Koordinaten-FALLBACK, falls der
|
# map_sepliste_kette_items als Koordinaten-FALLBACK, falls der
|
||||||
# sepliste-Eintrag (noch) kein "id"-Feld traegt (Altbestand;
|
# sepliste-Eintrag (noch) kein "id"-Feld traegt (Altbestand;
|
||||||
@@ -1368,6 +1381,7 @@ def process_blocks(blocks, lookup, dbg=None, lisp_weichen=None):
|
|||||||
# sammelt - csv:block-to-json (export.lsp) haengt den Text als
|
# sammelt - csv:block-to-json (export.lsp) haengt den Text als
|
||||||
# "warnung"-Feld an den betroffenen Scanner-Block im JSON an.
|
# "warnung"-Feld an den betroffenen Scanner-Block im JSON an.
|
||||||
"warnung": block.get("warnung", ""),
|
"warnung": block.get("warnung", ""),
|
||||||
|
"_zuordnung_attr": _sensor_zuordnung_attr(block),
|
||||||
**bbox_columns(block),
|
**bbox_columns(block),
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -784,6 +784,9 @@ def compute_sensor_zuordnung(items, tolerance_mm, kreisel_durchmesser_mm=800.0,
|
|||||||
gf_entries = []
|
gf_entries = []
|
||||||
vf_entries = []
|
vf_entries = []
|
||||||
kreisel_entries = []
|
kreisel_entries = []
|
||||||
|
# TeileIds aller Carrier (GF/VF/Kreisel) - fuer den Vorrang des
|
||||||
|
# ZUORDNUNG-Attributs ("_zuordnung_attr") vor der BBox-Suche.
|
||||||
|
carrier_ids = set()
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
bbox = item.get("_bbox")
|
bbox = item.get("_bbox")
|
||||||
@@ -793,9 +796,12 @@ def compute_sensor_zuordnung(items, tolerance_mm, kreisel_durchmesser_mm=800.0,
|
|||||||
teileid = item.get("teileid", "")
|
teileid = item.get("teileid", "")
|
||||||
if teileart == "ILS 2.0 Gefaellestrecke":
|
if teileart == "ILS 2.0 Gefaellestrecke":
|
||||||
gf_entries.append((teileid, _bounds(bbox, half_tol)))
|
gf_entries.append((teileid, _bounds(bbox, half_tol)))
|
||||||
|
carrier_ids.add(teileid)
|
||||||
elif teileart in ("ILS 2.0 Strecke", "ILS 2.0 Strecke - Modul"):
|
elif teileart in ("ILS 2.0 Strecke", "ILS 2.0 Strecke - Modul"):
|
||||||
vf_entries.append((teileid, _bounds(bbox, half_tol)))
|
vf_entries.append((teileid, _bounds(bbox, half_tol)))
|
||||||
|
carrier_ids.add(teileid)
|
||||||
elif teileart == KREISEL_SPLIT_TEILEART:
|
elif teileart == KREISEL_SPLIT_TEILEART:
|
||||||
|
carrier_ids.add(teileid)
|
||||||
bbox_links, bbox_rechts = kreisel_half_bboxes(item, kreisel_durchmesser_mm)
|
bbox_links, bbox_rechts = kreisel_half_bboxes(item, kreisel_durchmesser_mm)
|
||||||
kreisel_entries.append((f"{teileid}-L", _bounds(bbox_links, half_tol)))
|
kreisel_entries.append((f"{teileid}-L", _bounds(bbox_links, half_tol)))
|
||||||
kreisel_entries.append((f"{teileid}-R", _bounds(bbox_rechts, half_tol)))
|
kreisel_entries.append((f"{teileid}-R", _bounds(bbox_rechts, half_tol)))
|
||||||
@@ -824,6 +830,18 @@ def compute_sensor_zuordnung(items, tolerance_mm, kreisel_durchmesser_mm=800.0,
|
|||||||
result.append("")
|
result.append("")
|
||||||
continue
|
continue
|
||||||
bounds = _bounds(bbox, half_tol)
|
bounds = _bounds(bbox, half_tol)
|
||||||
|
# Vorrang: ZUORDNUNG aus der Zeichnung (cs-zuordnung-lauf), sofern sie
|
||||||
|
# einen vorhandenen Carrier nennt. Die BBox-Suche nimmt sonst den
|
||||||
|
# ERSTEN Treffer - an einer Kreisel-Kreisel-Weiche ragt ein Separator
|
||||||
|
# mit Toleranz in beide Kreisel und landete beim falschen (realer
|
||||||
|
# Fall test_system, Separator 0018: Attribut 0005, BBox -> 0006).
|
||||||
|
# Bei einem Kreisel wird nur noch die Haelfte (L/R) per BBox bestimmt.
|
||||||
|
attr = (item.get("_zuordnung_attr") or "").strip()
|
||||||
|
if attr in carrier_ids:
|
||||||
|
haelften = [e for e in kreisel_entries
|
||||||
|
if e[0] in (f"{attr}-L", f"{attr}-R")]
|
||||||
|
treffer = (_find(bounds, haelften) if haelften else None) or attr
|
||||||
|
else:
|
||||||
treffer = (_find(bounds, gf_entries)
|
treffer = (_find(bounds, gf_entries)
|
||||||
or _find(bounds, vf_entries)
|
or _find(bounds, vf_entries)
|
||||||
or _find(bounds, kreisel_entries)
|
or _find(bounds, kreisel_entries)
|
||||||
@@ -1383,7 +1401,18 @@ def compute_kreisel_umlauf(items, tolerance_mm, durchmesser_mm=800.0,
|
|||||||
continue
|
continue
|
||||||
bounds = _bounds(bbox, half_tol)
|
bounds = _bounds(bbox, half_tol)
|
||||||
matched_tid = None
|
matched_tid = None
|
||||||
|
# Vorrang: ZUORDNUNG-Attribut des Separators/Scanners, wenn es einen
|
||||||
|
# Kreisel nennt (siehe compute_sensor_zuordnung) - die BBox-Suche
|
||||||
|
# unten nimmt den ERSTEN ueberlappenden Kreisel und verwechselt an
|
||||||
|
# einer Kreisel-Kreisel-Weiche die beiden Kreisel.
|
||||||
|
attr = (item.get("_zuordnung_attr") or "").strip()
|
||||||
|
if attr in kreisel_bounds:
|
||||||
|
punkte_je_kreisel[attr].append(
|
||||||
|
(teileid, bbox.get("cx", 0.0), bbox.get("cy", 0.0)))
|
||||||
|
matched_tid = attr
|
||||||
for tid, kb in kreisel_bounds.items():
|
for tid, kb in kreisel_bounds.items():
|
||||||
|
if matched_tid is not None:
|
||||||
|
break
|
||||||
if _overlaps(bounds, kb):
|
if _overlaps(bounds, kb):
|
||||||
punkte_je_kreisel[tid].append(
|
punkte_je_kreisel[tid].append(
|
||||||
(teileid, bbox.get("cx", 0.0), bbox.get("cy", 0.0)))
|
(teileid, bbox.get("cx", 0.0), bbox.get("cy", 0.0)))
|
||||||
|
|||||||
@@ -538,3 +538,51 @@ class TestAsEsUeberEinlaufpunkt:
|
|||||||
umlauf = compute_kreisel_umlauf(
|
umlauf = compute_kreisel_umlauf(
|
||||||
items, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM)
|
items, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM)
|
||||||
assert "0071" not in [p[0] for p in umlauf["0001"]]
|
assert "0071" not in [p[0] for p in umlauf["0001"]]
|
||||||
|
|
||||||
|
|
||||||
|
class TestZuordnungAttributVorBBox:
|
||||||
|
"""Realer Fall test_system, Separator 0018: er sitzt an der Kreisel-
|
||||||
|
Kreisel-Weiche zweier Kreisel und ragt mit Toleranz in die Box BEIDER.
|
||||||
|
Die BBox-Suche nahm den ersten Kreisel in der Item-Liste (den falschen);
|
||||||
|
das ZUORDNUNG-Attribut aus der Zeichnung (cs-zuordnung-lauf) nennt den
|
||||||
|
richtigen und hat Vorrang."""
|
||||||
|
|
||||||
|
def _anlage(self, attr):
|
||||||
|
# Kreisel 0006 senkrecht (Achse +Y), AN8-Mitte bei (0, 400);
|
||||||
|
# Kreisel 0005 waagerecht (Achse +X), AN8-Mitte bei (0, -400):
|
||||||
|
# AN8/AN8 beruehren sich bei (0, 0). Separator auf der Oberkante von
|
||||||
|
# Kreisel 0005, 615 mm rechts der Weiche.
|
||||||
|
k6 = _kreisel("0006", 0.0, 0.0, 90.0, abstand=2000.0)
|
||||||
|
k5 = _kreisel("0005", -400.0, -400.0, 0.0, abstand=8000.0)
|
||||||
|
sep = _separator("0018", 615.0, 0.0)
|
||||||
|
sep["_zuordnung_attr"] = attr
|
||||||
|
sep["merkmale"] = {}
|
||||||
|
return [sep, k6, k5]
|
||||||
|
|
||||||
|
def test_umlauf_folgt_attribut(self):
|
||||||
|
items = self._anlage("0005")
|
||||||
|
umlauf = compute_kreisel_umlauf(items, tolerance_mm=200.0,
|
||||||
|
durchmesser_mm=DURCHMESSER_MM)
|
||||||
|
assert "0018" in [p[0] for p in umlauf["0005"]]
|
||||||
|
assert "0018" not in [p[0] for p in umlauf["0006"]]
|
||||||
|
|
||||||
|
def test_ohne_attribut_bbox_rueckfall(self):
|
||||||
|
items = self._anlage("")
|
||||||
|
umlauf = compute_kreisel_umlauf(items, tolerance_mm=200.0,
|
||||||
|
durchmesser_mm=DURCHMESSER_MM)
|
||||||
|
alle = [p[0] for pts in umlauf.values() for p in pts]
|
||||||
|
assert alle.count("0018") == 1
|
||||||
|
|
||||||
|
def test_sensor_zuordnung_folgt_attribut(self):
|
||||||
|
from export_neighbors import compute_sensor_zuordnung
|
||||||
|
items = self._anlage("0005")
|
||||||
|
zuo = compute_sensor_zuordnung(items, 200.0,
|
||||||
|
kreisel_durchmesser_mm=DURCHMESSER_MM)
|
||||||
|
assert zuo[0].startswith("0005")
|
||||||
|
|
||||||
|
def test_unbekanntes_attribut_bbox_rueckfall(self):
|
||||||
|
from export_neighbors import compute_sensor_zuordnung
|
||||||
|
items = self._anlage("9999")
|
||||||
|
zuo = compute_sensor_zuordnung(items, 200.0,
|
||||||
|
kreisel_durchmesser_mm=DURCHMESSER_MM)
|
||||||
|
assert zuo[0] != "9999" and zuo[0] != "nicht zugeordnet"
|
||||||
|
|||||||
Reference in New Issue
Block a user