diff --git a/Lisp/vf_linienzug.lsp b/Lisp/vf_linienzug.lsp index 26e9766..81368eb 100644 --- a/Lisp/vf_linienzug.lsp +++ b/Lisp/vf_linienzug.lsp @@ -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 diff --git a/dcl/vf_linienzug_wizard.dcl b/dcl/vf_linienzug_wizard.dcl index 52fea60..9dfdd20 100644 --- a/dcl/vf_linienzug_wizard.dcl +++ b/dcl/vf_linienzug_wizard.dcl @@ -213,14 +213,18 @@ vflw_as_achse : dialog { : radio_button { key = "winkel_90"; label = "90 Grad"; value = "1"; } : radio_button { key = "winkel_30"; label = "30 Grad"; } } + // Beschriftungen bewusst NEUTRAL: derselbe Dialog dient dem AS-Element + // am Kettenanfang (vorwaerts) UND dem ES-Element am Kettenende + // (rueckwaerts bauen, 2026-10-22). Welches Element gemeint ist, sagt die + // Kopfzeile ("kopf"). : boxed_radio_column { - label = "Seite (AS schleust nach):"; + label = "Seite (Element schleust nach):"; key = "seite"; : radio_button { key = "seite_links"; label = "Links"; value = "1"; } : radio_button { key = "seite_rechts"; label = "Rechts"; } } : boxed_column { - label = "Zufuehrungsachse (Fahrtrichtung):"; + label = "Fahrtrichtung (2. Punkt):"; // Startpunkt der Kette - er ist der Sitz des AS-Elements UND der // Ausgangspunkt der Achse; waehrend der Punktwahl haengt das // Gummiband an ihm (vfl-getpoint mit Basispunkt). diff --git a/lang/de_DE.json b/lang/de_DE.json index dc861fd..2cdeedd 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -56,6 +56,20 @@ "vfl-gf-laenge-modus-opt-steigend": "\n 3 - Steigende GF (Sonderfall, bis 3 Grad)", "vfl-gf-laenge-modus-opt-qc": "\n 4 - QC Linie mit GF (automatische Auf-Ab-Berechnung)", "vfl-gf-laenge-modus-opt-parallel": "\n 5 - Parallel zum Kreisel weiterbauen (nur Abstand eingeben)", + "vfl-richtung-header": "\n\nIn welche Richtung soll gebaut werden?", + "vfl-richtung-opt-vor": "\n 1 - Vorwaerts bauen (der gewaehlte Punkt ist der Kettenanfang)", + "vfl-richtung-opt-rueck": "\n 2 - Rueckwaerts bauen (der gewaehlte Punkt ist das Kettenende, ES-Austritt)", + "vfl-prompt-es-achse-p2": "\n\nFahrtrichtung - 2. Punkt (gibt nur die Richtung, der Endpunkt bleibt): ", + "vfl-es-basislinie-info": "\n Abfuehrungsachse (Fahrtrichtung): %1 Grad.", + "vfl-rueck-menu-linie-gf": "\n 1 - Neue Linie: GF (Gefaellestrecke)", + "vfl-rueck-laenge-opt-anfangspunkt": "\n 1 - Kettenanfang waehlen (Laenge ergibt sich, Anfangshoehe wird gerechnet)", + "vfl-rueck-laenge-opt-anfangshoehe": "\n 2 - Anfangshoehe festlegen (Laenge wird berechnet)", + "vfl-rueck-prompt-anfangspunkt": "\n\nKettenanfang waehlen (Richtung steht ueber die Abfuehrungsachse fest): ", + "vfl-rueck-prompt-anfangshoehe": "\nGewuenschte Hoehe am Kettenanfang (mm) <%1>: ", + "vfl-rueck-alert-hoehe-zu-klein": "Der Kettenanfang muss HOEHER liegen als das Kettenende - eine Gefaellestrecke faellt in Fahrtrichtung.", + "vfl-rueck-erfolg-punkt": "\n>>> Rueckwaerts gebaut: GF-Stueck %1 mm (horizontal), Anfangshoehe %2 mm.", + "vfl-rueck-erfolg-hoehe": "\n>>> Rueckwaerts gebaut: GF-Stueck %1 mm (horizontal), Anfangshoehe %2 mm.", + "vfl-rueck-erfolg-parallel": "\n>>> Rueckwaerts, parallel zum Kreisel: GF-Stueck %1 mm (horizontal), Bogen %2 Grad %3.", "vfl-par-prompt-abstand": "\n\nAbstand zum Kreisel (mm, senkrecht zur Zufuehrungsachse, von AS-KS_EIN bis Bogen-KS_AUS): ", "vfl-par-nur-kettenanfang": "'Parallel zum Kreisel' geht nur am Kettenanfang mit AS-Element UND festgelegter Zufuehrungsachse (2. Punkt im AS-Dialog).", "vfl-par-kein-bogen": "Kein GF-Bogen hebt den AS-Schwenk von %1 Grad auf (Seite %2) - parallel zum Kreisel nicht schliessbar.", diff --git a/lang/en_GB.json b/lang/en_GB.json index 543e8b6..7a115c0 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -56,6 +56,20 @@ "vfl-gf-laenge-modus-opt-steigend": "\n 3 - Rising GF (special case, up to 3 degrees)", "vfl-gf-laenge-modus-opt-qc": "\n 4 - QC line with GF (automatic up/down calculation)", "vfl-gf-laenge-modus-opt-parallel": "\n 5 - Continue parallel to the carousel (distance only)", + "vfl-richtung-header": "\n\nIn which direction should the chain be built?", + "vfl-richtung-opt-vor": "\n 1 - Build forwards (the picked point is the start of the chain)", + "vfl-richtung-opt-rueck": "\n 2 - Build backwards (the picked point is the end of the chain, ES outlet)", + "vfl-prompt-es-achse-p2": "\n\nTravel direction - 2nd point (defines the direction only, the end point stays): ", + "vfl-es-basislinie-info": "\n Outfeed axis (travel direction): %1 degrees.", + "vfl-rueck-menu-linie-gf": "\n 1 - New line: GF (gravity section)", + "vfl-rueck-laenge-opt-anfangspunkt": "\n 1 - Pick start of chain (length results, start height is calculated)", + "vfl-rueck-laenge-opt-anfangshoehe": "\n 2 - Set start height (length will be calculated)", + "vfl-rueck-prompt-anfangspunkt": "\n\nPick the start of the chain (direction is fixed by the outfeed axis): ", + "vfl-rueck-prompt-anfangshoehe": "\nDesired height at the start of the chain (mm) <%1>: ", + "vfl-rueck-alert-hoehe-zu-klein": "The start of the chain must be HIGHER than its end - a gravity section falls in the travel direction.", + "vfl-rueck-erfolg-punkt": "\n>>> Built backwards: GF section %1 mm (horizontal), start height %2 mm.", + "vfl-rueck-erfolg-hoehe": "\n>>> Built backwards: GF section %1 mm (horizontal), start height %2 mm.", + "vfl-rueck-erfolg-parallel": "\n>>> Backwards, parallel to the carousel: GF section %1 mm (horizontal), bend %2 degrees %3.", "vfl-par-prompt-abstand": "\n\nDistance to the carousel (mm, perpendicular to the feed axis, from AS KS_EIN to bend KS_AUS): ", "vfl-par-nur-kettenanfang": "'Parallel to the carousel' only works at the start of a chain with an AS element AND a defined feed axis (2nd point in the AS dialog).", "vfl-par-kein-bogen": "No GF bend cancels the AS plan turn of %1 degrees (side %2) - cannot close parallel to the carousel.", diff --git a/menu/SSG_LIB.cui b/menu/SSG_LIB.cui index 85b684e..e104881 100644 --- a/menu/SSG_LIB.cui +++ b/menu/SSG_LIB.cui @@ -118,7 +118,7 @@ - 3D Objekte stueckweise (in Arbeit) + aus 3D Linie ohne Zielhoehe ^C^C(ssg-ensure "VarioFoerderer") LinienzugVarioFoerdererStueckweise @@ -1088,7 +1088,7 @@ - 3D Objekte stueckweise (in Arbeit) + aus 3D Linie ohne Zielhoehe diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index b9f18a7..2fd0e54 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -449,6 +449,47 @@ (< (vfl-par-gf-laenge 100.0 200.0 300.0 1.0) 0.0)) ;; AS schwenkt gar nicht aus der Achse -> keine Loesung (tu-nil "vfl-par-laenge/kein-versatz" (vfl-par-gf-laenge 2000.0 0.0 0.0 0.0)) + + ;; --- Vorgabewert aus einem Prompt ziehen (Dialog-Feld vorbelegen) --- + ;; Die Konsolen-Prompts tragen ihre Vorgabe in Klammern; der Zahlen-Dialog + ;; liest sie daraus. Gesucht ist die LETZTE Klammergruppe MIT Zahl - die + ;; echten Texte enthalten davor "<" ohne Partner ("0 < Winkel <= 3") und + ;; runde Klammern mit Fliesstext, beides darf nicht stoeren. + (tu-eq "vflw-vorgabe/neigungswinkel" "3" + (vflw-vorgabe-aus-prompt "\nNeigungswinkel (Grad, 0 < Winkel <= 3) [3]: ")) + (tu-eq "vflw-vorgabe/neigungswinkel-berechnet" "3" + (vflw-vorgabe-aus-prompt + "\nNeigungswinkel (Grad, 0 < Winkel <= 3) [3] (deltaL wird berechnet und eingefuegt): ")) + (tu-eq "vflw-vorgabe/spitze-klammer" "2154.0" + (vflw-vorgabe-aus-prompt "\nGewuenschte Hoehe am Kettenanfang (mm) <2154.0>: ")) + (tu-eq "vflw-vorgabe/negativ" "-12.5" + (vflw-vorgabe-aus-prompt "Hoehendifferenz <-12.5>: ")) + ;; Ohne Klammergruppe bleibt das Feld leer (nil), nicht geraten. + (tu-nil "vflw-vorgabe/ohne-vorgabe" + (vflw-vorgabe-aus-prompt "\nAbstand zum Kreisel (mm, senkrecht zur Achse): ")) + (tu-nil "vflw-vorgabe/keine-zahl" + (vflw-vorgabe-aus-prompt "Bitte Namen eingeben [frei]: ")) + + ;; --- Rueckwaerts bauen: Punkt entgegen der Fahrtrichtung zuruecksetzen --- + ;; vfl-rueck-punkt ist der Rechenkern des Rueckwaerts-Baus: zu einem + ;; BEKANNTEN Endpunkt den Startpunkt eines Bausteins finden. Vorwaerts gilt + ;; P_next = P + (L*cos hz, L*sin hz, -L*tan w) - hier also mit umgekehrtem + ;; Vorzeichen. Die Hoehe muss STEIGEN (der Vorgaenger liegt hoeher). + (tu-eqf "vfl-rueck-punkt/ost-flach" + (list 0.0 0.0 0.0) (vfl-rueck-punkt '(1000.0 0.0 0.0) 0.0 1000.0 0.0)) + (tu-eqf "vfl-rueck-punkt/nord-flach" + (list 0.0 0.0 0.0) (vfl-rueck-punkt '(0.0 1000.0 0.0) 90.0 1000.0 0.0)) + ;; 45 Grad Gefaelle: 1000 mm horizontal zurueck = 1000 mm hoeher + (tu-eqf "vfl-rueck-punkt/45-grad" + (list 0.0 0.0 1000.0) (vfl-rueck-punkt '(1000.0 0.0 0.0) 0.0 1000.0 45.0)) + ;; Rundlauf: vorwaerts wieder auf den Ausgangspunkt (3 Grad, schraeg) + (setq _l (vfl-rueck-punkt '(5000.0 3000.0 1800.0) 30.0 2000.0 3.0)) + (tu-eqf "vfl-rueck-punkt/rundlauf-x" 5000.0 + (+ (car _l) (* 2000.0 (cos (/ (* 30.0 pi) 180.0))))) + (tu-eqf "vfl-rueck-punkt/rundlauf-y" 3000.0 + (+ (cadr _l) (* 2000.0 (sin (/ (* 30.0 pi) 180.0))))) + (tu-eqf "vfl-rueck-punkt/rundlauf-z" 1800.0 + (- (caddr _l) (* 2000.0 (/ (sin (/ (* 3.0 pi) 180.0)) (cos (/ (* 3.0 pi) 180.0)))))) )