feat: AS-Zufuehrungsachse fuer beide Winkel + GF-Option "Parallel zum Kreisel"

Die AS-Praeambel in Modus 1 fragt jetzt Winkel, Seite UND eine optionale
Zufuehrungsachse (2. Punkt) - im Assistenten alles in einem Fenster
(vflw_as_achse). Beide Angaben zusammen legen das AS vollstaendig fest: der
2. Punkt gibt die KS_EIN-Achse (Zufuehrung/Fahrtrichtung), links/rechts die
AS-Variante und damit die KS_AUS-Richtung der Kette. Bisher galt die Achse
nur beim 30-Grad-Element und ERSETZTE dort die Seitenfrage.

Darauf setzt die neue 5. Option der gf-laenge-modus-Frage auf, intern
"parallel" (vfl-gf-parallel-kreisel): AS -> GF-Stueck -> GF-Bogen aus EINEM
Abstandswert, gemessen senkrecht zur Zufuehrungsachse von AS-KS_EIN bis
Bogen-KS_AUS. Kein Separator dazwischen, damit der Abstand exakt getroffen
wird; die Kettenende-Frage stellt der Zweig selbst NACH dem Bau. Waehlbar
nur am Kettenanfang mit AS UND Achse.

Der Bogen ist nicht verdrahtet: gewaehlt wird der, der den gemessenen
Grundriss-Schwenk des AS-Blocks aufhebt (vf-element-plan-turn) - mit den
vorhandenen Bausteinen AS 30 -> 30-Grad-Bogen, AS 90 -> 90-Grad-Bogen,
immer zur Gegenseite. Querversatz von AS und Bogen wird gemessen statt
gerechnet; die Bogen-Probe laeuft dabei auf dem IDEALISIERTEN 3-Grad-Rahmen,
auf dem der echte Bogen spaeter sitzt (auf dem AS-Rahmen gemessen lag der
Abstand im BricsCAD-Test um 0.5 mm daneben).

Journal: [STR winkel][STR seite][PT p2 | NIL]. Welche Fragen gestellt wurden,
wird nicht journalisiert, sondern beim Replay an der Eintragsart erkannt -
alte Ketten bleiben ohne Sonderbehandlung editierbar. Die Journal-Werkzeuge
(vf_linienzug_grammatik.py, vf_spec_export.py, vf_journal_export.py,
vf_spec.lsp, test_hm_recformat.lsp) kennen das optionale Achsen-Token.

