Files
dxfmakros/tests/test_linienzug.lsp
T
y.wang b709f89404 test: U-Form-Faelle, Formkriterium laengs und Reihenanordnung der Linienzug-Tests
Drei neue Modus-2-Testfaelle aus echten Laeufen, ein Kriterium, das eine
heute eingebaute Fehlgeometrie gefunden haette, und ein aufgeraeumtes
Layout der Testketten.

1. Drei U-Form-Faelle (tests/testdata/linienzug_modus2_uform_*.json),
   aufgezeichnet mit lib/vf_journal_export.py. Gefaltete Laeufe: der Pfad
   wechselt ueber zwei 90-Grad-Vario-Kurven dreimal die Fahrtrichtung, die
   Kletterstrecke liegt in einem ANDEREN Schenkel als der Lauf-Eingang -
   genau der am 2026-10-29 korrigierte Fall. Variiert werden GF-Verteilung
   (alles am Einlauf vs. 1/2+1/2) und Winkel; beides ist noetig, weil die
   Verteilung entscheidet, ob ueberhaupt GF im fremden Schenkel steht, und
   der Restfehler der alten Bilanz mit L_GF skalierte.
   c:TEST_LINIENZUG_MODUS2 ist dafuer tabellengesteuert - ein weiterer Fall
   ist jetzt eine Zeile in *tlz-m2-faelle*.

2. RUECKNAHME des Schluss-Solves (vfl-zsolve-schluss-ziel, heute frueh
   eingebaut). Er loeste den letzten Fueller gegen Richtung UND Laenge und
   traf den Zielpunkt dadurch in X und Y exakt - um den Preis, dass er die
   letzte Gerade um 6.15 Grad verdrehte (hz-plan -90 -> -96.15), in der
   Zeichnung als schiefer Schenkel sichtbar. Das widerspricht der
   Entscheidung vom 2026-10-26: quer MUSS 0 sein, LAENGS DARF ABWEICHEN -
   der Hauptstrang bleibt gerade, der seitliche Versatz des ES-Elements ist
   dessen Bauform. Die Messwerte von heute (0.56 / 0.7 mm quer) stammen
   nicht von dieser Aenderung, sondern von der Laengenbilanz des gefalteten
   Laufs (35ebee1) - der Zweig blieb dort ungenutzt. Die Begruendung steht
   jetzt an der Aufrufstelle, damit niemand den Solve erneut baut.

3. laengs wird geprueft - als FORM-, nicht als Genauigkeitskriterium
   (erwartung_laengs_mm, Toleranz 5 mm). Der Wert ist die Ausladung des
   ES-Elements und je Fall eine Konstante (104.68 bei ES_Element_30,
   637.58 bei ES_Element_90). Punkt 2 war am Querwert NICHT erkennbar - die
   Verdrehung macht ihn gerade null, der Test meldete OK, gefunden wurde sie
   mit dem Auge. Ein verdrehter Hauptstrang zielt auf den Endpunkt und
   drueckt laengs auf ~0; daran faellt er auf. Gegengeprueft: mit kuenstlich
   auf 0.01 mm gesetzter Toleranz kippen alle fuenf Faelle auf ABWEICHUNG.

4. Alle sieben Ketten des Menueeintrags entstehen als Reihe auf Y=0, 20 m
   Abstand, statt verstreut. Die Modus-1-Skripte brauchen dafuer nur ihren
   EINEN point_abs (alles Weitere ist point_rel mit dL/hz); bei Modus 2
   mussten PT-Eintraege UND die Pfadkoordinaten in SEGS (Felder 6..9)
   mitwandern, sonst zeigt der Pfad woanders hin als Start und Ziel.
   Reine Translation - Laengen, Winkel und die geprueften Abweichungen sind
   davon unabhaengig; Z blieb stehen, weil die Starthoehe ein Eingabewert
   des Solvers ist. Geprueft: erstes Segment beginnt exakt am Startpunkt,
   letztes endet exakt am Zielpunkt, Messwerte unveraendert.

