diff --git a/Lisp/Gefaellestrecke.lsp b/Lisp/Gefaellestrecke.lsp index 674607b..f4507e4 100644 --- a/Lisp/Gefaellestrecke.lsp +++ b/Lisp/Gefaellestrecke.lsp @@ -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)))) diff --git a/Lisp/README.md b/Lisp/README.md index fa0b878..ed1672d 100644 --- a/Lisp/README.md +++ b/Lisp/README.md @@ -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 | diff --git a/Lisp/export.lsp b/Lisp/export.lsp index 1bcc0e6..c96eae2 100644 --- a/Lisp/export.lsp +++ b/Lisp/export.lsp @@ -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) diff --git a/Lisp/ssg_dbg.lsp b/Lisp/ssg_dbg.lsp index 6876cd0..41ca6b7 100644 --- a/Lisp/ssg_dbg.lsp +++ b/Lisp/ssg_dbg.lsp @@ -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) diff --git a/Lisp/ssg_id.lsp b/Lisp/ssg_id.lsp index 76dbee3..5041677 100644 --- a/Lisp/ssg_id.lsp +++ b/Lisp/ssg_id.lsp @@ -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*") diff --git a/Lisp/ssg_ks_insert.lsp b/Lisp/ssg_ks_insert.lsp index 983d390..35e21e4 100644 --- a/Lisp/ssg_ks_insert.lsp +++ b/Lisp/ssg_ks_insert.lsp @@ -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. diff --git a/Lisp/vf_core.lsp b/Lisp/vf_core.lsp index 38138ed..83df0c4 100644 --- a/Lisp/vf_core.lsp +++ b/Lisp/vf_core.lsp @@ -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)) diff --git a/cfg/export.cfg b/cfg/export.cfg index ee7b345..7918897 100644 --- a/cfg/export.cfg +++ b/cfg/export.cfg @@ -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__). # 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) diff --git a/cfg/layer.cfg b/cfg/layer.cfg index 6a208ae..102c316 100644 --- a/cfg/layer.cfg +++ b/cfg/layer.cfg @@ -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 diff --git a/doc/csv-export.md b/doc/csv-export.md index c3538f4..e05fca0 100644 --- a/doc/csv-export.md +++ b/doc/csv-export.md @@ -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 `. @@ -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"} ] }, { diff --git a/lang/de_DE.json b/lang/de_DE.json index 2c29f7f..365fc43 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -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.", diff --git a/lang/en_GB.json b/lang/en_GB.json index c084279..50c89ae 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -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.", diff --git a/lib/export_csv.py b/lib/export_csv.py index 81cea7a..1026ec0 100644 --- a/lib/export_csv.py +++ b/lib/export_csv.py @@ -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 diff --git a/lib/export_neighbors.py b/lib/export_neighbors.py index a832530..38cbc95 100644 --- a/lib/export_neighbors.py +++ b/lib/export_neighbors.py @@ -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 diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index 29249df..b031f1d 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -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")) )