[REFACTOR] Stufe 2, Schritte 0-4: Frage-Schale und reinen Bauteil trennen

Alle Aenderungen sind verhaltensgleich: die Frage-Schalen behalten ihre
Namen, keine Aufrufstelle in Modus 1/2/3 wurde angefasst.

Schritt 0 - vfl-ziel-report: der Soll-Ist-Vergleich am Kettenende stand
zweimal WORTGLEICH in vfl-vf-einheit-abschluss (einmal fuer "Kettenende ohne
ES", einmal "mit ES"); die Faelle unterscheiden sich nur darin, wann sie
melden, nicht was.

Schritt 1 - Winkelwahl aufgeteilt: vfl-winkel-gueltige (Kandidaten filtern)
und vfl-winkel-nach-index (Kandidat waehlen) sind rein; die Frage-Schale
vfl-waehle-winkel setzt sie zusammen. Neuer Vorgabe-Kanal
*vfl-winkel-idx-vorgabe*: ist er gesetzt, wird nicht gefragt. Kanal statt
Parameter, weil vfl-waehle-winkel aus vier Solvern gerufen wird
(vfl-vf-winkel, vfl-vf-entscheidung, vfl-body-zerlegung,
vfl-segment-entscheidung) - ein Parameter wuerde vier Signaturen und alle
deren Aufrufer aendern. Default nil, also heute ohne Wirkung.

Schritt 2 - vfl-es-masse-setzen: reines Globals-Setzen (ES-Winkel merken +
vf-set-es-masse) aus vfl-frage-es-seite gezogen. *vfl-es-winkel* wird LAZY
von den Blocknamen-Bauern gelesen und muss vor dem ersten ES-Insert stehen.

Schritt 3 - vfl-hor-koerper-bauen: die drei Fragen (Separator vor/nach,
Endpunkt) haengen an keinem berechneten Wert und stehen jetzt alle vor dem
Bau. Journal-Reihenfolge UND 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 - jeder Schritt 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). Nachweis:
Zeilenvergleich alt/neu, dieselben 15 geometrie-relevanten Zeilen in
derselben Reihenfolge.

Schritt 4 - vfl-body-abschluss-bauen: hier laesst sich NICHT alles vorab
fragen (vfl-neue-linie-messen braucht den Frame nach vfl-nach-3grad, die
Hoehenvorschlaege brauchen dL und die Kettenhoehe). Die Schale macht darum
3-Grad-Abschluss -> messen -> Vorschlaege -> Zielhoehe fragen, der reine Teil
beginnt bei der Zerlegung. Nachweis: Multiset-Vergleich der Zeilen, ausser
Kopf, Klammer und Aufruf ist keine Zeile veraendert.

Schritt 7 (Assertion-Teil) - vsp-journal-diff in Lisp/vf_spec.lsp: stellt das
aus der Spec erzeugte Journal Token fuer Token gegen das beim Bau neu
aufgezeichnete. Schaerfster 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. Neues Record-Feld journal_diff, neuer Test
test_journal_token_gleich.

Zwei bewusste Abweichungen von der Planung (begruendet in
doc/TODO-plan-vf-interactive.md, Abschnitt "Umsetzungsstand Stufe 2"):
vfl-hor-koerper-bauen behaelt ziel-modus als eigenen Parameter neben
ende-code (ende-code darf nil sein, die Separator-Subtraktionen haengen aber
allein am Ziel-Modus); vfl-body-abschluss-bauen braucht letzt-hz und
es-gewuenscht nicht.

Offen: Schritte 5, 6, 8, 9, 10. Das sind die, bei denen die Reihenfolge
selbst Geometrie ist - der Fahrplan verlangt nach jedem einen CAD-Lauf
(TEST_HUNDM05 + TEST_VF_SPEC + TEST_LINIENZUG), nach den Geometrie-Schritten
zusaetzlich DXF-Diff und Sivas-Export-Vergleich. Ohne Lauf zwischen den
Schritten waere ein Fehler nicht mehr einem Schritt zuzuordnen. Sie bringen
keine neue Faehigkeit: der Bau aus Daten laeuft seit Stufe 1 und ist mit
VF_SPEC_BAU belegt (5 OK, 0 Fehler).

Verifiziert: 109 pytest-Tests gruen, beide .lsp lint-sauber, kein neuer
alert/get*/ssget im Bau-Pfad (statischer Waechter).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-03 12:14:27 +02:00
parent 541ad3f89d
commit 02303ad7fc
4 changed files with 306 additions and 114 deletions
+195 -111
View File
@@ -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"))
+40 -2
View File
@@ -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"))