[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
+55 -4
View File
@@ -60,6 +60,16 @@
;;; Box, damit Sensoren knapp am Rand noch erfasst werden.
;;; 6. Bei zugeordneten Sensoren wird die ID des Carriers (Attribut
;;; "ID") in das Sensor-Attribut ZUORDNUNG geschrieben.
;;; 6a. AUSNAHME zu 1-5: Separatoren, deren Entity-Handle in
;;; *cs-sep-fix-by-handle* steht, werden NICHT geometrisch
;;; zugeordnet, sondern dem dort genannten Carrier. Das sind die
;;; temporaeren Kopien der in VF_n/GF_n/KREISEL_n VERPACKTEN
;;; Separator_SP-Symbole (csv:sep-proxies-erzeugen/-zuordnung-
;;; setzen, export.lsp): fuer sie ist der Carrier bekannt - es ist
;;; der Wrapper-Block, in dem sie stecken -, und Raten per
;;; Boundingbox waere schlechter als die bekannte Wahrheit (am
;;; Kettenende ueberlappen sich Carrier-Boxen). Existiert der
;;; genannte Carrier nicht (mehr), greifen wieder 1-5.
;;; 7. Sensoren, zu denen ueberhaupt kein Carrier existiert (leere
;;; Zeichnung), werden mit einem Kreis auf dem Layer *mark-layer*
;;; markiert (zentriert auf den Sensor).
@@ -92,6 +102,17 @@
;; zugeordneten Carriers geschrieben wird.
(setq *zuordnung-tag* "ZUORDNUNG")
;; Festgenagelte (nicht geometrisch zu ermittelnde) Separator-Zuordnungen:
;; Assoc-Liste (entity-handle . carrier-id). Wird von csv:sep-proxies-
;; zuordnung-setzen (export.lsp) fuer die temporaeren Kopien der in
;; VF_n/GF_n/KREISEL_n VERPACKTEN Separator_SP-Symbole gefuellt - fuer die ist
;; der Carrier bekannt (es ist der Wrapper, in dem sie stecken) und muss
;; nicht ueber Boundingbox/Distanz geraten werden. Bei ueberlappenden
;; Carrier-Boxen (Kettenende an einem Kreisel) wuerde die Geometrie sonst
;; leicht den Nachbarn treffen. Leer = alles rein geometrisch (Normalfall
;; beim interaktiven ZAEHLE_SEP_SCAN).
(if (null *cs-sep-fix-by-handle*) (setq *cs-sep-fix-by-handle* nil))
;; Markierung von Sensoren ohne jeglichen Carrier: Kreis auf eigenem Layer.
(setq *mark-layer* "S_SENSOR_UNZUGEORDNET")
(setq *mark-color* 2) ;; 2 = Gelb
@@ -362,6 +383,21 @@
)
)
;;; ------------------------------------------------------------
;;; Carrier-Record ueber seine ID finden (siehe *cs-sep-fix-by-handle*).
;;; carriers = Liste von Records (ename tagScan tagSep minx miny maxx
;;; maxy cx cy id label), id an Position 9.
;;; Rueckgabe: Record oder nil, wenn kein Carrier diese ID traegt.
;;; ------------------------------------------------------------
(defun cs-carrier-by-id (id carriers / rec treffer)
(if (and id (/= id ""))
(foreach rec carriers
(if (and (null treffer) (= (nth 9 rec) id))
(setq treffer rec)))
)
treffer
)
;;; ------------------------------------------------------------
;;; Zaehler in einer Assoc-Liste (Schluessel = ename) erhoehen.
;;; ------------------------------------------------------------
@@ -600,6 +636,7 @@
scanCounts sepCounts n-scan-unassigned n-sep-unassigned
n-strittig n-zuordnung-fehlt alt
s sent spt res mode rec best-d second-rec second-d
vorgabe-id vorgabe-rec
scanner-id contested handle
en nm2 ns nsep newS newP okS okP eintrag)
(setq carriers nil scanlist nil seplist nil
@@ -719,8 +756,19 @@
;; ANZAHL_SEPARATOR-Summe auch bei Sonderfaellen stimmt). ---
(foreach s seplist
(setq sent (car s) spt (cadr s))
(setq res (cs-resolve-sensor spt carriers grid))
(setq mode (nth 0 res) rec (nth 1 res))
;; Festgenagelte Zuordnung (verpackter Separator, Carrier bekannt -
;; siehe *cs-sep-fix-by-handle*) hat Vorrang vor der Geometrie. Nur
;; wenn der genannte Carrier gar nicht (mehr) existiert, wird
;; geometrisch zugeordnet.
(setq vorgabe-id (cdr (assoc (cdr (assoc 5 (entget sent))) *cs-sep-fix-by-handle*)))
(setq vorgabe-rec (cs-carrier-by-id vorgabe-id carriers))
(if vorgabe-rec
(setq mode 'vorgabe rec vorgabe-rec)
(progn
(setq res (cs-resolve-sensor spt carriers grid))
(setq mode (nth 0 res) rec (nth 1 res))
)
)
(cond
((eq mode 'none)
(setq n-sep-unassigned (1+ n-sep-unassigned))
@@ -728,12 +776,15 @@
((null rec) nil) ;; Sicherheitsnetz - tritt bei bbox/distance nicht auf
(t
(setq sepCounts (cs-bump sepCounts (car rec)))
;; Abweichung nur bei geometrischer Zuordnung als Korrektur melden -
;; bei mode 'vorgabe ist der geschriebene Wert die Vorgabe selbst,
;; keine Korrektur einer Fehl-Zuordnung.
(setq alt (cs-get-att-str sent *zuordnung-tag*))
(if (and alt (/= alt "") (/= alt (nth 9 rec)))
(if (and (not (eq mode 'vorgabe)) alt (/= alt "") (/= alt (nth 9 rec)))
(setq *cs-korrekturen*
(cons (list "Separator" (cs-get-att-str sent "ID") alt (nth 9 rec))
*cs-korrekturen*)))
(if (member mode '(bbox distance))
(if (member mode '(vorgabe bbox distance))
(if (not (cs-set-att sent *zuordnung-tag* (nth 9 rec)))
(setq n-zuordnung-fehlt (1+ n-zuordnung-fehlt))))
)