[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:
+2
-1
@@ -395,7 +395,8 @@ Sammelt INSERT-Blöcke aus der Zeichnung und exportiert sie als JSON oder ruft P
|
||||
| `c:EXPORTSIVAS` | Alle Blöcke als JSON sammeln und `export_sivas.py` aufrufen (Sivas-CSV) |
|
||||
| `c:EXPORTCSV` | Alle Blöcke als JSON sammeln und `export_csv.py` aufrufen (einfache Item-Liste, inkl. Omniflo Bogen/Weiche/Gerade und abschließender "Omniflo Sum"-Zeile) |
|
||||
| `csv:collect-export-blocks` | Alle INSERT-Blöcke der Zeichnung als JSON-Array sammeln |
|
||||
| `csv:run-export py-skript json-datei output-csv` | Python-Skript mit gesammelten Daten aufrufen; legt vorher fuer in VF_n/GF_n/KREISEL_n **verpackte** Separator_SP_2D/_3D-Sub-INSERTs (Sensor-Symbol; siehe `ssg-collect-nested-inserts` in `ssg_core.lsp` - NICHT der interne Staustrecke_Separator_SP-Trennsteg) je eine echte, temporaere Kopie daneben an (`csv:sep-proxies-erzeugen`), damit sie ganz normal ueber `ssg-id-check-all`/`csv:collect-export-blocks` erfasst werden, und entfernt sie danach wieder (`csv:sep-proxies-loeschen`) |
|
||||
| `csv:run-export py-skript json-datei output-csv` | Python-Skript mit gesammelten Daten aufrufen; legt vorher fuer in VF_n/GF_n/KREISEL_n **verpackte** Separator_SP_2D/_3D-Sub-INSERTs (Sensor-Symbol; siehe `ssg-collect-nested-inserts` in `ssg_core.lsp` - NICHT der interne Staustrecke_Separator_SP-Trennsteg) je eine echte, temporaere Kopie **an derselben Stelle** an (`csv:sep-proxies-erzeugen`, Position/Drehung/Skalierung ueber alle Verschachtelungsebenen hinweg identisch zum Vorbild - kein kosmetischer Versatz mehr, der die geometrische Sensor-Zuordnung verfaelschen wuerde), damit sie ganz normal ueber `ssg-id-check-all`/`csv:collect-export-blocks` erfasst werden, und entfernt sie danach wieder (`csv:sep-proxies-loeschen`). Reihenfolge ist wesentlich: **erst alle** Kopien aus allen Wrappern erzeugen, **dann** `ssg-id-check-all` (vergibt IDs oberhalb des globalen Maximums), **dann** `csv:sep-proxies-zuordnung-setzen` |
|
||||
| `csv:sep-proxies-zuordnung-setzen proxy-liste` | ZUORDNUNG jeder Separator-Kopie auf die **ID ihres Wrapper-Blocks** setzen (ein in VF_n mit ID 0010 verpackter Separator erhaelt ZUORDNUNG 0010) und die Zuordnung ueber `*cs-sep-fix-by-handle*` fuer `cs-zuordnung-lauf` festnageln - fuer verpackte Separatoren ist der Carrier bekannt und wird nicht per Boundingbox geraten. Muss NACH `ssg-id-check-all` laufen, da ein frisch gebauter Wrapper vorher noch keine ID hat |
|
||||
| `csv:read-attribs ename` | Attribute eines INSERT als JSON-Objekt lesen |
|
||||
| `c:OMNI_UPDATE_ATTRIBS` | HOEHE/DREHUNG aller Omniflo-Elemente aus der Zeichnung aktualisieren |
|
||||
|
||||
|
||||
+55
-4
@@ -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))))
|
||||
)
|
||||
|
||||
+142
-30
@@ -463,7 +463,8 @@
|
||||
;; csv:get-k-kos-strings) werden immer geschrieben, unabhaengig von
|
||||
;; include-bbox -- Kodierung ueber csv:kos-encode (siehe dort).
|
||||
(defun csv:block-to-json (ename include-bbox / ed blk-name layer pt rotation handle attribs bbox bbox-json
|
||||
insert-quat insert-kos k-list warnung-json warnung-eintrag)
|
||||
insert-quat insert-kos k-list warnung-json warnung-eintrag
|
||||
zuordnung-fix-json zuordnung-fix-eintrag)
|
||||
(setq ed (entget ename))
|
||||
(setq blk-name (cdr (assoc 2 ed)))
|
||||
(setq layer (cdr (assoc 8 ed)))
|
||||
@@ -517,6 +518,22 @@
|
||||
(if warnung-eintrag
|
||||
(setq warnung-json (strcat ",\"warnung\":\"" (csv:json-escape (cdr warnung-eintrag)) "\""))
|
||||
)
|
||||
;; Bekannte (nicht geratene) Separator-Zuordnung durchreichen: fuer die
|
||||
;; temporaeren Kopien VERPACKTER Separator_SP-Symbole steht der Carrier fest
|
||||
;; (der Wrapper-Block, in dem das Symbol steckt) - siehe
|
||||
;; csv:sep-proxies-zuordnung-setzen und *cs-sep-fix-by-handle*. export_csv.py
|
||||
;; berechnet die Zuordnung sonst ein zweites Mal per Boundingbox-
|
||||
;; Ueberschneidung (compute_sensor_zuordnung, Prioritaet GF > VF > Kreisel-
|
||||
;; haelfte) und wuerde den bekannten Wert damit ueberschreiben - z.B. auf
|
||||
;; eine Gefaellestrecke, in deren Box der Separator zufaellig hineinragt,
|
||||
;; obwohl er in einem VF-Block verpackt ist. Mit diesem Feld uebernimmt
|
||||
;; export_csv.py den Wert unveraendert.
|
||||
(setq zuordnung-fix-json "")
|
||||
(setq zuordnung-fix-eintrag (assoc handle *cs-sep-fix-by-handle*))
|
||||
(if zuordnung-fix-eintrag
|
||||
(setq zuordnung-fix-json
|
||||
(strcat ",\"zuordnung_fix\":\"" (csv:json-escape (cdr zuordnung-fix-eintrag)) "\""))
|
||||
)
|
||||
(strcat
|
||||
" {\"block_name\":\"" (csv:json-escape blk-name) "\""
|
||||
",\"layer\":\"" (csv:json-escape layer) "\""
|
||||
@@ -533,6 +550,7 @@
|
||||
",\"k4\":\"" (csv:k-kos k-list "K4") "\""
|
||||
bbox-json
|
||||
warnung-json
|
||||
zuordnung-fix-json
|
||||
"}"
|
||||
)
|
||||
)
|
||||
@@ -561,25 +579,38 @@
|
||||
(strcat (export:pattern "pattern_kreisel" "KR_*,KREISEL_*,ECKRAD_*") ",VF_*,GF_*")
|
||||
)
|
||||
|
||||
;; --- Versatz (mm, in Blockrichtung +X) der temporaeren Separator-Kopien ---
|
||||
;; "Einfach ausserhalb" der Original-Geometrie, damit sie nicht exakt auf
|
||||
;; ihr verpacktes Vorbild zu liegen kommen (rein kosmetisch/zur Unter-
|
||||
;; scheidbarkeit waehrend der - sehr kurzen - Lebensdauer der Kopie).
|
||||
(setq *csv-sep-proxy-versatz-mm* 500.0)
|
||||
;; --- Skalierungsfaktor absichern ---
|
||||
;; vla-InsertBlock verweigert den Faktor 0 (entartete Blockreferenz). Der
|
||||
;; kann nur aus einer fehlerhaften Blockdefinition kommen; 1.0 ist dann die
|
||||
;; brauchbarste Annahme - die Kopie steht wenigstens an der richtigen Stelle.
|
||||
(defun csv:skalierung-oder-eins (f)
|
||||
(if (or (null f) (equal f 0.0 1e-12)) 1.0 f)
|
||||
)
|
||||
|
||||
;; --- Fuer jedes verpackte Separator_SP-Symbol eine ECHTE, temporaere Kopie
|
||||
;; daneben in den Modellraum einfuegen ---
|
||||
;; an DERSELBEN Stelle in den Modellraum einfuegen ---
|
||||
;; Separator_SP-Sensor-Symbole, die beim Zusammenbau von VF_n/GF_n/
|
||||
;; KREISEL_n in die Compound-Blockdefinition "verpackt" wurden (siehe
|
||||
;; ssg-collect-nested-inserts), sind fuer (ssget "X" ...) unsichtbar - und
|
||||
;; damit weder fuer ssg-id-check-all (eigene ID) noch csv:collect-export-
|
||||
;; blocks (JSON-Zeile) erreichbar. Statt beide Sammel-Funktionen dafuer
|
||||
;; anzupassen, wird hier VOR der ID-Vergabe (csv:run-export) fuer jeden Fund
|
||||
;; eine ECHTE Kopie desselben Blocks an seiner Weltposition (+ Versatz in
|
||||
;; Blockrichtung, damit sie nicht exakt auf dem Original liegt) eingefuegt.
|
||||
;; eine ECHTE Kopie desselben Blocks eingefuegt.
|
||||
;; Diese Kopie ist ein normaler, top-level INSERT und durchlaeuft ID-
|
||||
;; Vergabe/Export danach unveraendert ueber die bestehenden Funktionen -
|
||||
;; deren Blockname-Muster (Separator_SP*) trifft ja bereits zu.
|
||||
;;
|
||||
;; Die Kopie steht bewusst EXAKT auf der Weltposition/-drehung/-skalierung
|
||||
;; ihres verpackten Vorbilds (frueher: +500 mm Versatz in Blockrichtung, rein
|
||||
;; kosmetisch). Der Versatz war schaedlich: die geometrische Sensor-Zuordnung
|
||||
;; in count_sep_scan.lsp arbeitet ueber die Carrier-Boundingbox, und ein um
|
||||
;; 500 mm verschobener Separator kippt am Kettenende in die Box des Nachbar-
|
||||
;; Carriers - die Kopie soll ihr Vorbild in der Zeichenebene 1:1
|
||||
;; repraesentieren. Die Platzierung der Ebenen zwischen Wrapper und Symbol
|
||||
;; steckt in den Records von ssg-collect-nested-inserts (Position/Drehung/
|
||||
;; Skalierung relativ zur Wrapper-Definition); hier kommt nur noch die
|
||||
;; Platzierung des top-level INSERT darauf.
|
||||
;;
|
||||
;; Einfuegen ueber vla-InsertBlock (ActiveX) statt "command _.INSERT": das
|
||||
;; legt fuer einen Block mit ATTDEFs automatisch ATTRIB-Entities mit den
|
||||
;; Vorgabewerten an (wie TEFInsert.lsp/tefl-insert-element, ssg_ks_insert.lsp
|
||||
@@ -593,11 +624,15 @@
|
||||
;; koennten in dieser Sitzung noch gar nicht geladen worden sein, falls die
|
||||
;; Zeichnung mit bereits vorhandenen VF_n/GF_n-Bloecken geoeffnet und direkt
|
||||
;; exportiert wird, ohne vorher FOERDERANLAGE/GEFAELLESTRECKE aufzurufen.
|
||||
;; Rueckgabe: Liste der neu eingefuegten Entity-Namen (fuer csv:sep-proxies-
|
||||
;; loeschen nach dem Export).
|
||||
;;
|
||||
;; Rueckgabe: Liste von Paaren (proxy-ename . wrapper-ename) - der Wrapper
|
||||
;; wird mitgefuehrt, weil erst NACH der ID-Vergabe daraus die ZUORDNUNG der
|
||||
;; Kopie gesetzt werden kann (csv:sep-proxies-zuordnung-setzen). Die
|
||||
;; proxy-enames dienen ausserdem csv:sep-proxies-loeschen nach dem Export.
|
||||
(defun csv:sep-proxies-erzeugen ( / ms ss-all i ename ed bname nested pt rotation
|
||||
nested-ent nested-ed nested-bname nested-rotation
|
||||
local-pt world-pt block-obj proxy-liste)
|
||||
psx psy psz c si rec nested-ent nested-bname
|
||||
local-pt nested-rot lx ly lz world-pt
|
||||
block-obj proxy-liste)
|
||||
(setq ss-all (ssget "X" (list (cons 0 "INSERT"))))
|
||||
(if ss-all
|
||||
(progn
|
||||
@@ -614,23 +649,45 @@
|
||||
(if nested
|
||||
(progn
|
||||
(setq pt (cdr (assoc 10 ed)))
|
||||
(setq rotation (cdr (assoc 50 ed)))
|
||||
(if (null rotation) (setq rotation 0.0))
|
||||
(foreach nested-ent nested
|
||||
(setq nested-ed (entget nested-ent))
|
||||
(setq nested-bname (cdr (assoc 2 nested-ed)))
|
||||
(setq local-pt (cdr (assoc 10 nested-ed)))
|
||||
(setq nested-rotation (cdr (assoc 50 nested-ed)))
|
||||
(if (null nested-rotation) (setq nested-rotation 0.0))
|
||||
(setq world-pt (csv:local-to-world pt rotation local-pt))
|
||||
(setq world-pt (list (+ (car world-pt) *csv-sep-proxy-versatz-mm*)
|
||||
(cadr world-pt)
|
||||
(caddr world-pt)))
|
||||
(setq rotation (cond ((cdr (assoc 50 ed))) (0.0)))
|
||||
(setq psx (cond ((cdr (assoc 41 ed))) (1.0)))
|
||||
(setq psy (cond ((cdr (assoc 42 ed))) (1.0)))
|
||||
(setq psz (cond ((cdr (assoc 43 ed))) (1.0)))
|
||||
(setq c (cos rotation) si (sin rotation))
|
||||
(foreach rec nested
|
||||
(setq nested-ent (nth 0 rec))
|
||||
(setq local-pt (nth 1 rec))
|
||||
(setq nested-rot (nth 2 rec))
|
||||
(setq nested-bname (cdr (assoc 2 (entget nested-ent))))
|
||||
(setq lx (* psx (car local-pt))
|
||||
ly (* psy (cadr local-pt))
|
||||
lz (* psz (caddr local-pt)))
|
||||
(setq world-pt
|
||||
(list (+ (car pt) (- (* lx c) (* ly si)))
|
||||
(+ (cadr pt) (+ (* lx si) (* ly c)))
|
||||
(+ (cond ((caddr pt)) (0.0)) lz)))
|
||||
;; Rotation fuer vla-InsertBlock in RADIANT (ActiveX-Konvention,
|
||||
;; anders als der Grad-Winkel von "command _.INSERT").
|
||||
(setq block-obj (vla-InsertBlock ms (vlax-3D-point world-pt)
|
||||
nested-bname 1.0 1.0 1.0 (+ rotation nested-rotation)))
|
||||
(setq proxy-liste (cons (vlax-vla-object->ename block-obj) proxy-liste))
|
||||
;; Gekapselt, weil hier - anders als beim frueheren festen
|
||||
;; 1.0/1.0/1.0 - die tatsaechlichen Skalierungsfaktoren
|
||||
;; durchgereicht werden: ein einzelner Sonderfall (entartete
|
||||
;; Skalierung, gespiegelter Zwischenblock) darf nicht den
|
||||
;; kompletten Export der Zeichnung abbrechen, sondern nur diese
|
||||
;; eine Kopie ausfallen lassen - mit Meldung, welcher Block.
|
||||
(setq block-obj
|
||||
(vl-catch-all-apply 'vla-InsertBlock
|
||||
(list ms (vlax-3D-point world-pt) nested-bname
|
||||
(csv:skalierung-oder-eins (* psx (nth 3 rec)))
|
||||
(csv:skalierung-oder-eins (* psy (nth 4 rec)))
|
||||
(csv:skalierung-oder-eins (* psz (nth 5 rec)))
|
||||
(+ rotation nested-rot))))
|
||||
(if (vl-catch-all-error-p block-obj)
|
||||
(princ (ssg-textf "exp-sep-proxy-fehler"
|
||||
(list nested-bname bname
|
||||
(vl-catch-all-error-message block-obj))))
|
||||
(setq proxy-liste
|
||||
(cons (cons (vlax-vla-object->ename block-obj) ename) proxy-liste))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -641,10 +698,56 @@
|
||||
proxy-liste
|
||||
)
|
||||
|
||||
;; --- ZUORDNUNG der temporaeren Separator-Kopien aus ihrem Wrapper setzen ---
|
||||
;; MUSS nach ssg-id-check-all laufen: erst dann steht die ID des Wrapper-
|
||||
;; Blocks (VF_n/GF_n/KREISEL_n) fest - ein frisch gebauter Wrapper kann beim
|
||||
;; Erzeugen der Kopien noch gar keine ID haben.
|
||||
;;
|
||||
;; Die Zuordnung eines VERPACKTEN Separators ist keine Schaetzung, sondern
|
||||
;; bekannt: er steckt in genau diesem Wrapper. Ein Separator, der in einem
|
||||
;; VF-Block mit ID 0010 verpackt ist, bekommt also ZUORDNUNG = 0010. Die
|
||||
;; geometrische Zuordnung in count_sep_scan.lsp (Carrier-Boundingbox bzw.
|
||||
;; naechster Carrier) wuerde diesen Wert bei jedem Lauf neu berechnen und ggf.
|
||||
;; ueberschreiben - bei ueberlappenden Boxen (Kettenende an einem Kreisel)
|
||||
;; landet der Separator dort leicht beim Nachbarn. Darum wird die bekannte
|
||||
;; Zuordnung ueber *cs-sep-fix-by-handle* (Key = Entity-Handle der Kopie)
|
||||
;; festgenagelt; cs-zuordnung-lauf uebernimmt sie unveraendert und zaehlt den
|
||||
;; Separator beim richtigen Carrier.
|
||||
;; Rueckgabe: Anzahl festgenagelter Kopien.
|
||||
(defun csv:sep-proxies-zuordnung-setzen (proxy-liste / paar proxy wrapper wrapper-id
|
||||
handle gesetzt ohne-id)
|
||||
(setq gesetzt 0 ohne-id 0)
|
||||
(setq *cs-sep-fix-by-handle* nil)
|
||||
(foreach paar proxy-liste
|
||||
(setq proxy (car paar))
|
||||
(setq wrapper (cdr paar))
|
||||
(setq wrapper-id (cdr (assoc "ID" (ssg-attrib-read wrapper))))
|
||||
(if (and wrapper-id (> (strlen wrapper-id) 0))
|
||||
(progn
|
||||
(ssg-attrib-set-on proxy (list (cons "ZUORDNUNG" wrapper-id)))
|
||||
(setq handle (cdr (assoc 5 (entget proxy))))
|
||||
(setq *cs-sep-fix-by-handle*
|
||||
(cons (cons handle wrapper-id) *cs-sep-fix-by-handle*))
|
||||
(setq gesetzt (1+ gesetzt))
|
||||
)
|
||||
;; Wrapper ohne ID-Attribut (Altbestand ohne ID-ATTDEF): keine bekannte
|
||||
;; Zuordnung - die Kopie faellt auf die geometrische Zuordnung zurueck.
|
||||
(setq ohne-id (1+ ohne-id))
|
||||
)
|
||||
)
|
||||
(if (> gesetzt 0)
|
||||
(princ (ssg-textf "exp-sep-zuordnung" (list (itoa gesetzt)))))
|
||||
(if (> ohne-id 0)
|
||||
(princ (ssg-textf "exp-sep-zuordnung-ohne-id" (list (itoa ohne-id)))))
|
||||
gesetzt
|
||||
)
|
||||
|
||||
;; --- Temporaere Separator_SP-Kopien (csv:sep-proxies-erzeugen) nach dem
|
||||
;; Export wieder entfernen ---
|
||||
;; proxy-liste = Paare (proxy-ename . wrapper-ename), nur der car wird geloescht.
|
||||
(defun csv:sep-proxies-loeschen (proxy-liste)
|
||||
(foreach ent proxy-liste (entdel ent))
|
||||
(foreach paar proxy-liste (entdel (car paar)))
|
||||
(setq *cs-sep-fix-by-handle* nil)
|
||||
(princ)
|
||||
)
|
||||
|
||||
@@ -736,9 +839,18 @@
|
||||
;; ID-Vergabe durch temporaere, echte Kopien ersetzen - sie werden am Ende
|
||||
;; dieser Funktion wieder entfernt (csv:sep-proxies-loeschen).
|
||||
(setq sep-proxies (csv:sep-proxies-erzeugen))
|
||||
;; Vor dem Export: IDs aller Bloecke sicherstellen und Duplikate korrigieren
|
||||
;; Vor dem Export: IDs aller Bloecke sicherstellen und Duplikate korrigieren.
|
||||
;; Erst hier - also erst nachdem ALLE Kopien aus allen Wrapper-Bloecken
|
||||
;; stehen - wird ueberhaupt eine ID vergeben. ssg-id-check-all ermittelt
|
||||
;; dabei zuerst das globale Maximum der bereits vergebenen IDs (Phase 0)
|
||||
;; und nummeriert die noch ID-losen Kopien erst danach - sonst bekaemen sie
|
||||
;; IDs, die die Wrapper (VF_n/GF_n/Kreisel) selbst schon tragen.
|
||||
(princ (ssg-textf "exp-check-ids" (list label)))
|
||||
(ssg-id-check-all)
|
||||
;; Jetzt steht die ID jedes Wrappers fest: die bekannte (nicht geratene)
|
||||
;; ZUORDNUNG der verpackten Separatoren auf die Wrapper-ID setzen und fuer
|
||||
;; cs-zuordnung-lauf festnageln.
|
||||
(csv:sep-proxies-zuordnung-setzen sep-proxies)
|
||||
;; Vor dem Export HOEHE/DREHUNG aller Omniflo-Elemente aktualisieren
|
||||
(princ (ssg-textf "exp-update-hoehe-drehung" (list label)))
|
||||
(omni:update-all-attribs)
|
||||
|
||||
+56
-10
@@ -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))))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
+40
-8
@@ -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
|
||||
|
||||
@@ -567,6 +567,9 @@
|
||||
"core-dwg-not-found": "\nFEHLER: Block nicht gefunden: %1",
|
||||
"exp-cfg-not-found": "\n[export] WARNUNG: %1 nicht gefunden/leer, verwende Vorgaben.",
|
||||
"exp-check-ids": "\n[%1] Pruefe und vergebe IDs...",
|
||||
"exp-sep-proxy-fehler": "\n[EXPORT] WARNUNG: Kopie des verpackten Separators '%1' aus Block '%2' fehlgeschlagen (%3) - dieser Separator fehlt im Export.",
|
||||
"exp-sep-zuordnung": "\n[%1] verpackte Separator-Kopie(n): ZUORDNUNG aus der ID des Wrapper-Blocks gesetzt.",
|
||||
"exp-sep-zuordnung-ohne-id": "\n[%1] verpackte Separator-Kopie(n) ohne Wrapper-ID - ZUORDNUNG wird geometrisch bestimmt.",
|
||||
"exp-update-hoehe-drehung": "\n[%1] Aktualisiere Hoehe/Drehung...",
|
||||
"exp-collect-blocks": "\n[%1] Sammle relevante Bloecke...",
|
||||
"exp-no-blocks-found": "\n[%1] Keine relevanten Bloecke gefunden.",
|
||||
|
||||
@@ -567,6 +567,9 @@
|
||||
"core-dwg-not-found": "\nERROR: Block not found: %1",
|
||||
"exp-cfg-not-found": "\n[export] WARNING: %1 not found/empty, using defaults.",
|
||||
"exp-check-ids": "\n[%1] Checking and assigning IDs...",
|
||||
"exp-sep-proxy-fehler": "\n[EXPORT] WARNING: copying the packed separator '%1' out of block '%2' failed (%3) - this separator is missing from the export.",
|
||||
"exp-sep-zuordnung": "\n[%1] packed separator copy/copies: ZUORDNUNG set from the wrapper block ID.",
|
||||
"exp-sep-zuordnung-ohne-id": "\n[%1] packed separator copy/copies without a wrapper ID - ZUORDNUNG determined geometrically.",
|
||||
"exp-update-hoehe-drehung": "\n[%1] Updating height/rotation...",
|
||||
"exp-collect-blocks": "\n[%1] Collecting relevant blocks...",
|
||||
"exp-no-blocks-found": "\n[%1] No relevant blocks found.",
|
||||
|
||||
+27
-4
@@ -282,10 +282,15 @@ def build_sensor_merkmale(block):
|
||||
Scanner-Block (siehe pattern_separator/pattern_scanner in cfg/export.cfg).
|
||||
ZUORDNUNG/ARTINR/LAYER (+ neuerdings ID) sind die ATTDEFs (siehe
|
||||
ils-insert-sensor/mubea:build-separator-one, ssg-id-collect-blocks in
|
||||
ssg_id.lsp)."""
|
||||
ssg_id.lsp).
|
||||
|
||||
"zuordnung_fix" (csv:block-to-json) hat Vorrang vor dem ZUORDNUNG-Attribut:
|
||||
es steht an den temporaeren Kopien der in VF_n/GF_n/KREISEL_n VERPACKTEN
|
||||
Separator_SP-Symbole und traegt die ID des Wrapper-Blocks, in dem das
|
||||
Symbol steckt - eine bekannte, keine geratene Zuordnung."""
|
||||
attribs = block.get("attribs", {})
|
||||
return {
|
||||
"Zuordnung": attribs.get("ZUORDNUNG", ""),
|
||||
"Zuordnung": block.get("zuordnung_fix") or attribs.get("ZUORDNUNG", ""),
|
||||
"ARTINR": attribs.get("ARTINR", ""),
|
||||
"Höhe": get_hoehe(block),
|
||||
"Drehung": get_drehung(block),
|
||||
@@ -757,6 +762,10 @@ def process_blocks(blocks, lookup, dbg=None):
|
||||
"bezeichnung": f"Separator :{separator_count}",
|
||||
"planquadrat": planquadrat_column(block, planquadrat_cfg),
|
||||
"merkmale": build_sensor_merkmale(block),
|
||||
# Privates Feld (nicht in der CSV): markiert eine BEKANNTE
|
||||
# Zuordnung, die compute_sensor_zuordnung unten nicht
|
||||
# ueberschreiben darf - siehe build_sensor_merkmale.
|
||||
"_zuordnung_fix": bool(block.get("zuordnung_fix")),
|
||||
**bbox_columns(block),
|
||||
})
|
||||
continue
|
||||
@@ -819,8 +828,22 @@ def process_blocks(blocks, lookup, dbg=None):
|
||||
sensor_zuordnung = compute_sensor_zuordnung(
|
||||
items, NEIGHBOR_TOLERANCE_MM, kreisel_durchmesser_mm=KREISEL_DURCHMESSER_MM, dbg=dbg)
|
||||
for item, zuordnung in zip(items, sensor_zuordnung):
|
||||
if item.get("teileart", "") in SENSOR_TEILEARTEN and zuordnung:
|
||||
item["merkmale"]["Zuordnung"] = zuordnung
|
||||
if item.get("teileart", "") not in SENSOR_TEILEARTEN or not zuordnung:
|
||||
continue
|
||||
# Bekannte Zuordnung (verpackter Separator, Carrier = Wrapper-Block,
|
||||
# siehe "_zuordnung_fix"/build_sensor_merkmale) nicht ueberschreiben:
|
||||
# die BBox-Suche hier priorisiert GF > Foerderer > Kreiselhaelfte und
|
||||
# wuerde einen in einem VF-Block verpackten Separator an eine
|
||||
# Gefaellestrecke haengen, in deren Box er zufaellig hineinragt.
|
||||
if item.get("_zuordnung_fix"):
|
||||
continue
|
||||
# Kein Treffer ("nicht zugeordnet") ersetzt einen vorhandenen
|
||||
# ZUORDNUNG-Attributwert nicht - sonst geht die in der Zeichnung
|
||||
# gesetzte Zuordnung verloren (siehe Docstring von
|
||||
# compute_sensor_zuordnung: ersetzen nur bei echtem Treffer).
|
||||
if zuordnung == "nicht zugeordnet" and item["merkmale"].get("Zuordnung"):
|
||||
continue
|
||||
item["merkmale"]["Zuordnung"] = zuordnung
|
||||
|
||||
# NACH der Separator-Zuordnung: zu jedem Scanner den raeumlich naechst-
|
||||
# gelegenen Separator suchen (eigene, unabhaengige Suche - siehe
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
test_export_ids.py - Validiert IDs und Sensor-Zuordnung im CSV-Export.
|
||||
|
||||
Prueft jede von TEST_EXPORT_ALL (test_export_all.lsp) geschriebene
|
||||
Datei tests/output/<basis>_export.csv:
|
||||
|
||||
1. Jede TeileId kommt HOECHSTENS EINMAL vor. Eine ID ist in der
|
||||
Zeichnung eindeutig - Gefaellestrecke/VF/Kreisel und der aus
|
||||
ihnen erzeugte Separator duerfen sich keine ID teilen.
|
||||
(Regression: ssg-id-check-all zog max-id frueher erst waehrend
|
||||
des Durchlaufs mit; die temporaeren Separator-Kopien aus
|
||||
csv:sep-proxies-erzeugen stehen in der ssget-Reihenfolge ganz
|
||||
vorne und bekamen dadurch 0001, 0002, ... - also genau die IDs
|
||||
der weiter hinten liegenden Wrapper-Bloecke.)
|
||||
|
||||
2. Die Zuordnung jedes Separators/Scanners zeigt auf eine TeileId,
|
||||
die es in derselben Datei auch gibt (bzw. auf eine Kreisel-
|
||||
haelfte "<TeileId>-L"/"-R"). Eine Zuordnung auf eine nicht
|
||||
existierende ID ist ein verwaister Verweis.
|
||||
|
||||
Beide Pruefungen brauchen keine Zeichnung und kein ezdxf - sie lesen
|
||||
nur die fertige CSV. Ohne Export-CSV in tests/output wird der Test
|
||||
uebersprungen (TEST_EXPORT_ALL in BricsCAD ausfuehren).
|
||||
"""
|
||||
|
||||
import csv
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
# TeileArten, die einen Sensor beschreiben (siehe SENSOR_TEILEARTEN in
|
||||
# lib/export_neighbors.py).
|
||||
SENSOR_TEILEARTEN = {"ILS 2.0 Separator", "ILS 2.0 Scanner"}
|
||||
|
||||
# Zuordnungs-Wert, den lib/export_neighbors.py setzt, wenn die Bounding-Box
|
||||
# des Sensors keinen Carrier trifft - kein verwaister Verweis, sondern die
|
||||
# ausdrueckliche Aussage "keiner".
|
||||
ZUORDNUNG_KEINE = "nicht zugeordnet"
|
||||
|
||||
# Platzhalter-TeileId fuer Bloecke ohne ID-Attribut (aeltere Exporte). Nicht
|
||||
# als ID zu werten und daher von der Eindeutigkeitspruefung ausgenommen.
|
||||
TEILEID_LEER = {"", "0000"}
|
||||
|
||||
|
||||
def _output_dir():
|
||||
return os.getenv("DXFM_TESTOUT",
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "output"))
|
||||
|
||||
|
||||
def _read_export_csv(path):
|
||||
"""Liest eine Export-CSV als Liste von Dicts (Spaltenname -> Wert).
|
||||
|
||||
AutoLISP/Python schreiben die Datei je nach Lauf in UTF-8 oder in der
|
||||
ANSI-Codepage des Systems - wie conftest._load_json erst UTF-8 versuchen,
|
||||
dann cp1252.
|
||||
"""
|
||||
for encoding in ("utf-8-sig", "cp1252"):
|
||||
try:
|
||||
with open(path, "r", encoding=encoding, newline="") as f:
|
||||
return list(csv.DictReader(f, delimiter=";"))
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
pytest.fail(f"{os.path.basename(path)}: weder als UTF-8 noch als cp1252 lesbar")
|
||||
|
||||
|
||||
def _export_csv_dateien():
|
||||
return sorted(glob.glob(os.path.join(_output_dir(), "*_export.csv")))
|
||||
|
||||
|
||||
def _merkmale(row):
|
||||
"""Merkmale-Spalte (JSON) einer CSV-Zeile als Dict, {} wenn unlesbar."""
|
||||
rohtext = (row.get("Merkmale") or "").strip()
|
||||
if not rohtext:
|
||||
return {}
|
||||
try:
|
||||
wert = json.loads(rohtext)
|
||||
except ValueError:
|
||||
return {}
|
||||
return wert if isinstance(wert, dict) else {}
|
||||
|
||||
|
||||
@pytest.fixture(params=_export_csv_dateien(),
|
||||
ids=lambda p: os.path.basename(p))
|
||||
def export_csv(request):
|
||||
"""Je gefundene tests/output/*_export.csv ein eigener Testlauf."""
|
||||
rows = _read_export_csv(request.param)
|
||||
if not rows:
|
||||
pytest.skip(f"{os.path.basename(request.param)} enthaelt keine Datenzeilen")
|
||||
if "TeileId" not in rows[0]:
|
||||
pytest.skip(f"{os.path.basename(request.param)} hat kein TeileId-Feld "
|
||||
"(Export aus einer aelteren Spaltenversion)")
|
||||
return os.path.basename(request.param), rows
|
||||
|
||||
|
||||
if not _export_csv_dateien():
|
||||
pytest.skip("Keine tests/output/*_export.csv vorhanden - "
|
||||
"TEST_EXPORT_ALL in BricsCAD ausfuehren",
|
||||
allow_module_level=True)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 1. Eindeutigkeit der TeileId
|
||||
# ============================================================
|
||||
|
||||
def test_teileid_eindeutig(export_csv):
|
||||
"""Keine TeileId darf zweimal vorkommen (Platzhalter ausgenommen)."""
|
||||
name, rows = export_csv
|
||||
|
||||
belegung = {}
|
||||
for row in rows:
|
||||
teileid = (row.get("TeileId") or "").strip()
|
||||
if teileid in TEILEID_LEER:
|
||||
continue
|
||||
belegung.setdefault(teileid, []).append(
|
||||
f'{(row.get("TeileArt") or "").strip()} "{(row.get("Bezeichnung") or "").strip()}"')
|
||||
|
||||
doppelt = {tid: wer for tid, wer in belegung.items() if len(wer) > 1}
|
||||
assert not doppelt, (
|
||||
f"{name}: {len(doppelt)} TeileId(s) mehrfach vergeben - eine ID ist je "
|
||||
"Zeichnungsobjekt eindeutig:\n" +
|
||||
"\n".join(f" {tid}: " + ", ".join(wer) for tid, wer in sorted(doppelt.items()))
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 2. Zuordnung der Sensoren zeigt auf eine existierende TeileId
|
||||
# ============================================================
|
||||
|
||||
def test_sensor_zuordnung_existiert(export_csv):
|
||||
"""Jede Sensor-Zuordnung verweist auf eine TeileId derselben Datei."""
|
||||
name, rows = export_csv
|
||||
|
||||
bekannte_ids = {(row.get("TeileId") or "").strip() for row in rows}
|
||||
bekannte_ids -= TEILEID_LEER
|
||||
|
||||
verwaist = []
|
||||
for row in rows:
|
||||
if (row.get("TeileArt") or "").strip() not in SENSOR_TEILEARTEN:
|
||||
continue
|
||||
zuordnung = str(_merkmale(row).get("Zuordnung", "")).strip()
|
||||
if not zuordnung or zuordnung == ZUORDNUNG_KEINE:
|
||||
continue
|
||||
# Kreiselhaelften werden als "<TeileId>-L"/"-R" notiert (siehe
|
||||
# compute_sensor_zuordnung in lib/export_neighbors.py).
|
||||
carrier_id = zuordnung[:-2] if zuordnung[-2:] in ("-L", "-R") else zuordnung
|
||||
if carrier_id not in bekannte_ids:
|
||||
verwaist.append(
|
||||
f' {(row.get("TeileArt") or "").strip()} '
|
||||
f'{(row.get("TeileId") or "").strip()} '
|
||||
f'("{(row.get("Bezeichnung") or "").strip()}") -> {zuordnung}')
|
||||
|
||||
assert not verwaist, (
|
||||
f"{name}: {len(verwaist)} Sensor(en) mit Zuordnung auf eine nicht "
|
||||
"vorhandene TeileId:\n" + "\n".join(verwaist)
|
||||
)
|
||||
Reference in New Issue
Block a user