Stufe 1 (LISP-Export-Pfad): export_raw.json enthaelt jetzt fuer jeden sepliste-Eintrag (AS/SEP/ES) einer VF_n/GF_n-Kette die ID des realen Bauteils, das ihn vertritt. - csv:sep-proxies-erzeugen (Separatoren) und die neuen AS/ES-Marker teilen sich csv:nested-welt-records/csv:block-einfuegen-welt zur Platzierung. - csv:sepliste-paare ordnet sepliste-Eintraege ihrem Bauteil exklusiv nach Abstand zu (kuerzester zuerst, dann verbraucht) statt "jeder Eintrag nimmt sich das naechste" - ein einzelner fehlerhafter Koordinatenwert (bekannter Bug: gespiegeltes x bei lfdnr=2) verschiebt dadurch keine andere Zuordnung mehr. - Neu: ZZ_ASES_MARKER - ein kleines Rechteck an jedem AS/ES-Kettenende, das nach dem Export STEHEN BLEIBT (anders als die temporaeren Separator-Kopien). Macht das sonst unsichtbare, in der Wrapper- Blockdefinition verpackte Kettenende in BricsCAD sichtbar/anklickbar und traegt seine ID unsichtbar am Attribut ID - dadurch behaelt ein AS/ES seine TeileId ueber Exportlaeufe hinweg. Wird bei jedem Export neu aufgebaut (Lage folgt der Kette), Wiederfund ueber ZUORDNUNG+TYP+ LFDNR, verwaiste Marker werden entfernt. In BricsCAD verifiziert (5 HundM05-Ketten, zwei Exportlaeufe: gleiche Marker-IDs, korrekte sepliste-"id"-Felder). Dabei einen realen Bug gefunden und behoben: die Blockdefinition des Markers uebernahm ueber einen ungefilterten entnext-Walk versehentlich auch die Attribute des zuletzt eingefuegten Blocks (VF_5 verlor dadurch alle Attribute inkl. ID) - jetzt auf LWPOLYLINE/ATTDEF gefiltert. 9 neue zeichnungsfreie Unit-Tests (csv:sepliste-paare/*, csv:asmark- finden/*, csv:proxy-typ/*), 4 neue de/en-Textschluessel (Paritaet geprueft). bin\run_tests.bat --lisp: 257/257 bestanden. Noch nicht enthalten (Stufe 2, Python): map_separator_kette_items nutzt das neue "id"-Feld noch nicht, build_strecke_schleus_items synthetisiert AS/ES-Zeilen weiterhin selbst statt die exportierten zu lesen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2063 lines
94 KiB
Common Lisp
2063 lines
94 KiB
Common Lisp
;; ============================================================
|
|
;; export.lsp - Export-Funktionen fuer INSERT-Bloecke
|
|
;;
|
|
;; Enthaelt:
|
|
;; - JSON-Sammler (csv:collect-export-blocks, csv:run-export)
|
|
;; - Python-basierter Export: EXPORTSIVAS, EXPORTCSV
|
|
;;
|
|
;; Alle Blockname-Muster dieses Moduls sind zentral in cfg/export.cfg
|
|
;; (INI-Style, Abschnitt [blockpattern]) deklariert und werden beim
|
|
;; Laden des Moduls einmalig eingelesen (export:load-cfg).
|
|
;; Datei-/Skriptnamen sind fest im Code hinterlegt (*export-filenames*).
|
|
;; ============================================================
|
|
|
|
(vl-load-com)
|
|
|
|
;; --- Export-Konfiguration aus cfg/export.cfg laden ---
|
|
;; Nutzt den zentralen INI-Parser aus ssg_core.lsp (ssg-load-ini / ssg-ini-get /
|
|
;; ssg-ini-list), damit fuer alle Module derselbe .cfg-Standard gilt.
|
|
(defun export:load-cfg ( / cfg-pfad)
|
|
(setq cfg-pfad (strcat (getenv "DXFM_CFG") "/export.cfg"))
|
|
(setq *export-cfg* (ssg-load-ini cfg-pfad))
|
|
(if (null *export-cfg*)
|
|
(princ (ssg-textf "exp-cfg-not-found" (list cfg-pfad)))
|
|
)
|
|
(princ)
|
|
)
|
|
|
|
;; --- Einzelwert aus *export-cfg* lesen, mit Fallback ---
|
|
;; section = "blockpattern"
|
|
(defun export:cfg (section key default)
|
|
(ssg-ini-get *export-cfg* section key default)
|
|
)
|
|
|
|
;; --- Muster aus [blockpattern] als normalisierter wcmatch-String ---
|
|
;; key = Config-Key, z.B. "pattern_kreisel"
|
|
;; default-str = Fallback-Wert als kommagetrennter String, falls Config fehlt
|
|
;; Rueckgabe: kommagetrennter String ohne Leerzeichen, z.B. "KR_*,KREISEL_*"
|
|
(defun export:pattern (key default-str)
|
|
(ssg-ini-list *export-cfg* "blockpattern" key default-str)
|
|
)
|
|
|
|
;; Beim Laden des Moduls einmalig einlesen (Lazy-Loading via ssg-ensure "export")
|
|
(export:load-cfg)
|
|
|
|
;; --- Test-Override fuer den Ausgabeordner (nur Lisp-Sitzungsspeicher) ---
|
|
;; Wird ausschliesslich von TEST_EXPORT_ALL (tests/test_export_all.lsp)
|
|
;; waehrend des Testlaufs auf DXFM_TESTOUT gesetzt und danach garantiert
|
|
;; wieder auf nil zurueckgesetzt. Bewusst KEIN (setenv "DXFM_RESULTS" ...):
|
|
;; (setenv ...) schreibt in BricsCAD dauerhaft in die Profil-Registry und
|
|
;; ueberlebt damit jeden Neustart.
|
|
(setq *export-test-override* nil)
|
|
|
|
|
|
;; --- Feste Datei-/Skriptnamen dieses Moduls (keine Config, direkt im Code) ---
|
|
(setq *export-filenames*
|
|
(list
|
|
(cons "raw_json_datei" "export_raw.json")
|
|
(cons "sivas_script" "export_sivas.py")
|
|
(cons "sivas_csv" "sivas.csv")
|
|
(cons "csv_script" "export_csv.py")
|
|
(cons "csv_csv" "export.csv")
|
|
)
|
|
)
|
|
|
|
;; --- Dateinamen aus *export-filenames* lesen ---
|
|
(defun export:filename (key)
|
|
(cdr (assoc key *export-filenames*))
|
|
)
|
|
|
|
;; --- Zeichnungsname als Dateiname-Praefix ermitteln ---
|
|
;; Liefert "<Zeichnungsname>_" oder "" (leer), wenn die Zeichnung noch nie
|
|
;; gespeichert wurde (DWGTITLED=0, Name z.B. "Zeichnung1"/"Drawing1").
|
|
;; So heisst die Export-CSV bei einer benannten Zeichnung "<name>_export.csv"
|
|
;; bzw. "<name>_sivas.csv" statt nur "export.csv"/"export_sivas.csv" - bei
|
|
;; mehreren offenen/parallel bearbeiteten Zeichnungen sonst nicht unterscheidbar.
|
|
(defun export:dwg-praefix ( / dwgname)
|
|
(if (= (getvar "DWGTITLED") 0)
|
|
""
|
|
(progn
|
|
(setq dwgname (getvar "DWGNAME"))
|
|
(if (and dwgname (/= dwgname ""))
|
|
(strcat (vl-filename-base dwgname) "_")
|
|
""
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
;; ============================================================
|
|
;; PYTHON-INTERPRETER ERMITTELN
|
|
;; ============================================================
|
|
;; BricsCAD erbt seine Umgebung von start_briscad.bat/setenv.bat - dort wird
|
|
;; das Projekt-venv NICHT aktiviert. Ein blankes "python" trifft unter Windows
|
|
;; 11 daher den WindowsApps-Platzhalter
|
|
;; (%LOCALAPPDATA%\Microsoft\WindowsApps\python.exe), der kein Interpreter ist,
|
|
;; sondern nur auf den Microsoft Store verweist und mit Exitcode 49 abbricht.
|
|
;; Der Export schrieb dann zwar die JSON, aber nie eine CSV.
|
|
;;
|
|
;; Aufloesungsreihenfolge (erster Treffer gewinnt):
|
|
;; 1. cfg/export.cfg [python] interpreter= (explizite Vorgabe)
|
|
;; 2. Umgebungsvariable DXFM_PYTHON (von setenv.bat gesetzt)
|
|
;; 3. <DXFMAKRO>/.venv/Scripts/python.exe (Projekt-venv, siehe install_py.bat)
|
|
;; 4. "py" (Python-Launcher, C:\Windows\py.exe) - kein Store-Platzhalter
|
|
;; 5. "python" (PATH) als letzter Ausweg
|
|
(defun export:python-store-stub-p (pfad / p)
|
|
(setq p (strcase (vl-string-translate "\\" "/" pfad)))
|
|
(and (vl-string-search "/WINDOWSAPPS/" p) T)
|
|
)
|
|
|
|
;; --- Kandidat pruefen: existierende Datei und kein Store-Platzhalter ---
|
|
(defun export:python-kandidat-ok-p (pfad)
|
|
(and pfad
|
|
(/= pfad "")
|
|
(findfile pfad)
|
|
(not (export:python-store-stub-p pfad)))
|
|
)
|
|
|
|
(defun export:python-exe ( / cfg-wert env-wert venv-pfad launcher-pfad)
|
|
(cond
|
|
;; 1. Explizite Vorgabe aus der Config
|
|
((export:python-kandidat-ok-p
|
|
(setq cfg-wert (export:cfg "python" "interpreter" "")))
|
|
cfg-wert)
|
|
;; 2. Umgebungsvariable
|
|
((export:python-kandidat-ok-p (setq env-wert (getenv "DXFM_PYTHON")))
|
|
env-wert)
|
|
;; 3. Projekt-venv
|
|
((export:python-kandidat-ok-p
|
|
(setq venv-pfad (strcat (cond ((getenv "DXFMAKRO")) ("."))
|
|
"/.venv/Scripts/python.exe")))
|
|
venv-pfad)
|
|
;; 4. Python-Launcher (liegt in C:\Windows, daher kein Store-Platzhalter).
|
|
;; Voller Pfad statt nur "py.exe": findfile durchsucht die BricsCAD-
|
|
;; Suchpfade, nicht zwangslaeufig den System-PATH.
|
|
((export:python-kandidat-ok-p
|
|
(setq launcher-pfad (strcat (cond ((getenv "SystemRoot")) ("C:/Windows"))
|
|
"/py.exe")))
|
|
launcher-pfad)
|
|
;; 5. Letzter Ausweg - kann der Store-Platzhalter sein, deshalb wird der
|
|
;; Exitcode nach dem Aufruf geprueft (siehe export:run-python).
|
|
(t "python")
|
|
)
|
|
)
|
|
|
|
|
|
;; --- Python-Skript synchron ausfuehren, Ausgabe in Logdatei ---
|
|
;; Ersetzt das frueherer "fire and forget" (startapp "cmd" "/c ..."): das
|
|
;; cmd-Fenster schloss sich sofort wieder, jede Python-Fehlermeldung (fehlender
|
|
;; Interpreter, Traceback) war unsichtbar und der Befehl meldete trotzdem
|
|
;; "Export gestartet". Jetzt wird ueber WScript.Shell.Run mit bWaitOnReturn=T
|
|
;; gewartet, stdout/stderr nach DXFM_LOG/export_python.log umgeleitet und der
|
|
;; Exitcode ausgewertet.
|
|
;; Rueckgabe: Exitcode (0 = ok) oder nil, wenn der Aufruf selbst scheiterte.
|
|
(defun export:run-python (py-exe py-skript args log-pfad / cmd sh rc code)
|
|
;; Aeussere Anfuehrungszeichen um die gesamte /c-Zeile: sonst verschluckt
|
|
;; cmd.exe die Quotes um den Interpreterpfad (Leerzeichen in "Program Files").
|
|
(setq cmd (strcat "cmd.exe /c \"\"" py-exe "\""))
|
|
(setq cmd (strcat cmd " \"" py-skript "\""))
|
|
(foreach a args (setq cmd (strcat cmd " \"" a "\"")))
|
|
(setq cmd (strcat cmd " > \"" log-pfad "\" 2>&1\""))
|
|
;; Run(cmd, 0, T): 0 = kein Fenster, T = warten und Exitcode liefern.
|
|
(setq rc
|
|
(vl-catch-all-apply
|
|
'(lambda ()
|
|
(setq sh (vlax-create-object "WScript.Shell"))
|
|
(setq code (vlax-invoke sh 'Run cmd 0 :vlax-true))
|
|
(vlax-release-object sh)
|
|
(setq sh nil)
|
|
code)))
|
|
(if (vl-catch-all-error-p rc)
|
|
(progn
|
|
(if sh (vl-catch-all-apply 'vlax-release-object (list sh)))
|
|
nil)
|
|
rc
|
|
)
|
|
)
|
|
|
|
|
|
;; --- Erste Zeilen der Python-Logdatei in die Kommandozeile spiegeln ---
|
|
;; Damit steht die eigentliche Fehlerursache direkt in BricsCAD und nicht nur
|
|
;; in der Logdatei. max-zeilen begrenzt die Ausgabe (Tracebacks koennen lang sein).
|
|
(defun export:log-ausgeben (log-pfad max-zeilen / fh zeile n)
|
|
(if (setq fh (open log-pfad "r"))
|
|
(progn
|
|
(setq n 0)
|
|
(while (and (setq zeile (read-line fh)) (< n max-zeilen))
|
|
(if (/= zeile "")
|
|
(progn (princ (strcat "\n | " zeile)) (setq n (1+ n)))
|
|
)
|
|
)
|
|
(close fh)
|
|
)
|
|
)
|
|
(princ)
|
|
)
|
|
|
|
|
|
;; --- String fuer JSON escapen (Anfuehrungszeichen/Backslash) ---
|
|
;; Konvertiert nicht-String-Werte (z.B. T, nil, Zahlen) defensiv in String.
|
|
;; ssg-str-replace-all (ssg_lang.lsp) statt vl-string-subst: vl-string-subst
|
|
;; ersetzt nur das ERSTE Vorkommen, nicht alle - bei einem Attributwert mit
|
|
;; MEHREREN Anfuehrungszeichen (z.B. BEZEICHNUNG einer Geruest-Baugruppe mit
|
|
;; zwei Zoll-Angaben wie 'F. 2" UND 1" ROHR') blieb das zweite Vorkommen
|
|
;; unescaped stehen und brach die JSON-Struktur beim Python-Parsen (realer
|
|
;; Bug, behoben) - export_raw.json/EXPORTCSV.
|
|
(defun csv:json-escape (s)
|
|
(if (not (= (type s) 'STR))
|
|
(setq s (if s (vl-princ-to-string s) ""))
|
|
)
|
|
(setq s (ssg-str-replace-all "\\\\" "\\" s))
|
|
(ssg-str-replace-all "\\\"" "\"" s)
|
|
)
|
|
|
|
|
|
;; --- Safearray/Variant-Rueckgabe von vla-getboundingbox in Liste wandeln ---
|
|
;; BricsCAD liefert ein Safearray, AutoCAD ein Variant mit Safearray.
|
|
(defun csv:pt->list (v)
|
|
(cond
|
|
((= (type v) 'variant) (vlax-safearray->list (vlax-variant-value v)))
|
|
((= (type v) 'safearray) (vlax-safearray->list v))
|
|
((listp v) v)
|
|
(t nil)
|
|
)
|
|
)
|
|
|
|
;; ============================================================
|
|
;; KOS-KOMPRIMIERUNG (Position + Rotation als 24-Zeichen Base64-String)
|
|
;; ============================================================
|
|
;; Kodiert Position (x,y,z in mm) und Rotation (als Quaternion qx,qy,qz,qw)
|
|
;; verlustarm (24 Bit Fixed-Point je Wert) in einen 24-Zeichen Base64-String.
|
|
;; Ported aus einer interaktiven Referenz-Routine (c:ExportKOS), hier als
|
|
;; reine Funktion fuer den automatischen Export nutzbar. Alle Elemente in
|
|
;; diesem Projekt werden ausschliesslich um die Z-Achse gedreht (siehe
|
|
;; DREHUNG-Konvention weiter unten), daher genuegt eine Halbwinkel-Quaternion
|
|
;; fuer eine reine Z-Rotation (csv:z-angle-to-quat) -- keine vollstaendige
|
|
;; Rotationsmatrix/Quaternion-Extraktion noetig.
|
|
(setq *csv-b64-chars* "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
|
|
|
|
;; --- Wert auf [min-val, max-val] begrenzen ---
|
|
(defun csv:clamp (val min-val max-val)
|
|
(max min-val (min val max-val))
|
|
)
|
|
|
|
;; --- Rotationswinkel (Grad, reine Z-Drehung) -> Quaternion (qx qy qz qw) ---
|
|
(defun csv:z-angle-to-quat (deg / rad)
|
|
(setq rad (/ (* deg pi) 180.0))
|
|
(list 0.0 0.0 (sin (/ rad 2.0)) (cos (/ rad 2.0)))
|
|
)
|
|
|
|
;; --- Liste vorzeichenbehafteter 24-Bit-Integer -> Base64 (je 4 Zeichen) ---
|
|
;; Gemeinsamer Kodierer fuer csv:kos-encode und csv:trans-encode.
|
|
(defun csv:b64-encode-ints (int-list / b64-str val)
|
|
(setq b64-str "")
|
|
(foreach val int-list
|
|
(if (< val 0) (setq val (+ val 16777216))) ; Zweierkomplement (24 Bit)
|
|
(setq b64-str (strcat b64-str
|
|
(substr *csv-b64-chars* (1+ (lsh val -18)) 1)
|
|
(substr *csv-b64-chars* (1+ (logand (lsh val -12) 63)) 1)
|
|
(substr *csv-b64-chars* (1+ (logand (lsh val -6) 63)) 1)
|
|
(substr *csv-b64-chars* (1+ (logand val 63)) 1)
|
|
))
|
|
)
|
|
b64-str
|
|
)
|
|
|
|
;; --- Position (mm) + Quaternion -> 24-Zeichen Base64-String ---
|
|
;; Fixed-Point: x,y,z (Faktor 1000 = mm) und qx,qy,qz werden je auf 24 Bit
|
|
;; gerundet (6 Werte * 4 Zeichen = 24 Zeichen). qw wird nicht mitkodiert,
|
|
;; sondern nur zur Vorzeichen-Normierung verwendet (q und -q sind dieselbe
|
|
;; Rotation). Fuer den Insertpoint (volle Lage), siehe csv:trans-encode fuer
|
|
;; reine Positionen (K1-K4).
|
|
(defun csv:kos-encode (x y z qx qy qz qw)
|
|
(if (< qw 0.0)
|
|
(setq qx (- qx) qy (- qy) qz (- qz))
|
|
)
|
|
(csv:b64-encode-ints (list
|
|
(fix (csv:clamp (* x 1000.0) -8388607 8388607))
|
|
(fix (csv:clamp (* y 1000.0) -8388607 8388607))
|
|
(fix (csv:clamp (* z 1000.0) -8388607 8388607))
|
|
(fix (csv:clamp (* qx 8388607.0) -8388607 8388607))
|
|
(fix (csv:clamp (* qy 8388607.0) -8388607 8388607))
|
|
(fix (csv:clamp (* qz 8388607.0) -8388607 8388607))
|
|
))
|
|
)
|
|
|
|
;; --- Position (nur x,y,z) -> 12-Zeichen Base64-String ---
|
|
;; Fixed-Point Faktor 10.0 (0.1 mm Genauigkeit, Bereich +-838 m), 3 Werte *
|
|
;; 4 Zeichen = 12 Zeichen. Fuer die Koordinatensysteme K1-K4 (nur Position,
|
|
;; keine Rotation). Ported aus der Referenz-Routine c:ExportTrans.
|
|
(defun csv:trans-encode (x y z)
|
|
(csv:b64-encode-ints (list
|
|
(fix (csv:clamp (* x 10.0) -8388607 8388607))
|
|
(fix (csv:clamp (* y 10.0) -8388607 8388607))
|
|
(fix (csv:clamp (* z 10.0) -8388607 8388607))
|
|
))
|
|
)
|
|
|
|
;; --- Lokalen Punkt (relativ zum Block-Ursprung) unter Beruecksichtigung der
|
|
;; VOLLEN 3D-Orientierung des Elternblocks in Weltkoordinaten umrechnen ---
|
|
;; parent-R = volle 3x3-Rotationsmatrix (mat3-* in ssg_core.lsp), nicht nur
|
|
;; ein Z-Drehwinkel: ein Elternblock kann selbst eine gekippte Extrusion
|
|
;; tragen (z.B. ein Separator-Proxy, der aus dem geneigten Abschnitt einer
|
|
;; VF-/GF-Kette neu zusammengesetzt wurde, siehe csv:block-to-json) - eine
|
|
;; reine Z-Drehung wuerde dessen Sub-Koordinatensysteme (K1-K4/KS_EIN/
|
|
;; KS_AUS) an einen falschen, teils gespiegelten Weltpunkt legen. Fuer einen
|
|
;; flachen Elternblock (Extrusion (0 0 1)) ist parent-R = Rz(rotation) und
|
|
;; das Ergebnis identisch zur frueheren reinen Z-Drehung. Rueckgabe: (x y z).
|
|
(defun csv:local-to-world (parent-pt parent-R local-pt / lz pz rot-pt)
|
|
(setq lz (if (caddr local-pt) (caddr local-pt) 0.0))
|
|
(setq pz (if (caddr parent-pt) (caddr parent-pt) 0.0))
|
|
(setq rot-pt (mat3-mul-vec3 parent-R (list (car local-pt) (cadr local-pt) lz)))
|
|
(list
|
|
(+ (car parent-pt) (car rot-pt))
|
|
(+ (cadr parent-pt) (cadr rot-pt))
|
|
(+ pz (caddr rot-pt))
|
|
)
|
|
)
|
|
|
|
;; --- K1-K4-Koordinatensysteme eines Blocks als Positions-Strings lesen ---
|
|
;; ename = Entity-Name des platzierten INSERT (Omniflo Bogen/Weiche/Gerade).
|
|
;; pt/parent-R = bereits ermittelte Welt-Position/volle 3x3-Orientierung des
|
|
;; Eltern-INSERT (siehe csv:block-to-json), keine erneute Abfrage.
|
|
;; Sucht in der Block-DEFINITION (wie omni:get-block-hoehe) nach direkten
|
|
;; Sub-INSERTs "K1".."K4" und rechnet deren lokale Position ueber die volle
|
|
;; Eltern-Orientierung in Weltkoordinaten um (csv:local-to-world). Es wird
|
|
;; nur die POSITION kodiert (12-Zeichen-String, csv:trans-encode) - keine
|
|
;; Rotation.
|
|
;; Zusaetzlich werden Strecken-Koordinatensysteme KS_EIN->K1 und KS_AUS->K2
|
|
;; (inkl. der KSYS_-Varianten, siehe ks-normalize-name in vf_core.lsp) gemappt,
|
|
;; sofern kein echter K1/K2 am Block vorhanden ist (echte K-Bloecke haben
|
|
;; Vorrang: sie werden vorne angehaengt, csv:k-kos liest per assoc den ersten).
|
|
;; Rueckgabe: Assoc-Liste (("K1" . "<pos-string>") ("K2" . ...) ...), nur
|
|
;; fuer tatsaechlich vorhandene K-Bloecke (nicht jedes Element hat alle 4).
|
|
;;
|
|
;; Gruppe-10 eines Sub-INSERTs ist OCS-lokal zu SEINER EIGENEN Extrusion
|
|
;; (Gruppe 210), nicht zwingend zur flachen XY-Ebene der Elterndefinition -
|
|
;; z.B. KS_AUS von SC_Entladung (BTMT-Entladung), das schraeg in 3D sitzt.
|
|
;; mat3-ocs-matrix/mat3-mul-vec3 (ssg_core.lsp, Arbitrary Axis Algorithm)
|
|
;; wandeln das erst in echte lokale Koordinaten um, bevor csv:local-to-world
|
|
;; mit der Eltern-Rotation weiterrechnet - derselbe Fix wie bereits in
|
|
;; ssg-collect-nested-inserts-rel (dort ausfuehrlich begruendet). Ohne diese
|
|
;; Umrechnung landete K2 tief unterhalb statt oberhalb des Elements (realer
|
|
;; Bug, sichtbar in der Materialfluss-Draufsicht sps_skel/HundM05_bbs.svg).
|
|
;; Fuer Extrusion=(0 0 1) (Normalfall, z.B. KS_EIN) reduziert sich das exakt
|
|
;; auf die alte reine Uebernahme - unveraendertes Verhalten dort. Denselben
|
|
;; Grund hat parent-R als VOLLE Matrix statt eines Z-Drehwinkels: ein
|
|
;; Elternblock (z.B. ein Separator-Proxy, ueber ssg-collect-nested-inserts
|
|
;; aus dem geneigten Abschnitt einer VF-/GF-Kette neu zusammengesetzt) kann
|
|
;; selbst eine gekippte Extrusion tragen - eine reine Z-Drehung des Eltern-
|
|
;; INSERT legte dessen K1/K2 dann an einen falschen, teils gespiegelten
|
|
;; Weltpunkt (realer Bug: K1 landete beim Vorzeichen von X/Y gespiegelt weit
|
|
;; ausserhalb der eigenen Boundingbox, sichtbar als fehlendes K-Kreuz in
|
|
;; sps_skel/HundM05_bbs.svg trotz gefuelltem K1 in der CSV).
|
|
(defun csv:get-k-kos-strings (ename pt parent-R / ed bname blk-tbl sub-ent sub-ed
|
|
sub-bname sub-pt sub-extrusion sub-pt-real world-pt
|
|
target is-real result)
|
|
(setq ed (entget ename))
|
|
(setq bname (cdr (assoc 2 ed)))
|
|
(setq blk-tbl (tblsearch "BLOCK" bname))
|
|
(setq result nil)
|
|
(if blk-tbl
|
|
(progn
|
|
;; (assoc -2) ist bereits das ERSTE Entity der Blockdefinition, NICHT
|
|
;; der BLOCK-Header - direkt hier starten, kein entnext davor (das
|
|
;; uebersprang sonst ein K1/KS_EIN, das zufaellig als erstes Entity in
|
|
;; der Blockdefinition steht - derselbe Bug wie frueher in
|
|
;; ssg-collect-nested-inserts-rel, siehe dortiger Kommentar).
|
|
(setq sub-ent (cdr (assoc -2 blk-tbl)))
|
|
(while sub-ent
|
|
(setq sub-ed (entget sub-ent))
|
|
(if (= (cdr (assoc 0 sub-ed)) "INSERT")
|
|
(progn
|
|
(setq sub-bname (strcase (cdr (assoc 2 sub-ed))))
|
|
;; Ziel-Slot bestimmen: echte K1-K4 direkt, KS_EIN/KS_AUS gemappt.
|
|
(setq is-real (wcmatch sub-bname "K1,K2,K3,K4"))
|
|
(setq target
|
|
(cond
|
|
(is-real sub-bname)
|
|
((or (= sub-bname "KS_EIN") (= sub-bname "KSYS_EIN")) "K1")
|
|
((or (= sub-bname "KS_AUS") (= sub-bname "KSYS_AUS")) "K2")
|
|
(t nil)))
|
|
;; Echte K-Bloecke immer aufnehmen (vorne = Vorrang bei assoc);
|
|
;; KS-abgeleitete nur, wenn der Slot noch nicht belegt ist.
|
|
(if (and target (or is-real (not (assoc target result))))
|
|
(progn
|
|
(setq sub-pt (cdr (assoc 10 sub-ed)))
|
|
(if (null (caddr sub-pt))
|
|
(setq sub-pt (list (car sub-pt) (cadr sub-pt) 0.0)))
|
|
(setq sub-extrusion (cond ((cdr (assoc 210 sub-ed))) ('(0.0 0.0 1.0))))
|
|
(setq sub-pt-real (mat3-mul-vec3 (mat3-ocs-matrix sub-extrusion) sub-pt))
|
|
(setq world-pt (csv:local-to-world pt parent-R sub-pt-real))
|
|
(setq result (cons
|
|
(cons target
|
|
(csv:trans-encode
|
|
(car world-pt) (cadr world-pt) (caddr world-pt)))
|
|
result))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
(setq sub-ent (entnext sub-ent))
|
|
)
|
|
)
|
|
)
|
|
result
|
|
)
|
|
|
|
;; --- Wert aus csv:get-k-kos-strings nach Name lesen, "" falls nicht vorhanden ---
|
|
(defun csv:k-kos (k-list name / found)
|
|
(setq found (assoc name k-list))
|
|
(if found (cdr found) "")
|
|
)
|
|
|
|
;; --- K1 (Anfang) / K2 (Ende) einer Omniflo-Gerade SYNTHETISIEREN ---
|
|
;; ename = Entity-Name des platzierten Gerade-INSERT.
|
|
;; pt/rot-rad = Welt-Einfuegepunkt (= Anfang) und -Rotation (radiant) des
|
|
;; Blocks (siehe csv:block-to-json).
|
|
;; Geraden fuehren - anders als Boegen/Weichen - KEINE echten K-Bloecke in der
|
|
;; Zeichnung. Fuer den Export werden ihre Anschluss-Koordinatensysteme aus der
|
|
;; Geometrie berechnet, als laegen dort echte KOS: K1 am Einfuegepunkt (Anfang
|
|
;; der lokalen Linie 0,0), K2 am Ende (laenge,0), ueber die Block-Rotation in
|
|
;; Weltkoordinaten gedreht. Laenge aus dem LAENGE- bzw. A-Attribut. Damit
|
|
;; koennen Geraden in der Python-Nachbarschaftserkennung (export_neighbors.py)
|
|
;; genauso ueber ihre KOS gegen andere Geraden/Boegen/Weichen geprueft werden.
|
|
;; Rueckgabe wie csv:get-k-kos-strings: (("K1" . str) ("K2" . str)) oder nil.
|
|
(defun csv:gerade-k-kos-strings (ename pt rot-rad / laenge-str laenge z end-pt)
|
|
(setq laenge-str (cond ((csv:get-attrib ename "LAENGE"))
|
|
((csv:get-attrib ename "A"))
|
|
(t nil)))
|
|
(if (and laenge-str (> (strlen laenge-str) 0))
|
|
(progn
|
|
(setq laenge (atof laenge-str))
|
|
(setq z (if (caddr pt) (caddr pt) 0.0))
|
|
(setq end-pt (list (+ (car pt) (* laenge (cos rot-rad)))
|
|
(+ (cadr pt) (* laenge (sin rot-rad)))
|
|
z))
|
|
(list
|
|
(cons "K1" (csv:trans-encode (car pt) (cadr pt) z))
|
|
(cons "K2" (csv:trans-encode (car end-pt) (cadr end-pt) (caddr end-pt))))
|
|
)
|
|
nil
|
|
)
|
|
)
|
|
|
|
;; --- KS-Position eines VF/GF-Wrappers ueber das RICHTIGE Element ---
|
|
;; bname = Blockname des VF_n/GF_n-Wrappers.
|
|
;; elem-pat = Muster des KS-tragenden Teilbloecks ("AS_ELEMENT*" bzw.
|
|
;; "ES_ELEMENT*", Grossschreibung - wcmatch ist unempfindlich).
|
|
;; ks-pat = Muster des gesuchten Koordinatensystems ("KS_EIN,KSYS_EIN"
|
|
;; bzw. "KS_AUS,KSYS_AUS").
|
|
;; Warum zwei Ebenen: das AS_Element UND das ES_Element eines VF_n fuehren
|
|
;; JEWEILS ein KS_EIN UND ein KS_AUS (siehe check_hoehen.lsp ch-measure-vfgf).
|
|
;; Ein flaches (ssg-collect-nested-inserts bname "KS_AUS") ueber den GANZEN
|
|
;; Wrapper lieferte darum zwei Treffer und (car ...) griff den falschen (den
|
|
;; des AS_Elements) - K2/ES landete auf der falschen (Anfangs-)Seite. Darum
|
|
;; ERST das passende Element (AS/ES) suchen und NUR IN DESSEN Blockdefinition
|
|
;; das KS. Die KS-Position wird durch die Weltlage des Elements (world-pt/
|
|
;; R-world/Skalierung aus dem Element-Record) in das Koordinatensystem der
|
|
;; Wrapper-Blockdefinition transformiert - dieselbe Verkettung wie in
|
|
;; ssg-collect-nested-inserts-rel (OCS ist in den Records bereits aufgeloest).
|
|
;; Rueckgabe: (x y z) im Wrapper-Definitionsframe, oder nil wenn nicht gefunden.
|
|
(defun csv:vfgf-ks-loc (bname elem-pat ks-pat / elem-hits elem-rec elem-bname
|
|
elem-pt elem-R elem-sx elem-sy elem-sz ks-hits ks-loc
|
|
scaled)
|
|
(setq elem-hits (ssg-collect-nested-inserts bname elem-pat))
|
|
(if elem-hits
|
|
(progn
|
|
(setq elem-rec (car elem-hits))
|
|
(setq elem-bname (strcase (cdr (assoc 2 (entget (car elem-rec))))))
|
|
;; Nur das KS INNERHALB dieser Element-Blockdefinition (nicht im Wrapper).
|
|
(setq ks-hits (ssg-collect-nested-inserts elem-bname ks-pat))
|
|
(if ks-hits
|
|
(progn
|
|
(setq elem-pt (cadr elem-rec))
|
|
(setq elem-R (caddr elem-rec))
|
|
(setq elem-sx (nth 3 elem-rec) elem-sy (nth 4 elem-rec) elem-sz (nth 5 elem-rec))
|
|
(setq ks-loc (cadr (car ks-hits))) ;; KS-Pos im Element-Frame
|
|
(setq scaled (list (* elem-sx (car ks-loc))
|
|
(* elem-sy (cadr ks-loc))
|
|
(* elem-sz (caddr ks-loc))))
|
|
;; Element-Weltlage (im Wrapper-Frame) + rotierte KS-Position.
|
|
(mapcar '+ elem-pt (mat3-mul-vec3 elem-R scaled))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
;; --- K1 (Eingang) / K2 (Ausgang) eines VF_n/GF_n-Wrappers ableiten ---
|
|
;; ename = Entity-Name des platzierten VF_n/GF_n-INSERT.
|
|
;; pt/rot-rad = Welt-Einfuegepunkt/-Rotation (radiant) des Wrappers (siehe
|
|
;; csv:block-to-json).
|
|
;; VF/GF-Wrapper fuehren - anders als Omniflo-Boegen - KEINE direkten K1-K4-
|
|
;; oder KS-Sub-INSERTs; ihre Streckenenden stecken je EINE Ebene tiefer:
|
|
;; KS_EIN im AS_Element*, KS_AUS im ES_Element* (siehe check_hoehen.lsp,
|
|
;; extract-ks-from-block-raw). csv:vfgf-ks-loc holt die KS-Position aus dem
|
|
;; JEWEILS zugehoerigen Element (nicht flach aus dem ganzen Wrapper - beide
|
|
;; Elemente tragen beide KS-Typen). Der so ermittelte Punkt (Wrapper-Frame)
|
|
;; wird ueber die Wrapper-Weltlage (reine Z-Drehung) in Weltkoordinaten
|
|
;; gedreht und wie K1-K4 als reiner Positions-String (csv:trans-encode)
|
|
;; exportiert. Damit hat auch eine Strecke K1 und K2 fuer die
|
|
;; Python-Kollisionspruefung (export_neighbors.py: Schleuselemente +
|
|
;; Kreisel-Kollision ueber die AS/ES-Position mit fester BBox statt der
|
|
;; ganzen Wrapper-Box).
|
|
;;
|
|
;; RICHTUNG: K1 ist IMMER der Eingang eines Elements, K2 IMMER der Ausgang.
|
|
;; AS = Eingangs-Element (traegt KS_EIN), ES = Ausgangs-Element (traegt KS_AUS).
|
|
;; - VarioFoerderer/Strecke (VF_*): Richtung liegt ueber die Elementrolle
|
|
;; fest -> K1 = AS-Ende (KS_EIN), K2 = ES-Ende (KS_AUS).
|
|
;; - Gefaellestrecke (GF_*): Foerderrichtung ist bergAB, also durch die HOEHE
|
|
;; bestimmt (siehe HOEHE_VON_mm > HOEHE_BIS_mm, check_hoehen.lsp). Der
|
|
;; Eingang ist das HOEHERE Ende, der Ausgang das TIEFERE - unabhaengig
|
|
;; davon, welches der beiden physischen Elemente das AS bzw. ES ist. Darum
|
|
;; wird bei GF nach Z sortiert (hoeheres Z -> K1, tieferes Z -> K2).
|
|
;; Rueckgabe wie csv:get-k-kos-strings: (("K1" . str) ("K2" . str)) - nur die
|
|
;; tatsaechlich gefundenen Enden.
|
|
(defun csv:vfgf-k-kos-strings (ename pt rot-rad / ed bname as-loc es-loc
|
|
as-world es-world ein-world aus-world result)
|
|
(setq ed (entget ename))
|
|
(setq bname (cdr (assoc 2 ed)))
|
|
(setq result nil)
|
|
;; Beide Enden im Wrapper-Frame holen und in Weltkoordinaten umrechnen.
|
|
;; VF_n/GF_n-Wrapper stehen an ihrem eigenen top-level INSERT immer flach
|
|
;; (Extrusion (0 0 1) - siehe csv:sep-proxies-erzeugen), darum genuegt hier
|
|
;; weiterhin die reine Z-Drehungsmatrix (mat3-rz) statt der vollen
|
|
;; Elternorientierung, die csv:get-k-kos-strings inzwischen braucht (siehe
|
|
;; dort). AS traegt das KS_EIN, ES das KS_AUS - jeweils aus dem RICHTIGEN
|
|
;; Element (csv:vfgf-ks-loc; beide Elemente fuehren beide KS-Typen).
|
|
(setq as-loc (csv:vfgf-ks-loc bname "AS_ELEMENT*" "KS_EIN,KSYS_EIN"))
|
|
(setq es-loc (csv:vfgf-ks-loc bname "ES_ELEMENT*" "KS_AUS,KSYS_AUS"))
|
|
(if as-loc (setq as-world (csv:local-to-world pt (mat3-rz rot-rad) as-loc)))
|
|
(if es-loc (setq es-world (csv:local-to-world pt (mat3-rz rot-rad) es-loc)))
|
|
;; Eingang/Ausgang festlegen (siehe Richtung im Kopfkommentar).
|
|
(cond
|
|
;; GF: Richtung ueber die Hoehe - hoeheres Z = Eingang, tieferes Z = Ausgang.
|
|
;; Nur wenn BEIDE Enden vorhanden sind; sonst faellt es auf die Rolle zurueck.
|
|
((and (wcmatch (strcase bname) "GF_*") as-world es-world)
|
|
(if (>= (caddr as-world) (caddr es-world))
|
|
(setq ein-world as-world aus-world es-world)
|
|
(setq ein-world es-world aus-world as-world)))
|
|
;; VF/Strecke (und GF mit nur einem Ende): AS = Eingang, ES = Ausgang.
|
|
(t (setq ein-world as-world aus-world es-world))
|
|
)
|
|
(if ein-world
|
|
(setq result (cons (cons "K1"
|
|
(csv:trans-encode (car ein-world) (cadr ein-world) (caddr ein-world))) result)))
|
|
(if aus-world
|
|
(setq result (cons (cons "K2"
|
|
(csv:trans-encode (car aus-world) (cadr aus-world) (caddr aus-world))) result)))
|
|
(reverse result)
|
|
)
|
|
|
|
|
|
;; --- Mindesthoehe der Bounding-Box (Z-Ausdehnung), aus cfg/export.cfg ---
|
|
;; [Boundingbox] bb_minimum_height_mm, Default 1 (mm) falls nicht gesetzt.
|
|
;; Verhindert dz=0 bei flachen 2D-Bloecken (Draufsicht ohne Z-Ausdehnung).
|
|
(defun csv:bb-minimum-height-mm ()
|
|
(atof (export:cfg "Boundingbox" "bb_minimum_height_mm" "1"))
|
|
)
|
|
|
|
;; Diagnose fuer csv:get-bbox: zaehlt fehlgeschlagene vla-getboundingbox-
|
|
;; Aufrufe und merkt sich die ERSTE Fehlermeldung samt betroffenem Block. Wird
|
|
;; von csv:run-export vor der Export-Schleife zurueckgesetzt und danach
|
|
;; ausgegeben - so sieht man in der BricsCAD-Konsole sofort, WARUM eine bbox
|
|
;; fehlt (frueher wurde der Fehler still zu nil verschluckt, und im JSON fehlte
|
|
;; nur das Feld). *csv-bbox-fail-count* = Anzahl, *csv-bbox-fail-first* =
|
|
;; "(Blockname) Meldung" des ersten Fehlers.
|
|
(setq *csv-bbox-fail-count* 0)
|
|
(setq *csv-bbox-fail-first* nil)
|
|
|
|
;; --- Bounding-Box (WCS, achsparallel) eines INSERT-Blocks ermitteln ---
|
|
;; Erzwingt vorher ein vla-update (Regen), da vla-getboundingbox sonst auf
|
|
;; nicht-regenerierten oder auf eingefrorenen Layern liegenden Bloecken scheitert.
|
|
;; Rueckgabe: Liste (cx cy cz dx dy dz) -- Mittelpunkt + Ausdehnung, oder nil bei Fehler.
|
|
;; dz wird auf csv:bb-minimum-height-mm angehoben, falls kleiner (siehe dort).
|
|
(defun csv:get-bbox (ename / obj res minpt maxpt dz min-hoehe)
|
|
(setq obj (vlax-ename->vla-object ename))
|
|
(vl-catch-all-apply 'vla-update (list obj))
|
|
(setq res
|
|
(vl-catch-all-apply
|
|
'(lambda ( / ll ur)
|
|
(vla-getboundingbox obj 'll 'ur)
|
|
(list (csv:pt->list ll) (csv:pt->list ur)))))
|
|
(if (vl-catch-all-error-p res)
|
|
(progn
|
|
;; Fehler NICHT mehr still schlucken: zaehlen und erste Meldung merken.
|
|
(setq *csv-bbox-fail-count* (1+ (cond (*csv-bbox-fail-count*) (0))))
|
|
(if (null *csv-bbox-fail-first*)
|
|
(setq *csv-bbox-fail-first*
|
|
(strcat "(" (cond ((cdr (assoc 2 (entget ename)))) ("?")) ") "
|
|
(vl-catch-all-error-message res))))
|
|
nil)
|
|
(progn
|
|
(setq minpt (car res))
|
|
(setq maxpt (cadr res))
|
|
(setq dz (- (caddr maxpt) (caddr minpt)))
|
|
(setq min-hoehe (csv:bb-minimum-height-mm))
|
|
(if (< dz min-hoehe) (setq dz min-hoehe))
|
|
(list
|
|
(/ (+ (car minpt) (car maxpt)) 2.0)
|
|
(/ (+ (cadr minpt) (cadr maxpt)) 2.0)
|
|
(/ (+ (caddr minpt) (caddr maxpt)) 2.0)
|
|
(- (car maxpt) (car minpt))
|
|
(- (cadr maxpt) (cadr minpt))
|
|
dz
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
;; --- Attribute eines INSERT-Blocks als JSON-Objekt lesen ---
|
|
;; ename = Entity-Name des INSERT
|
|
;; Rueckgabe: String wie {"TAG1":"Wert1","TAG2":"Wert2"}
|
|
(defun csv:read-attribs (ename / obj ed typ tag wert parts)
|
|
(setq obj (entnext ename))
|
|
(setq parts nil)
|
|
(while obj
|
|
(setq ed (entget obj))
|
|
(setq typ (cdr (assoc 0 ed)))
|
|
(if (equal typ "SEQEND") (setq obj nil)
|
|
(progn
|
|
(if (equal typ "ATTRIB")
|
|
(setq parts (cons (strcat "\"" (csv:json-escape (cdr (assoc 2 ed)))
|
|
"\":\"" (csv:json-escape (cdr (assoc 1 ed))) "\"")
|
|
parts))
|
|
)
|
|
(setq obj (entnext obj))
|
|
)
|
|
)
|
|
)
|
|
(if parts
|
|
(progn
|
|
(setq parts (reverse parts))
|
|
(strcat "{" (car parts)
|
|
(apply 'strcat (mapcar '(lambda (p) (strcat "," p)) (cdr parts)))
|
|
"}")
|
|
)
|
|
"{}"
|
|
)
|
|
)
|
|
|
|
|
|
;; --- Einen INSERT-Block als JSON-Objekt schreiben ---
|
|
;; include-bbox = T -> zusaetzlich Bounding-Box (Mitte + Ausdehnung) ermitteln
|
|
;; und als "bbox"-Objekt anhaengen (nur fuer EXPORTCSV, nicht fuer EXPORTSIVAS).
|
|
;; "insertpoint" (KOS-String von Position+Rotation des Blocks selbst) und
|
|
;; "k1".."k4" (KOS-Strings der Koordinatensysteme im Block, siehe
|
|
;; csv:get-k-kos-strings) werden immer geschrieben, unabhaengig von
|
|
;; include-bbox -- Kodierung ueber csv:kos-encode (siehe dort).
|
|
(defun csv:block-to-json (ename include-bbox / ed blk-name layer pt rotation extrusion R-world
|
|
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-ids sep-id)
|
|
(setq ed (entget ename))
|
|
(setq blk-name (cdr (assoc 2 ed)))
|
|
(setq layer (cdr (assoc 8 ed)))
|
|
(setq pt (cdr (assoc 10 ed)))
|
|
(setq rotation (cdr (assoc 50 ed)))
|
|
(setq handle (cdr (assoc 5 ed)))
|
|
(if (null rotation) (setq rotation 0.0))
|
|
(if (null (caddr pt)) (setq pt (list (car pt) (cadr pt) 0.0)))
|
|
;; Gruppe 10 ist OCS-lokal zur EIGENEN Extrusion dieses INSERT (Gruppe
|
|
;; 210) - fuer die allermeisten Bloecke (0 0 1) = Welt, aber ein Separator-
|
|
;; Proxy, der ueber ssg-collect-nested-inserts aus dem geneigten Abschnitt
|
|
;; einer VF-/GF-Kette neu zusammengesetzt wurde (csv:sep-proxies-erzeugen),
|
|
;; kann eine echt gekippte Extrusion tragen. Erst in echte Weltkoordinaten/
|
|
;; -orientierung umrechnen (Arbitrary Axis Algorithm, ssg_core.lsp), bevor
|
|
;; irgendetwas darauf aufbaut - sonst zeigt "x"/"y"/"z" (und darueber K1-K4,
|
|
;; siehe csv:get-k-kos-strings) einen falschen, teils spiegelverkehrten
|
|
;; Weltpunkt (realer Bug: Separator :4/6/8/10/12 in HundM05, K1 landete am
|
|
;; Vorzeichen gespiegelt weit ausserhalb der eigenen Boundingbox). Fuer
|
|
;; Extrusion=(0 0 1) reduziert sich das exakt auf die alte Uebernahme.
|
|
(setq extrusion (cond ((cdr (assoc 210 ed))) ('(0.0 0.0 1.0))))
|
|
(setq pt (mat3-mul-vec3 (mat3-ocs-matrix extrusion) pt))
|
|
(setq R-world (mat3-from-normal-rotation extrusion rotation))
|
|
(setq attribs (csv:read-attribs ename))
|
|
(setq insert-quat (csv:z-angle-to-quat (* (/ rotation pi) 180.0)))
|
|
(setq insert-kos (csv:kos-encode
|
|
(car pt) (cadr pt) (if (caddr pt) (caddr pt) 0.0)
|
|
(nth 0 insert-quat) (nth 1 insert-quat) (nth 2 insert-quat) (nth 3 insert-quat)))
|
|
(setq k-list (csv:get-k-kos-strings ename pt R-world))
|
|
;; Omniflo-Geraden fuehren keine echten K-Bloecke - K1/K2 aus der Geometrie
|
|
;; (Anfang/Ende ueber Laenge + Rotation) synthetisieren, damit sie im Export
|
|
;; wie echte Anschluss-Koordinatensysteme erscheinen und in der Nachbar-
|
|
;; schaftserkennung (export_neighbors.py) mitverwendet werden koennen.
|
|
(if (and (null k-list)
|
|
(wcmatch (strcase blk-name)
|
|
(export:pattern "pattern_gerade" "AP110*,AP_110*")))
|
|
(setq k-list (csv:gerade-k-kos-strings ename pt rotation))
|
|
)
|
|
;; VF_n/GF_n-Wrapper: K1 (Eingang) und K2 (Ausgang) eine Ebene tiefer aus
|
|
;; AS_Element/ES_Element ableiten (siehe csv:vfgf-k-kos-strings; VF ueber die
|
|
;; Elementrolle, GF ueber die Hoehe). Damit die Python-Kollisionspruefung
|
|
;; Kreisel<->Strecke ueber die AS/ES-Position mit fester BBox statt der
|
|
;; ganzen Wrapper-Box arbeiten kann.
|
|
(if (and (null k-list)
|
|
(wcmatch (strcase blk-name) "VF_*,GF_*"))
|
|
(setq k-list (csv:vfgf-k-kos-strings ename pt rotation))
|
|
)
|
|
(setq bbox-json "")
|
|
(if include-bbox
|
|
(progn
|
|
(setq bbox (csv:get-bbox ename))
|
|
(if bbox
|
|
(setq bbox-json
|
|
(strcat
|
|
",\"bbox\":{"
|
|
"\"cx\":" (rtos (nth 0 bbox) 2 4)
|
|
",\"cy\":" (rtos (nth 1 bbox) 2 4)
|
|
",\"cz\":" (rtos (nth 2 bbox) 2 4)
|
|
",\"dx\":" (rtos (nth 3 bbox) 2 4)
|
|
",\"dy\":" (rtos (nth 4 bbox) 2 4)
|
|
",\"dz\":" (rtos (nth 5 bbox) 2 4)
|
|
"}"
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
;; Warnung fuer strittige Scanner-Zuordnungen anhaengen (siehe
|
|
;; cs-zuordnung-lauf in count_sep_scan.lsp, das *cs-scanner-warnung-
|
|
;; by-handle* vor csv:collect-export-blocks befuellt). Referenziert
|
|
;; die Variable direkt statt ueber atoms-family: ein in AutoLISP noch
|
|
;; nie gesetztes globales Symbol wertet beim Lesen zu nil aus (kein
|
|
;; Fehler), genau wie *export-test-override* in diesem Modul.
|
|
(setq warnung-json "")
|
|
(setq warnung-eintrag (assoc handle *cs-scanner-warnung-by-handle*))
|
|
(if warnung-eintrag
|
|
(setq warnung-json (strcat ",\"warnung\":\"" (csv:json-escape (cdr warnung-eintrag)) "\""))
|
|
)
|
|
;; Bekannte (nicht geratene) Separator-Zuordnung durchreichen: fuer die
|
|
;; temporaeren Kopien VERPACKTER Separator_SP-Symbole steht der Carrier fest
|
|
;; (der Wrapper-Block, in dem das Symbol steckt) - siehe
|
|
;; csv:sep-proxies-zuordnung-setzen und *cs-sep-fix-by-handle*. export_csv.py
|
|
;; berechnet die Zuordnung sonst ein zweites Mal per Boundingbox-
|
|
;; Ueberschneidung (compute_sensor_zuordnung, Prioritaet GF > VF > Kreisel-
|
|
;; haelfte) und wuerde den bekannten Wert damit ueberschreiben - z.B. auf
|
|
;; eine Gefaellestrecke, in deren Box der Separator zufaellig hineinragt,
|
|
;; obwohl er in einem VF-Block verpackt ist. Mit diesem Feld uebernimmt
|
|
;; export_csv.py den Wert unveraendert.
|
|
(setq zuordnung-fix-json "")
|
|
(setq zuordnung-fix-eintrag (assoc handle *cs-sep-fix-by-handle*))
|
|
(if zuordnung-fix-eintrag
|
|
(setq zuordnung-fix-json
|
|
(strcat ",\"zuordnung_fix\":\"" (csv:json-escape (cdr zuordnung-fix-eintrag)) "\""))
|
|
)
|
|
;; VF_n/GF_n-Wrapper: Separator-/AS-/ES-Reihenfolge aus der beim Bau
|
|
;; geschriebenen XDATA lesen (siehe ssg-sepliste-xdata-lesen,
|
|
;; ssg_ks_insert.lsp) und als "sepliste"-Array einbetten. Nur gesetzt, wenn
|
|
;; die XDATA vorhanden ist (Altbestand-Wrapper ohne die neue XDATA bleiben
|
|
;; ohne dieses Feld - export_neighbors.py faellt dann auf den bisherigen
|
|
;; BBox-Kollisionspfad zurueck).
|
|
(setq sepliste-json "")
|
|
(if (car (atoms-family 1 '("SSG-SEPLISTE-XDATA-LESEN")))
|
|
(progn
|
|
(setq sepliste-app
|
|
(cond
|
|
((wcmatch (strcase blk-name) "VF_*") "SSG_VF_SEP")
|
|
((wcmatch (strcase blk-name) "GF_*") "SSG_GF_SEP")
|
|
(t nil)))
|
|
(if sepliste-app
|
|
(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)
|
|
)
|
|
(setq sepliste-json (strcat ",\"sepliste\":[" sepliste-teile "]"))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
(strcat
|
|
" {\"block_name\":\"" (csv:json-escape blk-name) "\""
|
|
",\"layer\":\"" (csv:json-escape layer) "\""
|
|
",\"handle\":\"" (csv:json-escape handle) "\""
|
|
",\"x\":" (rtos (car pt) 2 4)
|
|
",\"y\":" (rtos (cadr pt) 2 4)
|
|
",\"z\":" (rtos (if (caddr pt) (caddr pt) 0.0) 2 4)
|
|
",\"rotation\":" (rtos (* (/ rotation pi) 180.0) 2 4)
|
|
",\"attribs\":" attribs
|
|
",\"insertpoint\":\"" insert-kos "\""
|
|
",\"k1\":\"" (csv:k-kos k-list "K1") "\""
|
|
",\"k2\":\"" (csv:k-kos k-list "K2") "\""
|
|
",\"k3\":\"" (csv:k-kos k-list "K3") "\""
|
|
",\"k4\":\"" (csv:k-kos k-list "K4") "\""
|
|
bbox-json
|
|
warnung-json
|
|
zuordnung-fix-json
|
|
sepliste-json
|
|
"}"
|
|
)
|
|
)
|
|
|
|
|
|
;; --- Muster fuer verpackte (in einer Compound-Blockdefinition
|
|
;; verschachtelte) Separator-Sub-INSERTs ---
|
|
;; Beim finalen Zusammenbau von VF_n/GF_n/KREISEL_n werden Separator_SP-
|
|
;; Sensor-Symbole, die zuvor als eigene top-level INSERTs eingefuegt wurden,
|
|
;; zu Sub-INSERTs der Compound-Blockdefinition - (ssget "X" ...) findet sie
|
|
;; danach nicht mehr (siehe ssg-collect-nested-inserts, ssg_core.lsp).
|
|
;; Bewusst NUR das Sensor-SYMBOL (Separator_SP_2D/_3D), NICHT der intern
|
|
;; gezeichnete Staustrecke_Separator_SP-Trennsteg (300mm) - der ist ein
|
|
;; reines Geometrie-/Laengenelement der Staustrecke, kein eigenstaendig zu
|
|
;; zaehlendes Bauteil, und soll hier nicht mitkopiert werden.
|
|
(defun csv:pattern-nested-separator ()
|
|
"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
|
|
;; Separator_SP, ...) koennen von vornherein nichts "verpackt" enthalten
|
|
;; und werden gar nicht erst geprueft.
|
|
(defun csv:pattern-wrapper-fuer-proxies ()
|
|
(strcat (export:pattern "pattern_kreisel" "KR_*,KREISEL_*,ECKRAD_*") ",VF_*,GF_*")
|
|
)
|
|
|
|
;; --- Skalierungsfaktor absichern ---
|
|
;; vla-InsertBlock verweigert den Faktor 0 (entartete Blockreferenz). Der
|
|
;; kann nur aus einer fehlerhaften Blockdefinition kommen; 1.0 ist dann die
|
|
;; brauchbarste Annahme - die Kopie steht wenigstens an der richtigen Stelle.
|
|
(defun csv:skalierung-oder-eins (f)
|
|
(if (or (null f) (equal f 0.0 1e-12)) 1.0 f)
|
|
)
|
|
|
|
;; --- Fuer jedes verpackte 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
|
|
;; kosmetisch). Der Versatz war schaedlich: die geometrische Sensor-Zuordnung
|
|
;; in count_sep_scan.lsp arbeitet ueber die Carrier-Boundingbox, und ein um
|
|
;; 500 mm verschobener Separator kippt am Kettenende in die Box des Nachbar-
|
|
;; Carriers - die Kopie soll ihr Vorbild in der Zeichenebene 1:1
|
|
;; repraesentieren. Die Platzierung der Ebenen zwischen Wrapper und Symbol
|
|
;; steckt in den Records von ssg-collect-nested-inserts (Position/Drehung/
|
|
;; Skalierung relativ zur Wrapper-Definition); hier kommt nur noch die
|
|
;; Platzierung des top-level INSERT darauf.
|
|
;;
|
|
;; 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
|
|
records)
|
|
(setq ss-all (ssget "X" (list (cons 0 "INSERT"))))
|
|
(if ss-all
|
|
(progn
|
|
(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 nested-pattern)
|
|
)
|
|
)
|
|
(if nested
|
|
(progn
|
|
(setq pt (cdr (assoc 10 ed)))
|
|
(if (null (caddr pt)) (setq pt (list (car pt) (cadr pt) 0.0)))
|
|
(setq rotation (cond ((cdr (assoc 50 ed))) (0.0)))
|
|
;; Extrusion des Wrapper-INSERTs selbst beruecksichtigen statt
|
|
;; reine Z-Drehung anzunehmen - GF_n/VF_n stehen zwar bisher immer
|
|
;; flach (Extrusion (0 0 1)), aber mat3-from-normal-rotation
|
|
;; reduziert sich dafuer ohnehin exakt auf Rz(rotation), kostet
|
|
;; also nichts und macht den Code robust falls sich das aendert.
|
|
(setq extrusion0 (cond ((cdr (assoc 210 ed))) ('(0.0 0.0 1.0))))
|
|
(setq R0 (mat3-from-normal-rotation extrusion0 rotation))
|
|
(setq psx (cond ((cdr (assoc 41 ed))) (1.0)))
|
|
(setq psy (cond ((cdr (assoc 42 ed))) (1.0)))
|
|
(setq psz (cond ((cdr (assoc 43 ed))) (1.0)))
|
|
(foreach rec nested
|
|
(setq nested-ent (nth 0 rec))
|
|
(setq local-pt (nth 1 rec))
|
|
(setq R-nested (nth 2 rec))
|
|
(setq nested-bname (cdr (assoc 2 (entget nested-ent))))
|
|
(setq scaled-pt (list (* psx (car local-pt))
|
|
(* psy (cadr local-pt))
|
|
(* psz (caddr local-pt))))
|
|
(setq rot-pt (mat3-mul-vec3 R0 scaled-pt))
|
|
(setq world-pt (list (+ (car pt) (car rot-pt))
|
|
(+ (cadr pt) (cadr rot-pt))
|
|
(+ (caddr pt) (caddr rot-pt))))
|
|
(setq R-final (mat3-mul-mat3 R0 R-nested))
|
|
(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))
|
|
)
|
|
)
|
|
)
|
|
(setq i (1+ i))
|
|
)
|
|
)
|
|
)
|
|
(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 ---
|
|
;; MUSS nach ssg-id-check-all laufen: erst dann steht die ID des Wrapper-
|
|
;; Blocks (VF_n/GF_n/KREISEL_n) fest - ein frisch gebauter Wrapper kann beim
|
|
;; Erzeugen der Kopien noch gar keine ID haben.
|
|
;;
|
|
;; Die Zuordnung eines VERPACKTEN Separators ist keine Schaetzung, sondern
|
|
;; bekannt: er steckt in genau diesem Wrapper. Ein Separator, der in einem
|
|
;; VF-Block mit ID 0010 verpackt ist, bekommt also ZUORDNUNG = 0010. Die
|
|
;; geometrische Zuordnung in count_sep_scan.lsp (Carrier-Boundingbox bzw.
|
|
;; naechster Carrier) wuerde diesen Wert bei jedem Lauf neu berechnen und ggf.
|
|
;; ueberschreiben - bei ueberlappenden Boxen (Kettenende an einem Kreisel)
|
|
;; landet der Separator dort leicht beim Nachbarn. Darum wird die bekannte
|
|
;; Zuordnung ueber *cs-sep-fix-by-handle* (Key = Entity-Handle der Kopie)
|
|
;; festgenagelt; cs-zuordnung-lauf uebernimmt sie unveraendert und zaehlt den
|
|
;; Separator beim richtigen Carrier.
|
|
;; 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 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
|
|
(ssg-attrib-set-on proxy (list (cons "ZUORDNUNG" wrapper-id)))
|
|
(setq handle (cdr (assoc 5 (entget proxy))))
|
|
(setq *cs-sep-fix-by-handle*
|
|
(cons (cons handle wrapper-id) *cs-sep-fix-by-handle*))
|
|
(setq gesetzt (1+ gesetzt))
|
|
)
|
|
;; Wrapper ohne ID-Attribut (Altbestand ohne ID-ATTDEF): keine bekannte
|
|
;; Zuordnung - die Kopie faellt auf die geometrische Zuordnung zurueck.
|
|
(setq ohne-id (1+ ohne-id))
|
|
)
|
|
)
|
|
(if (> gesetzt 0)
|
|
(princ (ssg-textf "exp-sep-zuordnung" (list (itoa gesetzt)))))
|
|
(if (> ohne-id 0)
|
|
(princ (ssg-textf "exp-sep-zuordnung-ohne-id" (list (itoa ohne-id)))))
|
|
gesetzt
|
|
)
|
|
|
|
;; --- 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.
|
|
;;
|
|
;; 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).
|
|
;;
|
|
;; 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.
|
|
;;
|
|
;; 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.
|
|
;;
|
|
;; 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))
|
|
)
|
|
)
|
|
)
|
|
(reverse out)
|
|
)
|
|
|
|
;; --- 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)
|
|
)
|
|
|
|
|
|
;; --- Relevante Bloecke sammeln ---
|
|
;; Filtert INSERT-Entities auf exportierbare Bloecke:
|
|
;; KR_* = Kreisel Compound-Bloecke
|
|
;; AP110* = Omniflo Geraden
|
|
;; 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
|
|
;; diese Sammlung) - export_csv.py gibt ihnen eigene TeileArt-Zeilen,
|
|
;; export_sivas.py schliesst sie bewusst wieder aus (siehe dort), da ihre
|
|
;; Anzahl schon ueber ANZAHL_SEPARATOR/ANZAHL_SCANNER an Kreisel/GF/VF
|
|
;; gezaehlt wird.
|
|
;; BTMT-Beladung*/SC_Entladung* = eigenstaendige BTMT Be-/Entladestationen
|
|
;; (siehe pattern_btmt_beladung/pattern_btmt_entladung in cfg/export.cfg,
|
|
;; ils-insert-station in SSG_LIB_Commands.lsp). Anders als Separator/
|
|
;; Scanner werden sie von KEINEM anderen Element mitgezaehlt - export_csv.py
|
|
;; UND export_sivas.py fuehren sie beide als eigene TeileArt-Zeilen.
|
|
;; Eingang*/Ausgang* = Materialfluss-Ein-/Ausgangssymbole (siehe
|
|
;; pattern_eingang/pattern_ausgang in cfg/export.cfg, ils-insert-station in
|
|
;; SSG_LIB_Commands.lsp). Wie BTMT eigenstaendig, aber NUR fuer den
|
|
;; CSV-Export: export_csv.py gibt ihnen eigene TeileArt-Zeilen und traegt
|
|
;; sie - wie Separator/AS/ES - in die "TrackIds"-Spalte ein (auch auf
|
|
;; Kreiseln, siehe KREISEL_UMLAUF_TEILEARTEN in lib/export_neighbors.py);
|
|
;; export_sivas.py schliesst sie bewusst aus.
|
|
;; GR_* = Geruest-Baugruppen (siehe pattern_geruest in cfg/export.cfg,
|
|
;; GeruestInsert.lsp). Wie BTMT eigenstaendig und von keinem anderen
|
|
;; Element mitgezaehlt - export_csv.py UND export_sivas.py fuehren sie
|
|
;; beide als eigene TeileArt-Zeilen.
|
|
;; Gibt Auswahlsatz zurueck oder nil.
|
|
(defun csv:collect-export-blocks ( / ss-all ss-out i ename ed bname)
|
|
(setq ss-all (ssget "X" (list (cons 0 "INSERT"))))
|
|
(if (null ss-all) (setq ss-out nil)
|
|
(progn
|
|
(setq ss-out (ssadd))
|
|
(setq i 0)
|
|
(while (setq ename (ssname ss-all i))
|
|
(setq ed (entget ename))
|
|
(setq bname (cdr (assoc 2 ed)))
|
|
(if (or
|
|
(wcmatch bname (export:pattern "pattern_kreisel" "KR_*,KREISEL_*,ECKRAD_*"))
|
|
(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*"))
|
|
(wcmatch bname (export:pattern "pattern_btmt_entladung" "SC_Entladung*"))
|
|
(wcmatch bname (export:pattern "pattern_eingang" "Eingang*"))
|
|
(wcmatch bname (export:pattern "pattern_ausgang" "Ausgang*"))
|
|
(wcmatch bname (export:pattern "pattern_geruest" "GR_*"))
|
|
;; Rein numerische Namen = Omniflo SivasNr (Boegen/Weichen)
|
|
;; Rein LISP-intern (Python klassifiziert Omniflo stattdessen
|
|
;; ueber exakten Katalog-Match), daher fest im Code statt in
|
|
;; cfg/export.cfg.
|
|
(wcmatch bname "#*")
|
|
)
|
|
(ssadd ename ss-out)
|
|
)
|
|
(setq i (1+ i))
|
|
)
|
|
(if (= (sslength ss-out) 0) (setq ss-out nil))
|
|
)
|
|
)
|
|
ss-out
|
|
)
|
|
|
|
|
|
;; --- HOEHE/DREHUNG aller Omniflo-Elemente still aktualisieren ---
|
|
;; Wird automatisch vor jedem Export aufgerufen.
|
|
(defun omni:update-all-attribs ( / ss i ename ed bname teileart)
|
|
(setq ss (ssget "X" (list (cons 0 "INSERT"))))
|
|
(if ss
|
|
(progn
|
|
(setq i 0)
|
|
(while (setq ename (ssname ss i))
|
|
(setq ed (entget ename))
|
|
(setq bname (cdr (assoc 2 ed)))
|
|
(setq teileart (csv:get-attrib ename "TEILEART"))
|
|
(if (or teileart
|
|
(wcmatch (strcase bname) (export:pattern "pattern_gerade" "AP110*,AP_110*")))
|
|
(progn
|
|
(omni:set-hoehe-attrib ename)
|
|
(omni:set-drehung-attrib ename)
|
|
)
|
|
)
|
|
(setq i (1+ i))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
;; --- Gemeinsame Export-Funktion ---
|
|
;; Sammelt relevante Bloecke, schreibt JSON, ruft Python auf.
|
|
;; label = Anzeigename (z.B. "EXPORTCSV")
|
|
;; --- Export-Vorbereitung: Zeichnungszustand fuer den Export herrichten ---
|
|
;; Aus csv:run-export ausgelagert: alle Schritte, die VOR dem Sammeln/Schreiben
|
|
;; 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 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).
|
|
(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
|
|
;; dabei zuerst das globale Maximum der bereits vergebenen IDs (Phase 0)
|
|
;; und nummeriert die noch ID-losen Kopien erst danach - sonst bekaemen sie
|
|
;; IDs, die die Wrapper (VF_n/GF_n/Kreisel) selbst schon tragen.
|
|
(princ (ssg-textf "exp-check-ids" (list label)))
|
|
(ssg-id-check-all)
|
|
;; Jetzt steht die ID jedes Wrappers fest: die bekannte (nicht geratene)
|
|
;; ZUORDNUNG der verpackten Separatoren auf die Wrapper-ID setzen und fuer
|
|
;; cs-zuordnung-lauf festnageln. 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)
|
|
;; Vor dem Export: Separator-/Scanner-Zuordnung pruefen und
|
|
;; ANZAHL_SEPARATOR/ANZAHL_SCANNER an Kreisel/Streckengruppe/
|
|
;; Gefaellestrecke aktualisieren (siehe count_sep_scan.lsp,
|
|
;; cs-zuordnung-lauf). Scanner ausserhalb jeder Carrier-Boundingbox
|
|
;; werden dabei per Abstand zugeordnet (ZUORDNUNG-Attribut = Carrier-
|
|
;; ID) und - bei mehrdeutigem Abstand - als "strittig" markiert;
|
|
;; *cs-scanner-warnung-by-handle* wird von csv:block-to-json unten
|
|
;; ausgelesen und haengt den Warnungstext an den betroffenen Scanner-
|
|
;; Block im JSON an. verbose=nil haelt die Export-Konsole kompakt
|
|
;; (die ausfuehrliche Detailzeile je Carrier gibt es nur beim
|
|
;; interaktiven Befehl ZAEHLE_SEP_SCAN).
|
|
;;
|
|
;; Der Lauf ist teuer (Boundingbox+Regen je Carrier und Sensor),
|
|
;; darum vorher der billige Schnelltest cs-zuordnung-noetig-p (nur
|
|
;; Attribute lesen, keine Boundingboxen): nur bei leeren ZUORDNUNG-
|
|
;; Eintraegen oder abweichenden Summen wird tatsaechlich neu gerechnet.
|
|
(ssg-ensure "count_sep_scan")
|
|
(if (atoms-family 1 '("cs-zuordnung-lauf"))
|
|
(if (cs-zuordnung-noetig-p)
|
|
(cs-zuordnung-lauf label nil)
|
|
(princ (ssg-textf "sens-skip" (list label)))
|
|
)
|
|
)
|
|
;; Vor dem Sammeln/Schreiben EINMAL global regenerieren. vla-getboundingbox
|
|
;; liefert nur auf regenerierten Objekten eine gueltige Box; ein pro-Objekt
|
|
;; vla-update (csv:get-bbox) reicht dafuer in der Praxis nicht immer aus
|
|
;; (fuehrte real dazu, dass in einem Export ALLE Bloecke ohne bbox blieben).
|
|
;; REGENALL erzwingt die vollstaendige Regeneration der gesamten Zeichnung.
|
|
(vl-catch-all-apply '(lambda () (command "_.REGENALL")))
|
|
;; 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 ---------------
|
|
;; Aus csv:run-export ausgelagert: das fertige raw-JSON (out-pfad) an das
|
|
;; Python-Skript py-name uebergeben, synchron ausfuehren und den Exitcode
|
|
;; auswerten (kein stiller Fehlschlag). Arbeitet ausschliesslich ueber die
|
|
;; uebergebenen Pfade/Namen und die Umgebung (DXFM_LIB/DXFM_DATA/DXFM_LOG) -
|
|
;; keine Zeichnungs-/Sammel-Logik mehr, daher gut fuer sich testbar.
|
|
;; label/out-dir dienen nur den Meldungen bzw. dem Ergebnis-Pfad.
|
|
(defun csv:python-aufrufen (label out-dir out-pfad py-name csv-name
|
|
/ py-skript ergebnis-pfad py-exe log-pfad rc)
|
|
;; Python-Skript aufrufen. DXFM_LIB muss gesetzt sein - fehlt es
|
|
;; (z.B. weil BricsCAD nicht ueber bin/start_briscad.bat gestartet
|
|
;; wurde und die Umgebungsvariablen daher nie in diesen Prozess
|
|
;; vererbt wurden), wuerde das folgende strcat sonst mit einem
|
|
;; kryptischen Lisp-Fehler ("bad argument type <NIL>") abbrechen.
|
|
(if (null (getenv "DXFM_LIB"))
|
|
(princ (ssg-textf "exp-dxfm-lib-missing" (list label)))
|
|
(progn
|
|
(setq py-skript (strcat (getenv "DXFM_LIB") "/" py-name))
|
|
(if (findfile py-skript)
|
|
(progn
|
|
(setq ergebnis-pfad (strcat out-dir "/" (export:dwg-praefix) csv-name))
|
|
(setq py-exe (export:python-exe))
|
|
(setq log-pfad (strcat (cond ((getenv "DXFM_LOG")) (out-dir))
|
|
"/export_python.log"))
|
|
(princ (ssg-textf "exp-python-exe" (list label py-exe)))
|
|
(princ (ssg-textf "exp-calling-python" (list label)))
|
|
;; Synchron ausfuehren und Exitcode auswerten - ein stiller
|
|
;; Fehlschlag (z.B. WindowsApps-Platzhalter statt Interpreter)
|
|
;; darf nicht mehr als "Export gestartet" durchgehen.
|
|
(setq rc (export:run-python py-exe py-skript
|
|
(list out-pfad (getenv "DXFM_DATA") ergebnis-pfad)
|
|
log-pfad))
|
|
(cond
|
|
((null rc)
|
|
(princ (ssg-textf "exp-python-call-failed" (list label))))
|
|
((/= rc 0)
|
|
(princ (ssg-textf "exp-python-error" (list label rc log-pfad)))
|
|
(export:log-ausgeben log-pfad 12)
|
|
;; Exitcode 49 (Store-Platzhalter direkt) bzw. 9009 (cmd.exe:
|
|
;; "Befehl nicht gefunden") = kein brauchbarer Interpreter.
|
|
;; Gezielter Hinweis, wie er korrekt gesetzt wird.
|
|
(if (or (= rc 49) (= rc 9009)
|
|
(export:python-store-stub-p py-exe))
|
|
(princ (ssg-text "exp-python-store-hint"))
|
|
))
|
|
((not (findfile ergebnis-pfad))
|
|
(princ (ssg-textf "exp-csv-missing" (list label ergebnis-pfad log-pfad)))
|
|
(export:log-ausgeben log-pfad 12))
|
|
(t
|
|
(princ (ssg-textf "exp-export-done" (list label ergebnis-pfad))))
|
|
)
|
|
)
|
|
(princ (ssg-textf "exp-python-not-found" (list label py-skript)))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
;; py-name = Python-Skript-Name (z.B. "export_csv.py")
|
|
;; csv-name = Ergebnis-CSV-Name (z.B. "export.csv")
|
|
;; include-bbox = T -> Bounding-Box je Block ermitteln und mit exportieren
|
|
;; (siehe csv:block-to-json); fuer EXPORTSIVAS nil.
|
|
(defun csv:run-export (label py-name csv-name include-bbox / ss i ename fh out-pfad
|
|
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: alle erzeugten Kopien (unten wieder zu entfernen).
|
|
(setq sep-proxies (csv:export-vorbereiten label))
|
|
(princ (ssg-textf "exp-collect-blocks" (list label)))
|
|
|
|
(setq ss (csv:collect-export-blocks))
|
|
(if (null ss)
|
|
(progn
|
|
(princ (ssg-textf "exp-no-blocks-found" (list label)))
|
|
(princ)
|
|
)
|
|
(progn
|
|
(setq count (sslength ss))
|
|
(princ (ssg-textf "exp-blocks-found" (list label count)))
|
|
|
|
;; Ausgabeordner: DXFM_RESULTS, ausser *export-test-override* ist gesetzt
|
|
;; (nur waehrend TEST_EXPORT_ALL, siehe tests/test_export_all.lsp).
|
|
;; WICHTIG: Hier bewusst KEIN (setenv "DXFM_RESULTS" ...) verwenden!
|
|
;; (setenv ...) schreibt in BricsCAD dauerhaft in die Profil-Registry
|
|
;; und ueberlebt damit jeden Neustart - ein einziger Testlauf wuerde
|
|
;; DXFM_RESULTS fuer alle zukuenftigen Sitzungen unwiderruflich auf den
|
|
;; Testordner umbiegen. *export-test-override* ist eine reine
|
|
;; Lisp-Variable (nur Prozess-/Sitzungsspeicher) und daher sicher.
|
|
(setq out-dir (if *export-test-override*
|
|
*export-test-override*
|
|
(getenv "DXFM_RESULTS")))
|
|
(if (not (vl-file-directory-p out-dir)) (vl-mkdir out-dir))
|
|
(princ (ssg-textf "exp-output-dir" (list label out-dir)))
|
|
|
|
;; JSON-Datei zusammenbauen
|
|
(setq out-pfad (strcat out-dir "/"
|
|
(export:filename "raw_json_datei")))
|
|
(setq fh (open out-pfad "w"))
|
|
(if (null fh)
|
|
(progn
|
|
(princ (ssg-textf "exp-file-open-error" (list label out-pfad)))
|
|
(princ)
|
|
)
|
|
(progn
|
|
(write-line "[" fh)
|
|
(setq i 0)
|
|
(setq first-block T)
|
|
(while (setq ename (ssname ss i))
|
|
(if (not first-block)
|
|
(write-line "," fh)
|
|
)
|
|
(write-line (csv:block-to-json ename include-bbox) fh)
|
|
(setq first-block nil)
|
|
(setq i (1+ i))
|
|
)
|
|
(write-line "]" fh)
|
|
(close fh)
|
|
(princ (ssg-textf "exp-json-written" (list label out-pfad)))
|
|
;; bbox-Fehler dieses Laufs melden (falls csv:get-bbox scheiterte) -
|
|
;; nennt Anzahl und die erste Fehlermeldung samt Block, damit die
|
|
;; Ursache fehlender Bounding-Boxen sofort sichtbar ist statt still
|
|
;; im JSON zu fehlen.
|
|
(if (and *csv-bbox-fail-count* (> *csv-bbox-fail-count* 0))
|
|
(princ (ssg-textf "exp-bbox-fail"
|
|
(list (itoa *csv-bbox-fail-count*)
|
|
(cond (*csv-bbox-fail-first*) ("?"))))))
|
|
|
|
;; Python-Export-Skript aufrufen (ausgelagert, siehe oben).
|
|
(csv:python-aufrufen label out-dir out-pfad py-name csv-name)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
;; 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)
|
|
(princ)
|
|
)
|
|
|
|
|
|
;; --- EXPORTSIVAS: Sivas-spezifischer CSV-Export mit Summierungszeilen ---
|
|
(defun c:EXPORTSIVAS ()
|
|
(csv:run-export "EXPORTSIVAS"
|
|
(export:filename "sivas_script")
|
|
(export:filename "sivas_csv")
|
|
nil)
|
|
)
|
|
|
|
;; --- EXPORTCSV: Einfache Item-Liste aller Bloecke (ohne Summierung) ---
|
|
;; Ermittelt zusaetzlich je Block eine Bounding-Box (siehe csv:get-bbox),
|
|
;; die im Python-Skript als Position/Boundingbox-Spalten ausgegeben wird.
|
|
(defun c:EXPORTCSV ()
|
|
(csv:run-export "EXPORTCSV"
|
|
(export:filename "csv_script")
|
|
(export:filename "csv_csv")
|
|
T)
|
|
)
|
|
|
|
|
|
;; ============================================================
|
|
;; OMNIFLO HOEHE/DREHUNG AKTUALISIEREN
|
|
;; ============================================================
|
|
|
|
;; --- Attribut-Wert eines INSERT-Blocks nach Tag lesen ---
|
|
(defun csv:get-attrib (ename tag / obj ed typ atag)
|
|
(setq obj (entnext ename))
|
|
(while obj
|
|
(setq ed (entget obj))
|
|
(setq typ (cdr (assoc 0 ed)))
|
|
(if (equal typ "SEQEND")
|
|
(setq obj nil)
|
|
(progn
|
|
(if (and (equal typ "ATTRIB")
|
|
(= (strcase (cdr (assoc 2 ed))) (strcase tag)))
|
|
(progn (setq obj nil) (setq atag (cdr (assoc 1 ed))))
|
|
(setq obj (entnext obj))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
atag
|
|
)
|
|
|
|
;; --- Hoehe eines Omniflo-Blocks aus der Zeichnung ermitteln ---
|
|
;; Liest z-Koordinate des Einfuegepunkts.
|
|
;; Falls der Block KS_EIN, KS_AUS oder K1-K4 enthaelt,
|
|
;; wird die minimale Z-Koordinate dieser Unterelemente + Block-Z verwendet.
|
|
(defun omni:get-block-hoehe (ename / ed bname pt z-insert blk-tbl sub-ent sub-ed sub-bname sub-pt z-list z-min)
|
|
(setq ed (entget ename))
|
|
(setq bname (cdr (assoc 2 ed)))
|
|
(setq pt (cdr (assoc 10 ed)))
|
|
(setq z-insert (if (caddr pt) (caddr pt) 0.0))
|
|
(setq blk-tbl (tblsearch "BLOCK" bname))
|
|
(if (null blk-tbl)
|
|
z-insert
|
|
(progn
|
|
(setq sub-ent (entnext (cdr (assoc -2 blk-tbl))))
|
|
(setq z-list nil)
|
|
(while sub-ent
|
|
(setq sub-ed (entget sub-ent))
|
|
(if (= (cdr (assoc 0 sub-ed)) "INSERT")
|
|
(progn
|
|
(setq sub-bname (strcase (cdr (assoc 2 sub-ed))))
|
|
(if (wcmatch sub-bname
|
|
(export:pattern "pattern_ks_subblocks" "KS_EIN,KS_AUS,K1,K2,K3,K4"))
|
|
(progn
|
|
(setq sub-pt (cdr (assoc 10 sub-ed)))
|
|
(setq z-list (cons (if (caddr sub-pt) (caddr sub-pt) 0.0) z-list))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
(setq sub-ent (entnext sub-ent))
|
|
)
|
|
(if z-list
|
|
(progn
|
|
(setq z-min (car z-list))
|
|
(foreach z (cdr z-list)
|
|
(if (< z z-min) (setq z-min z))
|
|
)
|
|
(+ z-insert z-min)
|
|
)
|
|
z-insert
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
;; --- HOEHE-Attribut eines Omniflo-Blocks setzen ---
|
|
;; Liest Z-Koordinate (ggf. aus KS-Unterblock) und schreibt als HOEHE-Attribut.
|
|
(defun omni:set-hoehe-attrib (ename / hoehe)
|
|
(setq hoehe (omni:get-block-hoehe ename))
|
|
(ssg-attrib-set-on ename (list (cons "HOEHE" (rtos hoehe 2 1))))
|
|
hoehe
|
|
)
|
|
|
|
|
|
;; --- DREHUNG-Attribut eines Omniflo-Blocks setzen ---
|
|
;; Liest CAD-Rotationswinkel (Gruppe 50, Bogenmass) und schreibt als DREHUNG-Attribut.
|
|
(defun omni:set-drehung-attrib (ename / ed rotation deg)
|
|
(setq ed (entget ename))
|
|
(setq rotation (cdr (assoc 50 ed)))
|
|
(if (null rotation) (setq rotation 0.0))
|
|
(setq deg (* (/ rotation pi) 180.0))
|
|
(ssg-attrib-set-on ename (list (cons "DREHUNG" (rtos deg 2 1))))
|
|
deg
|
|
)
|
|
|
|
|
|
;; ============================================================
|
|
;; C:OMNI_UPDATE_ATTRIBS - HOEHE und DREHUNG aller Omniflo-Elemente aktualisieren
|
|
;; Setzt HOEHE aus Z-Koordinate (bzw. min. KS-Sub-Block) und
|
|
;; DREHUNG aus CAD-Rotationswinkel fuer alle Bloecke mit TEILEART-Attribut.
|
|
;; ============================================================
|
|
(defun c:OMNI_UPDATE_ATTRIBS ( / ss i ename ed bname teileart count)
|
|
(setq ss (ssget "X" (list (cons 0 "INSERT"))))
|
|
(if (null ss)
|
|
(princ (ssg-text "exp-omni-no-inserts"))
|
|
(progn
|
|
(setq count 0)
|
|
(setq i 0)
|
|
(while (setq ename (ssname ss i))
|
|
(setq ed (entget ename))
|
|
(setq bname (cdr (assoc 2 ed)))
|
|
;; Nur Omniflo-Elemente (erkennbar an TEILEART-Attribut oder AP110-Blockname)
|
|
(setq teileart (csv:get-attrib ename "TEILEART"))
|
|
(if (or teileart
|
|
(wcmatch (strcase bname) (export:pattern "pattern_gerade" "AP110*,AP_110*")))
|
|
(progn
|
|
(omni:set-hoehe-attrib ename)
|
|
(omni:set-drehung-attrib ename)
|
|
(setq count (1+ count))
|
|
)
|
|
)
|
|
(setq i (1+ i))
|
|
)
|
|
(princ (ssg-textf "exp-omni-updated" (list count)))
|
|
)
|
|
)
|
|
(princ)
|
|
)
|