sepliste: Bauteil-IDs eintragen, AS/ES ueber dauerhaften Marker sichtbar machen

Stufe 1 (LISP-Export-Pfad): export_raw.json enthaelt jetzt fuer jeden
sepliste-Eintrag (AS/SEP/ES) einer VF_n/GF_n-Kette die ID des realen
Bauteils, das ihn vertritt.

- csv:sep-proxies-erzeugen (Separatoren) und die neuen AS/ES-Marker teilen
  sich csv:nested-welt-records/csv:block-einfuegen-welt zur Platzierung.
- csv:sepliste-paare ordnet sepliste-Eintraege ihrem Bauteil exklusiv nach
  Abstand zu (kuerzester zuerst, dann verbraucht) statt "jeder Eintrag
  nimmt sich das naechste" - ein einzelner fehlerhafter Koordinatenwert
  (bekannter Bug: gespiegeltes x bei lfdnr=2) verschiebt dadurch keine
  andere Zuordnung mehr.
- Neu: ZZ_ASES_MARKER - ein kleines Rechteck an jedem AS/ES-Kettenende,
  das nach dem Export STEHEN BLEIBT (anders als die temporaeren
  Separator-Kopien). Macht das sonst unsichtbare, in der Wrapper-
  Blockdefinition verpackte Kettenende in BricsCAD sichtbar/anklickbar
  und traegt seine ID unsichtbar am Attribut ID - dadurch behaelt ein
  AS/ES seine TeileId ueber Exportlaeufe hinweg. Wird bei jedem Export
  neu aufgebaut (Lage folgt der Kette), Wiederfund ueber ZUORDNUNG+TYP+
  LFDNR, verwaiste Marker werden entfernt.

In BricsCAD verifiziert (5 HundM05-Ketten, zwei Exportlaeufe: gleiche
Marker-IDs, korrekte sepliste-"id"-Felder). Dabei einen realen Bug
gefunden und behoben: die Blockdefinition des Markers uebernahm ueber
einen ungefilterten entnext-Walk versehentlich auch die Attribute des
zuletzt eingefuegten Blocks (VF_5 verlor dadurch alle Attribute inkl.
ID) - jetzt auf LWPOLYLINE/ATTDEF gefiltert.

