Files
dxfmakros/tests/test_hundm05.lsp
T
s.ayadi 7242e815f8 HundM05-Testfall und DXF-Analyse-Skripte
- lib/dxf_scan_components.py: erkennt ILS-/Omniflo-Komponenten in fremden
  Projektzeichnungen und schreibt sie im Testdaten-JSON-Schema aus
- lib/dxf_abbild.py: getreue Abbildung der Bauteile einer Fremdzeichnung
  (Attribute, Weltkoordinaten, Unterkomponenten) ohne Interpretation
- tests/test_hundm05.{lsp,py}, testdata/hundm05.json, conftest-Fixtures und
  alltests.json-Eintrag fuer den Kreisel-Abschnitt aus ST500592_05.dxf
- menu: TEST_HUNDM05 im Testmenue, Connection_Insert/Edit in SSG_LIB.cui
- Doku: hartkodierte Pfade durch (getenv "DXFMAKRO") ersetzt

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 12:10:58 +02:00

602 lines
27 KiB
Common Lisp

;; ============================================================
;; test_hundm05.lsp - Integrationstest: baut den Kreisel-Abschnitt der
;; Anlage HundM aus der Fremdzeichnung ST500592_05.dxf nach.
;;
;; Liest tests/testdata/hundm05.json (erzeugt von lib/dxf_scan_components.py
;; mit --fenster 216000 144000 228000 156000) und baut in EINER Zeichnung:
;; - Kreisel (Eintraege mit "id", OHNE "type") Block KREISEL_*
;; "function": "connect" -> kreisel-connect-script (Start-/
;; Endpunkt, abstand+rotation werden intern berechnet)
;; "function": "insert" -> kreisel-insert-script
;; - Vario (test_id "VF_*") Block VF_* via variofoerderer-einfuegen
;; - Gefaelle (test_id "GF_*") Block GF_* via gefaellestrecke-einfuegen
;; - Gerade (test_id "GERADE_*") waagrechte Foerderstrecke via
;; insert-hz-scaled-block-by-ks (Staustrecke_SP_1000_mm)
;; - Omniflo (Eintraege mit "type") - NICHT gebaut, siehe hundm05:build-omniflo
;; - Sensoren (Eintraege mit "block") via _.INSERT
;;
;; Speichert (via SSG_RUN_ALL_TESTS bei "save":"dxf"):
;; tests/output/hundm05_tests.dxf
;; tests/output/hundm05_results.json (via hundm05:export-results)
;;
;; Unterschiede zu tests/test_mubea.lsp - warum eine eigene Datei und kein
;; gemeinsamer Treiber:
;; 1. Der Kreisel kommt hier als "connect" (zwei Punkte aus dem DXF), bei
;; Mubea als "insert" (Punkt + abstand + rotation von Hand).
;; 2. Die VarioFoerderer nennen ihren Startpunkt "startpunkt" (Liste, wie
;; object_data.md 3.1) statt x/y/z, und sie haben kein "L_GF1" - der
;; L_GF-Split ist hier "gleichmaessig" (Default nach object_data.md).
;; 3. Es gibt den Eintragstyp "gerade", den mubea.json nicht kennt.
;; 4. Eintraege mit "type" (Omniflo) muessen VOR "id" abgefragt werden, weil
;; sie beides tragen - bei Mubea gab es diesen Fall nicht.
;; test_mubea.lsp bleibt dadurch unangetastet und die bestehende Suite
;; unveraendert lauffaehig.
;;
;; 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_hundm05.lsp"))
;; TEST_HUNDM05
;; ============================================================
;; --- Ergebnis eines Bau-Schritts als JSON-String erzeugen ---
(defun hundm05: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 hundm05:ent-json (test-id kind ent / ed)
(if ent
(progn
(setq ed (entget ent))
(hundm05:result-json test-id kind "executed"
(cdr (assoc 2 ed)) (cdr (assoc 5 ed)) (cdr (assoc 10 ed))
(ssg-attrib-read ent)))
(hundm05:result-json test-id kind "failed" nil nil nil nil))
)
;; --- Letzten INSERT mit Blocknamen-Praefix (rueckwaerts) suchen ---
(defun hundm05: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
)
;; --- Zahl aus JSON lesen, mit Default statt Fehler bei fehlendem Feld ---
;; (float nil) bricht in AutoLISP ab. hundm05.json laesst - anders als
;; mubea.json - bewusst Felder weg (z.B. "winkel" und "L_GF1" beim Vario, damit
;; der Solver selbst waehlt), darum hier grundsaetzlich abgesichert lesen.
(defun hundm05:num (e key default / v)
(setq v (ssg-val e key))
(if (numberp v) (float v) default)
)
;; --- Punkt-Liste aus JSON lesen ("startpunkt"/"start_mm": [x, y, z]) ---
;; Liefert NIEMALS einen Ersatzpunkt. Ein Ursprungs-Fallback (0 0 0) waere hier
;; fatal: er baut die Anlage sichtbar, aber komplett an der falschen Stelle -
;; genau das passiert, wenn das Array in der JSON ueber mehrere Zeilen
;; umgebrochen ist (siehe hundm05:startpunkt).
(defun hundm05:pt (e key / v)
(setq v (ssg-val e key))
(if (and v (listp v) (>= (length v) 3)) (mapcar 'float v) nil)
)
;; --- Startpunkt eines Eintrags lesen: Array-Feld, sonst flache x/y/z ---
;; Rueckgabe: Punkt oder nil. Bei nil MUSS der Aufrufer den Eintrag als
;; "failed" melden und darf nichts bauen.
;;
;; Haeufigste Ursache fuer nil: ssg-parse-json-array (ssg_core.lsp) liest die
;; JSON ZEILENWEISE, und ssg-cfg-parse-array braucht das ganze Array in einer
;; Zeile. Ein von json.dump(indent=2) umgebrochenes Array
;; "start_mm": [
;; 219540.85,
;; ...
;; ]
;; liefert nil. Die Ausgabe von lib/dxf_scan_components.py zieht Zahlen-Arrays
;; darum wieder auf eine Zeile (arrays_einzeilig).
(defun hundm05:startpunkt (e feld / p)
(setq p (hundm05:pt e feld))
;; Ersatzweise flache x/y/z-Felder zulassen (Schema wie mubea.json)
(if (and (null p) (numberp (ssg-val e "x")))
(setq p (list (hundm05:num e "x" 0.0) (hundm05:num e "y" 0.0)
(hundm05:num e "z" 0.0))))
p
)
;; --- Meldung fuer einen nicht lesbaren Startpunkt ---
(defun hundm05:kein-startpunkt (tid feld)
(princ (strcat "\n FEHLER: " tid " - Feld \"" feld "\" nicht lesbar."
" Steht das Array in EINER Zeile? ssg-cfg-parse-array kann"
" keine umgebrochenen Arrays lesen. Nichts gebaut."))
)
;; ============================================================
;; Bau-Funktionen je Elementtyp
;; ============================================================
;; --- Kreisel: "connect" (Start-/Endpunkt) oder "insert" (Punkt+abstand) ---
;; Der Scanner liefert fuer AN8/SP8-Paare "connect": beide Stationen stehen im
;; DXF, abstand und rotation berechnet kreisel-connect-script daraus selbst
;; (abstand = Distanz - *kreisel-durchmesser*). "insert" bleibt fuer von Hand
;; gepflegte Eintraege moeglich (Schema wie mubea.json).
(defun hundm05:build-kreisel (e / id fn typ hoehe ptStart ptEnd x y z abstand rotation ent)
(setq id (ssg-val e "id")
fn (ssg-val e "function")
typ (ssg-val e "typ")
hoehe (ssg-val e "hoehe"))
(if (null typ) (setq typ "STANDARD"))
(if (numberp hoehe) (setq hoehe (float hoehe)) (setq hoehe nil))
(if (= fn "connect")
(progn
(setq ptStart (list (hundm05:num e "start_x" 0.0)
(hundm05:num e "start_y" 0.0)
(hundm05:num e "start_z" 0.0)))
(setq ptEnd (list (hundm05:num e "end_x" 0.0)
(hundm05:num e "end_y" 0.0)
(hundm05:num e "end_z" 0.0)))
(princ (strcat "\n [KREISEL] " id " connect ("
(rtos (car ptStart) 2 0) "," (rtos (cadr ptStart) 2 0) ") -> ("
(rtos (car ptEnd) 2 0) "," (rtos (cadr ptEnd) 2 0) ")"
" H=" (if hoehe (rtos hoehe 2 0) "-")))
(setq ent (kreisel-connect-script ptStart ptEnd typ hoehe)))
(progn
(setq x (hundm05:num e "x" 0.0)
y (hundm05:num e "y" 0.0)
z (hundm05:num e "z" 0.0)
abstand (hundm05:num e "abstand" *kreisel-default-laenge*)
rotation (hundm05:num e "rotation" 0.0))
(princ (strcat "\n [KREISEL] " id " insert (" (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 (list x y z) abstand rotation typ
(if hoehe hoehe z)))))
(hundm05:ent-json id "kreisel" ent)
)
;; --- Vario (VF_*) ---
;; Wie mubea:build-vario-one, aber ohne "winkel"-Zwang und ohne L_GF1-Ziel:
;; hundm05.json gibt nur deltaL/deltaH/richtung/seite/hz vor, den Bogenwinkel
;; waehlt berechne-alle-winkel selbst (kleinster gueltiger Kandidat) und der
;; L_GF-Split ist "gleichmaessig" (beide Haelften L_GF/2, Default nach
;; object_data.md 3.1). Ist "L_GF1" doch gesetzt, wird es als Vorlauf-Ziel
;; verwendet - dann verhaelt sich der Treiber wie der Mubea-Treiber.
(defun hundm05:build-vario (e / tid typ richtung deltaL deltaH seite hz startpunkt
gf1-ziel ergebnis best-winkel L_GF L_VF gf1 gf2
hoehe-von hoehe-bis lastEnt ent vf-nummer)
(setq tid (ssg-val e "test_id")
typ (ssg-val e "typ")
richtung (ssg-val e "richtung")
deltaL (hundm05:num e "deltaL" 0.0)
deltaH (hundm05:num e "deltaH" 0.0)
seite (ssg-val e "seite")
hz (hundm05:num e "hz" 0.0)
gf1-ziel (ssg-val e "L_GF1"))
(if (or (null typ) (= typ "")) (setq typ "standard"))
(if (or (null seite) (= seite "")) (setq seite "rechts"))
(if (or (null richtung) (= richtung "")) (setq richtung "Auf"))
(setq startpunkt (hundm05:startpunkt e "startpunkt"))
(if (null startpunkt) (hundm05:kein-startpunkt tid "startpunkt"))
(princ (strcat "\n [VARIO] " tid " (" typ ") -> deltaL=" (rtos deltaL 2 0)
" deltaH=" (rtos deltaH 2 0) " " richtung
" hz=" (rtos hz 2 0)))
(setq ergebnis
(if (= typ "etage")
(berechne-etage deltaL deltaH richtung seite)
(berechne-alle-winkel deltaL deltaH richtung nil)))
(setq best-winkel (car ergebnis) L_GF (cadr ergebnis) L_VF (caddr ergebnis))
;; startpunkt MUSS mit in die Bedingung: ohne ihn wuerde am Ursprung gebaut.
(if (and startpunkt best-winkel L_GF L_VF (> L_GF 0) (> L_VF 0))
(progn
;; L_GF-Split: mit L_GF1-Vorgabe wie bei Mubea (Vorlauf-Ziel), sonst
;; gleichmaessig auf Vor- und Nachlauf verteilt.
(if (numberp gf1-ziel)
(setq gf1 (min (float gf1-ziel) L_GF))
(setq gf1 (/ L_GF 2.0)))
(setq gf2 (max 0.0 (- L_GF gf1)))
(princ (strcat " -> Winkel=" (itoa best-winkel) " L_GF=" (rtos L_GF 2 1)
" L_VF=" (rtos L_VF 2 1) " L_GF1=" (rtos gf1 2 1)
" L_GF2=" (rtos gf2 2 1)))
;; Absolute Hoehen eintragen. hoehe-von/hoehe-bis gehen NUR in Label und
;; Attribute (MONTAGEHOEHE_m, HOEHE_VON_mm - vf_core.lsp), die Geometrie
;; kommt aus startpunkt. test_mubea.lsp uebergibt hier 0 und traegt damit
;; RELATIVE Hoehen ein; fuer diese Zeichnung stehen die echten Z-Werte im
;; DXF, also werden sie auch verwendet - sonst exportiert EXPORTCSV eine
;; falsche Montagehoehe (bei VF_HundM05_11 waeren es 2.914 m statt
;; -1.028 m).
(setq hoehe-von (caddr startpunkt))
(setq hoehe-bis (+ hoehe-von (if (= richtung "Auf") deltaH (- deltaH))))
(setq lastEnt (vf-lastent-ohne-attribute))
(if (= typ "etage")
(etage-foerderanlage-einfuegen deltaL deltaH richtung best-winkel gf1 gf2 L_VF startpunkt seite hz)
(variofoerderer-einfuegen deltaL deltaH richtung best-winkel gf1 gf2 L_VF startpunkt seite hz))
;; Blocknummer wie in der Produktion ueber vf-next-number holen (NICHT
;; fest auf 1) - sonst trifft das _.BLOCK des zweiten Foerderers auf einen
;; schon vorhandenen Namen, BricsCAD fragt "Neu definieren?" und
;; verschluckt die feste Antwortfolge (siehe test_mubea.lsp).
(setq vf-nummer (vf-next-number))
(setq ent (vf-block-erstellen typ seite vf-nummer 0 hoehe-von hoehe-bis
deltaH deltaL L_VF gf1 gf2 richtung best-winkel
startpunkt lastEnt hz "0" "Schoenenberger Geruest"))
(if (or (null ent) (/= (type ent) 'ENAME))
(setq ent (hundm05:last-insert-prefix "VF_")))
;; SSG_VF_EDIT-XDATA schreiben, sonst verweigert VARIOFOERDERER_EDIT beim
;; Doppelklick die Bearbeitung (vfs-edit-keine-xdata).
(if (and ent (= (type ent) 'ENAME) (member typ '("standard" "etage")))
(vf-edit-xdata-schreiben ent typ hz))
(hundm05:ent-json tid "vario" ent))
(progn
;; Ursache benennen, nicht pauschal "nicht loesbar" melden: ein fehlender
;; Startpunkt hat nichts mit dem Winkel-Solver zu tun, und eine falsche
;; Diagnose schickt die Fehlersuche in die falsche Richtung.
(if (null startpunkt)
(princ (strcat "\n FEHLER: " tid " nicht gebaut - Startpunkt fehlt"
" (siehe Meldung oben)."))
(princ (strcat "\n FEHLER: " tid " deltaL=" (rtos deltaL 2 0)
" deltaH=" (rtos deltaH 2 0) " nicht loesbar"
" (kein gueltiges L_GF/L_VF).")))
(hundm05:result-json tid "vario" "failed" nil nil startpunkt nil)))
)
;; --- Gefaellestrecke: Init-Konflikt umgehen -----------------------------
;; gefaellestrecke-einfuegen prueft "(if (not *lib-initialized*)
;; (gf-init-bibliothek))" - dasselbe Flag, das init-bibliothek (Vario) fuer
;; SEINE eigenen aus-dx/ein-dx setzt. Wird zuerst ein Vario gebaut, kaeme
;; gf-init-bibliothek nie mehr zum Zug und aus-dx/ein-dx blieben die
;; Vario-Werte. Darum einmal je Lauf erzwingen (wie in test_mubea.lsp).
(defun hundm05:ensure-gf-init ( / )
(if (not *hundm05-gf-init-done*)
(progn
(setq *lib-initialized* nil)
(gf-init-bibliothek)
(setq *lib-initialized* t)
(setq *hundm05-gf-init-done* T)))
)
;; --- Eine Gefaellestrecke bauen (Modus 1) ---
;; Aufbau ausschliesslich aus Anfangspunkt und horizontaler Gesamtlaenge
;; deltaL - KEIN Endpunkt (wie Produktion/gefaellestrecke.dcl Modus 1, siehe
;; object_data.md 2.1). L_stau aus deltaL abgeleitet, Formel identisch zu
;; test_gefaellestrecke.lsp/test_mubea.lsp.
(defun hundm05:build-gf (e / tid start-mm deltaL hz as-seite es-seite as-winkel
es-winkel winkel)
(setq tid (ssg-val e "test_id")
start-mm (hundm05:startpunkt e "start_mm")
deltaL (hundm05:num e "deltaL_mm" 0.0)
hz (hundm05:num e "hz_grad" 0.0)
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")
winkel (hundm05:num e "winkel_grad" 3.0))
(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 (null start-mm)
(progn
(hundm05:kein-startpunkt tid "start_mm")
(hundm05:result-json tid "gefaellestrecke" "failed" nil nil nil nil))
(hundm05:build-gf-punkt tid start-mm deltaL hz as-seite es-seite
as-winkel es-winkel winkel))
)
;; --- Gefaellestrecke mit gepruefter Startkoordinate bauen ---
(defun hundm05:build-gf-punkt (tid start-mm deltaL hz as-seite es-seite
as-winkel es-winkel winkel / rad sep-x L_stau ent)
(hundm05:ensure-gf-init)
;; Element-Masse zur gewaehlten AS/ES-Variante setzen, damit aus-dx/ein-dx
;; zur Variante (90/30 Grad) passen; danach auf 90 zuruecksetzen.
(vf-set-as-masse as-winkel as-seite)
(vf-set-es-masse es-winkel es-seite)
(setq rad (* winkel (/ pi 180.0)))
(setq sep-x (* 300.0 (cos rad)))
(setq L_stau (/ (- deltaL (float aus-dx) (float ein-dx) sep-x) (cos rad)))
(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) " deltaL=" (rtos deltaL 2 0)
" winkel=" (rtos winkel 2 2) " L_stau=" (rtos L_stau 2 1)))
(if (<= L_stau 0.0)
(progn
(princ (strcat " -> uebersprungen: L_stau <= 0 (deltaL " (rtos deltaL 2 0)
" zu kurz fuer AS/ES-Elemente + Separator)"))
(vf-set-as-masse "90" as-seite)
(vf-set-es-masse "90" es-seite)
(hundm05:result-json tid "gefaellestrecke" "failed" nil nil start-mm nil))
(progn
(setq ent (car (gefaellestrecke-einfuegen
L_stau winkel start-mm as-seite es-seite hz
deltaL as-winkel es-winkel "0" "Schoenenberger Geruest")))
(vf-set-as-masse "90" as-seite)
(vf-set-es-masse "90" es-seite)
(hundm05:ent-json tid "gefaellestrecke" ent)))
)
;; --- Waagrechte Foerderstrecke (GERADE_*) ---
;; Antrieb/Umlenk-Paare ohne Hoehendifferenz sind keine VarioFoerderer (deren
;; Winkel-Solver braucht eine Hoehendifferenz, siehe GERADE_MAX_DELTA_H in
;; lib/dxf_scan_components.py). Gebaut wird mit derselben Produktions-Primitive,
;; die der Etage-Foerderer fuer seine waagrechten Abschnitte nutzt:
;; insert-hz-scaled-block-by-ks skaliert Staustrecke_SP_1000_mm auf die Laenge
;; und richtet sie an hz aus (vert-winkel 0 = waagrecht).
;; Rueckgabe der Primitive ist der Ausgangspunkt, nicht die ENAME - der Block
;; wird darum ueber (entlast) eingesammelt.
(defun hundm05:build-gerade (e / tid laenge hz startpunkt blk ent)
(setq tid (ssg-val e "test_id")
laenge (hundm05:num e "laenge" 0.0)
hz (hundm05:num e "hz" 0.0)
startpunkt (hundm05:startpunkt e "startpunkt"))
(if (null startpunkt)
(hundm05:kein-startpunkt tid "startpunkt")
(princ (strcat "\n [GERADE] " tid " -> Start=(" (rtos (car startpunkt) 2 0)
"," (rtos (cadr startpunkt) 2 0) "," (rtos (caddr startpunkt) 2 0)
") laenge=" (rtos laenge 2 0) " hz=" (rtos hz 2 0))))
(if (or (null startpunkt) (<= laenge 0.0))
(progn
(if startpunkt (princ " -> uebersprungen: laenge <= 0"))
(hundm05:result-json tid "gerade" "failed" nil nil startpunkt nil))
(progn
;; Block VORHER laden und pruefen: insert-hz-scaled-block-by-ks ruft bei
;; fehlendem Block (exit) auf und wuerde den gesamten Testlauf abbrechen.
(setq blk (ssg-ils-block-laden "Staustrecke_SP_1000_mm"))
(if (null blk)
(progn
(princ " -> uebersprungen: Block Staustrecke_SP_1000_mm nicht ladbar")
(hundm05:result-json tid "gerade" "failed" "Staustrecke_SP_1000_mm"
nil startpunkt nil))
(progn
(insert-hz-scaled-block-by-ks "Staustrecke_SP_1000_mm"
startpunkt laenge hz 0)
(setq ent (entlast))
(if (and ent (atoms-family 1 '("ssg-id-generate")))
(ssg-id-generate ent))
(hundm05:ent-json tid "gerade" ent)))))
)
;; --- Omniflo (Eintraege mit "type": "bogen"/"weiche") ---
;; BEWUSST nicht gebaut. omni:insert-dxf (Lisp/OmniModulInsert.lsp) holt den
;; Einfuegepunkt zwingend per (command "_.INSERT" dxf-pfad pause ...) - es gibt
;; in Produktion keinen Aufrufpfad, der X/Y programmgesteuert uebergibt (siehe
;; object_data.md 4.1). Ein JSON-Treiber muesste den _.INSERT selbst nachbauen.
;; Im gewaehlten Abschnitt (Kreisel-Bereich) kommt kein einziger Omniflo-Eintrag
;; vor, darum wird hier nichts auf Verdacht implementiert, was der Testlauf nie
;; ausfuehren koennte. Der eigene Zweig ist trotzdem noetig: Omniflo-Eintraege
;; tragen ein "id"-Feld und wuerden sonst als Kreisel gebaut werden.
(defun hundm05:build-omniflo (e / tid)
(setq tid (cond ((ssg-val e "id")) ((ssg-val e "sivasnr")) ("OMNIFLO")))
(princ (strcat "\n [OMNIFLO] " tid " -> uebersprungen (nicht scriptbar, "
"siehe object_data.md 4.1)"))
(hundm05:result-json tid "omniflo" "unsupported"
(ssg-val e "sivasnr") nil
(list (hundm05:num e "x" 0.0) (hundm05:num e "y" 0.0)
(hundm05:num e "z" 0.0))
nil)
)
;; --- Ein einzelner Sensor/Block: Blockdefinition laden, dann per Name einfuegen ---
;; Wie mubea:build-separator-one. Der in JSON genannte Block wird ueber
;; ssg-ils-block-laden vorab definiert (loest 2D/3D-Suffix auf); schlaegt das
;; fehl, wird auf Separator_SP zurueckgefallen - genau das greift bei den
;; Blocknamen "S-LP"/"S-SP", die es in data/ils/ nicht gibt.
(defun hundm05:build-sensor (e idx / blockname x y z rot tid eff n pt)
(setq blockname (ssg-val e "block")
x (hundm05:num e "x" 0.0)
y (hundm05:num e "y" 0.0)
z (hundm05:num e "z" 0.0)
rot (hundm05:num e "rotation" 0.0)
tid (cond ((ssg-val e "test_id"))
((strcat "SENSOR_" (itoa idx)))))
(setq pt (list x y z))
(princ (strcat "\n [SENSOR] " tid " '" (if blockname blockname "?")
"' -> (" (rtos x 2 0) "," (rtos y 2 0) "," (rtos z 2 0)
") R=" (rtos rot 2 0)))
;; NICHT (and blockname (ssg-ils-block-laden ...)) verwenden: AutoLISPs AND
;; liefert T statt des letzten Wertes, das _.INSERT wuerde dann einen Block
;; namens T einfuegen (siehe Kommentar in test_mubea.lsp).
(setq eff (if blockname (ssg-ils-block-laden blockname)))
(if (null eff) (setq eff (ssg-ils-block-laden "Separator_SP")))
(if eff
(progn
(command "_.INSERT" eff pt "" "" rot)
;; Verbleibende Prompts GEZAEHLT mit Enter abfangen - ein Prompt, der die
;; Leer-Antwort ablehnt, haelt CMDACTIVE dauerhaft > 0 und eine ungebremste
;; Schleife wuerde BricsCAD haengen lassen.
(setq n 0)
(while (and (> (getvar "CMDACTIVE") 0) (< n 5))
(command "")
(setq n (1+ n)))
(if (> (getvar "CMDACTIVE") 0)
(progn
(command)
(princ (strcat "\n WARNUNG: INSERT von '" eff
"' nicht sauber beendet - abgebrochen."))))
(if (atoms-family 1 '("ssg-id-generate"))
(ssg-id-generate (entlast)))
(hundm05:ent-json tid "sensor" (entlast)))
(progn
;; Kein Block gefunden -> sichtbaren Platzhalter zeichnen
(if (not (tblsearch "LAYER" "HUNDM05_SENSOR"))
(entmake (list '(0 . "LAYER") '(2 . "HUNDM05_SENSOR") '(70 . 0) '(62 . 1))))
(entmake (list '(0 . "CIRCLE") (cons 10 pt) (cons 40 150.0)
'(8 . "HUNDM05_SENSOR")))
(princ " (Block fehlt - Platzhalter)")
(hundm05:result-json tid "sensor" "placeholder"
(if blockname blockname "Separator_SP") nil pt nil)))
)
;; --- JSON-Export der Ergebnisse ---
(defun hundm05:export-results (tests-out-dir / out-json f first)
(if (null *hundm05-test-results*)
(princ "\n Keine HundM05-Ergebnisse vorhanden.")
(progn
(vl-mkdir tests-out-dir)
(setq out-json (strcat tests-out-dir "/hundm05_results.json"))
(setq f (open out-json "w"))
(if f
(progn
(write-line "[" f)
(setq first T)
(foreach r *hundm05-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_HUNDM05 - Hauptbefehl
;; ============================================================
(defun c:TEST_HUNDM05 ( / json-datei daten eintrag tid res r
results-list sensor-idx anz-ok anz-fehler)
(dbgopen "hundm05_id.dbg" "DXFM_LOG")
;; Benoetigte Feature-Module laden
(ssg-ensure "KreiselInsert")
(ssg-ensure "VarioFoerderer")
(ssg-ensure "Gefaellestrecke")
(ssg-ensure "SSG_LIB_Commands")
(ssg-start "TEST_HUNDM05" '(("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))
;; Cache fuer den GF-Init-Workaround pro Lauf zuruecksetzen
(setq *hundm05-gf-init-done* nil)
;; Testdaten laden
(setq json-datei (strcat (getenv "DXFMAKRO") "/tests/testdata/hundm05.json"))
(if (not (findfile json-datei))
(progn
(princ (strcat "\n[TEST_HUNDM05] FEHLER: " json-datei " nicht gefunden!"))
(ssg-end)
(dbgclose)
(exit)))
(setq daten (ssg-load-json json-datei))
(if (null daten)
(progn
(princ "\n[TEST_HUNDM05] FEHLER: Testdaten konnten nicht geladen werden.")
(ssg-end)
(dbgclose)
(exit)))
(princ "\n\n================================================================")
(princ "\n TEST_HUNDM05 - HundM Kreisel-Abschnitt (ST500592_05.dxf)")
(princ (strcat "\n " (itoa (length daten)) " Eintraege geladen"))
(princ "\n================================================================")
(setq results-list '() sensor-idx 0 anz-ok 0 anz-fehler 0)
(foreach eintrag daten
(setq tid (ssg-val eintrag "test_id"))
(setq res
(cond
;; Sensor/Insert: hat "block"
((ssg-val eintrag "block")
(setq sensor-idx (1+ sensor-idx))
(hundm05:build-sensor eintrag sensor-idx))
;; Omniflo VOR Kreisel pruefen: diese Eintraege tragen "id" UND "type"
((ssg-val eintrag "type")
(hundm05:build-omniflo eintrag))
;; Kreisel: hat "id" (ohne "type")
((ssg-val eintrag "id")
(hundm05:build-kreisel eintrag))
;; Vario: test_id "VF_*"
((and tid (>= (strlen tid) 3) (= (substr tid 1 3) "VF_"))
(hundm05:build-vario eintrag))
;; Gefaellestrecke: test_id "GF_*"
((and tid (>= (strlen tid) 3) (= (substr tid 1 3) "GF_"))
(hundm05:build-gf eintrag))
;; Waagrechte Strecke: test_id "GERADE_*"
((and tid (>= (strlen tid) 7) (= (substr tid 1 7) "GERADE_"))
(hundm05:build-gerade eintrag))
(T
(princ "\n WARNUNG: Unbekannter Eintrag uebersprungen")
nil)))
;; Alle Builder liefern hier je einen String (keine Templates mit "anzahl"
;; wie bei Mubea) - der Vollstaendigkeit halber trotzdem Listen zulassen.
(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)
(vl-string-search "\"unsupported\"" 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 *hundm05-test-results* (reverse results-list))
(ssg-end)
(princ "\n TEST_HUNDM05 abgeschlossen.")
(princ)
(dbgclose)
)