Tests: 11 neue Unit-Tests fuer die reinen Kerne vfl-par-bogen-waehlen und
vfl-par-gf-laenge (tu-tests-vfl-parallel), TEST_UNIT 701/701 gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 11:04:16 +02:00
parent cccd362bba
commit 1cc163dc11
12 changed files with 815 additions and 150 deletions
+2 -2
View File
File diff suppressed because one or more lines are too long
+461 -96
View File
@@ -853,57 +853,144 @@
;; im Aufrufer-Code) - beides braucht daher keinen eigenen Parameter.
;; Original-Reihenfolge/-Journal: vfl-in-string (Ja/Nein), dann bei Ja
;; vfl-in-value (Winkel) + vfl-in-string (Seite) - bleibt unveraendert.
(defun vflw-gruppe-as-impl (kopf-key / dat dcl-pfad ergebnis gsetzen gwinkel gseite)
;;
;; achse-basis (NUR das AS-Element am Kettenanfang von Modus 1, Nutzer-Vorgabe
;; 2026-10-21; nil = wie bisher, sonst der Kettenstartpunkt): zeigt
;; zusaetzlich die Zufuehrungsachse (2. Punkt) im SELBEN
;; Bildschirm (Dialog vflw_as_achse) und legt deren Punkt als VIERTEN Wert in
;; die Pending-Queue - Reihenfolge wie im Bau-Ablauf: ja/nein, Winkel, Seite,
;; Achsen-Punkt. Beide Angaben zusammen legen das AS vollstaendig fest: der
;; 2. Punkt gibt die KS_EIN-Achse (Zufuehrung/Fahrtrichtung), links/rechts die
;; AS-Variante und damit die KS_AUS-Richtung der Kette. Wird KEIN Punkt
;; gewaehlt, geht ein nil in die Queue (Journal-Eintrag "NIL") und die Kette
;; laeuft wie bisher ueber die Richtung des naechsten Klicks.
;; Ohne achse-basis (ES-Element, Modi 2/3) bleibt alles unveraendert -
;; inklusive der ausgegrauten Seite beim 30-Grad-Element.
;;
;; Der Punkt-Button schliesst den Dialog (done_dialog 2) - (getpoint) direkt
;; aus einem action_tile-Callback funktioniert in BricsCAD nicht (siehe
;; vflw-g-punkt-str). Picken laeuft danach auf der Kommandozeile, und MIT
;; Punkt ist der Dialog fertig: er wird NICHT erneut gezeigt (Nutzer-Wunsch
;; 2026-10-22), anders als bei vflw-gruppe-punkt-hoehe-impl, wo nach dem Punkt
;; noch ein Zahlenfeld zu fuellen ist. Nur ein mit ESC abgebrochener Pick
;; zeigt den Dialog wieder. Fuer genau diesen Fall liest schon der Pick-Button
;; die Auswahl aus und der Dialog stellt sie beim Neuaufbau wieder her -
;; new_dialog setzt sonst alle Kacheln auf die DCL-Vorgaben (90/Links) zurueck.
(defun vflw-gruppe-as-impl (kopf-key achse-basis /
mit-achse dat dlg-name ergebnis
gsetzen gwinkel gseite gpunkt fertig)
;; achse-basis traegt BEIDES: nil = kein Achsen-Dialog (ES, Modi 2/3), sonst
;; der Kettenstartpunkt als Basis fuers Gummiband der Punktwahl - genau der
;; Punkt, von dem aus die Zufuehrungsachse gemessen wird.
(setq mit-achse (if achse-basis t nil))
(setq dlg-name (if mit-achse "vflw_as_achse" "vflw_as"))
(setq gsetzen "1" gwinkel "90" gseite "1" gpunkt nil fertig nil)
(while (not fertig)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_as" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(if (not (new_dialog dlg-name dat))
(progn (if (and dat (>= dat 0)) (unload_dialog dat)) (setq fertig t))
(progn
(set_tile "kopf" (vflw-clean (ssg-text kopf-key)))
(set_tile "setzen" "1")
(set_tile "winkel_90" "1")
(set_tile "seite_links" "1")
;; Seitenauswahl ist nur beim 90-Grad-Element wirksam: beim 30-Grad-
;; Element folgt die Seite aus der Zufuehrungsachse, die direkt nach
;; diesem Dialog ueber einen zweiten Punkt abgefragt wird (Nutzer-Vorgabe
;; 2026-10-14). Sie wird darum ausgegraut - und unten auch NICHT in die
;; Pending-Queue gelegt, sonst zoege die Punktfrage sie als vermeintlichen
;; Punkt heraus und erschiene gar nicht.
(set_tile "setzen" gsetzen)
(set_tile (if (= gwinkel "30") "winkel_30" "winkel_90") "1")
(set_tile (if (= gseite "2") "seite_rechts" "seite_links") "1")
;; Startpunkt IMMER zeigen - er ist der Ausgangspunkt der Achse und
;; haengt waehrend der Punktwahl am Gummiband. Der 2. Punkt samt
;; daraus entstehender Achsrichtung erscheint nur, wenn der Dialog
;; ueberhaupt noch einmal kommt (abgebrochene Punktwahl, s.u.); nach
;; einem erfolgreichen Pick schliesst der Dialog direkt, die Richtung
;; meldet dann vfl-frage-as-winkel-seite-oder-achse auf der Konsole.
(if mit-achse
(progn
(set_tile "start_anzeige"
(strcat "Startpunkt: " (vflw-g-punkt-str achse-basis)))
(if gpunkt
(set_tile "pt_anzeige"
(strcat (vflw-g-punkt-str gpunkt)
" -> Achse "
(rtos (vfl-planar-hz achse-basis gpunkt) 2 1)
" Grad")))))
;; Seitenauswahl ist OHNE Achsen-Dialog nur beim 90-Grad-Element
;; wirksam: dort folgt die Seite beim 30-Grad-Element aus der
;; Zufuehrungsachse (Nutzer-Vorgabe 2026-10-14). Sie wird darum
;; ausgegraut - und unten auch NICHT in die Pending-Queue gelegt, sonst
;; zoege die Punktfrage sie als vermeintlichen Punkt heraus und
;; erschiene gar nicht. MIT Achsen-Dialog wird die Seite dagegen immer
;; gebraucht (sie ist die AS-Variante, nicht mehr aus der Achse
;; ableitbar) und bleibt daher auch bei 30 Grad waehlbar.
(action_tile "setzen"
(if mit-achse
"(mode_tile \"winkel_90\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"winkel_30\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"seite_links\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"seite_rechts\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"pick\" (if (= (get_tile \"setzen\") \"1\") 0 1))"
"(mode_tile \"winkel_90\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"winkel_30\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"seite_links\" (if (and (= (get_tile \"setzen\") \"1\") (/= (get_tile \"winkel_30\") \"1\")) 0 1))
(mode_tile \"seite_rechts\" (if (and (= (get_tile \"setzen\") \"1\") (/= (get_tile \"winkel_30\") \"1\")) 0 1))")
(mode_tile \"seite_rechts\" (if (and (= (get_tile \"setzen\") \"1\") (/= (get_tile \"winkel_30\") \"1\")) 0 1))"))
(if (not mit-achse)
(progn
(action_tile "winkel_30"
"(mode_tile \"seite_links\" 1)
(mode_tile \"seite_rechts\" 1)")
(action_tile "winkel_90"
"(mode_tile \"seite_links\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"seite_rechts\" (if (= (get_tile \"setzen\") \"1\") 0 1))")
(mode_tile \"seite_rechts\" (if (= (get_tile \"setzen\") \"1\") 0 1))")))
(if mit-achse
(action_tile "pick"
"(setq gsetzen (get_tile \"setzen\"))
(setq gwinkel (if (= (get_tile \"winkel_30\") \"1\") \"30\" \"90\"))
(setq gseite (if (= (get_tile \"seite_rechts\") \"1\") \"2\" \"1\"))
(done_dialog 2)"))
(action_tile "accept"
"(setq gsetzen (get_tile \"setzen\"))
(setq gwinkel (if (= (get_tile \"winkel_30\") \"1\") \"30\" \"90\"))
(setq gseite (if (= (get_tile \"seite_rechts\") \"1\") \"2\" \"1\"))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
;; MIT Punkt-Button ist Code 2 belegt -> "Zurueck" nutzt 3 (wie in
;; vflw_punkt_zahl/vflw-gruppe-punkt-hoehe-impl).
(action_tile "back" (if mit-achse "(done_dialog 3)" "(done_dialog 2)"))
(setq ergebnis (start_dialog))
(unload_dialog dat)
;; Punkt-Button: Dialog ist zu, die Zeichnung hat den Fokus, getpoint
;; laeuft normal. MIT Punkt ist danach alles beisammen - der Ablauf
;; geht direkt in den "Ok Weiter"-Zweig, der Dialog wird NICHT noch
;; einmal gezeigt (Nutzer-Wunsch 2026-10-22: er stand vorher zweimal
;; da, einmal vor und einmal nach der Punktwahl - alle uebrigen
;; Angaben stehen beim Druck auf den Button ja schon fest, der Punkt
;; ist die letzte offene). OHNE Punkt (Abbruch mit ESC) wird der
;; Dialog dagegen erneut gezeigt, sonst waere nach einem Fehlklick
;; die ganze Eingabe weg.
(if (and mit-achse (= ergebnis 2))
(progn
(setq gpunkt (vfl-getpoint achse-basis (ssg-text "vfl-prompt-as-achse-p2")))
(if gpunkt (setq ergebnis 1))))
(cond
((= ergebnis 1)
(setq fertig t)
(if (= gsetzen "1")
;; Winkel-Slot wird von vfl-in-value (AS-Winkel, ueber
;; vf-frage-element-winkel) konsumiert - der erwartet direkt
;; "30"/"90", NICHT den Auswahl-Index wie beim Seite-Slot.
;; Beim 30-Grad-Element wird die Seite NICHT mitgegeben: sie ist im
;; Dialog ausgegraut und folgt aus der Zufuehrungsachse. Ein hier
;; abgelegter Wert wuerde von der nachfolgenden Punktfrage
;; faelschlich als Punkt konsumiert.
(if mit-achse
(vflw-pending-push-all (list "1" gwinkel gseite gpunkt))
;; Ohne Achsen-Dialog beim 30-Grad-Element KEINE Seite
;; mitgeben (siehe Kommentar oben).
(if (= gwinkel "30")
(vflw-pending-push-all (list "1" gwinkel))
(vflw-pending-push-all (list "1" gwinkel gseite)))
(vflw-pending-push-all (list "1" gwinkel gseite))))
(vflw-pending-push-all (list "2"))))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
((= ergebnis (if mit-achse 3 2))
(setq fertig t) (setq *vfl-wizard-abbruch-grund* "zurueck"))
;; Code 2 steht hier NUR noch fuer "Punktwahl mit ESC abgebrochen"
;; (mit Punkt ist ergebnis oben auf 1 gesetzt worden): fertig bleibt
;; nil, die Schleife zeigt den Dialog mit den gemerkten Werten
;; erneut - das ist KEIN Abbruch der ganzen Kette.
((and mit-achse (= ergebnis 2)) nil)
(t (setq fertig t) (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
)
@@ -1110,16 +1197,22 @@
;; separaten Dialog (vflw-gruppe-gf-steigend-untermodus-impl, weiter unten) -
;; der 3-Optionen-Dialog hier waere dort irrefuehrend (dritte Kachel
;; "Steigend" existiert in diesem Kontext gar nicht mehr).
(defun vflw-gruppe-gf-laenge-modus-impl ( / dat ergebnis gwahl)
;; mit-parallel: T, wenn "Parallel zum Kreisel" hier ueberhaupt baubar ist
;; (Kettenanfang MIT AS-Element UND festgelegter Zufuehrungsachse - ohne
;; Bezugsachse gibt es keinen Abstand). Sonst wird die Kachel deaktiviert,
;; statt sie anzubieten und die Antwort hinterher abzulehnen.
(defun vflw-gruppe-gf-laenge-modus-impl (mit-parallel / dat ergebnis gwahl)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_gf_laenge_modus" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text "vfl-gf-laenge-modus-header")))
(if (not mit-parallel) (mode_tile "modus_parallel" 1))
(action_tile "accept"
"(setq gwahl (cond ((= (get_tile \"modus_hoehe\") \"1\") \"2\")
((= (get_tile \"modus_steigend\") \"1\") \"3\")
((= (get_tile \"modus_qc\") \"1\") \"4\")
((= (get_tile \"modus_parallel\") \"1\") \"5\")
(t \"1\")))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
@@ -4717,6 +4810,227 @@
)
)
;; ------------------------------------------------------------------
;; "Parallel zum Kreisel weiterbauen" (5. Option der gf-laenge-modus-Frage,
;; intern "parallel"; Nutzer-Vorgabe 2026-10-21): der Regelfall an einer
;; Kreisel-Ausschleusung. Gebaut wird AS -> kurzes GF-Stueck (Standard-
;; Gefaelle) -> GF-Bogen, sodass die Kette danach PARALLEL zur
;; Zufuehrungsachse (= Kreisel-Laufrichtung) weiterlaeuft.
;;
;; EINZIGE Eingabe ist der ABSTAND, gemessen SENKRECHT zur Zufuehrungsachse
;; von KS_EIN des AS-Elements bis KS_AUS des Bogens. AS- und Bogen-Queranteil
;; sind durch die Bausteine fest; die Laenge des GF-Stuecks dazwischen ist die
;; einzige Unbekannte und folgt aus dem Abstand:
;; abstand = quer(AS) + L * sin(hz0 - hz_achse) + quer(Bogen)
;; (L = horizontale Laenge des GF-Stuecks wie bei vfl-insert-gf-segment).
;;
;; Warum hier KEIN Endpunkt geklickt wird: Zufuehrungsachse (2. Punkt) UND
;; AS-Seite stehen seit 2026-10-21 beide schon aus dem AS-Dialog fest (siehe
;; vfl-frage-as-winkel-seite-oder-achse) - die Achse gibt die KS_EIN-Richtung,
;; die Seite die AS-Variante und damit die KS_AUS-Richtung. Die Fahrtrichtung
;; nach dem AS ist dadurch bekannt, bevor irgendetwas gebaut ist.
;;
;; Bogenwahl NICHT fest verdrahtet: gesucht ist der Bogen, der den real
;; gemessenen Grundriss-Schwenk des AS-Blocks GENAU aufhebt
;; (as-turn + bogen-turn = 0, vf-element-plan-turn wie in vfl-qc-linie-mit-gf),
;; immer zur GEGENSEITE der AS-Seite. Mit den vorhandenen Bausteinen ergibt
;; das genau die Vorgabe-Tabelle: AS 30 Grad -> 30-Grad-Bogen, AS 90 Grad ->
;; 90-Grad-Bogen (AS links -> Bogen rechts und umgekehrt). Genau DARUM wird
;; gemessen statt eine Tabelle gepflegt: die Zuordnung folgt den Bloecken,
;; auch wenn ein Element anders vermessen ist als erwartet. Passt kein Bogen
;; in die Toleranz, wird das gemeldet statt schief gebaut.
;;
;; Quer-Anteile werden GEMESSEN, nicht gerechnet: AS ueber sein echtes KS_EIN
;; (der Klickpunkt ist der Block-URSPRUNG, KS_EIN liegt beim Drehteller-
;; Anschluss seitlich versetzt - siehe vfl-block-ks-punkt), der Bogen ueber
;; einen Probe-Bau, der danach wieder geloescht wird (dasselbe Prinzip und
;; derselbe Grund wie in vfl-qc-linie-mit-gf: die Bogen-Bausteine haengen an
;; einem bereits geneigten Rahmen, Messen ist zuverlaessiger als eine weitere
;; Rotationsformel). Der Probe-Bau haengt nur an der ANKUNFTS-Orientierung,
;; nicht an der Position - er liefert daher denselben Versatz wie der spaetere
;; echte Bau hinter dem GF-Stueck.
;;
;; KEIN Separator zwischen GF-Stueck und Bogen (Nutzer-Entscheidung
;; 2026-10-21) - der Abstand wird dadurch exakt getroffen.
;;
;; Die Kettenende-Frage stellt dieser Zweig SELBST, und zwar NACH dem Bau
;; (anders als die Zweige A/B/C, die sie vorab brauchen, um den ES-Fussabdruck
;; von der gepickten Laenge abzuziehen): hier gibt es keine gepickte Laenge,
;; von der etwas abzuziehen waere, und erst nach dem Bogen steht fest, wo die
;; Kette weiterlaeuft.
;;
;; Nutzt - wie vfl-qc-linie-mit-gf - die dynamisch sichtbaren Lokalvariablen
;; des Aufrufers vf-linienzug-modus (frame, p-aktuell, as-seite, as-vorhanden,
;; kettenanfang, fertig, es-seite, anzahl-gf, letzter-typ,
;; gf-kettenende-antwort).
;;
;; Die beiden Rechenkerne stehen bewusst als eigene, zeichnungsfreie
;; Funktionen davor (vfl-par-bogen-waehlen / vfl-par-gf-laenge) - so sind sie
;; in tests/test_unit.lsp ohne Zeichnung pruefbar (tu-tests-vfl-parallel).
;; Welcher Bogen hebt den AS-Schwenk auf? kandidaten ist eine Liste
;; (winkel . plan-turn) der real vorhandenen Bogen-Varianten EINER Seite;
;; gesucht ist der mit dem kleinsten |as-turn + plan-turn| innerhalb von tol.
;; Rueckgabe: Winkel (Int) oder nil, wenn keiner passt.
(defun vfl-par-bogen-waehlen (as-turn kandidaten tol / best best-diff diff kand)
(setq best nil best-diff nil)
(foreach kand kandidaten
(setq diff (abs (+ as-turn (cdr kand))))
(if (or (null best-diff) (< diff best-diff))
(setq best-diff diff best (car kand))))
(if (and best (<= best-diff tol)) best nil))
;; Horizontale Laenge des GF-Stuecks zwischen AS und Bogen.
;; abstand = gewuenschter Abstand (Betrag, senkrecht zur Zufuehrungsachse)
;; perp-as = Querversatz AS-KS_EIN -> AS-KS_AUS (vorzeichenbehaftet)
;; perp-bog = Querversatz des Bogens (KS_EIN -> KS_AUS, vorzeichenbehaftet)
;; k = sin(hz0 - hz-achse), Querversatz je Laengeneinheit GF
;; Die Seite steht ueber die AS-Variante schon fest - sie steckt im Vorzeichen
;; von k. Der Nutzer tippt deshalb einen reinen Betrag, das Ziel-Vorzeichen
;; wird daraus abgeleitet. Rueckgabe: Laenge (kann <= 0 sein, wenn der Abstand
;; kleiner ist als der feste Anteil von AS + Bogen) oder nil, wenn das AS gar
;; nicht aus der Achse schwenkt (k ~ 0, keine Loesung).
(defun vfl-par-gf-laenge (abstand perp-as perp-bog k / s)
(if (< (abs k) 1e-6)
nil
(progn
(setq s (if (> k 0.0) 1.0 -1.0))
(/ (- (* (abs abstand) s) perp-as perp-bog) k))))
(defun vfl-gf-parallel-kreisel ( / as-w as-turn bogen-seite bogen-winkel
kandidaten tol w
abstand abstand-min
checkpoint as-ent p-ein hz-a rad-a nx ny hz0 k
perp-as perp-bogen l-gf perp-ist rad3
acc-gfbogen-save acc-sep-save basis-frame
probe-checkpoint probe-frame dxp dyp
hz-nach erg-bogen)
(if (or (not kettenanfang) (not as-vorhanden) (null *vfl-as-basis-hz*))
(vfl-meldung (ssg-text "vfl-par-nur-kettenanfang"))
(progn
(setq as-w (vfl-as-winkel))
(setq as-turn (vf-element-plan-turn (strcat "AS_Element_" as-w "_" as-seite)))
;; Bogen zur Gegenseite der AS-Seite; Winkel aus den real vorhandenen
;; Varianten, der den AS-Schwenk am besten aufhebt.
(setq bogen-seite (if (= as-seite "rechts") "links" "rechts"))
(setq kandidaten '())
(foreach w *vfk-gf-bogen-winkel*
(setq kandidaten
(cons (cons w (vf-element-plan-turn (gf-bogen-blockname w bogen-seite)))
kandidaten)))
(setq tol 2.0) ; gleiche Toleranz wie in vfl-qc-linie-mit-gf
(setq bogen-winkel (vfl-par-bogen-waehlen as-turn (reverse kandidaten) tol))
(if (null bogen-winkel)
(vfl-meldung (ssg-textf "vfl-par-kein-bogen"
(list (rtos as-turn 2 1) bogen-seite)))
(progn
;; --- Abstand abfragen (VOR dem Bau: ein Abbruch laesst dann
;; garantiert nichts Halbfertiges stehen) ---
(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
(setq checkpoint (ssg-lastent-ohne-attribute))
;; --- AS bauen: KS_EIN-Achse auf die Zufuehrungsachse ---
(setq frame (vfl-insert-as-element-auf-achse "GF" p-aktuell
*vfl-as-basis-hz* as-seite))
(setq as-ent (entlast))
(setq p-ein (if as-ent
(vfl-block-ks-punkt (vlax-ename->vla-object as-ent) "KS_EIN")
nil))
(if (null p-ein) (setq p-ein p-aktuell))
(setq hz-a *vfl-as-basis-hz*)
(setq rad-a (* (float hz-a) (/ pi 180.0)))
(setq nx (- (sin rad-a)) ny (cos rad-a)) ; Normale zur Zufuehrungsachse
(setq hz0 (car (frame->hz-winkel frame))) ; Fahrtrichtung nach dem AS
(setq perp-as (+ (* (- (car (car frame)) (car p-ein)) nx)
(* (- (cadr (car frame)) (cadr p-ein)) ny)))
;; --- Bogen probeweise bauen, Querversatz messen, wieder weg ---
;; WICHTIG - die Probe muss auf GENAU dem Rahmen sitzen, auf dem
;; der echte Bogen spaeter steht, sonst misst sie eine andere
;; Lage. Das ist NICHT der Ausgangsrahmen des AS-Elements: der
;; traegt die reale Neigung des AS-KS_AUS, waehrend der Bogen
;; hinter dem GF-Stueck auf dessen IDEALISIERTEM Rahmen sitzt
;; (make-frame-from-dir + hz-winkel->xu mit genau hz0 und dem
;; Standard-Gefaellewinkel, siehe vfl-insert-gf-segment). Realer
;; Befund 2026-10-22 (BricsCAD): auf dem AS-Rahmen gemessen fiel
;; der Probe-Bogen 31.3 mm statt 22.6 mm - die daraus falsche
;; Verdrehung liess den Abstand um 0.5 mm danebenliegen. Mit dem
;; idealisierten Rahmen sind Probe und echter Bau per
;; Konstruktion identisch.
;; vfl-insert-gf-bogen-block zaehlt intern mit (GF_Bogen_L/R_xx) -
;; die Probe darf diese Zaehler nicht verfaelschen.
(setq basis-frame
(make-frame-from-dir (car frame)
(hz-winkel->xu hz0 (fix *vfk-gefaelle-winkel*))))
(setq acc-gfbogen-save *vfl-acc-gfbogen* acc-sep-save *vfl-acc-separator*)
(setq probe-checkpoint (ssg-lastent-ohne-attribute))
(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))))
(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)))
;; --- GF-Laenge aus dem Abstand loesen (reiner Kern) ---
(setq k (sin (* (- hz0 hz-a) (/ pi 180.0))))
(setq l-gf (vfl-par-gf-laenge abstand perp-as perp-bogen k))
(if (null l-gf)
(progn
(vfl-meldung (ssg-text "vfl-par-kein-versatz"))
(vfl-loesche-seit checkpoint))
(progn
;; Kleinster erreichbarer Abstand = der von AS und Bogen
;; allein (GF-Stueck = 0).
(setq abstand-min (abs (+ perp-as perp-bogen)))
(if (< l-gf 1.0)
(progn
(vfl-meldung (ssg-textf "vfl-par-zu-klein"
(list (rtos (abs abstand) 2 1) (rtos abstand-min 2 1))))
(vfl-loesche-seit checkpoint))
(progn
(setq rad3 (* (float (fix *vfk-gefaelle-winkel*)) (/ pi 180.0)))
(setq frame (vfl-insert-gf-segment (car frame) hz0 l-gf
(fix *vfk-gefaelle-winkel*)))
(vfl-acc-gf-seg (/ l-gf (cos rad3)) (fix *vfk-gefaelle-winkel*))
(setq anzahl-gf (1+ anzahl-gf))
(setq frame (vfl-insert-gf-bogen-block frame bogen-winkel bogen-seite))
(setq p-aktuell (car frame))
(setq letzter-typ "GF")
(setq perp-ist (abs (+ (* (- (car p-aktuell) (car p-ein)) nx)
(* (- (cadr p-aktuell) (cadr p-ein)) ny))))
(princ (ssg-textf "vfl-par-erfolg"
(list (rtos l-gf 2 1) (itoa bogen-winkel) bogen-seite
(rtos perp-ist 2 1))))
;; --- Kettenende/ES (siehe Kopfkommentar: hier NACH dem Bau) ---
(setq hz-nach (car (frame->hz-winkel frame)))
(setq gf-kettenende-antwort (vfl-frage-ist-kettenende))
(cond
((= gf-kettenende-antwort "1")
(setq es-seite (vfl-frage-es-seite nil))
;; Uebergangsbogen pruefen (No-Op, wenn die Kette hier
;; ohnehin auf der Fallend-Basis ankommt) - dieselbe
;; Absicherung wie im gemeinsamen Abschluss der Zweige
;; A/B/C.
(setq erg-bogen (vfl-gf-bogen-uebergang frame *vfk-gefaelle-winkel*))
(setq frame (car erg-bogen))
(setq frame (vfl-insert-es-element "GF" frame hz-nach
*vfk-gefaelle-winkel* (caddr (car frame)) es-seite))
(setq p-aktuell (car frame))
(setq fertig t))
((= gf-kettenende-antwort "2") (setq fertig t))
)
)
)
)
)
)
)
)
)
)
)
)
;; ------------------------------------------------------------------
;; "QC Linie mit GF und U-Form" (Option 5 der gf-laenge-modus-Frage,
;; Nutzer-Vorgabe 2026-09-13, nach mehreren Praezisierungsrunden - inkl. echter
@@ -5227,7 +5541,7 @@
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-es-nein-zielpunkt"))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-es-setzen-frage")))
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-es-setzen-frage" nil)))
(setq es-antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-es-nein-zielpunkt")) 1
"vfl-es-setzen-frage"))
@@ -6589,10 +6903,18 @@
(progn
;; BASISLINIEN-Modus: das AS gehoert mit seinem KS_EIN auf die
;; Zufuehrungsachse (*vfl-as-basis-hz*), nicht mit dem KS_AUS auf die
;; Kettenrichtung. Die Seite ergibt sich aus der Lage des naechsten
;; Kettenpunkts zur Basislinie (Nutzer-Vorgabe 2026-10-14): der liegt in
;; Richtung hz-neu, deltaL entfernt.
;; Kettenrichtung.
;;
;; Die Seite kommt seit 2026-10-21 normalerweise AUS DEM DIALOG
;; (*vfl-as-seite-explizit*) - sie ist die AS-Variante und darf dann
;; nicht mehr ueberschrieben werden. Nur beim Replay eines
;; Alt-Achsen-Journals (30 Grad, 2026-10-14..10-20) wurde sie gar nicht
;; gefragt; dort ergibt sie sich wie bisher aus der Lage des naechsten
;; Kettenpunkts zur Basislinie: der liegt in Richtung hz-neu, deltaL
;; entfernt.
(if *vfl-as-basis-hz*
(progn
(if (not *vfl-as-seite-explizit*)
(progn
(setq rad (* (float hz-neu) (/ pi 180.0)))
(setq ziel (list (+ (car p-aktuell) (* deltaL (cos rad)))
@@ -6607,7 +6929,7 @@
ziel))
;; Masse auf die jetzt bekannte Seite umstellen (Winkel steht schon).
(vf-set-as-masse (vfl-as-winkel) as-seite)
(princ (ssg-textf "vfl-as-seite-ermittelt" (list as-seite)))
(princ (ssg-textf "vfl-as-seite-ermittelt" (list as-seite)))))
;; KS_EIN auf die Zufuehrungsachse legen - NICHT KS_AUS auf hz-neu.
(setq frame (vfl-insert-as-element-auf-achse "GF" p-aktuell
*vfl-as-basis-hz* as-seite)))
@@ -6695,72 +7017,57 @@
(vf-set-as-masse *vfl-as-winkel* as-seite) ; Masse fuer Variante
as-seite)
;; AS-Winkel abfragen und danach ENTWEDER die Seite ODER - nur beim 30-Grad-
;; Element - eine Zufuehrungsachse (Nutzer-Vorgabe 2026-10-14: "bitte erstmal
;; nur fuer 30 Grad").
;; AS-Winkel abfragen, danach die Seite UND - fuer BEIDE Winkel - eine
;; optionale Zufuehrungsachse (Nutzer-Vorgabe 2026-10-21; bis dahin galt die
;; Achse nur fuer das 30-Grad-Element und ERSETZTE dort die Seitenfrage).
;;
;; Warum erst NACH dem Winkel: die Achsenfrage gilt nur fuer AS_Element_30, der
;; Winkel muss also schon feststehen. Der Kettenstartpunkt bleibt davon
;; unberuehrt - er ist und bleibt der echte Sitz des AS-Elements; die Achse
;; entsteht aus ihm plus EINEM weiteren Punkt, der nur die Richtung gibt.
;; Warum beides und warum in dieser Reihenfolge: die zwei Angaben beschreiben
;; verschiedene Dinge und legen das AS erst ZUSAMMEN vollstaendig fest -
;; 2. Punkt -> Richtung der KS_EIN-Achse (die Zufuehrung, z.B. die
;; Kreisel-Laufrichtung); der Startpunkt bleibt der Sitz des
;; AS-Elements, der zweite Punkt gibt nur die Richtung.
;; links/rechts -> AS-Variante und damit die KS_AUS-Richtung, also wohin die
;; Kette nach dem Ausschleusen laeuft.
;; Damit steht die Kettenrichtung schon vor dem ersten Streckenklick fest -
;; Voraussetzung fuer "Parallel zum Kreisel" (vfl-gf-parallel-kreisel), das
;; gar keinen Endpunkt mehr klickt, sondern nur noch einen Abstand bekommt.
;; Die Achse ist optional: ohne Punkt laeuft alles wie frueher ueber die
;; Richtung des naechsten Klicks.
;;
;; JOURNAL - deswegen ist die Reihenfolge so gewaehlt:
;; 90 Grad : [STR winkel] [STR seite] <- Eintrag fuer Eintrag wie bisher
;; 30 + Seite : [STR winkel] [STR seite] <- ebenfalls wie bisher
;; 30 + Achse : [STR winkel] [PT p2]
;; Die Modus-Wahl selbst wird BEWUSST NICHT journalisiert: sie ist aus der Art
;; des naechsten Eintrags eindeutig rekonstruierbar (PT = Achse, STR = Seite).
;; Dadurch bleiben alle aelteren Ketten ohne Sonderbehandlung editierbar.
;; JOURNAL - Eintragsfolge an dieser Stelle:
;; neu : [STR winkel] [STR seite] [PT p2] (ohne Achse: [NIL])
;; 30 + Achse alt : [STR winkel] [PT p2] (2026-10-14..10-20)
;; ganz alt : [STR winkel] [STR seite]
;; Welche Fragen gestellt werden, wird BEWUSST NICHT journalisiert: es ist aus
;; der Art des jeweils naechsten Eintrags eindeutig rekonstruierbar (PT direkt
;; nach dem Winkel = Alt-Achse ohne Seite; PT/NIL nach der Seite = neue Achse;
;; alles andere = Altbestand ohne Achse). Dadurch bleiben alle aelteren Ketten
;; ohne Sonderbehandlung editierbar. Ein neues Glied beginnt immer mit seiner
;; Menue-Antwort (STR) bzw. - im erzwungenen QC-Modus - mit einer Richtung
;; (REAL); ein PT/NIL an dieser Stelle kann daher nur die Achse sein.
;;
;; Setzt *vfl-as-basis-hz*/-p1 (nil, wenn keine Achse gewaehlt wurde).
;; Rueckgabe: Seite. Im Achsen-Modus vorlaeufig "links" - die endgueltige Seite
;; folgt aus der Lage des naechsten Kettenpunkts und wird in
;; vfl-kettenanfang-baustein gesetzt (Laengsverbrauch ist seitengleich, nur das
;; Vorzeichen des Querversatzes dreht).
;; Setzt *vfl-as-basis-hz*/-p1 (nil, wenn keine Achse gewaehlt wurde) und
;; *vfl-as-seite-explizit* (T, wenn die Seite wirklich gefragt wurde - nur
;; dann darf vfl-kettenanfang-baustein sie NICHT mehr aus der Lage des
;; naechsten Punkts ueberschreiben).
;; Rueckgabe: Seite. Beim Alt-Achsen-Journal vorlaeufig "links" - die
;; endgueltige Seite folgt dort aus der Lage des naechsten Kettenpunkts
;; (Laengsverbrauch ist seitengleich, nur das Vorzeichen des Querversatzes
;; dreht).
(defun vfl-frage-as-winkel-seite-oder-achse (seiten-headerkey startpunkt
/ antwort as-seite modus p2)
(setq *vfl-as-basis-hz* nil *vfl-as-basis-p1* nil)
/ antwort as-seite p2 mit-seite mit-achse)
(setq *vfl-as-basis-hz* nil *vfl-as-basis-p1* nil *vfl-as-seite-explizit* nil)
(princ (ssg-text "vf-winkel-aus-header"))
(princ (ssg-text "vf-winkel-90"))
(princ (ssg-text "vf-winkel-30"))
(setq *vfl-as-winkel*
(vfl-in-value "STR"
(function (lambda () (vf-frage-element-winkel "vf-winkel-aus-header")))))
;; Beim 30-Grad-Element wird die Seite NICHT gefragt (im Gruppendialog
;; ausgegraut) - stattdessen direkt die Zufuehrungsachse ueber einen zweiten
;; Punkt. Es gibt also keine Modus-Frage: 30 Grad = Achse, 90 Grad = Seite.
;;
;; AUSNAHME Replay: aeltere 30-Grad-Ketten wurden noch MIT Seitenfrage
;; gebaut, ihr Journal hat an dieser Stelle ein "STR" statt eines "PT".
;; Steht dort ein STR, ist es ein Altbestand und es wird wie frueher die
;; Seite gelesen - sonst liefe die Queue aus dem Takt.
(setq modus (if (= *vfl-as-winkel* "30") "1" "2"))
(if (and (= modus "1") *vfl-replay-queue* (/= (vfl-replay-peek-art) "PT"))
(setq modus "2"))
(if (= modus "1")
(progn
;; Der AS-Gruppendialog legt beim 30-Grad-Element bewusst KEINE Seite in
;; die Pending-Queue (siehe vflw-gruppe-as-impl) - die Queue ist hier
;; also leer und der folgende vfl-in-point fragt wirklich nach dem Punkt.
;; Zweiter Punkt gibt NUR die Richtung der Zufuehrung; der Startpunkt
;; bleibt der Sitz des AS-Elements.
(setq p2 (vfl-in-point startpunkt (ssg-text "vfl-prompt-as-achse-p2")))
(if (or (null p2)
(and (< (abs (- (car p2) (car startpunkt))) 1e-9)
(< (abs (- (cadr p2) (cadr startpunkt))) 1e-9)))
(progn
;; Keine brauchbare Richtung -> auf die Seitenfrage zurueckfallen.
;; (Der PT-Eintrag steht dann trotzdem im Journal und wird beim
;; Replay wieder als "Achse" gelesen - deshalb hier KEIN stiller
;; Rueckfall, sondern Abbruch.)
(princ (ssg-text "vfl-basislinie-punkte-gleich"))
(exit))
(progn
(setq *vfl-as-basis-p1* startpunkt)
(setq *vfl-as-basis-hz* (vfl-planar-hz startpunkt p2))
(princ (ssg-textf "vfl-basislinie-info" (list (rtos *vfl-as-basis-hz* 2 1))))
(setq as-seite "links"))) ; vorlaeufig, s.o.
)
;; --- Seite (links/rechts) ---
;; Uebersprungen NUR beim Replay eines Alt-Achsen-Journals (30 Grad,
;; 2026-10-14..10-20): dort steht direkt hinter dem Winkel der Achsenpunkt.
(setq mit-seite (not (and *vfl-replay-queue* (= (vfl-replay-peek-art) "PT"))))
(if mit-seite
(progn
(princ (ssg-text seiten-headerkey))
(princ (ssg-text "gf-seite-links"))
@@ -6768,7 +7075,34 @@
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "gf-seite-links") (ssg-text "gf-seite-rechts")) 1
seiten-headerkey))
(setq as-seite (if (= antwort "2") "rechts" "links"))))
(setq as-seite (if (= antwort "2") "rechts" "links"))
(setq *vfl-as-seite-explizit* t))
(setq as-seite "links")) ; vorlaeufig, s.o.
;; --- Zufuehrungsachse (2. Punkt, optional) ---
;; Beim Replay nur lesen, wenn an dieser Stelle wirklich ein Achsen-Eintrag
;; steht (PT = Punkt, NIL = bewusst ohne Achse gebaut) - sonst ist es ein
;; Altbestand ohne diese Frage und der naechste Eintrag gehoert schon zum
;; ersten Glied.
(setq mit-achse
(if *vfl-replay-queue*
(if (member (vfl-replay-peek-art) '("PT" "NIL")) t nil)
t))
(if mit-achse
(progn
(setq p2 (vfl-in-point startpunkt (ssg-text "vfl-prompt-as-achse-p2")))
(if (and p2
(or (> (abs (- (car p2) (car startpunkt))) 1e-9)
(> (abs (- (cadr p2) (cadr startpunkt))) 1e-9)))
(progn
(setq *vfl-as-basis-p1* startpunkt)
(setq *vfl-as-basis-hz* (vfl-planar-hz startpunkt p2))
(princ (ssg-textf "vfl-basislinie-info" (list (rtos *vfl-as-basis-hz* 2 1)))))
;; Kein (brauchbarer) Punkt -> keine Achse. KEIN Abbruch mehr: die
;; Achse ist seit 2026-10-21 optional, die Kette laeuft dann wie
;; frueher ueber die Richtung des naechsten Klicks. Der Journal-
;; Eintrag (PT bzw. NIL) steht trotzdem und wird beim Replay wieder
;; genauso gelesen.
(princ (ssg-text "vfl-basislinie-ohne")))))
(vf-set-as-masse *vfl-as-winkel* as-seite)
as-seite)
@@ -7062,7 +7396,7 @@
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)
rad3-sep sep-eindx-add sep-eindz-add gf-parallel-moeglich)
;; "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-*-
@@ -7162,7 +7496,10 @@
(setq as-vorhanden nil))
(progn
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-as-setzen-frage")))
;; startpunkt = Zufuehrungsachse (2. Punkt) gehoert hier in DENSELBEN
;; Dialog wie Winkel/Seite (Nutzer-Vorgabe 2026-10-21); der Punkt ist
;; zugleich die Gummiband-Basis der Punktwahl.
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-as-setzen-frage" startpunkt)))
(princ (ssg-text "vfl-as-setzen-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-as-nein"))
@@ -7175,10 +7512,13 @@
;; werden - sonst wuerde ein WINKEL_AS-Attribut den Wert der VORHERIGEN Kette
;; tragen, wenn diesmal kein AS gewaehlt wird.
(setq *vfl-as-winkel* nil *vfl-es-winkel* nil)
;; Winkel, dann Seite ODER (nur bei 30 Grad) eine Zufuehrungsachse - siehe
;; vfl-frage-as-winkel-seite-oder-achse. Im Achsen-Modus entfaellt die
;; Seitenfrage; die Seite folgt aus der Lage des naechsten Kettenpunkts und
;; wird in vfl-kettenanfang-baustein bestimmt.
;; Gilt genauso fuer die Achsen-Globals: ohne AS-Element bleibt sonst die
;; Zufuehrungsachse der VORHERIGEN Kette stehen.
(setq *vfl-as-basis-hz* nil *vfl-as-basis-p1* nil *vfl-as-seite-explizit* nil)
;; Winkel, dann Seite UND eine optionale Zufuehrungsachse (2. Punkt) - siehe
;; vfl-frage-as-winkel-seite-oder-achse. Beide zusammen legen AS-Lage und
;; Kettenrichtung fest; ohne Achse laeuft alles wie frueher ueber die
;; Richtung des naechsten Klicks.
(if as-vorhanden
(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),
@@ -7474,18 +7814,38 @@
(setq *vfl-forciere-qc-modus* nil)
)
(progn
(if (vfl-wizard-aktiv) (vl-catch-all-apply 'vflw-gruppe-gf-laenge-modus-impl nil))
;; "Parallel zum Kreisel" braucht AS-Element UND Zufuehrungsachse
;; als Bezug - sonst gibt es keinen Abstand, zu dem parallel
;; gebaut werden koennte.
(setq gf-parallel-moeglich
(and kettenanfang as-vorhanden *vfl-as-basis-hz*))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-gf-laenge-modus-impl (list gf-parallel-moeglich)))
(princ (ssg-text "vfl-gf-laenge-modus-header"))
(princ (ssg-text "vfl-gf-laenge-modus-opt-endpunkt"))
(princ (ssg-text "vfl-gf-laenge-modus-opt-hoehe"))
(princ (ssg-text "vfl-gf-laenge-modus-opt-steigend"))
(princ (ssg-text "vfl-gf-laenge-modus-opt-qc"))
(if gf-parallel-moeglich (princ (ssg-text "vfl-gf-laenge-modus-opt-parallel")))
(setq gf-laenge-modus (vfl-menu (ssg-text "prompt-wahl-1-2")
(if gf-parallel-moeglich
(list (ssg-text "vfl-gf-laenge-modus-opt-endpunkt")
(ssg-text "vfl-gf-laenge-modus-opt-hoehe")
(ssg-text "vfl-gf-laenge-modus-opt-steigend")
(ssg-text "vfl-gf-laenge-modus-opt-qc")) 1
(ssg-text "vfl-gf-laenge-modus-opt-qc")
(ssg-text "vfl-gf-laenge-modus-opt-parallel"))
(list (ssg-text "vfl-gf-laenge-modus-opt-endpunkt")
(ssg-text "vfl-gf-laenge-modus-opt-hoehe")
(ssg-text "vfl-gf-laenge-modus-opt-steigend")
(ssg-text "vfl-gf-laenge-modus-opt-qc"))) 1
"vfl-gf-laenge-modus-header"))
;; Menue-Antwort "5" (die neue 5. Option) auf einen SPRECHENDEN
;; internen Wert abbilden: "5" ist intern bereits durch die
;; erzwungene U-Form belegt (*vfl-forciere-qc-modus*, Linienzug-
;; Modus 5), die diese Frage ueberspringt und daher nie eine "5"
;; ins Journal schreibt. Journalisiert wird weiterhin die rohe
;; Menue-Antwort; die Abbildung passiert beim Replay identisch.
(if (= gf-laenge-modus "5") (setq gf-laenge-modus "parallel"))
)
)
;; Option 4 ("QC Linie mit GF"): eigenstaendiger, vollstaendig
@@ -7500,6 +7860,11 @@
(cond
((= gf-laenge-modus "4") (vfl-qc-linie-mit-gf))
((= gf-laenge-modus "5") (vfl-qc-linie-mit-gf-uform))
;; "Parallel zum Kreisel": ebenfalls eigenstaendig - baut AS + GF +
;; Bogen aus EINEM Abstandswert und stellt die Kettenende-Frage
;; selbst (sie kommt hier NACH dem Bau, nicht davor: erst nach dem
;; Bogen steht fest, wo die Kette weiterlaeuft).
((= gf-laenge-modus "parallel") (vfl-gf-parallel-kreisel))
(t
(progn
;; NEU: Kettenende/ES JETZT fragen - VOR der Laengen-/Gefaelle-
@@ -8250,7 +8615,7 @@
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-es-nein-zielpunkt"))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-es-setzen-frage")))
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-es-setzen-frage" nil)))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-es-nein-zielpunkt")) 1
"vfl-es-setzen-frage"))
@@ -10236,7 +10601,7 @@
"vfl-prompt-hoehe-startpunkt" "gf-status-abgebrochen" "Starthoehe (Z, mm):"))
(setq start-hoehe (caddr startpunkt))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-as-setzen-frage")))
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-as-setzen-frage" nil)))
(princ (ssg-text "vfl-as-setzen-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-m3-as-nein"))
@@ -10257,7 +10622,7 @@
"vfl-m3-prompt-zielhoehe" "gf-status-abgebrochen" "Zielhoehe (Z, mm):"))
(setq end-hoehe (caddr endpunkt))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-es-setzen-frage")))
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-es-setzen-frage" nil)))
(princ (ssg-text "vfl-es-setzen-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-es-nein-zielpunkt"))
+19 -2
View File
@@ -274,7 +274,18 @@
((and (= (type v) 'STR) (= (strcase v) "AUTO")) (vsp-raus "NIL" nil))
(T (vsp-zahl "REAL" v "eingang_hoehe"))))
(defun vsp-praeambel (kopf / punkt)
;; Praeambel-Tokens: PT(Startpunkt) REAL(Starthoehe) STR(AS ja/nein)
;; [STR(Winkel) STR(Seite) (PT|NIL)(Zufuehrungsachse)].
;;
;; Das Achsen-Feld "as_achse" ist OPTIONAL (Gegenstueck zu
;; vfl-frage-as-winkel-seite-oder-achse und zu praeambel() in
;; lib/vf_linienzug_grammatik.py):
;; [x, y, z] -> PT (Zufuehrungsachse ueber einen zweiten Punkt)
;; "ohne" -> NIL (bewusst ohne Achse gebaut)
;; fehlt -> gar kein Token = Journalformat VOR 2026-10-21. Das replayt
;; unveraendert; die Kettenrichtung kommt dann wie frueher aus
;; dem naechsten Klick.
(defun vsp-praeambel (kopf / punkt achse)
(setq *vsp-wo* "start")
(setq punkt (ssg-val kopf "start_punkt"))
(if (and punkt (listp punkt) (>= (length punkt) 3))
@@ -287,7 +298,13 @@
(if (vsp-ja-p (ssg-val kopf "as"))
(progn
(vsp-wert-str (ssg-val kopf "as_winkel") "as_winkel")
(vsp-code-raus *vsp-tab-seite* (ssg-val kopf "as_seite") "as_seite"))))
(vsp-code-raus *vsp-tab-seite* (ssg-val kopf "as_seite") "as_seite")
(setq achse (ssg-val kopf "as_achse"))
(cond
((and achse (listp achse) (>= (length achse) 3))
(vsp-raus "PT" (mapcar 'float achse)))
((equal achse "ohne") (vsp-raus "NIL" nil))
))))
;; naechste: Felder der NAECHSTEN Top-Level-Sektion (oder nil) - wird nur fuer
;; "Linie-GF" gebraucht (siehe dort). *vsp-es-verbraucht* (Ausgabe-Flag, vom
+80
View File
@@ -181,6 +181,80 @@ vflw_as : dialog {
}
}
// Wie vflw_as, aber ZUSAETZLICH mit der Zufuehrungsachse (2. Punkt) in
// DEMSELBEN Bildschirm - nur fuer das AS-Element am Kettenanfang von Modus 1
// (Nutzer-Vorgabe 2026-10-21: "Achse und links/rechts gehoeren in ein
// Fenster", beide zusammen legen AS-Lage UND Kettenrichtung fest: der 2.
// Punkt gibt die KS_EIN-Achse, links/rechts die AS-Variante und damit die
// KS_AUS-Richtung). Eigener Dialog statt einer dauerhaft ausgegrauten
// Punkt-Zeile in vflw_as - denselben Dialog nutzt auch das ES-Element am
// Kettenende, wo es gar keine Zufuehrungsachse gibt (gleiche Begruendung wie
// bei vflw_winkel_seite vs. vflw_winkel_seite_30_90).
// Punktwahl-Zeile nach dem Muster von vflw_punkt_zahl: der Button schliesst
// den Dialog (done_dialog 2), danach laeuft (getpoint) auf der
// Kommandozeile, danach wird der Dialog neu gezeigt - darum nutzt "back"
// hier Code 3.
vflw_as_achse : dialog {
label = "VarioFoerderer Assistent";
: column {
: text {
key = "kopf";
label = "";
width = 60;
}
: toggle {
key = "setzen";
label = "Ja, setzen";
}
: boxed_radio_column {
label = "Winkel:";
key = "winkel";
: radio_button { key = "winkel_90"; label = "90 Grad"; value = "1"; }
: radio_button { key = "winkel_30"; label = "30 Grad"; }
}
: boxed_radio_column {
label = "Seite (AS 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):";
// 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).
: text {
key = "start_anzeige";
label = "";
width = 46;
}
: row {
: button {
key = "pick";
label = "2. Punkt waehlen...";
fixed_width = true;
width = 20;
}
: text {
key = "pt_anzeige";
label = "(ohne Achse: Richtung aus dem naechsten Klick)";
width = 46;
}
}
}
}
: row {
alignment = centered;
: button { key = "back"; label = "Zurueck"; fixed_width = true; width = 12; }
spacer;
: button { key = "accept"; label = "Ok Weiter"; is_default = true; fixed_width = true; width = 12; }
spacer;
: button { key = "cancel"; label = "Abbrechen"; is_cancel = true; fixed_width = true; width = 12; }
}
}
// Winkel (30/60/90) + Seite - fuer GF-Bogen, wo alle drei Winkel geometrisch
// gueltig sind. Fuer AS-/ES-Element (wo 60 Grad NIE gueltig ist) stattdessen
// vflw_winkel_seite_30_90 verwenden (siehe vflw-gruppe-winkel-seite-impl/
@@ -402,6 +476,12 @@ vflw_gf_laenge_modus : dialog {
: radio_button { key = "modus_hoehe"; label = "Zielhoehe festlegen (Laenge wird berechnet)"; }
: radio_button { key = "modus_steigend"; label = "Steigende GF (Sonderfall, bis 3 Grad)"; }
: radio_button { key = "modus_qc"; label = "QC Linie mit GF (automatische Auf-Ab-Berechnung)"; }
// Nur am Kettenanfang mit AS-Element UND festgelegter Zufuehrungsachse
// waehlbar - sonst fehlt die Bezugsachse fuer den Abstand. Wird in
// vflw-gruppe-gf-laenge-modus-impl per mode_tile deaktiviert (anders
// als eine NIE waehlbare Kachel ist diese kontextabhaengig, siehe
// "sepnach" in vflw_sep_gf).
: radio_button { key = "modus_parallel"; label = "Parallel zum Kreisel weiterbauen (Abstand)"; }
}
}
+57
View File
@@ -105,6 +105,63 @@ Zwischen-Etappe, es geht in derselben VF-Einheit weiter (Vario-Kurve,
weiteres gerades Stueck, Auf/Ab). "Ja" beendet die VF-Einheit mit einer
Motorstation - optional zusaetzlich mit ES-Element (echtes Kettenende).
### 3a. Neu seit 2026-10-21: Zufuehrungsachse und "Parallel zum Kreisel"
Die Tabelle oben zitiert eine aeltere Aufzeichnung. Zwei Dinge sind seitdem
dazugekommen (aeltere Ketten bleiben unveraendert editierbar - das Journal
erkennt am Eintragstyp, welche Fragen damals gestellt wurden):
**Zufuehrungsachse (2. Punkt) - jetzt bei BEIDEN AS-Winkeln.** Nach Winkel
und Seite (Eintrag 4/5) fragt der AS-Dialog zusaetzlich einen zweiten Punkt
ab; im Assistenten stehen alle drei Angaben in EINEM Fenster
(`vflw_as_achse`). Die beiden Angaben beschreiben verschiedene Dinge und
legen das AS erst zusammen vollstaendig fest:
| Angabe | legt fest |
|---|---|
| 2. Punkt | Richtung der KS_EIN-Achse = die Zufuehrung, z.B. die Kreisel-Laufrichtung. Der Startpunkt bleibt der Sitz des AS-Elements, der zweite Punkt gibt nur die Richtung. |
| links/rechts | AS-Variante und damit die KS_AUS-Richtung, also wohin die Kette nach dem Ausschleusen laeuft. |
Der Dialog zeigt den Startpunkt mit an (er ist der Sitz des AS-Elements UND
der Ausgangspunkt der Achse); waehrend der Punktwahl haengt das Gummiband an
ihm. Mit dem gesetzten Punkt ist der Dialog fertig und schliesst direkt - die
ermittelte Achsrichtung meldet die Konsole. Nur ein mit ESC abgebrochener
Pick zeigt den Dialog noch einmal (mit den vorher gewaehlten Werten), damit
nach einem Fehlklick nichts verloren geht.
Die Achse ist **optional**: ohne Punkt laeuft alles wie frueher ueber die
Richtung des naechsten Klicks. Frueher ersetzte die Achse beim 30-Grad-
Element die Seitenfrage (die Seite wurde aus der Lage des naechsten Punkts
erraten); das gilt nur noch beim Nachbau alter Ketten.
**"Parallel zum Kreisel weiterbauen"** - 5. Option der Frage "Wie soll die
Laenge dieser Gefaellestrecke bestimmt werden?" (Eintrag 30). Sie erscheint
nur am Kettenanfang mit AS-Element UND festgelegter Zufuehrungsachse, weil
sie beides als Bezug braucht. Gebaut wird AS -> kurzes GF-Stueck -> GF-Bogen,
sodass die Kette danach parallel zur Zufuehrungsachse weiterlaeuft. Einzige
Eingabe ist der **Abstand**, gemessen senkrecht zur Zufuehrungsachse von
KS_EIN des AS bis KS_AUS des Bogens; die Laenge des GF-Stuecks dazwischen
wird daraus berechnet. Ein Separator zwischen GF-Stueck und Bogen wird
bewusst NICHT gesetzt, damit der Abstand exakt getroffen wird. Der Bogen wird
nicht geraten, sondern so gewaehlt, dass er den gemessenen Grundriss-Schwenk
des AS-Blocks genau aufhebt - immer zur Gegenseite der AS-Seite:
| AS | Bogen |
|---|---|
| 30 Grad links | 30 Grad rechts |
| 30 Grad rechts | 30 Grad links |
| 90 Grad links | 90 Grad rechts |
| 90 Grad rechts | 90 Grad links |
Diese Tabelle ist das ERGEBNIS der Messung, keine gepflegte Zuordnung im
Code - sie bleibt auch dann richtig, wenn ein Element anders vermessen ist
als hier erwartet.
Direkt nach dem Bogen kommt die gewohnte Frage "Ist das das Kettenende?"
(Ja mit ES / Ja ohne ES / Nein, weiterbauen) - hier ausnahmsweise NACH dem
Bau, weil es keine gepickte Laenge gibt, von der ein ES-Fussabdruck
abzuziehen waere.
## 4. Stufe 1 / Stufe 2 - wie die letzte Strecke berechnet wird
Am Kettenende (Eintrag 61/62) ist die Aufgabe: von der aktuellen Position/
+9 -1
View File
@@ -55,6 +55,13 @@
"vfl-gf-laenge-modus-opt-hoehe": "\n 2 - Zielhoehe festlegen (Laenge wird berechnet)",
"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-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.",
"vfl-par-kein-versatz": "Das AS-Element schwenkt nicht aus der Zufuehrungsachse heraus - so laesst sich kein Abstand erreichen.",
"vfl-par-zu-klein": "Abstand %1 mm zu klein: AS und Bogen allein brauchen schon %2 mm. Bitte groesseren Abstand waehlen.",
"vfl-par-erfolg": "\n>>> Parallel zum Kreisel: GF-Stueck %1 mm (horizontal), Bogen %2 Grad %3, erreichter Abstand %4 mm.",
"vfl-qc-nur-kettenanfang": "\nFEHLER: 'QC Linie mit GF' ist nur am Kettenanfang moeglich (braucht eine eigene AS-Seite) - bitte anderen Modus waehlen.",
"vfl-qc-prompt-richtung": "\n\nRichtung waehlen (Gerade, auf der AS-Eintritt und ES-Austritt liegen sollen) - Punkt in Fahrtrichtung anklicken: ",
"vfl-qc-ks-ein-gemessen": "\n>>> AS KS_EIN (Referenzgerade) gemessen: X=%1 Y=%2 Z=%3",
@@ -101,7 +108,8 @@
"vfl-prompt-endpunkt-fahrtrichtung": "\n\nEndpunkt entlang Fahrtrichtung waehlen (bestimmt die Laenge): ",
"vfl-prompt-as-achse-p2": "\n\nZufuehrungsachse - 2. Punkt (gibt nur die Richtung, der Startpunkt bleibt): ",
"vfl-basislinie-punkte-gleich": "\n Die beiden Punkte sind identisch - keine Richtung bestimmbar. Abbruch.",
"vfl-basislinie-info": "\n Zufuehrungsachse: %1 Grad. Die AS-Seite ergibt sich aus der Lage des naechsten Punkts.",
"vfl-basislinie-info": "\n Zufuehrungsachse (Fahrtrichtung): %1 Grad.",
"vfl-basislinie-ohne": "\n Keine Zufuehrungsachse festgelegt - die Richtung ergibt sich aus dem naechsten Klick.",
"vfl-as-seite-ermittelt": "\n AS-Seite aus der Lage des naechsten Punkts: %1",
"vfl-endpunkt-modus-header": "\n\nWie soll der Endpunkt bestimmt werden?",
"vfl-endpunkt-modus-opt-linie": "\n 1 - Ziellinie zeichnen (2 Punkte) - der Schnittpunkt bestimmt die Laenge",
+9 -1
View File
@@ -55,6 +55,13 @@
"vfl-gf-laenge-modus-opt-hoehe": "\n 2 - Set target height (length will be calculated)",
"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-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.",
"vfl-par-kein-versatz": "The AS element does not turn away from the feed axis - no distance can be reached this way.",
"vfl-par-zu-klein": "Distance %1 mm too small: AS and bend alone already need %2 mm. Please choose a larger distance.",
"vfl-par-erfolg": "\n>>> Parallel to the carousel: GF section %1 mm (horizontal), bend %2 degrees %3, resulting distance %4 mm.",
"vfl-qc-nur-kettenanfang": "\nERROR: 'QC line with GF' is only possible at the chain start (needs its own AS side) - please choose a different mode.",
"vfl-qc-prompt-richtung": "\n\nChoose direction (line on which AS entry and ES exit shall lie) - click a point in the travel direction: ",
"vfl-qc-ks-ein-gemessen": "\n>>> AS KS_EIN (reference line) measured: X=%1 Y=%2 Z=%3",
@@ -101,7 +108,8 @@
"vfl-prompt-endpunkt-fahrtrichtung": "\n\nSelect end point along travel direction (defines the length): ",
"vfl-prompt-as-achse-p2": "\n\nFeed axis - 2nd point (defines the direction only, the start point stays): ",
"vfl-basislinie-punkte-gleich": "\n The two points are identical - no direction can be derived. Aborting.",
"vfl-basislinie-info": "\n Feed axis: %1 degrees. The AS side follows from the position of the next point.",
"vfl-basislinie-info": "\n Feed axis (travel direction): %1 degrees.",
"vfl-basislinie-ohne": "\n No feed axis defined - the direction follows from the next click.",
"vfl-as-seite-ermittelt": "\n AS side from the position of the next point: %1",
"vfl-endpunkt-modus-header": "\n\nHow should the end point be determined?",
"vfl-endpunkt-modus-opt-linie": "\n 1 - Draw target line (2 points) - the intersection defines the length",
+13
View File
@@ -322,6 +322,19 @@ class ProtokollSink(Sink):
self.add("point_abs", "getpoint Startpunkt der Kette (absoluter "
"Weltpunkt, einziger Anker der ganzen Kette)", wert=punkt)
def as_achse(self, punkt):
# Zweiter absoluter Punkt - gibt NUR die Richtung der Zufuehrung,
# der Startpunkt bleibt der Sitz des AS-Elements. "nil" heisst: die
# Frage wurde gestellt und bewusst uebergangen (die Kettenrichtung
# kommt dann aus dem naechsten Klick).
if punkt is None:
self.add("nil", "getpoint Zufuehrungsachse - 2. Punkt: keiner "
"gewaehlt, Richtung kommt aus dem naechsten Klick")
else:
self.add("point_abs", "getpoint Zufuehrungsachse - 2. Punkt "
"(gibt nur die Richtung, der Startpunkt bleibt)",
wert=punkt)
def linie(self, dl, hz, frage):
was = _vorlage(_LINIE_TEXT, frage)
if hz is None:
+28
View File
@@ -287,6 +287,12 @@ class Sink(object):
def praeambel_start(self):
"""Kettenkopf beginnt (Startpunkt/Starthoehe/AS-Element)."""
def as_achse(self, punkt):
"""Zufuehrungsachse des AS-Elements verbraucht (2. Punkt, gibt nur
die Richtung). punkt = [x, y, z] oder None, wenn die Kette bewusst
OHNE Achse gebaut wurde. Wird gar nicht gerufen, wenn das Journal
diese Frage noch nicht kennt (vor 2026-10-21)."""
def praeambel_ende(self):
"""Kettenkopf fertig."""
@@ -518,9 +524,31 @@ class Kern(object):
self.sink.real_feld("hoehe", self.real(), "start_hoehe")
if self._ja("as", "AS-Element setzen", "as"):
self.sink.roh_feld("as_winkel", self.nimm("STR"), "as_winkel")
# Drei Journal-Formate, unterscheidbar an der Eintragsart (siehe
# vfl-frage-as-winkel-seite-oder-achse):
# PT direkt nach dem Winkel -> Alt-Achse ohne Seitenfrage
# (30-Grad-Element, 2026-10-14
# bis 10-20); die Seite wurde
# damals aus der Lage des
# naechsten Punkts ermittelt.
# STR, dann PT bzw. NIL -> Format ab 2026-10-21: Seite UND
# (optionale) Zufuehrungsachse.
# STR, dann etwas anderes -> Altbestand ohne Achsenfrage.
if self.art() == "PT":
self._as_achse()
else:
self._code("as_seite", SEITE, "AS-Seite", "as_seite")
if self.art() == "PT":
self._as_achse()
elif self.art() == "NIL":
self.nimm("NIL")
self.sink.as_achse(None)
self.sink.praeambel_ende()
def _as_achse(self):
"""Zufuehrungsachse: zweiter Punkt, der NUR die Richtung gibt."""
self.sink.as_achse([float(v) for v in self.nimm("PT").split(",")])
def sektionen(self):
while self.pos < len(self.tok):
label = self.nimm("STEP")
+26
View File
@@ -87,6 +87,11 @@ def tokens_aus_protokoll(eintraege):
tok.append(("STR", str(e["wert"])))
elif typ == "step":
tok.append(("STEP", str(e["wert"])))
elif typ == "nil":
# Eine Frage, die gestellt und bewusst uebergangen wurde (bisher
# nur die Zufuehrungsachse des AS-Elements) - im Journal ein
# "NIL"-Eintrag ohne Wert.
tok.append(("NIL", None))
else:
raise ValueError("unbekannter Eintragstyp: %r" % typ)
return tok
@@ -255,6 +260,14 @@ class SpecSink(Sink):
def punkt_abs(self, punkt):
self.ziel["punkt"] = punkt
def as_achse(self, punkt):
# Zufuehrungsachse des AS-Elements. "ohne" ist NICHT dasselbe wie ein
# fehlendes Feld: "ohne" heisst "die Frage wurde gestellt und bewusst
# uebergangen" (NIL im Journal), ein fehlendes Feld heisst "das
# Journal kennt die Frage gar nicht" (vor 2026-10-21). Nur so bleibt
# der Rundlauf Journal -> Spec -> Journal tokengleich.
self.ziel["as_achse"] = punkt if punkt is not None else "ohne"
def linie(self, dl, hz, frage):
self.ziel["dl"] = dl
if hz is not None:
@@ -398,6 +411,14 @@ class SpecSchreiber(object):
self.raus("STR", str(self.feld(start, "as_winkel")))
self.raus("STR", code_aus_wert(SEITE, self.feld(start, "as_seite"),
"as_seite"))
# Optional, Gegenstueck zu SpecSink.as_achse und zu
# vsp-praeambel in Lisp/vf_spec.lsp: Liste -> PT, "ohne" -> NIL,
# fehlt -> gar kein Token (Journalformat vor 2026-10-21).
achse = start.get("as_achse")
if isinstance(achse, (list, tuple)):
self.raus("PT", ",".join(_zahl(v) for v in achse))
elif achse == "ohne":
self.raus("NIL", None)
def sektion(self, sek, erster, naechste=None):
label = self.feld(sek, "glied")
@@ -641,6 +662,11 @@ def flache_objekte(spec_id, spec, kopf_extra=None):
if start["as"]:
kopf["as_winkel"] = start["as_winkel"]
kopf["as_seite"] = start["as_seite"]
# Nur schreiben, wenn vorhanden - ein fehlendes Feld ist die
# bewusste Aussage "Journalformat ohne Achsenfrage" (siehe
# SpecSink.as_achse), kein Datenverlust.
if "as_achse" in start:
kopf["as_achse"] = start["as_achse"]
for k, v in (kopf_extra or collections.OrderedDict()).items():
kopf[k] = v
objekte.append(kopf)
+10 -1
View File
@@ -31,7 +31,9 @@
;; anzahl_eingaben, block, dxf_handle, startpunkt_mm,
;; rotation_grad, beschreibung, sivas_* , erwartung_hinweis
;; Eingabe-Objekt (traegt "typ"): point_abs | point_rel | real | int |
;; string | step - in exakter Frage-Reihenfolge
;; string | step | nil - in exakter Frage-Reihenfolge
;; ("nil" = Frage gestellt und bewusst uebergangen, z.B.
;; keine Zufuehrungsachse gewaehlt)
;; Ein Kopf-Objekt beginnt eine Kette, die folgenden Eingabe-Objekte gehoeren
;; dazu (hmrec:gruppiere).
;;
@@ -291,6 +293,13 @@
(list (cons "STR" (hmrec:als-string (ssg-val e "wert")))))
((= typ "step")
(list (cons "STEP" (hmrec:als-string (ssg-val e "wert")))))
;; Eine Frage, die gestellt und bewusst uebergangen wurde (bisher nur die
;; Zufuehrungsachse des AS-Elements, siehe ProtokollSink.as_achse in
;; lib/vf_journal_export.py) - im Journal ein wertloser "NIL"-Eintrag.
;; Er MUSS mitlaufen: beim Replay poppt ihn vfl-in-point, sonst zoege
;; dieser Aufruf den naechsten Wert aus der Queue (Desync).
((= typ "nil")
(list (cons "NIL" nil)))
(T nil)
)
)
+55 -1
View File
@@ -399,6 +399,59 @@
)
;; ------------------------------------------------------------
;; TESTS: vf_linienzug.lsp - "Parallel zum Kreisel" (Rechenkerne)
;; ------------------------------------------------------------
;; Die Bau-Funktion vfl-gf-parallel-kreisel braucht eine Zeichnung (sie misst
;; AS und Bogen an echten Bloecken); ihre beiden Rechenkerne sind davon frei
;; und stehen deshalb hier:
;; vfl-par-bogen-waehlen - welcher Bogen hebt den AS-Schwenk auf?
;; vfl-par-gf-laenge - wie lang wird das GF-Stueck dazwischen?
;; Die Plan-Turn-Werte unten sind die Vorzeichen-Konvention aus
;; vf-element-plan-turn (KS_EIN -> KS_AUS in der XY-Ebene, links positiv).
(defun tu-tests-vfl-parallel ( / _b30 _b60 _b90 _kand _l)
(princ "\n[vf_linienzug Parallel zum Kreisel]")
;; --- Bogenwahl: der Bogen muss den AS-Schwenk GENAU aufheben ---
;; Rechts-Boegen schwenken negativ, Links-Boegen positiv.
(setq _b30 '(30 . -30.0) _b60 '(60 . -60.0) _b90 '(90 . -90.0))
(setq _kand (list _b30 _b60 _b90))
;; AS links 30 Grad (Schwenk +30) -> 30-Grad-Bogen nach rechts
(tu-eq "vfl-par-bogen/as30-rechts" 30 (vfl-par-bogen-waehlen 30.0 _kand 2.0))
;; AS links 90 Grad (Schwenk +90) -> 90-Grad-Bogen nach rechts
(tu-eq "vfl-par-bogen/as90-rechts" 90 (vfl-par-bogen-waehlen 90.0 _kand 2.0))
;; Generisch: auch ein 60-Grad-Schwenk findet seinen Bogen - die Zuordnung
;; folgt den gemessenen Bloecken, nicht einer gepflegten Tabelle.
(tu-eq "vfl-par-bogen/as60-rechts" 60 (vfl-par-bogen-waehlen 60.0 _kand 2.0))
;; kleine Messabweichung bleibt in der Toleranz
(tu-eq "vfl-par-bogen/toleranz-ok" 30 (vfl-par-bogen-waehlen 31.2 _kand 2.0))
;; ausserhalb der Toleranz: lieber nil als ein schiefer Bogen
(tu-nil "vfl-par-bogen/zu-weit-weg" (vfl-par-bogen-waehlen 45.0 _kand 2.0))
(tu-nil "vfl-par-bogen/leere-liste" (vfl-par-bogen-waehlen 30.0 '() 2.0))
;; Gegenseite: Links-Boegen heben einen negativen AS-Schwenk auf
(setq _kand (list '(30 . 30.0) '(60 . 60.0) '(90 . 90.0)))
(tu-eq "vfl-par-bogen/as-rechts30-links" 30 (vfl-par-bogen-waehlen -30.0 _kand 2.0))
;; --- GF-Laenge: abstand = perp-as + L*k + perp-bogen ---
;; Glatter Fall: k = 1 (AS schwenkt 90 Grad aus der Achse), feste Anteile
;; zusammen 500 -> fuer 2000 mm Abstand bleiben 1500 mm GF.
(tu-eqf "vfl-par-laenge/k1" 1500.0 (vfl-par-gf-laenge 2000.0 200.0 300.0 1.0))
;; Gegenseite: k negativ -> der Abstand zaehlt auf der anderen Seite, der
;; Betrag der Eingabe bleibt derselbe (Seite kommt aus der AS-Variante).
(tu-eqf "vfl-par-laenge/k-negativ" 1500.0
(vfl-par-gf-laenge 2000.0 -200.0 -300.0 -1.0))
;; Schraege Ausschleusung: k = sin(60 Grad)
(setq _l (vfl-par-gf-laenge 2000.0 0.0 0.0 (sin (/ (* 60.0 pi) 180.0))))
(tu-eqf "vfl-par-laenge/k-sin60" (/ 2000.0 (sin (/ (* 60.0 pi) 180.0))) _l)
;; Abstand kleiner als der feste Anteil -> negative Laenge (der Aufrufer
;; meldet das als "Abstand zu klein", die Rechnung selbst raet nicht).
(tu-true "vfl-par-laenge/zu-klein-negativ"
(< (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))
)
;; ------------------------------------------------------------
;; TESTS: vf_linienzug.lsp - Laenge aus einer Ziellinie (X/Y)
;; ------------------------------------------------------------
@@ -2365,7 +2418,8 @@
(princ "\n================================================================")
(foreach fn '("tu-tests-core" "tu-tests-lang" "tu-tests-id" "tu-tests-vfcore"
"tu-tests-etage" "tu-tests-vfl-es" "tu-tests-vfl-ziellinie"
"tu-tests-etage" "tu-tests-vfl-es" "tu-tests-vfl-parallel"
"tu-tests-vfl-ziellinie"
"tu-tests-export" "tu-tests-hoehen" "tu-tests-gf" "tu-tests-omni" "tu-tests-kreisel" "tu-tests-kreisel-bp"
"tu-tests-sepliste" "tu-tests-json" "tu-tests-geruest"
"tu-tests-sepscan" "tu-tests-vfl-segs" "tu-tests-vfl-abbruch"