feat: Linienzug Modus 1 kann rueckwaerts bauen (ES als exakter Anker)

Bisher war der geklickte Punkt immer der KettenANFANG. Oft steht aber das
andere Ende fest - typisch der Anschluss an einen Kreisel. Modus 1 fragt
deshalb jetzt ganz am Anfang nach der Bau-Richtung:
  vorwaerts  = geklickter Punkt ist der Kettenanfang (alles wie bisher)
  rueckwaerts= er ist das Kettenende, genauer das KS_AUS des ES-Elements

ABLAUF RUECKWAERTS
ES setzen? + Winkel + Seite + Fahrtrichtung (2. Punkt) in EINEM Dialog, dann
"Neue Linie: GF" und die Laengenfrage mit drei Optionen:
  1 Kettenanfang klicken  -> + Neigungswinkel, Anfangshoehe wird gerechnet
  2 Anfangshoehe vorgeben -> + Neigungswinkel, Laenge wird gerechnet
  3 Parallel zum Kreisel  -> nur Abstand (nur mit ES waehlbar)
Danach ist die Kette fertig. Die Spiegelfrage "Ist das der Kettenanfang?"
mit optionalem AS und Weiterbauen ist bewusst NICHT enthalten (Nutzer-
Entscheidung: vorerst kleiner Umfang).

ANKER
Das ES wird mit insert-block-mixed-to-ks und xy-ref/z-ref="KS_AUS" EXAKT auf
den geklickten Punkt gesetzt; alles Weitere wird daraus zurueckgerechnet
(vfl-rueck-punkt) und dann normal vorwaerts platziert. Gleiches Ergebnis,
aber mit den erprobten Einfuege-Primitiven. Eine Rest-Ungenauigkeit landet
am Kettenanfang statt am Kreisel-Anschluss.

Option 3 spiegelt die Vorwaerts-Variante: Bogenseite = Gegenseite der
ES-Seite, Bogenwinkel hebt den gemessenen ES-Schwenk auf. Dafuer werden die
bereits getesteten Kerne vfl-par-bogen-waehlen/vfl-par-gf-laenge
wiederverwendet - die Zuordnung (ES 30 links -> 30-Grad-Bogen rechts usw.)
faellt daraus heraus, statt gepflegt zu werden.

ZWEI FEHLER, DIE DER ERSTE BRICSCAD-TEST GEFUNDEN HAT
1. vflw-gruppe-as-impl legt VIER Werte in die Pending-Queue (ja/nein,
   Winkel, Seite, Punkt). Die Ja/Nein-Antwort wurde nicht konsumiert, alles
   rutschte eine Position weiter - der Winkel bekam die "1" der Ja/Nein-
   Frage, Blockname "ES_Element_1_links". Die Frage wird jetzt gestellt und
   ausgewertet; "Nein" ist dabei sinnvoll belegt (Kette endet direkt am
   geklickten Punkt, Richtung ueber vfl-frage-fahrtrichtung-p2).
2. insert-block-ks-to-ks richtet immer den EINGANG eines Blocks aus.
   Rueckwaerts sitzt der Bogen VOR dem GF-Stueck, sein AUSGANG muss also auf
   der GF-Achse liegen - der Eingang ist um den Bogenschwenk vorzudrehen
   (hz-bogen = hz-ein - bogen-turn). Ohne das zeigte der Ausgang um genau den
   Bogenwinkel daneben, KS_AUS und KS_EIN trafen sich nicht. Gilt fuer den
   Probe-Bau UND das echte Einfuegen - waere nur eins gedreht, saesse der
   Bogen richtig orientiert an der falschen Stelle.

BEDIENUNG
- Kurze Entscheidungen im Rueckwaerts-Ablauf als Radio-Buttons statt
  Auswahlliste (vfl-menu-radio).
- Beim Klick auf den Kettenanfang haengt der Cursor auf der Kettenachse
  (neuer Wrapper vfl-in-point-achse ueber das vorhandene
  vfl-getpoint-achse): ab dort kann nur noch entlang dieser Achse gebaut
  werden, ein frei schwenkendes Gummiband gaukelte eine Freiheit vor.
- Zahlen-Dialoge belegen ihr Feld jetzt mit der Vorgabe vor. Die Prompts
  tragen sie ohnehin in Klammern ("[3]", "<2154.0>"), der Dialog hat sie nur
  ignoriert - bewusst generisch geloest (vflw-vorgabe-aus-prompt), die
  Luecke hatten alle Zahlen-Dialoge des Assistenten.
- vflw_as beschriftet Seite und Achse neutral, weil derselbe Dialog jetzt
  AS (vorwaerts) und ES (rueckwaerts) bedient; die Punkt-Anzeige heisst beim
  ES "Endpunkt".

ABWAERTSKOMPATIBILITAET
Die Richtungsfrage steht VOR der AS-Frage und journalisiert ihren WERT
("vor"/"rueck") statt eines Index - ein Index "1"/"2" waere von der
nachfolgenden AS-Ja/Nein-Antwort nicht zu unterscheiden. Fehlt der Eintrag,
ist es ein Alt-Journal und es wird vorwaerts gebaut. Dafuer der neue Wrapper
vfl-menu-strwert.

