diff --git a/Lisp/vf_linienzug.lsp b/Lisp/vf_linienzug.lsp index 1bad0ec..618d9a9 100644 --- a/Lisp/vf_linienzug.lsp +++ b/Lisp/vf_linienzug.lsp @@ -1772,16 +1772,48 @@ ;; Aus der Ergebnisliste von berechne-alle-winkel die GUELTIGEN Winkel filtern ;; und - bei mehreren - den Nutzer waehlen lassen. Rueckgabe: (winkel L_GF L_VF) ;; oder nil, wenn kein gueltiger Winkel existiert. -(defun vfl-waehle-winkel (ergebnis-liste / gueltige idx antwort e labels) +;; --- Winkelwahl: reine Teile + Frage-Schale --- +;; Die gueltigen Kandidaten aus einer berechne-alle-winkel-Ergebnisliste +;; filtern. Gueltig heisst: baubar (cadddr) UND beide Laengen positiv. +;; Reine Funktion, keine Frage, keine Globals. +(defun vfl-winkel-gueltige (ergebnis-liste / gueltige) (setq gueltige '()) (foreach e ergebnis-liste (if (and (cadddr e) (numberp (cadr e)) (numberp (caddr e)) (> (cadr e) 0) (> (caddr e) 0)) (setq gueltige (append gueltige (list e))))) + gueltige) + +;; Kandidat Nummer idx (1-basiert) als (winkel L_GF L_VF). Ein Index +;; ausserhalb der Liste faellt auf den ersten Kandidaten zurueck - so +;; verhielt sich die Frage-Schale schon immer bei ungueltiger Eingabe. +(defun vfl-winkel-nach-index (gueltige idx / e) + (if (or (null idx) (< idx 1) (> idx (length gueltige))) (setq idx 1)) + (setq e (nth (1- idx) gueltige)) + (if e (list (nth 0 e) (nth 1 e) (nth 2 e)) nil)) + +;; Vorgabe-Kanal: ist *vfl-winkel-idx-vorgabe* gesetzt, wird NICHT gefragt, +;; sondern dieser Kandidat genommen. Derselbe Mechanismus, den die Datei +;; schon fuer die Wizard-Menues nutzt (dynamisch gebundene *vflw-menu-*). +;; Warum ein Kanal und kein Parameter: vfl-waehle-winkel wird aus vier +;; Solvern gerufen (vfl-vf-winkel, vfl-vf-entscheidung, vfl-body-zerlegung, +;; vfl-segment-entscheidung) - ein zusaetzlicher Parameter wuerde vier +;; Signaturen und alle deren Aufrufer aendern. +;; ACHTUNG: die Vorgabe gilt fuer JEDE folgende Winkelfrage, bis sie +;; zurueckgesetzt wird. Setzer muessen sie darum wieder auf nil stellen. +(if (not (boundp '*vfl-winkel-idx-vorgabe*)) + (setq *vfl-winkel-idx-vorgabe* nil)) + +;; Frage-Schale (Name unveraendert, alle Aufrufstellen bleiben): gueltige +;; Kandidaten bestimmen, bei genau einem ohne Frage nehmen, sonst fragen - +;; bzw. die Vorgabe verwenden. +(defun vfl-waehle-winkel (ergebnis-liste / gueltige idx antwort e labels) + (setq gueltige (vfl-winkel-gueltige ergebnis-liste)) (cond ((null gueltige) nil) - ((= (length gueltige) 1) - (setq e (car gueltige)) (list (nth 0 e) (nth 1 e) (nth 2 e))) + ((= (length gueltige) 1) (vfl-winkel-nach-index gueltige 1)) + (*vfl-winkel-idx-vorgabe* + (vfl-winkel-nach-index gueltige *vfl-winkel-idx-vorgabe*)) (t (princ (ssg-text "vfl-mehrere-winkel-header")) (setq idx 1 labels '()) @@ -1793,9 +1825,7 @@ (setq idx (1+ idx))) (setq antwort (vfl-menu-int (ssg-textf "vfl-prompt-wahl-bis-n" (list (length gueltige))) labels 1 "vfl-mehrere-winkel-header")) - (if (or (null antwort) (< antwort 1) (> antwort (length gueltige))) (setq antwort 1)) - (setq e (nth (1- antwort) gueltige)) - (list (nth 0 e) (nth 1 e) (nth 2 e))))) + (vfl-winkel-nach-index gueltige antwort)))) ;; berechne-alle-winkel ausfuehren (mit Linienzug-FESTE_HORIZONTAL = 1300) und ;; den Winkel waehlen lassen. Rueckgabe: (winkel L_GF L_VF) oder nil. @@ -2397,16 +2427,24 @@ ;; (vfl-vf-einheit) muss die Frage dann NICHT erneut stellen. Bei ziel-modus= ;; nil (Default/mid-chain-Fortsetzung): unveraendertes Verhalten, Rueckgabe ;; nur frame. -(defun vfl-baue-horizontal-koerper (frame hz dL ziel-modus gf2-laenge / - pt m1 sep-vor pt-vor-bogen sep-nach ist-ende-antwort) +;; --- Horizontaler Koerper: reiner Bauteil --- +;; Baut das horizontale Stueck aus FERTIGEN Antworten - keine Frage, kein +;; Dialog. Rueckgabe: neuer Frame (das Stueck endet flach, 0 Grad). +;; +;; ziel-modus bleibt ein EIGENER Parameter neben ende-code (der Fahrplan +;; wollte ihn ersetzen): die Separator-Subtraktionen haengen allein am +;; Ziel-Modus, nicht an der Endpunkt-Antwort - und ende-code darf nil sein +;; (abgebrochene Menuefrage). Mit nur einem Parameter waere genau dieser Fall +;; eine stille Verhaltensaenderung. +;; +;; REIHENFOLGE IST GEOMETRIE: jede dL-Subtraktion ist mit +;; (max *vfk-restlaenge-min-clamp* ...) geklammert, also nicht kommutativ, +;; und der auf_3-Insert sitzt BEWUSST zwischen Subtraktion 1 und 2 - sein +;; Fussabdruck wird GEMESSEN (vfl-projiziere-distanz), nicht geschaetzt. +;; Hier darf nichts umsortiert werden. +(defun vfl-hor-koerper-bauen (frame hz dL ziel-modus ende-code sep-vor sep-nach + gf2-laenge / pt m1 pt-vor-bogen) (setq pt (car frame)) - (if (vfl-wizard-aktiv) - (vl-catch-all-apply 'vflw-gruppe-horizontal-impl (list ziel-modus))) - (princ (ssg-text "vfl-sep-vor-frage")) - (princ (ssg-text "vfl-ja")) - (princ (ssg-text "vfl-nein")) - (setq sep-vor (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2") - (list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2 "vfl-sep-vor-frage") "1")) (if (and ziel-modus sep-vor) (setq dL (max *vfk-restlaenge-min-clamp* (- dL *vfk-separator-laenge*)))) ;; auf_3-Eintritt nur, wenn noch NICHT flach (sonst flache Zone fortsetzen). ;; dL ist die gewuenschte Reststrecke AB HIER (pt) - der reale Bogen- @@ -2421,18 +2459,53 @@ (setq pt (insert-rotated-block-with-ks "Vario_Bogen_auf_3_TEF_rechts" pt (ssg-cfg-or "vario" "gefaelle_winkel" 3) (car m1) (caddr m1) hz)) (setq dL (max *vfk-restlaenge-min-clamp* (- dL (vfl-projiziere-distanz pt-vor-bogen pt hz)))))) - ;; Separator NACH abfragen (noch nicht bauen) - im Ziel-Modus VOR der - ;; Laengenberechnung, damit der Fussabdruck feststeht. + (if (and ziel-modus sep-nach) (setq dL (max *vfk-restlaenge-min-clamp* (- dL *vfk-separator-laenge*)))) + ;; Ausgangs-Fussabdruck (Vario_Bogen_ab_3 + Motorstation) nur reservieren, + ;; wenn die Einheit tatsaechlich sofort schliesst (Antwort 1 oder 3); bei + ;; Antwort 1 zusaetzlich die GF2 hinter dem Motor. + (if ziel-modus + (cond + ((= ende-code "1") + (setq dL (max *vfk-restlaenge-min-clamp* (- dL (car (get-bogen-mass bogen-ab (fix *vfk-gefaelle-winkel*))) *vfk-stations-laenge* + (* (if gf2-laenge gf2-laenge 0.0) + (cos (* *vfk-gefaelle-winkel* (/ pi 180.0)))))))) + ((= ende-code "3") + (setq dL (max *vfk-restlaenge-min-clamp* (- dL (car (get-bogen-mass bogen-ab (fix *vfk-gefaelle-winkel*))) *vfk-stations-laenge*)))))) + ;; optionaler Separator VOR - in der horizontalen Ebene (0 Grad) + (if sep-vor (setq pt (vfl-sep-hz pt hz))) + ;; horizontale Zwischenstrecke (0 Grad) + (princ (ssg-textf "vfl-horizontale-zwischenstrecke" (list (rtos dL 2 2)))) + (setq pt (insert-inclined-scaled-block "Staustrecke_SP_1000_mm" pt dL 0 hz)) + (vfl-acc-vf-seg "horizontal" 0 dL) + ;; optionaler Separator NACH (jetzt tatsaechlich bauen) + (if sep-nach (setq pt (vfl-sep-hz pt hz))) + ;; KEIN ab_3 mehr -> das Stueck ENDET FLACH (0 Grad) + (make-frame-from-dir pt (hz-winkel->xu hz 0.0)) +) + +;; --- Horizontaler Koerper: Frage-Schale (Name unveraendert) --- +;; Fragt Separator vor/nach und - im Ziel-Modus - "Ist der Endpunkt der +;; Foerderer?", danach baut vfl-hor-koerper-bauen. Keine der drei Fragen +;; haengt an einem berechneten Wert, sie stehen darum alle vor dem Bau; die +;; Journal-Reihenfolge ist dieselbe wie zuvor. +;; Die Endpunkt-Antwort wird im Ziel-Modus mit zurueckgegeben (vor-antwort), +;; damit die Fortsetzungsschleife des Aufrufers sie nicht erneut erfragt. +(defun vfl-baue-horizontal-koerper (frame hz dL ziel-modus gf2-laenge / + sep-vor sep-nach ist-ende-antwort neu) + (if (vfl-wizard-aktiv) + (vl-catch-all-apply 'vflw-gruppe-horizontal-impl (list ziel-modus))) + (princ (ssg-text "vfl-sep-vor-frage")) + (princ (ssg-text "vfl-ja")) + (princ (ssg-text "vfl-nein")) + (setq sep-vor (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2") + (list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2 "vfl-sep-vor-frage") "1")) + ;; Separator NACH abfragen (noch nicht bauen) - im Ziel-Modus muss der + ;; Fussabdruck vor der Laengenberechnung feststehen. (princ (ssg-text "vfl-sep-nach-frage")) (princ (ssg-text "vfl-ja")) (princ (ssg-text "vfl-nein")) (setq sep-nach (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2") (list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2 "vfl-sep-nach-frage") "1")) - (if (and ziel-modus sep-nach) (setq dL (max *vfk-restlaenge-min-clamp* (- dL *vfk-separator-laenge*)))) - ;; Im Ziel-Modus: "Ist der Endpunkt der Foerderer?" JETZT abfragen (statt - ;; erst danach in der Aufruferschleife) - der Ausgangs-Fussabdruck - ;; (Vario_Bogen_ab_3 + Motorstation) wird nur reserviert, wenn tatsaechlich - ;; sofort geschlossen wird (Antwort 1 oder 3). (if ziel-modus (progn (princ (ssg-text "vfl-ist-endpunkt-frage")) @@ -2443,28 +2516,10 @@ (list (ssg-text "vfl-ja-nur-motorstation") (ssg-text "vfl-nein-weiterbauen") (ssg-text "vfl-ja-motorstation-kettenende")) 2 - "vfl-ist-endpunkt-frage")) - (cond - ((= ist-ende-antwort "1") - (setq dL (max *vfk-restlaenge-min-clamp* (- dL (car (get-bogen-mass bogen-ab (fix *vfk-gefaelle-winkel*))) *vfk-stations-laenge* - (* (if gf2-laenge gf2-laenge 0.0) - (cos (* *vfk-gefaelle-winkel* (/ pi 180.0)))))))) - ((= ist-ende-antwort "3") - (setq dL (max *vfk-restlaenge-min-clamp* (- dL (car (get-bogen-mass bogen-ab (fix *vfk-gefaelle-winkel*))) *vfk-stations-laenge*)))) - ) - ) - ) - ;; optionaler Separator VOR - in der horizontalen Ebene (0 Grad) - (if sep-vor (setq pt (vfl-sep-hz pt hz))) - ;; horizontale Zwischenstrecke (0 Grad) - (princ (ssg-textf "vfl-horizontale-zwischenstrecke" (list (rtos dL 2 2)))) - (setq pt (insert-inclined-scaled-block "Staustrecke_SP_1000_mm" pt dL 0 hz)) - (vfl-acc-vf-seg "horizontal" 0 dL) - ;; optionaler Separator NACH (jetzt tatsaechlich bauen) - (if sep-nach (setq pt (vfl-sep-hz pt hz))) - ;; KEIN ab_3 mehr -> das Stueck ENDET FLACH (0 Grad) - (setq frame (make-frame-from-dir pt (hz-winkel->xu hz 0.0))) - (if ziel-modus (list frame ist-ende-antwort) frame) + "vfl-ist-endpunkt-frage")))) + (setq neu (vfl-hor-koerper-bauen frame hz dL ziel-modus ist-ende-antwort + sep-vor sep-nach gf2-laenge)) + (if ziel-modus (list neu ist-ende-antwort) neu) ) ;; ============================================================ @@ -2536,36 +2591,21 @@ ;; mit Motor -> GF2 -> Separator -> ES ab). Speichert den Soll-Zielpunkt in ;; *vfl-ziel-punkt* fuer den Ist-Ziel-Report. ;; Rueckgabe: (frame anzahl-koerper letzt-hz gf2-laenge) oder nil. -(defun vfl-body-abschluss (frame letzt-hz p-umlenk / - linie-mess dL hzn hn dH richtn rad3 dec typ w lgf lvf - gf2 cnt p0 tx ty - hoehe-vorschlaege hoehe-ab hoehe-auf hoehe-fallback) - ;; Der Abschluss-Koerper (vfs-vf-koerper) und der Motor gehen von 3-Grad-Basis - ;; aus - eine gerade laufende flache Zone hier mit ab_3 abschliessen. - (setq frame (vfl-nach-3grad frame)) - (setq linie-mess (vfl-neue-linie-messen (car frame) letzt-hz)) - (if (null linie-mess) - (progn (princ (ssg-text "vfl-abgebrochen-kein-ziel")) nil) - (progn - (setq dL (car linie-mess) hzn (cadr linie-mess)) - ;; Baubare Vorschlaege (Ab/Auf) statt der unveraenderten Kettenhoehe als - ;; Default - sonst waere dH=0 ("Auf", da (>= 0.0 0.0)) und der Rest wird - ;; von vfl-body-zerlegung sofort als "nicht baubar" abgelehnt (kleineres - ;; Budget als vfl-vf-entscheidung: nur Motor+Separator, 800mm statt - ;; 1300mm) - siehe vfl-body-hoehe-vorschlaege/vfl-body-machbar-p. - (setq hoehe-vorschlaege (vfl-body-hoehe-vorschlaege (caddr (car frame)) dL)) - (setq hoehe-ab (nth 0 hoehe-vorschlaege) hoehe-auf (nth 1 hoehe-vorschlaege)) - (setq hoehe-fallback (if hoehe-ab hoehe-ab (caddr (car frame)))) - (vfl-hoehe-hinweis-falls-noetig hoehe-ab hoehe-auf) - (if (vfl-wizard-aktiv) - (vl-catch-all-apply 'vflw-gruppe-ziel-hoehe-impl - (list (list hoehe-ab hoehe-auf (caddr (car frame)))))) - (setq hn (vfl-in-real - (if (and hoehe-ab hoehe-auf) - (ssg-textf "vfl-prompt-hoehe-kettenende-2" - (list (rtos hoehe-ab 2 1) (rtos hoehe-auf 2 1))) - (ssg-textf "vfl-prompt-hoehe-kettenende" (list (rtos hoehe-fallback 2 1)))))) - (if (null hn) (setq hn hoehe-fallback)) +;; --- Kettenende-Abschluss: reiner Bauteil --- +;; Zerlegt die Reststrecke und baut Koerper + Motor-Vorbereitung. Fragt +;; nichts mehr - die einzige Rueckfrage steckt in vfl-body-zerlegung +;; (vfl-waehle-winkel, nur wenn mehrere Winkel gueltig sind) und ist ueber +;; *vfl-winkel-idx-vorgabe* vorab beantwortbar. +;; Rueckgabe: (frame anzahl-koerper hz gf2-laenge) oder nil, wenn der Rest +;; nicht baubar ist. +;; +;; Signatur schlanker als im Fahrplan skizziert: letzt-hz und es-gewuenscht +;; werden hier nicht gebraucht. es-gewuenscht wirkt beim Aufrufer, der +;; ein-dx/ein-dz um den Aufruf herum nullt (kein ES-Fussabdruck reservieren); +;; letzt-hz wurde nur einer wirkungslosen Zuweisung zugefuehrt (siehe unten). +(defun vfl-body-abschluss-bauen (frame p-umlenk dL hzn hn / + dH richtn rad3 dec typ w lgf lvf gf2 cnt + p0 tx ty letzt-hz) (setq dH (- hn (caddr (car frame)))) (setq richtn (if (>= dH 0.0) "Auf" "Ab")) (setq dH (abs dH)) @@ -2600,6 +2640,9 @@ (setq frame (vfl-frame-3grad (vfs-vf-koerper (car frame) richtn w lvf hzn) hzn)) (vfl-acc-vf-seg richtn w lvf) (setq cnt (1+ cnt)))) + ;; ohne Wirkung: letzt-hz ist lokal und wird nicht mehr gelesen - + ;; die Fahrtrichtung verlaesst die Funktion ueber die Rueckgabe. + ;; Beim Umzug bewusst wortwoertlich uebernommen. (setq letzt-hz hzn) (vfl-20m-check p-umlenk (car frame)) ;; GF2-Laenge (hinter dem Motor): aus der Zerlegung (variiert mit Winkel), @@ -2619,6 +2662,44 @@ (list frame cnt hzn gf2) ) ) +) + +;; --- Kettenende-Abschluss: Frage-Schale (Name unveraendert) --- +;; "Alles fragen, dann alles bauen" geht hier NICHT: vfl-neue-linie-messen +;; braucht den Frame NACH vfl-nach-3grad, und die Hoehenvorschlaege brauchen +;; die gemessene Laenge und die aktuelle Kettenhoehe. Darum in dieser +;; Reihenfolge: flache Zone abschliessen -> Laenge messen -> Vorschlaege +;; berechnen -> Zielhoehe fragen -> bauen. +(defun vfl-body-abschluss (frame letzt-hz p-umlenk / + linie-mess dL hzn hn + hoehe-vorschlaege hoehe-ab hoehe-auf hoehe-fallback) + ;; Der Abschluss-Koerper (vfs-vf-koerper) und der Motor gehen von 3-Grad-Basis + ;; aus - eine gerade laufende flache Zone hier mit ab_3 abschliessen. + (setq frame (vfl-nach-3grad frame)) + (setq linie-mess (vfl-neue-linie-messen (car frame) letzt-hz)) + (if (null linie-mess) + (progn (princ (ssg-text "vfl-abgebrochen-kein-ziel")) nil) + (progn + (setq dL (car linie-mess) hzn (cadr linie-mess)) + ;; Baubare Vorschlaege (Ab/Auf) statt der unveraenderten Kettenhoehe als + ;; Default - sonst waere dH=0 ("Auf", da (>= 0.0 0.0)) und der Rest wird + ;; von vfl-body-zerlegung sofort als "nicht baubar" abgelehnt (kleineres + ;; Budget als vfl-vf-entscheidung: nur Motor+Separator, 800mm statt + ;; 1300mm) - siehe vfl-body-hoehe-vorschlaege/vfl-body-machbar-p. + (setq hoehe-vorschlaege (vfl-body-hoehe-vorschlaege (caddr (car frame)) dL)) + (setq hoehe-ab (nth 0 hoehe-vorschlaege) hoehe-auf (nth 1 hoehe-vorschlaege)) + (setq hoehe-fallback (if hoehe-ab hoehe-ab (caddr (car frame)))) + (vfl-hoehe-hinweis-falls-noetig hoehe-ab hoehe-auf) + (if (vfl-wizard-aktiv) + (vl-catch-all-apply 'vflw-gruppe-ziel-hoehe-impl + (list (list hoehe-ab hoehe-auf (caddr (car frame)))))) + (setq hn (vfl-in-real + (if (and hoehe-ab hoehe-auf) + (ssg-textf "vfl-prompt-hoehe-kettenende-2" + (list (rtos hoehe-ab 2 1) (rtos hoehe-auf 2 1))) + (ssg-textf "vfl-prompt-hoehe-kettenende" (list (rtos hoehe-fallback 2 1)))))) + (if (null hn) (setq hn hoehe-fallback)) + (vfl-body-abschluss-bauen frame p-umlenk dL hzn hn) ) ) ) @@ -3083,6 +3164,17 @@ ;; sodass diese als Glied-Inhalt darauf folgen. Beim Replay wird der Marker in ;; vfl-replay-pop automatisch uebersprungen; die beiden STR-Werte poppen danach ;; regulaer. +;; ES-Masse setzen (kein Bau, nur Globals): Winkel merken und die +;; ein-dx/ein-dz-Masse des ES-Elements laden. Aus der Frage-Schale gezogen, +;; damit der Daten-Pfad sie ohne Frage setzen kann. +;; *vfl-es-winkel* wird LAZY von den Blocknamen-Bauern gelesen (vfl-es-winkel +;; weiter oben) - es MUSS also vor dem ersten ES-Insert stehen, sonst waehlt +;; der Bau den falschen Block. +(defun vfl-es-masse-setzen (winkel seite) + (setq *vfl-es-winkel* winkel) + (vf-set-es-masse winkel seite) + seite) + (defun vfl-frage-es-seite ( / antwort es-seite) (vfl-journal-mark "ES") (if (vfl-wizard-aktiv) @@ -3100,7 +3192,7 @@ (list (ssg-text "gf-seite-links") (ssg-text "gf-seite-rechts")) 1 "gf-seite-ein-header")) (setq es-seite (if (= antwort "2") "rechts" "links")) - (vf-set-es-masse *vfl-es-winkel* es-seite) + (vfl-es-masse-setzen *vfl-es-winkel* es-seite) es-seite ) @@ -3239,6 +3331,32 @@ ;; auto-ende: T => keine Kettenende-Frage, es wird direkt Separator + ES ;; gesetzt (Option "Neue Linie BIS Kettenende"). ;; Rueckgabe: (frame anzahl-koerper ende-flag es-seite-oder-nil). +;; Soll-Ist-Vergleich am Kettenende ausgeben (Zielpunkt aus dem +;; Modus-1-Zielmodus, gesetzt in vfl-body-abschluss). Stand vorher zweimal +;; wortgleich im Abschluss - einmal fuer "Kettenende ohne ES", einmal fuer +;; "Kettenende mit ES"; die Faelle unterscheiden sich nur darin, WANN sie +;; gemeldet werden, nicht WAS. +;; Loescht *vfl-ziel-punkt* danach: der Report gehoert zu genau einem +;; Kettenende, ein zweiter Aufruf soll nichts mehr melden. +(defun vfl-ziel-report (frame) + (if *vfl-ziel-punkt* + (progn + (princ (ssg-text "vfl-ist-ziel-vergleich-header")) + (princ (ssg-textf "vfl-soll-xyz" + (list (rtos (car *vfl-ziel-punkt*) 2 1) + (rtos (cadr *vfl-ziel-punkt*) 2 1) + (rtos (caddr *vfl-ziel-punkt*) 2 1)))) + (princ (ssg-textf "vfl-ist-xyz" + (list (rtos (car (car frame)) 2 1) + (rtos (cadr (car frame)) 2 1) + (rtos (caddr (car frame)) 2 1)))) + (princ (ssg-textf "vfl-abweichung-xyz" + (list (rtos (- (car (car frame)) (car *vfl-ziel-punkt*)) 2 1) + (rtos (- (cadr (car frame)) (cadr *vfl-ziel-punkt*)) 2 1) + (rtos (- (caddr (car frame)) (caddr *vfl-ziel-punkt*)) 2 1)))) + (setq *vfl-ziel-punkt* nil))) + (princ)) + (defun vfl-vf-einheit-abschluss (frame hz richtung winkel L_GF L_VF auto-ende / antwort res es-s ende es-gewuenscht) ;; GF-Verteilung: halbe Staustrecke am Ausgang (GF2) oder alles am Einlauf. @@ -3277,24 +3395,7 @@ (setq ende t) ;; Ist-Ziel-Report auch ohne ES (Vergleich Soll-Zielpunkt vs. tatsaechliches ;; Kettenende nach GF2/Motor). - (if *vfl-ziel-punkt* - (progn - (princ (ssg-text "vfl-ist-ziel-vergleich-header")) - (princ (ssg-textf "vfl-soll-xyz" - (list (rtos (car *vfl-ziel-punkt*) 2 1) - (rtos (cadr *vfl-ziel-punkt*) 2 1) - (rtos (caddr *vfl-ziel-punkt*) 2 1)))) - (princ (ssg-textf "vfl-ist-xyz" - (list (rtos (car (car frame)) 2 1) - (rtos (cadr (car frame)) 2 1) - (rtos (caddr (car frame)) 2 1)))) - (princ (ssg-textf "vfl-abweichung-xyz" - (list (rtos (- (car (car frame)) (car *vfl-ziel-punkt*)) 2 1) - (rtos (- (cadr (car frame)) (cadr *vfl-ziel-punkt*)) 2 1) - (rtos (- (caddr (car frame)) (caddr *vfl-ziel-punkt*)) 2 1)))) - (setq *vfl-ziel-punkt* nil) - ) - ) + (vfl-ziel-report frame) ) ((= antwort "1") (setq es-s (vfl-frage-es-seite)) @@ -3303,24 +3404,7 @@ (caddr (car frame)) es-s)) (setq ende t) ;; Ist-Ziel-Report (Option 3): Soll-ES (aus vfl-body-abschluss) vs. Ist-ES. - (if *vfl-ziel-punkt* - (progn - (princ (ssg-text "vfl-ist-ziel-vergleich-header")) - (princ (ssg-textf "vfl-soll-xyz" - (list (rtos (car *vfl-ziel-punkt*) 2 1) - (rtos (cadr *vfl-ziel-punkt*) 2 1) - (rtos (caddr *vfl-ziel-punkt*) 2 1)))) - (princ (ssg-textf "vfl-ist-xyz" - (list (rtos (car (car frame)) 2 1) - (rtos (cadr (car frame)) 2 1) - (rtos (caddr (car frame)) 2 1)))) - (princ (ssg-textf "vfl-abweichung-xyz" - (list (rtos (- (car (car frame)) (car *vfl-ziel-punkt*)) 2 1) - (rtos (- (cadr (car frame)) (cadr *vfl-ziel-punkt*)) 2 1) - (rtos (- (caddr (car frame)) (caddr *vfl-ziel-punkt*)) 2 1)))) - (setq *vfl-ziel-punkt* nil) - ) - ) + (vfl-ziel-report frame) ) (t (princ (ssg-text "vfl-sep-an-stelle-frage")) diff --git a/Lisp/vf_spec.lsp b/Lisp/vf_spec.lsp index a302c16..d56c968 100644 --- a/Lisp/vf_spec.lsp +++ b/Lisp/vf_spec.lsp @@ -529,6 +529,7 @@ (cons "eingaben_offen" 0) (cons "glieder_soll" nil) (cons "glieder_ist" nil) + (cons "journal_diff" nil) (cons "prompts" 0) (cons "meldungen" nil) (cons "spec_fehler" nil) @@ -600,6 +601,8 @@ ",\n" " \"glieder_ist\": " (vsp-json-liste (ssg-val rec "glieder_ist")) ",\n" + " \"journal_diff\": " (vsp-json-str (ssg-val rec "journal_diff")) + ",\n" " \"prompts\": " (vsp-json-int (ssg-val rec "prompts")) ",\n" " \"meldungen\": " (vsp-json-liste (ssg-val rec "meldungen")) ",\n" @@ -687,6 +690,32 @@ (setq soll (cdr soll) ist (cdr ist)))) diff) +;; Das aus der Spec erzeugte Journal gegen das beim Bau NEU aufgezeichnete +;; stellen. Beide muessen Token fuer Token gleich sein: der Replay gibt jeden +;; Wert an denselben Wrapper, der ihn wieder journalisiert - passt der Ablauf +;; zur Spec, entsteht dasselbe Journal. +;; +;; Das ist der SCHAERFSTE Desync-Detektor. Offene Queue-Werte und die +;; Glied-Folge zeigen nur grobe Abweichungen; ein einzelner Wert, der an der +;; falschen Stelle verbraucht wird, kann beide passieren lassen und trotzdem +;; andere Geometrie erzeugen. Rueckgabe: nil bei Gleichheit, sonst ein Text +;; mit der ersten Abweichung. +(defun vsp-journal-diff (soll ist / i a b diff) + (setq i 0 diff nil) + (while (and (null diff) (or soll ist)) + (setq i (1+ i) a (car soll) b (car ist)) + (cond + ((null a) (setq diff (strcat "Token " (itoa i) ": Spec zu Ende, gebaut " + (vl-princ-to-string b)))) + ((null b) (setq diff (strcat "Token " (itoa i) ": erwartet " + (vl-princ-to-string a) ", gebaut nichts"))) + ((not (equal a b)) + (setq diff (strcat "Token " (itoa i) ": erwartet " + (vl-princ-to-string a) ", gebaut " + (vl-princ-to-string b)))) + (T (setq soll (cdr soll) ist (cdr ist))))) + diff) + ;; Eine Kette aus einer Spec bauen. Rueckgabe: Ergebnis-Record (Alist). ;; Die Klammer um vf-linienzug-modus ist dieselbe wie in ;; tests/test_hm_recformat.lsp: *error* sichern, fangen, *error* zurueck. Weil @@ -696,7 +725,7 @@ ;; schon SSG_VF_EDIT_SEG/_PRE geschrieben). (defun vsp-bau-aus-spec (spec / kopf sid journal rec vor-ent alt-error err offen hl meld ist-glieder soll-glieder diff - ent ed dim) + ist-journal jdiff ent ed dim) (setq kopf (vsp-kopf spec)) (setq sid (vsp-spec-id kopf)) (setq journal (vsp-journal spec)) @@ -739,7 +768,8 @@ (setq err (vl-catch-all-apply 'vf-linienzug-modus '())) (setq offen (vsp-queue-rest)) ;; Diagnose VOR dem Reset sichern (vfl-journal-reset loescht sie). - (setq ist-glieder (vfl-journal-glieder (reverse *vfl-journal*))) + (setq ist-journal (reverse *vfl-journal*)) + (setq ist-glieder (vfl-journal-glieder ist-journal)) (setq hl (vsp-headless-fehler-text)) (setq meld (if (boundp '*vfl-meldungen*) *vfl-meldungen* nil)) (setq *error* alt-error) @@ -760,6 +790,8 @@ (setq rec (vsp-set rec "attribs" (ssg-attrib-read ent))))) (setq diff (vsp-glieder-diff soll-glieder ist-glieder)) + (setq jdiff (vsp-journal-diff journal ist-journal)) + (setq rec (vsp-set rec "journal_diff" jdiff)) (cond (hl (princ (strcat "\n DESYNC: " hl)) @@ -782,6 +814,12 @@ (princ (strcat "\n DESYNC: " diff)) (setq rec (vsp-set rec "status" "desync")) (setq rec (vsp-set rec "fehler_text" diff))) + ;; Feinster Detektor zuletzt: Queue geht auf, Glied-Folge stimmt - + ;; aber ein Wert wurde an anderer Stelle verbraucht als geplant. + (jdiff + (princ (strcat "\n DESYNC (Journal): " jdiff)) + (setq rec (vsp-set rec "status" "desync")) + (setq rec (vsp-set rec "fehler_text" jdiff))) ((null ent) (princ "\n FEHLER: kein VF_-Block entstanden") (setq rec (vsp-set rec "status" "abbruch")) diff --git a/doc/TODO-plan-vf-interactive.md b/doc/TODO-plan-vf-interactive.md index 5ab3451..05473bc 100644 --- a/doc/TODO-plan-vf-interactive.md +++ b/doc/TODO-plan-vf-interactive.md @@ -13,7 +13,7 @@ | P2 | `*vfl-headless*`: leere Replay-Queue wird zum Fehler | **erledigt** | | P3 | Kleinkram (`*vfl-meldungen*`-Reset, `vfl-view-refresh`) | **erledigt** | | S1 | Stufe 1: `Lisp/vf_spec.lsp` (Spec -> Journal -> Replay) | **Modus 1 fertig**, im CAD ungetestet | -| S2 | Stufe 2: echte Funktionstrennung (Schritte 0-11) | offen | +| S2 | Stufe 2: echte Funktionstrennung | **Schritte 0-4 + 7-Assertion fertig**, 5/6/8/9/10 offen, 11 zurueckgestellt | | PY | `lib/vf_journal_export.py`: Grammatik vervollstaendigen | **erledigt** | ### Was Stufe 0 konkret gebracht hat @@ -562,6 +562,64 @@ bei jedem Bau. vorliegen (`tests/testdata/hundm05.json` enthaelt ausschliesslich Modus-1- Ketten); sonst zurueckstellen +### Umsetzungsstand Stufe 2 (2026-09-03) + +**Fertig: Schritte 0-4 und der Assertion-Teil von Schritt 7.** Alle fuenf +sind verhaltensgleich - die Frage-Schalen behalten ihre Namen, keine +Aufrufstelle in Modus 1/2/3 wurde angefasst. + +| Schritt | Neu | Nachweis | +|---|---|---| +| 0 | `vfl-ziel-report` | zwei byte-identische Bloecke ersetzt, Diff zeigt nur die Entfernung | +| 1 | `vfl-winkel-gueltige`, `vfl-winkel-nach-index`, Kanal `*vfl-winkel-idx-vorgabe*` | Verzweigungen 1:1; ungueltiger Index faellt wie zuvor auf den ersten Kandidaten | +| 2 | `vfl-es-masse-setzen` | reines Globals-Setzen aus `vfl-frage-es-seite` gezogen | +| 3 | `vfl-hor-koerper-bauen` | Zeilenvergleich alt/neu: dieselben 15 geometrie-relevanten Zeilen in derselben Reihenfolge | +| 4 | `vfl-body-abschluss-bauen` | Multiset-Vergleich: ausser Kopf, Klammer und Aufruf keine Zeile veraendert | +| 7 (Teil) | `vsp-journal-diff` + Record-Feld `journal_diff` | neuer Test `test_journal_token_gleich` | + +Zu Schritt 3 im Detail: die drei Fragen (Separator vor/nach, Endpunkt) +haengen an keinem berechneten Wert und stehen jetzt alle vor dem Bau. Die +**Journal-Reihenfolge und die Entity-Reihenfolge bleiben identisch** - nur +interaktiv erscheint der auf_3-Uebergangsblock erst nach den drei Fragen +statt zwischen Frage 1 und 2. Die dL-Subtraktionskette wurde Zeile fuer +Zeile uebernommen, mit dem auf_3-Insert an derselben Stelle dazwischen. + +Zwei bewusste Abweichungen von der Planung: +- `vfl-hor-koerper-bauen` behaelt `ziel-modus` als EIGENEN Parameter neben + `ende-code` (der Fahrplan wollte ihn ersetzen). Die Separator-Subtraktionen + haengen allein am Ziel-Modus, und `ende-code` darf nil sein (abgebrochene + Menuefrage) - mit nur einem Parameter waere genau dieser Fall eine stille + Verhaltensaenderung. +- `vfl-body-abschluss-bauen` hat eine schlankere Signatur als skizziert: + `letzt-hz` und `es-gewuenscht` braucht es nicht. `es-gewuenscht` wirkt beim + Aufrufer (der `ein-dx`/`ein-dz` um den Aufruf herum nullt), `letzt-hz` ging + nur in eine wirkungslose Zuweisung - beim Umzug wortwoertlich uebernommen + und als solche markiert. + +Schritt 7 ist nur zur Haelfte erledigt: die Spec und der Serializer stehen +schon aus Stufe 1, neu ist die **Assertion** - `vsp-journal-diff` stellt das +aus der Spec erzeugte Journal Token fuer Token gegen das beim Bau neu +aufgezeichnete. Das ist der schaerfste Desync-Detektor: offene Queue-Werte +und die Glied-Folge zeigen nur grobe Abweichungen, ein einzelner Wert, der an +der falschen Stelle verbraucht wird, kann beide passieren lassen und trotzdem +andere Geometrie erzeugen. Sitzt im Daten-Pfad (`vf_spec.lsp`), nicht als +`dbgmsg` im Bau-Pfad - dort waere er nur bei offener `.dbg`-Datei sichtbar. + +**Offen: 5, 6, 8, 9, 10** (VF-Einheit aufteilen, Daten-Executor, +Einheit-Abschluss, Kettenebene je Glied-Zweig, `vfl-spec-ausfuehren`). +Bewusst NICHT vorab erledigt: das sind die Schritte, bei denen laut Abschnitt +"Wo Geometrie still kippen kann" die Reihenfolge selbst Geometrie ist. Der +Fahrplan verlangt nach jedem Schritt einen CAD-Lauf (`TEST_HUNDM05` + +`TEST_VF_SPEC` + `TEST_LINIENZUG`), nach den Geometrie-Schritten zusaetzlich +einen DXF-Diff und einen Sivas-Export-Vergleich. Ohne diese Laeufe zwischen +den Schritten waere ein Fehler nicht mehr einem Schritt zuzuordnen. + +Zur Erwartungshaltung: die Schritte 5-10 bringen **keine neue Faehigkeit**. +Der Bau aus Daten funktioniert seit Stufe 1 (ueber den Replay) und ist mit +`VF_SPEC_BAU` in der Praxis belegt. Was 5-10 bringen, ist der direkte +Daten-Pfad ohne den Replay-Umweg und einzeln aufrufbare Knoten-Bauteile - +Architektur, nicht Funktion. + ### Wo Geometrie still kippen kann - **Die dL-Subtraktionskette** in `vfl-baue-horizontal-koerper` (:2280, :2293, diff --git a/tests/test_hundm05.py b/tests/test_hundm05.py index 9da7305..4c02881 100644 --- a/tests/test_hundm05.py +++ b/tests/test_hundm05.py @@ -86,6 +86,18 @@ class TestSpecBau: f'{r["spec_id"]}: gebaut {r["glieder_ist"]}, ' \ f'geplant {r["glieder_soll"]}' + def test_journal_token_gleich(self, spec_ergebnisse): + """Der schaerfste Desync-Detektor: das aus der Spec erzeugte Journal + und das beim Bau neu aufgezeichnete muessen Token fuer Token gleich + sein. Queue-Rest und Glied-Folge zeigen nur grobe Abweichungen - ein + einzelner Wert, der an der falschen Stelle verbraucht wird, kann + beide passieren lassen und trotzdem andere Geometrie erzeugen.""" + for r in spec_ergebnisse: + if "journal_diff" not in r: + pytest.skip("Ergebnis aus einem Lauf vor Stufe 2") + assert not r["journal_diff"], \ + f'{r["spec_id"]}: {r["journal_diff"]}' + def test_keine_meldungen(self, spec_ergebnisse): for r in spec_ergebnisse: assert not r["meldungen"], \