5. Die drei letzten Verweise auf doc/VarioFoerderer_Linienzug_Prinzipien.md
   (bewusst unversioniert) tragen ihre Begruendung jetzt selbst: Entkopplung
   Neigung/XY, warum es genau EINEN Kletterer je Lauf gibt, und warum ein
   VF-Sub-Segment auf der 3-Grad-Basis beginnen und enden muss.

Geprueft: TEST_UNIT 897/897 (die 7 Tests der entfernten Funktion sind mit
ihr gegangen), TEST_LINIENZUG_MODUS2 5 von 5 OK, voller Menueablauf baut
sieben Ketten an den vorgesehenen Stellen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-29 14:28:57 +01:00

491 lines
22 KiB
Common Lisp

;; ============================================================
;; test_linienzug.lsp - Regressionstest fuer den VF-Linienzug (Modus 1)
;;
;; Eigenstaendige Testdatei (getrennt von test_foerderer.lsp).
;;
;; Der VF-Linienzug (vf-linienzug-modus) ist stark interaktiv (getpoint /
;; getstring / getint / getreal). Dieser Test spielt eine reale Session als
;; Skript ab: die vier Eingabe-Funktionen werden NUR waehrend des Testlaufs
;; durch Mocks ersetzt (mit Save/Restore), danach wieder hergestellt.
;;
;; Die Eingabe-Sequenz (Skript) wird NICHT mehr im Code hartkodiert, sondern
;; aus tests/testdata/linienzug_tests.json geladen. Jedes JSON-Objekt mit
;; "typ" ist ein Eingabe-Schritt in exakter Aufruf-Reihenfolge:
;; {"typ":"point_abs","wert":[x,y,z]} absoluter Punkt (getpoint)
;; {"typ":"point_rel","dL":..,"hz":..} Basispunkt + dL entlang Richtung hz
;; {"typ":"point_none"} getpoint ohne Punkt (Enter -> nil)
;; {"typ":"real","wert":..} getreal
;; {"typ":"int","wert":..} getint
;; {"typ":"string","wert":".."} getstring
;; Das erste Objekt (ohne "typ") enthaelt Beschreibung/Erwartung (Metadaten).
;;
;; Voraussetzungen:
;; - SSG_LIB geladen (VarioFoerderer.lsp inkl. vf_linienzug.lsp mit
;; vfl-getpoint, Gefaellestrecke.lsp, ssg_core.lsp)
;; - Umgebungsvariable DXFMAKRO gesetzt
;;
;; Speichert (Konvention fuer test_run_all.lsp):
;; - tests/output/linienzug_results.json (via linienzug:export-results)
;;
;; Erwartetes Ergebnis (Ist-Ziel-Report, siehe Metadaten in JSON):
;; dX ~ 0, dZ ~ 0 (Ziel exakt getroffen),
;; dY ~ 637 mm = seitlicher Fussabdruck des ES_Element_90 (90-Grad-Schwenk;
;; KEIN Baufehler, sondern die Bauform des ES-Elements).
;;
;; Aufruf in BricsCAD:
;; (load (strcat (getenv "DXFMAKRO") "/tests/test_linienzug.lsp"))
;; TEST_LINIENZUG (Standard-Szenario, linienzug_tests.json, in
;; alltests.json/SSG_RUN_ALL_TESTS registriert)
;; TEST_LINIENZUG_STUFE2 (Stufe-2-Fallback-Regressionstest Kettenende,
;; linienzug_tests_stufe2.json, manueller Aufruf)
;; ============================================================
;; Naechste Skript-Antwort liefern (FIFO). Bei leerer Queue Warnung + "".
(defun tlz-pop ( / v)
(if *tlz-q*
(progn (setq v (car *tlz-q*)) (setq *tlz-q* (cdr *tlz-q*)) v)
(progn (princ "\n [TEST_LINIENZUG] WARNUNG: Skript-Queue leer - unerwartete Abfrage!") "")
)
)
;; Mock-Eingabefunktionen. getpoint erhaelt als 1. Argument den Basispunkt
;; (p-akt) bei Folge-Segmenten bzw. einen Prompt-String beim Startpunkt.
;; Skript-Eintrag (A x y z) -> absoluter Punkt
;; Skript-Eintrag (P dL hz) -> Basispunkt + dL entlang Richtung hz (Grad),
;; sodass die Projektion in vfl-neue-linie-messen
;; exakt dL ergibt.
;; Skript-Eintrag KEIN -> nil (Enter ohne Punkt, z.B. optionale
;; AS-Zufuehrungsachse nicht gesetzt)
(defun tlz-getpoint (a b / spec base rad)
(setq spec (tlz-pop))
(setq base (if (and a (listp a)) a '(0.0 0.0 0.0)))
(cond
((= spec 'KEIN) nil)
((and (listp spec) (= (car spec) 'A))
(list (float (cadr spec)) (float (caddr spec)) (float (cadddr spec))))
(T
(progn
(setq rad (* (float (caddr spec)) (/ pi 180.0)))
(list (+ (car base) (* (float (cadr spec)) (cos rad)))
(+ (cadr base) (* (float (cadr spec)) (sin rad)))
(caddr base))
)
)
)
)
(defun tlz-getstring (a) (tlz-pop))
(defun tlz-getint (a) (tlz-pop))
(defun tlz-getreal (a) (tlz-pop))
;; --- Parsierte JSON-Daten in die Skript-Queue uebersetzen ---
;; Erwartet die von ssg-load-json gelieferte Liste von Alists.
;; Nur Objekte mit "typ" sind Eingabe-Schritte; Objekte ohne "typ"
;; (Metadaten) werden uebersprungen. Die Reihenfolge bleibt erhalten.
;; Rueckgabe: Queue im Format, das die Mock-Funktionen erwarten.
(defun tlz-daten->queue (daten / q typ)
(setq q nil)
(foreach schritt daten
(setq typ (ssg-val schritt "typ"))
(cond
((= typ "point_abs")
(setq q (cons (cons 'A (ssg-val schritt "wert")) q)))
((= typ "point_rel")
(setq q (cons (list 'P (ssg-val schritt "dL") (ssg-val schritt "hz")) q)))
((= typ "point_none")
(setq q (cons 'KEIN q)))
((= typ "real")
(setq q (cons (float (ssg-val schritt "wert")) q)))
((= typ "int")
(setq q (cons (ssg-val schritt "wert") q)))
((= typ "string")
(setq q (cons (ssg-val schritt "wert") q)))
)
)
(reverse q)
)
;; --- Metadaten-Objekt (Beschreibung/Erwartung) aus JSON-Daten holen ---
;; Liefert die erste Alist ohne "typ" oder nil.
(defun tlz-daten->meta (daten / meta)
(setq meta nil)
(foreach obj daten
(if (and (null meta) (null (ssg-val obj "typ")))
(setq meta obj)
)
)
meta
)
;; --- JSON-Export der Linienzug-Testergebnisse (Konvention fuer run-all) ---
(defun linienzug:export-results (tests-out-dir / out-json f)
(if (null *linienzug-test-results*)
(princ "\n Keine Linienzug-Ergebnisse vorhanden.")
(progn
(vl-mkdir tests-out-dir)
(setq out-json (strcat tests-out-dir "/linienzug_results.json"))
(setq f (open out-json "w"))
(if f
(progn
(write-line "[" f)
(write-line *linienzug-test-results* f)
(write-line "]" f)
(close f)
(princ (strcat "\n Ergebnisse: " out-json))
)
(princ (strcat "\n FEHLER: Kann " out-json " nicht schreiben!"))
)
)
)
)
;; --- Gemeinsamer Replay-Kern, von allen c:TEST_LINIENZUG*-Befehlen genutzt ---
;; befehl-anzeige: nur fuer die Konsolen-Ueberschrift/ssg-start (String).
;; test-id: Bezeichner fuer das *linienzug-test-results*-JSON (run-all-
;; Konvention, siehe linienzug:export-results).
;; Rueckgabe: (status schritte-anz rest-anz) - status ist "OK"/"ABWEICHUNG"/
;; "FEHLER". Setzt zusaetzlich *linienzug-test-results* (letzter Aufruf
;; gewinnt - identisch zum bisherigen Verhalten von c:TEST_LINIENZUG, das
;; ebenfalls nur einen einzigen Testfall pro Lauf kennt).
(defun tlz-run-datei (json-datei test-id befehl-anzeige /
daten meta schritte-anz rest-anz status
old-vgp old-gs old-gi old-gr err old-gui)
(if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek))
(if (null (car (atoms-family 1 '("VF-LINIENZUG-MODUS"))))
(progn
(alert (strcat "vf-linienzug-modus nicht geladen!\n"
"Bitte Menue/Module laden (VarioFoerderer + Gefaellestrecke)."))
(exit)
)
)
(princ "\n================================================================")
(princ (strcat "\n " befehl-anzeige " - VF-Linienzug Replay-Test"))
(princ "\n================================================================")
;; --- Eingabe-Skript aus JSON laden ---
(setq daten (ssg-load-json json-datei))
(if (null daten)
(progn
(alert (strcat "Testdaten nicht gefunden:\n" json-datei))
(exit)
)
)
(setq meta (tlz-daten->meta daten))
(setq *tlz-q* (tlz-daten->queue daten))
(setq schritte-anz (length *tlz-q*))
(princ (strcat "\n Testdaten: " json-datei))
(princ (strcat "\n Eingabe-Schritte: " (itoa schritte-anz)))
(if meta (princ (strcat "\n Szenario: " (ssg-val meta "beschreibung"))))
(ssg-start befehl-anzeige '(("OSMODE") ("ATTREQ") ("ATTDIA")))
(setvar "OSMODE" 0)
(setvar "ATTREQ" 0)
(setvar "ATTDIA" 0)
;; GUI aus: keine Wizard-DCL-Dialoge waehrend des automatischen Replays -
;; die Eingaben kommen aus der Mock-Queue (getstring/getint/getreal), siehe
;; ssg-gui-aus/ssg-gui-p in ssg_core.lsp und vfl-wizard-mode-p.
(setq old-gui (if (boundp '*ssg-gui-aus*) *ssg-gui-aus*))
(if (car (atoms-family 1 '("SSG-GUI-AUS"))) (ssg-gui-aus))
;; --- Eingabe-Funktionen sichern und durch Mocks ersetzen ---
;; Punkte ueber den Produktions-Wrapper vfl-getpoint (feste 2-Arity),
;; getstring/getint/getreal (alle 1-argumentig) direkt.
(setq old-vgp vfl-getpoint old-gs getstring old-gi getint old-gr getreal)
(setq vfl-getpoint tlz-getpoint
getstring tlz-getstring
getint tlz-getint
getreal tlz-getreal)
;; --- Linienzug-Modus abspielen (Fehler abfangen, damit Restore sicher laeuft) ---
(setq err (vl-catch-all-apply 'vf-linienzug-modus '()))
;; --- Eingabe-Funktionen wiederherstellen (IMMER) ---
(setq vfl-getpoint old-vgp getstring old-gs getint old-gi getreal old-gr)
;; GUI-Zustand von VOR dem Test wiederherstellen (auch nach Fehler) - NICHT
;; pauschal einschalten: unter SSG_RUN_ALL_TESTS ist sie fuer den ganzen
;; Batch aus, und ein pauschales ssg-gui-an liess den anschliessenden
;; EXPORTSIVAS nach Hoehen-Check/Bereinigen fragen (Lauf hing).
(setq *ssg-gui-aus* old-gui)
(setq rest-anz (length *tlz-q*))
(princ "\n================================================================")
(if (vl-catch-all-error-p err)
(progn
(setq status "FEHLER")
(princ (strcat "\n " befehl-anzeige ": FEHLER -> "
(vl-catch-all-error-message err))))
(progn
(setq status (if (= rest-anz 0) "OK" "ABWEICHUNG"))
(princ (strcat "\n " befehl-anzeige ": Kette gebaut."))
(if (> rest-anz 0)
(princ (strcat "\n WARNUNG: " (itoa rest-anz)
" Skript-Eintraege NICHT verbraucht (Ablauf weicht ab)."))
(princ "\n Alle Skript-Eingaben verbraucht (Ablauf wie erwartet)."))
(if meta
(princ (strcat "\n Erwartung Ist-Ziel: dX~"
(itoa (ssg-val meta "erwartung_dx_mm")) ", dZ~"
(itoa (ssg-val meta "erwartung_dz_mm")) ", dY~"
(itoa (ssg-val meta "erwartung_dy_mm")) " mm ("
(ssg-val meta "erwartung_hinweis") ").")))
)
)
(princ "\n================================================================")
;; --- Ergebnis fuer run-all als JSON-String ablegen ---
(setq *linienzug-test-results*
(strcat " {\n"
" \"test_id\": \"" test-id "\",\n"
" \"status\": \"" status "\",\n"
" \"schritte_gesamt\": " (itoa schritte-anz) ",\n"
" \"schritte_offen\": " (itoa rest-anz) "\n"
" }"))
(ssg-end)
(list status schritte-anz rest-anz)
)
(defun c:TEST_LINIENZUG ( / json-datei)
(setq json-datei
(strcat (getenv "DXFMAKRO") "\\tests\\testdata\\linienzug_tests.json"))
(tlz-run-datei json-datei "LZ_Standard" "TEST_LINIENZUG")
(princ)
)
;; Zweiter Testfall (Regressionstest, in dieser Session neu aufgenommen):
;; Stufe-2-Fallback beim Kettenende (typ=VF winkel=0, horizontales VF + GF-
;; Ausgleich), siehe Beschreibung/Kommentare in linienzug_tests_stufe2.json.
;; Eigenstaendiger Befehl statt zweitem Array-Eintrag in linienzug_tests.json,
;; damit LZ_Standard unangetastet bleibt (jede JSON-Datei bleibt ein
;; einzelnes Szenario, wie von tlz-daten->queue/-meta erwartet). NICHT in
;; tests/alltests.json registriert - manueller Einzelaufruf, bei Bedarf
;; spaeter dort ergaenzen.
(defun c:TEST_LINIENZUG_STUFE2 ( / json-datei)
(setq json-datei
(strcat (getenv "DXFMAKRO") "\\tests\\testdata\\linienzug_tests_stufe2.json"))
(tlz-run-datei json-datei "LZ_Stufe2_Kettenende" "TEST_LINIENZUG_STUFE2")
(princ)
)
;; ============================================================
;; MODUS 2 (aus 3D-Linie mit Zielhoehe) - Replay echter Laeufe
;; ============================================================
;; Anders als die Modus-1-Faelle oben braucht Modus 2 KEINE Mocks und KEINEN
;; gezeichneten Pfad: sein Journal traegt einen SEGS-Eintrag mit der kompletten
;; Segment-Essenz (Typ, Richtung, Laenge, Eckwinkel, Seite, Start/Ende), ist
;; also von den Original-Objekten unabhaengig - dieselbe Eigenschaft, auf der
;; schon vfl-konvertiere-ent beruht (vfl-journal-hat-segs-p). Der Test spielt
;; daher das echte, aus der Zeichnung ausgelesene Journal ab.
;;
;; Die Faelle sind zwei BEWUSST gebildete Paare, kein Sammelsurium:
;;
;; A/B (Ecke als Vario-Kurve bzw. als GF-Bogen) - derselbe Pfad, nur die
;; 90-Grad-Ecke einmal waagerecht und einmal geneigt eingebaut. Sie laufen
;; durch verschiedene Zweige (mit und ohne flache Zone), und genau der
;; VERGLEICH hat am 2026-10-26/27 Fehler aufgedeckt, die ein einzelner
;; Lauf nicht gezeigt haette (ES-Fussabdruck am falschen Segment: 878 mm
;; nur in der GF-Bogen-Variante).
;;
;; U-Form 1..3 (gefalteter Lauf) - der Pfad wechselt ueber zwei 90-Grad-
;; Vario-Kurven dreimal die Fahrtrichtung, die Kletterstrecke liegt
;; dadurch in einem ANDEREN Schenkel als der Lauf-Eingang. Hier variieren
;; GF-VERTEILUNG (alles am Einlauf vs. 1/2+1/2) und WINKEL. Beides ist
;; noetig: die Verteilung entscheidet, ob ueberhaupt GF im fremden
;; Schenkel steht (Nachrechner, Fueller-A), und der Restfehler der alten
;; Bilanz skalierte mit L_GF, also mit dem Winkel (6 Grad 90 mm, 9 Grad
;; 934 mm) - zwei Laeufe mit demselben Winkel haetten das verdeckt.
;;
;; Einen Fall zu loeschen nimmt dem Test den jeweiligen Vergleich und damit
;; seinen Wert.
;;
;; Aufgezeichnet mit lib/vf_journal_export.py aus echten BricsCAD-Laeufen
;; (A/B am 2026-10-27, U-Form am 2026-10-29).
;; Pruefkriterium (Nutzer-Vorgabe 2026-10-26): quer zur ES-KS_AUS-Achse und dZ
;; MUESSEN ~0 sein, laengs DARF abweichen (Ausladung des ES-Elements).
(setq *tlz-m2-toleranz* 1.0) ; mm; 0.2 ist die Messaufloesung der Blockmasse
;; Toleranz fuer den LAENGS-Wert gegen "erwartung_laengs_mm" aus den Testdaten.
;; Der Wert ist die Ausladung des ES-Elements und damit je Fall eine Konstante
;; (gemessen: 104.68 mm bei ES_Element_30, 637.58 mm bei ES_Element_90) - 5 mm
;; reichen also fuer Rundung, ohne echte Abweichungen durchzulassen.
;;
;; Warum ueberhaupt geprueft, wo laengs doch ausdruecklich abweichen DARF:
;; Am 2026-10-29 loeste ein Experiment den Schluss-Fueller gegen Richtung UND
;; Laenge und traf den Zielpunkt dadurch in X und Y exakt - um den Preis, dass
;; es die letzte Gerade um 6.15 Grad verdrehte (sichtbar schiefer Schenkel im
;; U-Form-Fall). quer und dZ waren dabei NULL, der Test meldete OK; gefunden
;; wurde der Fehler nur mit dem Auge in der Zeichnung.
;; laengs ist der Wert, der das verraet: ein verdrehter Hauptstrang zielt auf
;; den Endpunkt und drueckt laengs von ~638 mm auf ~0. Die Pruefung ist also
;; kein zweites Genauigkeitskriterium, sondern ein Formkriterium - sie stellt
;; sicher, dass der ES-Versatz noch da ist, wo er hingehoert.
(setq *tlz-m2-laengs-toleranz* 5.0)
;; Wert als String - eigene Kopie statt hmrec:als-string aus
;; test_hm_recformat.lsp: diese Datei soll fuer sich allein ladbar bleiben.
(defun tlz-m2-str (v)
(cond ((null v) "")
((= (type v) 'STR) v)
((= (type v) 'INT) (itoa v))
((= (type v) 'REAL) (rtos v 2 6))
(T (vl-princ-to-string v))))
;; Ein JSON-Record -> Journal-Eintrag (kind . wert), nil bei Metadaten-Zeile.
(defun tlz-m2-eintrag->journal (e / art)
(setq art (ssg-val e "art"))
(cond
((null art) nil)
((= art "PT") (cons "PT" (list (float (ssg-val e "x"))
(float (ssg-val e "y"))
(float (ssg-val e "z")))))
((= art "REAL") (cons "REAL" (float (ssg-val e "wert"))))
((= art "INT") (cons "INT" (fix (ssg-val e "wert"))))
;; SEGS traegt im Journal eine LISTE von Segment-Records, nicht den
;; String - genau diese Umwandlung macht auch der XDATA-Decoder
;; (vfl-journal-string->entry). Ohne sie bekommt vfl-in-segmente den
;; Rohstring und der erste mapcar darauf stirbt (real 2026-10-27).
((= art "SEGS") (cons "SEGS" (vfl-string->segs (tlz-m2-str (ssg-val e "wert")))))
(T (cons art (tlz-m2-str (ssg-val e "wert"))))))
;; Einen Modus-2-Fall abspielen. Rueckgabe: (status laengs quer dz offen).
(defun tlz-m2-run (json-datei test-id anzeige /
daten journal meta offen err status abw j
old-headless old-gui quer dz laengs
soll-laengs laengs-ok)
(if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek))
(princ (strcat "\n\n--- " anzeige " ---"))
;; Vorbelegen, damit der Ergebnis-Record auch im Fehlerzweig Zahlen hat -
;; sonst stirbt das abschliessende rtos an nil (real passiert 2026-10-27).
(setq laengs 0.0 quer 999.0 dz 999.0 offen 0 status "FEHLER")
(setq daten (ssg-load-json json-datei))
(if (null daten)
(progn (princ (strcat "\n FEHLER: Testdaten fehlen: " json-datei))
(setq status "FEHLER"))
(progn
(setq meta (car daten))
(setq journal '())
(foreach e daten
(setq j (tlz-m2-eintrag->journal e))
(if j (setq journal (cons j journal))))
(setq journal (reverse journal))
(princ (strcat "\n Journal-Eintraege: " (itoa (length journal))))
(if meta (princ (strcat "\n Szenario: " (ssg-val meta "kurz"))))
(ssg-start anzeige '(("OSMODE") ("ATTREQ") ("ATTDIA")))
(setvar "OSMODE" 0) (setvar "ATTREQ" 0) (setvar "ATTDIA" 0)
;; GUI aus (keine Wizard-Dialoge) UND headless: eine erschoepfte
;; Replay-Queue ist damit ein harter Abbruch MIT Fundstelle statt eines
;; stillen Rueckfalls auf eine Live-Abfrage, die im Batch haengen wuerde.
(setq old-gui *ssg-gui-aus* old-headless *vfl-headless*)
(if (car (atoms-family 1 '("SSG-GUI-AUS"))) (ssg-gui-aus))
(setq *vfl-headless* T *vfl-m2-abweichung* nil)
(vfl-journal-replay-start journal)
(setq err (vl-catch-all-apply 'vf-linienzug-modus2 '()))
(setq *vfl-headless* old-headless *ssg-gui-aus* old-gui)
(if (car (atoms-family 1 '("SSG-GUI-AN"))) (if (null old-gui) (ssg-gui-an)))
(setq offen (length *vfl-replay-queue*))
(setq abw (if (boundp '*vfl-m2-abweichung*) *vfl-m2-abweichung* nil))
(setq laengs (if abw (car abw) 0.0)
quer (if abw (cadr abw) 999.0)
dz (if abw (caddr abw) 999.0))
(cond
((vl-catch-all-error-p err)
(setq status "FEHLER")
(princ (strcat "\n FEHLER: " (vl-catch-all-error-message err))))
((null abw)
(setq status "FEHLER")
(princ "\n FEHLER: keine Abweichung gemessen (Bau nicht beendet)."))
(T
(princ (strcat "\n laengs=" (rtos laengs 2 1)
" quer=" (rtos quer 2 1)
" dZ=" (rtos dz 2 1) " mm"))
(if (> offen 0)
(princ (strcat "\n WARNUNG: " (itoa offen)
" Journal-Eintraege NICHT verbraucht (Ablauf weicht ab).")))
;; laengs gegen den erwarteten ES-Versatz pruefen, sofern die
;; Testdaten einen nennen (siehe *tlz-m2-laengs-toleranz*).
(setq soll-laengs (if meta (ssg-val meta "erwartung_laengs_mm") nil))
(setq laengs-ok
(if (numberp soll-laengs)
(<= (abs (- laengs (float soll-laengs))) *tlz-m2-laengs-toleranz*)
T))
(if (not laengs-ok)
(princ (strcat "\n WARNUNG: laengs " (rtos laengs 2 1)
" statt erwarteter " (rtos (float soll-laengs) 2 1)
" mm - der Hauptstrang zeigt nicht mehr in die"
" gezeichnete Richtung (ES-Versatz verschwunden?).")))
(setq status
(if (and (<= (abs quer) *tlz-m2-toleranz*)
(<= (abs dz) *tlz-m2-toleranz*)
laengs-ok
(= offen 0))
"OK" "ABWEICHUNG"))
(princ (strcat "\n -> " status
" (Soll: |quer| und |dZ| <= " (rtos *tlz-m2-toleranz* 2 1)
" mm, laengs = "
(if (numberp soll-laengs)
(strcat (rtos (float soll-laengs) 2 0) " +/- "
(rtos *tlz-m2-laengs-toleranz* 2 0) " mm")
"frei")
")"))))
(ssg-end)))
(list status laengs quer dz offen))
;; Testfall-Tabelle: (Dateiname test-id Anzeigename). Ein weiterer Fall ist
;; damit EINE Zeile - der Ablauf darunter ist generisch. Reihenfolge =
;; Ausgabereihenfolge; die beiden Paare stehen bewusst beieinander.
(setq *tlz-m2-faelle*
(list
(list "linienzug_modus2_variokurve.json" "LZ_M2_variokurve"
"Variante A: Ecke = Vario-Kurve")
(list "linienzug_modus2_gfbogen.json" "LZ_M2_gfbogen"
"Variante B: Ecke = GF-Bogen")
(list "linienzug_modus2_uform_gf1.json" "LZ_M2_uform_gf1"
"U-Form 1: GF alles am Einlauf")
(list "linienzug_modus2_uform_halb.json" "LZ_M2_uform_halb"
"U-Form 2: GF 1/2 + 1/2")
(list "linienzug_modus2_uform_halb_steil.json" "LZ_M2_uform_halb_steil"
"U-Form 3: GF 1/2 + 1/2, steilere Winkel-Option")))
(defun c:TEST_LINIENZUG_MODUS2 ( / basis fall r erg zeilen anzahl-ok)
;; Doppelte Backslashes: "\t" waere in AutoLISP ein TABULATOR, der Pfad
;; wuerde zu "...dxfmakros<TAB>ests<TAB>estdata" (real passiert 2026-10-27).
(setq basis (strcat (getenv "DXFMAKRO") "\\tests\\testdata\\"))
(princ "\n================================================================")
(princ (strcat "\n TEST_LINIENZUG_MODUS2 - " (itoa (length *tlz-m2-faelle*))
" Ketten aus echten BricsCAD-Laeufen"))
(princ "\n================================================================")
(setq erg '() zeilen '() anzahl-ok 0)
(foreach fall *tlz-m2-faelle*
(setq r (tlz-m2-run (strcat basis (car fall)) (cadr fall) (caddr fall)))
(if (= (car r) "OK") (setq anzahl-ok (1+ anzahl-ok)))
(setq erg (cons (list (caddr fall) (car r)) erg))
(setq zeilen
(cons (strcat " { \"test_id\": \"" (cadr fall)
"\", \"status\": \"" (car r)
"\", \"quer_mm\": " (rtos (caddr r) 2 2)
", \"dz_mm\": " (rtos (cadddr r) 2 2)
", \"laengs_mm\": " (rtos (cadr r) 2 2) " }")
zeilen)))
(setq erg (reverse erg) zeilen (reverse zeilen))
(princ "\n\n================================================================")
(princ "\n ERGEBNIS")
(princ "\n----------------------------------------------------------------")
(foreach e erg
(princ (strcat "\n " (car e) ": " (cadr e))))
(princ (strcat "\n----------------------------------------------------------------"
"\n " (itoa anzahl-ok) " von " (itoa (length *tlz-m2-faelle*))
" OK"))
(princ "\n================================================================")
;; Ergebnis fuer run-all (Konvention wie linienzug:export-results).
;; ssg-join aus ssg_core - die Zeilen sind bereits fertige JSON-Objekte.
(setq *linienzug-m2-test-results* (ssg-join zeilen ",\n"))
(princ)
)