[FIX] Eindeutige IDs und korrekte ZUORDNUNG der verpackten Separatoren

Beim CSV-Export von Mubea trugen Gefaellestrecke/VF/Kreisel und die aus
ihnen erzeugten Separator-Kopien dieselbe ID (26 Dubletten 0001-0026), und
die ZUORDNUNG der Separatoren zeigte auf den falschen Carrier.

ssg-id-check-all: neue Phase 0 ermittelt das globale Maximum der bereits
vergebenen IDs, BEVOR die erste neue ID vergeben wird (ausgelagert nach
ssg-id-max-in-ss, das auch ssg-id-max nutzt). Bisher wurde max-id erst
waehrend Phase 1 mitgezogen (Start 0); da (ssget "X" ...) in BricsCAD die
zuletzt erzeugten Entities zuerst liefert, standen die noch ID-losen
Separator-Kopien ganz vorne und bekamen 0001, 0002, ... - genau die IDs der
weiter hinten liegenden Wrapper-Bloecke. Phase 2 konnte das nicht heilen,
weil frisch vergebene IDs nicht in id-map landen.

ssg-collect-nested-inserts: fuehrt Position, Z-Drehung und Skalierung jetzt
ueber alle Verschachtelungsebenen mit (Records statt nackter Entity-Namen).
Vorher gab die Rekursion Entities tieferer Ebenen mit ihrer ROH-Position aus
der Zwischen-Blockdefinition zurueck - zwei Separatoren an derselben lokalen
Stelle in zwei verschieden platzierten Zwischenbloecken landeten dadurch auf
exakt derselben Weltposition.

csv:sep-proxies-erzeugen: die Kopie steht jetzt exakt auf Position/Drehung/
Skalierung ihres verpackten Vorbilds; der kosmetische Versatz von 500 mm ist
weg (er kippte Separatoren am Kettenende in die Boundingbox des Nachbar-
Carriers). Der vla-InsertBlock-Aufruf ist gekapselt, damit ein Sonderfall
nur diese eine Kopie ausfallen laesst statt den ganzen Export abzubrechen.

csv:sep-proxies-zuordnung-setzen (neu, laeuft NACH ssg-id-check-all, da ein
frisch gebauter Wrapper vorher keine ID hat): fuer einen VERPACKTEN Separator
ist der Carrier bekannt - es ist der Wrapper, in dem er steckt. ZUORDNUNG
wird daher auf die Wrapper-ID gesetzt (Separator in VF 0010 -> ZUORDNUNG
0010) und ueber *cs-sep-fix-by-handle* festgenagelt: cs-zuordnung-lauf
uebernimmt sie unveraendert statt sie geometrisch neu zu raten, und
csv:block-to-json reicht sie als "zuordnung_fix" an export_csv.py durch.
Ohne das ueberschrieb compute_sensor_zuordnung (Prioritaet GF > Foerderer >
Kreiselhaelfte) den bekannten Wert - in der Mubea-Zeichnung landeten 26 in
VF/GF verpackte Separatoren so bei einer Kreiselhaelfte.

export_csv.py: respektiert "zuordnung_fix" und ersetzt eine vorhandene
ZUORDNUNG nicht mehr durch "nicht zugeordnet" - nur ein echter Treffer
ueberschreibt, wie im Docstring von compute_sensor_zuordnung beschrieben.