9 neue zeichnungsfreie Unit-Tests (csv:sepliste-paare/*, csv:asmark-
finden/*, csv:proxy-typ/*), 4 neue de/en-Textschluessel (Paritaet
geprueft). bin\run_tests.bat --lisp: 257/257 bestanden.

Noch nicht enthalten (Stufe 2, Python): map_separator_kette_items nutzt
das neue "id"-Feld noch nicht, build_strecke_schleus_items synthetisiert
AS/ES-Zeilen weiterhin selbst statt die exportierten zu lesen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Samer Ayadi
2026-09-22 13:09:05 +02:00
co-authored by Claude Sonnet 5
parent 9671fe4e04
commit 618ff3d86c
15 changed files with 1205 additions and 216 deletions
+13
View File
@@ -288,6 +288,19 @@
(list (+ (car pt) (+ (* dx chz cv) (* dz chz sv)))
(+ (cadr pt) (+ (* dx shz cv) (* dz shz sv)))
(+ (caddr pt) (+ (* (- sv) dx) (* cv dz)))))
;; Diagnose (Schalter "vf-sepliste", siehe ssg_ks_insert.lsp): DIESE Funktion
;; setzt die Separatoren des Linienzugs, und da sie den Block ohne
;; KS_EIN-Korrektur direkt nach pt schiebt, ist pt exakt der Gruppencode 10,
;; den ssg-sepliste-sammeln spaeter einsammelt. Ein gespiegelter
;; Sepliste-Eintrag muss also schon hier mit gespiegeltem pt ankommen.
(if (car (atoms-family 1 '("SSG-SEPLISTE-DBG")))
(ssg-sepliste-dbg
(strcat "gf-insert-hz-with-ks " blockname
" hz=" (rtos (float hz-grad) 2 2)
" vert=" (rtos (float vert-grad) 2 2)
" dx=" (rtos (float dx) 2 2) " dz=" (rtos (float dz) 2 2)
" pt(=Gruppencode 10)=" (ssg-sepliste-dbg-pt pt)
" endpunkt=" (ssg-sepliste-dbg-pt endpunkt))))
(princ (ssg-textf "gf-status-hzks"
(list blockname (rtos hz-grad 2 0) grad-zeichen
(rtos vert-grad 2 1) grad-zeichen (rtos (caddr endpunkt) 2 1))))
+5 -1
View File
@@ -423,8 +423,12 @@ 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 **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: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`). Die verpackten **AS-/ES-Schleuselemente** derselben Kette bekommen stattdessen einen DAUERHAFTEN Marker (`csv:asmark-aktualisieren`, siehe unten); beide Wege teilen sich die Platzierungsrechnung (`csv:nested-welt-records` + `csv:block-einfuegen-welt`). 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` und `csv:sepliste-ids-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:asmark-aktualisieren` | AS-/ES-Kettenenden sichtbar machen: setzt an jedes verpackte `AS_Element*`/`ES_Element*` einer VF_n/GF_n-Kette ein kleines Rechteck (Block `ZZ_ASES_MARKER`, Groesse aus `cfg/export.cfg` `[Marker]`, Layer aus `cfg/layer.cfg` `ases_marker`), das nach dem Export **stehen bleibt**. Unsichtbare Attribute ID/ZUORDNUNG/TYP/LFDNR/ELEMENT/ARTINR; die `ID` fuellt `ssg-id-check-all` und der naechste Export **uebernimmt sie**, ein AS/ES behaelt seine TeileId also ueber Laeufe hinweg. Baut bei jedem Export alle Marker neu auf (Wiederfinden ueber ZUORDNUNG+TYP+LFDNR) und entfernt verwaiste |
| `csv:asmark-ids-setzen marker-liste` | Nach `ssg-id-check-all`: ZUORDNUNG (Wrapper-ID) am Marker nachtragen und seine ID am `sepliste`-Eintrag vermerken |
| `csv:sepliste-ids-setzen proxy-liste` | ID jeder Kopie (Separator UND AS/ES, beide Listen zusammen) an ihren `sepliste`-Eintrag des Wrappers haengen; Ergebnis in `*csv-sepliste-id-by-handle*` (Key = Wrapper-Handle), von `csv:block-to-json` als Feld `id` je Eintrag geschrieben. Muss NACH `ssg-id-check-all` laufen |
| `csv:sepliste-paare eintraege proxies` | Zuordnung `sepliste`-Eintrag -> Kopie: nur innerhalb desselben Typmarkers (`AS`/`ES`/`SEP`) und **exklusiv** (kuerzester Abstand zuerst, danach sind Eintrag und Kopie verbraucht). Damit verschiebt EIN fehlerhafter Koordinatenwert in der `sepliste` keine anderen Zuordnungen. Reine Rechenfunktion, getestet in `tests/test_unit.lsp` |
| `csv:read-attribs ename` | Attribute eines INSERT als JSON-Objekt lesen |
| `c:OMNI_UPDATE_ATTRIBS` | HOEHE/DREHUNG aller Omniflo-Elemente aus der Zeichnung aktualisieren |
+672 -190
View File
@@ -653,7 +653,7 @@
handle attribs bbox bbox-json
insert-quat insert-kos k-list warnung-json warnung-eintrag
zuordnung-fix-json zuordnung-fix-eintrag sepliste-json sepliste-app sepliste
sepliste-teile sepliste-first)
sepliste-teile sepliste-first sepliste-ids sep-id)
(setq ed (entget ename))
(setq blk-name (cdr (assoc 2 ed)))
(setq layer (cdr (assoc 8 ed)))
@@ -765,14 +765,24 @@
(setq sepliste (ssg-sepliste-xdata-lesen ename sepliste-app)))
(if sepliste
(progn
;; ID des Bauteils, das zu diesem Eintrag gehoert: beim Export wird
;; fuer JEDEN Eintrag (AS/SEP/ES) eine echte, temporaere Kopie des
;; verpackten Originals eingefuegt und ueber ssg-id-check-all
;; nummeriert; csv:sepliste-ids-setzen haengt die vergebene ID an den
;; 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.
(setq sepliste-ids (cdr (assoc handle *csv-sepliste-id-by-handle*)))
(setq sepliste-teile "" sepliste-first t)
(foreach e sepliste
(setq sep-id (cdr (assoc (nth 1 e) sepliste-ids)))
(setq sepliste-teile
(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)
)
@@ -818,6 +828,31 @@
"Separator_SP_2D,Separator_SP_3D"
)
;; --- Muster der verpackten AS-/ES-Schleuselemente ---
;; AS_Element_*/ES_Element_* sind die beiden Kettenenden einer VF_n/GF_n-
;; Strecke (Ein-/Ausschleusung zum Kreisel). Sie stecken - wie die Separator-
;; Symbole - als Sub-INSERTs in der Wrapper-Blockdefinition und sind damit
;; fuer (ssget "X" ...) unsichtbar. Dieses Muster findet sie in der
;; Blockdefinition (csv:nested-welt-records); gesetzt wird an ihrer Stelle
;; NICHT eine Kopie des Elements, sondern der dauerhafte Marker
;; ZZ_ASES_MARKER (csv:asmark-aktualisieren).
(defun csv:pattern-nested-schleus ()
(export:pattern "pattern_schleuselement" "AS_Element*,ES_Element*")
)
;; --- Typmarker eines ausgepackten Originals aus seinem Blocknamen ---
;; Liefert dieselben drei Marker, die ssg-sepliste-sammeln (ssg_ks_insert.lsp)
;; in die sepliste schreibt: "AS"/"ES"/"SEP". Ueber ihn wird eine Kopie ihrem
;; sepliste-Eintrag zugeordnet (csv:sepliste-paare) - zugeordnet wird nur
;; innerhalb desselben Typs.
(defun csv:proxy-typ (bname)
(cond
((wcmatch (strcase bname) "AS_ELEMENT*") "AS")
((wcmatch (strcase bname) "ES_ELEMENT*") "ES")
(t "SEP")
)
)
;; --- Muster der Compound-("Wrapper"-)Bloecke, deren Definition ueberhaupt
;; auf verpackte Separator_SP-Symbole durchsucht wird (KR_n/KREISEL_n/
;; ECKRAD_n/VF_n/GF_n) - alle anderen INSERTs (Omniflo, eigenstaendige
@@ -835,18 +870,23 @@
(if (or (null f) (equal f 0.0 1e-12)) 1.0 f)
)
;; --- Fuer jedes verpackte Separator_SP-Symbol eine ECHTE, temporaere Kopie
;; 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 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.
;; --- Fuer jedes verpackte Original die Weltlage ermitteln und daran ein
;; echtes Objekt in den Modellraum setzen ---
;; nested-pattern = wcmatch-Muster der zu suchenden Sub-INSERTs, siehe
;; csv:pattern-nested-separator (Separator-Symbole) bzw.
;; csv:pattern-nested-schleus (AS-/ES-Schleuselemente).
;;
;; Sub-Bloecke, 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, und in
;; der Zeichnung auch nicht als eigenes Objekt anklickbar. Diese Funktion
;; liefert ihre Weltlage, ohne die Zeichnung anzufassen; was daran gesetzt
;; wird, entscheidet der Aufrufer:
;; - csv:sep-proxies-erzeugen -> temporaere Kopie des Symbols selbst
;; (wird nach dem Export wieder geloescht)
;; - csv:asmark-aktualisieren -> dauerhafter AS/ES-Marker (bleibt stehen
;; und traegt die ID ueber Laeufe hinweg)
;;
;; Die Kopie steht bewusst EXAKT auf der Weltposition/-drehung/-skalierung
;; ihres verpackten Vorbilds (frueher: +500 mm Versatz in Blockrichtung, rein
@@ -859,39 +899,24 @@
;; 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
;; u.a. bereits nutzen) - OHNE jeden einzelnen Insert-Prompt (Blockname/
;; Einfuegepunkt/Skalierung/Drehwinkel) ins Kommandozeilen-Fenster zu
;; schreiben. "command _.INSERT" wuerde das bei JEDEM verpackten Fund tun,
;; selbst wenn alle Werte per LISP vorgegeben sind - bei mehreren Funden
;; entsprechend viel Bildschirm-Rauschen. vla-get-ModelSpace wird hier LOKAL
;; ermittelt statt den (evtl. noch unbelegten) globalen "modelspace" aus
;; vf_core.lsp/Gefaellestrecke.lsp/ssg_ks_insert.lsp vorauszusetzen - die
;; 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 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
;; Rueckgabe: Liste von Records
;; (wrapper-ename nested-ent nested-bname (x y z) R sx sy sz)
;; mit der fertigen Weltlage jedes Fundes (R = volle 3x3-Orientierung).
(defun csv:nested-welt-records (nested-pattern
/ ss-all i ename ed bname nested pt rotation
extrusion0 R0 psx psy psz rec nested-ent nested-bname
local-pt R-nested R-final scaled-pt rot-pt world-pt
T4 block-obj xform-fehler proxy-liste)
records)
(setq ss-all (ssget "X" (list (cons 0 "INSERT"))))
(if ss-all
(progn
(setq ms (vla-get-ModelSpace (vla-get-ActiveDocument (vlax-get-acad-object))))
(setq i 0)
(while (setq ename (ssname ss-all i))
(setq ed (entget ename))
(setq bname (cdr (assoc 2 ed)))
(setq nested
(if (wcmatch bname (csv:pattern-wrapper-fuer-proxies))
(ssg-collect-nested-inserts bname (csv:pattern-nested-separator))
(ssg-collect-nested-inserts bname nested-pattern)
)
)
(if nested
@@ -922,50 +947,10 @@
(+ (cadr pt) (cadr rot-pt))
(+ (caddr pt) (caddr rot-pt))))
(setq R-final (mat3-mul-mat3 R0 R-nested))
;; Volle 3D-Orientierung statt bisher nur eines Z-Drehwinkels:
;; ein einzelner Skalar-Drehwinkel kann eine Kippung ausserhalb
;; der XY-Ebene (Gefaellestrecke-/VF-Neigung) gar nicht
;; darstellen. Deshalb - wie insert-block-ks-to-ks (vf_core.lsp)
;; - am Ursprung mit Rotation=0 einfuegen und die komplette
;; Lage (Drehung UND Verschiebung) in EINER vla-TransformBy
;; anwenden, statt Rotation als vla-InsertBlock-Parameter.
(setq T4 (vlax-tmatrix
(list (list (car (car R-final)) (cadr (car R-final)) (caddr (car R-final)) (car world-pt))
(list (car (cadr R-final)) (cadr (cadr R-final)) (caddr (cadr R-final)) (cadr world-pt))
(list (car (caddr R-final)) (cadr (caddr R-final)) (caddr (caddr R-final)) (caddr world-pt))
(list 0.0 0.0 0.0 1.0))))
;; 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 '(0.0 0.0 0.0)) 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)))
0.0)))
(cond
((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)))))
(t
(setq xform-fehler (vl-catch-all-apply 'vla-TransformBy (list block-obj T4)))
(if (vl-catch-all-error-p xform-fehler)
(progn
(princ (ssg-textf "exp-sep-proxy-fehler"
(list nested-bname bname
(vl-catch-all-error-message xform-fehler))))
(vl-catch-all-apply 'vla-Delete (list block-obj))
)
(setq proxy-liste
(cons (cons (vlax-vla-object->ename block-obj) ename) proxy-liste))
)
)
)
(setq records
(cons (list ename nested-ent nested-bname world-pt R-final
(* psx (nth 3 rec)) (* psy (nth 4 rec)) (* psz (nth 5 rec)))
records))
)
)
)
@@ -973,7 +958,91 @@
)
)
)
proxy-liste
(reverse records)
)
;; --- Einen Block an einer vollen 3D-Weltlage einfuegen ---
;; ms = ModelSpace-Objekt (der Aufrufer holt es EINMAL, nicht je Block)
;; bname = einzufuegende Blockdefinition
;; world-pt/R = Weltposition und volle 3x3-Orientierung (siehe
;; csv:nested-welt-records)
;; sx/sy/sz = Skalierung; der AS/ES-Marker fuegt bewusst mit 1.0 ein (ein
;; gestauchtes Rechteck waere als Marker unbrauchbar), die
;; Separator-Kopie mit der tatsaechlichen Skalierung ihres
;; Vorbilds.
;; kontext = Blockname des Wrappers, nur fuer die Fehlermeldung.
;;
;; Volle 3D-Orientierung statt eines einzelnen Z-Drehwinkels: ein Skalar-
;; Drehwinkel kann eine Kippung ausserhalb der XY-Ebene (Gefaellestrecke-/
;; VF-Neigung) gar nicht darstellen. Deshalb - wie insert-block-ks-to-ks
;; (vf_core.lsp) - am Ursprung mit Rotation=0 einfuegen und die komplette Lage
;; (Drehung UND Verschiebung) in EINER vla-TransformBy anwenden.
;;
;; Einfuegen ueber vla-InsertBlock (ActiveX) statt "command _.INSERT": das legt
;; fuer einen Block mit ATTDEFs automatisch ATTRIB-Entities mit den Vorgabe-
;; werten an - OHNE jeden einzelnen Insert-Prompt (Blockname/Einfuegepunkt/
;; Skalierung/Drehwinkel) ins Kommandozeilen-Fenster zu schreiben.
;;
;; Beide ActiveX-Aufrufe sind gekapselt: ein einzelner Sonderfall (entartete
;; Skalierung, gespiegelter Zwischenblock) darf nicht den kompletten Export der
;; Zeichnung abbrechen, sondern nur dieses eine Objekt ausfallen lassen - mit
;; Meldung, welcher Block. Rueckgabe: ename des neuen INSERT oder nil.
(defun csv:block-einfuegen-welt (ms bname world-pt R sx sy sz kontext
/ T4 block-obj xform-fehler erg)
(setq T4 (vlax-tmatrix
(list (list (car (car R)) (cadr (car R)) (caddr (car R)) (car world-pt))
(list (car (cadr R)) (cadr (cadr R)) (caddr (cadr R)) (cadr world-pt))
(list (car (caddr R)) (cadr (caddr R)) (caddr (caddr R)) (caddr world-pt))
(list 0.0 0.0 0.0 1.0))))
(setq block-obj
(vl-catch-all-apply 'vla-InsertBlock
(list ms (vlax-3D-point '(0.0 0.0 0.0)) bname
(csv:skalierung-oder-eins sx)
(csv:skalierung-oder-eins sy)
(csv:skalierung-oder-eins sz)
0.0)))
(cond
((vl-catch-all-error-p block-obj)
(princ (ssg-textf "exp-sep-proxy-fehler"
(list bname kontext (vl-catch-all-error-message block-obj))))
(setq erg nil))
(t
(setq xform-fehler (vl-catch-all-apply 'vla-TransformBy (list block-obj T4)))
(if (vl-catch-all-error-p xform-fehler)
(progn
(princ (ssg-textf "exp-sep-proxy-fehler"
(list bname kontext (vl-catch-all-error-message xform-fehler))))
(vl-catch-all-apply 'vla-Delete (list block-obj))
(setq erg nil))
(setq erg (vlax-vla-object->ename block-obj))
)
)
)
erg
)
;; --- Temporaere Kopie je verpacktem Separator_SP-Symbol ---
;; Setzt csv:nested-welt-records + csv:block-einfuegen-welt zusammen: fuer
;; jeden Fund eine ECHTE Kopie DESSELBEN Blocks an derselben Weltlage.
;; Die AS-/ES-Kettenenden laufen NICHT mehr hierueber - fuer sie setzt
;; csv:asmark-aktualisieren einen dauerhaften Marker (siehe dort).
(defun csv:sep-proxies-erzeugen ( / ms recs rec proxy proxy-liste)
(setq recs (csv:nested-welt-records (csv:pattern-nested-separator)))
(if recs
(progn
(setq ms (vla-get-ModelSpace (vla-get-ActiveDocument (vlax-get-acad-object))))
(foreach rec recs
(setq proxy (csv:block-einfuegen-welt ms (nth 2 rec) (nth 3 rec) (nth 4 rec)
(nth 5 rec) (nth 6 rec) (nth 7 rec)
(cdr (assoc 2 (entget (nth 0 rec))))))
(if proxy
(setq proxy-liste
(cons (list proxy (nth 0 rec) (csv:proxy-typ (nth 2 rec)) (nth 3 rec))
proxy-liste)))
)
)
)
(reverse proxy-liste)
)
;; --- ZUORDNUNG der temporaeren Separator-Kopien aus ihrem Wrapper setzen ---
@@ -991,14 +1060,15 @@
;; 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
;; proxy-liste = Records (proxy-ename wrapper-ename typ (x y z)), siehe
;; csv:nested-proxies-erzeugen. Rueckgabe: Anzahl festgenagelter Kopien.
(defun csv:sep-proxies-zuordnung-setzen (proxy-liste / rec 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))
(foreach rec proxy-liste
(setq proxy (nth 0 rec))
(setq wrapper (nth 1 rec))
(setq wrapper-id (cdr (assoc "ID" (ssg-attrib-read wrapper))))
(if (and wrapper-id (> (strlen wrapper-id) 0))
(progn
@@ -1020,107 +1090,507 @@
gesetzt
)
;; --- Fuer jeden Sepliste-Eintrag (AS/SEP/ES) eines VF_n/GF_n-Wrappers eine
;; ECHTE, temporaere Separator_SP-Kopie an dessen Weltkoordinate einfuegen ---
;; --- Globale Zuordnung sepliste-Eintrag -> ID der zugehoerigen Kopie ---
;; Key = Handle des VF_n/GF_n-Wrappers, Wert = Alist ((lfdnr . "0031") ...).
;; Wird von csv:sepliste-ids-setzen NACH der ID-Vergabe gefuellt, von
;; csv:block-to-json beim Schreiben des "sepliste"-Arrays gelesen und von
;; csv:sep-proxies-loeschen wieder zurueckgesetzt - die IDs gehoeren den
;; temporaeren Kopien dieses EINEN Exportlaufs.
(setq *csv-sepliste-id-by-handle* nil)
;; --- Einen sepliste-Eintrag mit der ID seines Bauteils vermerken ---
;; Zwei Quellen schreiben in dieselbe Zuordnung - die dauerhaften AS/ES-Marker
;; (csv:asmark-ids-setzen) und die temporaeren Separator-Kopien
;; (csv:sepliste-ids-setzen). Beide muessen sich den Eintrag EINES Wrappers
;; teilen: ein zweites Paar (handle . alist) waere fuer assoc unsichtbar, die
;; zuerst eingetragene Liste wuerde immer gewinnen. Darum hier zusammenfuehren
;; statt vorne anzuhaengen.
(defun csv:sepliste-id-merken (handle lfdnr id / eintrag)
(setq eintrag (assoc handle *csv-sepliste-id-by-handle*))
(if eintrag
(setq *csv-sepliste-id-by-handle*
(subst (cons handle (append (cdr eintrag) (list (cons lfdnr id))))
eintrag *csv-sepliste-id-by-handle*))
(setq *csv-sepliste-id-by-handle*
(cons (cons handle (list (cons lfdnr id))) *csv-sepliste-id-by-handle*))
)
id
)
;; --- Abstands-Schwelle (mm) fuer die Meldung einer auffaelligen Zuordnung ---
;; Oberhalb dieses Abstands zwischen sepliste-Koordinate und tatsaechlicher
;; Lage der zugehoerigen Kopie wird gemeldet (die Zuordnung selbst bleibt
;; gueltig, siehe csv:sepliste-paare). Grosszuegig angesetzt: der SEP-Eintrag
;; nennt den Einfuegepunkt des Trennstegs (Staustrecke_Separator_SP_300_mm),
;; die Kopie das darin steckende Sensor-Symbol - ein Versatz in dieser
;; Groessenordnung ist normal, benachbarte Kettenglieder liegen dagegen
;; deutlich weiter auseinander (Staustrecken-Module ab 500 mm).
(defun csv:sepliste-abstand-warnung-mm () 500.0)
;; --- sepliste-Eintraege und ausgepackte Kopien einander zuordnen ---
;; eintraege = ((typ lfdnr x y) ...) wie ssg-sepliste-xdata-lesen sie liefert.
;; proxies = ((typ x y) ...) - Reihenfolge beliebig, der INDEX in dieser
;; Liste (0-basiert) ist der Schluessel im Ergebnis.
;; Rueckgabe: ((lfdnr index abstand) ...), aufsteigend nach lfdnr; Eintraege
;; ohne Partner fehlen darin.
;;
;; Hintergrund: Seit die in einer VF_n/GF_n-Kette liegenden Separatoren (und die
;; AS/ES-Elemente) beim Bau nur noch als Koordinaten-Eintraege in der
;; "sepliste"-XDATA gefuehrt werden (ssg-sepliste-sammeln, ssg_ks_insert.lsp),
;; gibt es fuer sie KEIN reales INSERT mehr in der Zeichnung - vla-getboundingbox
;; kann also keine Box liefern, und der frueher genutzte Proxy-Mechanismus
;; csv:sep-proxies-erzeugen findet nichts (der sucht VERPACKTE Separator_SP-
;; Sub-Bloecke, die es nicht mehr gibt). Ohne Box fielen diese Separatoren aus
;; der Nachbarschafts-/Umlauf-/Zuordnungserkennung des Python-Exports.
;; Zugeordnet wird nur innerhalb desselben Typmarkers (AS/ES/SEP) und
;; EXKLUSIV: alle Paare eines Typs werden nach Abstand sortiert, das kuerzeste
;; zuerst genommen, danach sind Eintrag UND Kopie verbraucht. Das ist bewusst
;; nicht "je Eintrag die naechste Kopie" (so macht es map_separator_kette_items
;; in export_csv.py): waehlen zwei Eintraege dieselbe Kopie, bleibt eine Kopie
;; ohne Eintrag und ein Eintrag ohne ID. Die exklusive Variante ist ausserdem
;; gegen EINEN fehlerhaften Koordinatenwert unempfindlich - alle uebrigen
;; Eintraege finden ihre Kopie ueber den kurzen Abstand zuerst, der fehlerhafte
;; bekommt den Rest zugeteilt (mit entsprechend grossem Abstand, den
;; csv:sepliste-ids-setzen meldet).
;;
;; Diese Funktion schliesst die Luecke analog zu csv:sep-proxies-erzeugen: fuer
;; JEDEN Sepliste-Eintrag (typ SEP, AS ODER ES) wird ein echter, top-level
;; Separator_SP-INSERT der aktuellen Dimension an die gespeicherte Weltposition
;; (x y) gesetzt. Diese Kopie durchlaeuft danach unveraendert ID-Vergabe und
;; Export (Blockname-Muster Separator_SP* trifft zu) und liefert per
;; vla-getboundingbox die echte Symbol-Box. AS/ES bekommen dieselbe Symbol-Box
;; wie ein SEP - fuer die Nachbarschaftserkennung genuegt das; die AS/ES-
;; Sonderrolle (Kettenende am Kreisel) ergibt sich im Python-Teil aus der
;; sepliste-Reihenfolge, nicht aus der Boxgroesse.
;; Die Sortierung ist eine STRIKTE Totalordnung (Abstand, dann lfdnr, dann
;; Index): vl-sort wirft Elemente weg, die unter der Vergleichsfunktion als
;; gleich gelten - zwei Paare mit identischem Abstand wuerden sonst zu einem
;; verschmelzen und ein Eintrag bliebe ohne ID.
;;
;; Die Drehung des Symbols ist in der sepliste NICHT gespeichert (nur typ/lfdnr/
;; x/y) - naeherungsweise die Wrapper-Drehung uebernehmen (bei ~210x150mm
;; Symbol und 200mm Nachbarschaftstoleranz unkritisch). Einfuegen ueber
;; vla-InsertBlock (kein Kommandozeilen-Prompt, legt ATTRIBs mit Vorgabewerten
;; an - identisch zu csv:sep-proxies-erzeugen).
;; Reine Rechenfunktion (keine Zeichnung, keine Datei) - getestet in
;; tests/test_unit.lsp (tu-tests-export, Testfaelle "csv:sepliste-paare/*").
(defun csv:sepliste-paare (eintraege proxies / kandidaten e p i dx dy
benutzt-e benutzt-p erg)
(setq kandidaten nil)
(foreach e eintraege
(setq i 0)
(foreach p proxies
(if (= (nth 0 e) (nth 0 p))
(progn
(setq dx (- (nth 2 e) (nth 1 p)))
(setq dy (- (nth 3 e) (nth 2 p)))
(setq kandidaten
(cons (list (sqrt (+ (* dx dx) (* dy dy))) (nth 1 e) i) kandidaten))
)
)
(setq i (1+ i))
)
)
(setq kandidaten
(vl-sort kandidaten
(function (lambda (a b)
(cond
((/= (nth 0 a) (nth 0 b)) (< (nth 0 a) (nth 0 b)))
((/= (nth 1 a) (nth 1 b)) (< (nth 1 a) (nth 1 b)))
(t (< (nth 2 a) (nth 2 b)))
)))))
(setq erg nil benutzt-e nil benutzt-p nil)
(foreach k kandidaten
(if (and (not (member (nth 1 k) benutzt-e))
(not (member (nth 2 k) benutzt-p)))
(progn
(setq benutzt-e (cons (nth 1 k) benutzt-e))
(setq benutzt-p (cons (nth 2 k) benutzt-p))
(setq erg (cons (list (nth 1 k) (nth 2 k) (nth 0 k)) erg))
)
)
)
(vl-sort erg (function (lambda (a b) (< (car a) (car b)))))
)
;; --- Nach der ID-Vergabe: ID jeder Kopie an ihren sepliste-Eintrag haengen ---
;; proxy-liste = Records (proxy-ename wrapper-ename typ (x y z)) aus
;; csv:sep-proxies-erzeugen UND csv:schleus-proxies-erzeugen - beide Listen
;; zusammen uebergeben, AS/ES und SEP stehen in DERSELBEN sepliste.
;; MUSS nach ssg-id-check-all laufen: vorher hat eine frisch erzeugte Kopie
;; noch keine ID.
;;
;; Rueckgabe: Liste von Paaren (proxy-ename . wrapper-ename) - gleiches Format
;; wie csv:sep-proxies-erzeugen, sodass csv:sep-proxies-zuordnung-setzen (setzt
;; ZUORDNUNG = Wrapper-ID) und csv:sep-proxies-loeschen unveraendert wirken.
(defun csv:sepliste-proxies-erzeugen ( / ss-all ms i ename ed bname sepliste-app
sepliste blk-eff rotation wpt eintrag
block-obj xform-fehler T4 cosr sinr
proxy-liste)
(setq ss-all (ssget "X" (list (cons 0 "INSERT"))))
(if (and ss-all (car (atoms-family 1 '("SSG-SEPLISTE-XDATA-LESEN"))))
(progn
(setq ms (vla-get-ModelSpace (vla-get-ActiveDocument (vlax-get-acad-object))))
(setq i 0)
(while (setq ename (ssname ss-all i))
(setq ed (entget ename))
(setq bname (cdr (assoc 2 ed)))
(setq sepliste-app
(cond
((wcmatch (strcase bname) "VF_*") "SSG_VF_SEP")
((wcmatch (strcase bname) "GF_*") "SSG_GF_SEP")
(t nil)))
(setq sepliste (if sepliste-app (ssg-sepliste-xdata-lesen ename sepliste-app)))
(if sepliste
(progn
;; Symbol-Blockdefinition der aktuellen Dimension sicherstellen.
(setq blk-eff (ssg-ils-block-laden "Separator_SP"))
(setq rotation (cond ((cdr (assoc 50 ed))) (0.0)))
(setq cosr (cos rotation) sinr (sin rotation))
(if blk-eff
(foreach eintrag sepliste
;; (typ lfdnr x y) - Weltkoordinate direkt aus der Sepliste.
(setq wpt (list (nth 2 eintrag) (nth 3 eintrag) 0.0))
(setq block-obj
(vl-catch-all-apply 'vla-InsertBlock
(list ms (vlax-3D-point '(0.0 0.0 0.0)) blk-eff 1.0 1.0 1.0 0.0)))
(cond
((vl-catch-all-error-p block-obj)
(princ (ssg-textf "exp-sep-proxy-fehler"
(list blk-eff bname
(vl-catch-all-error-message block-obj)))))
(t
;; Rz(rotation) + Verschiebung an die Sepliste-Weltposition
;; in EINER TransformBy (wie csv:sep-proxies-erzeugen).
(setq T4 (vlax-tmatrix
(list (list cosr (- sinr) 0.0 (car wpt))
(list sinr cosr 0.0 (cadr wpt))
(list 0.0 0.0 1.0 (caddr wpt))
(list 0.0 0.0 0.0 1.0))))
(setq xform-fehler (vl-catch-all-apply 'vla-TransformBy (list block-obj T4)))
(if (vl-catch-all-error-p xform-fehler)
(progn
(princ (ssg-textf "exp-sep-proxy-fehler"
(list blk-eff bname
(vl-catch-all-error-message xform-fehler))))
(vl-catch-all-apply 'vla-Delete (list block-obj)))
(setq proxy-liste
(cons (cons (vlax-vla-object->ename block-obj) ename) proxy-liste)))))
)
;; Ergebnis steht in *csv-sepliste-id-by-handle*; csv:block-to-json schreibt
;; es als Feld "id" in jeden sepliste-Eintrag. Damit muss der Python-Teil die
;; Kette nicht mehr ueber Koordinaten-Naehe auf reale Teile zurueckrechnen -
;; die Zuordnung steht bereits im Export.
;; Rueckgabe: Anzahl der Eintraege, die eine ID bekommen haben.
(defun csv:sepliste-ids-setzen (proxy-liste / wrapper-enames rec wrapper ed bname
handle app sepliste eigene proxies paare paar
proxy-rec id typen e relevant gesetzt ohne-id fehlend)
(setq gesetzt 0 ohne-id 0)
;; Wrapper einsammeln (jeder nur einmal, in Reihenfolge seiner ersten Kopie).
(setq wrapper-enames nil)
(foreach rec proxy-liste
(if (not (member (nth 1 rec) wrapper-enames))
(setq wrapper-enames (cons (nth 1 rec) wrapper-enames))))
(foreach wrapper (reverse wrapper-enames)
(setq ed (entget wrapper))
(setq bname (cdr (assoc 2 ed)))
(setq handle (cdr (assoc 5 ed)))
(setq app
(cond
((wcmatch (strcase bname) "VF_*") "SSG_VF_SEP")
((wcmatch (strcase bname) "GF_*") "SSG_GF_SEP")
(t nil)))
(setq sepliste
(if (and app (car (atoms-family 1 '("SSG-SEPLISTE-XDATA-LESEN"))))
(ssg-sepliste-xdata-lesen wrapper app)))
(if sepliste
(progn
;; Nur die Kopien DIESES Wrappers, in Erzeugungsreihenfolge - der Index
;; im Ergebnis von csv:sepliste-paare zeigt in genau diese Liste.
(setq eigene nil)
(foreach rec proxy-liste
(if (eq (nth 1 rec) wrapper) (setq eigene (cons rec eigene))))
(setq eigene (reverse eigene))
(setq proxies
(mapcar (function (lambda (r) (list (nth 2 r) (car (nth 3 r)) (cadr (nth 3 r)))))
eigene))
(setq paare (csv:sepliste-paare sepliste proxies))
(foreach paar paare
(setq proxy-rec (nth (nth 1 paar) eigene))
(setq id (cdr (assoc "ID" (ssg-attrib-read (nth 0 proxy-rec)))))
(if (and id (> (strlen id) 0))
(progn
(csv:sepliste-id-merken handle (nth 0 paar) id)
(setq gesetzt (1+ gesetzt))
)
(setq ohne-id (1+ ohne-id))
)
;; Auffaellig weit auseinander: die sepliste-Koordinate passt nicht
;; zur tatsaechlichen Lage des Bauteils (bekannter Fehlerfall:
;; gespiegelte x-Koordinate beim Bau). Die Zuordnung bleibt gueltig -
;; sie entsteht exklusiv, nicht ueber diesen Abstand -, wird aber
;; gemeldet, damit die Ursache nicht unbemerkt bleibt.
(if (> (nth 2 paar) (csv:sepliste-abstand-warnung-mm))
(princ (ssg-textf "exp-sepliste-abstand"
(list bname (nth 2 proxy-rec) (itoa (nth 0 paar))
(rtos (nth 2 paar) 2 0))))
)
)
;; Fehlende Partner NUR unter den Typen zaehlen, die diese Liste
;; ueberhaupt bedient: AS/ES kommen hier nicht vor (dafuer gibt es die
;; Marker, siehe csv:asmark-ids-setzen), ihre Eintraege waeren sonst
;; jedes Mal als "ohne Kopie" gemeldet.
(setq typen nil)
(foreach rec eigene
(if (not (member (nth 2 rec) typen)) (setq typen (cons (nth 2 rec) typen))))
(setq relevant 0)
(foreach e sepliste
(if (member (nth 0 e) typen) (setq relevant (1+ relevant))))
(setq fehlend (- relevant (length paare)))
(if (> fehlend 0)
(princ (ssg-textf "exp-sepliste-ohne-proxy" (list bname (itoa fehlend)))))
)
)
)
(if (> gesetzt 0)
(princ (ssg-textf "exp-sepliste-ids" (list (itoa gesetzt)))))
(if (> ohne-id 0)
(princ (ssg-textf "exp-sepliste-ohne-id" (list (itoa ohne-id)))))
gesetzt
)
;; ===========================================================================
;; AS-/ES-Marker (ZZ_ASES_MARKER)
;; ===========================================================================
;; Ein kleines Rechteck, das der Export an JEDES AS-/ES-Ende einer VF_n/GF_n-
;; Kette setzt und das dort DAUERHAFT stehen bleibt. Es ist zweierlei:
;;
;; Sichtbarmachung - die Kettenenden stecken verpackt in der Wrapper-
;; Blockdefinition (AS_Element_*/ES_Element_*) und sind in der Zeichnung
;; sonst weder anklickbar noch als eigenes Objekt erkennbar.
;; ID-Traeger - das (unsichtbare) ID-Attribut des Markers fuellt
;; ssg-id-check-all, und der naechste Export VERWENDET DENSELBEN MARKER
;; WIEDER. Ein AS/ES behaelt seine TeileId dadurch ueber Exportlaeufe
;; hinweg - anders als die temporaeren Separator-Kopien, die bei jedem Lauf
;; neu entstehen und neue IDs bekommen.
;;
;; Der Marker ist abgeleitete Geometrie: csv:asmark-aktualisieren baut bei
;; JEDEM Export alle Marker neu auf (Lage folgt damit automatisch einer
;; umgebauten Kette) und uebernimmt dabei nur die ID des bisherigen Markers.
;; Nicht wiederverwendete Marker werden geloescht - ein Marker ohne Kette
;; bleibt also nicht als Karteileiche stehen.
(defun csv:asmark-blockname () "ZZ_ASES_MARKER")
;; Kantenlaenge (mm) des Rechtecks, aus cfg/export.cfg [Marker].
(defun csv:asmark-groesse-mm ()
(atof (export:cfg "Marker" "ases_groesse_mm" "200"))
)
;; Attribut-Schema des Markers. Alle ATTDEFs sind UNSICHTBAR
;; (ssg-attrib-make-defs setzt Flag 70=1) - sichtbar ist nur das Rechteck.
;; ID - fuellt ssg-id-check-all; wird beim naechsten Export uebernommen
;; ZUORDNUNG - ID des VF_n/GF_n-Wrappers, zu dem das Kettenende gehoert
;; (zugleich der Schluessel, ueber den der Marker wiedergefunden
;; wird, siehe csv:asmark-finden)
;; TYP - "AS" oder "ES", wie in der sepliste (ssg-sepliste-sammeln)
;; LFDNR - lfdnr des zugehoerigen sepliste-Eintrags
;; ELEMENT - Blockname des echten Schleuselements (AS_Element_90_links_3D):
;; haelt die Variante (30/90, links/rechts, 2D/3D) fest, die der
;; Marker selbst nicht mehr zeigt
;; ARTINR - Sivas-Nummer aus dem Attribut des echten Elements
(setq *csv-asmark-attrib-defs*
'(("ID" "") ("ZUORDNUNG" "") ("TYP" "") ("LFDNR" "") ("ELEMENT" "") ("ARTINR" ""))
)
;; --- Blockdefinition des Markers bei Bedarf anlegen ---
;; Es gibt bewusst KEINE DWG dafuer: der Marker ist reine Hilfsgeometrie und
;; soll in jeder Zeichnung ohne Blockbibliothek entstehen koennen. Aufbau wie
;; bei den Geruest-Baugruppen (GeruestInsert.lsp): Geometrie + ATTDEFs in den
;; Modellraum zeichnen, alles seit lastEnt einsammeln, per "_.-BLOCK" zur
;; Definition machen (das entfernt die Vorlage-Entities wieder).
;; Die Geometrie liegt auf Layer "0", damit der Marker den Layer seiner
;; Blockreferenz annimmt (den setzt csv:asmark-aktualisieren aus cfg/layer.cfg).
;;
;; WICHTIG - der Auswahlsatz nimmt NUR die beiden hier erzeugten Entity-Typen
;; auf: (entlast) liefert bei einer Blockreferenz MIT Attributen die Referenz
;; selbst, und (entnext) steigt von dort in deren ATTRIB-Unterentities ab. Ohne
;; Filter landen also die Attribute des zuletzt eingefuegten Blocks (z.B. eines
;; gerade gebauten VF_n) mit im Auswahlsatz - "_.-BLOCK" verschiebt sie in die
;; neue Blockdefinition und der Block steht danach OHNE Attribute da (real
;; beobachtet: VF_5 verlor beim ersten Export saemtliche Attribute, inklusive
;; seiner ID).
;; Rueckgabe: Blockname oder nil.
(defun csv:asmark-blockdef-sicherstellen ( / name h lastEnt ss e typ)
(setq name (csv:asmark-blockname))
(if (tblsearch "BLOCK" name)
name
(progn
(setq h (/ (csv:asmark-groesse-mm) 2.0))
(setq lastEnt (entlast))
(entmake (list '(0 . "LWPOLYLINE") '(100 . "AcDbEntity") '(8 . "0")
'(100 . "AcDbPolyline") '(90 . 4) '(70 . 1)
(cons 10 (list (- h) (- h)))
(cons 10 (list h (- h)))
(cons 10 (list h h))
(cons 10 (list (- h) h))))
(ssg-attrib-make-defs *csv-asmark-attrib-defs* (/ h 2.0))
(setq ss (ssadd))
(setq e (if lastEnt (entnext lastEnt) (entnext)))
(while e
(setq typ (cdr (assoc 0 (entget e))))
(if (member typ '("LWPOLYLINE" "ATTDEF")) (ssadd e ss))
(setq e (entnext e))
)
(command "_.-BLOCK" name (list 0.0 0.0 0.0) ss "")
(if (tblsearch "BLOCK" name)
(progn (princ (ssg-textf "exp-asmark-blockdef" (list name))) name)
(progn (princ (ssg-textf "exp-asmark-blockdef-fehler" (list name))) nil)
)
)
)
)
;; --- Vorhandene Marker der Zeichnung einsammeln ---
;; Rueckgabe: Liste von (ename zuordnung typ lfdnr id), alle Werte als String
;; (fehlende Attribute als "").
(defun csv:asmark-sammeln ( / ss i ename attribs out)
(setq ss (ssget "X" (list (cons 0 "INSERT") (cons 2 (csv:asmark-blockname)))))
(if ss
(progn
(setq i 0)
(while (setq ename (ssname ss i))
(setq attribs (ssg-attrib-read ename))
(setq out
(cons (list ename
(cond ((cdr (assoc "ZUORDNUNG" attribs))) (""))
(cond ((cdr (assoc "TYP" attribs))) (""))
(cond ((cdr (assoc "LFDNR" attribs))) (""))
(cond ((cdr (assoc "ID" attribs))) ("")))
out))
(setq i (1+ i))
)
)
)
proxy-liste
(reverse out)
)
;; --- Temporaere Separator_SP-Kopien (csv:sep-proxies-erzeugen /
;; csv:sepliste-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 paar proxy-liste (entdel (car paar)))
;; --- Den Marker eines Kettenendes im Bestand suchen ---
;; Schluessel: Wrapper-ID + Typ + lfdnr (alles Strings). Bewusst NICHT ueber
;; die Position: eine Kette, die verschoben oder umgebaut wurde, soll ihre
;; TeileIds behalten. Ein Wrapper OHNE ID (frisch gebaut, ssg-id-check-all lief
;; noch nicht) findet folgerichtig nichts - seine Marker entstehen neu; die
;; ZUORDNUNG wird nach der ID-Vergabe nachgetragen (csv:asmark-ids-setzen),
;; sodass es beim naechsten Export passt.
(defun csv:asmark-finden (bestand zuordnung typ lfdnr / m treffer)
(foreach m bestand
(if (and (null treffer)
(= (nth 1 m) zuordnung)
(= (nth 2 m) typ)
(= (nth 3 m) lfdnr))
(setq treffer m)
)
)
treffer
)
;; --- Alle AS-/ES-Marker der Zeichnung neu aufbauen ---
;; Laeuft VOR ssg-id-check-all (csv:export-vorbereiten): neue Marker haben dann
;; noch kein ID-Attribut und werden dort nummeriert, uebernommene tragen die ID
;; ihres Vorgaengers und behalten sie.
;;
;; Ablauf je VF_n/GF_n-Wrapper mit sepliste:
;; 1. Weltlage aller verpackten AS-/ES-Elemente holen (csv:nested-welt-records)
;; 2. sie den AS-/ES-Eintraegen der sepliste zuordnen (csv:sepliste-paare -
;; dieselbe exklusive Zuordnung wie bei den Separator-Kopien; SEP-Eintraege
;; finden hier keinen Partner und bleiben unberuehrt)
;; 3. je Paar den bisherigen Marker suchen, seine ID merken, ihn loeschen und
;; an der AKTUELLEN Lage neu setzen
;; Was danach im Bestand uebrig ist, gehoert zu keiner Kette mehr und wird
;; geloescht.
;;
;; Rueckgabe: Liste von Records (marker-ename wrapper-ename typ lfdnr) fuer
;; csv:asmark-ids-setzen.
(defun csv:asmark-aktualisieren ( / recs bestand blockname ms wrapper-enames rec
wrapper ed bname wrapper-id app sepliste eigene
proxies paare paar elem lfdnr typ alt alt-id
mark artinr m neu uebernommen verwaist ergebnis)
(setq neu 0 uebernommen 0 verwaist 0)
(setq recs (csv:nested-welt-records (csv:pattern-nested-schleus)))
(setq bestand (csv:asmark-sammeln))
(setq blockname (if recs (csv:asmark-blockdef-sicherstellen)))
(if blockname
(progn
(setq ms (vla-get-ModelSpace (vla-get-ActiveDocument (vlax-get-acad-object))))
(setq wrapper-enames nil)
(foreach rec recs
(if (not (member (nth 0 rec) wrapper-enames))
(setq wrapper-enames (cons (nth 0 rec) wrapper-enames))))
(foreach wrapper (reverse wrapper-enames)
(setq ed (entget wrapper))
(setq bname (cdr (assoc 2 ed)))
(setq wrapper-id (cond ((cdr (assoc "ID" (ssg-attrib-read wrapper)))) ("")))
(setq app
(cond
((wcmatch (strcase bname) "VF_*") "SSG_VF_SEP")
((wcmatch (strcase bname) "GF_*") "SSG_GF_SEP")
(t nil)))
(setq sepliste
(if (and app (car (atoms-family 1 '("SSG-SEPLISTE-XDATA-LESEN"))))
(ssg-sepliste-xdata-lesen wrapper app)))
(if sepliste
(progn
(setq eigene nil)
(foreach rec recs
(if (eq (nth 0 rec) wrapper) (setq eigene (cons rec eigene))))
(setq eigene (reverse eigene))
(setq proxies
(mapcar (function (lambda (r)
(list (csv:proxy-typ (nth 2 r)) (car (nth 3 r)) (cadr (nth 3 r)))))
eigene))
(setq paare (csv:sepliste-paare sepliste proxies))
(foreach paar paare
(setq lfdnr (nth 0 paar))
(setq elem (nth (nth 1 paar) eigene))
(setq typ (csv:proxy-typ (nth 2 elem)))
(setq alt (csv:asmark-finden bestand wrapper-id typ (itoa lfdnr)))
(setq alt-id "")
(if alt
(progn
(setq alt-id (nth 4 alt))
(entdel (nth 0 alt))
(setq bestand (vl-remove alt bestand))
(setq uebernommen (1+ uebernommen))
)
(setq neu (1+ neu))
)
;; Marker mit Skalierung 1.0 einfuegen - die Skalierung des
;; Elements gehoert dem Element, ein gestauchtes Marker-Rechteck
;; waere als Positionszeichen unbrauchbar. Orientierung folgt
;; dagegen dem Element (auch bei geneigter Kette).
(setq mark (csv:block-einfuegen-welt ms blockname (nth 3 elem) (nth 4 elem)
1.0 1.0 1.0 bname))
(if mark
(progn
;; ARTINR nur lesen, wenn der Sub-INSERT ueberhaupt ATTRIBs
;; fuehrt (Gruppe 66 = 1): ssg-attrib-read wandert per entnext
;; bis zum SEQEND - fehlt der, laeuft es ueber das Element
;; hinaus durch die restliche Blockdefinition und liefert das
;; ARTINR IRGENDEINES spaeteren Bausteins der Kette.
(setq artinr "")
(if (equal 1 (cdr (assoc 66 (entget (nth 1 elem)))))
(setq artinr
(cond ((cdr (assoc "ARTINR" (ssg-attrib-read (nth 1 elem))))) (""))))
(ssg-attrib-set-on mark
(list (cons "ID" alt-id)
(cons "ZUORDNUNG" wrapper-id)
(cons "TYP" typ)
(cons "LFDNR" (itoa lfdnr))
(cons "ELEMENT" (nth 2 elem))
(cons "ARTINR" artinr)))
(ssg-layer-setzen mark "ases_marker")
(setq ergebnis (cons (list mark wrapper typ lfdnr) ergebnis))
)
)
)
)
)
)
)
)
;; Rest des Bestands: kein Kettenende mehr dahinter (Kette geloescht, Eintrag
;; entfallen, Wrapper ohne sepliste). Marker sind abgeleitete Geometrie -
;; ohne Kette bleiben sie nicht stehen.
(foreach m bestand
(entdel (nth 0 m))
(setq verwaist (1+ verwaist))
)
(if (or (> neu 0) (> uebernommen 0) (> verwaist 0))
(princ (ssg-textf "exp-asmark-bilanz"
(list (itoa neu) (itoa uebernommen) (itoa verwaist)))))
(reverse ergebnis)
)
;; --- Nach der ID-Vergabe: Marker-ID an den sepliste-Eintrag haengen ---
;; marker-liste = Records (marker-ename wrapper-ename typ lfdnr) aus
;; csv:asmark-aktualisieren. Zwei Aufgaben:
;; 1. ZUORDNUNG nachtragen - beim Setzen der Marker kann der Wrapper noch
;; keine ID gehabt haben (frisch gebaute Kette). Ohne sie faende der
;; NAECHSTE Export den Marker nicht wieder und legte ihn samt neuer ID
;; erneut an; die TeileId waere dann doch nicht stabil.
;; 2. die ID des Markers unter *csv-sepliste-id-by-handle* eintragen, damit
;; csv:block-to-json sie als Feld "id" am AS-/ES-Eintrag schreibt.
;; Rueckgabe: Anzahl der Eintraege mit ID.
(defun csv:asmark-ids-setzen (marker-liste / rec mark wrapper handle wrapper-id id
gesetzt ohne-id)
(setq gesetzt 0 ohne-id 0)
(foreach rec marker-liste
(setq mark (nth 0 rec))
(setq wrapper (nth 1 rec))
(setq handle (cdr (assoc 5 (entget wrapper))))
(setq wrapper-id (cdr (assoc "ID" (ssg-attrib-read wrapper))))
(if (and wrapper-id (> (strlen wrapper-id) 0))
(ssg-attrib-set-on mark (list (cons "ZUORDNUNG" wrapper-id))))
(setq id (cdr (assoc "ID" (ssg-attrib-read mark))))
(if (and id (> (strlen id) 0))
(progn
(csv:sepliste-id-merken handle (nth 3 rec) id)
(setq gesetzt (1+ gesetzt))
)
(setq ohne-id (1+ ohne-id))
)
)
(if (> gesetzt 0)
(princ (ssg-textf "exp-asmark-ids" (list (itoa gesetzt)))))
(if (> ohne-id 0)
(princ (ssg-textf "exp-sepliste-ohne-id" (list (itoa ohne-id)))))
gesetzt
)
;; --- Temporaere Kopien (csv:sep-proxies-erzeugen /
;; csv:schleus-proxies-erzeugen) nach dem Export wieder entfernen ---
;; proxy-liste = Records (proxy-ename wrapper-ename typ (x y z)), nur der car
;; (die Kopie selbst) wird geloescht.
;;
;; Der Debug-Schalter "csv_leave_proxies" (ssg_dbg.lsp) unterdrueckt das
;; Loeschen: die Kopien bleiben zur Ansicht in der Zeichnung stehen, damit sich
;; die tatsaechliche Lage der exportierten Separatoren gegen die
;; sepliste-Koordinaten pruefen laesst. *cs-sep-fix-by-handle* und
;; *csv-sepliste-id-by-handle* werden trotzdem zurueckgesetzt - sie gelten nur
;; fuer den gerade gelaufenen Export.
(defun csv:sep-proxies-loeschen (proxy-liste / behalten)
(setq behalten (and (car (atoms-family 1 '("DBG-SCHALTER-P")))
(dbg-schalter-p "csv_leave_proxies")))
(if behalten
(princ (strcat "\n[EXPORT] csv_leave_proxies=1: "
(itoa (length proxy-liste))
" Separator-Proxy-Kopie(n) bleiben in der Zeichnung"
" (Abschalten: (dbg-schalter-off \"csv_leave_proxies\"))"))
(foreach rec proxy-liste (entdel (car rec)))
)
(setq *cs-sep-fix-by-handle* nil)
(setq *csv-sepliste-id-by-handle* nil)
(princ)
)
@@ -1132,6 +1602,12 @@
;; Omniflo Boegen/Weichen = rein numerische Blocknamen (SivasNr)
;; Vario* = VarioFoerderer-Bloecke
;; GF_* = Gefaellestrecke Compound-Bloecke
;; ZZ_ASES_MARKER = die Marker der AS-/ES-Kettenenden einer VF_n/GF_n-Strecke
;; (siehe csv:asmark-aktualisieren; pattern_ases_marker in cfg/export.cfg).
;; Das Kettenende selbst steckt verpackt in der Wrapper-Blockdefinition und
;; waere sonst weder sichtbar noch exportierbar; der Marker vertritt es -
;; mit eigener, ueber Exportlaeufe stabiler ID und den Attributen
;; TYP/LFDNR/ELEMENT/ARTINR.
;; Separator_SP*/S-LP*, Scanner* = eigenstaendige Sensor-Bloecke (siehe
;; pattern_separator/pattern_scanner in cfg/export.cfg). Landen in
;; export_raw.json fuer BEIDE Exporte (EXPORTCSV/EXPORTSIVAS teilen sich
@@ -1170,6 +1646,7 @@
(wcmatch (strcase bname) (export:pattern "pattern_gerade" "AP110*,AP_110*"))
(wcmatch bname (export:pattern "pattern_strecke"
"Vario*,Staustrecke*,AUS_Element*,EIN_Element*,VF_*,GF_*"))
(wcmatch bname (export:pattern "pattern_ases_marker" "ZZ_ASES_MARKER*"))
(wcmatch bname (export:pattern "pattern_separator" "Separator_SP*,S-LP*"))
(wcmatch bname (export:pattern "pattern_scanner" "Scanner*"))
(wcmatch bname (export:pattern "pattern_btmt_beladung" "BTMT-Beladung*"))
@@ -1227,25 +1704,19 @@
;; den Zeichnungszustand herrichten - Separator-Proxies einpacken, IDs
;; sicherstellen, Proxy-Zuordnung setzen, Omniflo-Attribute (HOEHE/DREHUNG)
;; aktualisieren, Separator-/Scanner-Zuordnung pruefen, global regenerieren und
;; die bbox-Fehlerdiagnose zuruecksetzen. Rueckgabe: die Liste der erzeugten
;; Separator-Proxies (der Aufrufer entfernt sie am Ende wieder). label dient
;; nur den Meldungen.
(defun csv:export-vorbereiten (label / sep-proxies)
;; die bbox-Fehlerdiagnose zuruecksetzen. Rueckgabe: die Liste ALLER erzeugten
;; Kopien - Separatoren UND AS/ES-Schleuselemente (der Aufrufer entfernt sie am
;; Ende wieder). label dient nur den Meldungen.
(defun csv:export-vorbereiten (label / sep-proxies asmarks)
;; Verpackte Separator_SP-Symbole (siehe csv:sep-proxies-erzeugen) VOR der
;; ID-Vergabe durch temporaere, echte Kopien ersetzen - sie werden am Ende
;; von csv:run-export wieder entfernt (csv:sep-proxies-loeschen).
;;
;; HINWEIS: csv:sepliste-proxies-erzeugen (echte Kopien fuer die nur noch in
;; der sepliste-XDATA gefuehrten SEP/AS/ES-Eintraege der VF_n/GF_n-Wrapper)
;; ist BEWUSST NOCH NICHT eingehaengt: solange der Python-Export dieselben
;; Ketten-Separatoren bereits aus der sepliste erzeugt (build_separator_kette_
;; items), wuerden beide Wege jeden Separator DOPPELT zaehlen. Der Umbau auf
;; "LISP-Proxies fuehren, Python-Sonderweg raus" (inkl. Typmarker AS/ES) haengt
;; ausserdem am generellen vla-getboundingbox-Ausfall (Kreisel/GF/VF haben in
;; betroffenen Exporten ebenfalls keine bbox) - der ist zuerst zu klaeren.
;; Die Funktion bleibt erhalten und ist einsatzbereit, sobald der Python-
;; Sonderweg entfernt und AS/ES markiert sind.
(setq sep-proxies (csv:sep-proxies-erzeugen))
;; Die AS-/ES-Kettenenden stecken ebenfalls verpackt im Wrapper, bekommen
;; aber KEINE temporaere Kopie, sondern einen DAUERHAFTEN Marker
;; (ZZ_ASES_MARKER): er macht das Kettenende in der Zeichnung sichtbar und
;; traegt dessen ID ueber Exportlaeufe hinweg (siehe csv:asmark-*).
(setq asmarks (csv:asmark-aktualisieren))
;; 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
@@ -1256,8 +1727,17 @@
(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.
;; cs-zuordnung-lauf festnageln. Nur fuer die Separator-Kopien - die
;; AS/ES-Marker sind keine Sensoren und werden nicht gezaehlt (ihre
;; ZUORDNUNG setzt csv:asmark-ids-setzen).
(csv:sep-proxies-zuordnung-setzen sep-proxies)
;; Ebenfalls erst jetzt moeglich: die ID jedes Bauteils an seinen sepliste-
;; Eintrag haengen (csv:block-to-json schreibt sie als Feld "id" mit) -
;; AS/ES ueber die Marker, SEP ueber die temporaeren Kopien. Beide schreiben
;; in dieselbe Zuordnung, die hier einmal zurueckgesetzt wird.
(setq *csv-sepliste-id-by-handle* nil)
(csv:asmark-ids-setzen asmarks)
(csv:sepliste-ids-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)
@@ -1293,6 +1773,8 @@
;; bbox-Fehlerdiagnose fuer diesen Lauf zuruecksetzen (siehe csv:get-bbox).
(setq *csv-bbox-fail-count* 0)
(setq *csv-bbox-fail-first* nil)
;; Nur die TEMPORAEREN Kopien zurueckgeben - die AS/ES-Marker bleiben
;; bewusst in der Zeichnung stehen und duerfen nicht mitgeloescht werden.
sep-proxies)
;; --- Python-Export-Skript aufrufen und Ergebnis auswerten ---------------
@@ -1361,7 +1843,7 @@
first-block count out-dir sep-proxies)
;; Zeichnungszustand fuer den Export herrichten (Proxies, IDs, Zuordnung,
;; Attribut-Update, Sep/Scan-Zaehlung, REGENALL, bbox-Reset) - ausgelagert.
;; Rueckgabe: die erzeugten Separator-Proxies (unten wieder zu entfernen).
;; Rueckgabe: alle erzeugten Kopien (unten wieder zu entfernen).
(setq sep-proxies (csv:export-vorbereiten label))
(princ (ssg-textf "exp-collect-blocks" (list label)))
@@ -1428,7 +1910,7 @@
)
)
)
;; Temporaere Separator-/Scanner-Kopien wieder entfernen - sie haben ihren
;; Temporaere Separator-/AS-/ES-Kopien wieder entfernen - sie haben ihren
;; Zweck (ID-Vergabe + JSON-Zeile ueber die obigen, unveraenderten
;; Sammel-Funktionen) erfuellt.
(csv:sep-proxies-loeschen sep-proxies)
+25
View File
@@ -62,6 +62,31 @@
(if (not (assoc nm *dbg-schalter*))
(setq *dbg-schalter* (cons (cons nm 1) *dbg-schalter*))))
;; ------------------------------------------------------------
;; TEMPORAER - Diagnose "gespiegelte Sepliste-Koordinate" (lfdnr=2)
;;
;; Beide Schalter hier stehen NUR fuer diese Fehlersuche auf 1 und werden
;; danach wieder entfernt (mitsamt der zugehoerigen Instrumentierung in
;; ssg_ks_insert.lsp/Gefaellestrecke.lsp/vf_core.lsp bzw. dem Guard in
;; csv:sep-proxies-loeschen, export.lsp). Sie sind bewusst vorregistriert,
;; damit nach einem BricsCAD-Neustart ohne weiteres Zutun protokolliert wird.
;;
;; vf-sepliste - protokolliert den kompletten Weg der
;; Sepliste-Koordinaten beim Bau einer VF-/GF-Kette
;; nach DXFM_LOG/vf_sepliste.dbg (eigene Datei, nicht
;; ueber *dbg-path*).
;; csv_leave_proxies - laesst die temporaeren Separator_SP-Kopien, die der
;; Export aus den Wrappern auspackt, nach dem Export in
;; der Zeichnung stehen, damit ihre tatsaechliche Lage
;; in BricsCAD gegen die sepliste-Koordinaten geprueft
;; werden kann. ACHTUNG: die Kopien werden bei JEDEM
;; Export neu erzeugt und sammeln sich an - Zeichnung
;; danach nicht speichern.
;; ------------------------------------------------------------
(foreach nm '("vf-sepliste" "csv_leave_proxies")
(if (not (assoc nm *dbg-schalter*))
(setq *dbg-schalter* (cons (cons nm 1) *dbg-schalter*))))
(defun dbg-schalter-set (name wert)
(setq *dbg-schalter*
(cons (cons name wert)
+6
View File
@@ -60,6 +60,12 @@
(wcmatch bname "KR_*,KREISEL_*,ECKRAD_*")
(wcmatch (strcase bname) "AP110*,AP_110*,AP60*,AP_60*,APG110*")
(wcmatch bname "Vario*,Staustrecke*,AUS_Element*,EIN_Element*,VF_*,GF_*")
;; ZZ_ASES_MARKER: die Marker der AS-/ES-Kettenenden (siehe
;; csv:asmark-aktualisieren, export.lsp). Sie brauchen eine ID -
;; sie vertreten das Kettenende im Export ("sepliste"-Feld "id"
;; und eigene JSON-Zeile) und behalten sie ueber Laeufe hinweg,
;; weil der Marker in der Zeichnung stehen bleibt.
(wcmatch bname "ZZ_ASES_MARKER*")
(wcmatch bname "Separator_SP*,S-LP*,Scanner*")
(wcmatch bname "BTMT-Beladung*,SC_Entladung*")
(wcmatch bname "Eingang*,Ausgang*")
+113 -5
View File
@@ -220,6 +220,14 @@
(+ (car einfuegepunkt) (- (* chv dx-loc) (* shv dy-loc)))
(+ (cadr einfuegepunkt) (+ (* shv dx-loc) (* chv dy-loc)))
(+ (caddr einfuegepunkt) dz-loc)))
(ssg-sepliste-dbg
(strcat "insert-block-by-ks " blockname
" hz=" (rtos (float hz) 2 2)
" einfuegepunkt=" (ssg-sepliste-dbg-pt einfuegepunkt)
" ks_ein_lokal=" (ssg-sepliste-dbg-pt (car ks-ein))
" ks_aus_lokal=" (ssg-sepliste-dbg-pt (car ks-aus))
" offset(=Gruppencode 10)=" (ssg-sepliste-dbg-pt offset)
" ausgang=" (ssg-sepliste-dbg-pt ausgang)))
(princ (ssg-textf "vfc-ks-aus-xyz"
(list (rtos (car ausgang) 2 2) (rtos (cadr ausgang) 2 2) (rtos (caddr ausgang) 2 2))))
ausgang
@@ -266,6 +274,12 @@
(+ (car startpunkt) (* laenge chv cvv))
(+ (cadr startpunkt) (* laenge shv cvv))
(+ (caddr startpunkt) (* laenge (- svv)))))
(ssg-sepliste-dbg
(strcat "insert-inclined-scaled-block " blockname
" laenge=" (rtos laenge 2 2) " winkel=" (rtos (float winkel) 2 2)
" hz=" (rtos (float hz) 2 2) " scale=" (rtos scale 2 4)
" startpunkt(=Gruppencode 10)=" (ssg-sepliste-dbg-pt startpunkt)
" endpunkt=" (ssg-sepliste-dbg-pt endpunkt)))
(princ (ssg-textf "vfc-endpunkt-xyz"
(list (rtos (car endpunkt) 2 2) (rtos (cadr endpunkt) 2 2) (rtos (caddr endpunkt) 2 2))))
endpunkt
@@ -346,6 +360,17 @@
(+ (car startpunkt) (+ (* dx chv cvv) (* dz chv svv)))
(+ (cadr startpunkt) (+ (* dx shv cvv) (* dz shv svv)))
(+ (caddr startpunkt) (+ (* dx (- svv)) (* dz cvv)))))
(ssg-sepliste-dbg
(strcat "insert-rotated-block-with-ks " blockname
" winkel=" (rtos (float winkel) 2 2) " hz=" (rtos (float hz) 2 2)
" startpunkt=" (ssg-sepliste-dbg-pt startpunkt)
" ks_ein_lokal=" (ssg-sepliste-dbg-pt (list ein-x ein-y ein-z))
" ks_aus_lokal=" (if ks-aus-raw
(ssg-sepliste-dbg-pt (list aus-x aus-y aus-z))
"nil (Fallback dx/dz aus cfg)")
" dx=" (rtos dx 2 2) " dz=" (rtos dz 2 2)
" ins-pt(=Gruppencode 10)=" (ssg-sepliste-dbg-pt ins-pt)
" endpunkt=" (ssg-sepliste-dbg-pt endpunkt)))
(princ (ssg-textf "vfc-endpunkt-xyz-dxdz"
(list (rtos (car endpunkt) 2 2) (rtos (cadr endpunkt) 2 2) (rtos (caddr endpunkt) 2 2)
(rtos dx 2 1) (rtos dz 2 1))))
@@ -371,6 +396,73 @@
;; bekommt nur eine laufende Nummer innerhalb der Liste. Der Export bildet
;; daraus bei Bedarf eigene, global fortlaufende TeileIds.
;; --- Diagnose-Protokoll (Schalter "vf-sepliste", siehe ssg_dbg.lsp) ---------
;; TEMPORAER fuer die Fehlersuche "gespiegelte Sepliste-Koordinate (lfdnr=2)" -
;; nach Abschluss mitsamt Schalter wieder entfernen.
;; Der Schalter ist in ssg_dbg.lsp mit Default 1 vorregistriert, das Protokoll
;; laeuft also nach einem BricsCAD-Neustart ohne weiteres Zutun. Es genuegt,
;; EINE VF-/GF-Kette zu bauen - das Protokoll landet in
;; DXFM_LOG/vf_sepliste.dbg. Protokolliert wird der komplette Weg der
;; Sepliste-Koordinaten: je Einfuegeprimitive der Startpunkt, die am Block
;; gemessenen KS_EIN/KS_AUS-Lokalpunkte, der daraus berechnete
;; Block-Einfuegepunkt (ins-pt = genau der Wert, den die Sepliste spaeter als
;; Gruppencode 10 einsammelt) und der Folgepunkt - sowie am Ende die
;; entnext-Wanderung von ssg-sepliste-sammeln mit JEDEM gefundenen INSERT.
;; Hintergrund: im Export tauchen Sepliste-Koordinaten auf, die nicht zur
;; sichtbaren Geometrie passen (x exakt negiert) - die Herkunft dieses Werts
;; ist ohne Laufzeit-Protokoll nicht zu klaeren.
;;
;; Nutzt NUR die Schalterverwaltung aus ssg_dbg.lsp, aber BEWUSST einen eigenen
;; Dateihandle statt dbgopen/dbgmsg: ssg_dbg fuehrt mit *dbg-path* genau EINE
;; global offene Datei. Der Linienzug-Bau (vfl-modus1, Schalter per Default EIN)
;; oeffnet seine vfl_modus1.dbg und haelt *dbg-path* damit besetzt - die
;; Sepliste-Zeilen waeren sonst stillschweigend dort gelandet statt in einer
;; eigenen Datei. Genau dieser Bau ist aber der zu protokollierende Vorgang.
(if (not (boundp '*ssg-sepliste-dbg-pfad*)) (setq *ssg-sepliste-dbg-pfad* nil))
;; Legt die Datei beim ERSTEN Aufruf je Sitzung an (leert sie dabei) und
;; liefert den Pfad; danach nur noch den gemerkten Pfad. nil = Schalter aus.
(defun ssg-sepliste-dbg-oeffnen ( / dir fh)
(if (and (car (atoms-family 1 '("DBG-SCHALTER-P")))
(dbg-schalter-p "vf-sepliste"))
(progn
(if (null *ssg-sepliste-dbg-pfad*)
(progn
(setq dir (getenv "DXFM_LOG"))
(if (or (null dir) (= dir "")) (setq dir "."))
(if (and (/= (substr dir (strlen dir) 1) "\\")
(/= (substr dir (strlen dir) 1) "/"))
(setq dir (strcat dir "\\")))
(setq *ssg-sepliste-dbg-pfad* (strcat dir "vf_sepliste.dbg"))
(setq fh (open *ssg-sepliste-dbg-pfad* "w"))
(if fh
(progn
(write-line "=== vf-sepliste Diagnose ===" fh)
(close fh)
(princ (strcat "\n[DBG] vf-sepliste-Protokoll: " *ssg-sepliste-dbg-pfad*)))
(progn
(princ (strcat "\n[DBG] FEHLER: " *ssg-sepliste-dbg-pfad*
" nicht schreibbar"))
(setq *ssg-sepliste-dbg-pfad* nil)))))
*ssg-sepliste-dbg-pfad*)
nil))
;; Eine Zeile anhaengen (kein Puffer - ein Bau erzeugt nur wenige hundert
;; Zeilen, dafuer geht keine Zeile verloren, wenn der Bau abbricht).
(defun ssg-sepliste-dbg (msg / pfad fh)
(if (setq pfad (ssg-sepliste-dbg-oeffnen))
(progn
(setq fh (open pfad "a"))
(if fh (progn (write-line msg fh) (close fh)))))
(princ))
;; Punkt (x y z) kompakt als "(x, y, z)" - nil-sicher.
(defun ssg-sepliste-dbg-pt (pt)
(if (and pt (listp pt))
(strcat "(" (rtos (car pt) 2 2) ", " (rtos (cadr pt) 2 2) ", "
(rtos (cond ((caddr pt)) (0.0)) 2 2) ")")
"nil"))
;; Einfaches Chunking eines Strings in Stuecke <= n Zeichen (DXF-Limit einer
;; 1000-Gruppe ist 255 Byte) - eigenstaendige Kopie statt vfl-chunk-string
;; (vf_linienzug.lsp), da diese Datei laut Lademechanismus VOR vf_linienzug
@@ -387,22 +479,32 @@
;; Baureihenfolge (identische entnext-Wanderung wie die Auswahlsatz-Sammlung
;; der Aufrufer, nur mit wcmatch-Filter). Rueckgabe: Liste von
;; (typ lfdnr x y), typ = "SEP"/"AS"/"ES", lfdnr 1-basiert in Baureihenfolge.
(defun ssg-sepliste-sammeln (lastEnt / ent bname typ lfdnr pt liste)
(defun ssg-sepliste-sammeln (lastEnt / ent ed bname typ lfdnr pt liste)
(ssg-sepliste-dbg "=== ssg-sepliste-sammeln: entnext-Wanderung ab lastEnt ===")
(setq liste '() lfdnr 0)
(setq ent (if lastEnt (entnext lastEnt) (entnext)))
(while ent
(if (= (cdr (assoc 0 (entget ent))) "INSERT")
(setq ed (entget ent))
(if (= (cdr (assoc 0 ed)) "INSERT")
(progn
(setq bname (cdr (assoc 2 (entget ent))))
(setq bname (cdr (assoc 2 ed)))
(setq typ (cond
((wcmatch bname "Staustrecke_Separator_SP_300_mm*") "SEP")
((wcmatch bname "AS_Element_*") "AS")
((wcmatch bname "ES_Element_*") "ES")
(t nil)))
(setq pt (cdr (assoc 10 ed)))
;; JEDEN INSERT protokollieren (auch die nicht klassifizierten) - nur so
;; laesst sich im .dbg nachvollziehen, welches Entity einen auffaelligen
;; Einfuegepunkt liefert und in welcher Reihenfolge es entstand.
(ssg-sepliste-dbg
(strcat "INSERT " bname
" handle=" (cond ((cdr (assoc 5 ed))) ("-"))
" typ=" (cond (typ) ("-"))
" pt=" (ssg-sepliste-dbg-pt pt)))
(if typ
(progn
(setq lfdnr (1+ lfdnr))
(setq pt (cdr (assoc 10 (entget ent))))
(setq liste (cons (list typ lfdnr (car pt) (cadr pt)) liste))
)
)
@@ -410,7 +512,13 @@
)
(setq ent (entnext ent))
)
(reverse liste)
(setq liste (reverse liste))
(ssg-sepliste-dbg (strcat "Ergebnis: " (itoa (length liste)) " Sepliste-Eintraege"))
(foreach e liste
(ssg-sepliste-dbg
(strcat " " (nth 0 e) " lfdnr=" (itoa (nth 1 e))
" x=" (rtos (nth 2 e) 2 2) " y=" (rtos (nth 3 e) 2 2))))
liste
)
;; Koordinate mit GENAU 2 Nachkommastellen formatieren - bewusst OHNE rtos.
+10
View File
@@ -402,6 +402,16 @@
(setq xu-out (mat3-mul-vec3 R xu-aus)
yu-out (mat3-mul-vec3 R yu-aus)
zu-out (mat3-mul-vec3 R zu-aus))
;; Diagnose (Schalter "vf-sepliste", siehe ssg_ks_insert.lsp): setzt im
;; Linienzug die AS-/ES-Elemente, also den ERSTEN und LETZTEN Sepliste-Eintrag.
;; Die Translation (tx ty tz) ist hier der Gruppencode 10 des Blocks.
(if (car (atoms-family 1 '("SSG-SEPLISTE-DBG")))
(ssg-sepliste-dbg
(strcat "insert-block-ks-to-ks " blockname
" ziel-P=" (ssg-sepliste-dbg-pt P-t)
" ks_ein_lokal=" (ssg-sepliste-dbg-pt P-ein)
" translation(=Gruppencode 10)=" (ssg-sepliste-dbg-pt (list tx ty tz))
" ks_aus_welt=" (ssg-sepliste-dbg-pt P-out))))
(princ (ssg-textf "vfc-block-eingefuegt-ksaus"
(list blockname (rtos (car P-out) 2 2) (rtos (cadr P-out) 2 2) (rtos (caddr P-out) 2 2))))
(list P-out xu-out yu-out zu-out))
+32
View File
@@ -48,6 +48,25 @@
# den DWGs (ssg-id-check-all fuellt es vor jedem Export, siehe
# ssg-id-collect-blocks in ssg_id.lsp).
#
# pattern_schleuselement: die AS-/ES-Schleuselemente an den Enden einer
# VF_n/GF_n-Kette. Sie liegen in der gespeicherten Zeichnung NICHT als eigener
# top-level INSERT vor, sondern verpackt in der Wrapper-Blockdefinition. Dieses
# Muster dient nur der SUCHE in der Blockdefinition (csv:nested-welt-records in
# Lisp/export.lsp) - exportiert wird nicht das Element selbst, sondern sein
# Marker (siehe pattern_ases_marker).
#
# pattern_ases_marker: der Marker, den der Export an jedes AS-/ES-Kettenende
# setzt (ZZ_ASES_MARKER, ein kleines Rechteck, csv:asmark-aktualisieren in
# Lisp/export.lsp). Er bleibt nach dem Export in der Zeichnung stehen, macht
# das sonst unsichtbare Kettenende anklickbar und traegt dessen ID ueber
# Exportlaeufe hinweg (Attribute ID/ZUORDNUNG/TYP/LFDNR/ELEMENT/ARTINR, alle
# unsichtbar). Die ID landet im "sepliste"-Array des Wrappers (Feld "id" je
# Eintrag) UND als eigene Zeile in export_raw.json. Von Lisp's Block-SAMMLUNG
# genutzt; die Python-Klassifikation kennt das Muster noch nicht (der Block
# faellt dort durch alle Muster und wird uebersprungen) - sobald sie es kennt,
# ersetzt sie damit die bisher in Python synthetisierten Ein-/Ausschleus-
# element-Zeilen (build_strecke_schleus_items).
#
# pattern_geruest: Geruest-Baugruppen (GeruestInsert.lsp, GR_<id>_<timestamp>).
# Eigenstaendig wie BTMT, kein ANZAHL_*-Attribut an einem Carrier - export_csv.py
# UND export_sivas.py fuehren sie als eigene TeileArt-Zeilen.
@@ -61,6 +80,8 @@ pattern_strecke= Vario*, Staustrecke*, AUS_Element*, EIN_Element*, VF_
pattern_variofoerderer= VF_*
pattern_gefaellestrecke= GF_*
pattern_strecke_module= Vario*, Staustrecke*, AUS_Element*, EIN_Element*
pattern_schleuselement= AS_Element*, ES_Element*
pattern_ases_marker= ZZ_ASES_MARKER*
pattern_separator= Separator_SP*, S-LP*
pattern_scanner= Scanner*
pattern_btmt_beladung= BTMT-Beladung*
@@ -169,6 +190,17 @@ omniflo_ks_toleranz_mm=10
# Ueberschneidungstest - zur Diagnose, warum (keine) Nachbarn gefunden werden.
debug_log=1
[Marker]
# Kantenlaenge (mm) des quadratischen Markers, den der Export an jedes AS-/ES-
# Kettenende einer VF_n/GF_n-Strecke setzt (Block ZZ_ASES_MARKER, siehe
# csv:asmark-* in Lisp/export.lsp). Der Marker sitzt MITTIG auf dem
# Einfuegepunkt des Schleuselements und folgt dessen Drehung. Nur optisch
# relevant - Nachbarschaft/Zuordnung rechnen nicht mit dieser Groesse.
# Wirkt erst, wenn die Blockdefinition NEU angelegt wird: in einer Zeichnung,
# die ZZ_ASES_MARKER schon kennt, bleibt die vorhandene Groesse bestehen
# (Block ueber _PURGE entfernen, um sie neu zu erzeugen). Default 200.
ases_groesse_mm=200
[Boundingbox]
# Mindesthoehe (Z-Ausdehnung) der von export.lsp (csv:get-bbox, per
# vla-getboundingbox) ermittelten Bounding-Box. Flache 2D-Bloecke (Draufsicht)
+6
View File
@@ -132,6 +132,12 @@ gefaellestrecke = _3D_ILS_SP, 3
# ============================================================
[allgemein]
# Marker der AS-/ES-Kettenenden (Block ZZ_ASES_MARKER), den der Export an
# jedes Schleuselement einer VF_n/GF_n-Strecke setzt - siehe csv:asmark-* in
# Lisp/export.lsp. Reine Hilfsgeometrie wie die Planquadrate, darum ein
# eigener, leicht abschaltbarer Layer.
ases_marker = ZZ_ASES_Marker, 6
# Kreisel-Tangenten (die beiden Linien zwischen AN8 und SP8).
# HINWEIS: "S_LP" ist der bisher hart im Code stehende Name. Der
# Firmenstandard kennt nur "ILS_LP" - S_LP ist sehr wahrscheinlich ein
+103 -9
View File
@@ -12,9 +12,12 @@ BricsCAD (LISP) Python
───────────────── ──────
c:EXPORTCSV
-> csv:run-export
-> Proxies fuer verpackte
-> Kopien fuer verpackte
Separatoren einfuegen
-> AS/ES-Marker aufbauen
-> IDs vergeben (ssg-id-check-all)
-> IDs in die sepliste
eintragen
-> Separator-/Scanner-Zuordnung
(count_sep_scan.lsp)
-> REGENALL
@@ -48,15 +51,32 @@ Bounding-Boxen braucht).
durchlaeuft danach ID-Vergabe und Export ganz normal ueber den freien-
Separator-Pfad (Blockname-Muster `Separator_SP*` trifft zu) und bekommt
dadurch eine echte `vla-getboundingbox`-Box, die das verpackte Original nie
haben koennte. Rueckgabe: Liste `(proxy-ename . wrapper-ename)`.
haben koennte. Rueckgabe: Liste von Records
`(proxy-ename wrapper-ename typ (x y z))`.
1b. **`csv:asmark-aktualisieren`** — die AS-/ES-Enden derselben Kette
(`AS_Element*`/`ES_Element*`, Muster `pattern_schleuselement`) bekommen
KEINE temporaere Kopie, sondern einen DAUERHAFTEN Marker — siehe eigener
Abschnitt "Der AS/ES-Marker" unten. Beide Wege teilen sich die
Platzierungsrechnung (`csv:nested-welt-records` liefert die Weltlage jedes
verpackten Fundes, `csv:block-einfuegen-welt` setzt ein Objekt dorthin).
2. **`ssg-id-check-all`** — vergibt fehlende IDs (inkl. der frisch erzeugten
Proxies) und korrigiert Duplikate. Muss NACH den Proxies laufen, sonst
wuerden diese Kopien numerisch nie erfasst.
wuerden diese Kopien numerisch nie erfasst. `ssg-id-collect-blocks`
(`ssg_id.lsp`) erfasst dafuer auch `AS_Element*`/`ES_Element*`.
3. **`csv:sep-proxies-zuordnung-setzen`** — jetzt steht die ID jedes Wrappers
fest: die Proxy-Kopie bekommt `ZUORDNUNG = Wrapper-ID` fest zugewiesen
(nicht geraten) und wird zusaetzlich in `*cs-sep-fix-by-handle*` vermerkt,
damit `count_sep_scan.lsp` diesen Wert spaeter nicht per Geometrie
ueberschreibt.
ueberschreibt. Nur fuer die Separator-Kopien — AS/ES sind keine Sensoren.
3b. **`csv:asmark-ids-setzen` + `csv:sepliste-ids-setzen`** — ebenfalls erst
jetzt moeglich: jedes Bauteil wird seinem `sepliste`-Eintrag zugeordnet und
seine ID unter `*csv-sepliste-id-by-handle*` (Key = Wrapper-Handle)
abgelegt; `csv:block-to-json` schreibt sie als Feld `id` in den Eintrag.
AS/ES laufen ueber die Marker (die ihre ID schon kennen, die Zuordnung
steht im Marker-Attribut `LFDNR`), SEP ueber die temporaeren Kopien und
`csv:sepliste-paare` — siehe eigener Abschnitt unten. `csv:asmark-ids-setzen`
traegt zusaetzlich die `ZUORDNUNG` (Wrapper-ID) am Marker nach; ohne sie
faende der naechste Export den Marker nicht wieder.
4. **`omni:update-all-attribs`** — Hoehe/Drehung aller Omniflo-Elemente vor
dem Export aktualisieren.
5. **`count_sep_scan.lsp::cs-zuordnung-lauf`** (nur bei Bedarf, siehe
@@ -88,7 +108,7 @@ Pro INSERT wird ein JSON-Objekt geschrieben:
| `bbox` | `csv:get-bbox` (`vla-getboundingbox`) | NUR wenn `include-bbox=T` und der Aufruf gelingt; fehlt sonst komplett im JSON |
| `warnung` | `*cs-scanner-warnung-by-handle*` | strittige Scanner-Zuordnung aus `cs-zuordnung-lauf` |
| `zuordnung_fix` | `*cs-sep-fix-by-handle*` | nur bei Proxy-Kopien: die feststehende Wrapper-ID |
| `sepliste` | `ssg-sepliste-xdata-lesen` | nur bei VF_n/GF_n, siehe eigener Abschnitt |
| `sepliste` | `ssg-sepliste-xdata-lesen` + `*csv-sepliste-id-by-handle*` | nur bei VF_n/GF_n, siehe eigener Abschnitt |
**K1-K4 je Blocktyp:** normale ILS-Bloecke fuehren echte K1-K4-Sub-Bloecke.
Omniflo-Geraden (`AP110*`) haben keine echten K-Bloecke — K1/K2 werden aus
@@ -132,6 +152,80 @@ Separatoren als Eintraege mit Vorgaenger-/Nachfolger-Wissen. Python fuehrt
beides zusammen (siehe `map_separator_kette_items` unten) statt sie doppelt
zu zaehlen.
### Das Feld `id` je sepliste-Eintrag
Seit `csv:sepliste-ids-setzen` traegt JEDER Eintrag zusaetzlich die `id` des
realen Bauteils, das ihn in der Zeichnung vertritt:
```json
{"typ": "AS", "lfdnr": 1, "x": 5070.1, "y": 1199.2, "id": "0031"}
```
Das ist dieselbe ID, die die zugehoerige temporaere Kopie in ihrer eigenen
JSON-Zeile traegt — die Verbindung "Kettenglied <-> reales Teil" steht damit
schon im Export und muss in Python nicht mehr ueber Koordinaten-Naehe geraten
werden (`map_separator_kette_items`, siehe unten; solange Python das Feld noch
nicht liest, aendert sich dort nichts). Das Feld fehlt, wenn keine Kopie
erzeugt werden konnte oder deren ID-Attribut leer blieb — Altbestand und
Fehlerfaelle verhalten sich damit wie bisher.
### Der AS/ES-Marker (`ZZ_ASES_MARKER`)
Die beiden Kettenenden stecken als `AS_Element_*`/`ES_Element_*` verpackt in
der Wrapper-Blockdefinition: in der Zeichnung sind sie weder anklickbar noch
einzeln erkennbar, und `(ssget "X" ...)` findet sie nicht. `csv:asmark-*`
(`Lisp/export.lsp`) setzt darum an jedes Ende ein kleines Rechteck, das nach
dem Export in der Zeichnung STEHEN BLEIBT:
- **Sichtbarmachung** — Kantenlaenge aus `cfg/export.cfg` `[Marker]`
`ases_groesse_mm` (Default 200 mm), mittig auf dem Einfuegepunkt des
Schleuselements, in dessen Drehung (auch bei geneigter Kette). Layer aus
`cfg/layer.cfg` `[allgemein]` `ases_marker` — abschaltbar wie jede andere
Hilfsgeometrie. Die Blockdefinition wird bei Bedarf zur Laufzeit erzeugt
(`csv:asmark-blockdef-sicherstellen`, Rechteck + ATTDEFs + `_.-BLOCK`), es
gibt keine DWG dafuer.
- **ID-Traeger** — die Attribute (alle unsichtbar) sind
`ID`, `ZUORDNUNG` (Wrapper-ID), `TYP` (`AS`/`ES`), `LFDNR` (sepliste-Eintrag),
`ELEMENT` (Blockname des echten Elements, haelt die Variante 30/90,
links/rechts, 2D/3D fest) und `ARTINR`. `ssg-id-check-all` fuellt die `ID`,
und weil der Marker liegen bleibt, **behaelt ein AS/ES seine TeileId ueber
Exportlaeufe hinweg** — im Unterschied zu den Separator-Kopien, die jeder
Lauf neu erzeugt und neu nummeriert.
Der Marker ist abgeleitete Geometrie: jeder Export baut ihn neu auf (die Lage
folgt damit automatisch einer umgebauten Kette) und uebernimmt vom bisherigen
Marker nur dessen `ID`. Wiedergefunden wird er ueber (`ZUORDNUNG`, `TYP`,
`LFDNR`) — bewusst nicht ueber die Position, damit eine verschobene Kette ihre
TeileIds behaelt. Was am Ende keinem Kettenende mehr entspricht (Kette
geloescht, Eintrag entfallen), wird geloescht; Karteileichen entstehen nicht.
Ein frisch gebauter Wrapper hat beim Setzen der Marker noch keine ID — seine
Marker bekommen die `ZUORDNUNG` nach der ID-Vergabe nachgetragen und sind ab
dem naechsten Export wiederfindbar.
Im Export ist der Marker eine eigene Zeile (`pattern_ases_marker` in
`cfg/export.cfg`) und liefert die `id` des AS-/ES-Eintrags in der `sepliste`.
**Zuordnung Eintrag -> Kopie (`csv:sepliste-paare`).** Verglichen wird nur
innerhalb desselben Typmarkers (`AS`/`ES`/`SEP`) und EXKLUSIV: alle Paare
eines Typs werden nach Abstand sortiert, das kuerzeste zuerst genommen, danach
sind Eintrag UND Kopie verbraucht. Zwei Gruende gegen das naheliegende "je
Eintrag die naechste Kopie":
- waehlen zwei Eintraege dieselbe Kopie, bleibt eine Kopie ohne Eintrag und
ein Eintrag ohne ID (genau dieses Muster verursacht in Python die
Separatoren ohne `Nachbarn`/`TrackIds`);
- EIN fehlerhafter Koordinatenwert in der sepliste (bekannter Fall: gespiegeltes
`x` bei `lfdnr=2`) verschiebt so keine anderen Zuordnungen mehr — alle
uebrigen Eintraege finden ihre Kopie ueber den kurzen Abstand zuerst, der
fehlerhafte bekommt den Rest zugeteilt. Liegt dieser Rest weiter als
`csv:sepliste-abstand-warnung-mm` (500 mm) entfernt, meldet der Export das
(`exp-sepliste-abstand`), ohne die Zuordnung zu verwerfen.
Die Sortierung ist eine strikte Totalordnung (Abstand, dann `lfdnr`, dann
Index) — `vl-sort` wirft Elemente weg, die unter der Vergleichsfunktion gleich
sind, zwei Paare mit identischem Abstand wuerden sonst zu einem verschmelzen.
Getestet in `tests/test_unit.lsp` (`csv:sepliste-paare/*`, zeichnungsfrei).
## Phase 2 (Python): `lib/export_csv.py::process_blocks`
Aufruf: `python export_csv.py <export_raw.json> <data_dir> <output.csv>`.
@@ -215,10 +309,10 @@ relevanten Felder):
"k2": "AAAAQwAATkAAAAAA...",
"bbox": {"min": [10800.0, 4850.0, 0.0], "max": [13000.0, 5150.0, 300.0]},
"sepliste": [
["AS", 1, 10800.0, 5000.0],
["SEP", 2, 11600.0, 5000.0],
["SEP", 3, 12300.0, 5000.0],
["ES", 4, 13000.0, 5000.0]
{"typ": "AS", "lfdnr": 1, "x": 10800.0, "y": 5000.0, "id": "0025"},
{"typ": "SEP", "lfdnr": 2, "x": 11600.0, "y": 5000.0, "id": "0022"},
{"typ": "SEP", "lfdnr": 3, "x": 12300.0, "y": 5000.0, "id": "0023"},
{"typ": "ES", "lfdnr": 4, "x": 13000.0, "y": 5000.0, "id": "0026"}
]
},
{
+9 -1
View File
@@ -700,9 +700,17 @@
"plq-geloescht": "\n[DELETE_PLANQUADRATE] Planquadrat-Raster entfernt (%1 Einfuegung(en)).",
"plq-nichts-zu-loeschen": "\n[DELETE_PLANQUADRATE] Kein Planquadrat-Raster in der Zeichnung vorhanden.",
"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-proxy-fehler": "\n[EXPORT] WARNUNG: Kopie von '%1' aus Block '%2' fehlgeschlagen (%3) - dieses Element 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-sepliste-ids": "\n[EXPORT] %1 sepliste-Eintrag/Eintraege mit der ID des zugehoerigen Bauteils versehen.",
"exp-sepliste-ohne-id": "\n[EXPORT] %1 sepliste-Eintrag/Eintraege ohne ID - die Kopie hat kein gefuelltes ID-Attribut.",
"exp-sepliste-ohne-proxy": "\n[EXPORT] WARNUNG: Block '%1': %2 sepliste-Eintrag/Eintraege ohne passende Kopie - diese Eintraege bleiben im Export ohne ID.",
"exp-sepliste-abstand": "\n[EXPORT] WARNUNG: Block '%1': %2-Eintrag lfdnr=%3 liegt %4 mm von seinem Bauteil entfernt - sepliste-Koordinate pruefen (Zuordnung bleibt gueltig).",
"exp-asmark-blockdef": "\n[EXPORT] Blockdefinition '%1' fuer die AS/ES-Marker angelegt.",
"exp-asmark-blockdef-fehler": "\n[EXPORT] WARNUNG: Blockdefinition '%1' konnte nicht angelegt werden - es werden keine AS/ES-Marker gesetzt.",
"exp-asmark-bilanz": "\n[EXPORT] AS/ES-Marker: %1 neu, %2 mit bisheriger ID uebernommen, %3 verwaiste entfernt.",
"exp-asmark-ids": "\n[EXPORT] %1 AS/ES-Marker mit ihrer ID am sepliste-Eintrag vermerkt.",
"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.",
+9 -1
View File
@@ -700,9 +700,17 @@
"plq-geloescht": "\n[DELETE_PLANQUADRATE] Planquadrat grid removed (%1 insertion(s)).",
"plq-nichts-zu-loeschen": "\n[DELETE_PLANQUADRATE] No Planquadrat grid found in the drawing.",
"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-proxy-fehler": "\n[EXPORT] WARNING: copying '%1' out of block '%2' failed (%3) - this element 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-sepliste-ids": "\n[EXPORT] %1 sepliste entry/entries tagged with the ID of the matching part.",
"exp-sepliste-ohne-id": "\n[EXPORT] %1 sepliste entry/entries without an ID - the copy has no ID attribute value.",
"exp-sepliste-ohne-proxy": "\n[EXPORT] WARNING: block '%1': %2 sepliste entry/entries without a matching copy - those entries stay without an ID in the export.",
"exp-sepliste-abstand": "\n[EXPORT] WARNING: block '%1': %2 entry lfdnr=%3 is %4 mm away from its part - check the sepliste coordinate (the mapping stays valid).",
"exp-asmark-blockdef": "\n[EXPORT] Block definition '%1' for the AS/ES markers created.",
"exp-asmark-blockdef-fehler": "\n[EXPORT] WARNING: block definition '%1' could not be created - no AS/ES markers are placed.",
"exp-asmark-bilanz": "\n[EXPORT] AS/ES markers: %1 new, %2 kept with their previous ID, %3 orphaned ones removed.",
"exp-asmark-ids": "\n[EXPORT] %1 AS/ES marker ID(s) recorded at their sepliste entry.",
"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.",
+103 -8
View File
@@ -44,6 +44,7 @@ from export_neighbors import (
compute_strecke_kreisel_schleus,
compute_sep_kette,
compute_kreisel_umlauf,
dbg_highlight,
SENSOR_TEILEARTEN,
WEICHE_TEILEART,
GF_VF_TEILEARTEN,
@@ -683,9 +684,30 @@ def map_separator_kette_items(items, sep_kette, sep_kette_by_key=None, dbg=None)
if zuordnung:
sep_items_by_zuordnung.setdefault(zuordnung, []).append(it)
if dbg:
dbg("=== map_separator_kette_items: Kandidatenpools je Zuordnung ===")
for zuordnung, kandidaten in sep_items_by_zuordnung.items():
dbg(f" Zuordnung {zuordnung!r}: "
f"{[k.get('teileid', '') for k in kandidaten]}")
dbg("")
dbg("=== map_separator_kette_items: sepliste-SEP -> Separator-Item ===")
# id(item) -> Liste der sep_kette-Keys, die genau dieses Item als
# naechsten Kandidaten gewaehlt haben - Diagnose der NICHT-exklusiven
# Zuordnung (jeder Key sucht unabhaengig im vollen Pool, siehe Docstring):
# waehlen zwei Keys denselben Kandidaten, bleibt ein anderer Kandidat im
# selben Pool ohne jeden sep_kette-Eintrag und damit ohne Nachbarn/TrackIds.
gewaehlt_von = {}
# Zuordnungen, fuer die es ueberhaupt sepliste-SEP-Schluessel gibt. Nur in
# DIESEN Pools ist ein nicht gewaehlter Kandidat auffaellig; frei auf einem
# Kreisel platzierte Separatoren tragen keine sepliste und bekommen ihre
# Nachbarn aus dem Kreisel-Umlauf (siehe resolve_umlauf_und_sepliste_nachbarn).
zuordnungen_mit_sepliste = set()
for key, eintrag in sep_kette.items():
if eintrag.get("typ") != "SEP":
continue
zuordnungen_mit_sepliste.add(eintrag["strecke_id"])
kandidaten = sep_items_by_zuordnung.get(eintrag["strecke_id"], [])
if not kandidaten:
if dbg:
@@ -694,18 +716,48 @@ def map_separator_kette_items(items, sep_kette, sep_kette_by_key=None, dbg=None)
continue
# Naechster INSERT-Punkt zur sepliste-Koordinate.
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))
entfernungen = sorted(
((math.hypot(it.get("_x", 0.0) - sx, it.get("_y", 0.0) - sy), it)
for it in kandidaten),
key=lambda p: p[0])
best_d, best = entfernungen[0]
sep_kette_by_key[key] = best
gewaehlt_von.setdefault(id(best), []).append(key)
if dbg:
dbg(f" sepliste-SEP {key} -> Separator-Item TeileId "
f"{best.get('teileid', '')!r}")
rangliste = ", ".join(f"{it.get('teileid', '')!r}={d:.0f}mm"
for d, it in entfernungen)
dbg(f" sepliste-SEP {key} (x={sx:.0f},y={sy:.0f}) -> Separator-Item "
f"TeileId {best.get('teileid', '')!r} (d={best_d:.0f}mm); "
f"Kandidaten sortiert: [{rangliste}]"
f"{dbg_highlight(best.get('teileid', ''))}")
if dbg:
dbg("")
mehrfach = {iid: keys for iid, keys in gewaehlt_von.items() if len(keys) > 1}
if mehrfach:
dbg(" WARNUNG: folgende Separator-Items wurden von MEHREREN "
"sepliste-Keys unabhaengig als naechster Kandidat gewaehlt "
"(nicht-exklusive Zuordnung):")
for keys in mehrfach.values():
teileid = sep_kette_by_key[keys[0]].get("teileid", "")
dbg(f" TeileId {teileid!r} gewaehlt von Keys: {keys}"
f"{dbg_highlight(teileid)}")
gewaehlte_ids = set(gewaehlt_von.keys())
for zuordnung, kandidaten in sep_items_by_zuordnung.items():
if zuordnung not in zuordnungen_mit_sepliste:
continue
for it in kandidaten:
if id(it) not in gewaehlte_ids:
teileid = it.get("teileid", "")
dbg(f" Separator-Item TeileId {teileid!r} (Zuordnung "
f"{zuordnung!r}) wurde von KEINEM sepliste-Key gewaehlt "
f"- bleibt ohne sep_kette-Eintrag, damit ohne Nachbarn/"
f"TrackIds{dbg_highlight(teileid)}")
return sep_kette_by_key
def resolve_umlauf_und_sepliste_nachbarn(items, kreisel_umlauf, sep_kette,
sep_kette_by_key, weiche_by_key):
sep_kette_by_key, weiche_by_key, dbg=None):
"""Loest die "Nachbarn"-Spalte aller Zeilen auf, die build_kreisel_weiche_items/
build_strecke_schleus_items/map_separator_kette_items zunaechst leer
gelassen (bzw. deren Item nur registriert) haben, weil ihr(e) Nachbar(n)
@@ -740,13 +792,27 @@ def resolve_umlauf_und_sepliste_nachbarn(items, kreisel_umlauf, sep_kette,
if it.get("teileart", "") in KREISEL_UMLAUF_TEILEARTEN}
alle_schluessel = {**sep_kette_by_key, **weiche_by_key, **freipunkt_by_id}
if dbg:
dbg("=== resolve_umlauf_und_sepliste_nachbarn ===")
dbg(f" freipunkt_by_id: {sorted(freipunkt_by_id.keys())}")
dbg(f" sep_kette_by_key: "
f"{[(k, v.get('teileid', '')) for k, v in sep_kette_by_key.items()]}")
dbg(f" weiche_by_key: "
f"{[(k, v.get('teileid', '')) for k, v in weiche_by_key.items()]}")
dbg("")
dbg(" -- Pass 1: aus kreisel_umlauf --")
# Freie Separatoren, Weichen UND die AS/ES-Enden andockender Ketten
# bekommen ihre "Nachbarn"-Spalte aus dem Kreisel-Umlauf.
if kreisel_umlauf:
for punkte in kreisel_umlauf.values():
for kreisel_tid, punkte in kreisel_umlauf.items():
for punkt_key, vorgaenger, nachfolger in punkte:
item = alle_schluessel.get(punkt_key)
if item is None:
if dbg:
dbg(f" Kreisel {kreisel_tid} Punkt {punkt_key!r}: "
f"KEIN Item in alle_schluessel gefunden - "
f"uebersprungen{dbg_highlight(punkt_key)}")
continue
nachbarn = []
for nachbar_key in (vorgaenger, nachfolger):
@@ -755,8 +821,26 @@ def resolve_umlauf_und_sepliste_nachbarn(items, kreisel_umlauf, sep_kette,
nachbar_item = alle_schluessel.get(nachbar_key)
if nachbar_item is not None:
nachbarn.append(nachbar_item.get("teileid", ""))
elif dbg:
dbg(f" Kreisel {kreisel_tid} Punkt {punkt_key!r}: "
f"Nachbar-Key {nachbar_key!r} nicht in alle_schluessel "
f"aufloesbar{dbg_highlight(item.get('teileid', ''))}")
teileid = item.get("teileid", "")
if nachbarn:
item["nachbarn"] = ", ".join(nachbarn)
if dbg:
dbg(f" Kreisel {kreisel_tid} Punkt {punkt_key!r} "
f"(TeileId {teileid!r}): nachbarn={nachbarn}"
f"{dbg_highlight(teileid)}")
elif dbg:
dbg(f" Kreisel {kreisel_tid} Punkt {punkt_key!r} "
f"(TeileId {teileid!r}): KEINE Nachbarn aufgeloest "
f"(vorgaenger={vorgaenger!r}, nachfolger={nachfolger!r})"
f"{dbg_highlight(teileid)}")
if dbg:
dbg("")
dbg(" -- Pass 2: ketteninterne sepliste-Nachbarn --")
# Ketteninterne sepliste-Nachbarn (SEP <-> SEP, SEP <-> AS/ES) - das
# jeweilige Kettenende (vorgaenger/nachfolger ist None, z.B. das AS-Ende
@@ -765,6 +849,10 @@ def resolve_umlauf_und_sepliste_nachbarn(items, kreisel_umlauf, sep_kette,
for key, eintrag in sep_kette.items():
item = sep_kette_by_key.get(key)
if item is None:
if dbg:
dbg(f" sepliste-Key {key!r} (typ={eintrag.get('typ')!r}): "
f"KEIN Item in sep_kette_by_key - uebersprungen "
f"(siehe map_separator_kette_items){dbg_highlight(key)}")
continue
nachbarn = []
for nachbar_key in (eintrag.get("vorgaenger"), eintrag.get("nachfolger")):
@@ -773,8 +861,15 @@ def resolve_umlauf_und_sepliste_nachbarn(items, kreisel_umlauf, sep_kette,
nachbar_item = alle_schluessel.get(nachbar_key)
if nachbar_item is not None:
nachbarn.append(nachbar_item.get("teileid", ""))
teileid = item.get("teileid", "")
if nachbarn:
item["nachbarn"] = ", ".join(nachbarn)
if dbg:
dbg(f" sepliste-Key {key!r} (TeileId {teileid!r}): "
f"vorgaenger={eintrag.get('vorgaenger')!r} "
f"nachfolger={eintrag.get('nachfolger')!r} -> "
f"nachbarn={nachbarn} item.nachbarn={item.get('nachbarn', '')!r}"
f"{dbg_highlight(teileid)}")
def write_separatorliste_merkmale(items, sep_kette_by_key, dbg=None):
@@ -1333,7 +1428,7 @@ def process_blocks(blocks, lookup, dbg=None):
write_separatorliste_merkmale(items, sep_kette_by_key, dbg=dbg)
resolve_umlauf_und_sepliste_nachbarn(
items, kreisel_umlauf, sep_kette, sep_kette_by_key, weiche_by_key)
items, kreisel_umlauf, sep_kette, sep_kette_by_key, weiche_by_key, dbg=dbg)
# Spalte "TrackIds" je Kreisel: dieselben Umlauf-Punkte (freie
# Separatoren, AS/ES-Enden, Kreisel-Kreisel-Weichen), die oben schon die
+34 -1
View File
@@ -95,6 +95,19 @@ KREISEL_UMLAUF_TEILEARTEN = {
"Eingang", "Ausgang",
}
# Temporaerer Diagnose-Filter fuers Debuglog (siehe [Nachbarschaft] debug_log
# in cfg/export.cfg): markiert Zeilen zu diesen TeileIds im .dbg-Log
# besonders, um die fehlende Nachbarn-/TrackIds-Zuordnung einzelner
# Separatoren (0025/0029/0031/0035) nachzuvollziehen. Nach Abschluss der
# Diagnose wieder auf einen leeren Set zuruecksetzen oder entfernen.
DEBUG_HIGHLIGHT_TEILEIDS = {"0025", "0029", "0031", "0035"}
def dbg_highlight(teileid):
"""Suffix fuer eine Debuglog-Zeile, wenn teileid in
DEBUG_HIGHLIGHT_TEILEIDS steht - sonst leerer String."""
return " <<<< ZIEL-ID" if str(teileid) in DEBUG_HIGHLIGHT_TEILEIDS else ""
# Nur "ILS 2.0 Kreisel" (echte Kreisel-Bloecke mit AN8/Antrieb + SP8/Spann-
# station + ABSTAND-Attribut) werden links/rechts gesplittet - "ILS 2.0
# Eckrad" hat keine dieser zwei Stationen und bleibt eine einzelne BBox.
@@ -1480,15 +1493,32 @@ def compute_kreisel_umlauf(items, sep_kette, tolerance_mm, durchmesser_mm=800.0,
for item in items:
if item.get("teileart", "") not in KREISEL_UMLAUF_TEILEARTEN:
continue
teileid = item.get("teileid", "")
bbox = item.get("_bbox")
if not bbox:
if dbg:
dbg(f" Umlauf-Kandidat {teileid!r} ({item.get('teileart', '')!r}): "
f"KEINE Bounding-Box - kann keinem Kreisel zugeordnet werden"
f"{dbg_highlight(teileid)}")
continue
bounds = _bounds(bbox, half_tol)
matched_tid = None
for tid, kb in kreisel_bounds.items():
if _overlaps(bounds, kb):
punkte_je_kreisel[tid].append(
(item.get("teileid", ""), bbox.get("cx", 0.0), bbox.get("cy", 0.0)))
(teileid, bbox.get("cx", 0.0), bbox.get("cy", 0.0)))
matched_tid = tid
break
if dbg:
if matched_tid is not None:
dbg(f" Umlauf-Kandidat {teileid!r} ({item.get('teileart', '')!r}) "
f"bbox(cx={bbox.get('cx', 0):.0f},cy={bbox.get('cy', 0):.0f}) "
f"-> Kreisel {matched_tid}{dbg_highlight(teileid)}")
else:
dbg(f" Umlauf-Kandidat {teileid!r} ({item.get('teileart', '')!r}) "
f"bbox(cx={bbox.get('cx', 0):.0f},cy={bbox.get('cy', 0):.0f}) "
f"-> KEIN Kreisel in Reichweite (bounds={_fmt_bounds(bounds)})"
f"{dbg_highlight(teileid)}")
# AS/ES-Enden jeder Kette mit sepliste: naechstgelegener Kreisel (wie
# compute_strecke_kreisel_schleus._nearest), aber direkt aus sep_kette
@@ -1591,5 +1621,8 @@ def compute_kreisel_umlauf(items, sep_kette, tolerance_mm, durchmesser_mm=800.0,
if dbg:
dbg(f" Kreisel {tid} ({drehrichtung}): Umlauf "
f"{' -> '.join(p[0] for p in sortiert)}")
for punkt_key, vorgaenger, nachfolger in umlauf:
dbg(f" Punkt {punkt_key!r}: vorgaenger={vorgaenger!r} "
f"nachfolger={nachfolger!r}{dbg_highlight(punkt_key)}")
return result
+65
View File
@@ -301,6 +301,15 @@
;; ------------------------------------------------------------
;; TESTS: export.lsp - CSV/KOS-Kodierung
;; ------------------------------------------------------------
;; Hilfsfunktion der csv:sepliste-paare-Tests: nur (lfdnr index) vergleichen,
;; nicht den Fliesskomma-Abstand. Bewusst auf Top-Level (nicht im Testblock):
;; per defun IN einem Testblock definierte Funktionen sind in BricsCAD im
;; laufenden Durchlauf nicht zuverlaessig gebunden (siehe Ladearchitektur oben).
(defun tu-paare (e p)
(mapcar (function (lambda (x) (list (nth 0 x) (nth 1 x))))
(csv:sepliste-paare e p))
)
(defun tu-tests-export ()
(princ "\n[export]")
@@ -324,6 +333,62 @@
;; json-escape: Anfuehrungszeichen maskieren
(tu-eq "csv:json-escape/quote" "a\\\"b" (csv:json-escape "a\"b"))
;; --- csv:sepliste-paare: sepliste-Eintrag <-> ausgepackte Kopie ---------
;; Reine Rechenfunktion (Eintraege + Kopien rein, (lfdnr index abstand)
;; raus) - hier ohne Zeichnung testbar. Im Export liefert
;; csv:sepliste-ids-setzen die Kopien in Erzeugungsreihenfolge; nur der
;; Index zaehlt, darum vergleichen die Tests (lfdnr index) statt der
;; Fliesskomma-Abstaende.
(tu-eq "csv:sepliste-paare/leer" nil (csv:sepliste-paare nil nil))
;; Gerade Kette AS -> SEP -> SEP -> ES; Kopien bewusst in anderer
;; Reihenfolge als die sepliste (die Erzeugungsreihenfolge der Kopien folgt
;; der Blockdefinition, nicht der Bauabfolge).
(setq _e '(("AS" 1 0.0 0.0) ("SEP" 2 1000.0 0.0) ("SEP" 3 2000.0 0.0) ("ES" 4 3000.0 0.0)))
(setq _p '(("SEP" 2000.0 0.0) ("AS" 0.0 0.0) ("ES" 3000.0 0.0) ("SEP" 1000.0 0.0)))
(tu-eq "csv:sepliste-paare/kette" '((1 1) (2 3) (3 0) (4 2)) (tu-paare _e _p))
;; EIN fehlerhafter Koordinatenwert (bekannter Fehlerfall: gespiegeltes x
;; bei lfdnr=2) darf die uebrigen Zuordnungen nicht verschieben - die
;; exklusive Vergabe teilt dem fehlerhaften Eintrag den Rest zu.
(setq _e '(("SEP" 1 1000.0 0.0) ("SEP" 2 -1000.0 -5000.0) ("SEP" 3 3000.0 0.0)))
(setq _p '(("SEP" 1000.0 0.0) ("SEP" 2000.0 0.0) ("SEP" 3000.0 0.0)))
(tu-eq "csv:sepliste-paare/gespiegelte-koordinate" '((1 0) (2 1) (3 2)) (tu-paare _e _p))
;; Gleiche Abstaende duerfen kein Paar verschlucken: vl-sort wirft
;; Elemente weg, die unter der Vergleichsfunktion gleich sind - deshalb
;; sortiert csv:sepliste-paare nach (Abstand, lfdnr, Index).
(setq _e '(("SEP" 1 0.0 0.0) ("SEP" 2 0.0 0.0)))
(setq _p '(("SEP" 0.0 0.0) ("SEP" 0.0 0.0)))
(tu-eq "csv:sepliste-paare/gleiche-abstaende" '((1 0) (2 1)) (tu-paare _e _p))
;; Typ trennt: eine AS-Kopie wird nie einem SEP-Eintrag zugeteilt, der
;; SEP-Eintrag bleibt ohne Partner (und im Export ohne ID).
(setq _e '(("AS" 1 0.0 0.0) ("SEP" 2 10.0 0.0)))
(setq _p '(("AS" 5.0 0.0)))
(tu-eq "csv:sepliste-paare/typ-trennt" '((1 0)) (tu-paare _e _p))
;; --- csv:asmark-finden: bisherigen AS/ES-Marker im Bestand wiederfinden ---
;; Schluessel ist (Wrapper-ID, Typ, lfdnr) - alle drei muessen passen, sonst
;; bekaeme ein Kettenende die ID eines fremden Markers. Bestandsformat:
;; (ename zuordnung typ lfdnr id), siehe csv:asmark-sammeln.
(setq _mk '((ME1 "0021" "AS" "1" "0031")
(ME2 "0021" "ES" "4" "0032")
(ME3 "0022" "AS" "1" "0033")))
(tu-eq "csv:asmark-finden/treffer"
'(ME2 "0021" "ES" "4" "0032") (csv:asmark-finden _mk "0021" "ES" "4"))
(tu-eq "csv:asmark-finden/anderer-wrapper"
'(ME3 "0022" "AS" "1" "0033") (csv:asmark-finden _mk "0022" "AS" "1"))
(tu-nil "csv:asmark-finden/falsche-lfdnr" (csv:asmark-finden _mk "0021" "AS" "2"))
(tu-nil "csv:asmark-finden/falscher-typ" (csv:asmark-finden _mk "0021" "SEP" "1"))
(tu-nil "csv:asmark-finden/ohne-wrapper-id" (csv:asmark-finden _mk "" "AS" "1"))
(tu-nil "csv:asmark-finden/leerer-bestand" (csv:asmark-finden nil "0021" "AS" "1"))
;; csv:proxy-typ: Typmarker aus dem Blocknamen (Grundlage der Zuordnung).
(tu-eq "csv:proxy-typ/as" "AS" (csv:proxy-typ "AS_Element_90_links_3D"))
(tu-eq "csv:proxy-typ/es" "ES" (csv:proxy-typ "ES_Element_30_rechts_2D"))
(tu-eq "csv:proxy-typ/sep" "SEP" (csv:proxy-typ "Separator_SP_3D"))
)