Files
dxfmakros/tests/test_mubea.lsp
T
2026-07-27 16:54:04 +02:00

444 lines
19 KiB
Common Lisp

;; ============================================================
;; test_mubea.lsp - Integrationstest: erzeugt das Mubea-Gesamtmodell
;;
;; Liest tests/testdata/mubea.json und baut in EINER Zeichnung:
;; - Kreisel (Eintraege mit "id", Block KREISEL_*) via kreisel-insert-script
;; - Vario (test_id "VF_*") Block VF_* via variofoerderer-einfuegen
;; + vf-block-erstellen
;; (deltaL/L_VF werden fuer winkel+deltaH geloest, L_GF Split 500/4230)
;; - Gefaelle (test_id "GF_*", Modus 1) Block GF_* via gefaellestrecke-einfuegen
;; - Separator (Eintraege mit "block") Block S-LP/Separator_SP via _.INSERT
;;
;; Speichert (via SSG_RUN_ALL_TESTS bei "save":"dxf"):
;; tests/output/mubea_tests.dxf
;; tests/output/mubea_results.json (via mubea:export-results)
;;
;; Voraussetzungen:
;; - SSG_LIB geladen (KreiselInsert, VarioFoerderer, Gefaellestrecke,
;; SSG_LIB_Commands, ssg_core, vf_core)
;; - Umgebungsvariable DXFMAKRO gesetzt
;;
;; Aufruf in BricsCAD:
;; (load (strcat (getenv "DXFMAKRO") "/tests/test_mubea.lsp"))
;; TEST_MUBEA
;; ============================================================
;; --- Ergebnis eines Bau-Schritts als JSON-String erzeugen ---
(defun mubea:result-json (test-id kind status block-name block-handle insert-point attribs /
json tag val first)
(setq json (strcat " {\n"
" \"test_id\": \"" test-id "\",\n"
" \"kind\": \"" kind "\",\n"
" \"status\": \"" status "\",\n"
" \"block_name\": \"" (if block-name block-name "") "\",\n"
" \"block_handle\": \"" (if block-handle block-handle "") "\",\n"
" \"insert_point\": ["
(if insert-point
(strcat (rtos (car insert-point) 2 1) ", "
(rtos (cadr insert-point) 2 1) ", "
(rtos (if (caddr insert-point) (caddr insert-point) 0.0) 2 1))
"0.0, 0.0, 0.0")
"],\n"
" \"actual_attributes\": {"))
(setq first T)
(foreach att attribs
(setq tag (car att) val (cdr att))
(if (not first) (setq json (strcat json ",")))
(setq json (strcat json "\n \"" tag "\": \"" (if val val "") "\""))
(setq first nil))
(setq json (strcat json "\n }\n }"))
json
)
;; --- Aus einer Block-ENAME ein Ergebnis-JSON erzeugen ---
(defun mubea:ent-json (test-id kind ent / ed)
(if ent
(progn
(setq ed (entget ent))
(mubea:result-json test-id kind "executed"
(cdr (assoc 2 ed)) (cdr (assoc 5 ed)) (cdr (assoc 10 ed))
(ssg-attrib-read ent)))
(mubea:result-json test-id kind "failed" nil nil nil nil))
)
;; --- Letzten INSERT mit Blocknamen-Praefix (rueckwaerts) suchen ---
(defun mubea:last-insert-prefix (pref / ent found ed bn)
(setq ent (entlast))
(while (and ent (null found))
(setq ed (entget ent) bn (cdr (assoc 2 ed)))
(if (and (= (cdr (assoc 0 ed)) "INSERT") bn
(>= (strlen bn) (strlen pref))
(= (substr bn 1 (strlen pref)) pref))
(setq found ent))
(setq ent (entprev ent)))
found
)
;; ============================================================
;; Bau-Funktionen je Elementtyp
;; ============================================================
;; --- Kreisel (wie test_kreisel: kreisel-insert-script) ---
(defun mubea:build-kreisel (e / id x y z abstand rotation typ pt ent)
(setq id (ssg-val e "id")
x (float (ssg-val e "x"))
y (float (ssg-val e "y"))
z (float (ssg-val e "z"))
abstand (float (ssg-val e "abstand"))
rotation (float (ssg-val e "rotation"))
typ (ssg-val e "typ"))
(if (null typ) (setq typ "STANDARD"))
(setq pt (list x y z))
(princ (strcat "\n [KREISEL] " id " -> (" (rtos x 2 0) "," (rtos y 2 0)
"," (rtos z 2 0) ") A=" (rtos abstand 2 0) " R=" (rtos rotation 2 1)))
(setq ent (kreisel-insert-script pt abstand rotation typ z))
(mubea:ent-json id "kreisel" ent)
)
;; --- Vario (VF_*): deltaL/L_VF fuer winkel+deltaH loesen, dann bauen ---
(defun mubea:build-vario (e / tid richtung deltaH winkel gf1 gf2 gf-ziel seite hz
startpunkt dlA dlB it1 it2 lgfA lgfB steig dL item L_VF
hoehe-bis lastEnt ent)
(setq tid (ssg-val e "test_id")
richtung (ssg-val e "richtung")
deltaH (float (ssg-val e "deltaH"))
winkel (fix (ssg-val e "winkel"))
gf1 (float (ssg-val e "L_GF1"))
gf2 (float (ssg-val e "L_GF2"))
seite (ssg-val e "seite")
hz (ssg-val e "hz")
startpunkt (list (float (ssg-val e "x")) (float (ssg-val e "y")) (float (ssg-val e "z"))))
(if (or (null seite) (= seite "")) (setq seite "links"))
(if (null hz) (setq hz 0.0) (setq hz (float hz)))
(setq gf-ziel (+ gf1 gf2))
(princ (strcat "\n [VARIO] " tid " -> Winkel=" (itoa winkel)
" deltaH=" (rtos deltaH 2 0) " L_GF1=" (rtos gf1 2 0)
" L_GF2=" (rtos gf2 2 0)))
;; L_GF ist linear in deltaL -> deltaL aus zwei Stuetzstellen interpolieren,
;; sodass L_GF gesamt = gf-ziel am gewaehlten Winkel.
(setq dlA 5000.0 dlB 10000.0)
(setq it1 (car (vl-remove-if-not '(lambda (a) (= (car a) winkel))
(cadddr (berechne-alle-winkel dlA deltaH richtung nil)))))
(setq it2 (car (vl-remove-if-not '(lambda (a) (= (car a) winkel))
(cadddr (berechne-alle-winkel dlB deltaH richtung nil)))))
(if (and it1 it2 (numberp (cadr it1)) (numberp (cadr it2))
(/= (cadr it1) (cadr it2)))
(progn
(setq lgfA (cadr it1) lgfB (cadr it2))
(setq steig (/ (- lgfB lgfA) (- dlB dlA)))
(setq dL (+ dlA (/ (- gf-ziel lgfA) steig)))
(setq item (car (vl-remove-if-not '(lambda (a) (= (car a) winkel))
(cadddr (berechne-alle-winkel dL deltaH richtung nil)))))
(setq L_VF (caddr item))
(princ (strcat " -> deltaL=" (rtos dL 2 1) " L_VF=" (rtos L_VF 2 1)))
(setq hoehe-bis (if (= richtung "Auf") deltaH (- deltaH)))
(setq lastEnt (vf-lastent-ohne-attribute))
(variofoerderer-einfuegen dL deltaH richtung winkel gf1 gf2 L_VF startpunkt seite hz)
(setq ent (vf-block-erstellen "standard" seite 1 0 0 hoehe-bis
deltaH dL L_VF gf1 gf2 richtung winkel
startpunkt lastEnt hz "0" "Schoenenberger Geruest"))
(if (or (null ent) (/= (type ent) 'ENAME))
(setq ent (mubea:last-insert-prefix "VF_")))
(mubea:ent-json tid "vario" ent))
(progn
(princ (strcat "\n FEHLER: Winkel " (itoa winkel) " nicht loesbar (kein gueltiges L_GF)."))
(mubea:result-json tid "vario" "failed" nil nil nil nil)))
)
;; --- Gefaellestrecke: Init-Konflikt umgehen -----------------------------
;; gefaellestrecke-einfuegen ruft "(if (not *lib-initialized*) (gf-init-
;; bibliothek))" auf (Gefaellestrecke.lsp:1567) - dasselbe Flag, das
;; init-bibliothek (Vario, vf_standard.lsp) fuer SEINE EIGENEN aus-dx/ein-dx/
;; aus-dz/ein-dz setzt. Da C:TEST_MUBEA zuerst den Vario baut (init-
;; bibliothek setzt *lib-initialized*=T), wuerde gf-init-bibliothek NIE mehr
;; laufen und die Gefaellestrecke mit den (voellig andersartigen) Vario-
;; Bogenmassen statt den echten AS_Element/ES_Element-Massen rechnen - genau
;; das fuehrte zur falschen Zielhoehe im ersten TEST_MUBEA-Lauf. Fix: Flag
;; kurz zuruecksetzen, gf-init-bibliothek EINMAL erzwingen (danach haben
;; aus-dx/ein-dx/aus-dz/ein-dz die korrekten GF-spezifischen Werte).
(defun mubea:ensure-gf-init ( / )
(if (not *mubea-gf-init-done*)
(progn
(setq *lib-initialized* nil)
(gf-init-bibliothek)
(setq *lib-initialized* t)
(setq *mubea-gf-init-done* T)
)
)
)
;; --- Gefaellestrecke: L_stau <-> Ziel-Hoehendifferenz kalibrieren ---------
;; Bei festem Element-Winkel (3 Grad) ist deltaH linear in L_stau (nur die
;; Staustrecke ist skalierbar). Statt die Formel aus test_gefaellestrecke.lsp
;; zu uebernehmen (die L_stau aus einer ZIEL-LAENGE ableitet, nicht aus einer
;; ZIEL-HOEHE - beides ist bei festem Winkel nicht unabhaengig waehlbar),
;; wird die reale Steigung/Achsenabschnitt per zwei Probe-Bauten gemessen
;; (gefaellestrecke-einfuegen liefert deltaH exakt zurueck) und die Probe-
;; Inserts danach wieder geloescht. Rueckgabe: (steigung . achsenabschnitt).
(defun mubea:gf-calibrate (as-seite es-seite as-winkel es-winkel hz /
probe-pt l1 l2 e1 e2 dz1 dz2 steigung achsenabschnitt)
(mubea:ensure-gf-init)
(setq probe-pt (list 0.0 -500000.0 0.0)) ;; Schrott-Punkt, wird geloescht
(setq l1 3000.0 l2 8000.0)
(setq e1 (gefaellestrecke-einfuegen l1 3.0 probe-pt as-seite es-seite hz
l1 as-winkel es-winkel "0" "Schoenenberger Geruest"))
(setq e2 (gefaellestrecke-einfuegen l2 3.0 probe-pt as-seite es-seite hz
l2 as-winkel es-winkel "0" "Schoenenberger Geruest"))
(setq dz1 (cadr e1) dz2 (cadr e2))
(setq steigung (/ (- dz2 dz1) (- l2 l1)))
(setq achsenabschnitt (- dz1 (* steigung l1)))
;; Probe-Bloecke wieder entfernen (nur das INSERT - Blockdefinition bleibt
;; ungenutzt in der Blocktabelle, erscheint nicht im Modelspace/Zaehlungen)
(if (car e1) (entdel (car e1)))
(if (car e2) (entdel (car e2)))
(princ (strcat "\n [GF-Kalibrierung] L_stau=" (rtos l1 2 0) "->dz=" (rtos dz1 2 1)
" L_stau=" (rtos l2 2 0) "->dz=" (rtos dz2 2 1)
" Steigung=" (rtos steigung 2 6) " Achsenabschnitt=" (rtos achsenabschnitt 2 2)))
(cons steigung achsenabschnitt)
)
;; --- Eine einzelne Gefaellestrecke bauen (Modus 1) ---
;; L_stau wird aus der ZIEL-HOEHENDIFFERENZ (start_mm.z - ende_mm.z) ueber die
;; kalibrierte lineare Beziehung berechnet (siehe mubea:gf-calibrate) - NICHT
;; aus der XY-Distanz (bei festem 3-Grad-Winkel legen Laenge und
;; Hoehendifferenz sich gegenseitig fest, beides gleichzeitig aus den realen
;; Koordinaten zu uebernehmen ist geometrisch inkonsistent).
;; Rueckgabe: Ergebnis-JSON-String.
(defun mubea:build-gf-one (tid start-mm ende-mm hz as-seite es-seite as-winkel es-winkel /
ziel-dz kalib steigung achsenabschnitt test-L_stau
real-deltaL dx dy ent)
;; Kalibrierung einmalig pro (as-seite/es-seite/as-winkel/es-winkel/hz)-
;; Kombination; fuer alle Kopien mit denselben Parametern wiederverwendet.
(if (null *mubea-gf-kalib-cache*) (setq *mubea-gf-kalib-cache* '()))
(setq kalib (cdr (assoc (list as-seite es-seite as-winkel es-winkel hz) *mubea-gf-kalib-cache*)))
(if (null kalib)
(progn
(setq kalib (mubea:gf-calibrate as-seite es-seite as-winkel es-winkel hz))
(setq *mubea-gf-kalib-cache*
(cons (cons (list as-seite es-seite as-winkel es-winkel hz) kalib) *mubea-gf-kalib-cache*))
)
)
(setq steigung (car kalib) achsenabschnitt (cdr kalib))
(setq ziel-dz (- (caddr start-mm) (caddr ende-mm)))
(setq test-L_stau (/ (- ziel-dz achsenabschnitt) steigung))
;; Nur zu Log-/Attributzwecken: reale XY-Distanz start_mm->ende_mm
(setq dx (- (car ende-mm) (car start-mm))
dy (- (cadr ende-mm) (cadr start-mm)))
(setq real-deltaL (sqrt (+ (* dx dx) (* dy dy))))
(princ (strcat "\n [GF] " tid " -> Start=(" (rtos (car start-mm) 2 0)
"," (rtos (cadr start-mm) 2 0) "," (rtos (caddr start-mm) 2 0)
") hz=" (rtos hz 2 0) " Ziel-dz=" (rtos ziel-dz 2 1)
" L_stau=" (rtos test-L_stau 2 1)))
(setq ent (car (gefaellestrecke-einfuegen
test-L_stau 3.0 start-mm as-seite es-seite hz
real-deltaL as-winkel es-winkel "0" "Schoenenberger Geruest")))
(mubea:ent-json tid "gefaellestrecke" ent)
)
;; --- Gefaellestrecke(n) aus einem Template-Eintrag bauen ---
;; Ein einziger JSON-Eintrag beschreibt per "anzahl" + "delta_y"/"delta_x" eine
;; ganze Reihe identischer Gefaellestrecken (statt 20 Einzeleintraege). Element i
;; (1..anzahl) wird um (i-1)*delta in Y bzw. X versetzt (start_mm UND ende_mm
;; gemeinsam). Fehlen anzahl/delta -> genau eine Strecke am angegebenen Ort.
;; Aus JSON: start_mm, ende_mm, hz_grad, as_seite, es_seite, as_winkel,
;; es_winkel, anzahl (Default 1), delta_y (Default 0), delta_x (Default 0).
;; Rueckgabe: LISTE von Ergebnis-JSON-Strings (eine je Kopie).
(defun mubea:build-gf (e / tid0 start0 ende0 hz as-seite es-seite as-winkel es-winkel
anzahl dy dx i off nr tid s-mm e-mm ergebnisse)
(setq tid0 (ssg-val e "test_id")
start0 (mapcar 'float (ssg-val e "start_mm"))
ende0 (mapcar 'float (ssg-val e "ende_mm"))
hz (float (ssg-val e "hz_grad"))
as-seite (ssg-val e "as_seite")
es-seite (ssg-val e "es_seite")
as-winkel(ssg-val e "as_winkel")
es-winkel(ssg-val e "es_winkel")
anzahl (ssg-val e "anzahl")
dy (ssg-val e "delta_y")
dx (ssg-val e "delta_x"))
(if (or (null as-seite) (= as-seite "")) (setq as-seite "links"))
(if (or (null es-seite) (= es-seite "")) (setq es-seite "links"))
(if (or (null as-winkel) (= as-winkel "")) (setq as-winkel "90"))
(if (or (null es-winkel) (= es-winkel "")) (setq es-winkel "90"))
(if (or (null anzahl) (< anzahl 1)) (setq anzahl 1))
(setq dy (if dy (float dy) 0.0))
(setq dx (if dx (float dx) 0.0))
(setq ergebnisse '() i 0)
(while (< i anzahl)
(setq off (float i))
(setq nr (1+ i))
;; Bei mehreren Kopien fortlaufende ID GF_Mubea_01.. bilden (kompatibel zur
;; frueheren Einzelbenennung); bei genau einer Kopie den Template-Namen behalten.
(setq tid
(if (> anzahl 1)
(strcat tid0 "_" (if (< nr 10) (strcat "0" (itoa nr)) (itoa nr)))
tid0))
(setq s-mm (list (+ (car start0) (* dx off)) (+ (cadr start0) (* dy off)) (caddr start0)))
(setq e-mm (list (+ (car ende0) (* dx off)) (+ (cadr ende0) (* dy off)) (caddr ende0)))
(setq ergebnisse
(cons (mubea:build-gf-one tid s-mm e-mm hz as-seite es-seite as-winkel es-winkel)
ergebnisse))
(setq i (1+ i))
)
(reverse ergebnisse)
)
;; --- Separator (Eintraege mit "block"): _.INSERT der Blockdatei ---
;; Verwendet den in JSON genannten Block; faellt auf Separator_SP zurueck
;; (Block der "Separator einfuegen"-Routine ILS_SEPARATOR_INSERT).
(defun mubea:build-separator (e idx / blockname x y z rot pt pfad tid)
(setq blockname (ssg-val e "block")
x (float (ssg-val e "x"))
y (float (ssg-val e "y"))
z (float (ssg-val e "z"))
rot (ssg-val e "rotation"))
(if (null rot) (setq rot 0))
(setq rot (float rot))
(setq pt (list x y z))
(setq tid (strcat "SEPARATOR_" (itoa idx)))
(princ (strcat "\n [SEP] " tid " '" (if blockname blockname "?")
"' -> (" (rtos x 2 0) "," (rtos y 2 0) "," (rtos z 2 0) ") R=" (rtos rot 2 0)))
(setq pfad (and blockname (ssg-ils-block-datei blockname)))
(if (null pfad) (setq pfad (ssg-ils-block-datei "Separator_SP")))
(if pfad
(progn
(command "_.INSERT" pfad pt "" "" rot)
(mubea:ent-json tid "separator" (entlast)))
(progn
;; Kein Block gefunden -> sichtbaren Platzhalter zeichnen
(if (not (tblsearch "LAYER" "MUBEA_SEPARATOR"))
(entmake (list '(0 . "LAYER") '(2 . "MUBEA_SEPARATOR") '(70 . 0) '(62 . 1))))
(entmake (list '(0 . "CIRCLE") (cons 10 pt) (cons 40 150.0) '(8 . "MUBEA_SEPARATOR")))
(princ " (Block fehlt - Platzhalter)")
(mubea:result-json tid "separator" "placeholder"
(if blockname blockname "S-LP") nil pt nil)))
)
;; --- JSON-Export der Mubea-Ergebnisse ---
(defun mubea:export-results (tests-out-dir / out-json f first)
(if (null *mubea-test-results*)
(princ "\n Keine Mubea-Ergebnisse vorhanden.")
(progn
(vl-mkdir tests-out-dir)
(setq out-json (strcat tests-out-dir "/mubea_results.json"))
(setq f (open out-json "w"))
(if f
(progn
(write-line "[" f)
(setq first T)
(foreach r *mubea-test-results*
(if (not first) (write-line "," f))
(write-line r f)
(setq first nil))
(write-line "]" f)
(close f)
(princ (strcat "\n Ergebnisse: " out-json)))
(princ (strcat "\n FEHLER: Kann " out-json " nicht schreiben!")))))
)
;; ============================================================
;; C:TEST_MUBEA - Hauptbefehl
;; ============================================================
(defun c:TEST_MUBEA ( / json-datei daten eintrag tid blk res r
results-list sep-idx anz-ok anz-fehler kind)
;; Benoetigte Feature-Module laden
(ssg-ensure "KreiselInsert")
(ssg-ensure "VarioFoerderer")
(ssg-ensure "Gefaellestrecke")
(ssg-ensure "SSG_LIB_Commands")
(ssg-start "TEST_MUBEA" '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA")))
(setvar "OSMODE" 0)
(setvar "ATTREQ" 0)
(setvar "ATTDIA" 0)
;; Block-Bibliothek (Bogen-/AS-/ES-Masse) initialisieren - fuer Vario+GF noetig
(if (or (not *lib-initialized*) (null bogen-auf))
(init-bibliothek))
;; Caches fuer GF-Init-Workaround und Hoehenkalibrierung pro Lauf zuruecksetzen
;; (siehe mubea:ensure-gf-init / mubea:gf-calibrate weiter oben)
(setq *mubea-gf-init-done* nil)
(setq *mubea-gf-kalib-cache* nil)
;; Testdaten laden
(setq json-datei (strcat (getenv "DXFMAKRO") "/tests/testdata/mubea.json"))
(if (not (findfile json-datei))
(progn
(princ (strcat "\n[TEST_MUBEA] FEHLER: " json-datei " nicht gefunden!"))
(ssg-end)
(exit)))
(setq daten (ssg-load-json json-datei))
(if (null daten)
(progn
(princ "\n[TEST_MUBEA] FEHLER: Testdaten konnten nicht geladen werden.")
(ssg-end)
(exit)))
(princ "\n\n================================================================")
(princ "\n TEST_MUBEA - Mubea-Gesamtmodell")
(princ (strcat "\n " (itoa (length daten)) " Eintraege geladen"))
(princ "\n================================================================")
(setq results-list '() sep-idx 0 anz-ok 0 anz-fehler 0)
(foreach eintrag daten
(setq tid (ssg-val eintrag "test_id"))
(setq res
(cond
;; Separator: hat "block"
((ssg-val eintrag "block")
(setq sep-idx (1+ sep-idx))
(mubea:build-separator eintrag sep-idx))
;; Kreisel: hat "id"
((ssg-val eintrag "id")
(mubea:build-kreisel eintrag))
;; Vario: test_id "VF_*"
((and tid (>= (strlen tid) 3) (= (substr tid 1 3) "VF_"))
(mubea:build-vario eintrag))
;; Gefaellestrecke: test_id "GF_*"
((and tid (>= (strlen tid) 3) (= (substr tid 1 3) "GF_"))
(mubea:build-gf eintrag))
(T
(princ (strcat "\n WARNUNG: Unbekannter Eintrag uebersprungen"))
nil)))
;; mubea:build-gf liefert eine LISTE (Template -> mehrere Strecken), die
;; uebrigen Builder je einen String. Beides einheitlich einsammeln.
(if res
(progn
(if (= (type res) 'STR) (setq res (list res)))
(foreach r res
(setq results-list (cons r results-list))
(if (or (vl-string-search "\"executed\"" r)
(vl-string-search "\"placeholder\"" r))
(setq anz-ok (1+ anz-ok))
(setq anz-fehler (1+ anz-fehler)))))))
(princ "\n================================================================")
(princ (strcat "\n Ergebnis: " (itoa anz-ok) " OK, " (itoa anz-fehler) " Fehler"))
(princ "\n================================================================")
(setq *mubea-test-results* (reverse results-list))
(ssg-end)
(princ "\n TEST_MUBEA abgeschlossen.")
(princ)
)