TESTS
12 neue file- und zeichnungsfreie Unit-Tests: vfl-rueck-punkt (Rundlauf
vorwaerts/rueckwaerts) und vflw-vorgabe-aus-prompt (gegen die echten
Prompt-Texte, inkl. der Stolperstellen "0 < Winkel <= 3" ohne schliessendes
">" und dem Nachsatz in runden Klammern). TEST_UNIT konnte noch nicht
laufen - BricsCAD war die ganze Sitzung geoeffnet. Der Rueckwaerts-Bau
selbst ist vom Nutzer in BricsCAD geprueft und bestaetigt.

SSG_LIB.cui ist das von BricsCAD neu erzeugte Kompilat zur Menue-Aenderung
des vorigen Commits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 13:41:09 +02:00
parent e1e5fbf0c1
commit fb567949be
6 changed files with 528 additions and 7 deletions
+451 -3
View File
@@ -683,13 +683,47 @@
;; Generischer Zahlen-Dialog (ersetzt getreal). Rueckgabe: reale Zahl oder
;; nil (Abbruch/leer - wie getreal bei blossem Enter, von den Aufrufern
;; bereits ueberall mit einem Default abgefangen).
(defun vflw-zahl-impl (prompt / dat dcl-pfad dlg-wert ergebnis)
;; Vorgabewert aus einem Prompt ziehen, damit der Zahlen-Dialog sein Eingabe-
;; feld damit vorbelegen kann. Die Konsolen-Prompts tragen ihn seit jeher in
;; Klammern ("[3]", "<2154.0>"); im Dialog blieb das Feld trotzdem leer und
;; der Wert war nur in der Kopfzeile ablesbar (Nutzer-Wunsch 2026-10-22:
;; gleich eintragen). Bewusst GENERISCH statt an einer Aufrufstelle: dieselbe
;; Luecke hatten alle Zahlen-Dialoge des Assistenten.
;;
;; Gesucht wird die LETZTE Klammergruppe, deren Inhalt eine Zahl ist - damit
;; stoeren Klammern im uebrigen Text nicht. Beispiel:
;; "Neigungswinkel (Grad, 0 < Winkel <= 3) [3]: " -> "3"
;; Das "<" in "0 < Winkel" hat kein schliessendes ">" und faellt weg, "(Grad,
;; ...)" ist keine Zahl. Rueckgabe: Zahl als String oder nil.
(defun vflw-vorgabe-aus-prompt (prompt / i n c zu inhalt ergebnis)
(setq n (strlen prompt) i 1 ergebnis nil)
(while (<= i n)
(setq c (substr prompt i 1))
(if (or (= c "[") (= c "<"))
(progn
;; vl-string-search zaehlt 0-basiert, substr 1-basiert: die 1-basierte
;; Position i der oeffnenden Klammer ist als 0-basierter Startwert
;; genau die Stelle DAHINTER.
(setq zu (vl-string-search (if (= c "[") "]" ">") prompt i))
(if zu
(progn
(setq inhalt (substr prompt (1+ i) (- zu i)))
(if (distof inhalt 2) (setq ergebnis inhalt))))))
(setq i (1+ i)))
ergebnis)
(defun vflw-zahl-impl (prompt / dat dcl-pfad dlg-wert ergebnis vorgabe)
(setq dcl-pfad (vflw-dcl-pfad))
(setq dat (load_dialog dcl-pfad))
(if (not (new_dialog "vflw_zahl" dat))
(progn (if (and dat (>= dat 0)) (unload_dialog dat)) nil)
(progn
(set_tile "kopf" (vflw-clean prompt))
;; Vorgabe ins Feld schreiben - "Ok Weiter" ohne Aenderung liefert damit
;; denselben Wert wie frueher das leere Feld (dort griff der Default
;; beim Aufrufer).
(setq vorgabe (vflw-vorgabe-aus-prompt prompt))
(if vorgabe (set_tile "wert" vorgabe))
(action_tile "accept" "(setq dlg-wert (get_tile \"wert\")) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
@@ -914,8 +948,11 @@
;; meldet dann vfl-frage-as-winkel-seite-oder-achse auf der Konsole.
(if mit-achse
(progn
;; Beim AS ist der Punkt der Kettenanfang, beim ES (Rueckwaerts-
;; Bau) das Kettenende - die Beschriftung folgt der Kopfzeile.
(set_tile "start_anzeige"
(strcat "Startpunkt: " (vflw-g-punkt-str achse-basis)))
(strcat (if (= kopf-key "vfl-es-setzen-frage") "Endpunkt: " "Startpunkt: ")
(vflw-g-punkt-str achse-basis)))
(if gpunkt
(set_tile "pt_anzeige"
(strcat (vflw-g-punkt-str gpunkt)
@@ -2032,6 +2069,16 @@
(defun vfl-in-point (base prompt)
(vfl-in-value "PT" (function (lambda () (vfl-getpoint base prompt)))))
;; Wie vfl-in-point, aber der Cursor haengt auf einer ACHSE (SNAPANG+ORTHO,
;; siehe vfl-getpoint-achse) statt frei in der Ebene. Fuer Punkte, deren
;; Richtung bereits feststeht und bei denen der Klick nur noch die LAENGE
;; beisteuert - dort fuehrt ein frei schwenkendes Gummiband sonst in die Irre
;; (Nutzer-Befund 2026-10-22 beim Rueckwaerts-Bau: der Kettenanfang kann nur
;; auf der Achse des Separator-KS_EIN liegen, die Gummiband-Linie zeigte aber
;; schraeg daneben). Journal-Format unveraendert: ein "PT"-Eintrag.
(defun vfl-in-point-achse (base prompt hz)
(vfl-in-value "PT" (function (lambda () (vfl-getpoint-achse base prompt hz)))))
;; Objektauswahl (Modus 2: Pfad-Objekte). Wie die anderen Wrapper repliziert
;; bzw. journalisiert - aber KEIN Wizard-Dialog (Objektwahl braucht wie
;; Punktwahl die Zeichnung, kein DCL-Fenster wuerde hier helfen). Beim Replay
@@ -2147,6 +2194,33 @@
(setq *vflw-menu-radio* T)
(vfl-in-string prompt))
;; Wie vfl-menu-radio, journalisiert aber den WERT der Auswahl statt des
;; Auswahl-Index ("vor"/"rueck" statt "1"/"2"). Gebraucht, wo eine NEUE Frage
;; VOR eine bestehende gesetzt wird: mit einem Index waere beim Replay nicht
;; mehr unterscheidbar, ob der naechste Eintrag zur neuen oder zur alten Frage
;; gehoert - die Folgefrage benutzt ja dieselben "1"/"2". Mit einem eigenen
;; Wertevorrat sind die Mengen disjunkt und ein Blick per
;; vfl-replay-peek-wert genuegt. Entspricht der Konvention "Werte
;; selbstbeschreibend fuehren, nicht als positionsabhaengige Indizes"
;; (CLAUDE.md) - dieselbe Begruendung wie bei vfl-menu-winkel.
;; werte: Rueckgabewerte in der Reihenfolge der Labels.
(defun vfl-menu-strwert (prompt option-labels werte default-idx frage-key /
*vflw-menu-optionen* *vflw-menu-default* *vflw-menu-frage*
*vflw-menu-radio*)
(setq *vflw-menu-optionen* option-labels)
(setq *vflw-menu-default* default-idx)
(setq *vflw-menu-frage* frage-key)
(setq *vflw-menu-radio* T)
(vfl-in-value "STR"
(function (lambda ( / i k)
(setq i (if (and (vfl-wizard-mode-p) *vflw-menu-optionen*)
(vflw-radio-wahl (ssg-text frage-key) *vflw-menu-optionen* *vflw-menu-default*)
(getstring prompt)))
(setq k (if (and i (/= i "")) (atoi i) *vflw-menu-default*))
(if (and (>= k 1) (<= k (length werte)))
(nth (1- k) werte)
(nth (1- *vflw-menu-default*) werte))))))
(defun vfl-menu-int (prompt option-labels default-idx frage-key /
*vflw-menu-optionen* *vflw-menu-default* *vflw-menu-frage*)
(setq *vflw-menu-optionen* option-labels)
@@ -5032,6 +5106,350 @@
)
)
;; ==================================================================
;; RUECKWAERTS BAUEN (Modus 1, Nutzer-Vorgabe 2026-10-22)
;; ==================================================================
;; Normalfall ist "vorwaerts": der geklickte Punkt ist der KettenANFANG, dort
;; sitzt das AS-Element, und die Kette waechst von da weg - die Endhoehe ist
;; das Ergebnis. Manchmal steht aber das ANDERE Ende fest (typisch: der
;; Anschluss an einen Kreisel). Dann ist der geklickte Punkt das KS_AUS des
;; ES-Elements, und gebaut wird von dort nach hinten.
;;
;; ANKER-ENTSCHEIDUNG (Nutzer, 2026-10-22): das ES wird ZUERST mit seinem
;; KS_AUS exakt auf den geklickten Punkt gesetzt; alles Weitere wird daraus
;; rueckwaerts bestimmt. Eine Rest-Ungenauigkeit landet damit am
;; Kettenanfang, wo sie nicht stoert - genau spiegelbildlich zum
;; Vorwaerts-Fall, wo das AS-KS_EIN der exakte Anker ist.
;;
;; Umfang zunaechst bewusst klein (Nutzer-Vorgabe): ES + EIN GF-Stueck
;; (+ bei "Parallel zum Kreisel" ein Bogen davor), danach ist die Kette
;; fertig. Die Spiegel-Frage "Ist das der Kettenanfang?" (mit optionalem
;; AS-Element und Weiterbauen) ist bewusst NICHT eingebaut.
;;
;; Alle Bausteine sind GERADE bzw. haben einen messbaren Fussabdruck, darum
;; wird nirgends "rueckwaerts eingefuegt": es wird der jeweilige
;; START-Punkt ausgerechnet und dann ganz normal vorwaerts platziert. Das
;; Ergebnis ist identisch, nutzt aber die erprobten Einfuege-Primitiven.
;; ES-Winkel + Seite + Abfuehrungsachse (2. Punkt) - Spiegelbild von
;; vfl-frage-as-winkel-seite-oder-achse. Die Achse ist hier NICHT optional:
;; ohne Richtung laesst sich das ES gar nicht platzieren (vorwaerts kommt die
;; Richtung aus dem ersten Streckenklick, den es hier nicht gibt).
;; Setzt *vfl-es-winkel*, die ES-Masse (vf-set-es-masse) und
;; *vfl-es-basis-hz*. Rueckgabe: Seite ("links"/"rechts") oder nil, wenn
;; keine brauchbare Achse gewaehlt wurde.
;; Fahrtrichtung ueber einen zweiten Punkt. Rueckgabe: hz in Grad oder nil.
;; Eigene kleine Schale, weil sie an ZWEI Stellen gebraucht wird: mit
;; ES-Element (dort bestimmt sie zugleich dessen Lage) und ohne ES-Element
;; (dann endet die Kette direkt am geklickten Punkt, die Richtung muss aber
;; trotzdem bekannt sein - vorwaerts kaeme sie aus dem ersten Streckenklick,
;; den es rueckwaerts nicht gibt).
(defun vfl-frage-fahrtrichtung-p2 (basis / p2)
(setq p2 (vfl-in-point basis (ssg-text "vfl-prompt-es-achse-p2")))
(if (and p2
(or (> (abs (- (car p2) (car basis))) 1e-9)
(> (abs (- (cadr p2) (cadr basis))) 1e-9)))
(progn
(setq *vfl-es-basis-hz* (vfl-planar-hz basis p2))
(princ (ssg-textf "vfl-es-basislinie-info" (list (rtos *vfl-es-basis-hz* 2 1))))
*vfl-es-basis-hz*)
(progn (princ (ssg-text "vfl-basislinie-punkte-gleich")) nil)))
(defun vfl-frage-es-winkel-seite-achse (startpunkt / antwort es-seite)
(setq *vfl-es-basis-hz* nil)
(princ (ssg-text "vf-winkel-ein-header"))
(princ (ssg-text "vf-winkel-90"))
(princ (ssg-text "vf-winkel-30"))
(setq *vfl-es-winkel*
(vfl-in-value "STR"
(function (lambda () (vf-frage-element-winkel "vf-winkel-ein-header")))))
(princ (ssg-text "gf-seite-ein-header"))
(princ (ssg-text "gf-seite-links"))
(princ (ssg-text "gf-seite-rechts"))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(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)
(if (vfl-frage-fahrtrichtung-p2 startpunkt) es-seite nil))
;; Punkt um eine Strecke ENTGEGEN der Fahrtrichtung zurueckversetzen.
;; laenge-planar = horizontale (Grundriss-)Laenge, winkel = Gefaelle in Grad
;; (positiv = fallend). Vorwaerts gilt
;; P_next = P + (L*cos(hz), L*sin(hz), -L*tan(winkel))
;; - rueckwaerts also mit umgekehrtem Vorzeichen. Damit liegt der Startpunkt
;; eines Bausteins fest, der an einem BEKANNTEN Endpunkt ankommen soll.
(defun vfl-rueck-punkt (p hz laenge-planar winkel / rad radw)
(setq rad (* (float hz) (/ pi 180.0)))
(setq radw (* (float winkel) (/ pi 180.0)))
(list (- (car p) (* laenge-planar (cos rad)))
(- (cadr p) (* laenge-planar (sin rad)))
(+ (caddr p) (* laenge-planar (/ (sin radw) (cos radw))))))
;; ES-Element so platzieren, dass sein KS_AUS EXAKT auf p-ende liegt und von
;; dort in Richtung hz-achse weiterzeigt; davor den Separator, den das
;; ES-Element im Vorwaerts-Bau immer mitbringt (vfl-insert-es-element).
;;
;; insert-block-mixed-to-ks dreht den Block IMMER so, dass sein KS_EIN auf den
;; uebergebenen Rahmen passt - xy-ref/z-ref steuern nur die Verschiebung.
;; Fuer "KS_AUS zeigt entlang hz-achse" muss der Rahmen also um den
;; Grundriss-Schwenk des ES zurueckgedreht werden: hz-ein = hz-achse - es-turn
;; (dieselbe Rechnung wie in vfl-insert-as-element, nur andersherum gebraucht).
;; Rueckgabe: (frame-vor-dem-Separator hz-ein) - der Punkt, an dem das
;; GF-Stueck ankommen muss.
(defun vfl-rueck-es-setzen (es-w es-s p-ende hz-achse /
es-turn hz-ein w rad-w frame-ziel p-ein p-sep)
(setq es-turn (vf-element-plan-turn (strcat "ES_Element_" es-w "_" es-s)))
(setq hz-ein (- hz-achse es-turn))
(setq w (fix *vfk-gefaelle-winkel*))
;; Zielrahmen traegt die Standard-Fallneigung: so kommt die Kette am ES an
;; (vgl. vfl-insert-es-element, das den ES ebenfalls auf den geneigten
;; Rahmen setzt).
(setq frame-ziel (make-frame-from-dir p-ende (hz-winkel->xu hz-ein w)))
(insert-block-mixed-to-ks (strcat "ES_Element_" es-w "_" es-s)
frame-ziel (caddr p-ende) "KS_AUS" "KS_AUS")
;; KS_EIN des GERADE gesetzten ES messen - der Versatz KS_EIN->KS_AUS ist
;; beim ES seitlich und laesst sich nicht aus dem Klickpunkt ableiten.
(setq p-ein (vfl-block-ks-punkt (vlax-ename->vla-object (entlast)) "KS_EIN"))
(if (null p-ein) (setq p-ein p-ende))
;; Separator rueckwaerts davor: sein KS_AUS trifft das ES-KS_EIN.
(setq p-sep (vfl-rueck-punkt p-ein hz-ein
(* *vfk-separator-laenge* (cos (* (float w) (/ pi 180.0)))) w))
(gf-insert-hz-with-ks "Staustrecke_Separator_SP_300_mm" p-sep hz-ein w
*vfk-separator-laenge* 0)
(if (boundp '*vfl-acc-separator*)
(setq *vfl-acc-separator* (1+ *vfl-acc-separator*)))
(list p-sep hz-ein))
;; GF-Gerade rueckwaerts: sie soll an p-ziel ANKOMMEN, Grundrisslaenge
;; laenge-planar, Neigung winkel. Rueckgabe: ihr Startpunkt (= neues
;; Kettenende nach hinten).
(defun vfl-rueck-gf (p-ziel hz laenge-planar winkel / p-start)
(setq p-start (vfl-rueck-punkt p-ziel hz laenge-planar winkel))
(vfl-insert-gf-segment p-start hz laenge-planar winkel)
(vfl-acc-gf-seg (/ laenge-planar (cos (* (float winkel) (/ pi 180.0)))) winkel)
p-start)
;; ------------------------------------------------------------------
;; Rueckwaerts-Ablauf (Treiber). Nutzt - wie die uebrigen Bau-Zweige - die
;; dynamisch sichtbaren Lokalvariablen des Aufrufers vf-linienzug-modus
;; (startpunkt = geklicktes KETTENENDE, frame, p-aktuell, anzahl-gf,
;; es-seite, fertig, letzter-typ).
(defun vfl-rueckwaerts-bauen ( / es-w es-s es-vorhanden hz-achse erg p-ziel hz-ein
modus gf-max-winkel winkel laenge deltaH
p-pick p-start h-start rad
bogen-seite bogen-winkel bogen-turn hz-bogen
kandidaten tol w
abstand perp-ziel perp-bogen nx ny rad-a bw
acc-gfbogen-save acc-sep-save probe-checkpoint
probe-frame basis-frame dxp dyp dzp p-bogen-start)
;; --- ES-Element: "setzen?" + Winkel + Seite + Fahrtrichtung ---
;; WICHTIG: vflw-gruppe-as-impl legt VIER Werte in die Pending-Queue
;; (ja/nein, Winkel, Seite, Punkt). Die Ja/Nein-Antwort MUSS hier zuerst
;; konsumiert werden - fehlt dieser Schritt, rutscht alles um eine Position
;; und der Winkel bekommt die "1" der Ja/Nein-Frage (realer Fehler
;; 2026-10-22: Blockname "ES_Element_1_links").
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-es-setzen-frage" startpunkt)))
(princ (ssg-text "vfl-es-setzen-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-nein"))
(setq es-vorhanden
(/= "2" (vfl-menu-radio (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 1
"vfl-es-setzen-frage")))
(setq w (fix *vfk-gefaelle-winkel*))
(if es-vorhanden
(progn
(setq es-s (vfl-frage-es-winkel-seite-achse startpunkt))
(if (null es-s) (progn (princ (ssg-text "vfl-abgebrochen")) (exit)))
(setq es-seite es-s)
(setq es-w (vfl-es-winkel))
(setq hz-achse *vfl-es-basis-hz*)
;; ES + Separator setzen - Anker: KS_AUS exakt auf dem geklickten Punkt.
(setq erg (vfl-rueck-es-setzen es-w es-s startpunkt hz-achse))
(setq p-ziel (nth 0 erg) hz-ein (nth 1 erg)))
(progn
;; Ohne ES endet die Kette direkt am geklickten Punkt; die Richtung
;; muss trotzdem bekannt sein.
(setq hz-achse (vfl-frage-fahrtrichtung-p2 startpunkt))
(if (null hz-achse) (progn (princ (ssg-text "vfl-abgebrochen")) (exit)))
(setq p-ziel startpunkt hz-ein hz-achse)))
(setq gf-max-winkel (float (ssg-cfg-or "vario" "gefaelle_winkel" 3)))
;; --- Nur EINE Element-Option (Nutzer-Vorgabe): Neue Linie GF ---
;; vfl-menu-radio statt vfl-menu: Radio-Buttons statt Auswahlliste, wie bei
;; den uebrigen kurzen Entscheidungen im Assistenten (Nutzer-Wunsch
;; 2026-10-22). Journal-Semantik ist identisch - derselbe STR-Eintrag.
(princ (ssg-text "vfl-naechstes-element-header"))
(princ (ssg-text "vfl-rueck-menu-linie-gf"))
(vfl-menu-radio (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-rueck-menu-linie-gf")) 1 "vfl-naechstes-element-header")
;; --- Laengen-Frage: drei Optionen (Nutzer-Vorgabe) ---
;; "Parallel zum Kreisel" braucht das ES als Bezug (sein Grundriss-Schwenk
;; bestimmt den Bogen) - ohne ES-Element entfaellt die Option.
(princ (ssg-text "vfl-gf-laenge-modus-header"))
(princ (ssg-text "vfl-rueck-laenge-opt-anfangspunkt"))
(princ (ssg-text "vfl-rueck-laenge-opt-anfangshoehe"))
(if es-vorhanden (princ (ssg-text "vfl-gf-laenge-modus-opt-parallel")))
(setq modus (vfl-menu-radio (ssg-text "prompt-wahl-1-2")
(if es-vorhanden
(list (ssg-text "vfl-rueck-laenge-opt-anfangspunkt")
(ssg-text "vfl-rueck-laenge-opt-anfangshoehe")
(ssg-text "vfl-gf-laenge-modus-opt-parallel"))
(list (ssg-text "vfl-rueck-laenge-opt-anfangspunkt")
(ssg-text "vfl-rueck-laenge-opt-anfangshoehe"))) 1
"vfl-gf-laenge-modus-header"))
(cond
;; --- Option 3: Parallel zum Kreisel (Spiegelbild von
;; vfl-gf-parallel-kreisel) ---
((= modus "3")
;; Bogen zur GEGENSEITE der ES-Seite, Winkel so, dass er den
;; Grundriss-Schwenk des ES aufhebt - identische Regel wie vorwaerts,
;; nur mit es-turn statt as-turn. Ergibt mit den vorhandenen Bausteinen
;; die Nutzer-Tabelle: ES 30 links -> 30-Grad-Bogen rechts usw.
(setq bogen-seite (if (= es-s "rechts") "links" "rechts"))
(setq kandidaten '())
(foreach bw *vfk-gf-bogen-winkel*
(setq kandidaten
(cons (cons bw (vf-element-plan-turn (gf-bogen-blockname bw bogen-seite)))
kandidaten)))
(setq tol 2.0) ; wie in vfl-qc-linie-mit-gf
(setq bogen-winkel
(vfl-par-bogen-waehlen
(vf-element-plan-turn (strcat "ES_Element_" es-w "_" es-s))
(reverse kandidaten) tol))
(if (null bogen-winkel)
(vfl-meldung (ssg-textf "vfl-par-kein-bogen"
(list (rtos (vf-element-plan-turn
(strcat "ES_Element_" es-w "_" es-s)) 2 1)
bogen-seite)))
(progn
(setq abstand (vfl-in-real (ssg-text "vfl-par-prompt-abstand")))
(if (or (null abstand) (<= (abs abstand) 0.0))
(princ (ssg-text "vfl-abgebrochen"))
(progn
;; Normale zur Abfuehrungsachse; alle Querversatz-Werte beziehen
;; sich auf den geklickten Punkt (= ES-KS_AUS, perp = 0).
(setq rad-a (* (float hz-achse) (/ pi 180.0)))
(setq nx (- (sin rad-a)) ny (cos rad-a))
(setq perp-ziel (+ (* (- (car p-ziel) (car startpunkt)) nx)
(* (- (cadr p-ziel) (cadr startpunkt)) ny)))
;; Bogen-Querversatz messen - Probe auf GENAU dem Rahmen, auf dem
;; der echte Bogen spaeter sitzt (idealisierter Gefaelle-Rahmen,
;; siehe gleichlautende Begruendung in vfl-gf-parallel-kreisel).
;;
;; RUECKWAERTS-BESONDERHEIT: der Bogen sitzt VOR dem GF-Stueck,
;; sein AUSGANG muss also auf der GF-Richtung hz-ein liegen.
;; insert-block-ks-to-ks richtet aber immer den EINGANG am
;; uebergebenen Rahmen aus - der Eingang muss daher um den
;; Bogenschwenk vorgedreht werden: hz-bogen = hz-ein - bogen-turn.
;; Ohne diese Drehung zeigt der Bogen-Ausgang um genau den
;; Bogenwinkel neben der GF-Achse und die KS treffen sich nicht
;; (realer Fehler 2026-10-22). Vorwaerts (vfl-gf-parallel-kreisel)
;; tritt das nicht auf: dort folgt der Bogen dem GF-Stueck, sein
;; Eingang liegt also ohnehin schon auf der GF-Richtung.
(setq bogen-turn (cdr (assoc bogen-winkel kandidaten)))
(setq hz-bogen (- hz-ein bogen-turn))
(setq acc-gfbogen-save *vfl-acc-gfbogen* acc-sep-save *vfl-acc-separator*)
(setq probe-checkpoint (ssg-lastent-ohne-attribute))
(setq basis-frame (make-frame-from-dir p-ziel (hz-winkel->xu hz-bogen w)))
(setq probe-frame (vfl-insert-gf-bogen-block basis-frame bogen-winkel bogen-seite))
(setq dxp (- (car (car probe-frame)) (car (car basis-frame))))
(setq dyp (- (cadr (car probe-frame)) (cadr (car basis-frame))))
(setq dzp (- (caddr (car probe-frame)) (caddr (car basis-frame))))
(vfl-loesche-seit probe-checkpoint)
(setq *vfl-acc-gfbogen* acc-gfbogen-save *vfl-acc-separator* acc-sep-save)
(setq perp-bogen (+ (* dxp nx) (* dyp ny)))
;; Querversatz des GF-Stuecks je Laengeneinheit: es laeuft auf
;; hz-ein, die Achse auf hz-achse - die Differenz ist genau der
;; ES-Schwenk. Rueckwaerts gezaehlt kehrt sich das Vorzeichen um.
(setq laenge (vfl-par-gf-laenge abstand perp-ziel (- perp-bogen)
(sin (* (- hz-achse hz-ein) (/ pi 180.0)))))
(cond
((null laenge) (vfl-meldung (ssg-text "vfl-par-kein-versatz")))
((< laenge 1.0)
(vfl-meldung (ssg-textf "vfl-par-zu-klein"
(list (rtos (abs abstand) 2 1)
(rtos (abs (- perp-ziel perp-bogen)) 2 1)))))
(t
(setq p-start (vfl-rueck-gf p-ziel hz-ein laenge w))
(setq anzahl-gf (1+ anzahl-gf))
;; Bogen rueckwaerts: sein KS_AUS muss den GF-Start treffen -
;; also um den GEMESSENEN Versatz (inkl. Hoehe) zurueckgesetzt.
(setq p-bogen-start (list (- (car p-start) dxp)
(- (cadr p-start) dyp)
(- (caddr p-start) dzp)))
;; Gleiche Eingangsdrehung wie bei der Probe (s.o.) - nur so
;; passt der gemessene Versatz und der Ausgang landet auf der
;; GF-Achse.
(setq basis-frame (make-frame-from-dir p-bogen-start (hz-winkel->xu hz-bogen w)))
(setq frame (vfl-insert-gf-bogen-block basis-frame bogen-winkel bogen-seite))
(setq p-aktuell p-bogen-start)
(princ (ssg-textf "vfl-rueck-erfolg-parallel"
(list (rtos laenge 2 1) (itoa bogen-winkel) bogen-seite)))
(setq fertig t))))))))
;; --- Option 2: Anfangshoehe vorgeben, Laenge ergibt sich ---
((= modus "2")
(setq h-start (vfl-in-real (ssg-textf "vfl-rueck-prompt-anfangshoehe"
(list (rtos (caddr p-ziel) 2 1)))))
(if (null h-start) (setq h-start (caddr p-ziel)))
(setq deltaH (- h-start (caddr p-ziel)))
(setq winkel (vfl-in-real (ssg-textf "vfl-prompt-neigungswinkel-laenge-berechnet"
(list (rtos gf-max-winkel 2 1)))))
(if (null winkel) (setq winkel gf-max-winkel))
(cond
((<= deltaH 0.0) (vfl-meldung (ssg-text "vfl-rueck-alert-hoehe-zu-klein")))
((or (<= winkel 0.0) (> winkel gf-max-winkel))
(vfl-meldung (ssg-textf "vfl-alert-winkel-ungueltig"
(list (rtos winkel 2 1) (rtos gf-max-winkel 2 1)))))
(t
(setq laenge (/ deltaH (/ (sin (* winkel (/ pi 180.0)))
(cos (* winkel (/ pi 180.0))))))
(if (< laenge 1.0)
(princ (ssg-text "vfl-fehler-linie-zu-kurz"))
(progn
(setq p-start (vfl-rueck-gf p-ziel hz-ein laenge winkel))
(setq anzahl-gf (1+ anzahl-gf))
(setq frame (make-frame-from-dir p-start (hz-winkel->xu hz-ein winkel)))
(setq p-aktuell p-start)
(princ (ssg-textf "vfl-rueck-erfolg-hoehe"
(list (rtos laenge 2 1) (rtos (caddr p-start) 2 1))))
(setq fertig t))))))
;; --- Option 1: Kettenanfang klicken, Anfangshoehe ergibt sich ---
(t
;; Cursor auf die Kettenachse zwingen: ab hier kann nur noch ENTLANG
;; hz-ein gebaut werden (der Separator gibt die Richtung vor), ein frei
;; schwenkendes Gummiband wuerde eine Freiheit vorgaukeln, die es nicht
;; gibt. ORTHO laesst beide Richtungen der Achse zu - gemessen wird
;; ohnehin die Projektion.
(setq p-pick (vfl-in-point-achse p-ziel (ssg-text "vfl-rueck-prompt-anfangspunkt") hz-ein))
(if (null p-pick)
(progn (princ (ssg-text "vfl-abgebrochen")) (exit)))
;; Der Klick gibt nur die LAENGE her - die Richtung steht ueber die
;; Abfuehrungsachse schon fest. Darum auf hz-ein zurueckprojizieren
;; (entgegen der Fahrtrichtung gemessen).
(setq rad (* (float hz-ein) (/ pi 180.0)))
(setq laenge (+ (* (- (car p-ziel) (car p-pick)) (cos rad))
(* (- (cadr p-ziel) (cadr p-pick)) (sin rad))))
(setq winkel (vfl-in-real (ssg-textf "vfl-prompt-neigungswinkel"
(list (rtos gf-max-winkel 2 1)))))
(if (null winkel) (setq winkel gf-max-winkel))
(cond
((< laenge 1.0) (princ (ssg-text "vfl-fehler-linie-zu-kurz")))
((or (<= winkel 0.0) (> winkel gf-max-winkel))
(vfl-meldung (ssg-textf "vfl-alert-winkel-ungueltig"
(list (rtos winkel 2 1) (rtos gf-max-winkel 2 1)))))
(t
(setq p-start (vfl-rueck-gf p-ziel hz-ein laenge winkel))
(setq anzahl-gf (1+ anzahl-gf))
(setq frame (make-frame-from-dir p-start (hz-winkel->xu hz-ein winkel)))
(setq p-aktuell p-start)
(princ (ssg-textf "vfl-rueck-erfolg-punkt"
(list (rtos laenge 2 1) (rtos (caddr p-start) 2 1))))
(setq fertig t)))))
(setq letzter-typ "GF")
(princ))
;; ------------------------------------------------------------------
;; "QC Linie mit GF und U-Form" (Option 5 der gf-laenge-modus-Frage,
;; Nutzer-Vorgabe 2026-09-13, nach mehreren Praezisierungsrunden - inkl. echter
@@ -7397,7 +7815,8 @@
startpunkt-familie gf-kettenende-antwort
vorab-antwort vorab-es-gewuenscht vorab-es-seite
save-eindx-vorab save-eindz-vorab erg-es vorab-ziel-punkt
rad3-sep sep-eindx-add sep-eindz-add gf-parallel-moeglich)
rad3-sep sep-eindx-add sep-eindz-add gf-parallel-moeglich
bau-richtung)
;; "Zurueck"/"Abbrechen"-Flag ganz am Anfang zuruecksetzen (auch schon vor
;; der ersten Praeambel-Frage) - sonst wuerde ein von einem VORHERIGEN Lauf
;; noch gesetztes Flag (z.B. nach "Abbrechen") den allerersten vfl-in-*-
@@ -7489,6 +7908,30 @@
;; faellt dann auf einen neutralen Text zurueck (kein falsches "GF"/"VF").
;; Nur wenn GAR KEIN KS_AUS in Toleranz gefunden wurde, wird wie bisher
;; gefragt.
;; --- Bau-Richtung (Nutzer-Vorgabe 2026-10-22) ---
;; "vor" = der geklickte Punkt ist der KettenANFANG (alles wie bisher).
;; "rueck"= er ist das KettenENDE, genauer das KS_AUS des ES-Elements;
;; gebaut wird von dort nach hinten (vfl-rueckwaerts-bauen).
;; Journalisiert wird der WERT, nicht der Auswahl-Index (vfl-menu-strwert):
;; die Frage steht VOR der AS-Ja/Nein-Frage, die ebenfalls "1"/"2" schreibt -
;; mit einem Index waere ein Alt-Journal (ohne diese Frage) beim Replay nicht
;; mehr davon zu unterscheiden. So genuegt ein Blick auf den Wert.
(setq bau-richtung
(if (and *vfl-replay-queue*
(not (member (vfl-replay-peek-wert) '("vor" "rueck"))))
"vor" ; Altbestand ohne diese Frage
(progn
(princ (ssg-text "vfl-richtung-header"))
(princ (ssg-text "vfl-richtung-opt-vor"))
(princ (ssg-text "vfl-richtung-opt-rueck"))
(vfl-menu-strwert (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-richtung-opt-vor") (ssg-text "vfl-richtung-opt-rueck"))
(list "vor" "rueck") 1 "vfl-richtung-header"))))
;; Startpunkt-Erkennung und AS-Praeambel gelten NUR vorwaerts - rueckwaerts
;; gibt es kein AS-Element am geklickten Punkt, sondern ein ES.
(if (= bau-richtung "rueck")
(setq as-vorhanden nil startpunkt-familie nil)
(progn
(setq erg (vfl-ks-aus-hz-an-punkt startpunkt))
(setq startpunkt-familie (if erg (vfl-kette-typ-familie (cdr erg)) nil))
(if erg
@@ -7524,11 +7967,16 @@
(setq as-seite (vfl-frage-as-winkel-seite-oder-achse "vfl-aus-seite-header" startpunkt)))
;; ES-Seite wird erst am Kettenende abgefragt (siehe vfl-frage-es-seite),
;; da das ES-Element erst dort eingefuegt wird.
))
(setq p-aktuell startpunkt)
(setq letzter-typ nil fertig nil frame nil)
(setq anzahl-gf 0 anzahl-vf 0)
(vfl-acc-reset)
;; Rueckwaerts: ES + GF (+ Bogen) komplett hier bauen und fertig=t setzen -
;; die Hauptschleife unten laeuft dann gar nicht erst an, der gemeinsame
;; Abschluss (Block, Attribute, Journal-XDATA) danach aber ganz normal.
(if (= bau-richtung "rueck") (vfl-rueckwaerts-bauen))
;; *vfl-journal*-Reset steht jetzt GANZ AM ANFANG der Funktion (vor der
;; Startpunkt/Hoehe/AS-Praeambel) - siehe Kommentar dort. Hier NICHT mehr
;; noetig (und schaedlich: wuerde die bereits aufgezeichnete Praeambel