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 <noreply@anthropic.com>
This commit is contained in:
Michael Stangl
2026-09-24 10:58:53 +02:00
co-authored by Claude Sonnet 5
parent 42309c77d2
commit 4f1a0e0b5b
4 changed files with 71 additions and 22 deletions
+15 -2
View File
@@ -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)
+28 -4
View File
@@ -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__":
+19 -12
View File
@@ -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:
+9 -4
View File
@@ -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,