tests/test_export_ids.py (neu): prueft je tests/output/*_export.csv, dass
jede TeileId hoechstens einmal vorkommt und jede Sensor-Zuordnung auf eine
existierende TeileId zeigt. Gegen die fehlerhafte Mubea-CSV schlaegt der
Test mit allen 26 Dubletten an.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-03 11:38:18 +02:00
parent 5902343352
commit 21684101f9
9 changed files with 488 additions and 57 deletions
+40 -8
View File
@@ -62,13 +62,17 @@
)
;; --- Hoechste ID in der Zeichnung ermitteln ---
;; Durchsucht alle exportierbaren Bloecke nach dem Attribut "ID".
;; --- Hoechste bereits vergebene ID in einem Auswahlsatz ermitteln ---
;; ss = Auswahlsatz (i.d.R. aus ssg-id-collect-blocks) oder nil.
;; Rueckgabe: Integer (hoechste ID) oder 0 wenn keine gefunden.
(defun ssg-id-max ( / ss i ename attribs id-val id-num max-id)
(dbgf "ssg-id-max")
;;
;; Bewusst als eigene Funktion: ssg-id-check-all MUSS diesen Wert ueber den
;; GESAMTEN Auswahlsatz kennen, BEVOR es die erste neue ID vergibt (siehe
;; Phase 0 dort) - sonst haengt die Neuvergabe von der (beliebigen)
;; ssget-Reihenfolge ab und vergibt IDs, die weiter hinten im Auswahlsatz
;; schon belegt sind.
(defun ssg-id-max-in-ss (ss / i ename attribs id-val id-num max-id)
(setq max-id 0)
(setq ss (ssg-id-collect-blocks))
(if ss
(progn
(setq i 0)
@@ -87,6 +91,16 @@
)
)
)
max-id
)
;; --- Hoechste ID in der Zeichnung ermitteln ---
;; Durchsucht alle exportierbaren Bloecke nach dem Attribut "ID".
;; Rueckgabe: Integer (hoechste ID) oder 0 wenn keine gefunden.
(defun ssg-id-max ( / max-id)
(dbgf "ssg-id-max")
(setq max-id (ssg-id-max-in-ss (ssg-id-collect-blocks)))
(dbgreturn max-id)
)
@@ -156,10 +170,26 @@
0
)
(progn
;; Phase 1: Alle IDs sammeln und hoechste ID ermitteln
;; Phase 0: Hoechste BEREITS vergebene ID ueber den GESAMTEN Auswahlsatz
;; ermitteln - zwingend VOR der ersten Neuvergabe in Phase 1.
;;
;; Frueher wurde max-id erst waehrend Phase 1 mitgefuehrt (Start 0) und
;; eine fehlende ID sofort mit max-id+1 belegt. Die Reihenfolge von
;; (ssget "X" ...) ist aber beliebig - in BricsCAD kommen die zuletzt
;; erzeugten Entities zuerst. Genau das trifft die temporaeren
;; Separator-Kopien aus csv:sep-proxies-erzeugen (export.lsp): sie haben
;; noch keine ID, stehen ganz vorne und bekamen daher 0001, 0002, ... -
;; also exakt die IDs, die die weiter hinten liegenden Kreisel/VF_n/GF_n
;; laengst tragen. Phase 2 konnte das nicht heilen, weil die frisch
;; vergebenen IDs nicht in id-map landen (und auch nicht muessen: sie
;; liegen jetzt garantiert oberhalb jeder vorhandenen ID).
(dbgp "=== Phase 0: hoechste vorhandene ID ermitteln ===")
(setq max-id (ssg-id-max-in-ss ss))
(dbgp (strcat "Phase 0: hoechste vorhandene ID = " (itoa max-id)))
;; Phase 1: Alle IDs sammeln, Bloecke ohne ID neu nummerieren
(dbgp "=== Phase 1: IDs sammeln ===")
(setq id-map nil)
(setq max-id 0)
(setq i 0)
(while (setq ename (ssname ss i))
(setq attribs (ssg-attrib-read ename))
@@ -171,7 +201,9 @@
(if (and id-val (> (strlen id-val) 0))
(progn
(setq id-num (atoi id-val))
(if (> id-num max-id) (setq max-id id-num))
;; max-id NICHT mehr hier nachziehen - es steht seit Phase 0 auf
;; dem globalen Maximum und dient ab jetzt nur noch als Zaehler
;; fuer die Neuvergabe.
;; In Map eintragen: (id-num . (ename1 ename2 ...))
(setq entry (assoc id-num id-map))
(if entry