[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
+56 -10
View File
@@ -761,26 +761,72 @@
;; die durchlaeuft ID-Vergabe/Export danach ganz normal ueber die
;; unveraenderten ssg-id-check-all/csv:collect-export-blocks. Rekursiv,
;; falls ein Treffer selbst nochmal in einem weiteren Sub-Compound-Block
;; steckt. Rueckgabe: Liste von Entity-Namen (aus der Blockdefinition, kein
;; Selection-Set - sie sind nicht Space-resident und daher fuer ssadd nicht
;; sicher verwendbar), oder nil. Die gefundenen Entity-Namen bleiben ueber
;; entget normal lesbar (Blockname/Einfuegepunkt/Rotation), sollten aber
;; NICHT direkt exportiert werden - eine eigene Bounding-Box
;; (vla-getboundingbox) liefert fuer sie keine sinnvollen Weltkoordinaten,
;; da sie nicht Space-resident sind.
(defun ssg-collect-nested-inserts (bname pattern / blk-tbl sub-ent sub-ed sub-bname result)
;; steckt.
;;
;; Rueckgabe: Liste von Treffer-Records, oder nil. Ein Record ist
;; (ename (x y z) rot-rad sx sy sz)
;; mit der Platzierung des Treffers RELATIV zur abgefragten Blockdefinition
;; bname - ueber alle Verschachtelungsebenen hinweg aufsummiert (Position,
;; Z-Drehung und Skalierung). Der Aufrufer muss also nur noch die Platzierung
;; des top-level INSERT daraufrechnen. Der rohe Einfuegepunkt aus
;; (entget ename) ist bei mehr als einer Ebene NICHT verwendbar - er gilt nur
;; innerhalb der unmittelbaren Zwischen-Blockdefinition.
;;
;; Kein Selection-Set: die Entities sind nicht Space-resident und damit fuer
;; ssadd nicht sicher verwendbar. Sie bleiben ueber entget normal lesbar
;; (Blockname, Attribute), sollten aber NICHT direkt exportiert werden - eine
;; eigene Bounding-Box (vla-getboundingbox) liefert fuer sie keine sinnvollen
;; Weltkoordinaten.
(defun ssg-collect-nested-inserts (bname pattern)
(ssg-collect-nested-inserts-rel bname pattern '(0.0 0.0 0.0) 0.0 1.0 1.0 1.0)
)
;; --- Rekursions-Rumpf mit mitgefuehrter Platzierung ---
;; basis-pt/basis-rot/bsx/bsy/bsz = Platzierung der gerade durchsuchten
;; Blockdefinition im Koordinatensystem des URSPRUENGLICH abgefragten Blocks.
;; Auf jeder Ebene wird der Einfuegepunkt des Sub-INSERT erst mit der
;; Skalierung, dann mit der Drehung der Ebene darueber verrechnet und
;; aufaddiert - genau das fehlte frueher: die Rekursion gab die Entities
;; tieferer Ebenen mit ihrer ROH-Position aus der jeweiligen Zwischen-
;; Blockdefinition zurueck. Zwei Separatoren, die in zwei verschieden
;; platzierten Zwischenbloecken an derselben lokalen Stelle sitzen, landeten
;; dadurch auf exakt derselben Weltposition (und damit beim falschen Carrier).
;;
;; Modellannahme wie im uebrigen Export (csv:local-to-world): reine
;; Z-Drehung, keine OCS-/Extrusionsrichtung. Fuer die Wrapper-Ketten
;; VF_n/GF_n/KREISEL_n trifft das zu.
(defun ssg-collect-nested-inserts-rel (bname pattern basis-pt basis-rot bsx bsy bsz
/ blk-tbl sub-ent sub-ed sub-bname loc-pt
rot sx sy sz c si lx ly lz wx wy wz result)
(setq blk-tbl (tblsearch "BLOCK" bname))
(if blk-tbl
(progn
(setq c (cos basis-rot) si (sin basis-rot))
(setq sub-ent (entnext (cdr (assoc -2 blk-tbl))))
(while sub-ent
(setq sub-ed (entget sub-ent))
(if (= (cdr (assoc 0 sub-ed)) "INSERT")
(progn
(setq sub-bname (cdr (assoc 2 sub-ed)))
(setq loc-pt (cdr (assoc 10 sub-ed)))
(setq rot (cond ((cdr (assoc 50 sub-ed))) (0.0)))
(setq sx (cond ((cdr (assoc 41 sub-ed))) (1.0)))
(setq sy (cond ((cdr (assoc 42 sub-ed))) (1.0)))
(setq sz (cond ((cdr (assoc 43 sub-ed))) (1.0)))
(setq lx (* bsx (car loc-pt))
ly (* bsy (cadr loc-pt))
lz (* bsz (cond ((caddr loc-pt)) (0.0))))
(setq wx (+ (car basis-pt) (- (* lx c) (* ly si)))
wy (+ (cadr basis-pt) (+ (* lx si) (* ly c)))
wz (+ (caddr basis-pt) lz))
(if (wcmatch sub-bname pattern)
(setq result (cons sub-ent result))
(setq result (append result (ssg-collect-nested-inserts sub-bname pattern)))
(setq result (cons (list sub-ent (list wx wy wz) (+ basis-rot rot)
(* bsx sx) (* bsy sy) (* bsz sz))
result))
(setq result (append result
(ssg-collect-nested-inserts-rel sub-bname pattern
(list wx wy wz) (+ basis-rot rot)
(* bsx sx) (* bsy sy) (* bsz sz))))
)
)
)