From 4f1a0e0b5bb9460fc79601d6129f3b0d320009c7 Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Thu, 24 Sep 2026 10:58:53 +0200 Subject: [PATCH] export_csv: sepliste-Koordinaten (x/y) entfernen, nur noch typ/lfdnr/id Validiert (Nutzer-Befund): die sepliste-XDATA-Koordinaten sind der rohe Gruppencode-10-Punkt des jeweiligen Bau-Primitivs und teils gespiegelt/um mehrere 100mm verschoben (z.B. Separator 0037: x=-5070.1 statt 5070.1, ausserhalb der VF_1-Boundingbox). Die eigene Export-Zeile jedes Bauteils (x/y aus der echten Proxy-Kopie) ist dagegen verlaesslich und liegt korrekt in der Boundingbox. sepliste dient nur noch der ID-Zuordnung/Reihenfolge, nicht mehr als Koordinatenquelle. Der Python-Faellback in map_sepliste_kette_items (nur fuer SEP-Eintraege ohne aufloesbares "id"-Feld, Altbestand) matchte bisher per naechstem sepliste-Punkt - jetzt erster unverbrauchter Kandidat je Zuordnung, da keine Koordinate mehr vorliegt. lib/dump_vf_kette_inhalt.py zeigt Position/Typ jetzt ueber die ID-Verknuepfung zur eigenen Bauteil-Zeile statt ueber die entfernten sepliste-Felder. Co-Authored-By: Claude Sonnet 5 --- Lisp/export.lsp | 17 +++++++++++++++-- lib/dump_vf_kette_inhalt.py | 32 ++++++++++++++++++++++++++++---- lib/export_csv.py | 31 +++++++++++++++++++------------ lib/export_neighbors.py | 13 +++++++++---- 4 files changed, 71 insertions(+), 22 deletions(-) diff --git a/Lisp/export.lsp b/Lisp/export.lsp index 1487e27..e792d61 100644 --- a/Lisp/export.lsp +++ b/Lisp/export.lsp @@ -786,6 +786,21 @@ ;; Eintrag (Key = Handle dieses Wrappers). Fehlt sie (Kopie nicht ;; erzeugbar, Block ohne ID-ATTDEF, Altbestand), bleibt das Feld weg - ;; der Python-Teil faellt dann auf seine Koordinaten-Zuordnung zurueck. + ;; + ;; KEIN x/y mehr im JSON: die sepliste-XDATA-Koordinaten sind der + ;; rohe Gruppencode-10-Punkt des jeweiligen Einfuege-Primitivs + ;; (siehe gf-insert-hz-with-ks-Kommentar, ssg_ks_insert.lsp) und + ;; koennen gespiegelt/verschoben sein (Nutzer-Befund 2026-09-25: + ;; Sepliste-x/y von SEP-Eintraegen wich um bis zu mehrere 100mm bzw. + ;; vorzeichenverkehrt von der tatsaechlichen, aus der echten + ;; Proxy-Kopie berechneten Position ab - siehe die "x"/"y"-Felder + ;; der eigenen Bauteil-Zeile, die verlaesslich sind). x/y wurden nie + ;; fuer die ID-Zuordnung gebraucht (die laeuft VORHER, LISP-seitig, + ;; ueber csv:sepliste-paare/csv:sepliste-ids-setzen mit einer + ;; eigenen, unabhaengigen XDATA-Lesung) - nur der Python-Naehe- + ;; Fallback (map_sepliste_kette_items in export_csv.py, fuer + ;; SEP-Eintraege ohne "id", Altbestand ohne ID-ATTDEF) konsumierte + ;; sie und degradiert jetzt auf 0.0/0.0 (siehe dort). (setq sepliste-ids (cdr (assoc handle *csv-sepliste-id-by-handle*))) (setq sepliste-teile "" sepliste-first t) (foreach e sepliste @@ -794,8 +809,6 @@ (strcat sepliste-teile (if sepliste-first "" ",") "{\"typ\":\"" (nth 0 e) "\"" ",\"lfdnr\":" (itoa (nth 1 e)) - ",\"x\":" (rtos (nth 2 e) 2 2) - ",\"y\":" (rtos (nth 3 e) 2 2) (if sep-id (strcat ",\"id\":\"" (csv:json-escape sep-id) "\"") "") "}")) (setq sepliste-first nil) diff --git a/lib/dump_vf_kette_inhalt.py b/lib/dump_vf_kette_inhalt.py index 3708b3c..4fe52bf 100644 --- a/lib/dump_vf_kette_inhalt.py +++ b/lib/dump_vf_kette_inhalt.py @@ -105,15 +105,37 @@ def wrapper_aus_export_raw(export_raw): return wrapper -def sepliste_zusammenfassung(wrapper_block): +def bauteile_nach_id(export_raw): + """Lookup ID-Attribut -> Bauteil-Record. Die sepliste selbst traegt seit + 2026-09-25 kein x/y mehr (die dortigen Koordinaten waren der rohe, + teils gespiegelte/verschobene Gruppencode-10-Punkt des Bau-Primitivs, + siehe export.lsp-Kommentar bei sepliste-teile) - die verlaessliche + Position steht an der eigenen Export-Zeile des Bauteils (x/y/z dort + kommen aus der echten Proxy-Kopie).""" + out = {} + for b in export_raw: + bid = b.get("attribs", {}).get("ID") + if bid: + out[bid] = b + return out + + +def sepliste_zusammenfassung(wrapper_block, bauteile): sepliste = wrapper_block.get("sepliste", []) if not sepliste: return " (keine sepliste-XDATA am Wrapper - Altbestand oder Fehler)" zeilen = [] for s in sepliste: + teil = bauteile.get(s.get("id")) + if teil: + pos = f"x={teil['x']:10.2f} y={teil['y']:10.2f}" + typ = teil.get("strecke_typ") + if typ: + pos += f" strecke_typ={typ}" + else: + pos = "(kein Bauteil mit dieser ID gefunden)" zeilen.append( - f" lfdnr={s['lfdnr']:2d} typ={s['typ']:3s} id={s.get('id','?'):6s} " - f"x={s['x']:10.2f} y={s['y']:10.2f}" + f" lfdnr={s['lfdnr']:2d} typ={s['typ']:3s} id={s.get('id','?'):6s} {pos}" ) return "\n".join(zeilen) @@ -128,9 +150,11 @@ def main(): export_raw = None wrapper_liste = [] + bauteile = {} if len(sys.argv) == 3: export_raw = lade_json(sys.argv[2]) wrapper_liste = wrapper_aus_export_raw(export_raw) + bauteile = bauteile_nach_id(export_raw) if len(wrapper_liste) != len(ketten): print( f"[WARNUNG] {len(ketten)} Ketten in der Spec, aber " @@ -150,7 +174,7 @@ def main(): print(kette_zusammenfassung(kette)) if i < len(wrapper_liste): print("--- sepliste (tatsaechlich exportierte Separator-Reihenfolge) ---") - print(sepliste_zusammenfassung(wrapper_liste[i])) + print(sepliste_zusammenfassung(wrapper_liste[i], bauteile)) if __name__ == "__main__": diff --git a/lib/export_csv.py b/lib/export_csv.py index 06701a5..b23c6d8 100644 --- a/lib/export_csv.py +++ b/lib/export_csv.py @@ -21,7 +21,6 @@ von export_raw.json bis zur fertigen CSV-Zeile) siehe doc/csv-export.md. """ import json -import math import sys import os @@ -567,19 +566,29 @@ def map_sepliste_kette_items(items, sep_kette, dbg=None): 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. + weiterhin gegen die per Zuordnung vorgefilterten Separator-Items, je + Strecke der erste noch nicht verbrauchte Kandidat (Baureihenfolge in + items). AS/ES ohne "id" bleiben unaufgeloest - fuer sie existiert seit + dem Wegfall der Kreisel<->Strecke-Kollisionserzeugung (frueher + compute_strecke_kreisel_schleus/build_strecke_schleus_items) ohnehin + kein Item mehr, das sie vertreten koennte. + + Der Fallback matchte frueher per naechstem sepliste-Koordinatenpunkt + (x/y) - die sepliste-XDATA traegt seit 2026-09-25 kein x/y mehr, weil + diese Koordinaten der rohe, teils gespiegelte/verschobene Gruppencode-10- + Punkt des jeweiligen Bau-Primitivs waren (siehe export.lsp-Kommentar bei + sepliste-teile) und keine verlaessliche Zuordnungsgrundlage. Der + ID-Abgleich oben deckt praktisch jeden aktuellen Export ab; dieser + Fallback ist reine Altbestands-Kompatibilitaet. Rueckgabe: sep_kette_by_key (neues dict). """ sep_kette_by_key = {} by_teileid = {it.get("teileid", ""): it for it in items if it.get("teileid", "")} - # Kandidatenpool nur noch fuer den Koordinaten-Fallback (SEP ohne "id"). + # Kandidatenpool nur noch fuer den Fallback (SEP ohne "id"); verbrauchte + # Kandidaten werden entfernt, damit zwei Sepliste-Eintraege nicht auf + # dasselbe Item fallen. sep_items_by_zuordnung = {} for it in items: if it.get("teileart", "") != "ILS 2.0 Separator": @@ -620,13 +629,11 @@ def map_sepliste_kette_items(items, sep_kette, dbg=None): dbg(f" {key}: kein Separator-Item mit Zuordnung " f"{eintrag['strecke_id']!r} gefunden - uebersprungen") continue - 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)) + best = kandidaten.pop(0) sep_kette_by_key[key] = best per_fallback += 1 if dbg: - dbg(f" {key} (x={sx:.0f},y={sy:.0f}) -> Naehe-Fallback auf " + dbg(f" {key} -> Fallback (erster unverbrauchter Kandidat) auf " f"Separator-Item TeileId {best.get('teileid', '')!r}") if dbg: diff --git a/lib/export_neighbors.py b/lib/export_neighbors.py index 826cc47..8f89e08 100644 --- a/lib/export_neighbors.py +++ b/lib/export_neighbors.py @@ -1153,7 +1153,8 @@ def _asel_collision_boxes(item, half_tol, box_dims): # "sepliste" (item["sepliste"], von csv:block-to-json aus SSG_VF_SEP/ # SSG_GF_SEP-XDATA gelesen) ist die beim Bau in Baureihenfolge gesammelte # Liste der Separator-/AS-/ES-Sub-Bloecke EINER VF_n/GF_n-Kette: -# [{"typ": "AS"|"SEP"|"ES", "lfdnr": int, "x": float, "y": float}, ...]. +# [{"typ": "AS"|"SEP"|"ES", "lfdnr": int, "id": str}, ...] (kein x/y - siehe +# compute_sep_kette-Docstring). # Ersetzt das bisherige Raten der Ketten-internen Reihenfolge (die Linie # selbst wird nicht exportiert) durch die tatsaechliche Bau-Reihenfolge. @@ -1168,7 +1169,7 @@ def compute_sep_kette(items, dbg=None): naechste Eintrag in der Sepliste (die bereits in Baureihenfolge vorliegt) - kein Rateverfahren noetig. - Rueckgabe: dict {key: {"typ", "x", "y", "id", "strecke_id", "vorgaenger", + Rueckgabe: dict {key: {"typ", "id", "strecke_id", "vorgaenger", "nachfolger"}} - vorgaenger/nachfolger sind wieder solche keys, oder None am jeweiligen Kettenende (Verkettung mit dem Kreisel-Umlauf uebernimmt compute_kreisel_umlauf). "id" ist die TeileId des Bauteils, das diesen @@ -1176,6 +1177,12 @@ def compute_sep_kette(items, dbg=None): ZZ_ASES_MARKER, SEP ueber die beim Export ausgepackte temporaere Separator_SP-Kopie - siehe csv:sepliste-ids-setzen, Lisp/export.lsp) - leerer String, wenn (noch) keine ID zugewiesen wurde (Altbestand). + + Kein x/y mehr: die sepliste-XDATA traegt seit 2026-09-25 keine + Koordinaten mehr (der rohe Gruppencode-10-Punkt des jeweiligen + Bau-Primitivs war teils gespiegelt/verschoben und keine verlaessliche + Positionsquelle - siehe export.lsp-Kommentar bei sepliste-teile). Die + verlaessliche Position steht an der eigenen Export-Zeile des Bauteils. """ result = {} for item in items: @@ -1190,8 +1197,6 @@ def compute_sep_kette(items, dbg=None): key = keys[i] result[key] = { "typ": entry.get("typ", ""), - "x": entry.get("x", 0.0), - "y": entry.get("y", 0.0), "id": str(entry.get("id", "")).strip(), "strecke_id": sid, "vorgaenger": keys[i - 1] if i > 0 else None,