Files
dxfmakros/Lisp/vf_linienzug.lsp
T
Michael StanglandClaude Sonnet 5 2f56729308 export_csv: Separator-TYP (VF/GF) bauzeitig je Separator statt vom Wrapper-Blocknamen ableiten
csv:wrapper-strecke-typ (voriger Ansatz) las den Streckentyp aus dem
VF_n/GF_n-Wrapper-Blocknamen - falsch, weil ein einzelner Wrapper intern
mehrfach zwischen Foerderer- und Gefaellestrecke-Abschnitten wechseln kann
(HundM05 VF_1: 4 Separatoren, 2x VF, 2x GF). Jede Separator-Einfuegestelle in
vf_linienzug.lsp/Gefaellestrecke.lsp/vf_standard.lsp/vf_etage.lsp markiert
das frisch eingefuegte Staustrecke_Separator_SP_300_mm-Insert jetzt direkt
per XDATA (ssg-sep-typ-xdata-schreiben/-lesen, ssg_ks_insert.lsp). Der Export
liest den Marker am verpackten Original (nested-ent), nicht mehr am Wrapper.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 17:28:31 +02:00

10984 lines
590 KiB
Common Lisp
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
;; ============================================================
;; VF_LINIENZUG - Gemischte Gefaellestrecke/VarioFoerderer-Kette
;; Typ "linienzug" fuer VarioFoerderer (3. Typ neben "standard"/"etage")
;; ============================================================
;; Kette: immer genau EIN AS_Element am Anfang, genau EIN ES_Element am Ende,
;; dazwischen beliebig viele Segmente. Jedes gerade Segment wird automatisch
;; klassifiziert:
;; - Endpunkt hoeher als Startpunkt -> immer VF (Gefaelle kann nicht steigen,
;; reine Schwerkraftstrecke)
;; - Endpunkt tiefer, Neigung >= 3 Grad -> reine Gefaellestrecke (GF), da 3
;; Grad im ganzen Projekt die kleinste
;; Neigung ist (Vario_Bogen_auf/ab_3)
;; - Endpunkt tiefer, Neigung < 3 Grad -> VF (zu flach fuer reine GF):
;; erst Horizontale Mitte pruefen
;; (berechne-horizontale-mitte), sonst
;; diskreten Winkel 3-51 Grad suchen
;; (berechne-alle-winkel)
;; Nach einem GF-Segment: naechstes Element nur GF-Bogen oder neue Linie.
;; Nach einem VF-Segment: naechstes Element nur Vario-Kurve oder neue Linie.
;;
;; Architektur-Entscheidung (mit Nutzer abgestimmt): eigener Befehlsablauf,
;; NICHT ueber die berechne-fn/einfuege-fn-Registry (die ist fuer ein einzelnes
;; durchgehendes Segment gedacht, nicht fuer eine interaktive Mehrsegment-Kette).
;; c:VarioFoerderer erkennt den Typ "linienzug" und dispatcht direkt hierher.
;;
;; BEKANNTE EINSCHRAENKUNGEN dieser ersten Version (bitte in BricsCAD pruefen):
;; - Vario_Kurve_*-Bloecke (data/ils/3D/) wurden bislang nirgends im Projekt
;; verwendet. Ob sie KS_EIN/KS_AUS enthalten (Voraussetzung fuer
;; insert-block-ks-to-ks) ist ungeklaert und muss beim ersten Testlauf
;; verifiziert werden.
;; - Am Uebergang GF-Segment -> VF-Segment (ueber "neue Linie", die
;; automatisch als VF eingestuft wird) kann ein sichtbarer Knick entstehen:
;; vfs-mitte-teil beginnt sein erstes Element (GF1) immer fest bei 3 Grad,
;; unabhaengig vom Neigungswinkel des vorangehenden GF-Segments.
;; - Die Fusspunkte von AS-/ES-Element (aus-dx/dz bzw. ein-dx/dz) werden vom
;; Erst- bzw. Letzt-Segment abgezogen (vfl-as-deltaL/H-korrigiert bzw. die
;; Separator+ES-Reservierung im Kettenende-Modus), damit der gepickte
;; Endpunkt vom gebauten GF/VF-Koerper exakt getroffen wird.
;; ============================================================
;; Attribut-Definitionen fuer den Linienzug-Block kommen aus dem gemeinsamen
;; Strecken-Schema in ssg_core.lsp (ssg-strecke-attrib-defs). Der TYP wird zur
;; Laufzeit bestimmt: einsegmentige GF ohne Bogen -> "Gefaellestrecke"
;; (reduziert), sonst -> "Streckengruppe" (voll, segmentweise Werte).
;; Toleranzband (Grad) um die feste 3-Grad-Neigung: liegt der natuerliche
;; Winkel eines fallenden Segments innerhalb 3+/-Toleranz, wird es als reine
;; 3-Grad-Gefaellestrecke gebaut. Steiler -> VF-ab, flacher -> VF (flach).
;; Grund: eine reine Gefaellestrecke ist nie steiler als 3 Grad.
;; Bei Bedarf empirisch anpassen.
(if (null *vfl-gf-winkel-toleranz*) (setq *vfl-gf-winkel-toleranz* 0.5))
;; Mindestlaenge (mm) fuer die 3-Grad-Gefaellestrecke GF1 am Einlauf (zwischen
;; AS und Umlenkstation). Im Linienzug sitzt die gesamte Staustrecke am Einlauf
;; (GF1 = komplettes L_GF), GF2 am Ausgang entfaellt. Faellt das berechnete
;; L_GF darunter, wird GF1 auf diesen Wert angehoben - damit der 3-Grad-
;; Anschluss immer physisch vorhanden ist.
(if (null *vfl-gf-min-laenge*) (setq *vfl-gf-min-laenge* 400.0))
;; Horizontales Budget der festen Elemente im Linienzug-VF:
;; Umlenkstation (500) + Motorstation (500) + EIN Separator am Einlauf (300)
;; = 1300 mm. Der Ausgangs-Separator entfaellt (anders als Standard-VF=1600).
(if (null *vfl-feste-horizontal*) (setq *vfl-feste-horizontal* 1300.0))
;; ============================================================
;; TEIL 0: ATTRIBUT-AKKUMULATOREN (werden waehrend des Baus gefuellt)
;; ============================================================
;; Segment-Listen werden in Bau-Reihenfolge angehaengt und spaeter
;; kommagetrennt in die Attribute geschrieben. Bogen-/Kurven-Zaehler als Alist.
(defun vfl-acc-reset ()
(setq *vfl-acc-lvf* '() ; L_VF je VF-Sub-Segment (m, String)
*vfl-acc-lgf* '() ; L_GF je GF-Segment (m, String) - inkl. GF1/GF2
*vfl-acc-gfwinkel* '() ; Neigungswinkel je GF-Segment (String, parallel zu lgf)
*vfl-acc-richtung* '() ; "Auf"/"Ab"/"horizontal" je VF-Sub-Segment
*vfl-acc-winkel* '() ; Winkel je VF-Sub-Segment (String, 0=horizontal)
*vfl-acc-motorseite* '() ; Seite ("rechts"/"links") je Motorstation
*vfl-acc-gfbogen* '() ; Alist ("L_90".n ...) GF-Boegen
*vfl-acc-variokurve* '() ; Alist ("A_90".n ...) Vario-Kurven
*vfl-ziel-punkt* nil ; Soll-ES-Punkt fuer Option-3-Ist-Ziel-Report
*vfl-acc-separator* 0 ; Anzahl eingefuegter Separatoren (300 mm)
;; "Zurueck"/"Abbrechen"-Button (siehe vfl-in-value/vfl-modus-abbruch-
;; sichern): nil (normal) / "zurueck" (letztes Glied verwerfen und neu
;; fragen) / "abbrechen" (Bau hier stoppen, Baustand bleibt stehen).
*vfl-wizard-abbruch-grund* nil
*vfl-abbruch-letztes-ent* nil))
;; Alist-Zaehler erhoehen / lesen
(defun vfl-inc-count (al key / e)
(setq e (assoc key al))
(if e (subst (cons key (1+ (cdr e))) e al) (cons (cons key 1) al)))
(defun vfl-get-count (al key / e)
(if (setq e (assoc key al)) (cdr e) 0))
;; Ein VF-Sub-Segment (Koerper) erfassen. Winkel 0 => horizontal.
(defun vfl-acc-vf-seg (richtung winkel L_VF)
(setq *vfl-acc-lvf* (append *vfl-acc-lvf* (list (rtos (/ L_VF 1000.0) 2 3))))
(setq *vfl-acc-winkel* (append *vfl-acc-winkel* (list (itoa (fix winkel)))))
(setq *vfl-acc-richtung* (append *vfl-acc-richtung*
(list (if (= (fix winkel) 0) "horizontal" richtung)))))
;; Ein GF-Segment erfassen (Laenge = Schraeglaenge in m, winkel = Neigung).
;; Gilt fuer reine GF-Chain-Segmente UND die VF-internen GF1/GF2-Anschluesse.
(defun vfl-acc-gf-seg (L_GF winkel)
(setq *vfl-acc-lgf* (append *vfl-acc-lgf* (list (rtos (/ L_GF 1000.0) 2 3))))
(setq *vfl-acc-gfwinkel* (append *vfl-acc-gfwinkel* (list (rtos (float winkel) 2 1)))))
;; Liste kommagetrennt verketten ("" bei leer).
(defun vfl-join-komma (lst / s first)
(setq s "" first t)
(foreach x lst
(if first (progn (setq s x) (setq first nil)) (setq s (strcat s "," x))))
s)
;; Gewaehlte AS-/ES-Winkelvariante ("30"/"90"). Vorgabe "90", solange der Modus
;; nichts anderes gesetzt hat (Blocknamen AS_Element_<winkel>_<seite>).
(defun vfl-as-winkel () (if (boundp '*vfl-as-winkel*) *vfl-as-winkel* "90"))
(defun vfl-es-winkel () (if (boundp '*vfl-es-winkel*) *vfl-es-winkel* "90"))
;; ============================================================
;; TEIL 0a-2: MELDUNGEN (statt alert im Bau-Pfad)
;; ============================================================
;; Ein (alert ...) im Bau-Pfad blockiert jeden nicht-interaktiven Lauf
;; (Batch-Konvertierung vfl-konvertiere-ent, Testtreiber, Spec-Bau) mit einem
;; modalen Fenster - und der Text ist zugleich die EINZIGE Auskunft darueber,
;; welche Sektion warum abgewiesen wurde. vfl-meldung sammelt ihn deshalb
;; immer ein (*vfl-meldungen*, aelteste zuerst -> Auswertung im Ergebnis-
;; Record), schreibt ihn ins Debug-Log und zeigt ihn nur dann modal, wenn die
;; GUI erlaubt ist (ssg-gui-p, ssg_core.lsp); sonst geht er auf die Konsole.
;; Interaktiv ist das Verhalten damit unveraendert.
;; NUR fuer Meldungen aus dem Bau-Pfad. Die reinen Interaktiv-Alerts (fehlende
;; DCL-Datei, "Glied nicht editierbar", "kein Journal") bleiben alert - sie
;; koennen nur bei echter Benutzerbedienung auftreten.
(if (not (boundp '*vfl-meldungen*)) (setq *vfl-meldungen* nil))
(defun vfl-meldung (txt)
;; Ein fehlender i18n-Schluessel liefert nil - dann wuerde strcat
;; hier abbrechen, und zwar in dem Moment, in dem ohnehin schon
;; etwas schiefgelaufen ist. Also abfangen statt nachtraeglich
;; suchen.
(if (null txt) (setq txt "(Meldung ohne Text)"))
(setq *vfl-meldungen* (append *vfl-meldungen* (list txt)))
(dbgmsg (strcat "MELDUNG: " txt))
(dbgflush)
(if (ssg-gui-p)
(alert txt)
(princ (strcat "\n" txt)))
txt)
;; ============================================================
;; TEIL 0a-3: HEADLESS-NOTAUSGANG
;; ============================================================
;; Bei aktivem *vfl-headless* darf keine Live-Eingabe mehr stattfinden. Eine
;; erschoepfte Replay-Queue heisst dann: die Datenquelle (aufgezeichnetes
;; Journal bzw. Spec) passt nicht zum tatsaechlichen Bau-Ablauf (Desync) - ein
;; getpoint/getreal/ssget an dieser Stelle wuerde den Batch-Lauf still
;; blockieren statt den Fehler zu zeigen. Bisher fielen die Wrapper
;; STILLSCHWEIGEND auf Live-Eingabe zurueck; genau das wird hier zum harten,
;; lokalisierten Abbruch.
;;
;; *vfl-headless-fehler* traegt danach die Fundstelle (welche Art Eingabe,
;; welches Glied, welche Eingabe-Nummer) und wird vom Aufrufer in den
;; Ergebnis-Record uebernommen. Die Texte sind reine Diagnose (kein
;; ssg-text-Key noetig), sie erscheinen im normalen Betrieb nie.
;;
;; *vfl-headless-antwort-fn*: optionaler Hook, der eine nicht vorhersagbare
;; Rueckfrage doch noch beantworten darf (Beispiel: vfl-waehle-winkel fragt
;; nur dann, wenn mehrere Winkel-Kandidaten geometrisch gueltig sind - das
;; laesst sich vorab nicht immer wissen). Der gelieferte Wert wird normal
;; journalisiert; die Ersetzung wird zusaetzlich als Meldung protokolliert,
;; damit ein solcher Lauf nie als "sauber" durchgeht.
;; Aufruf: (fn was ort) -> Wert.
(if (not (boundp '*vfl-headless*)) (setq *vfl-headless* nil))
(if (not (boundp '*vfl-headless-fehler*)) (setq *vfl-headless-fehler* nil))
(if (not (boundp '*vfl-headless-antwort-fn*)) (setq *vfl-headless-antwort-fn* nil))
(defun vfl-headless-p () (and (boundp '*vfl-headless*) *vfl-headless*))
;; Fundstelle als Text: Glied-Nummer (STEP-Marker im bisherigen Journal) und
;; Nummer der Eingabe, die JETZT faellig gewesen waere (STEP-Marker zaehlen
;; nicht mit, die entstehen beim Replay neu).
(defun vfl-headless-ort ( / steps)
(setq steps (vfl-steps-zaehlen *vfl-journal*))
(strcat "Glied " (itoa steps)
", Eingabe " (itoa (1+ (- (length *vfl-journal*) steps)))))
;; Harter Abbruch ohne Rueckfrage (kehrt NICHT zurueck, (exit) wird vom
;; vl-catch-all-apply des Aufrufers gefangen). Fuer Eingaben, bei denen ein
;; nachgelieferter Wert nicht sinnvoll waere - z.B. eine Objektauswahl: eine
;; Teilauswahl wuerde die Fragenzahl des ganzen Modus-2-Ablaufs still
;; verschieben.
(defun vfl-headless-abbruch (was / ort steps)
(setq steps (vfl-steps-zaehlen *vfl-journal*))
(setq ort (vfl-headless-ort))
(setq *vfl-headless-fehler*
(list (cons "was" was)
(cons "glied" steps)
(cons "eingabe" (1+ (- (length *vfl-journal*) steps)))
(cons "ort" ort)))
(vfl-meldung (strcat "HEADLESS-ABBRUCH: keine Eingabedaten mehr fuer ["
was "] - " ort))
(exit))
;; Wie vfl-headless-abbruch, aber der Antwort-Hook darf zuerst uebernehmen.
;; Rueckgabe dann (wert) als 1-elementige Liste - dieselbe Form wie
;; vfl-replay-pop, damit die Aufrufstelle sie unveraendert weiterverwendet.
(defun vfl-headless-notausgang (was / ort v)
(if *vfl-headless-antwort-fn*
(progn
(setq ort (vfl-headless-ort))
(setq v (vl-catch-all-apply *vfl-headless-antwort-fn* (list was ort)))
(if (vl-catch-all-error-p v) (setq v nil))
(vfl-meldung (strcat "HEADLESS: Ersatzantwort fuer [" was "] - " ort))
(list v))
(vfl-headless-abbruch was)))
;; Gemeinsame Vorbedingung aller drei Linienzug-Modi: die GF-Segmente/-Boegen
;; nutzen Funktionen aus Gefaellestrecke.lsp (gf-insert-hz-incl-scaled,
;; gf-bogen-blockname, ...). Bei reiner VarioFoerderer-Ladung ohne
;; Gefaellestrecke wuerde der GF-Zweig fehlschlagen - deshalb hier pruefen und
;; danach die AS/ES-/Bogen-Bibliothek initialisieren (init-bibliothek), falls
;; noch nicht geschehen. alert-key: modusspezifischer i18n-Text (Modus 1 ist
;; ausfuehrlicher als 2/3, daher als Parameter statt vereinheitlicht).
(defun vfl-gf-abhaengigkeit-sicherstellen (alert-key)
(if (null (car (atoms-family 1 '("GF-INSERT-HZ-INCL-SCALED"))))
(progn (vfl-meldung (ssg-text alert-key)) (exit)))
(if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek)))
;; ============================================================
;; TEIL 0b: EINGABE-JOURNAL (RECORD & REPLAY)
;; ============================================================
;; Jede interaktive Eingabe im Modus-1-Aufrufbaum laeuft ueber die Wrapper
;; vfl-in-point/-string/-real/-int. Diese arbeiten in zwei Modi:
;; Record (*vfl-replay-queue* = nil): normal fragen + in *vfl-journal* anhaengen.
;; Replay (*vfl-replay-queue* gesetzt): naechsten gespeicherten Wert liefern
;; (nicht fragen) und ebenfalls in *vfl-journal* anhaengen. Laeuft die
;; Queue leer, wird ab hier wieder live gefragt (nahtloser Uebergang).
;; Weil der Builder deterministisch bzgl. seiner Eingaben ist, reproduziert das
;; Abspielen desselben Journals exakt dieselbe Kette. *vfl-journal* wird beim
;; Replay komplett neu aufgebaut (durch die erneute Ausfuehrung), die Queue ist
;; nur Lesequelle.
;;
;; Journal-Eintrag: (kind . value) mit kind aus
;; "PT" Punkt (Liste x y z) "STR" String (auch "")
;; "REAL" Realzahl "INT" Ganzzahl
;; "NIL" Abbruch/Default (value nil) "STEP" Glied-Checkpoint (value = Label)
;; *vfl-journal* wird in UMGEKEHRTER Reihenfolge gehalten (neuestes zuerst, cons).
;; *vfl-seg-glied-letzte* = Glied-Stand nach der vorherigen Iteration der
;; Bau-Hauptschleife (fuer die Segment-XDATA, siehe vfl-segment-xdata-sichern).
;; MUSS bei jedem Journal-Start auf 0 stehen, sonst bekaeme das erste Glied
;; eines neuen Baus einen zu hohen Index und die Praeambel wuerde nicht
;; geschrieben.
(if (not (boundp '*vfl-seg-glied-letzte*)) (setq *vfl-seg-glied-letzte* 0))
;; Journal und Replay-Queue muessen auch VOR dem ersten
;; vfl-journal-reset lesbar sein: die vfl-in-*-Wrapper und die
;; Headless-Diagnose lesen sie ungeschuetzt.
(if (not (boundp '*vfl-journal*)) (setq *vfl-journal* nil))
(if (not (boundp '*vfl-replay-queue*)) (setq *vfl-replay-queue* nil))
(defun vfl-journal-reset ()
;; Sammel-Meldungen und Headless-Diagnose gehoeren zum LAUF, nicht zur
;; Sitzung - sonst tragen sie in einen neuen Bau die Befunde des alten.
(setq *vfl-meldungen* nil *vfl-headless-fehler* nil)
(setq *vfl-journal* '() *vfl-replay-queue* nil *vfl-seg-glied-letzte* 0)
(if (boundp '*vflw-pending*) (setq *vflw-pending* nil)))
;; Wie vfl-journal-reset, aber OHNE *vfl-replay-queue*/*vflw-pending*/die
;; Meldungs-Sammler zu loeschen. Fuer den Aufruf INNERHALB von
;; vf-linienzug-modus/-modus2 selbst (siehe dortige Kommentare): die
;; REPLAY-getriebenen Aufrufer (vsp-bau-aus-spec) haben *vfl-replay-queue*
;; per vfl-journal-replay-start VOR dem Aufruf absichtlich befuellt - ein
;; voller Reset hier wirft die noch unverbrauchte Queue mitten im Bau weg
;; (realer Bug: HEADLESS-ABBRUCH direkt nach der AS-Element-Praeambel,
;; obwohl die Queue laut Debug-Log noch Eintraege hatte). Der eigentliche
;; Zweck - *vfl-journal* nicht ueber mehrere Ketten derselben Sitzung
;; hinweg wachsen zu lassen - bleibt erhalten.
(defun vfl-journal-reset-lauf ()
(setq *vfl-journal* '() *vfl-seg-glied-letzte* 0))
;; Eintrag anhaengen (vorn, da Reverse-Reihenfolge).
;; Zusaetzlich (falls ein Debug-Schalter fuer die aktuelle Sitzung eine .dbg-
;; Datei geoeffnet hat, siehe ssg_dbg.lsp/dbg-schalter-open) wird jeder
;; Eintrag als FRAGE/ANTWORT-Paar mitgeloggt - da ALLE interaktiven Eingaben
;; von Modus 1 UND Modus 2 durch diesen einen Punkt laufen (vfl-in-point/
;; -string/-real/-int/-value/-selection), deckt das beide Modi komplett ab,
;; ohne jede einzelne Aufrufstelle im Bau-Ablauf separat instrumentieren zu
;; muessen. Modus 3 nutzt bewusst rohe getstring/getreal/getint (reine
;; Konsole, kein Journal) und wird stattdessen direkt in vf-linienzug-modus3
;; mit eigenen dbgmsg-Aufrufen geloggt. dbgmsg/dbg sind von sich aus No-Ops,
;; solange keine .dbg-Datei offen ist (*dbg-path* nil) - daher hier bewusst
;; UNBEDINGT aufgerufen, kein eigener An/Aus-Check noetig.
(defun vfl-journal-record (kind val)
(setq *vfl-journal* (cons (cons kind val) *vfl-journal*))
(if (= kind "STEP")
(dbgmsg (strcat "--- GLIED: " (if val (vl-princ-to-string val) "?") " ---"))
(progn
(dbgmsg (strcat "EINGABE [" kind "]:"))
(dbg val)
(dbgflush))))
;; Glied-Checkpoint setzen (Label spaeter via vfl-journal-steplabel setzbar).
(defun vfl-journal-mark (label)
(vfl-journal-record "STEP" label))
;; Label des JUENGSTEN STEP-Eintrags nachtraeglich setzen (der Segmenttyp steht
;; erst nach der Menue-Auswahl fest, der Marker sitzt aber am Iterationskopf).
(defun vfl-journal-steplabel (label / found)
(setq found nil)
(setq *vfl-journal*
(mapcar
(function (lambda (e)
(if (and (not found) (= (car e) "STEP"))
(progn (setq found t) (cons "STEP" label))
e)))
*vfl-journal*))
(dbgmsg (strcat "--- GLIED-LABEL: " (if label label "?") " ---"))
(dbgflush)
label)
;; Naechsten Eingabe-Eintrag aus der Replay-Queue poppen (STEP-Marker dabei
;; ueberspringen - die werden bei der erneuten Ausfuehrung neu erzeugt).
;; Rueckgabe: (value) als 1-elementige Liste, falls ein Eintrag da war
;; (unterscheidet einen echten nil-Wert von einer leeren Queue); nil, wenn die
;; Queue erschoepft ist (danach faellt der Wrapper auf Live-Eingabe zurueck).
(defun vfl-replay-pop ( / e)
(while (and *vfl-replay-queue* (= (car (car *vfl-replay-queue*)) "STEP"))
(setq *vfl-replay-queue* (cdr *vfl-replay-queue*)))
(if *vfl-replay-queue*
(progn
(setq e (car *vfl-replay-queue*))
(setq *vfl-replay-queue* (cdr *vfl-replay-queue*))
(list (cdr e)))
nil))
;; ============================================================
;; TEIL 0b-1b: GLIED-SCHEMA (single source of truth)
;; ============================================================
;; Das Journal ist und bleibt eine flache (kind . value)-Liste - das Format auf
;; der XDATA aendert sich NICHT (keine Migration noetig, Replay unveraendert).
;; Neu ist nur eine zentrale, DEKLARATIVE Beschreibung, welche Felder in welcher
;; Reihenfolge und mit welchem Typ ein Glied ausmachen. Damit gibt es genau EINE
;; Stelle, die die Struktur eines Glieds kennt - Bauen, Lesen, Validieren und
;; das Debug-Log leiten sich alle daraus ab (kein Auseinanderlaufen von je-Typ-
;; slice-bauen/slice-werte mehr, aus dem der Winkel-Bug entstand).
;;
;; Ein Feld ist: (name kind werte). name = Feldname (fuers Log), kind = Journal-
;; kind ("STR"/"INT"/"REAL"/"PT"), werte = Liste erlaubter Werte oder nil (frei).
;; Das fuehrende STEP (Glied-Checkpoint) ist NICHT Teil der Feldliste - es wird
;; von den Slice-Helfern separat vorangestellt bzw. beim Lesen uebersprungen.
;;
;; Nur die FESTEN, kurzen Glieder sind hier erfasst (AS, ES, GF-Bogen, Vario-
;; Kurve). Die VF-Einheit/Horizontal-VF ist ein variabler Dialog-Fluss mit
;; optionsabhaengiger Feldzahl - sie ist bewusst KEIN Feld-Record und wird
;; weiter direkt behandelt (nicht einzeln editierbar, siehe vfl-edit-glied).
;;
;; Feldkonventionen (wie vom bestehenden Code erzeugt):
;; seite "1"=links / "2"=rechts
;; menue fuehrende "Naechstes Element waehlen"-Antwort (nur GF-Bogen: "1")
;; winkel GF-Bogen/Vario-Kurve als INT (30/60/90), AS/ES als STR ("30"/"90")
;; GF-Bogen/Vario-Kurve-Winkel kommen zentral aus *vfk-gf-bogen-winkel*, AS/ES-
;; Winkel aus *vfk-as-es-winkel* (beide vf_konstanten.lsp) - hier nicht mehr
;; hartcodiert. Daher (list ...) statt Quote fuer die betroffenen Winkel-Slots.
;; Fallback, falls vf_konstanten (aus irgendeinem Grund) nicht geladen wurde.
(if (null *vfk-gf-bogen-winkel*) (setq *vfk-gf-bogen-winkel* '(30 60 90)))
(if (null *vfk-as-es-winkel*) (setq *vfk-as-es-winkel* '("30" "90")))
(setq *vfl-glied-schema*
(list
(cons "AS"
(list (list "winkel" "STR" *vfk-as-es-winkel*)
'("seite" "STR" ("1" "2"))))
(cons "ES"
(list (list "winkel" "STR" *vfk-as-es-winkel*)
'("seite" "STR" ("1" "2"))))
(cons "GF-Bogen"
(list '("menue" "STR" ("1"))
(list "winkel" "INT" *vfk-gf-bogen-winkel*)
'("seite" "STR" ("1" "2"))))
(cons "Vario-Kurve"
(list (list "winkel" "INT" *vfk-gf-bogen-winkel*)
'("seite" "STR" ("1" "2"))
'("variante" "STR" ("1" "2"))))))
;; --- VF-Einheit: Sub-Step-Referenz (variable Laenge) ---------------------
;; Die VF-Einheit (Linie-VF / Horizontal-VF) ist KEIN Glied mit fester Feldzahl,
;; sondern eine SCHLEIFE, die beliebig viele Sub-Segmente aneinanderhaengt, bis
;; der Nutzer "Ende" waehlt. Sie kann ausserdem Vario-Kurven ENTHALTEN. Ein
;; starres Feld-Schema wie oben passt daher nicht.
;;
;; Diese Referenz beschreibt die EINZELNEN Teilfragen, die im VF-Fluss auftreten
;; koennen - jede fuer sich ein kleiner benannter Record. Anders als bei den
;; festen Gliedern ist die REIHENFOLGE hier verzweigungsabhaengig; die Benennung
;; im Log erfolgt deshalb ueber das Wert-Muster (nicht ueber eine feste
;; Position). Zweck: lesbares Log ("VF.sep-vor = ja") + Wert-Validierung, OHNE
;; den fragilen Bau-Fluss nachzubauen. min/max = wie oft die Teilfrage je
;; VF-Einheit auftreten kann (Doku-Wert; die Schleife macht max offen = nil).
;;
;; Teilfrage kind werte min max Bedeutung
;; menue STR "3"/"4" 1 1 Hauptmenue-Wahl Linie-VF/Horizontal-VF
;; laenge DL - 1 n Reststrecke (Punktwahl -> dL)
;; zielhoehe REAL - 0 n Zielhoehe (nur Auf/Ab bzw. Linie-VF)
;; sep-vor STR "1"/"2" 0 n Separator vor (horizontaler Koerper)
;; sep-nach STR "1"/"2" 0 n Separator nach (horizontaler Koerper)
;; ist-ende STR "1"/"2"/"3" 1 n "Ist Endpunkt der Foerderer?" (Schleife)
;; naechstes-vf STR "1"/"2"/"3" 0 n Fortsetzung Horiz./Vario-Kurve/Auf-Ab
;; es-gewuenscht STR "1"/"2" 0 1 ES setzen? (nur Kettenende-Option 3)
;; variokurve.* siehe Glied "Vario-Kurve" (eingebettet moeglich)
;;
;; Fuer die Log-Benennung (vfl-schema-log-vf) reicht die Unterscheidung nach
;; Wert-Muster: eine STR mit Wert "3" ist ein 1-3-Menue-Schritt, "1"/"2" eine
;; Ja/Nein-Frage; REAL/DL sind Laenge bzw. Hoehe.
(setq *vfl-vf-substeps*
(list
'("STR-menue" "STR" ("3" "4")) ; fuehrende Menue-Wahl
'("STR-janein" "STR" ("1" "2")) ; sep-vor/-nach, es-gewuenscht
'("STR-menue3" "STR" ("1" "2" "3")))) ; ist-ende, naechstes-vf
;; Feldliste eines Glied-Typs (nil, wenn nicht im Schema - z.B. VF-Einheit).
(defun vfl-schema-felder (label) (cdr (assoc label *vfl-glied-schema*)))
(defun vfl-schema-feld-name (feld) (car feld))
(defun vfl-schema-feld-kind (feld) (cadr feld))
(defun vfl-schema-feld-werte (feld) (caddr feld))
;; Prueft EINEN Wert gegen eine Felddefinition. Rueckgabe: nil = ok, sonst ein
;; Fehlertext (fuer Log/Alert). Prueft (a) den Typ passend zum kind und (b) die
;; erlaubten Werte, falls im Feld angegeben.
(defun vfl-schema-feld-pruefen (feld val / kind werte typ-ok)
(setq kind (vfl-schema-feld-kind feld) werte (vfl-schema-feld-werte feld))
(setq typ-ok
(cond ((= kind "STR") (= (type val) 'STR))
((= kind "INT") (= (type val) 'INT))
((= kind "REAL") (or (= (type val) 'REAL) (= (type val) 'INT)))
((= kind "PT") (and (listp val) (= (length val) 3)))
(t t)))
(cond
((not typ-ok)
(strcat (vfl-schema-feld-name feld) ": erwartet " kind ", ist "
(vl-princ-to-string (type val))))
((and werte (not (member val werte)))
(strcat (vfl-schema-feld-name feld) "=" (vl-princ-to-string val)
" nicht in erlaubten Werten " (vl-princ-to-string werte)))
(t nil)))
;; Baut eine Journal-Slice aus einem Glied-Label und einer Werteliste (in
;; Feldreihenfolge). Stellt das STEP voran. AS ist ein Sonderfall (kein STEP -
;; Praeambel), daher hier NICHT erlaubt (nil-Rueckgabe). Validiert jeden Wert
;; vor dem Bauen; bei einem Fehler wird er als dbgmsg protokolliert, der Wert
;; aber trotzdem geschrieben (Sicherung geht vor - lieber ein warnendes Log als
;; ein verworfener Bau). Rueckgabe: forward-order Slice (Liste (kind . value)).
(defun vfl-schema-slice-bauen (label werte / felder out feld val fehler)
(setq felder (vfl-schema-felder label))
(if (or (null felder) (= label "AS"))
nil
(progn
(setq out (list (cons "STEP" label)))
(while felder
(setq feld (car felder) val (car werte))
(setq fehler (vfl-schema-feld-pruefen feld val))
(if fehler (dbgmsg (strcat ">> SCHEMA-WARN " label "." fehler)))
(setq out (append out (list (cons (vfl-schema-feld-kind feld) val))))
(setq felder (cdr felder) werte (cdr werte)))
out)))
;; Liest die Feldwerte aus einer Glied-Slice (STEP + Felder) in Feldreihenfolge.
;; Nimmt der Reihe nach den naechsten Eintrag, dessen kind zum jeweiligen Feld
;; passt (ueberspringt das fuehrende STEP automatisch). Fehlt ein Wert oder
;; stimmt der Typ nicht, wird der (optionale) Default aus defaults an dieser
;; Position eingesetzt. Rueckgabe: Werteliste in Feldreihenfolge.
(defun vfl-schema-slice-werte (label slice defaults / felder rest out feld val gefunden)
(setq felder (vfl-schema-felder label) rest slice out '())
(while felder
(setq feld (car felder) gefunden nil)
;; naechsten passenden Eintrag suchen (STEP und Fremdtypen ueberspringen)
(while (and rest (not gefunden))
(if (= (car (car rest)) (vfl-schema-feld-kind feld))
(setq val (cdr (car rest)) gefunden t))
(setq rest (cdr rest)))
(setq out (append out (list (if gefunden val (car defaults)))))
(setq felder (cdr felder) defaults (cdr defaults)))
out)
;; Benanntes Debug-Log fuer eine ganze Glied-Slice: statt roher [INT] value=90-
;; Zeilen die Felder als "GF-Bogen.winkel = 90". No-Op ohne offene .dbg-Datei.
;; Wird beim Sichern (vfl-segment-xdata-sichern) genutzt.
(defun vfl-schema-log-slice (label slice / felder werte feld fehler)
(setq felder (vfl-schema-felder label))
(if felder
(progn
(setq werte (vfl-schema-slice-werte label slice
(mapcar '(lambda (f) nil) felder)))
(dbgmsg (strcat ">> GLIED " label ":"))
(while felder
(setq feld (car felder))
(setq fehler (vfl-schema-feld-pruefen feld (car werte)))
(dbgmsg (strcat " " (vfl-schema-feld-name feld) " = "
(vl-princ-to-string (car werte))
(if fehler (strcat " <!! " fehler) "")))
(setq felder (cdr felder) werte (cdr werte)))
(dbgflush))))
;; Benennt einen EINZELNEN VF-Slice-Eintrag (kind . value) nach Wert-Muster
;; (siehe *vfl-vf-substeps*). Rueckgabe: (name . fehlertext-oder-nil). laufend =
;; wie oft schon eine STR gesehen wurde (die 1. STR einer VF-Einheit ist die
;; menue-Wahl "3"/"4", spaetere STR sind Ja/Nein- bzw. 1-3-Menue-Schritte).
(defun vfl-vf-substep-benennen (eintrag pos-str / kind val name fehler)
(setq kind (car eintrag) val (cdr eintrag))
(cond
((= kind "STEP") (cons "STEP" nil))
((= kind "DL") (cons "laenge" nil)) ; Punktwahl -> Reststrecke
((= kind "REAL") (cons "hoehe" nil)) ; Zielhoehe (Auf/Ab bzw. Linie-VF)
((= kind "PT") (cons "punkt" nil))
((= kind "INT") ; eingebettete Vario-Kurve
(setq fehler (vfl-schema-feld-pruefen (list "vk-winkel" "INT" *vfk-gf-bogen-winkel*) val))
(cons "variokurve.winkel" fehler))
((= kind "STR")
(cond
((= pos-str 0) ; 1. STR = fuehrende Menue-Wahl
(setq fehler (vfl-schema-feld-pruefen '("menue" "STR" ("3" "4")) val))
(cons "menue" fehler))
((member val '("3")) ; nur 1-3-Menue kann "3" sein
(cons "ist-ende/naechstes-vf" nil))
((member val '("1" "2")) ; ja/nein ODER 1-3 (1/2)
(cons "wahl(1/2)" nil))
(t (cons "str" (strcat "unerwarteter STR-Wert " (vl-princ-to-string val))))))
(t (cons kind nil))))
;; Benanntes Debug-Log fuer eine ganze VF-Einheit-Slice (variable Laenge). Statt
;; roher [STR]/[REAL]-Zeilen jede Teilfrage benannt ("VF.menue = 4",
;; "VF.laenge = 4566.7", "VF.wahl(1/2) = 2"). Validiert dabei, was validierbar
;; ist (menue-Wert, eingebettete Vario-Kurven-Winkel). No-Op ohne offene .dbg.
(defun vfl-schema-log-vf (label slice / rest e paar name fehler pos-str)
(dbgmsg (strcat ">> GLIED " label " (VF-Einheit, variable Laenge):"))
(setq rest slice pos-str 0)
(foreach e rest
(if (/= (car e) "STEP")
(progn
(setq paar (vfl-vf-substep-benennen e pos-str))
(setq name (car paar) fehler (cdr paar))
(dbgmsg (strcat " " name " = " (vl-princ-to-string (cdr e))
(if fehler (strcat " <!! " fehler) "")))
(if (= (car e) "STR") (setq pos-str (1+ pos-str))))))
(dbgflush))
;; ============================================================
;; TEIL 0b-2: WIZARD-EINGABE (DCL statt Konsole)
;; ============================================================
;; Ersetzt NUR die LIVE-Eingabe (frischer Bau) durch DCL-Dialoge
;; (dcl/vf_linienzug_wizard.dcl). Journal-Replay (Editieren bestehender
;; Ketten, vfl-edit-ent) ist UNVERAENDERT: er liefert seinen Wert ueber
;; "popped" und erreicht den Wizard-Zweig unten gar nicht erst.
;; Die ORIGINALEN Konsolen-Fragen (princ + getstring/getreal/getint an jeder
;; Aufrufstelle) bleiben vollstaendig im Code stehen (weiterhin de/en ueber
;; ssg-text/lang/*.json) - sie laufen bei *vfl-wizard-mode* = nil unveraendert
;; wie bisher. Schnell zurueckschalten: Befehl VF_WIZARD_AUS bzw.
;; (setq *vfl-wizard-mode* nil).
;; Sicherheitsnetz: jeder Wizard-Aufruf ist per vl-catch-all-apply
;; abgesichert - jeder Fehler (fehlendes DCL, kaputtes Tile etc.) faellt
;; automatisch und lautlos auf die alte Konsolen-Eingabe zurueck, es gibt
;; also keinen Abbruch-Pfad, der NUR durch den Wizard entsteht.
(if (not (boundp '*vfl-wizard-mode*)) (setq *vfl-wizard-mode* T))
(if (not (boundp '*vflw-menu-optionen*)) (setq *vflw-menu-optionen* nil))
(if (not (boundp '*vflw-menu-default*)) (setq *vflw-menu-default* 1))
(if (not (boundp '*vflw-menu-frage*)) (setq *vflw-menu-frage* nil))
;; Effektiver Wizard-Zustand: Wizard-DCL-Dialoge nur, wenn *vfl-wizard-mode*
;; gesetzt UND die GUI global nicht abgeschaltet ist (Tests: (ssg-gui-aus)).
;; Bei abgeschalteter GUI fallen alle Eingaben auf den Konsolen-/getXXX-Pfad
;; zurueck, den die Testrunner per Mock/Replay bedienen.
(defun vfl-wizard-mode-p ()
(and *vfl-wizard-mode*
(or (not (car (atoms-family 1 '("SSG-GUI-P")))) (ssg-gui-p))))
;; Wizard-GRUPPEN-Dialoge (vflw-gruppe-*-impl) duerfen NUR bei echter Live-
;; Eingabe aufgehen - waehrend eines Journal-Replays (Editieren/Fortsetzen
;; einer bestehenden Kette, *vfl-replay-queue* gesetzt) liefern vfl-in-point/
;; -string/-real/-int/-value ihre Werte bereits stumm aus der Replay-Queue;
;; ein trotzdem geoeffneter Gruppen-Dialog wuerde einerseits unnoetig
;; blockieren und andererseits seine Antworten in *vflw-pending* ablegen, wo
;; sie NICHT konsumiert werden (die Wrapper pruefen die Replay-Queue zuerst)
;; und liegen bleiben - sobald die Replay-Queue erschoepft ist und auf Live-
;; Eingabe umgeschaltet wird, liest der naechste Wrapper diese laengst
;; veralteten, falsch typisierten Werte statt neu zu fragen (siehe
;; vfl-neue-linie-messen: fuehrte zu einem String-statt-Punkt-Absturz).
(defun vfl-wizard-aktiv () (and (vfl-wizard-mode-p) (null *vfl-replay-queue*)))
(defun c:VF_WIZARD_AN ()
(setq *vfl-wizard-mode* T)
(princ "\nVF-Linienzug-Assistent: EIN (DCL-Dialoge)")
(princ))
(defun c:VF_WIZARD_AUS ()
(setq *vfl-wizard-mode* nil)
(princ "\nVF-Linienzug-Assistent: AUS (Konsolen-Fragen)")
(princ))
(defun vflw-dcl-pfad ()
(strcat (vl-string-translate "\\" "/" (getenv "DXFM_DCL")) "/vf_linienzug_wizard.dcl"))
;; Text fuer Dialog-Anzeige (Kopfzeile ODER Popup-Listen-Option) saeubern:
;; fuehrende Newlines/Leerzeichen entfernen, DANACH ein fuehrendes
;; Konsolen-Ziffernpraefix wie "1 - " (aus "1 - 90 Grad", "2 - Links" usw.)
;; abschneiden - im Dialog klickt der Nutzer die Option, die Ziffer braucht
;; er nur an der Kommandozeile. Die Ziffer/der Index selbst wird davon nicht
;; beruehrt: Popup-Auswahl und Pending-Queue arbeiten weiterhin ueber die
;; Listenposition (get_tile "..." liefert einen 0-basierten Index), nicht
;; ueber diesen Anzeigetext. Kopfzeilen-Texte beginnen nie mit einer Ziffer
;; und sind von diesem zweiten Schritt daher unberuehrt.
(defun vflw-clean (s / i n)
(while (and (> (strlen s) 0) (member (substr s 1 1) (list "\n" " ")))
(setq s (substr s 2)))
(setq n (strlen s) i 1)
(while (and (<= i n) (wcmatch (substr s i 1) "#")) (setq i (1+ i)))
(if (> i 1)
(progn
(while (and (<= i n) (= (substr s i 1) " ")) (setq i (1+ i)))
(if (and (<= i n) (= (substr s i 1) "-"))
(progn
(setq i (1+ i))
(while (and (<= i n) (= (substr s i 1) " ")) (setq i (1+ i)))
(setq s (substr s i))))))
s)
;; 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)
(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))
(action_tile "accept" "(setq dlg-wert (get_tile \"wert\")) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((and (= ergebnis 1) dlg-wert (> (strlen dlg-wert) 0)) (atof dlg-wert))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck") nil)
((= ergebnis 0) (setq *vfl-wizard-abbruch-grund* "abbrechen") nil)
(t nil)))))
(defun vflw-zahl (prompt / r)
(setq r (vl-catch-all-apply 'vflw-zahl-impl (list prompt)))
(if (vl-catch-all-error-p r) (getreal prompt) r))
;; Generischer Auswahl-Dialog (ersetzt eine princ-Menue-Liste + getstring/
;; getint). optionen = Liste bereits lokalisierter Options-Texte,
;; default-idx = 1-basiert vorselektiert. Rueckgabe: "1".."N" (String, wie
;; getstring) oder "" (Abbruch/leer - faellt bei den Aufrufern ueberall auf
;; den bestehenden Default-Zweig zurueck, genau wie ein leeres Enter heute).
(defun vflw-wahl-impl (prompt optionen default-idx / dat dcl-pfad dlg-opt ergebnis opt)
(setq dcl-pfad (vflw-dcl-pfad))
(setq dat (load_dialog dcl-pfad))
(if (not (new_dialog "vflw_wahl" dat))
(progn (if (and dat (>= dat 0)) (unload_dialog dat)) "")
(progn
(set_tile "kopf" (vflw-clean prompt))
(start_list "opts")
(foreach opt optionen (add_list (vflw-clean opt)))
(end_list)
(set_tile "opts" (itoa (max 0 (1- (if default-idx default-idx 1)))))
(action_tile "accept" "(setq dlg-opt (get_tile \"opts\")) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1) (itoa (1+ (atoi dlg-opt))))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck") "")
(t (setq *vfl-wizard-abbruch-grund* "abbrechen") "")))))
(defun vflw-wahl (prompt optionen default-idx / r)
(setq r (vl-catch-all-apply 'vflw-wahl-impl (list prompt optionen default-idx)))
(if (vl-catch-all-error-p r) "" r))
;; ============================================================
;; TEIL 0b-4: GRUPPEN-DIALOGE - GEMEINSAME PUNKTWAHL-BAUSTEINE
;; ============================================================
;; Punktwahl aus einem offenen Dialog heraus: (getpoint) direkt aus einem
;; action_tile-Callback funktioniert in BricsCAD NICHT zuverlaessig (der
;; Dialog behaelt den Fokus, die Zeichnung reagiert nicht auf Klicks - per
;; Test bestaetigt). Stattdessen das robuste "Schliessen-Picken-Neu-Zeigen"-
;; Muster: der Pick-Button beendet den Dialog ueber einen eigenen
;; done_dialog-Code (2), NACH dem unload_dialog laeuft (getpoint) ganz
;; normal auf der Kommandozeile, danach wird der Dialog mit dem Ergebnis neu
;; aufgebaut (new_dialog erneut) - eine (while ...)-Schleife um new_dialog/
;; start_dialog statt eines einmaligen Aufrufs.
(defun vflw-g-punkt-str (pt)
(strcat "X=" (rtos (car pt) 2 1) " Y=" (rtos (cadr pt) 2 1) " Z=" (rtos (caddr pt) 2 1)))
;; ------------------------------------------------------------
;; Gruppe "Punkt+Hoehe": ein Punkt gefolgt von einer davon abhaengigen
;; Hoehen-Zahl (Default = Z des gepickten Punkts, wie im Original-Prompt).
;; Wiederverwendet fuer Modus 1 "Kettenstart" (Startpunkt+Starthoehe) UND
;; Modus 2 "Startpunkt/Endpunkt" (jeweils +Hoehe) - kopf-key/wlabel-text pro
;; Aufruf. Original-Reihenfolge/-Journal an jeder Aufrufstelle: vfl-in-point,
;; dann vfl-in-real - bleibt unveraendert, nur die Werte kommen aus der
;; Pending-Queue.
(defun vflw-gruppe-punkt-hoehe-impl (kopf-key wlabel-text /
dat dcl-pfad ergebnis gpunkt gwert wert-akt fertig)
(setq wert-akt "0" fertig nil)
(while (not fertig)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_punkt_zahl" 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 "wlabel" wlabel-text)
(set_tile "wert" wert-akt)
(if gpunkt (set_tile "pt_anzeige" (vflw-g-punkt-str gpunkt)))
(action_tile "pick" "(done_dialog 2)")
(action_tile "accept" "(setq gwert (get_tile \"wert\")) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
;; Code 3 statt 2 - 2 ist hier schon durch "Punkt waehlen..." belegt.
(action_tile "back" "(done_dialog 3)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 2)
;; Dialog ist jetzt zu - Zeichnung hat den Fokus, getpoint geht normal.
(setq gpunkt (vfl-getpoint nil "\nPunkt waehlen: "))
(if gpunkt (setq wert-akt (rtos (caddr gpunkt) 2 1)))
;; fertig bleibt nil -> Schleife zeigt den Dialog mit dem Ergebnis erneut.
)
((= ergebnis 1)
(setq fertig t)
(if (and gpunkt gwert (> (strlen gwert) 0))
(vflw-pending-push-all (list gpunkt (atof gwert)))))
((= ergebnis 3) (setq fertig t) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq fertig t) (setq *vfl-wizard-abbruch-grund* "abbrechen")))
)
)
)
(princ))
;; ------------------------------------------------------------
;; Gruppe "AS-/ES-Element": Ja/Nein + [wenn Ja] Winkel(30/90) + Seite.
;; Wiederverwendet fuer Modus 1 UND Modus 2, jeweils fuer AS (Kettenanfang)
;; UND ES (Kettenende) - kopf-key pro Aufruf traegt die konkrete Frage; die
;; Winkel/Seite-Optionen sind in allen vier Faellen identisch (90/30 bzw.
;; links/rechts), "Nein" ist journalseitig immer "2" (identischer Vergleich
;; 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)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_as" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text kopf-key)))
(set_tile "setzen" "1")
(set_tile "winkel_90" "1")
(set_tile "seite_links" "1")
(action_tile "setzen"
"(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))")
(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)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1)
(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.
(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"))
)
)
)
(princ))
;; ------------------------------------------------------------
;; Gruppe "Winkel+Seite": GF-Bogen (3 Winkel-Optionen) ODER ES-Element am
;; Kettenende (2 Winkel-Optionen). Original-Reihenfolge/-Journal:
;; vfl-menu-winkel/vfl-in-value (Winkel), dann vfl-in-string (Seite).
;; winkel-werte: Liste der zu den Optionen gehoerenden WINKELWERTE, in der
;; Reihenfolge der Labels - GF-Bogen (30 60 90), ES ("90" "30"). Der Winkel
;; wird IMMER als echter Wert in die Pending-Queue geschrieben (nie als Index),
;; damit Journal/Log selbsterklaerend sind. GF-Bogen legt Zahlen ab (vom
;; vfl-menu-winkel/vfl-in-value als "INT" journalisiert), ES Strings "90"/"30".
(defun vflw-gruppe-winkel-seite-impl (kopf-key winkel-optionen winkel-werte /
dat dcl-pfad ergebnis gwinkel-grad gseite
mit-60 dlg-name)
;; 60 Grad ist NUR beim GF-Bogen/Vario-Kurve eine gueltige Option - bei
;; AS/ES (winkel-werte immer nur ("90" "30")) waere ein staendig
;; ausgegrautes "60 Grad" verwirrend, daher ein eigener 2-Optionen-Dialog
;; OHNE die Kachel statt mode_tile-Deaktivierung (siehe DCL-Kommentar).
(setq mit-60 (vfl-winkel-in-liste-p 60 winkel-werte))
(setq dlg-name (if mit-60 "vflw_winkel_seite" "vflw_winkel_seite_30_90"))
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog dlg-name dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text kopf-key)))
(if mit-60
(progn
(if (not (vfl-winkel-in-liste-p 30 winkel-werte)) (mode_tile "winkel_30" 1))
(if (not (vfl-winkel-in-liste-p 90 winkel-werte)) (mode_tile "winkel_90" 1))
)
)
(set_tile (strcat "winkel_" (itoa (vfl-winkel-als-zahl (car winkel-werte)))) "1")
(set_tile "seite_links" "1")
(action_tile "accept"
(if mit-60
"(setq gwinkel-grad (cond ((= (get_tile \"winkel_30\") \"1\") 30)
((= (get_tile \"winkel_60\") \"1\") 60)
(t 90)))
(setq gseite (if (= (get_tile \"seite_rechts\") \"1\") \"2\" \"1\"))
(done_dialog 1)"
"(setq gwinkel-grad (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)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1)
;; Original-Eintrag aus winkel-werte uebernehmen (nicht den nackten
;; Grad-Wert) - erhaelt den Typ (INT fuer GF-Bogen, STR fuer ES).
(vflw-pending-push-all
(list (car (vl-remove-if-not
(function (lambda (w) (= (vfl-winkel-als-zahl w) gwinkel-grad)))
winkel-werte))
gseite)))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; Nur Winkel (30/90), OHNE Seite - fuer "QC Linie mit GF" (vfl-qc-linie-
;; mit-gf): deren ES-Seite = AS-Seite ist bereits erzwungen, eine Seiten-
;; Frage im selben Dialog waere irrefuehrend. Schiebt "90"/"30" (den
;; AUFGELOESTEN Wert, kein Index - vf-frage-element-winkel wird beim
;; Pending-Konsum NICHT mehr durchlaufen) in die Pending-Queue.
(defun vflw-gruppe-qc-winkel-impl (kopf-key / dat ergebnis gwinkel)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_qc_winkel" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text kopf-key)))
(action_tile "accept"
"(setq gwinkel (if (= (get_tile \"winkel_30\") \"1\") \"30\" \"90\"))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1) (vflw-pending-push-all (list gwinkel)))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; ------------------------------------------------------------
;; Gruppe "Vario-Kurve": Winkel(90/60/30) + Seite + Variante(aussen/innen).
;; Reihenfolge/Journal: vfl-menu-winkel (Winkel als ECHTER Wert 90/60/30, als
;; INT journalisiert - kein Index mehr), vfl-in-string (Seite), vfl-in-string
;; (Variante).
(defun vflw-gruppe-variokurve-impl ( / dat dcl-pfad ergebnis gwinkel gseite gvariante)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_variokurve" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text "vfl-variokurve-winkel-header")))
(set_tile "winkel_90" "1")
(set_tile "seite_links" "1")
(set_tile "variante_innen" "1")
(action_tile "accept"
"(setq gwinkel (cond ((= (get_tile \"winkel_30\") \"1\") 30)
((= (get_tile \"winkel_60\") \"1\") 60)
(t 90)))
(setq gseite (if (= (get_tile \"seite_rechts\") \"1\") \"2\" \"1\"))
(setq gvariante (if (= (get_tile \"variante_innen\") \"1\") \"2\" \"1\"))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1) (vflw-pending-push-all (list gwinkel gseite gvariante)))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; ------------------------------------------------------------
;; Gruppe "Gefaelle festlegen" (Linie GF): Modus(Hoehe/Winkel) + EIN
;; Wertfeld (Beschriftung wechselt mit dem Modus). KEIN Punkt-Feld: die
;; Laenge/Fahrtrichtung ist an der Aufrufstelle bereits VOR dieser Gruppe
;; ueber vfl-neue-linie-messen/vfl-in-abstand als deltaL/hz ermittelt (nur
;; noch ein Skalarpaar, kein Punkt mehr - siehe Kommentar bei
;; vfl-in-abstand) - dessen Z-Konzept existiert nicht mehr, "Hoehe" hier ist
;; ein eigener, unabhaengiger Zahlenwert. Original-Reihenfolge/-Journal ab hier:
;; vfl-in-string (Modus), danach vfl-in-real (Winkel ODER Hoehe).
;; nur-winkel: T fuer den "Zielhoehe zuerst festlegen"-Zweig von "Neue Linie:
;; GF" (Zielhoehe ist dort BEREITS fix, deltaL wird erst aus Winkel+Hoehe
;; berechnet) - "Gegebene Hoehe" ergibt dann keinen Sinn mehr und wird per
;; mode_tile gesperrt, der Hinweistext zeigt stattdessen, dass deltaL
;; berechnet wird. Konventionswahl dieses Projekts (siehe vf-linienzug-modus,
;; wahl="Linie-GF"): Option "1" = Hoehe, Option "2" = Winkel - Hoehe ist
;; daher hier Default, nicht der DCL-eigene value="1" bei modus_winkel.
;; winkel-als-default: T, wenn (bei nicht gesperrter Hoehe-Option) trotzdem
;; der Neigungswinkel statt der Zielhoehe vorausgewaehlt sein soll - genutzt
;; von der Aufrufstelle "Zielhoehe zuerst" (die dortige Nachfrage ist zwar
;; textlich "Hoehe zuerst", die pragmatische Standard-Antwort ist aber meist
;; der direkt vorgegebene Neigungswinkel).
(defun vflw-gruppe-gefaelle-impl (hoehe-default nur-winkel winkel-als-default /
dat dcl-pfad ergebnis gmodus gwert winkel-default)
(setq winkel-default (ssg-cfg-or "vario" "gefaelle_winkel" 3))
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_gefaelle" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text "vfl-gefaelle-festlegen-header")))
(cond
(nur-winkel
(set_tile "modus_winkel" "1")
(set_tile "wlabel" "Neigungswinkel (Grad):")
(set_tile "wert" (rtos (float winkel-default) 2 1))
(mode_tile "modus_hoehe" 1)
(set_tile "hinweis" (ssg-text "vfl-gf-hinweis-laenge-berechnet")))
(winkel-als-default
(set_tile "modus_winkel" "1")
(set_tile "wlabel" "Neigungswinkel (Grad):")
(set_tile "wert" (rtos (float winkel-default) 2 1)))
(t
(set_tile "modus_hoehe" "1")
(set_tile "wlabel" "Zielhoehe (Z, mm):")
(set_tile "wert" (rtos hoehe-default 2 1))))
;; Wertfeld MUSS beim Moduswechsel mit umschalten - sonst steht dort
;; nach dem Wechsel noch der Wert des vorherigen Modus (Hoehe/Winkel
;; sind nicht dieselbe Groesse, ein stehengebliebener Hoehenwert waere
;; als Neigungswinkel unsinnig).
(action_tile "modus_hoehe"
"(set_tile \"wlabel\" \"Zielhoehe (Z, mm):\")
(set_tile \"wert\" (rtos hoehe-default 2 1))")
(action_tile "modus_winkel"
"(set_tile \"wlabel\" \"Neigungswinkel (Grad):\")
(set_tile \"wert\" (rtos (float winkel-default) 2 1))")
(action_tile "accept"
"(setq gmodus (if (= (get_tile \"modus_winkel\") \"1\") \"2\" \"1\"))
(setq gwert (get_tile \"wert\")) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1)
(if nur-winkel
(vflw-pending-push-all (list (atof gwert)))
(vflw-pending-push-all (list gmodus (atof gwert)))))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; "Neue Linie: GF" - Vorfrage "Wie soll die Laenge bestimmt werden?"
;; (Endpunkt/Zielhoehe/Steigende GF). Die verschachtelte Unterfrage bei
;; Zweig C (Endpunkt vs. Zielhoehe, nur 2 Optionen) hat einen EIGENEN,
;; 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)
(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")))
(action_tile "accept"
"(setq gwahl (cond ((= (get_tile \"modus_hoehe\") \"1\") \"2\")
((= (get_tile \"modus_steigend\") \"1\") \"3\")
((= (get_tile \"modus_qc\") \"1\") \"4\")
(t \"1\")))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1) (vflw-pending-push-all (list gwahl)))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; Zweig C ("Steigende GF") Unterfrage Endpunkt/Zielhoehe - dieselben 2
;; Optionen wie vflw-gruppe-gf-laenge-modus-impl, aber ohne die 3. Kachel
;; "Steigend" (siehe dcl/vf_linienzug_wizard.dcl, vflw_gf_steigend_untermodus).
;; Lief bisher mangels eigenem Dialog auf den generischen Dropdown-Fallback
;; (vflw-wahl) hinaus.
(defun vflw-gruppe-gf-steigend-untermodus-impl ( / dat ergebnis gwahl)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_gf_steigend_untermodus" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text "vfl-gf-laenge-modus-header")))
(action_tile "accept"
"(setq gwahl (if (= (get_tile \"modus_hoehe\") \"1\") \"2\" \"1\"))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1) (vflw-pending-push-all (list gwahl)))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; "Neue Linie: GF" - Separator VOR/NACH (2 Ja/Nein-Toggles in einem
;; Bildschirm). Siehe dcl/vf_linienzug_wizard.dcl (vflw_sep_gf).
(defun vflw-gruppe-sep-gf-impl ( / dat ergebnis gsepvor gsepnach)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_sep_gf" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text "vfl-sep-gf-header")))
(set_tile "sepvor" "0")
(set_tile "sepnach" "0")
(action_tile "accept"
"(setq gsepvor (get_tile \"sepvor\")) (setq gsepnach (get_tile \"sepnach\")) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1)
(vflw-pending-push-all
(list (if (= gsepvor "1") "1" "2") (if (= gsepnach "1") "1" "2"))))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; ------------------------------------------------------------
;; Gruppe "Ziel-Hoehe": NUR die Hoehen-Zahl (kein Punkt-Feld). Wiederverwendet
;; an mehreren Stellen (Linie-VF, VF-Einheit-Fortsetzung "Auf/Ab",
;; vfl-body-abschluss). Die Segmentlaenge ist an der Aufrufstelle bereits
;; separat ueber vfl-neue-linie-messen/vfl-in-abstand als deltaL (Skalar,
;; kein Punkt mehr) ermittelt - die Hoehe hier ist eine davon unabhaengige
;; Zahl (Default = aktuelle Kettenhoehe, wie im Original-Prompt). Nutzt
;; bewusst den generischen Einzel-Dialog vflw_zahl (kein Punkt-Button, der
;; faelschlich eine hier moegliche Punktwahl suggerieren wuerde).
;; hoehe-default: entweder eine Zahl (aktuelle Kettenhoehe, alte Vorbelegung -
;; die bei direkter Uebernahme immer als deltaH=0 abgelehnt wird), ODER eine
;; Liste (hoehe-ab hoehe-auf hoehe-kette [hoehe-pick]) mit zwei baubaren
;; Vorschlaegen (siehe vfl-vf-hoehe-vorschlaege/vfl-segment-hoehe-vorschlaege)
;; - dann zeigt der Kopftext beide Richtungen. Vorbelegt wird, in dieser
;; Reihenfolge: hoehe-pick (die tatsaechliche Z-Hoehe des zuvor per Objektfang
;; gepickten Endpunkts, siehe *vfl-letzter-pick-z*/vfl-hoehe-endpunkt-abfragen
;; - der Nutzer hat damit meist schon die gewuenschte Zielhoehe angeklickt),
;; sonst die Ab-Variante (haeufigerer Fall, siehe vfl-segment-entscheidung).
;; Ist weder hoehe-pick noch hoehe-ab vorhanden, faellt die Vorbelegung auf
;; die unveraenderte Kettenhoehe zurueck (altes Verhalten) plus Warnhinweis.
(defun vflw-gruppe-ziel-hoehe-impl (hoehe-default / dat dcl-pfad ergebnis dlg-wert
zwei-werte hoehe-ab hoehe-auf hoehe-kette
hoehe-pick vorbelegt)
(setq zwei-werte (listp hoehe-default))
(if zwei-werte
(progn
(setq hoehe-ab (nth 0 hoehe-default) hoehe-auf (nth 1 hoehe-default))
(setq hoehe-kette (nth 2 hoehe-default) hoehe-pick (nth 3 hoehe-default))
(setq vorbelegt (cond (hoehe-pick hoehe-pick) (hoehe-ab hoehe-ab) (t hoehe-kette))))
(setq vorbelegt hoehe-default))
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_zahl" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf"
(vflw-clean
(cond
((and zwei-werte hoehe-ab hoehe-auf hoehe-pick)
(ssg-textf "vfl-prompt-hoehe-endpunkt-3"
(list (rtos hoehe-ab 2 1) (rtos hoehe-auf 2 1) (rtos hoehe-pick 2 1))))
((and zwei-werte hoehe-ab hoehe-auf)
(ssg-textf "vfl-prompt-hoehe-endpunkt-2"
(list (rtos hoehe-ab 2 1) (rtos hoehe-auf 2 1))))
(t (ssg-textf "vfl-prompt-hoehe-endpunkt" (list (rtos vorbelegt 2 1)))))))
(set_tile "wert" (rtos vorbelegt 2 1))
(action_tile "accept" "(setq dlg-wert (get_tile \"wert\")) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((and (= ergebnis 1) dlg-wert (> (strlen dlg-wert) 0))
(vflw-pending-push-all (list (atof dlg-wert))))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
((= ergebnis 0) (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; ============================================================
;; TEIL 0b-4a: WEITERE GRUPPEN-DIALOGE (Modus 1, Radio-Buttons statt Dropdown)
;; ============================================================
;; Text in maximal max-zeilen Zeilen von je hoechstens max-len Zeichen
;; umbrechen (an Wortgrenzen; bereits vorhandene "\n" bleiben als Zeilenumbruch
;; erhalten) - fuer DCL-text-Tiles, die selbst NICHT automatisch umbrechen
;; (einzeiliges Tile, laengerer Text wird sonst stillschweigend abgeschnitten -
;; siehe vflw-gruppe-ja-nein-impl). Ueberzaehlige Zeilen werden auf eine "..."-
;; Zeile gekuerzt statt verworfen, damit sichtbar bleibt, dass Text fehlt.
(defun vflw-umbruch-zeilen (text max-len max-zeilen / roh-zeilen zeilen woerter aktuelle i gekuerzt)
(setq roh-zeilen (vfl-kette-split text "\n"))
(setq zeilen '())
(foreach roh roh-zeilen
(setq woerter (vfl-kette-split roh " ") aktuelle nil)
(foreach wort woerter
(cond
((= wort ""))
((null aktuelle) (setq aktuelle wort))
((<= (+ (strlen aktuelle) 1 (strlen wort)) max-len)
(setq aktuelle (strcat aktuelle " " wort)))
(t (setq zeilen (append zeilen (list aktuelle)))
(setq aktuelle wort))))
(setq zeilen (append zeilen (list (if aktuelle aktuelle "")))))
(if (> (length zeilen) max-zeilen)
(progn
(setq gekuerzt '() i 0)
(repeat (1- max-zeilen)
(setq gekuerzt (append gekuerzt (list (nth i zeilen))) i (1+ i)))
(setq zeilen (append gekuerzt (list "...")))))
zeilen)
;; ------------------------------------------------------------
;; Generische Ja/Nein-Rueckfrage (2 Radio-Buttons, dynamischer Kopftext ueber
;; MEHRERE Zeilen - siehe vflw-umbruch-zeilen, ein einzelnes text-Tile schneidet
;; laengere Hinweise sonst ab) - wiederverwendet ueberall dort, wo bisher eine
;; einzelne Ja/Nein-Frage auf den generischen Dropdown (vflw_wahl)
;; zurueckgefallen waere, z.B. "trotzdem mit Mindestlaenge bauen?"
;; (Zu-kurz-Nachfrage in vfl-baue-horizontal-koerper).
(defun vflw-gruppe-ja-nein-impl (kopf-txt / dat ergebnis gwahl zeilen zeile i dlg-name max-zeilen)
;; Zeilenzahl VOR new_dialog ermitteln - waehlt eine von drei Groessen-
;; varianten (1/3/6 Kopf-Zeilen), damit kurze Hinweise nicht mit leeren
;; Zeilen aufgeblaeht werden (siehe Kommentar bei den vflw_ja_nein_*-
;; Dialogen im DCL).
(setq zeilen (vflw-umbruch-zeilen (vflw-clean kopf-txt) 70 6))
(cond
((<= (length zeilen) 1) (setq dlg-name "vflw_ja_nein_1" max-zeilen 1))
((<= (length zeilen) 3) (setq dlg-name "vflw_ja_nein_3" max-zeilen 3))
(t (setq dlg-name "vflw_ja_nein_6" max-zeilen 6)))
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog dlg-name dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(setq i 1)
(foreach zeile zeilen
(set_tile (strcat "kopf" (itoa i)) zeile)
(setq i (1+ i)))
(while (<= i max-zeilen) (set_tile (strcat "kopf" (itoa i)) "") (setq i (1+ i)))
;; Default "Nein" (nicht der DCL-eigene "Ja"-Default): alle bisherigen
;; Aufrufstellen (Separator-Zusatzfragen) haben auch im Konsolen-Pfad
;; "Nein" als Vorgabe (vfl-menu default-idx=2).
(set_tile "wahl_nein" "1")
(action_tile "accept" "(setq gwahl (get_tile \"wahl_nein\")) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1)
;; Konsolen-Echo: im Wizard-Modus kommt die Antwort aus dem Dialog,
;; nicht aus einer getippten Konsolen-Eingabe - ohne dieses Echo
;; steht im (Debug-)Log/Konsolen-Log nirgends, was hier gewaehlt
;; wurde.
(princ (ssg-textf "vfl-wizard-antwort-echo"
(list (ssg-text (if (= gwahl "1") "vfl-antwort-nein" "vfl-antwort-ja")))))
(vflw-pending-push-all (list (if (= gwahl "1") "2" "1"))))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; Generische Auf/Ab-Richtungsfrage (2 Radio-Buttons, dynamischer Kopftext) -
;; wiederverwendet fuer die Kletter-Teilstrecke ohne Zielhoehe, sowohl
;; "Naechstes im VF" als auch "Neue Linie: Ab/Auf VF" mit reduziertem
;; Eingang.
(defun vflw-gruppe-richtung-impl (kopf-txt / dat ergebnis grichtung)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_richtung" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean kopf-txt))
(set_tile "richtung_auf" "1")
(action_tile "accept" "(setq grichtung (get_tile \"richtung_ab\")) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1) (vflw-pending-push-all (list (if (= grichtung "1") "2" "1"))))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; Startpunkt-Typ (kein eindeutiger GF-/VF-Baustein per KS_AUS-Scan erkannt,
;; siehe vfl-eingang-modus-bestimmen): EINE flache 3-Optionen-Frage (direkt
;; Umlenkstation / GF-Stueck+Umlenkstation / ohne Umlenkstation) statt
;; verschachtelter Zwei-Schritt-Abfrage.
(defun vflw-gruppe-startpunkt-typ-impl ( / dat ergebnis gwahl)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_startpunkt_typ" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-textf "vfl-frage-startpunkt-typ" (list (rtos *vfl-gf-min-laenge* 2 0)))))
(set_tile "eingang_direkt" "1")
(action_tile "accept"
"(setq gwahl (cond ((= (get_tile \"eingang_stumpf\") \"1\") \"2\")
((= (get_tile \"eingang_ohne\") \"1\") \"3\")
(t \"1\")))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1) (vflw-pending-push-all (list gwahl)))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; "Ist das Kettenende?" (2-Optionen-Variante: Ja-Separator+ES / Nein-
;; weiterbauen) - vfl-vf-einheit-abschluss.
(defun vflw-gruppe-ist-kettenende-2opt-impl ( / dat ergebnis gwahl)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_ist_kettenende_2opt" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text "vfl-ist-kettenende-frage")))
(action_tile "accept" "(setq gwahl (get_tile \"wahl_ja\")) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1) (vflw-pending-push-all (list (if (= gwahl "1") "1" "2"))))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; --- Modus 2: Lauf-Abschluss (NUR wenn der VF-Lauf das Pfadende ist) ---
;; "1"=ohne Motorstation (offenes Ende) / "2"=Motorstation setzen (kein ES) /
;; "3"=Motorstation + ES-Element (Kettenende). Plain ok_cancel wie alle
;; anderen Modus-2-Segment-Dialoge - kein Zurueck/Abbrechen-Mechanismus.
;; es-frueh-abgelehnt: die Upfront-Frage ("ES-Element setzen?") wurde bereits
;; mit "Nein" beantwortet - Option 3 dann deaktivieren, sonst widerspricht sie
;; der schon getroffenen Entscheidung (Nutzer-Feedback).
(defun vflw-m2-lauf-abschluss-impl (kopf-txt es-frueh-abgelehnt / dat ergebnis gwahl)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_m2_lauf_abschluss" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean kopf-txt))
(set_tile "abschluss_motor" "1")
(if es-frueh-abgelehnt (mode_tile "abschluss_motor_es" 1))
(action_tile "accept"
"(setq gwahl (cond ((= (get_tile \"abschluss_offen\") \"1\") \"1\")
((= (get_tile \"abschluss_motor_es\") \"1\") \"3\")
(t \"2\")))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (= ergebnis 1) (vflw-pending-push-all (list gwahl)))))
(princ))
;; --- Modus 2: Lauf-Anfang (NUR wenn der VF-Lauf der Pfadanfang ist) ---
;; "1"=ohne Umlenkstation (offener Anfang) / "2"=Umlenkstation direkt (kein
;; GF1/Separator) / "3"=Umlenkstation mit GF1+Separator (heutiges Standard-
;; verhalten). Plain ok_cancel, analog vflw-m2-lauf-abschluss-impl.
(defun vflw-m2-lauf-anfang-impl (kopf-txt / dat ergebnis gwahl)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_m2_lauf_anfang" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean kopf-txt))
(set_tile "anfang_umlenk_gf1" "1")
(action_tile "accept"
"(setq gwahl (cond ((= (get_tile \"anfang_offen\") \"1\") \"1\")
((= (get_tile \"anfang_umlenk\") \"1\") \"2\")
(t \"3\")))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (= ergebnis 1) (vflw-pending-push-all (list gwahl)))))
(princ))
;; GF-Verteilung (Haelfte Einlauf+Ausgang / alles Einlauf) - kopf-txt dynamisch,
;; wiederverwendet fuer Modus 1 (vfl-vf-einheit-abschluss) UND Modus 3
;; (Segment-Loop br-gf-mode). Die beiden Aufrufer kodieren die Antwort
;; UNTERSCHIEDLICH (Modus 1: "1"=Haelfte/"2"=Einlauf; Modus 3: "2"=Haelfte/
;; "1"=Einlauf) - haelfte-code/einlauf-code legen das je Aufrufer fest.
(defun vflw-gruppe-gf-verteilung-impl (kopf-txt haelfte-code einlauf-code / dat ergebnis gwahl)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_gf_verteilung" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean kopf-txt))
(action_tile "accept" "(setq gwahl (get_tile \"wahl_haelfte\")) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1) (vflw-pending-push-all (list (if (= gwahl "1") haelfte-code einlauf-code))))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; "Naechstes im VF" (innerhalb einer laufenden VF-Einheit weiterbauen):
;; Horizontaler Foerderer / Vario-Kurve / Auf-Ab-Foerderer.
(defun vflw-gruppe-naechstes-im-vf-impl ( / dat ergebnis gwahl)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_naechstes_im_vf" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text "vfl-naechstes-element-vf")))
(action_tile "accept"
"(setq gwahl (cond ((= (get_tile \"wahl_horizontal\") \"1\") \"1\")
((= (get_tile \"wahl_variokurve\") \"1\") \"2\")
(t \"3\")))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1) (vflw-pending-push-all (list gwahl)))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; Feste Zuordnung Standard-Winkel -> Radio-Tile-Key, gemeinsam genutzt von
;; vflw-gruppe-winkel-hoehen-impl und vflw-gruppe-winkel-wahl-impl (beide
;; Dialoge nutzen dieselben 12 Slots w_3..w_51).
(setq *vflw-winkel-tile-keys*
'((3 . "w_3") (6 . "w_6") (9 . "w_9") (12 . "w_12") (15 . "w_15") (18 . "w_18")
(21 . "w_21") (27 . "w_27") (33 . "w_33") (39 . "w_39") (45 . "w_45") (51 . "w_51")))
;; Winkel/Hoehen-Tabelle (vfl-waehle-winkel-hoehen-tabelle): optionen
;; = ALLE konfigurierten Winkel (winkel dH-oder-nil L_VF-oder-nil gueltig),
;; gueltige = die daraus gefilterten baubaren. Fuer JEDEN der 12 Radio-Slots
;; wird das Text-Tile daneben befuellt: baubar -> Endhoehe, nicht baubar ->
;; Grund (wie die Konsolen-Zeile) - und NUR die baubaren werden per mode_tile
;; freigeschaltet. Pusht die POSITION (1-basiert) des gewaehlten Winkels
;; INNERHALB von gueltige, identisch zu dem, was vfl-menu-int an dieser
;; Aufrufstelle sonst liefern wuerde.
(defun vflw-gruppe-winkel-hoehen-impl (optionen gueltige start-z / dat ergebnis e gwinkel
tk htk zeile idx gefunden)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_winkel_hoehen" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text "vfl-winkel-hoehen-header")))
(foreach e optionen
(setq tk (cdr (assoc (car e) *vflw-winkel-tile-keys*)))
(if tk
(progn
(setq htk (strcat "h_" (substr tk 3)))
(if (nth 3 e)
(progn
(set_tile htk (ssg-textf "vfl-winkel-hoehen-zeile-tile" (list (rtos (+ start-z (nth 1 e)) 2 0))))
(mode_tile tk 0)
)
(progn
(set_tile htk (ssg-text "vfl-winkel-zeile-tile-ungueltig"))
(mode_tile tk 1)
)
)
)
)
)
(setq gefunden nil)
(foreach e gueltige
(setq tk (cdr (assoc (car e) *vflw-winkel-tile-keys*)))
(if (and tk (not gefunden)) (progn (set_tile tk "1") (setq gefunden t)))
)
(action_tile "accept"
"(setq gwinkel (cond
((= (get_tile \"w_3\") \"1\") 3) ((= (get_tile \"w_6\") \"1\") 6)
((= (get_tile \"w_9\") \"1\") 9) ((= (get_tile \"w_12\") \"1\") 12)
((= (get_tile \"w_15\") \"1\") 15) ((= (get_tile \"w_18\") \"1\") 18)
((= (get_tile \"w_21\") \"1\") 21) ((= (get_tile \"w_27\") \"1\") 27)
((= (get_tile \"w_33\") \"1\") 33) ((= (get_tile \"w_39\") \"1\") 39)
((= (get_tile \"w_45\") \"1\") 45) (t 51)))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1)
(setq idx 1 zeile nil)
(foreach e gueltige
(if (= (car e) gwinkel) (setq zeile idx))
(setq idx (1+ idx)))
(if zeile (vflw-pending-push-all (list zeile))))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; Winkel-Wahl aus berechne-alle-winkel (Zielhoehe-basierter Solve):
;; ergebnis-liste = ALLE konfigurierten Winkel (winkel L_GF L_VF gueltig),
;; gueltige = die daraus gefilterten baubaren (Filter komplexer als nur der
;; gueltig-Flag - siehe vfl-waehle-winkel: zusaetzlich L_GF/L_VF > 0 -
;; deshalb hier NICHT (nth 3 e) pruefen, sondern per gueltige-winkel-Liste
;; abgleichen). Fuer JEDEN der 12 Radio-Slots wird das Text-Tile daneben
;; befuellt: baubar -> L_GF+L_VF, nicht baubar -> "nicht baubar" - und NUR
;; die baubaren werden per mode_tile freigeschaltet. Pusht die POSITION
;; (1-basiert) des gewaehlten Winkels INNERHALB von gueltige.
(defun vflw-gruppe-winkel-wahl-impl (ergebnis-liste gueltige / dat ergebnis e gwinkel idx
tk htk zeile gefunden gueltige-winkel)
(setq gueltige-winkel (mapcar 'car gueltige))
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_winkel_wahl" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text "vfl-mehrere-winkel-header")))
(foreach e ergebnis-liste
(setq tk (cdr (assoc (car e) *vflw-winkel-tile-keys*)))
(if tk
(progn
(setq htk (strcat "h_" (substr tk 3)))
(if (member (car e) gueltige-winkel)
(progn
(set_tile htk (ssg-textf "vfl-winkel-wahl-zeile-tile" (list (rtos (cadr e) 2 1) (rtos (caddr e) 2 1))))
(mode_tile tk 0)
)
(progn
(set_tile htk (ssg-text "vfl-winkel-zeile-tile-ungueltig"))
(mode_tile tk 1)
)
)
)
)
)
(setq gefunden nil)
(foreach e gueltige
(setq tk (cdr (assoc (car e) *vflw-winkel-tile-keys*)))
(if (and tk (not gefunden)) (progn (set_tile tk "1") (setq gefunden t)))
)
(action_tile "accept"
"(setq gwinkel (cond
((= (get_tile \"w_3\") \"1\") 3) ((= (get_tile \"w_6\") \"1\") 6)
((= (get_tile \"w_9\") \"1\") 9) ((= (get_tile \"w_12\") \"1\") 12)
((= (get_tile \"w_15\") \"1\") 15) ((= (get_tile \"w_18\") \"1\") 18)
((= (get_tile \"w_21\") \"1\") 21) ((= (get_tile \"w_27\") \"1\") 27)
((= (get_tile \"w_33\") \"1\") 33) ((= (get_tile \"w_39\") \"1\") 39)
((= (get_tile \"w_45\") \"1\") 45) (t 51)))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1)
(setq idx 1 zeile nil)
(foreach e gueltige
(if (= (car e) gwinkel) (setq zeile idx))
(setq idx (1+ idx)))
(if zeile (vflw-pending-push-all (list zeile))))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; "Naechstes Element waehlen" - Hauptmenue des Linienzug-Aufbaus (Modus 1).
;; Kettenanfang (frame=nil): alle 6 Optionen waehlbar, Vario-Kurve inklusive
;; (kann nur als allererstes Kettenglied gewaehlt werden - siehe Kommentar an
;; der Aufrufstelle). Sonst (Frame vorhanden): Vario-Kurve per mode_tile
;; deaktiviert, die uebrigen 5 Optionen behalten dieselbe Reihenfolge/
;; Bedeutung wie im Konsolen-Menue, nur ohne die Luecke. Pusht die
;; POSITIONELLE Antwort ("1".."6" bzw. "1".."5"), identisch zur bisherigen
;; Konsolen-Nummerierung, damit Journal/Replay unveraendert bleiben - die
;; Radio-Buttons selbst tragen daher STABILE, kontextunabhaengige Keys
;; (wahl_gfbogen usw.), waehrend die Umrechnung auf die richtige Positions-
;; Nummer hier im Callback passiert (kettenanfang: 1:1: 1-6; sonst: Slot 2
;; entfaellt, alles ab Slot 3 ruecktr um 1 nach vorn).
;; familie: bei Kettenanfang das Ergebnis der KS_AUS-Erkennung ("GF"/"VF"/nil)
;; - eindeutig als VF erkannt (Startpunkt liegt auf einer bestehenden
;; VF-Kette) macht "GF-Bogen"/"Neue Linie: GF" als DIREKTE Fortsetzung keinen
;; Sinn (kein GF-Baustein wird an dieser Stelle einfach an ein VF-Teil
;; angeflanscht) - diese beiden Optionen werden dann ausgegraut. "Neue Linie
;; BIS Kettenende" bleibt immer waehlbar (hat ihre eigene GF/VF-Erkennung).
(defun vflw-gruppe-naechstes-element-impl (kettenanfang familie / dat ergebnis slot gwahl)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_naechstes_element" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text "vfl-naechstes-element-header")))
(set_tile "wahl_liniegf" "1")
(if (not kettenanfang) (mode_tile "wahl_variokurve" 1))
(if (and kettenanfang (equal familie "VF"))
(progn
(mode_tile "wahl_gfbogen" 1)
(mode_tile "wahl_liniegf" 1)
(set_tile "wahl_variokurve" "1")))
(action_tile "accept"
"(setq slot (cond ((= (get_tile \"wahl_gfbogen\") \"1\") 1)
((= (get_tile \"wahl_variokurve\") \"1\") 2)
((= (get_tile \"wahl_linievf\") \"1\") 4)
((= (get_tile \"wahl_horizontalvf\") \"1\") 5)
((= (get_tile \"wahl_linieende\") \"1\") 6)
(t 3)))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1)
(setq gwahl (if (or kettenanfang (<= slot 2)) slot (1- slot)))
(vflw-pending-push-all (list (itoa gwahl))))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
(princ))
;; ============================================================
;; TEIL 0b-4b: SEGMENT-GRUPPEN-DIALOGE (Modus 2 + Modus 3)
;; ============================================================
;; Modus 2 und Modus 3 gehen die Kette Segment fuer Segment durch und stellen
;; je Segment mehrere unmittelbar aufeinanderfolgende Fragen (Typ + ggf.
;; Neigung/Vario-Winkel/Variante). Diese werden - wie die uebrigen Gruppen -
;; im Wizard in EINEM Dialog erfasst und die Antworten in *vflw-pending*
;; abgelegt; die unveraenderten vfl-in-*/vfl-menu-Aufrufe im Segment-Loop
;; konsumieren sie danach der Reihe nach. Der Dialog-Kopf zeigt immer
;; "Segment i/n ..." (wie die Konsolen-Frage). Das jeweils abgefragte Segment
;; wird VOR dem Dialog per vfl-seg-highlight in der Zeichnung hervorgehoben.
;;
;; Highlight: Segment-Entity (LINE/ARC) hervorheben (redraw-Modus 3) bzw.
;; zuruecksetzen (0). ent darf nil sein (dann No-op) - der Aufrufer uebergibt
;; (car (nth i kette)) als VLA-Objekt; wir wandeln in einen Ename fuer redraw.
(defun vfl-seg-highlight (ent an / en)
(if ent
(progn
(setq en (vl-catch-all-apply 'vlax-vla-object->ename (list ent)))
(if (and en (not (vl-catch-all-error-p en)))
(redraw en (if an 3 0)))))
(princ))
;; Kopfzeile "Segment i/n: <Text>" fuer die Segment-Dialoge (i 1-basiert).
(defun vfl-seg-kopf (i n txt)
(strcat "Segment " (itoa i) "/" (itoa n) ": " txt))
;; --- Modus 2, Segment "Gerade": Typ (GF/VF) + [bei GF] Neigung ---
;; Original-Reihenfolge/-Journal im Loop: vfl-menu (Typ), dann bei GF
;; vfl-in-real (Neigung). Bei VF wird KEIN Neigungswert gefragt -> nur "2"
;; in die Pending-Queue. neigung-default: Vorbelegung Neigungsfeld (Grad).
(defun vflw-seg-linie-m2-impl (kopf-txt neigung-default / dat ergebnis gtyp gwert gsepvor gsepnach)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_seg_linie_m2" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean kopf-txt))
(set_tile "typ_gf" "1")
(set_tile "wert" (rtos (float neigung-default) 2 1))
;; Neigungsfeld + Separator-Toggles nur bei GF aktiv.
(action_tile "typ_gf"
"(mode_tile \"wert\" 0) (mode_tile \"sep_vor\" 0) (mode_tile \"sep_nach\" 0)")
(action_tile "typ_vf"
"(mode_tile \"wert\" 1) (mode_tile \"sep_vor\" 1) (mode_tile \"sep_nach\" 1)")
(action_tile "accept"
"(setq gtyp (if (= (get_tile \"typ_gf\") \"1\") \"0\" \"1\"))
(setq gwert (get_tile \"wert\"))
(setq gsepvor (get_tile \"sep_vor\"))
(setq gsepnach (get_tile \"sep_nach\"))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (= ergebnis 1)
(if (= gtyp "0")
;; GF + Neigung + Separator VOR/NACH (Ja/Nein je "1"/"0")
(vflw-pending-push-all
(list "1" (atof gwert) (if (= gsepvor "1") "1" "2") (if (= gsepnach "1") "1" "2")))
(vflw-pending-push-all (list "2")))))) ; VF (keine Neigung/Separator)
(princ))
;; --- Modus 3, Segment "Gerade": Typ (GF/VF-Ab/VF-Auf/VF-Hor) + Wert ---
;; Original-Reihenfolge/-Journal im Loop: vfl-in-string (Typ "1".."4"), dann
;; bei GF vfl-in-real (Neigung), bei VF-Ab/Auf vfl-in-int (Vario-Winkel), bei
;; VF-Hor KEINE weitere Frage. neigung-default/vario-default: Vorbelegungen.
(defun vflw-seg-linie-m3-impl (kopf-txt neigung-default vario-default /
dat ergebnis gtyp gwert)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_seg_linie_m3" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean kopf-txt))
(set_tile "typ_gf" "1")
(set_tile "wlabel" "GF-Neigung (Grad):")
(set_tile "wert" (rtos (float neigung-default) 2 1))
;; Feld/Beschriftung schaltet mit dem Typ um: GF->Neigung, VF-Ab/Auf->
;; Vario-Winkel, VF-Hor->deaktiviert. Werte-Defaults liegen als lokale
;; Variablen vor und werden im Callback (dynamic scope) gelesen.
(action_tile "typ_gf"
"(set_tile \"wlabel\" \"GF-Neigung (Grad):\")
(set_tile \"wert\" (rtos (float neigung-default) 2 1))
(mode_tile \"wert\" 0)")
(action_tile "typ_vfab"
"(set_tile \"wlabel\" \"Vario-Winkel (Grad):\")
(set_tile \"wert\" (rtos (float vario-default) 2 1))
(mode_tile \"wert\" 0)")
(action_tile "typ_vfauf"
"(set_tile \"wlabel\" \"Vario-Winkel (Grad):\")
(set_tile \"wert\" (rtos (float vario-default) 2 1))
(mode_tile \"wert\" 0)")
(action_tile "typ_vfhor"
"(set_tile \"wlabel\" \"(kein Wert)\")
(mode_tile \"wert\" 1)")
(action_tile "accept"
"(setq gtyp (cond ((= (get_tile \"typ_gf\") \"1\") \"0\")
((= (get_tile \"typ_vfab\") \"1\") \"1\")
((= (get_tile \"typ_vfauf\") \"1\") \"2\")
(t \"3\")))
(setq gwert (get_tile \"wert\")) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (= ergebnis 1)
(cond
;; typ 0 = GF -> "1" + Neigung(real)
((= gtyp "0") (vflw-pending-push-all (list "1" (atof gwert))))
;; typ 1 = VF-Ab -> "2" + Vario-Winkel(int)
((= gtyp "1") (vflw-pending-push-all (list "2" (fix (atof gwert)))))
;; typ 2 = VF-Auf -> "3" + Vario-Winkel(int)
((= gtyp "2") (vflw-pending-push-all (list "3" (fix (atof gwert)))))
;; typ 3 = VF-Hor -> "4" (kein Wert)
(t (vflw-pending-push-all (list "4")))))))
(princ))
;; --- Segment "Eck/Bogen" (Modus 2 + Modus 3): Typ + [bei Vario] Variante ---
;; Original-Reihenfolge/-Journal: vfl-in-string/vfl-menu (Typ "1"/"2"), dann
;; bei Vario-Kurve vfl-in-string/vfl-menu (Variante "1"/"2"). Bei GF-Bogen
;; wird KEINE Variante gefragt -> nur "1" in die Pending-Queue.
;; vario-erlaubt: T, wenn Vario-Kurve hier zulaessig ist (Modus 2 immer T;
;; Modus 3 nur im offenen VF-Lauf - sonst konsumiert der Aufrufer die
;; Variante-Antwort nicht und sie wuerde ins naechste Segment lecken). Bei
;; nil wird die Vario-Kurve-Option deaktiviert und bei versehentlicher Wahl
;; als GF-Bogen ("1") behandelt (der Aufrufer weist Vario dort ohnehin ab).
(defun vflw-seg-bogen-impl (kopf-txt vario-erlaubt / dat ergebnis gtyp gvariante)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_seg_bogen" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean kopf-txt))
(if (eq vario-erlaubt 'ERZWUNGEN)
(progn
(set_tile "typ_vario" "1")
(mode_tile "typ_gfbogen" 1) ; GF-Bogen gesperrt - Vario ist Pflicht
(mode_tile "variante_aussen" 0) ; Variante sofort aktiv
(mode_tile "variante_innen" 0))
(progn
(set_tile "typ_gfbogen" "1")
(set_tile "variante_aussen" "1")
(mode_tile "variante_aussen" 1) ; Variante erst bei Vario-Kurve aktiv
(mode_tile "variante_innen" 1)
;; Vario-Kurve-Option selbst nur anklickbar, wenn erlaubt (Modus 3
;; ausserhalb eines offenen VF-Laufs: deaktiviert - ihr Label kann
;; hier NICHT auf "(nur im VF-Lauf)" umbeschriftet werden, radio_button-
;; Labels sind fest, siehe DCL-Kommentar).
(if (not vario-erlaubt) (mode_tile "typ_vario" 1))
;; Variante-Feld nur aktivieren, wenn Vario gewaehlt UND erlaubt.
(if vario-erlaubt
(progn
(action_tile "typ_gfbogen"
"(mode_tile \"variante_aussen\" 1) (mode_tile \"variante_innen\" 1)")
(action_tile "typ_vario"
"(mode_tile \"variante_aussen\" 0) (mode_tile \"variante_innen\" 0)")))))
(action_tile "accept"
"(setq gtyp (if (= (get_tile \"typ_vario\") \"1\") \"1\" \"0\"))
(setq gvariante (if (= (get_tile \"variante_innen\") \"1\") \"1\" \"0\"))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (= ergebnis 1)
(if (and (= gtyp "1") vario-erlaubt)
;; Vario-Kurve: "2" + Variante ("1"=aussen / "2"=innen; Index+1)
(vflw-pending-push-all (list "2" (itoa (1+ (atoi gvariante)))))
;; GF-Bogen (oder Vario unzulaessig): "1" (keine Variante)
(vflw-pending-push-all (list "1"))))))
(princ))
;; ============================================================
;; TEIL 0b-3: GRUPPEN-DIALOGE (PENDING-QUEUE)
;; ============================================================
;; Mehrere Fragen, die im Ablauf IMMER unmittelbar hintereinander kommen
;; (kein Zwischenschritt, keine Verzweigung dazwischen), werden im Wizard in
;; EINEM Dialog gemeinsam erfasst (siehe vflw-gruppe-* unten). Die
;; darunterliegende Aufruf-Reihenfolge in vf-linienzug-modus/vfl-* bleibt
;; dabei WOERTLICH UNVERAENDERT (weiterhin ein vfl-in-point/-string/-real/
;; -int/-value-Aufruf pro Wert, in derselben Reihenfolge) - nur die HERKUNFT
;; des Werts aendert sich: statt live zu fragen (oder einen Einzel-Dialog zu
;; zeigen) wird der naechste Wert aus *vflw-pending* entnommen, den der
;; Gruppen-Dialog vorher dort abgelegt hat. Journal-Aufzeichnung passiert
;; weiterhin in JEDEM einzelnen vfl-in-*-Aufruf selbst - Editieren/Replay
;; bestehender Ketten ist dadurch unveraendert (siehe TEIL 0b-2).
;; Bricht der Nutzer den Gruppen-Dialog ab, bleibt *vflw-pending* leer -
;; die nachfolgenden vfl-in-*-Aufrufe fallen dann automatisch auf ihre
;; EINZEL-Dialoge zurueck (kein Sonderfall noetig).
(if (not (boundp '*vflw-pending*)) (setq *vflw-pending* nil))
(defun vflw-pending-pop ( / v)
(if *vflw-pending*
(progn (setq v (car *vflw-pending*)) (setq *vflw-pending* (cdr *vflw-pending*)) (list v))
nil))
(defun vflw-pending-push-all (vals) (setq *vflw-pending* (append *vflw-pending* vals)) nil)
;; --- Eingabe-Wrapper ---
;; kind steuert nur, wie ein FRISCH live erfasster Wert typisiert wird; beim
;; Replay wird der gespeicherte Wert unveraendert durchgereicht.
;;
;; Alle Basis-Wrapper teilen dasselbe 3-Zweig-Muster (Replay-Queue ->
;; Pending-Queue -> Live-Eingabe) plus Journal-Aufzeichnung. Dieses Skelett
;; ist EINMAL in vfl-in-value (weiter unten) implementiert; die folgenden
;; Wrapper liefern nur ihre Live-Eingabe-Funktion (livefn) und ihr kind-Tag.
;; Journal-Format und Signaturen bleiben damit unveraendert (63 Aufrufstellen).
;; Ausnahme: vfl-in-selection (OBJS/Handles) hat abweichende Replay-/Journal-
;; Logik und bleibt eigenstaendig.
(defun vfl-in-point (base prompt)
(vfl-in-value "PT" (function (lambda () (vfl-getpoint base prompt)))))
;; 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
;; werden die gespeicherten Handles zurueck in Enames aufgeloest; Handles
;; sind - anders als Enames - ueber Sitzungen hinweg stabil und damit fuer
;; die XDATA-Persistenz geeignet. Loest sich ein Handle nicht mehr auf (z.B.
;; weil der Nutzer die urspruengliche Pfad-Geometrie zwischenzeitlich
;; geloescht hat), wird das klar gemeldet statt eine unvollstaendige Auswahl
;; stillschweigend weiterzureichen.
(defun vfl-in-selection (filter / popped v handles h en k ss fehlt)
;; Zentraler Abbruch-Trigger (siehe Kommentar in vfl-in-value): auch
;; vfl-in-selection hat eine eigenstaendige Replay-/Journal-Logik und laeuft
;; NICHT ueber vfl-in-value - ohne diesen Check bliebe ein von einem
;; VORHERGEHENDEN Gruppen-Dialog gesetztes *vfl-wizard-abbruch-grund*
;; unbemerkt liegen, statt hier zuverlaessig zu terminieren.
(if *vfl-wizard-abbruch-grund*
(progn
(dbgmsg (strcat "ABBRUCH-TRIGGER: *vfl-wizard-abbruch-grund*=" *vfl-wizard-abbruch-grund* " -> (exit)"))
(exit)))
(setq popped (if *vfl-replay-queue* (vfl-replay-pop) nil))
(if popped
(progn
(setq handles (car popped) v nil fehlt 0)
(foreach h handles
(setq en (vl-catch-all-apply 'handent (list h)))
(if (or (vl-catch-all-error-p en) (null en))
(setq fehlt (1+ fehlt))
(setq v (cons en v))))
(setq v (reverse v))
(if (> fehlt 0)
(progn
(vfl-meldung (ssg-textf "vfl-m3-alert-objekte-fehlen" (list (itoa fehlt))))
;; Headless NICHT mit einer Teilauswahl weiterbauen: die Segmentzahl
;; bestimmt die gesamte Fragenfolge des Modus-2-Ablaufs, ein
;; fehlendes Pfad-Objekt verschiebt sie still. Ein nachgelieferter
;; Wert waere hier sinnlos, darum -abbruch statt -notausgang.
(if (vfl-headless-p) (vfl-headless-abbruch "OBJS-unvollstaendig")))))
(progn
;; Ohne Replay-Daten gibt es headless keine Objektauswahl.
(if (vfl-headless-p) (vfl-headless-abbruch "OBJS"))
(setq ss (ssget filter))
(setq v nil)
(if ss
(progn
(setq k 0)
(while (< k (sslength ss))
(setq v (cons (ssname ss k) v))
(setq k (1+ k)))
(setq v (reverse v))))))
(setq handles (mapcar (function (lambda (e) (cdr (assoc 5 (entget e))))) v))
(vfl-journal-record (if v "OBJS" "NIL") handles)
v)
;; String: "" (Enter) ist ein GUELTIGER String (kein Abbruch) -> "STR". Daher
;; das strikte STR-Praedikat statt der Default-non-nil-Pruefung.
(defun vfl-in-string (prompt)
(vfl-in-value-p "STR"
(function (lambda (v) (eq (type v) 'STR)))
(function (lambda ()
(if (and (vfl-wizard-mode-p) *vflw-menu-optionen*)
(vflw-wahl (if *vflw-menu-frage* (ssg-text *vflw-menu-frage*) prompt)
*vflw-menu-optionen* *vflw-menu-default*)
(getstring prompt))))))
(defun vfl-in-real (prompt)
(vfl-in-value "REAL"
(function (lambda ()
(if (vfl-wizard-mode-p) (vflw-zahl prompt) (getreal prompt))))))
(defun vfl-in-int (prompt)
(vfl-in-value "INT"
(function (lambda ( / vs)
(if (and (vfl-wizard-mode-p) *vflw-menu-optionen*)
(progn
(setq vs (vflw-wahl (if *vflw-menu-frage* (ssg-text *vflw-menu-frage*) prompt)
*vflw-menu-optionen* *vflw-menu-default*))
(if (and vs (> (strlen vs) 0)) (atoi vs) nil))
(getint prompt))))))
;; Menue-Wrapper: princ-Ausgabe der Optionen bleibt beim Aufrufer unveraendert
;; stehen (Konsolen-Fallback, de/en via ssg-text) - hier wird nur die
;; ABSCHLIESSENDE Eingabe-Zeile ersetzt. frage-key: ssg-text-Schluessel der
;; eigentlichen Frage (die Kopfzeile, die vorher separat per princ ausgegeben
;; wurde, z.B. "vfl-ist-endpunkt-frage") - OHNE das wuerde der Wizard-Dialog
;; nur die generische "Ihre Wahl (1/2/3)..."-Zeile zeigen und die eigentliche
;; Frage bliebe unsichtbar (nur auf der Konsole hinter dem Dialog). Die
;; Lokalvariablen sind ueber die '/'-Deklaration dynamisch nur waehrend
;; dieses Aufrufs sichtbar (AutoLISP dynamic scoping) und wirken daher
;; ausschliesslich auf den EINEN vfl-in-string/-int-Aufruf im Rumpf.
(defun vfl-menu (prompt option-labels default-idx frage-key /
*vflw-menu-optionen* *vflw-menu-default* *vflw-menu-frage*)
(setq *vflw-menu-optionen* option-labels)
(setq *vflw-menu-default* default-idx)
(setq *vflw-menu-frage* frage-key)
(vfl-in-string prompt))
(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)
(setq *vflw-menu-default* default-idx)
(setq *vflw-menu-frage* frage-key)
(vfl-in-int prompt))
;; Winkel-Menue: fragt einen WINKELWERT (30/60/90) ab und journalisiert ihn
;; als echten Winkel (INT-Eintrag = 30/60/90), NICHT als Auswahl-Index 1/2/3.
;; Damit ist jedes Journal/Log selbsterklaerend und alle GF-Bogen-/Vario-Kurve-
;; Stellen (Live-Bau, Slice, Edit) teilen denselben Wert. winkel-werte ist die
;; Liste der zur Optionsreihenfolge gehoerenden Winkel (einheitlich (30 60 90)
;; fuer GF-Bogen UND Vario-Kurve); default-winkel der vorbelegte Winkelwert. Bei Konsolen-Eingabe
;; tippt der Nutzer den Winkel direkt (30/60/90); ungueltige Eingaben fallen auf
;; den naechstliegenden gueltigen Wert bzw. den Default. Rueckgabe: Winkel (Int).
(defun vfl-menu-winkel (prompt option-labels winkel-werte default-winkel frage-key /
*vflw-menu-optionen* *vflw-menu-default* *vflw-menu-frage* v)
(setq *vflw-menu-optionen* option-labels)
(setq *vflw-menu-frage* frage-key)
;; Default-Index = Position des Default-Winkels in winkel-werte (1-basiert).
(setq *vflw-menu-default*
(+ 1 (- (length winkel-werte) (length (member default-winkel winkel-werte)))))
(setq v (vfl-in-value "INT"
(function (lambda () (vfl-winkel-live prompt winkel-werte default-winkel)))))
;; Absicherung: nur gueltige Winkel zulassen (auch bei Alt-Journals mit Index).
(vfl-winkel-normieren v winkel-werte default-winkel))
;; Live-Abfrage eines Winkelwerts. Wizard: Auswahl aus den Labels, Position ->
;; winkel-werte. Konsole: direkte Zahleneingabe (ENTER = Default).
(defun vfl-winkel-live (prompt winkel-werte default-winkel / vs idx r)
(if (and (vfl-wizard-mode-p) *vflw-menu-optionen*)
(progn
(setq vs (vflw-wahl (if *vflw-menu-frage* (ssg-text *vflw-menu-frage*) prompt)
*vflw-menu-optionen* *vflw-menu-default*))
(setq idx (if (and vs (> (strlen vs) 0)) (atoi vs) 0))
(if (and (>= idx 1) (<= idx (length winkel-werte)))
(nth (1- idx) winkel-werte)
default-winkel))
(progn
(setq r (getint prompt))
(if r r default-winkel))))
;; Normiert einen (moeglicherweise aus Alt-Journals stammenden Index oder einen
;; frei getippten Wert) auf einen gueltigen Winkel aus winkel-werte. Ein Wert,
;; der bereits gueltig ist, bleibt unveraendert. Ansonsten Default.
(defun vfl-winkel-normieren (v winkel-werte default-winkel / n)
(setq n (cond ((null v) default-winkel)
((= (type v) 'STR) (atoi v))
(T v)))
(if (member n winkel-werte) n default-winkel))
;; Generisches Eingabe-Skelett: der gemeinsame 3-Zweig-Ablauf (Replay-Queue ->
;; Pending-Queue -> Live-Eingabe) plus Journal-Aufzeichnung, den alle Basis-
;; Wrapper (vfl-in-point/-string/-real/-int) teilen. livefn ist ein aufrufbares
;; Objekt ohne Argumente (die eigentliche Live-Eingabe, z.B. getpoint/getreal
;; oder eine fragende Hilfsfunktion wie vf-frage-element-winkel); es wird NUR
;; aufgerufen, wenn weder Replay- noch Pending-Wert vorliegt.
;; kind: Journal-Typ eines frisch/gueltig erfassten Werts. Ein non-nil v gilt
;; als gueltig (-> kind), nil als Abbruch (-> "NIL"). Fuer Faelle, in denen die
;; Gueltigkeit strenger geprueft werden muss (z.B. vfl-in-string: "" ist ein
;; gueltiger String), siehe vfl-in-value-p.
;; Generisches Echo der Antwort auf JEDE vfl-in-*-Frage (Nutzerwunsch: nach
;; jeder Konsolen-/Wizard-Frage soll sichtbar sein, was gewaehlt wurde) -
;; zentral in vfl-in-value/-value-p verankert (statt an jeder Aufrufstelle
;; einzeln), deckt dadurch ALLE Fragen ab, auch kuenftige. Bei einer Menue-
;; Frage (vfl-menu/-menu-int, *vflw-menu-optionen* gesetzt - dynamisch aus
;; deren eigener '/'-Deklaration sichtbar) wird die Antwort, falls sie als
;; 1-basierter Index in die Options-Liste passt, zum Options-Text aufgeloest
;; (vflw-clean entfernt das "\n N - "-Praefix); sonst (z.B. vfl-menu-winkel
;; mit einem echten Winkelwert statt Index, oder eine freie Texteingabe wie
;; der AS-/ES-Winkel "90"/"30") wird der Rohwert gezeigt. Nur STR/INT
;; (echte Auswahl-Antworten) - REAL (Hoehen/Laengen/Winkel-Zahlen) und
;; PT/DL (Punktwahl) werden NICHT echot, das Ergebnis zeigt sich ohnehin
;; ueber die nachfolgende Bau-/KS_AUS-Ausgabe.
(defun vfl-echo-antwort (kind v / idx label)
(if (and v (member kind '("STR" "INT")))
(progn
(setq label nil)
(if (and *vflw-menu-optionen*
(setq idx (if (= kind "INT") v (atoi v)))
(>= idx 1) (<= idx (length *vflw-menu-optionen*)))
(setq label (vflw-clean (nth (1- idx) *vflw-menu-optionen*))))
(vfl-echo-wahl (if label label (if (= kind "STR") v (vl-princ-to-string v))))
)
)
)
(defun vfl-in-value (kind livefn / popped v)
;; Zentraler Abbruch-Trigger fuer den "Zurueck"/"Abbrechen"-Button (siehe
;; vfl-modus-abbruch-sichern): EIN Check hier deckt vfl-in-point/-string/
;; -real/-int/vfl-menu/-menu-int komplett ab, statt jede Aufrufstelle im
;; Bau-Ablauf einzeln pruefen zu muessen. Laeuft AUSSERHALB von vl-catch-
;; all-apply (das umschliesst nur den vorgelagerten Gruppen-Dialog-Aufruf),
;; (exit) erreicht also zuverlaessig den scharf geschalteten *error*-Handler.
(if *vfl-wizard-abbruch-grund*
(progn
(dbgmsg (strcat "ABBRUCH-TRIGGER: *vfl-wizard-abbruch-grund*=" *vfl-wizard-abbruch-grund* " -> (exit)"))
(exit)))
(setq popped (if *vfl-replay-queue* (vfl-replay-pop) nil))
(if popped
(setq v (car popped))
(progn
(setq popped (vflw-pending-pop))
;; Headless: statt still live zu fragen (Batch-Haenger, und der Grund
;; des Desyncs waere verloren) abbrechen bzw. den Antwort-Hook fragen.
(if (and (null popped) (vfl-headless-p))
(setq popped (vfl-headless-notausgang kind)))
(if popped (setq v (car popped)) (setq v (apply livefn nil)))))
(vfl-journal-record (if v kind "NIL") v)
(vfl-echo-antwort kind v)
v)
;; Variante mit explizitem Gueltig-Praedikat (fuer vfl-in-string: "" ist gueltig).
(defun vfl-in-value-p (kind gueltig-p livefn / popped v)
;; Siehe Kommentar in vfl-in-value - derselbe zentrale Abbruch-Trigger.
(if *vfl-wizard-abbruch-grund*
(progn
(dbgmsg (strcat "ABBRUCH-TRIGGER: *vfl-wizard-abbruch-grund*=" *vfl-wizard-abbruch-grund* " -> (exit)"))
(exit)))
(setq popped (if *vfl-replay-queue* (vfl-replay-pop) nil))
(if popped
(setq v (car popped))
(progn
(setq popped (vflw-pending-pop))
(if (and (null popped) (vfl-headless-p)) ; siehe vfl-in-value
(setq popped (vfl-headless-notausgang kind)))
(if popped (setq v (car popped)) (setq v (apply livefn nil)))))
(vfl-journal-record (if (apply gueltig-p (list v)) kind "NIL") v)
(vfl-echo-antwort kind v)
v)
;; --- String-Hilfen (generisch, Trenner beliebig) ---
(defun vfl-strjoin (lst sep / s first)
(setq s "" first t)
(foreach x lst
(if first (progn (setq s x) (setq first nil)) (setq s (strcat s sep x))))
s)
(defun vfl-strsplit (s sep / pos out)
(setq out '())
(while (setq pos (vl-string-search sep s))
(setq out (cons (substr s 1 pos) out))
(setq s (substr s (+ pos 1 (strlen sep)))))
(reverse (cons s out)))
;; --- SEGS: Segment-Essenz einer Modus-3-Kette (selbst-enthalten) ---
;; Ein SEGS-Wert ist die AUS DEN PFAD-OBJEKTEN ABGELEITETE Zahlenliste, die der
;; Modus-3-Bau ohnehin allein verwendet (gf-analysiere-kette + Start/Endpunkte
;; aus gf-sortiere-objekte). Sie ersetzt im Journal die frueheren OBJS-Handles:
;; dadurch ist die Kette im XDATA vollstaendig beschrieben und beim Doppelklick-
;; Replay unabhaengig davon, ob die urspruenglichen LINE/ARC noch existieren
;; (robuster als der OBJS-Weg von Modus 2). Siehe doc/variofoerderer.md.
;;
;; Ein Segment ist eine 9er-Liste, die die "Bau-Essenz" und die fuer die
;; AS-Trimmung/das Soll-Ende noetigen absoluten Punkte zusammenfuehrt:
;; (typ hz laenge bwinkel bseite spx spy epx epy)
;; typ "Linie"/"Bogen" (delimiter-frei, daher unquotiert serialisiert)
;; hz Eintritts-Horizontalwinkel (Grad)
;; laenge 2D-Laenge / Sehnenmass (mm)
;; bwinkel Bogenwinkel (0 bei Linie)
;; bseite "links"/"rechts"
;; spx spy Startpunkt (XY, mm) des Segments \ aus gf-sortiere-objekte,
;; epx epy Endpunkt (XY, mm) des Segments / fuer Trimmung + Soll-Ende
;;
;; Serialisierung: Segmente per ";" getrennt, Felder je Segment per "|". Beide
;; Trenner sind kollisionsfrei - die einzigen String-Felder (typ/bseite) sind
;; feste Schluesselwoerter ohne ";" oder "|", alle uebrigen Felder sind Zahlen
;; (rtos-Mode-2, wie PT/REAL). Ein leeres SEGS ("") ist eine leere Liste.
(defun vfl-seg->string (seg)
(vfl-strjoin
(list (nth 0 seg) ; typ (String)
(rtos (float (nth 1 seg)) 2 6) ; hz
(rtos (float (nth 2 seg)) 2 6) ; laenge
(rtos (float (nth 3 seg)) 2 6) ; bwinkel
(nth 4 seg) ; bseite (String)
(rtos (float (nth 5 seg)) 2 6) ; spx
(rtos (float (nth 6 seg)) 2 6) ; spy
(rtos (float (nth 7 seg)) 2 6) ; epx
(rtos (float (nth 8 seg)) 2 6)) ; epy
"|"))
(defun vfl-string->seg (s / f)
(setq f (vfl-strsplit s "|"))
(list (nth 0 f) ; typ
(atof (nth 1 f)) ; hz
(atof (nth 2 f)) ; laenge
(fix (atof (nth 3 f))) ; bwinkel (Ganzzahl, wie gf-analysiere-kette)
(nth 4 f) ; bseite
(atof (nth 5 f)) ; spx
(atof (nth 6 f)) ; spy
(atof (nth 7 f)) ; epx
(atof (nth 8 f)))) ; epy
(defun vfl-segs->string (segs)
(vfl-strjoin (mapcar 'vfl-seg->string segs) ";"))
(defun vfl-string->segs (s)
(if (= (strlen s) 0)
'()
(mapcar 'vfl-string->seg (vfl-strsplit s ";"))))
;; --- Serialisierung (tagged, selbstbeschreibend) ---
;; Zeilenformat "kind:payload", Eintraege per "\n" verkettet. getstring/getreal
;; liefern nie ein Newline, daher ist "\n" ein sicherer Record-Trenner.
;; "OBJS" (Objektauswahl, Modus 2): Payload = kommagetrennte Liste von
;; Entity-Handles (Hex-Strings, kollidieren nie mit dem Komma-Trenner).
;; Handles bleiben - anders als Enames - ueber Sitzungen/Undo hinweg stabil
;; und sind damit die richtige Persistenzform fuer ssget-Ergebnisse.
;; "SEGS" (Segment-Essenz, Modus 3): Payload = ";"-getrennte Segmente, Felder
;; per "|" (siehe vfl-segs->string oben).
(defun vfl-entry->string (e / k v)
(setq k (car e) v (cdr e))
(cond
((= k "PT") (strcat "PT:" (rtos (car v) 2 6) "," (rtos (cadr v) 2 6) ","
(rtos (caddr v) 2 6)))
((= k "REAL") (strcat "REAL:" (rtos v 2 6)))
((= k "DL") (strcat "DL:" (rtos v 2 6)))
((= k "INT") (strcat "INT:" (itoa v)))
((= k "STR") (strcat "STR:" v))
((= k "STEP") (strcat "STEP:" v))
((= k "OBJS") (strcat "OBJS:" (vfl-strjoin v ",")))
((= k "SEGS") (strcat "SEGS:" (vfl-segs->string v)))
(t "NIL:")))
(defun vfl-string->entry (ln / p kind pay c)
(setq p (vl-string-search ":" ln))
(if (null p)
(cons "NIL" nil)
(progn
(setq kind (substr ln 1 p) pay (substr ln (+ p 2)))
(cond
((= kind "PT")
(setq c (vfl-strsplit pay ","))
(cons "PT" (list (atof (nth 0 c)) (atof (nth 1 c)) (atof (nth 2 c)))))
((= kind "REAL") (cons "REAL" (atof pay)))
((= kind "DL") (cons "DL" (atof pay)))
((= kind "INT") (cons "INT" (atoi pay)))
((= kind "STR") (cons "STR" pay))
((= kind "STEP") (cons "STEP" pay))
((= kind "OBJS") (cons "OBJS" (if (> (strlen pay) 0) (vfl-strsplit pay ",") nil)))
((= kind "SEGS") (cons "SEGS" (vfl-string->segs pay)))
(t (cons "NIL" nil))))))
;; forward-journal ist bereits in Bau-Reihenfolge (nicht die umgekehrte
;; *vfl-journal*-Rohform) - gemeinsame Serialisierung fuer Ketten- UND
;; Segment-XDATA (vfl-journal-xdata-schreiben / vfl-journal-xdata-schreiben-seg).
(defun vfl-journal-list->string (forward-journal)
(vfl-strjoin (mapcar 'vfl-entry->string forward-journal) "\n"))
(defun vfl-journal->string ()
(vfl-journal-list->string (reverse *vfl-journal*)))
;; --- JSON-Dump des Journals fuer die Debug-Datei (nur Diagnose) ---
;; Wandelt EINEN Journal-Eintrag (kind . value) in ein JSON-Objekt
;; {"kind":"PT","value":[x,y,z]} um. String-Werte werden gequotet (mit
;; einfachem Escaping fuer " und \), Zahlen unquotiert, PT als Zahlen-Array,
;; OBJS als String-Array, NIL als null. Rein additive Diagnose - beruehrt die
;; XDATA-Serialisierung (vfl-entry->string) NICHT.
(defun vfl-json-escape (s / out c i n)
(setq out "" i 1 n (strlen s))
(while (<= i n)
(setq c (substr s i 1))
(setq out (strcat out (cond ((= c "\"") "\\\"") ((= c "\\") "\\\\") (t c))))
(setq i (1+ i)))
out)
(defun vfl-json-num (x) (rtos x 2 6)) ; einheitliches Zahlformat (wie vfl-entry->string)
(defun vfl-entry->json (e / k v)
(setq k (car e) v (cdr e))
(strcat "{\"kind\":\"" k "\",\"value\":"
(cond
((null v) "null")
((= k "PT") (strcat "[" (vfl-json-num (car v)) "," (vfl-json-num (cadr v)) ","
(vfl-json-num (caddr v)) "]"))
((= k "REAL") (vfl-json-num v))
((= k "DL") (vfl-json-num v))
((= k "INT") (itoa v))
((= k "STR") (strcat "\"" (vfl-json-escape v) "\""))
((= k "STEP") (strcat "\"" (vfl-json-escape v) "\""))
((= k "OBJS") (strcat "[" (vfl-strjoin
(mapcar (function (lambda (h) (strcat "\"" (vfl-json-escape h) "\"")))
v) ",") "]"))
((= k "SEGS") (strcat "["
(vfl-strjoin
(mapcar
(function (lambda (seg)
(strcat "[\"" (vfl-json-escape (nth 0 seg)) "\","
(vfl-json-num (float (nth 1 seg))) ","
(vfl-json-num (float (nth 2 seg))) ","
(vfl-json-num (float (nth 3 seg))) ",\""
(vfl-json-escape (nth 4 seg)) "\","
(vfl-json-num (float (nth 5 seg))) ","
(vfl-json-num (float (nth 6 seg))) ","
(vfl-json-num (float (nth 7 seg))) ","
(vfl-json-num (float (nth 8 seg))) "]")))
v) ",")
"]"))
(t "null"))
"}"))
;; forward-journal (Bau-Reihenfolge) als kompakter JSON-Array-String.
(defun vfl-journal->json (forward-journal)
(strcat "[" (vfl-strjoin (mapcar 'vfl-entry->json forward-journal) ",") "]"))
(defun vfl-string->journal (s / out)
(setq out '())
(foreach ln (vfl-strsplit s "\n")
(if (> (strlen ln) 0) (setq out (cons (vfl-string->entry ln) out))))
(reverse out))
;; --- Persistenz am VF_n-Block ueber die SSG_VF_EDIT-XDATA-App ---
;; Layout der 1000-Gruppen: [0]="linienzug" (Marker), [1..]=Journal-String in
;; 250-Byte-Chunks (DXF-Limit 255). Rueckwaertskompatibel zum Standard/Etage-
;; Reader vfs-xdata-lesen, der alle 1000-Werte als Liste liefert.
(defun vfl-chunk-string (s n / out len)
(setq out '())
(while (> (setq len (strlen s)) n)
(setq out (cons (substr s 1 n) out))
(setq s (substr s (1+ n))))
(if (> (strlen s) 0) (setq out (cons s out)))
(reverse out))
;; marker: "linienzug" (Modus 1, Sektions-Editieren via vfl-edit-ent) oder
;; "linienzug2" (Modus 2, vfl-edit-ent2 - voller Reset ohne Sektionswahl,
;; siehe dortiger Kommentar). Der Marker steht in der ersten 1000-Gruppe und
;; entscheidet beim Lesen (vfl-journal-xdata-lesen), welcher Editier-Pfad
;; greift.
(defun vfl-journal-xdata-schreiben (ent marker / chunks appentry)
(if (null *vf-xdata-app*) (setq *vf-xdata-app* "SSG_VF_EDIT"))
(regapp *vf-xdata-app*)
;; Diagnose: komplettes Journal beim Erzeugen der Geometrie als JSON in die
;; Debug-Datei (No-Op ohne offene .dbg-Datei, siehe dbgmsg). marker + JSON-
;; Array der Eintraege in Bau-Reihenfolge.
(dbgmsg (strcat "XDATA-GESCHRIEBEN marker=" marker))
(dbgmsg (strcat "XDATA-JOURNAL-JSON " (vfl-journal->json (reverse *vfl-journal*))))
(dbgflush)
(setq chunks (vfl-chunk-string (vfl-journal->string) 250))
(setq appentry
(cons *vf-xdata-app*
(cons (cons 1000 marker)
(mapcar (function (lambda (c) (cons 1000 c))) chunks))))
(entmod (append (entget ent) (list (list -3 appentry))))
ent)
;; Rueckgabe: (marker . journal) oder nil (kein/unbekanntes Journal).
(defun vfl-journal-xdata-lesen (ent / xd)
(setq xd (vfs-xdata-lesen ent))
(if (and xd (member (car xd) '("linienzug" "linienzug2" "linienzug3")))
(cons (car xd) (vfl-string->journal (apply 'strcat (cdr xd))))
nil))
;; --- Segment-XDATA (pro einzelnem Glied-Entity, eigener App-Name) ---
;; Eigene App "SSG_VF_EDIT_SEG" statt SSG_VF_EDIT: sauber getrennt vom
;; Ketten-Journal, keine Kollisionsgefahr mit dessen Marker-Schema
;; ("linienzug"/"linienzug2"/"standard"/"etage"). Wird auf JEDES seit
;; Glied-Start neu erzeugte Entity geschrieben, WAEHREND es noch frei in der
;; Zeichnung liegt (vor dem finalen -BLOCK-Sweep in vfl-block-erstellen) -
;; siehe vfl-segment-xdata-sichern. Rein additive Zusatzinformation: das
;; bestehende Ketten-Journal (SSG_VF_EDIT) bleibt unabhaengig davon die
;; primaere/vollstaendige Quelle, Segment-XDATA ist kein Ersatz dafuer.
;; Layout: [0]="linienzug-seg" (Marker), [1]=Glied-Index (1-basiert, String),
;; [2..]=Segment-Journal-String in 250-Byte-Chunks.
(defun vfl-journal-xdata-schreiben-seg (ent seg-idx seg-journal / chunks appentry)
(regapp "SSG_VF_EDIT_SEG")
(setq chunks (vfl-chunk-string (vfl-journal-list->string seg-journal) 250))
(setq appentry
(cons "SSG_VF_EDIT_SEG"
(cons (cons 1000 "linienzug-seg")
(cons (cons 1000 (itoa seg-idx))
(mapcar (function (lambda (c) (cons 1000 c))) chunks)))))
(entmod (append (entget ent) (list (list -3 appentry))))
ent)
;; Rueckgabe: (seg-idx . segment-journal) oder nil (kein/unbekanntes Segment-XDATA).
(defun vfl-journal-xdata-lesen-seg (ent / xd app-data werte)
(setq xd (entget ent '("SSG_VF_EDIT_SEG")))
(setq app-data (cdr (assoc -3 xd)))
(if app-data
(progn
(setq werte (mapcar 'cdr (cdr (car app-data))))
(if (and werte (= (car werte) "linienzug-seg") (cadr werte))
(cons (atoi (cadr werte)) (vfl-string->journal (apply 'strcat (cddr werte))))
nil))
nil))
;; --- Praeambel-XDATA (eigene App "SSG_VF_EDIT_PRE") -----------------------
;; Die Segment-XDATA (SSG_VF_EDIT_SEG) deckt nur die GLIEDER ab - alles vor
;; dem ersten STEP (Startpunkt, Starthoehe, AS ja/nein + AS-Winkel/-Seite)
;; steht in keiner Slice. Fuer eine vollstaendige Wiederherstellung aus der
;; Zeichnung allein (c:VF_SEKTION_RESTORE) fehlt damit genau der Kopf des
;; Journals. Deshalb wird die Praeambel zusaetzlich als eigene XDATA-App auf
;; die Entities des ERSTEN Gliedes geschrieben - eigene App, weil pro Entity
;; nur ein SSG_VF_EDIT_SEG-Record Platz hat (der gehoert dem Glied).
;; Layout: [0]="linienzug-pre" (Marker), [1..]=Journal-String in 250-Byte-Chunks.
;;
;; Praeambel = alle Journal-Eintraege VOR dem ersten STEP.
(defun vfl-journal-praeambel (forward-journal / out fertig)
(setq out '() fertig nil)
(foreach e forward-journal
(if (and (not fertig) (= (car e) "STEP")) (setq fertig t))
(if (not fertig) (setq out (cons e out))))
(reverse out))
(defun vfl-journal-xdata-schreiben-pre (ent pre-journal / chunks appentry)
(regapp "SSG_VF_EDIT_PRE")
(setq chunks (vfl-chunk-string (vfl-journal-list->string pre-journal) 250))
(setq appentry
(cons "SSG_VF_EDIT_PRE"
(cons (cons 1000 "linienzug-pre")
(mapcar (function (lambda (c) (cons 1000 c))) chunks))))
(entmod (append (entget ent) (list (list -3 appentry))))
ent)
;; Rueckgabe: Praeambel-Journal oder nil (keine/unbekannte Praeambel-XDATA).
(defun vfl-journal-xdata-lesen-pre (ent / xd app-data werte)
(setq xd (entget ent '("SSG_VF_EDIT_PRE")))
(setq app-data (cdr (assoc -3 xd)))
(if app-data
(progn
(setq werte (mapcar 'cdr (cdr (car app-data))))
(if (and werte (= (car werte) "linienzug-pre"))
(vfl-string->journal (apply 'strcat (cdr werte)))
nil))
nil))
;; Wird direkt nach dem Segment-Dispatch-cond in vf-linienzug-modus aufgerufen
;; (per vl-catch-all-apply abgesichert - ein Fehler hier darf den Hauptbau nie
;; abbrechen, rein additive Zusatz-XDATA). seg-lastEnt = Schnappschuss VOR
;; diesem Glied (vf-lastent-ohne-attribute, siehe Aufruf). wahl wird hier
;; nicht mehr gebraucht (das Label steckt schon im STEP-Eintrag des Journals
;; via vfl-journal-steplabel) - Parameter bleibt fuer eine spaetere dbgmsg-
;; Diagnose nuetzlich, daher weiterhin uebergeben.
;; Ermittelt Glied-Index + Journal-Teilsequenz selbst aus dem aktuellen
;; *vfl-journal* (zu diesem Zeitpunkt bereits um dieses Glied vollstaendig
;; ergaenzt) und schreibt sie auf JEDES seit seg-lastEnt neu erzeugte Entity
;; (entnext-Wanderung, gleiches Muster wie vfl-block-erstellen/
;; vfl-modus-abbruch-sichern).
(defun vfl-segment-xdata-sichern (seg-lastEnt wahl / forward-journal seg-idx seg-von
seg-journal log-slice e cnt slice-label pre-journal)
(setq forward-journal (reverse *vfl-journal*))
(setq seg-idx (length (vfl-journal-glieder forward-journal)))
;; Erstes Glied DIESER Iteration: eine Iteration kann mehrere Glieder
;; erzeugen (VF-Einheit + eingebettete Vario-Kurven). *vfl-seg-glied-letzte*
;; haelt den Glied-Stand der vorherigen Iteration.
(if (null *vfl-seg-glied-letzte*) (setq *vfl-seg-glied-letzte* 0))
(setq seg-von (1+ *vfl-seg-glied-letzte*))
(setq cnt 0)
(if (and (> seg-idx 0) (>= seg-idx seg-von))
(progn
;; XDATA: der KOMPLETTE in dieser Iteration entstandene Journal-Abschnitt
;; (Glieder seg-von .. seg-idx), indiziert mit dem ERSTEN Glied-Index.
;; Nur so ist die Kette spaeter luecken- und desync-frei aus der
;; Zeichnung rekonstruierbar (c:VF_SEKTION_RESTORE).
(setq seg-journal (vfl-journal-ab-glied forward-journal seg-von))
;; Fuers Log/die Schema-Validierung weiterhin die Slice des LETZTEN
;; Glieds (unveraendertes Verhalten, siehe Kommentar unten).
(setq log-slice (vfl-journal-slice forward-journal seg-idx))
;; Praeambel (Startpunkt/Starthoehe/AS) bei der ERSTEN Iteration
;; mitschreiben - ohne sie waere die Kette aus der Zeichnung nicht
;; vollstaendig rekonstruierbar (vfl-journal-xdata-schreiben-pre).
;; Danach aendert sich die Praeambel nicht mehr.
(setq pre-journal (if (= seg-von 1) (vfl-journal-praeambel forward-journal) nil))
;; Tatsaechliches Label der geschnittenen Slice (der STEP-Wert am Anfang).
;; Kann von wahl (dem Hauptschleifen-Label) ABWEICHEN: enthaelt die
;; VF-Einheit eine Vario-Kurve, ist DEREN STEP das letzte Glied, seg-idx
;; zeigt darauf, und die Slice ist die Vario-Kurve-Slice - waehrend wahl
;; noch "Horizontal-VF"/"Linie-VF" ist. Fuers Log/die Validierung zaehlt
;; das echte Slice-Label, nicht wahl (sonst falsch etikettiert).
(setq slice-label
(if (and log-slice (= (car (car log-slice)) "STEP"))
(cdr (car log-slice)) wahl))
(setq e (if seg-lastEnt (entnext seg-lastEnt) (entnext)))
(while e
(vfl-journal-xdata-schreiben-seg e seg-von seg-journal)
(if pre-journal (vfl-journal-xdata-schreiben-pre e pre-journal))
(setq cnt (1+ cnt))
(setq e (entnext e))
)
;; Glied-Stand NUR fortschreiben, wenn wirklich Entities beschrieben
;; wurden. Ein Glied ohne Geometrie (z.B. "VF-Einheit nicht baubar" -
;; Menue-Antwort und Laenge sind journalisiert, gebaut wurde nichts) hat
;; keinen Traeger fuer seine XDATA. Bliebe der Stand trotzdem stehen,
;; waere sein Journal-Abschnitt unauffindbar und die Kette waere ab dort
;; nur noch mit Luecke wiederherstellbar. So wandert der Abschnitt
;; stattdessen in den naechsten Record, der Entities bekommt.
(if (> cnt 0) (setq *vfl-seg-glied-letzte* seg-idx))
;; Diagnose: den tatsaechlichen Segment-Journal-Inhalt als JSON loggen
;; (analog zum Ketten-XDATA-JOURNAL-JSON in vfl-journal-xdata-schreiben)
;; plus die Anzahl der beschriebenen Entities. No-Op ohne offene .dbg.
(dbgmsg (strcat "SEGMENT-XDATA-JSON Glied " (itoa seg-von)
(if (> seg-idx seg-von) (strcat ".." (itoa seg-idx)) "")
" (" (if slice-label slice-label "?") ") auf " (itoa cnt)
" Entity(s): " (vfl-journal->json seg-journal)))
;; Zusaetzlich benannt loggen (statt roher [INT]/[STR]-Zeilen). Feste
;; Glieder (AS/ES/GF-Bogen/Vario-Kurve) ueber ihr Feld-Schema
;; (GF-Bogen.winkel = 90), die VF-Einheit (Linie-VF/Horizontal-VF, variable
;; Laenge) ueber den Sub-Step-Logger (VF.menue = 4, VF.laenge = ...). Beide
;; validieren, was validierbar ist, und markieren Abweichungen (<!! ...>).
;; Ueber slice-label (echtes Glied), nicht wahl.
;; Bewusst log-slice (genau EIN Glied) - die Schema-Logger erwarten die
;; Slice eines einzelnen Glieds, nicht den Iterations-Abschnitt.
(if (vfl-schema-felder slice-label)
(vfl-schema-log-slice slice-label log-slice)
(if (member slice-label '("Linie-VF" "Horizontal-VF"))
(vfl-schema-log-vf slice-label log-slice)))
)
(dbgmsg (strcat "SEGMENT-XDATA: kein Glied vorhanden (" (if wahl wahl "?") ")"))
)
(dbgflush)
(princ))
;; --- Abbruch-Sicherung ---
;; Alle nach lastEnt erzeugten Entities werden NICHT geloescht, sondern
;; GENAUSO wie beim erfolgreichen Abschluss (vfl-block-erstellen) zu einem
;; neuen VF_n-Block gewickelt, mit dem VOLLEN aktuellen Eingabe-Journal
;; (inkl. des angefangenen, noch unfertigen letzten Gliedes) als XDATA. Die
;; Geometrie bleibt in der Zeichnung stehen und ist sofort per Doppelklick
;; weiter editierbar/fortsetzbar (vfl-edit-ent) - unabhaengig davon, ob es
;; sich um einen frischen Bau oder einen Editier-Neuaufbau handelte (kein
;; Sonderfall mehr noetig). Aufgerufen aus dem *error*-Handler von
;; vf-linienzug-modus.
;; Debug-Session eines Modus starten - gemeinsam fuer alle drei Linienzug-Modi
;; (frueher 3x fast wortgleich, nur Schalter-/Datei-/Funktions-/Sessiontext
;; unterschieden sich). Oeffnet die dbg-Datei und schreibt Kopf + Session-Start.
;; Rueckgabe: dbg-an (T, wenn geloggt wird). Der Modus-1-Zusatz (Replay-Queue-
;; JSON-Diagnose) bleibt an M1s Aufrufstelle - er ist M1-spezifisch.
(defun vfl-dbg-session-start (schalter datei funcname sessiontext / dbg-an)
(setq dbg-an (dbg-schalter-open schalter datei "DXFM_LOG"))
(if dbg-an
(progn
(dbgf funcname)
(dbgmsg sessiontext)
(dbgflush)))
dbg-an)
;; Fruehen, DUENNEN dbg-*error*-Hook installieren (Modus 2/3): schliesst bei
;; einem Abbruch waehrend der Pfad-/Praeambel-Phase (noch keine Geometrie) nur
;; die dbg-Datei sauber und faellt auf dbg-old-error zurueck. Wird spaeter vom
;; scharfen Wickel-Handler (vfl-abbruch-handler-installieren) abgeloest, sobald
;; vfl-nummer/lastEnt feststehen. Frueher in M2 und M3 wortgleich ausgeschrieben.
;; VERTRAG (dynamic scoping, wie vfl-abbruch-handler-installieren): der Aufrufer
;; MUSS dbg-old-error und dbg-an als Locals gebunden haben; der Hook-Lambda loest
;; sie zur Abbruchzeit gegen den Modus-Frame auf. dbg-old-error hier setzen
;; (Save von *error*) und zurueckgeben.
(defun vfl-dbg-hook-installieren ()
(setq dbg-old-error *error*)
(setq *error*
(function (lambda (msg)
(setq *error* dbg-old-error)
(if dbg-an
(progn
(dbgmsg (strcat "=== ABBRUCH: " (if msg msg "(exit)") " ==="))
(dbgreturn nil)
(dbgclose)))
(if dbg-old-error (dbg-old-error msg) (princ)))))
dbg-old-error)
;; Den scharfen Abbruch-Wickel-Handler als *error* installieren - gemeinsam
;; fuer alle drei Linienzug-Modi (frueher 3x wortgleich ausgeschrieben, nur der
;; Marker-String "linienzug"/"linienzug2"/"linienzug3" unterschied sich).
;;
;; Der Handler faengt jeden Abbruch (ESC / (exit) / Laufzeitfehler) ab, wickelt
;; die bis dahin gebaute Teil-Geometrie per vfl-modus-abbruch-sichern zu einem
;; VF_n-Block mit dem vollen Journal (Marker als XDATA) und stellt *error*
;; wieder her. Details/Begruendung (dynamic scoping, *ssg-ils-dim*-Reset,
;; ssg-end) siehe die ausfuehrlichen Kommentare an der frueheren Aufrufstelle
;; in vf-linienzug-modus.
;;
;; VERTRAG (dynamic scoping): Der Aufrufer MUSS als Locals gebunden haben und
;; VOR diesem Aufruf gesetzt haben: old-error (Ruecksprung-*error*), lastEnt,
;; vfl-nummer, anzahl-gf, anzahl-vf, startpunkt, frame, as-seite, es-seite,
;; dbg-an. Sie werden vom Handler-Lambda zur ABBRUCHZEIT dynamisch aufgeloest -
;; also gegen den dann noch aktiven Modus-Frame, nicht gegen diesen Konstruktor
;; (der ist beim Abbruch laengst zurueckgekehrt). marker wird dagegen ZUR
;; INSTALL-ZEIT als String-Literal in den Lambda eingebacken (list-Konstruktion),
;; damit der Handler ihn ohne globalen Zwischenzustand selbst traegt.
(defun vfl-abbruch-handler-installieren (marker)
;; *error* auf eine LAMBDA-LISTE setzen, in die der marker als String-Literal
;; eingebaut ist (list-Konstruktion). AutoLISP ruft *error* als Funktion auf,
;; egal ob (lambda ...) oder (function (lambda ...)) - die reine Lambda-Liste
;; genuegt und vermeidet eval/function-Feinheiten. Alle uebrigen Symbole
;; (old-error, lastEnt, ...) bleiben ungequotet-frei und werden zur Aufrufzeit
;; dynamisch aufgeloest.
(setq *error*
(list 'lambda '(msg)
'(setq *error* old-error)
'(setq *ssg-ils-dim* nil)
(list 'vfl-modus-abbruch-sichern 'lastEnt 'vfl-nummer 'anzahl-gf 'anzahl-vf
'startpunkt 'frame 'as-seite 'es-seite marker)
'(if dbg-an
(progn
(dbgmsg (strcat "=== ABBRUCH: " (if msg msg "(exit)") " ==="))
(dbgreturn nil)
(dbgclose)))
'(if (and (boundp '*ssg-start-stack*) *ssg-start-stack*) (ssg-end))
'(princ)))
*error*)
(defun vfl-modus-abbruch-sichern (lastEnt vfl-nummer anzahl-gf anzahl-vf
startpunkt frame as-seite es-seite marker /
e cnt vfl-ins wickel-erg grund fwd n trunc)
;; Abbruch-Grund VOR dem Wickeln merken (das Flag wird unten evtl. schon
;; fuer den "Zurueck"-Neuaufruf zurueckgesetzt) UND immer konsumieren, damit
;; kein "Zurueck"/"Abbrechen" aus diesem Lauf in einen etwaigen naechsten
;; durchsickert (siehe auch Reset am Kopf von vf-linienzug-modus).
(setq grund *vfl-wizard-abbruch-grund*)
(setq *vfl-wizard-abbruch-grund* nil)
(dbgmsg (strcat "ABBRUCH-SICHERN erreicht: grund=" (if grund grund "nil(ESC/Fehler)")))
(setq cnt 0 e (if lastEnt (entnext lastEnt) (entnext)))
(while e (setq cnt (1+ cnt)) (setq e (entnext e)))
(cond
((or (= cnt 0) (null frame))
(princ (ssg-text "vfl-abbruch-nichts-gebaut"))
;; "Zurueck" ohne jede gebaute Geometrie (z.B. ganz am Anfang) - nichts
;; zu verwerfen, einfach denselben Lauf frisch neu starten.
(if (= grund "zurueck") (vl-catch-all-apply 'vf-linienzug-modus)))
(t
;; vfl-block-erstellen nutzt intern (command "_.UCS" ...) fuer den
;; BKS-Wechsel (ssg-block-wrap-welt) - im *error*-Handler-Kontext
;; riskanter als eine einfache Loesch-Schleife, daher abgesichert.
(setq wickel-erg
(vl-catch-all-apply
(function (lambda ()
(setq vfl-ins
(vfl-block-erstellen vfl-nummer anzahl-gf anzahl-vf
(caddr startpunkt) (caddr (car frame))
(vfl-planar-dist startpunkt (car frame))
as-seite es-seite startpunkt lastEnt))
(if vfl-ins (vfl-journal-xdata-schreiben vfl-ins marker))))
nil))
(if (vl-catch-all-error-p wickel-erg)
(princ (ssg-textf "vfl-abbruch-wickeln-fehler"
(list (vl-catch-all-error-message wickel-erg))))
(progn
(princ (ssg-textf "vfl-abbruch-gesichert" (list (itoa cnt))))
(setq *vfl-abbruch-letztes-ent* vfl-ins)
;; "Zurueck": letztes (angefangenes) Glied verwerfen und automatisch
;; genau das tun, was der Sektions-Reset-Zweig von vfl-edit-ent sonst
;; ueber den Auswahl-Dialog macht - nur ohne den Dialog, Ziel-Sektion
;; ist immer "ein Glied weniger als jetzt vorhanden".
(if (and (= grund "zurueck") vfl-ins)
(progn
(setq fwd (reverse *vfl-journal*))
(setq n (length (vfl-journal-glieder fwd)))
(setq trunc (vfl-journal-truncate fwd (max 0 (1- n))))
(princ (ssg-text "vfl-zurueck-glied-verworfen"))
(entdel vfl-ins)
(vfl-journal-replay-start trunc)
(vl-catch-all-apply 'vf-linienzug-modus)))
)
)
)
)
(princ))
;; Replay-Modus scharf schalten: die uebergebene (Vorwaerts-)Journalliste wird
;; zur Lesequelle, *vfl-journal* startet leer und wird bei der erneuten
;; Ausfuehrung neu aufgebaut. Genutzt von vfl-edit-ent und der Wiederaufnahme.
(defun vfl-journal-replay-start (forward-journal)
(setq *vfl-replay-queue* forward-journal *vfl-journal* '()
*vfl-seg-glied-letzte* 0)
(if (boundp '*vflw-pending*) (setq *vflw-pending* nil)))
;; --- Glied-Zerlegung fuer den Editier-Dialog ---
;; Labels der Glieder (STEP-Marker) in Bau-Reihenfolge.
(defun vfl-journal-glieder (forward-journal / out)
(setq out '())
(foreach e forward-journal
(if (= (car e) "STEP") (setq out (cons (cdr e) out))))
(reverse out))
;; Journal auf die ersten (keep) Glieder kuerzen: Praeambel (vor dem 1. STEP)
;; bleibt immer erhalten; ab dem (keep+1)-ten STEP wird alles verworfen.
(defun vfl-journal-truncate (forward-journal keep / seen out done)
(setq seen 0 out '() done nil)
(foreach e forward-journal
(if (not done)
(if (= (car e) "STEP")
(if (< seen keep)
(progn (setq seen (1+ seen)) (setq out (cons e out)))
(setq done t))
(setq out (cons e out)))))
(reverse out))
;; Journal-Eintraege GENAU eines Glieds liefern: der (from-step-idx)-te STEP
;; (1-basiert, in Bau-Reihenfolge) bis ausschliesslich dem naechsten STEP.
;; Praeambel-Eintraege vor dem 1. STEP gehoeren zu keinem Glied und werden
;; hier nie zurueckgegeben (anders als vfl-journal-truncate, das sie immer
;; mitnimmt). Genutzt fuer die Segment-XDATA (vfl-segment-xdata-sichern) und
;; das Ersetzen eines einzelnen Glieds (vfl-journal-splice).
(defun vfl-journal-slice (forward-journal from-step-idx / seen out started done)
(setq seen 0 out '() started nil done nil)
(foreach e forward-journal
(if (not done)
(cond
((and (= (car e) "STEP") started) (setq done t))
((= (car e) "STEP")
(setq seen (1+ seen))
(if (= seen from-step-idx) (progn (setq started t) (setq out (cons e out)))))
(started (setq out (cons e out))))))
(reverse out))
;; Journal-Eintraege AB dem (from-step-idx)-ten Glied bis zum ENDE des
;; Journals (alle folgenden STEPs inklusive). Gegenstueck zu
;; vfl-journal-slice, das bei genau einem Glied stoppt.
;; Gebraucht fuer die Segment-XDATA: eine Iteration der Bau-Hauptschleife kann
;; MEHRERE Glieder erzeugen (eine VF-Einheit mit eingebetteten Vario-Kurven
;; legt Horizontal-VF/Linie-VF + je Kurve ein eigenes STEP ab). Auf die
;; Entities dieser Iteration muss deshalb der GESAMTE in ihr entstandene
;; Journal-Abschnitt, nicht nur die Slice des letzten Glieds - sonst fehlen
;; die Eingaben der Einheit selbst und die Kette ist aus der Zeichnung nicht
;; mehr vollstaendig rekonstruierbar (c:VF_SEKTION_RESTORE).
(defun vfl-journal-ab-glied (forward-journal from-step-idx / seen out started)
(setq seen 0 out '() started nil)
(foreach e forward-journal
(if started
(setq out (cons e out))
(if (= (car e) "STEP")
(progn
(setq seen (1+ seen))
(if (= seen from-step-idx)
(progn (setq started t) (setq out (cons e out))))))))
(reverse out))
;; Anzahl der Glieder (STEP-Eintraege) in einem Journal-Abschnitt.
(defun vfl-steps-zaehlen (journal / n)
(setq n 0)
(foreach e journal (if (= (car e) "STEP") (setq n (1+ n))))
n)
;; Journal-Eintraege des Glieds (seg-idx) durch (new-seg-journal) ersetzen -
;; alle anderen Glieder (davor UND danach) sowie die Praeambel bleiben
;; unveraendert. Gegenstueck zu vfl-journal-slice (welches genau dieses Glied
;; ISOLIERT liefert). Genutzt von der Einzelsegment-Bearbeitung (vfl-edit-glied).
(defun vfl-journal-splice (forward-journal seg-idx new-seg-journal / seen out)
(setq seen 0 out '())
(foreach e forward-journal
(progn
(if (= (car e) "STEP") (setq seen (1+ seen)))
(cond
((< seen seg-idx) (setq out (cons e out))) ; vor dem Ziel-Glied: unveraendert
((> seen seg-idx) (setq out (cons e out))) ; nach dem Ziel-Glied: unveraendert
((and (= seen seg-idx) (= (car e) "STEP")) ; Ziel-Glied: einmalig durch
(setq out (append (reverse new-seg-journal) out)))))) ; new-seg-journal ersetzen,
(reverse out)) ; alle uebrigen Eintraege verwerfen
;; Internes (sprachneutrales) Glied-Token in lesbaren Text der AKTUELLEN Sprache
;; uebersetzen. Da im Journal nur das Token steht, folgt die Anzeige immer der
;; aktiven Sprache - ein in Deutsch aufgezeichnetes Journal zeigt in englischer
;; UI englische Labels (und umgekehrt).
(defun vfl-glied-label-text (w)
(cond ((= w "GF-Bogen") (ssg-text "vfl-glied-gf-bogen"))
((= w "Linie-GF") (ssg-text "vfl-glied-gf"))
((= w "Linie-VF") (ssg-text "vfl-glied-vf"))
((= w "Horizontal-VF") (ssg-text "vfl-glied-horizontal-vf"))
((= w "Linie") (ssg-text "vfl-glied-linie"))
((= w "AS") (ssg-text "vfl-glied-as"))
((= w "ES") (ssg-text "vfl-glied-es"))
((= w "Vario-Kurve") (ssg-text "vfl-glied-vario"))
((= w "?") (ssg-text "vfl-glied-offen"))
(t w)))
;; ============================================================
;; TEIL 1: SEGMENT-ENTSCHEIDUNG (GF oder VF)
;; ============================================================
;; Aus der Ergebnisliste von berechne-alle-winkel die GUELTIGEN Winkel filtern
;; und - bei mehreren - den Nutzer waehlen lassen. Rueckgabe: (winkel L_GF L_VF)
;; oder nil, wenn kein gueltiger Winkel existiert.
;; --- Winkelwahl: reine Teile + Frage-Schale ---
;; Die gueltigen Kandidaten aus einer berechne-alle-winkel-Ergebnisliste
;; filtern. Gueltig heisst: baubar (cadddr) UND beide Laengen positiv.
;; Reine Funktion, keine Frage, keine Globals.
(defun vfl-winkel-gueltige (ergebnis-liste / gueltige)
(setq gueltige '())
(foreach e ergebnis-liste
(if (and (cadddr e) (numberp (cadr e)) (numberp (caddr e))
(> (cadr e) 0) (> (caddr e) 0))
(setq gueltige (append gueltige (list e)))))
gueltige)
;; Kandidat Nummer idx (1-basiert) als (winkel L_GF L_VF). Ein Index
;; ausserhalb der Liste faellt auf den ersten Kandidaten zurueck - so
;; verhielt sich die Frage-Schale schon immer bei ungueltiger Eingabe.
(defun vfl-winkel-nach-index (gueltige idx / e)
(if (or (null idx) (< idx 1) (> idx (length gueltige))) (setq idx 1))
(setq e (nth (1- idx) gueltige))
(if e (list (nth 0 e) (nth 1 e) (nth 2 e)) nil))
;; Vorgabe-Kanal: ist *vfl-winkel-idx-vorgabe* gesetzt, wird NICHT gefragt,
;; sondern dieser Kandidat genommen. Derselbe Mechanismus, den die Datei
;; schon fuer die Wizard-Menues nutzt (dynamisch gebundene *vflw-menu-*).
;; Warum ein Kanal und kein Parameter: vfl-waehle-winkel wird aus vier
;; Solvern gerufen (vfl-vf-winkel, vfl-vf-entscheidung, vfl-body-zerlegung,
;; vfl-segment-entscheidung) - ein zusaetzlicher Parameter wuerde vier
;; Signaturen und alle deren Aufrufer aendern.
;; ACHTUNG: die Vorgabe gilt fuer JEDE folgende Winkelfrage, bis sie
;; zurueckgesetzt wird. Setzer muessen sie darum wieder auf nil stellen.
(if (not (boundp '*vfl-winkel-idx-vorgabe*))
(setq *vfl-winkel-idx-vorgabe* nil))
;; Hilfsfunktionen fuer winkel-werte-Listen, die je nach Aufrufer Zahlen
;; (GF-Bogen) oder Strings (ES) enthalten koennen.
(defun vfl-winkel-als-zahl (w) (if (numberp w) w (atoi w)))
(defun vfl-winkel-in-liste-p (grad liste)
(vl-some (function (lambda (w) (= (vfl-winkel-als-zahl w) grad))) liste))
;; Punkt 7: fuer eine VF-Einheit mit reduziertem Eingang (eingang-modus <>
;; nil, siehe vfl-eingang-modus-bestimmen) gibt es KEINE frei waehlbare
;; GF-Laenge mehr, mit der eine beliebige Zielhoehe exakt getroffen werden
;; koennte (L_GF ist dort immer 0.0) - stattdessen wird der Kletter-Winkel
;; aus der Bogen-Bibliothek (*vfk-bogen-winkel* ueber ssg-cfg-or) probiert
;; und die sich daraus ERGEBENDE Endhoehe je Winkel tabellarisch angezeigt.
;; Rueckgabe je Zeile: (winkel dH L_VF gueltig).
(defun vfl-winkel-hoehen-optionen (deltaL richtung / winkel-list rad3 w
mass1 mass2 r2 winkel-eff radEff
bx1 bz1 bx2 bz2 L_VF dH gueltig ergebnisse)
(setq winkel-list (ssg-cfg-or "vario" "bogen_winkel"
'(3 6 9 12 15 18 21 27 33 39 45 51)))
(setq rad3 (* *vfk-gefaelle-winkel* (/ pi 180.0)))
(setq ergebnisse '())
(foreach w winkel-list
(if (= richtung "Auf")
(setq mass1 (get-bogen-mass bogen-auf w) mass2 (get-bogen-mass bogen-ab w)
r2 (* (float (- *vfk-gefaelle-winkel* w)) (/ pi 180.0))
winkel-eff (- w *vfk-gefaelle-winkel*))
(setq mass1 (get-bogen-mass bogen-ab w) mass2 (get-bogen-mass bogen-auf w)
r2 (* (float (+ w *vfk-gefaelle-winkel*)) (/ pi 180.0))
winkel-eff (+ w *vfk-gefaelle-winkel*)))
(setq dH nil L_VF nil gueltig nil)
(if (and mass1 mass2)
(progn
(setq bx1 (+ (* (car mass1) (cos rad3)) (* (caddr mass1) (sin rad3))))
(setq bz1 (+ (* (- (car mass1)) (sin rad3)) (* (caddr mass1) (cos rad3))))
(setq bx2 (+ (* (car mass2) (cos r2)) (* (caddr mass2) (sin r2))))
(setq bz2 (+ (* (- (car mass2)) (sin r2)) (* (caddr mass2) (cos r2))))
(setq radEff (* (float winkel-eff) (/ pi 180.0)))
(setq L_VF (/ (- deltaL bx1 bx2) (cos radEff)))
(if (> L_VF *vfk-restlaenge-min-clamp*)
(progn
(setq dH (+ bz1 bz2 (* L_VF (sin radEff))))
(setq gueltig t)
)
)
)
)
(setq ergebnisse (append ergebnisse (list (list w dH L_VF gueltig))))
)
ergebnisse)
;; UI zu vfl-winkel-hoehen-optionen: ALLE konfigurierten Winkel als Tabelle
;; zeigen (gueltige mit resultierender Endhoehe, ungueltige mit Hinweis
;; "Bogen passt nicht in die gewaehlte Strecke"), dann aus den GUELTIGEN
;; waehlen lassen. start-z = aktuelle Z-Koordinate (fuer die Anzeige der
;; absoluten Ende-Hoehe). Rueckgabe: (winkel dH L_VF gueltig) oder nil, wenn
;; kein einziger Winkel bei dieser Streckenlaenge baubar ist.
(defun vfl-waehle-winkel-hoehen-tabelle (deltaL richtung start-z /
optionen gueltige idx labels e antwort)
(setq optionen (vfl-winkel-hoehen-optionen deltaL richtung))
(princ (ssg-text "vfl-winkel-hoehen-header"))
(foreach e optionen
(if (nth 3 e)
(princ (ssg-textf "vfl-winkel-hoehen-zeile" (list (car e) (rtos (+ start-z (nth 1 e)) 2 0))))
(princ (ssg-textf "vfl-winkel-hoehen-zeile-ungueltig" (list (car e))))
)
)
(setq gueltige (vl-remove-if-not (function (lambda (e) (nth 3 e))) optionen))
(cond
((null gueltige) nil)
((= (length gueltige) 1) (car gueltige))
(t
(setq idx 1 labels '())
(foreach e gueltige
(setq labels (append labels
(list (ssg-textf "vfl-winkel-hoehen-option"
(list idx (car e) (rtos (+ start-z (nth 1 e)) 2 0))))))
(setq idx (1+ idx)))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-winkel-hoehen-impl (list optionen gueltige start-z)))
(setq antwort (vfl-menu-int (ssg-textf "vfl-prompt-wahl-bis-n" (list (length gueltige)))
labels 1 "vfl-winkel-hoehen-header"))
(if (or (null antwort) (< antwort 1) (> antwort (length gueltige))) (setq antwort 1))
(nth (1- antwort) gueltige))))
;; Frage-Schale (Name unveraendert, alle Aufrufstellen bleiben): gueltige
;; Kandidaten bestimmen, bei genau einem ohne Frage nehmen, sonst fragen -
;; bzw. die Vorgabe verwenden.
(defun vfl-waehle-winkel (ergebnis-liste / gueltige idx antwort e labels)
(setq gueltige (vfl-winkel-gueltige ergebnis-liste))
(cond
((null gueltige) nil)
((= (length gueltige) 1) (vfl-winkel-nach-index gueltige 1))
(*vfl-winkel-idx-vorgabe*
(vfl-winkel-nach-index gueltige *vfl-winkel-idx-vorgabe*))
(t
(princ (ssg-text "vfl-mehrere-winkel-header"))
(setq idx 1 labels '())
(foreach e gueltige
(princ (ssg-textf "vfl-winkel-option"
(list idx (car e) (rtos (cadr e) 2 1) (rtos (caddr e) 2 1))))
(setq labels (append labels
(list (ssg-textf "vfl-winkel-option" (list idx (car e) (rtos (cadr e) 2 1) (rtos (caddr e) 2 1))))))
(setq idx (1+ idx)))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-winkel-wahl-impl (list ergebnis-liste gueltige)))
(setq antwort (vfl-menu-int (ssg-textf "vfl-prompt-wahl-bis-n" (list (length gueltige)))
labels 1 "vfl-mehrere-winkel-header"))
(vfl-winkel-nach-index gueltige antwort))))
;; berechne-alle-winkel ausfuehren (mit Linienzug-FESTE_HORIZONTAL = 1300) und
;; den Winkel waehlen lassen. Rueckgabe: (winkel L_GF L_VF) oder nil.
;; aus-dx/aus-dz temporaer nullen: an jeder Aufrufstelle (ueber vfl-vf-
;; entscheidung) ist ein evtl. vorhandenes AS-Element bereits real eingefuegt
;; und deltaL bereits aus dessen echtem KS_AUS neu berechnet (vfl-projiziere-
;; distanz) - der Platzbedarf ist also schon "verbraucht" und darf nicht
;; nochmal in berechne-alle-winkel abgezogen werden (sonst fehlt am Ende
;; systematisch genau dieser Betrag, aus-dx typischerweise mehrere hundert mm).
;; ein-dx/ein-dz bleiben unangetastet: das ES-Element ist an dieser Stelle noch
;; nicht gebaut (folgt erst nach der "Kettenende?"-Frage) - konsistent mit
;; vfl-body-zerlegung, die ebenfalls nur aus-dx/aus-dz nullt.
(defun vfl-vf-winkel (deltaL deltaH richtung / save-ausdx save-ausdz res)
(setq save-ausdx aus-dx save-ausdz aus-dz)
(setq aus-dx 0.0 aus-dz 0.0)
(setq res
(vfl-waehle-winkel
(nth 3 (berechne-alle-winkel deltaL deltaH richtung *vfl-feste-horizontal* nil))))
(setq aus-dx save-ausdx aus-dz save-ausdz)
res)
;; Rueckgabe: (typ winkel L_GF L_VF) - erzwingt IMMER eine VF-Einheit (nie GF),
;; genutzt sowohl von vfl-segment-entscheidung (automatische Zweig-Auswahl) als
;; auch direkt vom expliziten "Ab/Auf VF"-Menuepunkt in Modus 1 (vf-linienzug-modus).
;; typ="VF": winkel = best-winkel (0 = Horizontale Mitte), L_GF/L_VF wie
;; berechne-alle-winkel bzw. berechne-horizontale-mitte
;; typ=nil : keine VF-Einheit fuer dieses deltaL/deltaH geometrisch moeglich
;; Duenner Wrapper: eigene Mindestlaenge (Umlenkstation+Motorstation), sonst
;; delegiert an vfl-winkel-stufe-entscheidung mit dem Eingangs-Budget einer
;; frischen VF-Einheit. schon-flach immer nil - der Frame kommt hier immer
;; frisch von der Umlenkstation bei 3 Grad, nie flach.
(defun vfl-vf-entscheidung (deltaL deltaH richtung)
(if (< deltaL *vfk-vf-mindestlaenge*)
(list nil nil nil nil)
(vfl-winkel-stufe-entscheidung deltaL deltaH richtung nil *vfl-feste-horizontal*)))
;; Stumme Variante von berechne-alle-winkel: NUR die Machbarkeits-Pruefung
;; (mind. ein Winkel mit L_GF>=0 und L_VF>=0), OHNE die Diagnosetabelle auf
;; die Kommandozeile zu schreiben. Rechenweg 1:1 aus berechne-alle-winkel
;; (vf_standard.lsp) uebernommen - bei Aenderungen dort auch hier nachziehen.
;; Gebraucht von vfl-vf-machbar-p fuer die Sondierung in vfl-vf-min-deltah,
;; die viele Testwerte durchprobiert (dort wuerde die Tabellenausgabe von
;; berechne-alle-winkel die Konsole zuspammen).
(defun vfl-winkel-machbar-p (deltaL deltaH richtung feste-hz schon-flach /
winkel-list cos3 sin3 mass1 mass2 rad5 rad7
bogen-x1 bogen-z1 bogen-x2 bogen-z2 cosa sina
A B winkel-eff cosEff sinEff L_GF L_VF
abs-dz-AUS abs-dz-EIN gefunden entry-mass entry-dx entry-dz)
(if (null feste-hz) (setq feste-hz FESTE_HORIZONTAL))
(setq entry-dx 0.0 entry-dz 0.0)
(if schon-flach
(progn
(setq entry-mass (get-bogen-mass bogen-ab (fix *vfk-gefaelle-winkel*)))
(setq entry-dx (car entry-mass) entry-dz (caddr entry-mass))))
(setq abs-dz-AUS (abs aus-dz) abs-dz-EIN (abs ein-dz))
(setq winkel-list (ssg-cfg-or "vario" "bogen_winkel"
'(3 6 9 12 15 18 21 27 33 39 45 51)))
(setq cos3 (cos (* (ssg-cfg-or "vario" "gefaelle_winkel" 3) (/ pi 180.0)))
sin3 (sin (* (ssg-cfg-or "vario" "gefaelle_winkel" 3) (/ pi 180.0))))
(setq gefunden nil)
(foreach winkel winkel-list
(if (not gefunden)
(progn
(if (= richtung "Auf")
(setq mass1 (get-bogen-mass bogen-auf winkel)
mass2 (get-bogen-mass bogen-ab winkel))
(setq mass1 (get-bogen-mass bogen-ab winkel)
mass2 (get-bogen-mass bogen-auf winkel)))
(if (and mass1 mass2)
(progn
(setq rad5 (* *vfk-gefaelle-winkel* (/ pi 180.0)))
(setq rad7 (* (float (if (= richtung "Auf") (- *vfk-gefaelle-winkel* winkel) (+ winkel *vfk-gefaelle-winkel*)))
(/ pi 180.0)))
(setq bogen-x1 (+ (* (car mass1) (cos rad5)) (* (caddr mass1) (sin rad5))))
(setq bogen-z1 (+ (* (- (car mass1)) (sin rad5)) (* (caddr mass1) (cos rad5))))
(setq bogen-x2 (+ (* (car mass2) (cos rad7)) (* (caddr mass2) (sin rad7))))
(setq bogen-z2 (+ (* (- (car mass2)) (sin rad7)) (* (caddr mass2) (cos rad7))))
(setq cosa (cos (* winkel (/ pi 180.0)))
sina (sin (* winkel (/ pi 180.0))))
(setq A (- deltaL aus-dx ein-dx bogen-x1 bogen-x2 (* feste-hz cos3) entry-dx))
(setq winkel-eff (if (= richtung "Auf") (- winkel *vfk-gefaelle-winkel*) (+ winkel *vfk-gefaelle-winkel*)))
(setq cosEff (cos (* winkel-eff (/ pi 180.0)))
sinEff (sin (* winkel-eff (/ pi 180.0))))
(if (= richtung "Auf")
(setq B (+ deltaH abs-dz-AUS abs-dz-EIN (abs entry-dz) (- bogen-z1) (- bogen-z2)
(* feste-hz sin3)))
(setq B (+ (- deltaH abs-dz-AUS abs-dz-EIN (abs entry-dz) (* feste-hz sin3))
bogen-z1 bogen-z2)))
(if (> (abs sina) 0.0001)
(progn
(setq L_GF (/ (- (* A sinEff) (* B cosEff)) sina))
(if (= richtung "Auf")
(setq L_VF (/ (+ (* A sin3) (* B cos3)) sina))
(setq L_VF (/ (- (* B cos3) (* A sin3)) sina)))
(if (and (numberp L_GF) (numberp L_VF) (>= L_GF 0) (>= L_VF 0))
(setq gefunden t))
)
)
)
)
)
)
)
gefunden
)
;; Nur-Machbarkeits-Check (kein interaktives Waehlen wie vfl-waehle-winkel):
;; T, wenn fuer deltaL/deltaH/richtung MINDESTENS ein gueltiger Winkel bzw.
;; die horizontale Mitte existiert - sonst nil. Nutzt dieselbe Fallunter-
;; scheidung wie vfl-vf-entscheidung, aber ohne Nutzer-Interaktion (fuer die
;; Sondierung in vfl-vf-min-deltah). aus-dx/aus-dz werden wie in vfl-vf-winkel
;; temporaer genullt (AS-Footprint an dieser Stelle bereits verbraucht) -
;; sonst wuerde die Sondierung ein zu kleines Budget sehen und einen
;; eigentlich baubaren Wert faelschlich als "nicht baubar" verwerfen.
(defun vfl-vf-machbar-p (deltaL deltaH richtung / horizontal-info save-ausdx save-ausdz ergebnis)
(if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek))
(cond
((< deltaL *vfk-vf-mindestlaenge*) nil)
((< deltaH 1.0) nil)
((= richtung "Auf")
(setq save-ausdx aus-dx save-ausdz aus-dz)
(setq aus-dx 0.0 aus-dz 0.0)
(setq ergebnis (vfl-winkel-machbar-p deltaL deltaH "Auf" *vfl-feste-horizontal* nil))
(setq aus-dx save-ausdx aus-dz save-ausdz)
ergebnis)
(t
(setq horizontal-info (berechne-horizontale-mitte deltaL deltaH "Ab" nil))
(if (and horizontal-info (caddr horizontal-info))
t
(progn
(setq save-ausdx aus-dx save-ausdz aus-dz)
(setq aus-dx 0.0 aus-dz 0.0)
(setq ergebnis (vfl-winkel-machbar-p deltaL deltaH "Ab" *vfl-feste-horizontal* nil))
(setq aus-dx save-ausdx aus-dz save-ausdz)
ergebnis)))
)
)
;; Kleinste baubare Hoehendifferenz (mm, > 0) fuer deltaL/richtung sondieren -
;; linearer Anstieg in 10-mm-Schritten (grob genug fuer einen Vorschlagswert,
;; fein genug um keine schmale gueltige Zone zu ueberspringen) bis 1/3 von
;; deltaL (grosszuegige Obergrenze; steilere Faelle braucht kein Vorschlag).
;; Rueckgabe: deltaH (mm) oder nil, wenn in diesem Bereich nichts baubar ist.
(defun vfl-vf-min-deltah (deltaL richtung / schritt grenze h gefunden)
(setq schritt 10.0)
(setq grenze (/ deltaL *vfk-gefaelle-winkel*))
(setq h schritt gefunden nil)
(while (and (not gefunden) (<= h grenze))
(if (vfl-vf-machbar-p deltaL h richtung)
(setq gefunden h)
(setq h (+ h schritt)))
)
gefunden
)
;; Liefert zwei baubare Ziel-Hoehen-Vorschlaege (Ab zuerst, Auf danach) fuer
;; die aktuelle Kettenhoehe p-aktuell-z bei gegebenem deltaL - Ersatz fuer den
;; blossen "unveraenderte Ist-Hoehe"-Default, der bei direkter Uebernahme
;; immer als deltaH=0 ("kein sinnvolles VF") abgelehnt wird (siehe
;; vfl-vf-entscheidung). NUR-VF-Variante: fuer den expliziten "Linie-VF"-
;; Menuepunkt, wo GF keine Option ist (der Nutzer hat VF bereits gewaehlt).
;; Rueckgabe: (hoehe-ab hoehe-auf) - je nil, wenn in der sondierten Zone kein
;; baubarer Wert existiert.
(defun vfl-vf-hoehe-vorschlaege (p-aktuell-z deltaL / min-ab min-auf)
(setq min-ab (vfl-vf-min-deltah deltaL "Ab"))
(setq min-auf (vfl-vf-min-deltah deltaL "Auf"))
(list
(if min-ab (- p-aktuell-z min-ab) nil)
(if min-auf (+ p-aktuell-z min-auf) nil))
)
;; Wie vfl-vf-hoehe-vorschlaege, aber fuer den automatischen "Linie"-Menue-
;; punkt (vfl-segment-entscheidung: GF ODER VF). Der Ab-Vorschlag nutzt dort
;; NICHT die VF-Sondierung, sondern direkt die feste 3-Grad-GF-Neigung
;; (deltaH = deltaL*tan(3 Grad), wie im bestehenden Kurzsegment-Zweig,
;; vfl-baue-mehrsegment-linie oben) - bei exakt 3 Grad natuerlichem Winkel
;; greift IMMER der GF-Zweig von vfl-segment-entscheidung (Toleranzband um
;; 3 Grad), eine reine Gefaellestrecke hat keine deltaL-Mindestlaenge und ist
;; damit garantiert baubar, ohne die (teurere) VF-Sondierung zu brauchen. Der
;; Auf-Vorschlag bleibt VF-only (GF kann nicht steigen).
(defun vfl-segment-hoehe-vorschlaege (p-aktuell-z deltaL / min-ab min-auf rad3)
(setq rad3 (* (float (ssg-cfg-or "vario" "gefaelle_winkel" 3)) (/ pi 180.0)))
(setq min-ab (* deltaL (/ (sin rad3) (cos rad3))))
(setq min-auf (vfl-vf-min-deltah deltaL "Auf"))
(list
(- p-aktuell-z min-ab)
(if min-auf (+ p-aktuell-z min-auf) nil))
)
;; Hinweis auf die Konsole, wenn NUR EINE der beiden Richtungen einen
;; baubaren Vorschlag hat (die andere Richtung ist bei dieser Segmentlaenge
;; also aussichtslos) - macht sichtbar, warum der Dialog/Prompt dann nur
;; einen Wert zeigt, statt es wie einen Fehler wirken zu lassen.
(defun vfl-hoehe-hinweis-falls-noetig (hoehe-ab hoehe-auf)
(if (or (and hoehe-ab (not hoehe-auf)) (and hoehe-auf (not hoehe-ab)))
(princ (ssg-text "vfl-hinweis-hoehe-vorschlag-fehlt")))
(princ)
)
;; Gemeinsame Frage-Schale "Zielhoehe nach Endpunkt-Pick" - buendelt das an
;; mehreren Stellen (vfl-body-abschluss/Kettenende, VF-Fortsetzung Auf/Ab,
;; Horizontal-VF-Eingang, "Linie" GF-Familie) fast identisch wiederholte
;; Muster (Wizard-Dialog + Konsolen-Fallback + leere Eingabe -> Vorschlag).
;; *vfl-letzter-pick-z* (von vfl-in-abstand gesetzt, siehe dort) hat Vorrang
;; vor dem rein rechnerischen Ab-Vorschlag: wurde der Endpunkt per Objektfang
;; auf einem echten 3D-Punkt (z.B. einem vorhandenen Block) gepickt, ist genau
;; diese Hoehe die vom Nutzer gemeinte Zielhoehe - nicht der naechstgelegene
;; nur rechnerisch baubare Wert. Der Vorschlag bleibt im Eingabefeld/Dialog
;; jederzeit ueberschreibbar; der final gewaehlte Wert (hn) fliesst dabei
;; UNVERAENDERT in den Bau ein (siehe Aufrufer) - eine manuelle Korrektur wird
;; also korrekt weiterverrechnet, nicht verworfen.
;; schluessel-basis waehlt die passenden lang-Keys ("endpunkt"/"kettenende"/
;; "linienendpunkt", siehe lang/de_DE.json) - vor dem Anlegen weiterer
;; Varianten immer erst pruefen, ob eine der drei bestehenden passt.
;; Rueckgabe: die gewaehlte Zielhoehe, NIE nil (leere Eingabe -> Vorschlag).
(defun vfl-hoehe-endpunkt-abfragen (hoehe-ab hoehe-auf ist-hoehe schluessel-basis / hoehe-fallback hn)
(setq hoehe-fallback
(cond (*vfl-letzter-pick-z* *vfl-letzter-pick-z*) (hoehe-ab hoehe-ab) (t ist-hoehe)))
(vfl-hoehe-hinweis-falls-noetig hoehe-ab hoehe-auf)
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-ziel-hoehe-impl
(list (list hoehe-ab hoehe-auf ist-hoehe *vfl-letzter-pick-z*))))
(setq hn
(vfl-in-real
(cond
((and hoehe-ab hoehe-auf *vfl-letzter-pick-z*)
(ssg-textf (strcat "vfl-prompt-hoehe-" schluessel-basis "-3")
(list (rtos hoehe-ab 2 1) (rtos hoehe-auf 2 1) (rtos *vfl-letzter-pick-z* 2 1))))
((and hoehe-ab hoehe-auf)
(ssg-textf (strcat "vfl-prompt-hoehe-" schluessel-basis "-2")
(list (rtos hoehe-ab 2 1) (rtos hoehe-auf 2 1))))
(t
(ssg-textf (strcat "vfl-prompt-hoehe-" schluessel-basis)
(list (rtos hoehe-fallback 2 1)))))))
(if (null hn) (setq hn hoehe-fallback))
hn
)
;; Konsolen-Echo nach Endpunkt-Pick + Zielhoehe-Eingabe: zeigt den absoluten
;; Endpunkt (X/Y, aus p-aktuell + deltaL entlang hz-neu zurueckgerechnet - nur
;; deltaL/hz werden journalisiert, siehe vfl-in-abstand) sowie die eingegebene
;; Zielhoehe UND das daraus resultierende deltaH VOR jeder spaeteren
;; Footprint-Reduktion (Separator/ES bei linie-ende-modus) - damit am
;; Bildschirm nachvollziehbar bleibt, welcher Rohwert in eine spaeter
;; angezeigte, ggf. reduzierte Zahl eingeht (sonst wirkt ein reduzierter Wert
;; wie ein Rechenfehler statt wie eine bewusste Reservierung).
(defun vfl-echo-endpunkt-hoehe (p-aktuell deltaL hz-neu hoehe-neu deltaH richtung / rad ex ey)
(setq rad (* (float hz-neu) (/ pi 180.0)))
(setq ex (+ (car p-aktuell) (* deltaL (cos rad))))
(setq ey (+ (cadr p-aktuell) (* deltaL (sin rad))))
(princ (ssg-textf "vfl-echo-endpunkt-hoehe"
(list (rtos ex 2 1) (rtos ey 2 1) (rtos deltaL 2 0) (rtos hz-neu 2 1)
(rtos hoehe-neu 2 1) (rtos (caddr p-aktuell) 2 1)
(rtos deltaH 2 1) richtung)))
(princ)
)
;; Konsolen-Echo der aktuellen Kettenposition (X/Y/Z) - nach jedem gebauten
;; Kettenglied aufgerufen, damit der Nutzer ohne Attribut-Blick sofort sieht,
;; wo die Kette gerade steht (Wunsch nach durchgaengigem Fortschritts-Echo,
;; analog zu vfl-echo-endpunkt-hoehe).
(defun vfl-echo-position (pt)
(princ (ssg-textf "vfl-echo-position"
(list (rtos (car pt) 2 2) (rtos (cadr pt) 2 2) (rtos (caddr pt) 2 2))))
(princ)
)
;; Konsolen-Echo der getroffenen Menue-Auswahl (Label-Text statt nur "1"/"2"),
;; damit ein Log/Protokoll nachvollziehbar bleibt, was gewaehlt wurde.
(defun vfl-echo-wahl (label)
(princ (ssg-textf "vfl-echo-wahl" (list label)))
(princ)
)
;; Wie vfl-vf-machbar-p, aber fuer das Budget von vfl-body-zerlegung
;; (Kettenende-Modus, "Ja - Motorstation setzen, Kettenende festlegen"):
;; feste-hz=800 (nur Motor+Separator, KEIN Umlenk-/AS-Anteil) statt
;; *vfl-feste-horizontal*, Mindestlaenge 800mm statt 1000mm, und bei
;; praktisch flachem Rest (deltaH<1) NUR die horizontale Mitte (kein
;; direkter GF-Fallback wie bei vfl-vf-machbar-p - vfl-body-zerlegung prueft
;; GF nur beim natuerlichen ~3-Grad-Fall weiter unten). aus-dx/aus-dz werden
;; wie im Original immer genullt (nicht nur bedingt wie bei vfl-vf-machbar-p),
;; da der Abschluss-Koerper nie einen AS-Fussabdruck traegt.
;; Reserviertes Fest-Budget am Kettenende: Motor+Separator (800), AUSSER kein
;; ES gewuenscht - dann baut vfl-insert-es-element (das den Separator
;; MITBRINGT) gar nicht erst, also darf auch kein Separator reserviert werden
;; (sonst bleibt die Kette um dessen Laenge hinter dem Zielpunkt zurueck -
;; realer Bug, siehe vfl-body-zerlegung).
(defun vfl-body-budget (es-gewuenscht)
(if es-gewuenscht
*vfk-feste-horizontal-kettenende*
(- *vfk-feste-horizontal-kettenende* *vfk-separator-laenge*)))
(defun vfl-body-machbar-p (deltaL deltaH richtung es-gewuenscht schon-flach /
horizontal-info save-ausdx save-ausdz ergebnis budget)
(if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek))
(setq budget (vfl-body-budget es-gewuenscht))
(setq save-ausdx aus-dx save-ausdz aus-dz)
(setq aus-dx 0.0 aus-dz 0.0)
(setq ergebnis
(cond
((< deltaL budget) nil)
((< deltaH 1.0)
(setq horizontal-info (berechne-horizontale-mitte deltaL deltaH "Ab" schon-flach))
(and horizontal-info (caddr horizontal-info) t))
((= richtung "Auf")
(vfl-winkel-machbar-p deltaL deltaH "Auf" budget schon-flach))
(t
(setq horizontal-info (berechne-horizontale-mitte deltaL deltaH "Ab" schon-flach))
(if (and horizontal-info (caddr horizontal-info))
t
(vfl-winkel-machbar-p deltaL deltaH "Ab" budget schon-flach)))
)
)
(setq aus-dx save-ausdx aus-dz save-ausdz)
ergebnis
)
;; Wie vfl-vf-min-deltah, aber mit vfl-body-machbar-p (Budget von
;; vfl-body-zerlegung statt vfl-vf-entscheidung).
(defun vfl-body-min-deltah (deltaL richtung es-gewuenscht schon-flach / schritt grenze h gefunden)
(setq schritt 10.0)
(setq grenze (/ deltaL *vfk-gefaelle-winkel*))
(setq h schritt gefunden nil)
(while (and (not gefunden) (<= h grenze))
(if (vfl-body-machbar-p deltaL h richtung es-gewuenscht schon-flach)
(setq gefunden h)
(setq h (+ h schritt)))
)
gefunden
)
;; Wie vfl-vf-hoehe-vorschlaege, aber fuer vfl-body-abschluss (Kettenende-
;; Modus): nutzt vfl-body-min-deltah (800mm-Budget, 500mm ohne ES) statt
;; vfl-vf-min-deltah (1300mm-Budget) - sonst wuerde ein Vorschlag als baubar
;; erscheinen, der es am Kettenende (kleineres Budget: nur Motor+Separator,
;; kein Umlenk-Anteil) tatsaechlich nicht ist.
(defun vfl-body-hoehe-vorschlaege (p-aktuell-z deltaL es-gewuenscht schon-flach / min-ab min-auf)
(setq min-ab (vfl-body-min-deltah deltaL "Ab" es-gewuenscht schon-flach))
(setq min-auf (vfl-body-min-deltah deltaL "Auf" es-gewuenscht schon-flach))
(list
(if min-ab (- p-aktuell-z min-ab) nil)
(if min-auf (+ p-aktuell-z min-auf) nil))
)
;; Rueckgabe: (typ winkel L_GF L_VF)
;; typ="GF": winkel = kontinuierlicher Neigungswinkel, L_GF/L_VF ungenutzt (nil)
;; typ="VF": winkel = best-winkel (0 = Horizontale Mitte), L_GF/L_VF wie
;; berechne-alle-winkel bzw. berechne-horizontale-mitte
;; typ=nil : weder GF noch VF fuer dieses deltaL/deltaH geometrisch moeglich
(defun vfl-segment-entscheidung (deltaL deltaH richtung / winkel-natuerlich)
(if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek))
(cond
;; Zu kurz fuer eine VF-Einheit: Umlenkstation (500 mm) + Motorstation
;; (500 mm) belegen zusammen 1000 mm deltaL - darunter ist kein VF baubar.
;; Also nur GF moeglich, und GF ist mindestens 3 Grad geneigt -> fest 3 Grad.
;; Steigend geht mit einer Gefaellestrecke nicht -> nicht baubar.
((< deltaL *vfk-vf-mindestlaenge*)
(if (= richtung "Auf")
(list nil nil nil nil)
(list "GF" *vfk-gefaelle-winkel* nil nil)))
;; Steigend: Gefaelle kann nicht steigen -> nur VF moeglich. deltaH<1.0
;; (kein messbarer Hoehenunterschied) faellt jetzt NICHT mehr sofort raus -
;; vfl-vf-entscheidung (ueber vfl-winkel-stufe-entscheidung) behandelt das
;; selbst als "praktisch flach" (horizontales VF + GF2, Stufe 2).
((= richtung "Auf") (vfl-vf-entscheidung deltaL deltaH richtung))
;; Fallend: natuerlichen Neigungswinkel bestimmen (atan der Schraege).
;; Eine reine Gefaellestrecke ist nie steiler als 3 Grad, daher:
;; ~3 Grad (Toleranzband) -> reine GF, am NATUERLICHEN Winkel gebaut
;; sonst -> VF (vfl-vf-entscheidung)
;; WICHTIG: winkel-natuerlich (nicht der feste *vfk-gefaelle-winkel*) wird
;; zurueckgegeben - vfl-insert-gf-segment (der tatsaechliche Bauaufruf,
;; siehe Aufrufer) baut GENAU bei diesem Winkel ueber die volle deltaL,
;; trifft deltaH damit per Definition exakt. Der feste 3-Grad-Wert wuerde
;; hier - anders als beim Kettenende-Abschluss (vfl-winkel-stufe-
;; entscheidung/vfl-body-abschluss, wo GF2 ohnehin IMMER fest bei 3 Grad
;; gebaut wird und ein abweichender Rueckgabewinkel wirkungslos waere) -
;; einen echten Hoehenfehler erzeugen, sobald der natuerliche Winkel nur
;; NAHE (Toleranz *vfl-gf-winkel-toleranz*=0.5 Grad), aber nicht EXAKT 3
;; Grad ist.
(t
(setq winkel-natuerlich (* (atan (/ deltaH deltaL)) (/ 180.0 pi)))
(if (<= (abs (- winkel-natuerlich *vfk-gefaelle-winkel*)) *vfl-gf-winkel-toleranz*)
(list "GF" winkel-natuerlich nil nil)
(vfl-vf-entscheidung deltaL deltaH richtung))
)
)
)
;; ============================================================
;; TEIL 2: SEGMENT-EINFUEGUNG (ohne AS/ES - reine Kettenmitte)
;; ============================================================
;; Reine, kontinuierlich skalierte Gefaelleschraege (kein AS/ES, kein Bogen).
;; punkt: Startpunkt (3D), hz: Horizontalrichtung, deltaL: horizontaler
;; Fussabdruck, winkel: Neigungswinkel (aus vfl-segment-entscheidung).
;; Die Schraeglaenge wird aus deltaL/cos(winkel) abgeleitet, damit der
;; horizontale Fussabdruck exakt deltaL entspricht (deltaH = deltaL*tan(winkel)
;; ergibt sich damit konsistent). Rueckgabe: neuer Frame am Segmentende.
(defun vfl-insert-gf-segment (punkt hz deltaL winkel / l-schraeg endpunkt)
(setq l-schraeg (/ deltaL (cos (* (float winkel) (/ pi 180.0)))))
(setq endpunkt
(gf-insert-hz-incl-scaled "Staustrecke_SP_1000_mm" punkt l-schraeg hz winkel))
(make-frame-from-dir endpunkt (hz-winkel->xu hz winkel))
)
;; Ansicht auf Grundriss (Draufsicht, Welt-UCS) zoomen: nach jedem
;; Kettenglied mit Geometrieerzeugung wird die neu entstandene Strecke sonst
;; leicht aus dem sichtbaren Bereich oder in eine schraege 3D-Ansicht
;; hinauslaufen - der naechste Punkt (vfl-neue-linie-messen, siehe unten)
;; waere dann schwer/ungenau anzuwaehlen. _PLAN "_World" aendert nur die
;; ANSICHT, nicht das aktuell aktive BKS. Rein kosmetisch (per
;; vl-catch-all-apply abgesichert) - ein Fehler hier darf den eigentlichen
;; Kettenbau nie unterbrechen.
(defun vfl-view-refresh ( / )
;; Headless nur Kosten: die Ansicht sieht niemand, _PLAN/_ZOOM laufen aber
;; vor JEDER Laengeneingabe (vfl-neue-linie-messen) - im Batch reine Zeit.
(if (not (vfl-headless-p))
(vl-catch-all-apply
(function (lambda ()
(command "_.PLAN" "_World")
(command "_.ZOOM" "_Extents")))
nil))
(princ))
;; --- Hilfsfunktionen fuer die VF-Einheit ---
;; Planare (XY-)Distanz und Richtung zwischen zwei Punkten.
(defun vfl-planar-dist (p1 p2)
(sqrt (+ (expt (- (car p2) (car p1)) 2) (expt (- (cadr p2) (cadr p1)) 2))))
(defun vfl-planar-hz (p1 p2)
(* (atan (- (cadr p2) (cadr p1)) (- (car p2) (car p1))) (/ 180.0 pi)))
;; Neue Linie ausmessen: Laenge (deltaL) und Fahrtrichtung (hz) bestimmen.
;; Ist hz-vorgabe gesetzt (Fahrtrichtung durch das vorherige Element - z.B.
;; einen GF-Bogen oder eine Vario-Kurve - bereits festgelegt), wird der
;; gewaehlte Punkt auf diese Richtung PROJIZIERT: die Linie folgt exakt der
;; Fahrtrichtung, der Nutzer gibt praktisch nur die Laenge vor (eine gerade
;; Foerderstrecke kann die Richtung nicht aendern). Nur beim allerersten
;; Segment (hz-vorgabe=nil) definiert der gewaehlte Punkt die Richtung frei.
;; Punkt-Abfrage mit FESTER 2-Arity (Basispunkt + Prompt). Verhaltensneutraler
;; Wrapper um das variadische Built-in getpoint: mit Basispunkt (base) wird die
;; 2-Argument-Form genutzt (Gummiband), ohne (base=nil) die reine Prompt-Form.
;; Zweck: ein Testmock kann diese feste Signatur ersetzen (getpoint selbst kann
;; als defun nicht 1- UND 2-argumentig gemockt werden).
;; DYNMODE waehrend des Picks auf 3 (Pointer- + Dimensions-Input) setzen, damit
;; BricsCAD Distanz/Winkel dynamisch am Cursor anzeigt (Laengen-Feedback beim
;; Picken) - danach den Nutzer-Wert wiederherstellen.
(defun vfl-getpoint (base prompt / old-dynmode pt)
(setq old-dynmode (vl-catch-all-apply 'getvar (list "DYNMODE")))
(if (vl-catch-all-error-p old-dynmode) (setq old-dynmode nil))
(if old-dynmode (vl-catch-all-apply 'setvar (list "DYNMODE" 3)))
(setq pt (if base (getpoint base prompt) (getpoint prompt)))
(if old-dynmode (vl-catch-all-apply 'setvar (list "DYNMODE" old-dynmode)))
pt
)
;; Vorschlagswert fuer die GF-Zielhoehe: die aktuelle Kettenhoehe (ist-hoehe)
;; UNVERAENDERT als Default anzubieten fuehrt in eine Falle - ein GF-Segment
;; MUSS fallen (0 Grad Gefaelle gibt es nicht), ein direkt uebernommener
;; Vorschlag mit deltaH=0 wird also immer als "kann nicht steigen" abgelehnt.
;; Deshalb hier auf volle mm ABGERUNDET (truncate, nicht rtos-Rundung - die
;; koennte sonst aufrunden und ueber die Ist-Hoehe hinausgehen) und bei
;; bereits ganzzahligen Werten zusaetzlich 1mm abgezogen, damit der Vorschlag
;; immer echt unterhalb der Ist-Hoehe liegt und direkt uebernehmbar ist.
(defun vfl-gf-hoehe-vorschlag (ist-hoehe / gekappt)
(setq gekappt (float (fix ist-hoehe)))
(if (>= gekappt ist-hoehe) (- gekappt 1.0) gekappt))
;; Punkt+Prompt-Eingabe fuer eine neue Linie journalfrei in (deltaL . hz)
;; uebersetzen: LIVE wird ganz normal ein Punkt gepickt (Gummiband), aber
;; NICHT der Punkt selbst journalisiert, sondern nur die daraus berechnete
;; Distanz (immer) und - NUR wenn hz-vorgabe nil war (freie Richtung, gilt
;; ausschliesslich fuer das allererste Segment der Kette) - zusaetzlich die
;; gesnappte Richtung. Ist hz-vorgabe bereits gesetzt (jedes Nicht-Start-
;; Segment), journalisiert diese Funktion NUR deltaL - die Richtung ist ja
;; ohnehin immer die des Vorgaengers, ein Journal-Eintrag dafuer waere
;; redundant. Rueckgabe: (deltaL . hz) oder nil bei Abbruch/leerer Eingabe.
(defun vfl-in-abstand (p-akt hz-vorgabe prompt / p2 rad ux uy deltaL hz-aktuell popped)
;; *vfl-letzter-pick-z* IMMER zuerst zuruecksetzen (auch im Replay-Zweig,
;; siehe unten) - ein Wert aus einem fruehreren Live-Pick derselben Sitzung
;; darf niemals in einen Replay-/Headless-Nachbau durchsickern.
(setq *vfl-letzter-pick-z* nil)
;; Zentraler Abbruch-Trigger (siehe Kommentar in vfl-in-value): vfl-in-abstand
;; hat eine EIGENE Pending-/Replay-Logik und laeuft NICHT ueber vfl-in-value/
;; -value-p - ohne diesen Check wuerde ein per Wizard-Dialog gesetztes
;; *vfl-wizard-abbruch-grund* hier NICHT erkannt: die Pending-Queue ist nach
;; einem Dialog-Abbruch leer, der Code faellt dann auf einen rohen
;; (vfl-getpoint ...)-Aufruf zurueck statt zu terminieren - genau das
;; Fehlerbild "Abbrechen springt zurueck in die Abfrage" statt zu beenden.
(if *vfl-wizard-abbruch-grund*
(progn
(dbgmsg (strcat "ABBRUCH-TRIGGER: *vfl-wizard-abbruch-grund*=" *vfl-wizard-abbruch-grund* " -> (exit)"))
(exit)))
;; Eigene Replay-Logik (zwei Eintraege pro Aufruf), darum ein eigener
;; Headless-Riegel: leere Queue = Desync, nicht "Nutzer soll picken".
(if (and (null *vfl-replay-queue*) (vfl-headless-p))
(vfl-headless-abbruch "DL"))
(if *vfl-replay-queue*
(progn
;; Replay: IMMER nur deltaL aus der Queue lesen (nie einen Punkt). Bei
;; freier Richtung (hz-vorgabe nil) steht direkt DANACH noch die
;; gesnappte Richtung als zweiter Eintrag im Journal (siehe
;; Live-Zweig unten) - Reihenfolge: deltaL, dann optional hz.
(setq popped (vfl-replay-pop))
(if (null popped)
(setq deltaL nil)
(setq deltaL (car popped)))
(if (and deltaL (null hz-vorgabe))
(progn
(setq popped (vfl-replay-pop))
;; Queue endet zwischen DL und Richtung: headless ein Desync, nicht
;; stillschweigend 0 Grad (das wuerde die ganze Kette verdrehen).
(if (and (null popped) (vfl-headless-p))
(vfl-headless-abbruch "REAL-hz"))
(setq hz-aktuell (if popped (car popped) 0.0)))
(setq hz-aktuell hz-vorgabe)))
(progn
;; Live/Wizard: Pending-Queue zuerst pruefen (Konsistenz mit den
;; anderen vfl-in-*-Wrappern/Sicherheitsnetz, siehe vfl-wizard-aktiv-
;; Kommentar oben) - aktuell legt kein Gruppen-Dialog hier einen Punkt
;; ab, dieser Zweig bleibt also praktisch immer leer. Sonst normal
;; picken (Gummiband). Der Punkt lebt nur hier lokal, er wird nie
;; journalisiert.
(setq popped (vflw-pending-pop))
(setq p2 (if popped (car popped) (vfl-getpoint p-akt prompt)))
;; *vfl-letzter-pick-z*: die Z-Hoehe DIESES Picks, fuer eine unmittelbar
;; nachfolgende Zielhoehe-Frage (siehe vfl-hoehe-endpunkt-abfragen) als
;; Vorschlag verfuegbar machen - vorher bei JEDEM Endpunkt-Pick
;; ersatzlos verworfen (nur X/Y gingen in deltaL ein), obwohl gerade ein
;; Snap auf einen vorhandenen 3D-Block (Objektfang) die vom Nutzer
;; gemeinte Zielhoehe meist schon exakt liefert. Nur uebernehmen, wenn
;; die Z-Koordinate spuerbar von der aktuellen Konstruktionsebene
;; (ELEVATION) abweicht - sonst ist es nur ein flacher Freihand-Klick
;; ohne echte 3D-Information, kein echter Objektfang. Analog zu
;; *vfl-stufe2-minl-vorschlag* ein transientes globales Vorschlags-Feld,
;; nicht Teil des Rueckgabewerts (der bleibt unveraendert deltaL/hz);
;; oben bereits auf nil zurueckgesetzt.
(if (and p2 (> (abs (- (caddr p2) (getvar "ELEVATION"))) 0.01))
(setq *vfl-letzter-pick-z* (caddr p2)))
(if (null p2)
(setq deltaL nil)
(progn
(setq hz-aktuell (if hz-vorgabe hz-vorgabe (vfl-hz-snappen-absolut (vfl-planar-hz p-akt p2))))
(setq rad (* (float hz-aktuell) (/ pi 180.0)) ux (cos rad) uy (sin rad))
(setq deltaL (+ (* (- (car p2) (car p-akt)) ux) (* (- (cadr p2) (cadr p-akt)) uy)))))))
;; Journalisieren fuer BEIDE Zweige (Replay UND Live) - wie alle anderen
;; vfl-in-*-Wrapper (siehe vfl-in-point/-real/-int): ein beim Replay aus der
;; alten Queue gelesener Wert MUSS ins neue *vfl-journal* uebernommen werden,
;; sonst fehlt er beim erneuten Speichern (vfl-journal-xdata-schreiben nach
;; einem Edit) - genau das fuehrte beim ZWEITEN Edit einer Kette zum
;; deltaL-Desync (die replizierten DL/Richtungs-Eintraege waren im nach dem
;; ersten Edit gespeicherten Journal verschwunden). Reihenfolge identisch zur
;; Lese-Reihenfolge oben: erst DL, dann - nur beim ersten Segment
;; (hz-vorgabe nil) - die gesnappte Richtung als REAL.
(vfl-journal-record (if deltaL "DL" "NIL") deltaL)
(if (and deltaL (null hz-vorgabe))
(vfl-journal-record "REAL" hz-aktuell))
(if deltaL (cons deltaL hz-aktuell) nil))
;; Rueckgabe: (deltaL hz) oder nil bei Abbruch. Foerderer-Maximallaenge 25 m:
;; bei Ueberschreitung wird die Eingabe abgelehnt und der Endpunkt erneut
;; abgefragt.
;;
;; WICHTIG (siehe vfl-in-abstand): journalisiert wird NUR deltaL (+ ggf. die
;; gesnappte Richtung beim allerersten Segment), niemals ein absoluter Punkt.
;; deltaL ist eine reine Distanz relativ zum AKTUELLEN Frame (p-akt) entlang
;; der (ggf. vom Vorgaenger geerbten) Fahrtrichtung - eine absolute
;; Weltkoordinate wuerde dagegen nur zur URSPRUENGLICHEN Frame-Position
;; passen. Wird nach einem Einzelsegment-Edit (vfl-edit-glied, z.B. GF-Bogen
;; Seite/Winkel aendern) die Kette AB einem anderen Frame neu verkettet, ist
;; eine gespeicherte Distanz+Richtung sofort und ohne jede Umrechnung wieder
;; korrekt - ein gespeicherter absoluter Punkt waere es nicht (siehe Bug:
;; Neuprojektion des alten Punkts gegen den neuen Frame ergab eine falsche,
;; teils negative Laenge und in der Folge einen Queue-Desync-Absturz).
;; Einzige Ausnahme im ganzen Modus 1: der Kettenstartpunkt (vf-linienzug-
;; modus, vfl-in-point auf den allerersten Klick) - er bleibt ein echter
;; absoluter Punkt, weil er den einzigen Anker der ganzen Kette markiert und
;; von keiner Bogen-Aenderung betroffen sein kann.
;; Reine Richtungswahl OHNE Laenge (journalisiert nur REAL, kein DL-Token) -
;; fuer Faelle, in denen am Kettenanfang eine Richtung noetig ist, aber (noch)
;; keine echte Segmentlaenge (z.B. Zielhoehe-zuerst-Zweige "Linie-GF", wo die
;; Laenge erst spaeter aus Hoehe/Winkel berechnet wird).
(defun vfl-in-richtung (p-akt prompt)
(vfl-in-value "REAL"
(function (lambda ( / p2)
(setq p2 (vfl-getpoint p-akt prompt))
(if p2 (vfl-hz-snappen-absolut (vfl-planar-hz p-akt p2)) nil)))))
(defun vfl-neue-linie-messen (p-akt hz-vorgabe / erg deltaL hz-aktuell ergebnis fertig)
(vfl-view-refresh)
(setq fertig nil)
(while (not fertig)
(setq erg (vfl-in-abstand p-akt hz-vorgabe
(if hz-vorgabe
(ssg-text "vfl-prompt-endpunkt-fahrtrichtung")
(ssg-text "vfl-prompt-endpunkt-frei"))))
(if (null erg)
(setq ergebnis nil fertig t)
(progn
(setq deltaL (car erg) hz-aktuell (cdr erg))
(if (> deltaL *vfk-segment-maximallaenge*)
(princ (ssg-textf "vfl-fehler-laenge-max" (list (rtos (/ deltaL 1000.0) 2 2))))
(progn
(setq ergebnis (list deltaL hz-aktuell))
(setq fertig t)
)
)
)
)
)
ergebnis
)
;; Planare Projektionsdistanz von neu-basis zu ziel-punkt entlang der
;; Fahrtrichtung hz (Grad) - wird genutzt, um nach dem AS-Element-Insert die
;; tatsaechlich noetige Restlaenge (vom echten KS_AUS zum urspruenglich
;; gepickten Punkt) direkt zu berechnen. Bewusst KEINE Fussabdruck-Schaetzung
;; (weder aus KS_EIN->KS_AUS noch aus Block-Ursprung->KS_AUS): beide ignorieren
;; die Dreh­teller-Rotation, die insert-block-mixed-to-ks beim echten Einfuegen
;; anwendet, und liefern empirisch bestaetigt einen falschen Wert (~210mm
;; Schaetzung vs. ~420mm tatsaechlich noetiger Versatz bei AS_Element_30).
(defun vfl-projiziere-distanz (neu-basis ziel-punkt hz / rad)
(setq rad (* (float hz) (/ pi 180.0)))
(+ (* (- (car ziel-punkt) (car neu-basis)) (cos rad))
(* (- (cadr ziel-punkt) (cadr neu-basis)) (sin rad))))
;; Fahrtrichtung + Vorlauflaenge FUER EIN DREHENDES AS-/ES-ELEMENT so
;; korrigieren, dass dessen KS_AUS exakt den absoluten Zielpunkt trifft - real
;; gemessener Bug: die bisherige Reservierung zog nur den Laengsanteil
;; (ein-dx/aus-dx, hz-unabhaengig IMMER exakt der Laengsanteil des rohen
;; Block-Fussabdrucks) vom Budget ab, nie den Queranteil (ein-dy/aus-dy), der
;; bei jedem Winkel <>0 Grad entsteht (0 bei 0 Grad, maximal bei 90 Grad) -
;; die Kette landete dadurch seitlich exakt um raw-dy versetzt neben dem Ziel
;; (BricsCAD-bestaetigt: Modus 1 UND Modus 2, ES_Element_90, ca. 638mm Versatz,
;; Hoehe und Laengsrichtung dabei beide exakt getroffen).
;; Herleitung: Sei D = ziel - start (2D), raw-dx/raw-dy der ROHE, ungedrehte
;; Fussabdruck (vf-element-masse, KS_AUS-KS_EIN bei Rotation 0). Bei
;; Einfuegewinkel hz gilt IMMER (Rotationsinvariante: die Projektion eines
;; gedrehten Vektors auf seine eigene Drehachse aendert sich durch die Drehung
;; nicht): Laengsanteil (entlang hz) = raw-dx, Queranteil (quer zu hz) =
;; raw-dy - unabhaengig vom konkreten hz. Mit L = Vorlauflaenge + raw-dx
;; (effektive Gesamt-Laengsstrecke inkl. Element-Vorschub) folgt daraus ein
;; rechtwinkliges Dreieck: |D|^2 = L^2 + raw-dy^2 (D, L-Anteil und raw-dy-
;; Anteil bilden ein orthonormales Paar), also L = sqrt(|D|^2 - raw-dy^2), und
;; die gesuchte Richtung hz' = Winkel von D MINUS dem Winkel, den raw-dy quer
;; dazu beitraegt (atan2(raw-dy, L)).
;; Rueckgabe: (hz-korrigiert L) - L ist die korrigierte RAW-Distanz zum
;; Zielpunkt entlang hz-korrigiert (Laengsanteil von D), NICHT bereits um
;; raw-dx reduziert - genau wie das bisherige deltaL (vfl-neue-linie-messen/
;; vfl-projiziere-distanz) wird sie unveraendert an die bestehenden Solver
;; (vfl-body-zerlegung/vfl-vf-entscheidung -> berechne-alle-winkel) weiter-
;; gereicht, die raw-dx (ein-dx/aus-dx) SELBST intern abziehen (siehe deren
;; A-Formel) - hier zusaetzlich abzuziehen waere eine Doppel-Reservierung.
;; nil, wenn |D| < |raw-dy| (Ziel liegt naeher als der reine Seitenversatz -
;; geometrisch unerreichbar, unabhaengig von jeder Winkelwahl).
(defun vfl-richtung-mit-drehendem-fussabdruck (start ziel raw-dx raw-dy /
dx dy dsq l hz-ziel hz-korr)
(setq dx (- (car ziel) (car start)) dy (- (cadr ziel) (cadr start)))
(setq dsq (- (+ (* dx dx) (* dy dy)) (* raw-dy raw-dy)))
(if (< dsq 0.0)
nil
(progn
(setq l (sqrt dsq))
(setq hz-ziel (* (atan dy dx) (/ 180.0 pi)))
(setq hz-korr (- hz-ziel (* (atan raw-dy l) (/ 180.0 pi))))
(list hz-korr l)
)
)
)
;; Rahmen am Ende eines vfs-*-Bausteins: die Bausteine (Entry/Koerper/Exit)
;; enden IMMER auf der 3-Grad-Basisneigung (siehe Prinzipien-Dok Abschnitt 6).
(defun vfl-frame-3grad (punkt hz)
(make-frame-from-dir punkt (hz-winkel->xu hz (ssg-cfg-or "vario" "gefaelle_winkel" 3))))
;; 20-Meter-Regel: warnt, wenn die VF-Segmente seit der Umlenkstation 20 m
;; ueberschreiten (Prinzipien-Dok Abschnitt 7). v1: nur Hinweis, kein
;; automatisches Einfuegen einer Zwischen-Motorstation.
(defun vfl-20m-check (p-umlenk p-akt / laenge)
(setq laenge (vfl-planar-dist p-umlenk p-akt))
(if (> laenge *vfk-motorabstand-warnung*)
(princ (ssg-textf "vfl-20m-hinweis" (list (rtos (/ laenge 1000.0) 2 2))))
)
)
;; Baut EINE reine VarioFoerderer-Einheit als interaktive Sub-Kette:
;; genau EINE Umlenkstation (Eingang) ... beliebig viele Koerper-Sub-Segmente
;; und Vario-Kurven ... genau EINE Motorstation (Ausgang). Siehe Prinzipien-Dok
;; Abschnitt 2+4. Jedes Koerper-Sub-Segment beginnt/endet auf 3-Grad-Neigung.
;; frame: Eingangsrahmen (KS_AUS des Vorgaenger-Elements, i.d.R. AS-Element).
;; hz1/richtung1/winkel1/L_GF1/L_VF1: Daten des ersten (bereits klassifizierten)
;; VF-Linien-Sub-Segments aus vfl-segment-entscheidung.
;; gf-am-ausgang: T => halbe Staustrecke als GF2 am Ausgang (ohne Separator),
;; nil => gesamte Staustrecke am Einlauf (GF1), kein GF2.
;; Neigung des Frames aus der xu-Richtung ablesen: T => (nahezu) flach (0 Grad),
;; nil => auf 3-Grad-Basis. Damit wird der Uebergang auf_3/ab_3 nur dann gesetzt,
;; wenn wirklich ein Neigungswechsel noetig ist.
(defun vfl-frame-flach-p (frame)
(< (abs (cadr (frame->hz-winkel frame))) *vfk-frame-flach-toleranz*))
;; Separator (300 mm) HORIZONTAL (0 Grad) an einen Punkt anfuegen - fuer das
;; horizontale Stueck, wo der Separator in der 0-Grad-Ebene liegt (NICHT auf der
;; 3-Grad-Basis wie vfl-insert-separator). Rueckgabe: Endpunkt.
(defun vfl-sep-hz (pt hz / ep)
(princ (ssg-text "vfl-sep-horizontal-info"))
(setq ep (gf-insert-hz-with-ks "Staustrecke_Separator_SP_300_mm" pt hz 0 *vfk-separator-laenge* 0))
(ssg-sep-typ-xdata-schreiben (entlast) "VF")
(setq *vfl-acc-separator* (1+ *vfl-acc-separator*))
ep)
;; Uebergang zurueck auf die 3-Grad-Basis, FALLS der Frame gerade flach (0 Grad)
;; ist: fuegt einen Vario_Bogen_ab_3 ein. Wird vor jedem 3-Grad-Element
;; (gewinkeltes VF, Motorstation, ES) aufgerufen, damit der ab_3-Uebergang erst
;; DANN kommt, wenn er wirklich gebraucht wird (die flache Zone bleibt sonst
;; flach). Ist der Frame schon auf 3-Grad-Basis, bleibt er unveraendert.
(defun vfl-nach-3grad (frame / hz m pt)
(if (vfl-frame-flach-p frame)
(progn
(setq hz (car (frame->hz-winkel frame)))
(setq m (get-bogen-mass bogen-ab 3))
(princ (ssg-text "vfl-bogen-ab3-uebergang"))
(setq pt (insert-rotated-block-with-ks "Vario_Bogen_ab_3_TEF_rechts" (car frame)
0 (car m) (caddr m) hz))
(vfl-frame-3grad pt hz))
frame))
;; Genereller Uebergang zwischen zwei Basis-Neigungen (0/+3/-3 Grad,
;; Vorzeichenkonvention wie frame->hz-winkel: positiv=fallend, negativ=
;; steigend) MIT DER VF-BIBLIOTHEK (Vario_Bogen_*_TEF_rechts, init-
;; bibliothek) - fuer Uebergaenge VOR/IN einer VF-Baugruppe (vfs-vf-koerper/
;; Motorstation), wo der Bogen ohnehin als VF-Familie zaehlt. Reine GF-
;; Kontexte (Linie-GF) nutzen stattdessen vfl-gf-bogen-uebergang (GF-eigene
;; Bloecke, siehe dort) - aktuell KEIN Aufrufer mehr hier im Code, aber als
;; Baustein fuer die dokumentierte Luecke in vfl-body-abschluss-bauen
;; (steigender Frame-Eintritt vor einem VF-Koerper) bewusst nicht entfernt.
;; Die VF-Bibliothek kennt nur einen 3-Grad-Bogen gegenueber FLACH (0 Grad) -
;; ein Wechsel zwischen der fallenden (+3) und der steigenden (-3) Basis
;; braucht daher IMMER zwei Boegen ueber 0 als Zwischenhalt. "auf"-Boegen
;; senken die Neigung numerisch (Richtung steigend), "ab"-Boegen erhoehen sie
;; (Richtung fallend) - siehe vfl-nach-3grad/vfl-hor-koerper-bauen, deren
;; Uebergaenge hier verallgemeinert werden. Ist kein Uebergang noetig (Start-
;; /Zielbasis identisch), no-op.
;; Rueckgabe: (neuer-frame fussabdruck-summe) - fussabdruck-summe (mm, auf hz
;; projiziert) muss der Aufrufer von der geplanten Restlaenge/Zielstrecke
;; abziehen.
(defun vfl-bogen-uebergang (frame ziel-winkel / t0 basis-ziel hz pt m summe vor-pt)
(setq hz (car (frame->hz-winkel frame)))
(setq t0 (cadr (frame->hz-winkel frame)))
(setq t0 (cond ((< (abs t0) *vfk-frame-flach-toleranz*) 0)
((< t0 0) (- (fix *vfk-gefaelle-winkel*)))
(t (fix *vfk-gefaelle-winkel*))))
(setq basis-ziel (cond ((< (abs ziel-winkel) *vfl-gf-winkel-toleranz*) 0)
((< ziel-winkel 0) (- (fix *vfk-gefaelle-winkel*)))
(t (fix *vfk-gefaelle-winkel*))))
(setq summe 0.0)
(if (/= t0 basis-ziel)
(progn
;; Schritt 1: t0 -> 0 (falls t0 noch nicht flach ist).
(if (/= t0 0)
(progn
(setq vor-pt (car frame))
(if (> t0 0)
(progn
(setq m (get-bogen-mass bogen-auf (fix *vfk-gefaelle-winkel*)))
(princ (ssg-text "vfl-bogen-auf3-uebergang"))
(setq pt (insert-rotated-block-with-ks "Vario_Bogen_auf_3_TEF_rechts" vor-pt
(fix *vfk-gefaelle-winkel*) (car m) (caddr m) hz)))
(progn
(setq m (get-bogen-mass bogen-ab (fix *vfk-gefaelle-winkel*)))
(princ (ssg-text "vfl-bogen-ab3-uebergang"))
(setq pt (insert-rotated-block-with-ks "Vario_Bogen_ab_3_TEF_rechts" vor-pt
(- (fix *vfk-gefaelle-winkel*)) (car m) (caddr m) hz)))
)
(setq summe (+ summe (vfl-projiziere-distanz vor-pt pt hz)))
(setq frame (make-frame-from-dir pt (hz-winkel->xu hz 0)))
)
)
;; Schritt 2: 0 -> basis-ziel (falls das Ziel nicht flach ist).
(if (/= basis-ziel 0)
(progn
(setq vor-pt (car frame))
(if (> basis-ziel 0)
(progn
(setq m (get-bogen-mass bogen-ab (fix *vfk-gefaelle-winkel*)))
(princ (ssg-text "vfl-bogen-ab3-uebergang"))
(setq pt (insert-rotated-block-with-ks "Vario_Bogen_ab_3_TEF_rechts" vor-pt
0 (car m) (caddr m) hz)))
(progn
(setq m (get-bogen-mass bogen-auf (fix *vfk-gefaelle-winkel*)))
(princ (ssg-text "vfl-bogen-auf3-uebergang"))
(setq pt (insert-rotated-block-with-ks "Vario_Bogen_auf_3_TEF_rechts" vor-pt
0 (car m) (caddr m) hz)))
)
(setq summe (+ summe (vfl-projiziere-distanz vor-pt pt hz)))
(setq frame (make-frame-from-dir pt (hz-winkel->xu hz basis-ziel)))
)
)
)
)
(list frame summe)
)
;; ------------------------------------------------------------------
;; GF-eigene Vertikalboegen (Nutzer-Vorgabe): 8 dedizierte Bloecke in
;; data/ils - Vario_Bogen_ab/auf_3/6_rechts (OHNE "_TEF_", das Suffix der
;; VF-Bibliothek). Zweck: reine GF-Uebergaenge (Linie-GF: Einstieg aus der
;; aktuellen Kettenneigung, Ausstieg vor ES/AS) sollen NICHT laenger die
;; VF-Bogen-Bibliothek (bogen-ab/bogen-auf, init-bibliothek) mitbenutzen -
;; die Bloecke tragen dadurch ihre eigene GF-Familie in vfl-kette-typ statt
;; als VF ("BOGENAUF"/"BOGENAB") erkannt zu werden, und ein KS_AUS-Scan am
;; Kettenanfang klassifiziert eine Kette, die mit einem dieser Boegen endet,
;; weiterhin korrekt als GF. Eigener Satz Globals/Init, damit KEINE
;; Abhaengigkeit zu vf_standard.lsp/init-bibliothek entsteht (ensure-block-
;; loaded/vf-element-masse sind bereits generische ssg_ks_insert.lsp/
;; vf_core.lsp-Helfer, keine VF-spezifischen).
(setq *gf-bogen-auf* nil *gf-bogen-ab* nil *gf-bogen-lib-initialized* nil)
(defun vfl-gf-bogen-init ( / bname m)
(if (not *gf-bogen-lib-initialized*)
(progn
(setq *gf-bogen-auf* nil *gf-bogen-ab* nil)
(foreach w '(3 6)
(setq bname (strcat "Vario_Bogen_auf_" (itoa w) "_rechts"))
(if (tblsearch "BLOCK" (ensure-block-loaded bname))
(progn
(setq m (vf-element-masse bname))
(if m (setq *gf-bogen-auf* (cons (cons w m) *gf-bogen-auf*))
(princ (ssg-textf "vfl-gf-bogen-ks-fehlt" (list bname)))))
(princ (ssg-textf "vfl-gf-bogen-block-fehlt" (list bname))))
(setq bname (strcat "Vario_Bogen_ab_" (itoa w) "_rechts"))
(if (tblsearch "BLOCK" (ensure-block-loaded bname))
(progn
(setq m (vf-element-masse bname))
(if m (setq *gf-bogen-ab* (cons (cons w m) *gf-bogen-ab*))
(princ (ssg-textf "vfl-gf-bogen-ks-fehlt" (list bname)))))
(princ (ssg-textf "vfl-gf-bogen-block-fehlt" (list bname))))
)
(setq *gf-bogen-auf* (reverse *gf-bogen-auf*) *gf-bogen-ab* (reverse *gf-bogen-ab*))
(setq *gf-bogen-lib-initialized* t)
)
)
)
;; Wie vfl-bogen-uebergang, aber fuer reine GF-Kontexte (Linie-GF: Einstieg
;; aus der aktuellen Kettenneigung VOR dem GF-Segment, Ausstieg VOR ES/AS) -
;; nutzt die GF-eigenen Vertikalboegen (siehe oben) statt der VF-Bibliothek.
;; WICHTIG: der 6-Grad-Sprung (steigend<->fallend) braucht dadurch NUR EINEN
;; dedizierten 6-Grad-Bogen statt zwei 3-Grad-Boegen ueber 0 als Zwischenhalt
;; (vfl-bogen-uebergang) - kuerzer und geometrisch sauberer, sobald die
;; passenden 6-Grad-Bloecke vorhanden sind (Nutzer-Vorgabe 2026-09-12).
;; Rueckgabe: (neuer-frame fussabdruck-summe), wie vfl-bogen-uebergang.
(defun vfl-gf-bogen-uebergang (frame ziel-winkel / t0 basis-ziel hz pt m summe)
(vfl-gf-bogen-init)
(setq hz (car (frame->hz-winkel frame)))
(setq t0 (cadr (frame->hz-winkel frame)))
(setq t0 (cond ((< (abs t0) *vfk-frame-flach-toleranz*) 0)
((< t0 0) (- (fix *vfk-gefaelle-winkel*)))
(t (fix *vfk-gefaelle-winkel*))))
(setq basis-ziel (cond ((< (abs ziel-winkel) *vfl-gf-winkel-toleranz*) 0)
((< ziel-winkel 0) (- (fix *vfk-gefaelle-winkel*)))
(t (fix *vfk-gefaelle-winkel*))))
(setq summe 0.0)
(cond
((= t0 basis-ziel) (list frame summe))
;; 6 Grad in einem Zug: steigend<->fallend, EIN dedizierter Bogen.
((and (/= t0 0) (/= basis-ziel 0))
(if (> t0 0)
(progn
(setq m (get-bogen-mass *gf-bogen-auf* 6))
(princ (ssg-text "vfl-gf-bogen-auf6-uebergang"))
(setq pt (insert-rotated-block-with-ks "Vario_Bogen_auf_6_rechts" (car frame)
(fix *vfk-gefaelle-winkel*) (car m) (caddr m) hz)))
(progn
(setq m (get-bogen-mass *gf-bogen-ab* 6))
(princ (ssg-text "vfl-gf-bogen-ab6-uebergang"))
(setq pt (insert-rotated-block-with-ks "Vario_Bogen_ab_6_rechts" (car frame)
(- (fix *vfk-gefaelle-winkel*)) (car m) (caddr m) hz)))
)
(setq summe (vfl-projiziere-distanz (car frame) pt hz))
(list (make-frame-from-dir pt (hz-winkel->xu hz basis-ziel)) summe))
;; 3 Grad: flach -> geneigt oder geneigt -> flach, EIN 3-Grad-Bogen.
(t
(if (= t0 0)
(if (> basis-ziel 0)
(progn
(setq m (get-bogen-mass *gf-bogen-ab* 3))
(princ (ssg-text "vfl-gf-bogen-ab3-uebergang"))
(setq pt (insert-rotated-block-with-ks "Vario_Bogen_ab_3_rechts" (car frame)
0 (car m) (caddr m) hz)))
(progn
(setq m (get-bogen-mass *gf-bogen-auf* 3))
(princ (ssg-text "vfl-gf-bogen-auf3-uebergang"))
(setq pt (insert-rotated-block-with-ks "Vario_Bogen_auf_3_rechts" (car frame)
0 (car m) (caddr m) hz))))
(if (> t0 0)
(progn
(setq m (get-bogen-mass *gf-bogen-auf* 3))
(princ (ssg-text "vfl-gf-bogen-auf3-uebergang"))
(setq pt (insert-rotated-block-with-ks "Vario_Bogen_auf_3_rechts" (car frame)
(fix *vfk-gefaelle-winkel*) (car m) (caddr m) hz)))
(progn
(setq m (get-bogen-mass *gf-bogen-ab* 3))
(princ (ssg-text "vfl-gf-bogen-ab3-uebergang"))
(setq pt (insert-rotated-block-with-ks "Vario_Bogen_ab_3_rechts" (car frame)
(- (fix *vfk-gefaelle-winkel*)) (car m) (caddr m) hz))))
)
(setq summe (vfl-projiziere-distanz (car frame) pt hz))
(list (make-frame-from-dir pt (hz-winkel->xu hz basis-ziel)) summe))
)
)
;; ECHTE Neigungs-Fortsetzung UEBER die von vfl-gf-bogen-uebergang
;; unterstuetzten Zustaende (flach/+3/-3) hinaus - z.B. steigend-3 ->
;; steigend-6 (oder mit grad=6 weiter -> steigend-9) und zurueck.
;; vfl-gf-bogen-uebergang selbst kennt NUR Uebergaenge zu/von flach (der
;; 3-Grad-Fall) bzw. zwischen +3/-3 (der kombinierte 6-Grad-Fall) - fuer eine
;; WEITERE Neigungs-Aenderung AUSGEHEND von einem bereits geneigten Zustand
;; (z.B. Option 5's S bei 6 oder 9 statt 3 Grad, Nutzer-Vorgabe 2026-09-13:
;; ECHTE Bausteine statt rein rechnerischem Ruecksprung) wird derselbe rohe
;; insert-rotated-block-with-ks-Aufruf wie dort direkt genutzt, nur mit einer
;; von 0/+3/-3 abweichenden "eingehende-neigung" als Rotationsparameter -
;; "Vario_Bogen_auf_3/6_rechts"/"_ab_3/6_rechts" sind generische Bloecke mit
;; einem FIXEN eigenen Bogen (grad=3 oder 6), ihre Projektion funktioniert
;; fuer JEDEN eingehenden Rotationswinkel, nicht nur die drei von
;; vfl-gf-bogen-uebergang klassifizierten Zustaende.
;; richtung="auf": eingehende-neigung -> eingehende-neigung-grad (steiler
;; steigend bzw. weniger fallend). richtung="ab": eingehende-neigung ->
;; eingehende-neigung+grad (steiler fallend bzw. weniger steigend).
(defun vfl-gf-bogen-weiter (frame richtung eingehende-neigung grad / m blockname pt hz exit-neigung)
(vfl-gf-bogen-init)
(setq hz (car (frame->hz-winkel frame)))
(if (= richtung "auf")
(progn
(setq m (get-bogen-mass *gf-bogen-auf* grad))
(setq blockname (strcat "Vario_Bogen_auf_" (itoa grad) "_rechts"))
(setq exit-neigung (- eingehende-neigung grad)))
(progn
(setq m (get-bogen-mass *gf-bogen-ab* grad))
(setq blockname (strcat "Vario_Bogen_ab_" (itoa grad) "_rechts"))
(setq exit-neigung (+ eingehende-neigung grad)))
)
(setq pt (insert-rotated-block-with-ks blockname (car frame) eingehende-neigung (car m) (caddr m) hz))
(make-frame-from-dir pt (hz-winkel->xu hz exit-neigung))
)
;; Verkettet mehrere vfl-gf-bogen-weiter-Schritte, um eine BELIEBIGE
;; Neigungs-Erweiterung (Vielfaches von 3 Grad) mit MOEGLICHST WENIGEN
;; Bausteinen zu erreichen (moeglichst viele 6-Grad-Bloecke, ein 3-Grad-Block
;; fuer den Rest) - Option 5's dynamische Winkelwahl (Nutzer-Vorgabe
;; 2026-09-13: "gegebene Laenge -> passenden Winkel waehlen" statt einem
;; einzigen fest verdrahteten Winkel). extra-grad=0 (S bei nur 3 Grad, der
;; symmetrische Grundfall) baut nichts und gibt frame unveraendert zurueck.
;; richtung wie vfl-gf-bogen-weiter ("auf"/"ab"); eingehende-neigung ist der
;; Startwert VOR der Erweiterung (z.B. -3 fuer "auf").
(defun vfl-gf-bogen-erweitern (frame richtung eingehende-neigung extra-grad / rest neigung-jetzt)
(setq rest extra-grad)
(setq neigung-jetzt eingehende-neigung)
(while (>= rest 6)
(setq frame (vfl-gf-bogen-weiter frame richtung neigung-jetzt 6))
(setq neigung-jetzt (if (= richtung "auf") (- neigung-jetzt 6) (+ neigung-jetzt 6)))
(setq rest (- rest 6))
)
(if (= rest 3)
(setq frame (vfl-gf-bogen-weiter frame richtung neigung-jetzt 3))
)
frame
)
;; Projiziert eine rohe (dx,dz)-Bogenmasse (bei Rotation 0 gemessen, siehe
;; get-bogen-mass) in die Welt, wenn der Bogen bei einer gegebenen Neigung
;; (Grad, gefaelle-winkel-Konvention: positiv=fallend) eingebaut wird -
;; dieselbe Rz(hz)*Ry(rot)-Projektion wie insert-rotated-block-with-ks, hier
;; OHNE Insert (reine Vorausberechnung fuer vfl-gf-auf-ab-solve). Rueckgabe:
;; (welt-dx welt-dz).
(defun vfl-projiziere-neigung (dx dz rot-grad / rad)
(setq rad (* (float rot-grad) (/ pi 180.0)))
(list (+ (* dx (cos rad)) (* dz (sin rad)))
(+ (- (* dx (sin rad))) (* dz (cos rad))))
)
;; Rein rechnerisches (KEIN Bau) Pendant zu vfl-gf-bogen-erweitern - fuer
;; Option 4's Formel-basierten Solver (vfl-gf-auf-ab-solve), der die
;; Neigungs-Erweiterung VORAB in die Fixterme einrechnen muss, BEVOR
;; ueberhaupt etwas gebaut wird (Nutzer-Vorgabe 2026-09-14: dieselbe
;; dynamische Winkelwahl wie Option 5, jetzt auch fuer Option 4). Summiert
;; die WELT-(dx,dz) einer Kette aus moeglichst wenigen Bausteinen
;; (moeglichst viele 6-Grad-, ein 3-Grad-Rest-Baustein), exakt in derselben
;; Reihenfolge/Neigungs-Abfolge wie vfl-gf-bogen-erweitern es spaeter ECHT
;; baut - extra-grad=0 liefert (0.0 0.0). Rueckgabe: (welt-dx welt-dz).
(defun vfl-gf-bogen-summe (richtung eingehende-neigung extra-grad /
rest neigung-jetzt m proj sum-dx sum-dz)
(setq sum-dx 0.0 sum-dz 0.0 rest extra-grad neigung-jetzt eingehende-neigung)
(while (>= rest 6)
(setq m (get-bogen-mass (if (= richtung "auf") *gf-bogen-auf* *gf-bogen-ab*) 6))
(setq proj (vfl-projiziere-neigung (car m) (caddr m) neigung-jetzt))
(setq sum-dx (+ sum-dx (car proj)) sum-dz (+ sum-dz (cadr proj)))
(setq neigung-jetzt (if (= richtung "auf") (- neigung-jetzt 6) (+ neigung-jetzt 6)))
(setq rest (- rest 6))
)
(if (= rest 3)
(progn
(setq m (get-bogen-mass (if (= richtung "auf") *gf-bogen-auf* *gf-bogen-ab*) 3))
(setq proj (vfl-projiziere-neigung (car m) (caddr m) neigung-jetzt))
(setq sum-dx (+ sum-dx (car proj)))
(setq sum-dz (+ sum-dz (cadr proj)))
)
)
(list sum-dx sum-dz)
)
;; 2D-Rotation eines LOKALEN (dx,dy)-Versatzes (KS_EIN->KS_AUS eines Blocks
;; bei Rotation 0, siehe vf-element-masse) um einen WELT-Richtungswinkel
;; (Grad) - dieselbe Rz(hz)-Rotationsformel wie in insert-block-ks-to-ks/
;; insert-rotated-block-with-ks, hier eigenstaendig fuer eine Vorab-
;; Berechnung OHNE Insert. Rueckgabe: (welt-dx welt-dy).
(defun vfl-rot2d (dx dy hz-grad / rad)
(setq rad (* (float hz-grad) (/ pi 180.0)))
(list (- (* dx (cos rad)) (* dy (sin rad)))
(+ (* dx (sin rad)) (* dy (cos rad)))))
;; 2D-Kreuzprodukt (ax,ay) x (bx,by) = ax*by - ay*bx. Fuer eine Gerade durch
;; P0 mit Richtungsvektor d_ref gilt fuer einen Punkt P: (P-P0) x d_ref = 0
;; genau dann, wenn P auf der Geraden liegt.
(defun vfl-kreuz2d (ax ay bx by) (- (* ax by) (* ay bx)))
;; Weltposition eines benannten KS-Markers ("KS_EIN"/"KS_AUS") eines BEREITS
;; in der Zeichnung platzierten Blocks - wie vfl-kette-ks-aus-richtung, aber
;; fuer einen beliebigen KS-Namen und ohne die Richtung (nur der Punkt wird
;; gebraucht). Ueber eine skalierungsneutrale KOPIE gemessen (das Original
;; bleibt unangetastet in der Zeichnung stehen). Rueckgabe: Weltpunkt oder
;; nil, falls der Marker nicht gefunden wurde.
;; Hintergrund (Nutzer-Befund 2026-09-12): bei AS_Element/ES_Element liegt
;; KS_EIN/KS_AUS aus mechanischen Gruenden (Drehteller-Anschluss) seitlich
;; versetzt vom Block-URSPRUNG - insert-block-mixed-to-ks fluchtet bewusst
;; den Ursprung (nicht KS_EIN) auf den Kettenpunkt (siehe dortiger
;; Kommentar). Der geklickte Startpunkt ist darum NICHT identisch mit
;; KS_EIN - fuer die Kolinearitaets-Referenzgerade in vfl-qc-linie-mit-gf
;; wird das ECHTE, gemessene KS_EIN gebraucht, nicht der rohe Klickpunkt.
(defun vfl-block-ks-punkt (block-obj ks-name / copyobj ksdata p)
(setq copyobj (vla-Copy block-obj))
(vla-put-XScaleFactor copyobj 1.0)
(vla-put-YScaleFactor copyobj 1.0)
(vla-put-ZScaleFactor copyobj 1.0)
(setq ksdata (extract-ks-from-block-raw copyobj))
(if (not (vlax-erased-p copyobj)) (vl-catch-all-apply 'vla-Delete (list copyobj)))
(setq p (if (assoc ks-name ksdata) (car (cadr (assoc ks-name ksdata))) nil))
p
)
;; ------------------------------------------------------------------
;; "QC Linie mit GF" (Option 4 der gf-laenge-modus-Frage, Nutzer-Vorgabe
;; 2026-09-12, Geometrie danach nach BricsCAD-Test komplett ueberarbeitet):
;; automatisch berechnete Auf-Ab-GF-Kette, die auf derselben
;; Hoehe endet, auf der sie beginnt - AS -> fallend-GF -> 30-Grad-Bogen ->
;; [fallend-GF -> Bogen auf (6 Grad, ggf. steiler) -> steigend-GF -> Bogen ab
;; (6 Grad, ggf. steiler) -> fallend-GF] -> 30-Grad-Bogen (Gegenseite) ->
;; fallend-GF -> ES. Die beiden fallenden Mittelstuecke (vor/nach dem
;; Steig-Stueck) werden als EIN gemeinsamer Unbekannter F_total behandelt
;; (Nutzer-Vorgabe, aufgeteilt nach einem waehlbaren Anteil) -
;; vfl-gf-auf-ab-solve loest F_total und das steigende Mittelstueck S aus
;; der vorgegebenen Mittelteil-Gesamtlaenge und der geforderten Netto-
;; Hoehenaenderung (hier immer 0, da AS/ES auf derselben Hoehe liegen
;; sollen). S's Neigung wird dabei DYNAMISCH gewaehlt (Nutzer-Vorgabe
;; 2026-09-14, dieselbe Idee wie Option 5): 3 Grad (symmetrischer
;; Grundfall) zuerst, dann 6/9/12/...Grad, bis F_total UND S beide baubar
;; (>=0) sind - der FLACHSTE passende Winkel gewinnt, damit auch kuerzere
;; Mittelteil-Gesamtlaengen noch baubar sind, statt nur bei einem einzigen
;; fest verdrahteten Winkel zu funktionieren.
;;
;; Weltmass-Formel wie insert-rotated-block-with-ks (ssg_ks_insert.lsp):
;; world_dx = dx*cos(rot) + dz*sin(rot) (horizontal, entlang hz)
;; world_dz = -dx*sin(rot) + dz*cos(rot) (Hoehenaenderung)
;; rot=+gefaelle-winkel fuer den "auf"-Bogen (Einstieg fallend), rot=
;; -gefaelle-winkel fuer den "ab"-Bogen (Einstieg steigend) - siehe
;; vfl-gf-bogen-uebergang, das dieselben Tabellen beim tatsaechlichen Bau
;; verwendet.
;; s-winkel (Nutzer-Vorgabe 2026-09-14: dieselbe dynamische Winkelwahl wie
;; Option 5 - "gegebene Laenge -> passenden Winkel waehlen"): die Neigung
;; des steigenden Mittelstuecks S (Grad, STEIGEND-Betrag - F_total bleibt
;; IMMER bei *vfk-gefaelle-winkel*, nur S wird bei Bedarf steiler). Der
;; Aufrufer probiert typischerweise s-winkel=3 (symmetrischer Grundfall),
;; dann 6, 9, ... bis gueltig=T (siehe vfl-qc-linie-mit-gf).
;; Rueckgabe: (f-total s gueltig ziel-laenge-min) - f-total/s als horizontale
;; Laengen (wie deltaL bei vfl-insert-gf-segment), gueltig=nil bei negativem
;; Ergebnis (Mittelteil-Vorgabe geometrisch nicht baubar). ziel-laenge-min ist
;; in diesem Fall die kleinste Gesamtlaenge, mit der f-total UND s BEIDE >= 0
;; wuerden (Nutzer-Vorgabe 2026-09-13: als Vorschlag in der Fehlermeldung
;; ausgeben) - aus F_total+S=a, -F_total*tan3+S*tan(s-winkel)=b (a,b wie
;; unten): S>=0 braucht a>=-b/tan3, F_total>=0 braucht a>=b/tan(s-winkel) -
;; das striktere (groessere) der beiden Minima entscheidet.
(defun vfl-gf-auf-ab-solve (ziel-laenge-horiz ziel-hoehe-delta s-winkel /
rad3 tan3 rad-s tan-s m-auf m-ab proj-auf proj-ab
extra-grad extra-auf extra-ab
fix-dx fix-dz a b f-total s gueltig
a-min-s a-min-ftotal a-min ziel-laenge-min)
(vfl-gf-bogen-init)
(setq rad3 (* (float (fix *vfk-gefaelle-winkel*)) (/ pi 180.0)))
(setq tan3 (/ (sin rad3) (cos rad3)))
(setq rad-s (* (float s-winkel) (/ pi 180.0)))
(setq tan-s (/ (sin rad-s) (cos rad-s)))
(setq m-auf (get-bogen-mass *gf-bogen-auf* 6))
(setq m-ab (get-bogen-mass *gf-bogen-ab* 6))
;; Basis-Uebergaenge (IMMER vorhanden, wie bisher): auf_6 bei Einstieg
;; fallend(+3), ab_6 bei Einstieg steigend(-3).
(setq proj-auf (vfl-projiziere-neigung (car m-auf) (caddr m-auf) (fix *vfk-gefaelle-winkel*)))
(setq proj-ab (vfl-projiziere-neigung (car m-ab) (caddr m-ab) (- (fix *vfk-gefaelle-winkel*))))
;; Zusaetzliche Erweiterung (nur falls s-winkel > *vfk-gefaelle-winkel*):
;; "auf"-Kette von -3 weiter auf -s-winkel, "ab"-Kette von -s-winkel
;; zurueck auf -3 (rein rechnerisch - vfl-gf-bogen-summe, echter Bau folgt
;; per vfl-gf-bogen-erweitern erst NACH dem Solve).
(setq extra-grad (- s-winkel (fix *vfk-gefaelle-winkel*)))
(setq extra-auf (vfl-gf-bogen-summe "auf" (- (fix *vfk-gefaelle-winkel*)) extra-grad))
(setq extra-ab (vfl-gf-bogen-summe "ab" (- s-winkel) extra-grad))
;; Fixterme: zwei Separatoren (300mm, fallend) + alle Bogen-Beitraege.
(setq fix-dx (+ (* 2.0 *vfk-separator-laenge* (cos rad3))
(car proj-auf) (car proj-ab)
(car extra-auf) (car extra-ab)))
(setq fix-dz (+ (* -2.0 *vfk-separator-laenge* (sin rad3))
(cadr proj-auf) (cadr proj-ab)
(cadr extra-auf) (cadr extra-ab)))
(setq a (- ziel-laenge-horiz fix-dx))
(setq b (- ziel-hoehe-delta fix-dz))
;; F_total+S=a, -F_total*tan3+S*tan-s=b => S=(b+a*tan3)/(tan3+tan-s)
(setq s (/ (+ b (* a tan3)) (+ tan3 tan-s)))
(setq f-total (- a s))
(setq gueltig (and (>= f-total -0.01) (>= s -0.01)))
(setq a-min-s (- (/ b tan3)))
(setq a-min-ftotal (/ b tan-s))
(setq a-min (if (> a-min-ftotal a-min-s) a-min-ftotal a-min-s))
(setq ziel-laenge-min (+ fix-dx a-min))
(list f-total s gueltig ziel-laenge-min)
)
;; Ablauf/Bau von Option 4. Nutzt die dynamisch (AutoLISP-typisch) sichtbaren
;; Lokalvariablen des Aufrufers vf-linienzug-modus (frame, p-aktuell,
;; as-vorhanden, as-seite, kettenanfang, fertig, es-seite, anzahl-gf, ...) -
;; wie alle anderen wahl-Zweige dort auch. Setzt am Ende frame/p-aktuell auf
;; den ES-Ausgang und fertig=t; der gemeinsame Rest des Hauptloops (Segment-
;; XDATA, Echo, Block-Erstellung) laeuft danach unveraendert weiter.
(defun vfl-qc-linie-mit-gf ( / as-w as-s theta-ref as-turn es-turn
bogen-turn-30-r bogen-turn-30-l bogen-turn-90-r bogen-turn-90-l
bogen-seite bogen-winkel bogen-turn-echt
b-noetig tol hz0 hz1 hz2 e1 e1-brutto e2 anteil ziel-laenge
loesung f-total s gueltig fa fb hz-jetzt
erg-bogen soll-hoehe ist-hoehe antwort
s-winkel extra-grad rad-s versuche
rad3 tan3
p0x p0y px py vx vy esx esy dref-x dref-y
hz1-rad hz2-rad zaehler nenner abweichung
checkpoint p0-real h-aussen-rest theta-ref-rad
probe-checkpoint probe-frame probe-frame2
bg2x bg2y bg2z esz acc-gfbogen-save acc-sep-save)
(if (not kettenanfang)
(princ (ssg-text "vfl-qc-nur-kettenanfang"))
(progn
;; --- AS-Winkel/-Seite: wiederverwenden, falls am Kettenanfang schon
;; entschieden (Regelfall) - sonst (Nutzer hatte "Kein AS-Element"
;; gewaehlt) hier eine eigene, minimale Nachfrage, da diese Option
;; konzeptionell IMMER mit einem AS beginnt.
(if as-vorhanden
(progn (setq as-w (vfl-as-winkel)) (setq as-s as-seite))
(progn
(if (vfl-wizard-aktiv) (vl-catch-all-apply 'vflw-gruppe-qc-winkel-impl (list "vf-winkel-aus-header")))
(princ (ssg-text "vf-winkel-aus-header"))
(princ (ssg-text "vf-winkel-90"))
(princ (ssg-text "vf-winkel-30"))
(setq as-w (vfl-in-value "STR"
(function (lambda () (vf-frage-element-winkel "vf-winkel-aus-header")))))
(setq *vfl-as-winkel* as-w)
(princ (ssg-text "vfl-aus-seite-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
"vfl-aus-seite-header"))
(setq as-s (if (= antwort "2") "rechts" "links"))
(setq as-seite as-s)
(vf-set-as-masse as-w as-s)
(setq as-vorhanden t)
)
)
;; --- Richtung waehlen (Nutzer-Vorgabe 2026-09-12): die Gerade, auf
;; der KS_EIN des AS-Elements UND (nach dem ganzen Umweg) KS_AUS des
;; ES-Elements liegen sollen - per Mausklick wie ueberall sonst im
;; Linienzug (vfl-in-richtung, auf das 30-Grad-Weltraster gesnappt).
;; p0x/p0y (das REFERENZ-KS_EIN fuer die Kolinearitaets-Pruefung) sind
;; hier einfach der gepickte Punkt selbst - fuer DIESE Option wird
;; AS_Element unten bewusst MIT KS_EIN (nicht mit dem Block-Ursprung)
;; auf den Klickpunkt gefluchtet (Nutzer-Vorgabe 2026-09-12: der
;; Klickpunkt soll die reale Anlagenplanung, z.B. Kreisel-Anschluss,
;; exakt treffen).
(setq theta-ref (vfl-in-richtung p-aktuell (ssg-text "vfl-qc-prompt-richtung")))
(if (null theta-ref) (progn (princ (ssg-text "vfl-abgebrochen")) (exit)))
;; Echter Grundriss-Schwenk des AS-Elements (KEINE Annahme "30 bzw. 90
;; Grad genau" - die reale Blockgeometrie entscheidet, siehe
;; vf-element-plan-turn) - die gewaehlte Richtung ist die EINTRITTS-
;; Achse (KS_EIN), die tatsaechliche Fahrtrichtung danach (hz0, KS_AUS)
;; liegt um diesen Schwenk verdreht.
(setq as-turn (vf-element-plan-turn (strcat "AS_Element_" as-w "_" as-s)))
(setq hz0 (+ theta-ref as-turn))
;; --- Mengengeruest abfragen ---
;; E1 ist die GESAMTE Strecke vom AS-Austritt bis zum ersten 30-Grad-
;; Bogen INKLUSIVE des dazwischenliegenden 300mm-Separators (Nutzer-
;; Korrektur 2026-09-12: vorher kam der Separator ZUSAETZLICH oben
;; drauf, die Kette wurde dadurch laenger als eingegeben) - die reine
;; GF-Geradenstrecke ist E1 MINUS des Separator-Horizontalanteils.
(setq rad3 (* (float (fix *vfk-gefaelle-winkel*)) (/ pi 180.0)))
(setq e1-brutto (vfl-in-real (ssg-textf "vfl-qc-prompt-e1" (list (rtos *vfk-qc-einlauf-laenge* 2 0)))))
(if (null e1-brutto) (setq e1-brutto *vfk-qc-einlauf-laenge*))
(setq e1 (- e1-brutto (* *vfk-separator-laenge* (cos rad3))))
(setq ziel-laenge (vfl-in-real (ssg-text "vfl-qc-prompt-gesamtlaenge-mittelteil")))
(setq anteil (vfl-in-real (ssg-textf "vfl-qc-prompt-anteil" (list (rtos *vfk-qc-fallend-anteil-default* 2 2)))))
(if (null anteil) (setq anteil *vfk-qc-fallend-anteil-default*))
;; E2 (Laenge 2. Bogen -> ES) wird NICHT gefragt (Nutzer-Vorgabe
;; 2026-09-12): sobald Winkel und alle anderen Laengen feststehen, ist
;; sie die einzige verbleibende Freiheit, um die Kette exakt auf
;; theta-ref zurueckzufuehren - siehe die Geraden-Gleichung weiter
;; unten (nach dem 2. Bogen, ueber vfl-kreuz2d).
(if (vfl-wizard-aktiv) (vl-catch-all-apply 'vflw-gruppe-qc-winkel-impl (list "vf-winkel-ein-header")))
(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")))))
(setq es-seite as-s) ; Nutzer-Vorgabe: ES-Seite = AS-Seite, keine Frage
(vf-set-es-masse *vfl-es-winkel* es-seite)
;; --- Bogen-Seite UND -Winkel HERLEITEN (keine Frage, Nutzer-Vorgabe
;; 2026-09-12): beide Boegen benutzen DIESELBE Seite UND denselben
;; Winkel. Damit die Kette am Ende wieder auf theta-ref liegt, muss der
;; Boegen-Schwenk b die Gleichung as-turn + 2*b + es-turn = 0
;; erfuellen - b wird daraus hergeleitet und gegen die vier real
;; vorhandenen Bogen-Varianten (30/90 Grad x links/rechts - AS/ES
;; koennen laut Nutzer-Vorgabe nur 30 oder 90 Grad sein, mehr braucht
;; es daher nicht) geprueft (kein Raten); passt keine, ist die
;; Winkel-Kombination nicht schliessbar.
(setq es-turn (vf-element-plan-turn (strcat "ES_Element_" *vfl-es-winkel* "_" es-seite)))
(setq bogen-turn-30-r (vf-element-plan-turn "Gefaellebogen_rechts_30_R500"))
(setq bogen-turn-30-l (vf-element-plan-turn "Gefaellebogen_links_30_R500"))
(setq bogen-turn-90-r (vf-element-plan-turn "Gefaellebogen_rechts_90_R500"))
(setq bogen-turn-90-l (vf-element-plan-turn "Gefaellebogen_links_90_R500"))
(setq b-noetig (/ (- (+ as-turn es-turn)) 2.0))
(setq tol 2.0)
(setq bogen-seite nil bogen-winkel nil)
(cond
((< (abs (- bogen-turn-30-r b-noetig)) tol) (setq bogen-seite "rechts" bogen-winkel 30))
((< (abs (- bogen-turn-30-l b-noetig)) tol) (setq bogen-seite "links" bogen-winkel 30))
((< (abs (- bogen-turn-90-r b-noetig)) tol) (setq bogen-seite "rechts" bogen-winkel 90))
((< (abs (- bogen-turn-90-l b-noetig)) tol) (setq bogen-seite "links" bogen-winkel 90))
)
(if (null bogen-seite)
(princ (ssg-textf "vfl-qc-bogen-nicht-schliessbar"
(list (rtos as-turn 2 1) (rtos es-turn 2 1) (rtos b-noetig 2 1))))
(progn
(setq bogen-turn-echt
(cond
((and (= bogen-seite "rechts") (= bogen-winkel 30)) bogen-turn-30-r)
((and (= bogen-seite "links") (= bogen-winkel 30)) bogen-turn-30-l)
((and (= bogen-seite "rechts") (= bogen-winkel 90)) bogen-turn-90-r)
(T bogen-turn-90-l)))
(setq rad3 (* (float (fix *vfk-gefaelle-winkel*)) (/ pi 180.0)))
(setq tan3 (/ (sin rad3) (cos rad3)))
(setq dref-x (cos (* (float theta-ref) (/ pi 180.0))))
(setq dref-y (sin (* (float theta-ref) (/ pi 180.0))))
;; --- AS + E1 + Sep + 1. Bogen JETZT SCHON WIRKLICH BAUEN (nicht erst
;; nach der Hoehenbilanz) - Grund (Nutzer-Befund 2026-09-12):
;; Gefaellebogen-Bloecke haengen an einem BEREITS 3-Grad-geneigten
;; Rahmen - ihre reale Positions-/Hoehenwirkung liess sich per Formel
;; nicht zuverlaessig vorhersagen (siehe vfl-gf-auf-ab-solve-Historie);
;; MESSEN nach echtem Bau ist robuster als eine weitere Rotationsformel
;; zu vertrauen. Faellt die Kette hinterher als "nicht baubar" durch,
;; wird diese Teil-Geometrie ueber checkpoint/vfl-loesche-seit wieder
;; entfernt.
;; AS-Einfuegung fuer diese Option BEWUSST NICHT ueber
;; vfl-insert-as-element (das fluchtet den Block-URSPRUNG auf den
;; Kettenpunkt, siehe dortiger Kommentar - Mittelachsen-Konvention der
;; UEBRIGEN Linie-GF-Zweige) - Nutzer-Vorgabe 2026-09-12 fuer DIESE
;; Option: der Klickpunkt soll die reale Anlagenplanung exakt treffen
;; (z.B. Anschluss an einen Kreisel), darum wird hier KS_EIN direkt
;; (xy-ref/z-ref="KS_EIN") auf den Klickpunkt gefluchtet - p0-real ist
;; dadurch einfach der Klickpunkt selbst, keine Nachmessung mehr noetig.
(setq checkpoint (vf-lastent-ohne-attribute))
(setq p0-real p-aktuell)
(setq p0x (car p0-real) p0y (cadr p0-real))
(setq theta-ref-rad (* (float theta-ref) (/ pi 180.0)))
(setq frame (insert-block-mixed-to-ks
(strcat "AS_Element_" as-w "_" as-s)
(make-frame-from-dir p-aktuell (list (cos theta-ref-rad) (sin theta-ref-rad) 0.0))
(caddr p-aktuell) "KS_EIN" "KS_EIN"))
(setq p-aktuell (car frame))
(setq soll-hoehe (caddr p-aktuell))
(princ (ssg-textf "vfl-qc-ks-ein-gemessen"
(list (rtos p0x 2 2) (rtos p0y 2 2) (rtos (caddr p0-real) 2 2))))
(setq hz-jetzt (car (frame->hz-winkel frame)))
(setq frame (vfl-insert-gf-segment (car frame) hz-jetzt e1 (fix *vfk-gefaelle-winkel*)))
(vfl-acc-gf-seg (/ e1 (cos rad3)) (fix *vfk-gefaelle-winkel*))
(setq anzahl-gf (1+ anzahl-gf))
(setq frame (vfl-insert-separator frame))
(setq frame (vfl-insert-gf-bogen-block frame bogen-winkel bogen-seite))
(setq hz1 (car (frame->hz-winkel frame)))
(setq hz2 (+ hz1 bogen-turn-echt))
;; --- 2. Bogen UND Separator+ES werden HIER PROBEWEISE an der
;; AKTUELLEN Stelle (nach dem 1. Bogen) eingefuegt, ihre reale
;; Wirkung gemessen, dann wieder geloescht (Nutzer-Log 2026-09-12:
;; trotz mehrerer Korrekturrunden an der Formel-Schaetzung blieb eine
;; Rest-Abweichung - die Division im E2-Solve kann jeden
;; verbleibenden Schaetzfehler stark verstaerken, wenn hz2 nah an
;; theta-ref liegt. ECHT MESSEN statt einer vierten Formel-Korrektur).
;; Gueltig, weil sowohl der 2. Bogen als auch Separator+ES NUR von der
;; ANKUNFTS-ORIENTIERUNG abhaengen (hz1 bzw. die daraus resultierende
;; hz2), nicht von der Position - die Probe an dieser Stelle liefert
;; also exakt dieselbe relative Wirkung wie der spaetere echte Bau
;; nach Fa/S/Fb (dieselbe Orientierung, nur eine andere Position). Das
;; Mittelteil (Fa+Sep+Bogen6auf+S+Bogen6ab+Fb+Sep) hat als GANZES per
;; Definition die horizontale Laenge ziel-laenge (Nutzer-Eingabe) -
;; IMMER, unabhaengig von der spaeteren F_total/S-Aufteilung (beide
;; Teile liegen auf derselben Fahrtrichtung hz1) - daher genuegt fuer
;; die Position vor der Probe ein einfacher ziel-laenge-Vorschub.
(setq hz1-rad (* (float hz1) (/ pi 180.0)))
;; vfl-insert-gf-bogen-block/vfl-insert-es-element zaehlen intern in
;; *vfl-acc-gfbogen*/*vfl-acc-separator* mit (fuer die spaeteren
;; GF_Bogen_L/R_xx- bzw. Separator-Attribute) - die Probe darf diese
;; Zaehler NICHT dauerhaft verfaelschen, darum sichern/zuruecksetzen.
(setq acc-gfbogen-save *vfl-acc-gfbogen* acc-sep-save *vfl-acc-separator*)
(setq probe-checkpoint (vf-lastent-ohne-attribute))
(setq probe-frame (vfl-insert-gf-bogen-block frame bogen-winkel bogen-seite))
(setq hz2 (car (frame->hz-winkel probe-frame)))
(setq bg2x (- (car (car probe-frame)) (car (car frame))))
(setq bg2y (- (cadr (car probe-frame)) (cadr (car frame))))
(setq bg2z (- (caddr (car probe-frame)) (caddr (car frame))))
(setq probe-frame2 (vfl-insert-es-element "GF" probe-frame hz2 (fix *vfk-gefaelle-winkel*)
(caddr (car probe-frame)) es-seite))
(setq esx (- (car (car probe-frame2)) (car (car probe-frame))))
(setq esy (- (cadr (car probe-frame2)) (cadr (car probe-frame))))
(setq esz (- (caddr (car probe-frame2)) (caddr (car probe-frame))))
(vfl-loesche-seit probe-checkpoint)
(setq *vfl-acc-gfbogen* acc-gfbogen-save *vfl-acc-separator* acc-sep-save)
(setq hz2-rad (* (float hz2) (/ pi 180.0)))
(setq px (+ (car (car frame)) (* ziel-laenge (cos hz1-rad)) bg2x))
(setq py (+ (cadr (car frame)) (* ziel-laenge (sin hz1-rad)) bg2y))
(setq vx (- px p0x) vy (- py p0y))
(setq zaehler (- (+ (vfl-kreuz2d vx vy dref-x dref-y)
(vfl-kreuz2d esx esy dref-x dref-y))))
(setq nenner (vfl-kreuz2d (cos hz2-rad) (sin hz2-rad) dref-x dref-y))
(setq e2 (/ zaehler nenner))
(if (< e2 1.0)
(progn
(princ (ssg-textf "vfl-qc-e2-negativ" (list (rtos e2 2 0))))
(vfl-loesche-seit checkpoint))
(progn
;; --- Ziel-Hoehenaenderung des Mittelteils: AS+E1+Sep+1.Bogen sind
;; bereits ECHT gebaut (frame traegt die reale Hoehe nach dem 1.
;; Bogen) - kein Formel-Risiko mehr fuer diesen Teil. WICHTIG: die
;; Referenz fuer "gleiche Hoehe" ist KS_EIN des AS-Elements (Z des
;; Klickpunkts, siehe insert-block-mixed-to-ks z-ref="KS_EIN"), NICHT
;; soll-hoehe (= AS's KS_AUS-Hoehe, direkt nach dem Bau gemessen) -
;; genau wie bei der X/Y-Referenzgerade weiter oben. AS hat einen
;; ECHTEN, von Null verschiedenen KS_EIN->KS_AUS-Hoehenanteil (aus-dz,
;; Nutzer-Korrektur vom Sitzungsbeginn) - der muss hier separat wieder
;; hinzugerechnet werden (die reale Frame-Messung ab soll-hoehe
;; deckt nur "AS-KS_AUS bis nach 1.Bogen" ab, nicht "AS-KS_EIN bis
;; AS-KS_AUS"). bg2z/esz sind die oben ECHT GEMESSENEN Hoehenanteile
;; des 2. Bogens bzw. von Separator+ES (Probe-Bau) - kein
;; Formel-Risiko mehr. Es fehlt nur noch E2 selbst (reine Gerade,
;; E2*tan3 exakt).
(setq h-aussen-rest
(+ aus-dz
(- (caddr (car frame)) soll-hoehe)
bg2z
(- (* e2 tan3))
esz))
;; --- Solver: F_total/S aus vorgegebener Mittelteil-Gesamtlaenge und
;; der noch fehlenden Hoehenaenderung (Rest bis Gesamtbilanz=0) - S
;; beginnt beim symmetrischen Grundfall (3 Grad) und wird in 3-Grad-
;; Schritten steiler (6, 9, 12, ...) probiert, bis F_total/S beide
;; baubar sind (>=0) - der FLACHSTE passende Winkel wird automatisch
;; gewaehlt (Nutzer-Vorgabe 2026-09-14: dieselbe dynamische Winkelwahl
;; wie Option 5, "gegebene Laenge -> passenden Winkel waehlen"). ---
(setq s-winkel (fix *vfk-gefaelle-winkel*))
(setq versuche 0 gueltig nil)
(while (and (not gueltig) (< versuche 10))
(setq loesung (vfl-gf-auf-ab-solve ziel-laenge (- h-aussen-rest) s-winkel))
(setq f-total (nth 0 loesung) s (nth 1 loesung) gueltig (nth 2 loesung))
(if (not gueltig) (setq s-winkel (+ s-winkel (fix *vfk-gefaelle-winkel*))))
(setq versuche (1+ versuche))
)
(princ (ssg-textf "vfl-qc-s-winkel-echo" (list (itoa s-winkel))))
(if (not gueltig)
(progn
(princ (ssg-textf "vfl-qc-nicht-baubar" (list (rtos f-total 2 0) (rtos s 2 0) (rtos (nth 3 loesung) 2 0))))
(vfl-loesche-seit checkpoint))
(progn
(setq extra-grad (- s-winkel (fix *vfk-gefaelle-winkel*)))
(setq rad-s (* (float s-winkel) (/ pi 180.0)))
(setq fa (* f-total anteil))
(setq fb (- f-total fa))
;; --- Rest WIRKLICH bauen (physische Reihenfolge; AS/E1/Sep/1.
;; Bogen sind bereits gebaut, frame steht auf dessen Ausgang) ---
(setq hz-jetzt (car (frame->hz-winkel frame)))
(setq frame (vfl-insert-gf-segment (car frame) hz-jetzt fa (fix *vfk-gefaelle-winkel*)))
(vfl-acc-gf-seg (/ fa (cos rad3)) (fix *vfk-gefaelle-winkel*))
(setq anzahl-gf (1+ anzahl-gf))
(setq frame (vfl-insert-separator frame))
(setq erg-bogen (vfl-gf-bogen-uebergang frame (- (fix *vfk-gefaelle-winkel*))))
(setq frame (car erg-bogen))
;; --- S bei s-winkel Grad (dynamisch gewaehlt): die zusaetzliche
;; Neigung wird mit ECHTEN Bausteinen erreicht (vfl-gf-bogen-
;; erweitern, wie bei Option 5). ---
(setq frame (vfl-gf-bogen-erweitern frame "auf" (- (fix *vfk-gefaelle-winkel*)) extra-grad))
(setq hz-jetzt (car (frame->hz-winkel frame)))
(setq frame (vfl-insert-gf-segment (car frame) hz-jetzt s (- s-winkel)))
(vfl-acc-gf-seg (/ s (cos rad-s)) (- s-winkel))
(setq anzahl-gf (1+ anzahl-gf))
(setq frame (vfl-gf-bogen-erweitern frame "ab" (- s-winkel) extra-grad))
(setq erg-bogen (vfl-gf-bogen-uebergang frame (fix *vfk-gefaelle-winkel*)))
(setq frame (car erg-bogen))
(setq hz-jetzt (car (frame->hz-winkel frame)))
(setq frame (vfl-insert-gf-segment (car frame) hz-jetzt fb (fix *vfk-gefaelle-winkel*)))
(vfl-acc-gf-seg (/ fb (cos rad3)) (fix *vfk-gefaelle-winkel*))
(setq anzahl-gf (1+ anzahl-gf))
(setq frame (vfl-insert-separator frame))
(setq frame (vfl-insert-gf-bogen-block frame bogen-winkel bogen-seite))
(setq hz-jetzt (car (frame->hz-winkel frame)))
(setq frame (vfl-insert-gf-segment (car frame) hz-jetzt e2 (fix *vfk-gefaelle-winkel*)))
(vfl-acc-gf-seg (/ e2 (cos rad3)) (fix *vfk-gefaelle-winkel*))
(setq anzahl-gf (1+ anzahl-gf))
(setq frame (vfl-insert-es-element "GF" frame hz-jetzt (fix *vfk-gefaelle-winkel*)
(caddr (car frame)) es-seite))
(setq p-aktuell (car frame))
(setq letzter-typ "GF")
(setq ist-hoehe (caddr p-aktuell))
(setq abweichung (vfl-kreuz2d (- (car p-aktuell) p0x) (- (cadr p-aktuell) p0y) dref-x dref-y))
(princ (ssg-textf "vfl-qc-erfolg"
(list (rtos soll-hoehe 2 1) (rtos ist-hoehe 2 1) (rtos abweichung 2 1))))
(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
;; BricsCAD-Tests): wie vfl-qc-linie-mit-gf (Option 4), aber statt EINES
;; 30/90-Grad-Bogens ein kompletter U-Turn ueber ZWEI 90-Grad-GF-
;; Horizontalboegen (dieselbe Seite = GEGENSEITE der AS-Seite, zusammen ~180
;; Grad Wende) mit einer festen Zwischenstrecke dazwischen. E1, die feste
;; Zwischenstrecke, beide 90-Grad-Bogen behalten dabei durchgehend die
;; aktuelle 3-Grad-Neigung bei (wie ueberall sonst in Linie-GF) - eine
;; probeweise horizontale (0 Grad) Variante wurde getestet und wieder
;; verworfen (Nutzer-Korrektur 2026-09-13).
;; AS/ES-Winkel sind fest 90 Grad, nur die Seite ist waehlbar. E1 UND die
;; Referenzrichtung werden in EINEM Klick bestimmt (vfl-in-abstand) - der
;; Klickpunkt markiert den Start des 1. Bogens, Separator-Laenge wird intern
;; abgezogen (wie Option 4: Klickpunkt = exakter Bogen-Start). AS wird mit
;; xy-ref/z-ref="KS_EIN" eingefuegt (Klickpunkt = KS_EIN exakt). theta-ref
;; (die Klick-Richtung) ist E1's Fahrtrichtung, NICHT AS's Eintrittsachse -
;; AS's KS_EIN-Achse (eintritt-hz = theta-ref - as-turn) steht bei einem
;; echten 90-Grad-Element senkrecht dazu und ist die Baseline, auf die ES am
;; Ende EXAKT zurueckkehren muss (Nutzer-Vorgabe, 3. Praezisierungsrunde:
;; erzwungen, nicht mehr nur informativ wie urspruenglich).
;; Mittelteil (Nutzer-Vorgabe 2026-09-13, finale Struktur nach mehreren
;; Praezisierungsrunden): nach dem 2. (horizontalen) Bogen direkt Fa
;; (fallend, STANDARD 3 Grad, UNBEKANNT) + Sep -> Uebergang fallend->
;; steigend (auf_6, STANDARD - Fa/Sep garantieren sauber 3 Grad davor) ->
;; ECHTE Zusatz-Neigung auf -9 Grad (zweiter Vario_Bogen_auf_6-Baustein,
;; vfl-gf-bogen-weiter) -> S (steigend, 9 Grad - STEILER als Fa, UNBEKANNT,
;; Nutzer-Vorgabe: je steiler S, desto kleiner kann E1 gewaehlt werden) ->
;; ECHTE Rueckkehr auf -3 Grad (zweiter Vario_Bogen_ab_6-Baustein) ->
;; Uebergang steigend->fallend (ab_6, STANDARD) -> ES (mit automatisch
;; gebuendeltem Separator). Fa UND S sind BEIDE Unbekannte, geloest aus
;; einem 2x2-System: Hoehenbilanz (S*tan9-Fa*tan3=D) UND Lage-Bilanz/
;; Kolinearitaet zur Baseline (Fa+S=T, Kreuzprodukt-Gleichung wie Option 4's
;; E2-Loesung, inkl. dem Fixterm des Separators zwischen Fa und dem
;; auf_6-Uebergang). Der komplette Rest (Uebergang auf_6+Zusatz-auf_6+
;; Zusatz-ab_6+ab_6+Sep+ES) haengt nur von der Ankunfts-Orientierung ab
;; (nicht von Fa/S selbst) - sein GESAMTER Hoehen- UND Lage-Beitrag (mit
;; Fa=S=0) wird darum per Probe-Bau+Messen VORAB ermittelt (dasselbe Prinzip
;; wie bei Option 4), danach Fa/S direkt geloest und alles ECHT gebaut. Die
;; asymmetrische Neigung (Fa bei 3, S bei 9 statt beide bei 3) macht Fa/S
;; erst ueber E1 steuerbar - bei identischen Winkeln waeren D und T
;; rechnerisch gleich empfindlich auf alle Laengen-Eingaben, das
;; Gleichungssystem also nicht ueber Nutzereingaben beeinflussbar (Fa+S=T
;; haengt NIE von den Neigungswinkeln ab, nur die Hoehenbilanz tut es -
;; erst der Winkel-Unterschied entkoppelt beide Gleichungen genug, um
;; ueberhaupt eine Loesung ueber E1/Zwischenstrecke anzusteuern).
(defun vfl-qc-linie-mit-gf-uform ( / as-w as-s bogen-seite theta-ref as-turn eintritt-hz
hz-jetzt e1 e1-brutto erg-abstand fest-laenge
e1-neigung e1-neigung-rad
s fa d-hoehe t-lage gueltig sep-dx-planar sep-dz
s-winkel extra-grad rad-s tan-s versuche
tan-e1neigung d-fa-de1 e1-vorschlag
soll-hoehe ist-hoehe rad3 tan3
p0x p0y dref-x dref-y theta-ref-rad
checkpoint p0-real h-aussen-rest hoehe-vor-fa
p2x p2y hz2 hz2-rad fixedx fixedy dx-fix dy-fix
probe-checkpoint probe-frame-a probe-frame-b probe-frame-c dz-fix
acc-gfbogen-save acc-sep-save
abweichung antwort erg-bogen)
(if (not kettenanfang)
(princ (ssg-text "vfl-qc-uform-nur-kettenanfang"))
(progn
;; --- AS-Seite: wiederverwenden, falls am Kettenanfang schon
;; entschieden - Winkel wird IMMER auf 90 erzwungen (Nutzer-Vorgabe),
;; unabhaengig davon, was bei der allgemeinen AS-Frage evtl. schon
;; gewaehlt wurde (vf-set-as-masse wird darum hier IMMER erneut mit "90"
;; aufgerufen, auch wenn as-vorhanden schon t war).
(princ (ssg-text "vfl-qc-uform-winkel-fix"))
(if as-vorhanden
(setq as-s as-seite)
(progn
(princ (ssg-text "vfl-aus-seite-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
"vfl-aus-seite-header"))
(setq as-s (if (= antwort "2") "rechts" "links"))
(setq as-seite as-s)
(setq as-vorhanden t)
)
)
(setq as-w "90")
(vf-set-as-masse as-w as-s)
;; Bogen-Seite = GEGENSEITE der AS-Seite (Nutzer-Korrektur 2026-09-13,
;; nach BricsCAD-Test: mit bogen-seite=as-s lief der U-Turn in die
;; falsche Richtung zurueck - die AS-Drehteller-Seite und die
;; GF-Bogen-Seite sind entgegengesetzt konventioniert).
(setq bogen-seite (if (= as-s "links") "rechts" "links"))
(setq rad3 (* (float (fix *vfk-gefaelle-winkel*)) (/ pi 180.0)))
(setq tan3 (/ (sin rad3) (cos rad3)))
;; rad-s/tan-s (S's Neigung) werden weiter unten PRO WINKEL-KANDIDAT neu
;; berechnet (dynamische Winkelwahl, Nutzer-Vorgabe 2026-09-13) - Fa
;; faellt immer bei 3 Grad, VOR S (asymmetrische Neigung fuer Fa/S, je
;; steiler S desto kleiner kann E1 werden).
;; --- Richtung UND E1-Laenge in EINEM Klick (Nutzer-Vorgabe
;; 2026-09-13): der Klickpunkt markiert den Start des 1. 90-Grad-Bogens;
;; Separator-Laenge wird intern abgezogen, damit der Bogen exakt dort
;; beginnt (wie Option 4's Klickpunkt=KS_EIN-Prinzip). ---
(setq erg-abstand (vfl-in-abstand p-aktuell nil (ssg-text "vfl-qc-uform-prompt-richtung-e1")))
(if (null erg-abstand) (progn (princ (ssg-text "vfl-abgebrochen")) (exit)))
(setq e1-brutto (car erg-abstand) theta-ref (cdr erg-abstand))
;; Konsolen-Echo (Nutzer-Vorgabe 2026-09-13): anders als bei einem
;; Zahl-Dialog zeigt ein reiner Klick den erfassten Wert nicht von
;; selbst an - wie bei vfl-echo-endpunkt-hoehe/vfl-echo-position wird
;; das Ergebnis darum explizit ausgegeben.
(princ (ssg-textf "vfl-qc-uform-e1-echo" (list (rtos e1-brutto 2 0) (rtos theta-ref 2 1))))
(setq as-turn (vf-element-plan-turn (strcat "AS_Element_" as-w "_" as-s)))
;; --- E1-Neigung: NICHT die angenommene *vfk-gefaelle-winkel* (3 Grad),
;; sondern die REALE, am AS-Block gemessene Neigungsaenderung KS_EIN->
;; KS_AUS (Nutzer-Vorgabe 2026-09-13, NUR fuer diese Option - siehe
;; vf-element-plan-neigung-Kommentar). E1 schliesst damit exakt an AS's
;; echten KS_AUS an, ohne kuenstlichen Neigungssprung. Als Nebeneffekt
;; (Nutzer-Befund) wird dadurch Fa/S ueber E1 steuerbar - bei E1-Neigung
;; = 3 Grad waeren D (Hoehenbilanz) und T (Lagebilanz) rechnerisch exakt
;; gleich empfindlich auf E1, Fa haette also NICHT ueber E1 korrigiert
;; werden koennen.
(setq e1-neigung (vf-element-plan-neigung (strcat "AS_Element_" as-w "_" as-s)))
(setq e1-neigung-rad (* (float e1-neigung) (/ pi 180.0)))
(princ (ssg-textf "vfl-qc-uform-e1-neigung-echo" (list (rtos e1-neigung 2 2))))
;; Separator-Abzug mit e1-neigung (nicht rad3) - der Separator erbt die
;; Neigung des vorangehenden Segments (E1), nicht die feste 3 Grad.
(setq e1 (- e1-brutto (* *vfk-separator-laenge* (cos e1-neigung-rad))))
;; --- Restliches Mengengeruest ---
(setq fest-laenge (vfl-in-real (ssg-textf "vfl-qc-prompt-u-fest-laenge" (list (rtos *vfk-qc-u-fest-laenge* 2 0)))))
(if (null fest-laenge) (setq fest-laenge *vfk-qc-u-fest-laenge*))
(setq es-seite as-s) ; Nutzer-Vorgabe: ES-Seite = AS-Seite, keine Frage
(vf-set-es-masse "90" es-seite) ; ES-Winkel ebenfalls fest 90, keine Frage
;; --- AS wirklich bauen (Klickpunkt = KS_EIN, wie Option 4) ---
;; WICHTIG (Nutzer-Korrektur 2026-09-13): theta-ref ist die gewuenschte
;; FAHRTRICHTUNG von E1 (der Klick misst e1-brutto entlang theta-ref,
;; siehe vfl-in-abstand) - das ist AS's AUSTRITTS-Richtung (KS_AUS),
;; NICHT die Eintritts-Achse (KS_EIN)! AS_Element_90 dreht intern um
;; as-turn (gemessen, nicht angenommen) - die KS_EIN-Achse muss darum
;; um as-turn ZURUECKgedreht werden (eintritt-hz = theta-ref - as-turn),
;; damit KS_AUS nach dem Bau exakt auf theta-ref zeigt. Bei einem
;; echten 90-Grad-Bogen bedeutet das: die KS_EIN-Achse steht senkrecht
;; zur gewaehlten Richtung.
(setq eintritt-hz (- theta-ref as-turn))
;; Referenzgerade (Baseline), zu der ES am Ende EXAKT zurueckkehren soll
;; (Nutzer-Vorgabe 2026-09-13, 3. Runde: erzwungen, nicht mehr nur
;; informativ), ist AS's EINTRITTS-Achse (eintritt-hz, senkrecht zu
;; theta-ref) - NICHT theta-ref selbst (das ist E1's Fahrtrichtung).
(setq dref-x (cos (* (float eintritt-hz) (/ pi 180.0))))
(setq dref-y (sin (* (float eintritt-hz) (/ pi 180.0))))
(setq checkpoint (vf-lastent-ohne-attribute))
(setq p0-real p-aktuell)
(setq p0x (car p0-real) p0y (cadr p0-real))
(setq theta-ref-rad (* (float eintritt-hz) (/ pi 180.0)))
(setq frame (insert-block-mixed-to-ks
(strcat "AS_Element_" as-w "_" as-s)
(make-frame-from-dir p-aktuell (list (cos theta-ref-rad) (sin theta-ref-rad) 0.0))
(caddr p-aktuell) "KS_EIN" "KS_EIN"))
(setq p-aktuell (car frame))
(setq soll-hoehe (caddr p-aktuell))
(princ (ssg-textf "vfl-qc-ks-ein-gemessen"
(list (rtos p0x 2 2) (rtos p0y 2 2) (rtos (caddr p0-real) 2 2))))
;; --- E1 + Sep bei e1-neigung (REAL gemessen, NICHT der festen 3-Grad-
;; Annahme - siehe Kommentar oben), danach 1. Bogen, feste
;; Zwischenstrecke, 2. Bogen bei der jetzt aktuellen Neigung (Bogen/
;; Zwischenstrecke sind neigungs-agnostisch, "erhalten die aktuelle
;; Neigung") - ALLES strikt sequenziell ECHT gebaut. ---
(setq hz-jetzt (car (frame->hz-winkel frame)))
(setq frame (vfl-insert-gf-segment (car frame) hz-jetzt e1 e1-neigung))
(vfl-acc-gf-seg (/ e1 (cos e1-neigung-rad)) e1-neigung)
(setq anzahl-gf (1+ anzahl-gf))
(setq frame (vfl-insert-separator frame))
;; --- Rein rechnerischer Ruecksprung auf EXAKT 3 Grad (Nutzer-Vorgabe
;; 2026-09-13): e1-neigung ist die REALE AS-Neigung, oft ein "krummer"
;; Wert, keiner der beiden Zustaende (flach/3 Grad), die die
;; Uebergangs-Bogen-Bibliothek kennt - ein spaeterer Uebergang auf
;; steigend (fuer Bogen-auf-6) wuerde sonst falsch klassifiziert
;; (Nutzer-Befund: "auf_3" statt "auf_6" im Log). Position/Fahrtrichtung
;; bleiben unveraendert, nur die Neigung wird auf *vfk-gefaelle-winkel*
;; gesetzt - wie jeder GF-Geraden-Uebergang (vfl-insert-gf-segment endet
;; ja ebenfalls immer mit genau der uebergebenen winkel, kein
;; Uebergangs-Baustein noetig) ---
(setq hz-jetzt (car (frame->hz-winkel frame)))
(setq frame (make-frame-from-dir (car frame) (hz-winkel->xu hz-jetzt (fix *vfk-gefaelle-winkel*))))
(setq frame (vfl-insert-gf-bogen-block frame 90 bogen-seite))
(setq hz-jetzt (car (frame->hz-winkel frame)))
(setq frame (vfl-insert-gf-segment (car frame) hz-jetzt fest-laenge (fix *vfk-gefaelle-winkel*)))
(vfl-acc-gf-seg (/ fest-laenge (cos rad3)) (fix *vfk-gefaelle-winkel*))
(setq anzahl-gf (1+ anzahl-gf))
(setq frame (vfl-insert-gf-bogen-block frame 90 bogen-seite))
;; --- Nochmaliger rein rechnerischer Ruecksprung auf EXAKT 3 Grad NACH
;; Bogen2 (Nutzer-Befund 2026-09-13: die Gefaellebogen_..._90_R500-
;; Bloecke geben ihre eigene, REAL gemessene KS_AUS-Neigung zurueck -
;; die weicht leicht von der eingehenden Neigung ab und fiel dann in
;; die "flach"-Toleranz von vfl-gf-bogen-uebergang, was die PROBE
;; direkt betrifft: die Probe ruft vfl-gf-bogen-uebergang naemlich MIT
;; dem rohen frame direkt nach Bogen2 auf, NOCH BEVOR Fa gebaut wird -
;; Fa's eigene Neigungs-Korrektur (explizites winkel=3) kommt fuer die
;; Probe also zu spaet. Position (car frame) bleibt unveraendert, nur
;; die Neigung wird hart auf *vfk-gefaelle-winkel* gesetzt - dasselbe
;; Prinzip wie beim E1-Fix weiter oben. ---
(setq hz-jetzt (car (frame->hz-winkel frame)))
(setq frame (make-frame-from-dir (car frame) (hz-winkel->xu hz-jetzt (fix *vfk-gefaelle-winkel*))))
(setq hoehe-vor-fa (caddr (car frame)))
(setq p2x (car (car frame)) p2y (cadr (car frame)))
(setq hz2 (car (frame->hz-winkel frame)))
(setq hz2-rad (* (float hz2) (/ pi 180.0)))
;; --- Dynamische Winkelwahl (Nutzer-Vorgabe 2026-09-13: "gegebene
;; Laenge -> passenden Winkel waehlen" statt einem einzigen fest
;; verdrahteten Winkel): S beginnt beim symmetrischen Grundfall (3
;; Grad, extra-grad=0) und wird in 3-Grad-Schritten steiler (6, 9,
;; 12, ...), bis Fa/S beide baubar sind (>=0) - der FLACHSTE passende
;; Winkel wird automatisch gewaehlt. Jede Erweiterung nutzt ECHTE
;; Bausteine (vfl-gf-bogen-erweitern: moeglichst viele 6-Grad-, ein
;; 3-Grad-Rest-Baustein). Je Kandidat: Probe-Bau+Messen (wie Option 4)
;; von Bogen-auf-6 + Erweiterung + Rueckkehr + Bogen-ab-6 + Sep+ES
;; (Fa=S=0) - ihr GESAMTER Hoehen- UND Lage-Beitrag haengt nur von der
;; Ankunfts-Orientierung ab (hz2), nicht von Fa/S. Fa (fallend, VOR S,
;; 3 Grad) und S (steigend, NACH Fa, s-winkel) sind die beiden
;; Unbekannten in einem 2x2-System: Hoehenbilanz (S*tan(s-winkel)-
;; Fa*tan3=D) UND Lage-Bilanz (Fa+S=T, Kolinearitaet zur Baseline
;; eintritt-hz/dref). ---
;; --- Separator ZWISCHEN Fa und dem auf_6-Uebergang: die Probe beginnt
;; DIREKT bei p2 (ohne Fa/Sep) - Fa selbst steckt schon in der Gleichung
;; (S-Fa), aber der Separator NACH Fa hat eine EIGENE, FESTE (vom
;; Nutzer nicht beeinflussbare) Horizontal-/Hoehenwirkung, die sonst
;; NIRGENDS mitgezaehlt wuerde (Bug-Fund 2026-09-13: realer Test lief
;; durch, aber deltaX/deltaZ zur Baseline waren NICHT null). Analog zu
;; Option 4's fix-dx/fix-dz (vfl-gf-auf-ab-solve) wird sein Fussabdruck
;; hier explizit ergaenzt. ---
(setq sep-dx-planar (* *vfk-separator-laenge* (cos rad3)))
(setq sep-dz (* *vfk-separator-laenge* (sin rad3)))
(setq s-winkel (fix *vfk-gefaelle-winkel*))
(setq versuche 0 gueltig nil)
(while (and (not gueltig) (< versuche 10))
(setq extra-grad (- s-winkel (fix *vfk-gefaelle-winkel*)))
(setq rad-s (* (float s-winkel) (/ pi 180.0)))
(setq tan-s (/ (sin rad-s) (cos rad-s)))
(setq acc-gfbogen-save *vfl-acc-gfbogen* acc-sep-save *vfl-acc-separator*)
(setq probe-checkpoint (vf-lastent-ohne-attribute))
(setq probe-frame-a (car (vfl-gf-bogen-uebergang frame (- (fix *vfk-gefaelle-winkel*)))))
(setq probe-frame-a (vfl-gf-bogen-erweitern probe-frame-a "auf" (- (fix *vfk-gefaelle-winkel*)) extra-grad))
(setq probe-frame-a (vfl-gf-bogen-erweitern probe-frame-a "ab" (- s-winkel) extra-grad))
(setq probe-frame-b (car (vfl-gf-bogen-uebergang probe-frame-a (fix *vfk-gefaelle-winkel*))))
(setq probe-frame-c (vfl-insert-es-element "GF" probe-frame-b (car (frame->hz-winkel probe-frame-b))
(fix *vfk-gefaelle-winkel*) (caddr (car probe-frame-b)) es-seite))
(setq dz-fix (- (caddr (car probe-frame-c)) hoehe-vor-fa))
(setq dx-fix (- (car (car probe-frame-c)) p2x))
(setq dy-fix (- (cadr (car probe-frame-c)) p2y))
(vfl-loesche-seit probe-checkpoint)
(setq *vfl-acc-gfbogen* acc-gfbogen-save *vfl-acc-separator* acc-sep-save)
(setq h-aussen-rest (+ aus-dz (- hoehe-vor-fa soll-hoehe) dz-fix (- sep-dz)))
(setq d-hoehe (- h-aussen-rest))
(setq fixedx (+ p2x dx-fix) fixedy (+ p2y dy-fix))
(setq t-lage (- (/ (- (vfl-kreuz2d (- fixedx p0x) (- fixedy p0y) dref-x dref-y))
(vfl-kreuz2d (cos hz2-rad) (sin hz2-rad) dref-x dref-y))
sep-dx-planar))
(setq s (/ (+ d-hoehe (* t-lage tan3)) (+ tan3 tan-s)))
(setq fa (- t-lage s))
(setq gueltig (and (>= s -0.01) (>= fa -0.01)))
(if (not gueltig) (setq s-winkel (+ s-winkel (fix *vfk-gefaelle-winkel*))))
(setq versuche (1+ versuche))
)
(princ (ssg-textf "vfl-qc-uform-fa-s-echo" (list (rtos fa 2 0) (rtos s 2 0) (itoa s-winkel))))
(if (not gueltig)
(progn
;; --- Mindest-E1-Vorschlag (Nutzer-Vorgabe 2026-09-13, analog zu
;; Option 4's Mindest-Gesamtlaenge): d(Fa)/d(E1) ist EXAKT konstant
;; (haengt nur von s-winkel/e1-neigung ab, nicht von E1 selbst) -
;; die Korrektur ist damit kein Schaetzwert, sondern eine exakte
;; algebraische Aufloesung nach E1, bei der Fa (mit dem STEILSTEN
;; probierten S-Winkel, hier also der beste erreichbare Fall) genau
;; 0 wird. ---
(setq tan-e1neigung (/ (sin e1-neigung-rad) (cos e1-neigung-rad)))
(setq d-fa-de1 (/ (- tan-s tan-e1neigung) (+ tan3 tan-s)))
(setq e1-vorschlag (if (> d-fa-de1 0.0001) (+ e1-brutto (/ (- fa) d-fa-de1)) nil))
(if e1-vorschlag
(princ (ssg-textf "vfl-qc-uform-fa-s-negativ-e1" (list (rtos fa 2 0) (rtos s 2 0) (rtos e1-vorschlag 2 0))))
(princ (ssg-textf "vfl-qc-uform-fa-s-negativ" (list (rtos fa 2 0) (rtos s 2 0))))
)
(vfl-loesche-seit checkpoint))
(progn
(setq extra-grad (- s-winkel (fix *vfk-gefaelle-winkel*)))
;; --- Fa ECHT bauen (fallend, STANDARD 3 Grad, VOR S) + Sep ---
(setq frame (vfl-insert-gf-segment (car frame) hz2 fa (fix *vfk-gefaelle-winkel*)))
(vfl-acc-gf-seg (/ fa (cos rad3)) (fix *vfk-gefaelle-winkel*))
(setq anzahl-gf (1+ anzahl-gf))
(setq frame (vfl-insert-separator frame))
;; --- Uebergang fallend->steigend (auf_6, STANDARD - Fa/Sep
;; garantieren sauber 3 Grad davor) ---
(setq erg-bogen (vfl-gf-bogen-uebergang frame (- (fix *vfk-gefaelle-winkel*))))
(setq frame (car erg-bogen))
;; --- S bei s-winkel Grad (dynamisch gewaehlt): die zusaetzliche
;; Neigung wird mit ECHTEN Bausteinen erreicht (vfl-gf-bogen-
;; erweitern, siehe dort). ---
(setq frame (vfl-gf-bogen-erweitern frame "auf" (- (fix *vfk-gefaelle-winkel*)) extra-grad))
(setq hz-jetzt (car (frame->hz-winkel frame)))
(setq frame (vfl-insert-gf-segment (car frame) hz-jetzt s (- s-winkel)))
(vfl-acc-gf-seg (/ s (cos rad-s)) (- s-winkel))
(setq anzahl-gf (1+ anzahl-gf))
(setq frame (vfl-gf-bogen-erweitern frame "ab" (- s-winkel) extra-grad))
(setq erg-bogen (vfl-gf-bogen-uebergang frame (fix *vfk-gefaelle-winkel*)))
(setq frame (car erg-bogen))
(setq hz-jetzt (car (frame->hz-winkel frame)))
(setq frame (vfl-insert-es-element "GF" frame hz-jetzt (fix *vfk-gefaelle-winkel*)
(caddr (car frame)) es-seite))
(setq p-aktuell (car frame))
(setq letzter-typ "GF")
(setq ist-hoehe (caddr p-aktuell))
(setq abweichung (vfl-kreuz2d (- (car p-aktuell) p0x) (- (cadr p-aktuell) p0y) dref-x dref-y))
(princ (ssg-textf "vfl-qc-erfolg"
(list (rtos soll-hoehe 2 1) (rtos ist-hoehe 2 1) (rtos abweichung 2 1))))
(setq fertig t)
)
)
)
)
)
;; Horizontales Sub-Segment bauen. Die flache Zone (0 Grad) wird NICHT mehr
;; automatisch mit ab_3 auf die 3-Grad-Basis zurueckgefuehrt - das Stueck ENDET
;; FLACH. Der ab_3-Uebergang kommt erst, wenn ein 3-Grad-Element folgt
;; (vfl-nach-3grad). Der auf_3-Eintritt wird nur gesetzt, wenn der Frame noch
;; NICHT flach ist (sonst bleibt die laufende flache Zone erhalten). Separatoren
;; VOR/NACH liegen in der 0-Grad-Ebene. Rueckgabe: neuer Frame (flach, 0 Grad).
;; ziel-modus=T: dL ist die GESAMT-Zielstrecke ab pt (der Nutzer hat einen
;; Endpunkt gepickt, den die Kette exakt treffen soll). In diesem Fall werden
;; ALLE Fragen, die die spaeter tatsaechlich gebaute Laenge beeinflussen
;; (Separator VOR/NACH, UND "Ist der Endpunkt der Foerderer?"), VOR der
;; Laengenberechnung gestellt - erst wenn wirklich alle Informationen da sind,
;; wird dL final berechnet und die horizontale Strecke gebaut. Das verhindert,
;; dass die Kette am Ende ueber den gepickten Punkt hinausragt, nur weil
;; nachtraeglich noch ein Separator oder eine Motorstation dazukommt.
;; gf2-laenge: die (schon feststehende) GF2-Laenge aus der GF-Verteilungs-
;; Frage (L_GF2-bau in vfl-vf-einheit) - wird bei Antwort "1" (nur Motor-
;; station) MIT reserviert, da vfs-vf-exit sie direkt danach anbaut. Bei
;; Antwort "3" (Kettenende definieren) NICHT reservieren: dort berechnet
;; vfl-body-abschluss ein eigenes, unabhaengiges ziel-gf2 (siehe dort) - hier
;; unbekannt und irrelevant.
;; Rueckgabe bei ziel-modus=T: (frame ist-endpunkt-antwort) - der Aufrufer
;; (vfl-vf-einheit) muss die Frage dann NICHT erneut stellen. Bei ziel-modus=
;; nil (Default/mid-chain-Fortsetzung): unveraendertes Verhalten, Rueckgabe
;; nur frame.
;; --- Horizontaler Koerper: reiner Bauteil ---
;; Baut das horizontale Stueck aus FERTIGEN Antworten - keine Frage, kein
;; Dialog. Rueckgabe: neuer Frame (das Stueck endet flach, 0 Grad).
;;
;; ziel-modus bleibt ein EIGENER Parameter neben ende-code (der Fahrplan
;; wollte ihn ersetzen): die Separator-Subtraktionen haengen allein am
;; Ziel-Modus, nicht an der Endpunkt-Antwort - und ende-code darf nil sein
;; (abgebrochene Menuefrage). Mit nur einem Parameter waere genau dieser Fall
;; eine stille Verhaltensaenderung.
;;
;; REIHENFOLGE IST GEOMETRIE: jede dL-Subtraktion ist mit
;; (max *vfk-restlaenge-min-clamp* ...) geklammert, also nicht kommutativ,
;; und der auf_3-Insert sitzt BEWUSST zwischen Subtraktion 1 und 2 - sein
;; Fussabdruck wird GEMESSEN (vfl-projiziere-distanz), nicht geschaetzt.
;; Hier darf nichts umsortiert werden.
;; Klemmt v auf *vfk-restlaenge-min-clamp*, falls es darunter faellt, und
;; meldet das (headless-sicher via vfl-meldung statt einer interaktiven
;; Rueckfrage - der Bauteil selbst bleibt deterministisch/kopflos-tauglich,
;; siehe vf_spec.lsp; anders als im urspruenglichen einteiligen Entwurf kann
;; hier daher nicht mehr nachgefragt/rueckgebaut werden, nur informieren).
(defun vfl-hor-koerper-klemmen (v / geklemmt)
(setq geklemmt (max *vfk-restlaenge-min-clamp* v))
(if (< v *vfk-restlaenge-min-clamp*)
(vfl-meldung (ssg-textf "vfl-hinweis-horizontal-fussabdruck-geklemmt"
(list (rtos v 2 0) (rtos geklemmt 2 0)))))
geklemmt)
(defun vfl-hor-koerper-bauen (frame hz dL ziel-modus ende-code sep-vor sep-nach
gf2-laenge / pt m1 pt-vor-bogen)
(setq pt (car frame))
(if (and ziel-modus sep-vor) (setq dL (vfl-hor-koerper-klemmen (- dL *vfk-separator-laenge*))))
;; auf_3-Eintritt nur, wenn noch NICHT flach (sonst flache Zone fortsetzen).
;; dL ist die gewuenschte Reststrecke AB HIER (pt) - der reale Bogen-
;; Fussabdruck (gemessen, nicht aus der Tabellen-Masse geschaetzt - die
;; stimmt nach der Rotation nicht mehr exakt) wird von dL abgezogen, damit
;; das horizontale Stueck am gewuenschten Zielpunkt endet.
(if (not (vfl-frame-flach-p frame))
(progn
(setq pt-vor-bogen pt)
(setq m1 (get-bogen-mass bogen-auf 3))
(princ (ssg-text "vfl-bogen-auf3-uebergang"))
(setq pt (insert-rotated-block-with-ks "Vario_Bogen_auf_3_TEF_rechts" pt
(ssg-cfg-or "vario" "gefaelle_winkel" 3) (car m1) (caddr m1) hz))
(setq dL (vfl-hor-koerper-klemmen (- dL (vfl-projiziere-distanz pt-vor-bogen pt hz))))))
(if (and ziel-modus sep-nach) (setq dL (vfl-hor-koerper-klemmen (- dL *vfk-separator-laenge*))))
;; Ausgangs-Fussabdruck (Vario_Bogen_ab_3 + Motorstation) nur reservieren,
;; wenn die Einheit tatsaechlich sofort schliesst (Antwort 1 oder 3); bei
;; Antwort 1 zusaetzlich die GF2 hinter dem Motor.
(if ziel-modus
(cond
((= ende-code "1")
(setq dL (vfl-hor-koerper-klemmen (- dL (car (get-bogen-mass bogen-ab (fix *vfk-gefaelle-winkel*))) *vfk-stations-laenge*
(* (if gf2-laenge gf2-laenge 0.0)
(cos (* *vfk-gefaelle-winkel* (/ pi 180.0))))))))
((= ende-code "3")
(setq dL (vfl-hor-koerper-klemmen (- dL (car (get-bogen-mass bogen-ab (fix *vfk-gefaelle-winkel*))) *vfk-stations-laenge*))))))
;; optionaler Separator VOR - in der horizontalen Ebene (0 Grad)
(if sep-vor (setq pt (vfl-sep-hz pt hz)))
;; horizontale Zwischenstrecke (0 Grad)
(princ (ssg-textf "vfl-horizontale-zwischenstrecke" (list (rtos dL 2 2))))
(setq pt (insert-inclined-scaled-block "Staustrecke_SP_1000_mm" pt dL 0 hz))
(vfl-acc-vf-seg "horizontal" 0 dL)
;; optionaler Separator NACH (jetzt tatsaechlich bauen)
(if sep-nach (setq pt (vfl-sep-hz pt hz)))
;; KEIN ab_3 mehr -> das Stueck ENDET FLACH (0 Grad)
(make-frame-from-dir pt (hz-winkel->xu hz 0.0))
)
;; Frage "Ist der Endpunkt der Foerderer?" (Motorstation/weiterbauen/
;; Motorstation+Kettenende) - eigene Funktion, da an mehreren Stellen genutzt
;; (Ziel-Modus des horizontalen Koerpers UND die Fortsetzungsschleife von
;; vfl-vf-einheit). Zeigt im Wizard-Modus SELBST den Dialog (vflw_horizontal,
;; radio-basiert) und legt die Antwort in die Pending-Queue - der Konsolen-
;; Text bleibt darunter unveraendert stehen (Fallback bei Fehlern/GUI aus).
(defun vfl-frage-ist-endpunkt ( / dat ergebnis gende)
(princ (ssg-text "vfl-ist-endpunkt-frage"))
(princ (ssg-text "vfl-ja-nur-motorstation"))
(princ (ssg-text "vfl-nein-weiterbauen"))
(princ (ssg-text "vfl-ja-motorstation-kettenende"))
(if (vfl-wizard-aktiv)
(progn
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_horizontal" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text "vfl-ist-endpunkt-frage")))
(set_tile "istende_1" "1")
(action_tile "accept"
"(setq gende (cond ((= (get_tile \"istende_0\") \"1\") \"0\")
((= (get_tile \"istende_2\") \"1\") \"2\")
(t \"1\")))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1) (vflw-pending-push-all (list (itoa (1+ (atoi gende))))))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
)
)
(vfl-menu (ssg-text "vfl-prompt-wahl-1-3-def2")
(list (ssg-text "vfl-ja-nur-motorstation")
(ssg-text "vfl-nein-weiterbauen")
(ssg-text "vfl-ja-motorstation-kettenende")) 2
"vfl-ist-endpunkt-frage"))
;; Frage "Ist das das Kettenende?" (Linie-GF-Segment-Schleife) - eigene
;; Funktion, da an zwei identischen Stellen genutzt (mit/ohne vorherigen
;; Separator-Einbau). Im Wizard-Modus zeigt sie SELBST den Dialog
;; (vflw_kettenende, radio-basiert) und legt die Antwort in die Pending-
;; Queue - analog zu vfl-frage-ist-endpunkt.
(defun vfl-frage-ist-kettenende ( / dat ergebnis gwahl antwort)
(princ (ssg-text "vfl-ist-kettenende-header"))
(princ (ssg-text "vfl-kettenende-opt-ja-es"))
(princ (ssg-text "vfl-kettenende-opt-ja-ohne-es"))
(princ (ssg-text "vfl-kettenende-opt-nein"))
(if (vfl-wizard-aktiv)
(progn
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_kettenende" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text "vfl-ist-kettenende-header")))
(action_tile "accept"
"(setq gwahl (cond ((= (get_tile \"wahl_ja_es\") \"1\") \"1\")
((= (get_tile \"wahl_ja_ohne_es\") \"1\") \"2\")
(t \"3\")))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1) (vflw-pending-push-all (list gwahl)))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
)
)
(setq antwort (vfl-menu (ssg-text "vfl-prompt-wahl-1-3-def3")
(list (ssg-text "vfl-kettenende-opt-ja-es")
(ssg-text "vfl-kettenende-opt-ja-ohne-es")
(ssg-text "vfl-kettenende-opt-nein")) 3
"vfl-ist-kettenende-header"))
antwort)
;; Frage-Schale "ES-Element setzen?" (+ falls ja: Winkel/Seite) - ausgelagert
;; aus vfl-vf-einheit (Option 3 "Kettenende"), damit sie auch VOR einem
;; Winkel-Solve aufgerufen werden kann: der tatsaechliche ES-Fussabdruck
;; (ein-dx/ein-dz, per vfl-es-masse-setzen INNERHALB von vfl-frage-es-seite
;; gesetzt) muss dafuer VOR dem Solve feststehen, sonst waehlt dieser einen zu
;; flachen/langen Winkel und der spaeter noetige Schwanz (Motorstation+
;; Separator+ES) passt nicht mehr in die Reststrecke (siehe vfl-vf-einheit,
;; vorab-antwort-Parameter). Bei "Nein" werden ein-dx/ein-dz genullt (kein
;; ES-Fussabdruck reserviert) und der alte Wert als 3./4. Rueckgabewert
;; mitgegeben - der Aufrufer muss ihn nach getaner Zerlegung wiederherstellen.
;; Rueckgabe: (es-gewuenscht es-seite save-eindx save-eindz).
(defun vfl-es-element-abfragen ( / es-antwort es-gewuenscht es-seite save-eindx save-eindz)
(princ (ssg-text "vfl-es-setzen-frage"))
(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")))
(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"))
(setq es-gewuenscht (/= es-antwort "2"))
(if es-gewuenscht
(setq es-seite (vfl-frage-es-seite t))
(progn (setq save-eindx ein-dx save-eindz ein-dz)
(setq ein-dx 0.0 ein-dz 0.0)))
(list es-gewuenscht es-seite save-eindx save-eindz))
;; --- Horizontaler Koerper: Frage-Schale (Name unveraendert) ---
;; Fragt Separator vor/nach und - im Ziel-Modus - "Ist der Endpunkt der
;; Foerderer?", danach baut vfl-hor-koerper-bauen. Keine der drei Fragen
;; haengt an einem berechneten Wert, sie stehen darum alle vor dem Bau; die
;; Journal-Reihenfolge ist dieselbe wie zuvor.
;; Die Endpunkt-Antwort wird im Ziel-Modus mit zurueckgegeben (vor-antwort),
;; damit die Fortsetzungsschleife des Aufrufers sie nicht erneut erfragt.
(defun vfl-baue-horizontal-koerper (frame hz dL ziel-modus gf2-laenge /
sep-vor sep-nach ist-ende-antwort neu)
(princ (ssg-text "vfl-sep-vor-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-nein"))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-ja-nein-impl (list (ssg-text "vfl-sep-vor-frage"))))
(setq sep-vor (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2 "vfl-sep-vor-frage") "1"))
;; Separator NACH abfragen (noch nicht bauen) - im Ziel-Modus muss der
;; Fussabdruck vor der Laengenberechnung feststehen.
(princ (ssg-text "vfl-sep-nach-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-nein"))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-ja-nein-impl (list (ssg-text "vfl-sep-nach-frage"))))
(setq sep-nach (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2 "vfl-sep-nach-frage") "1"))
(if ziel-modus
(setq ist-ende-antwort (vfl-frage-ist-endpunkt)))
(setq neu (vfl-hor-koerper-bauen frame hz dL ziel-modus ist-ende-antwort
sep-vor sep-nach gf2-laenge))
(if ziel-modus (list neu ist-ende-antwort) neu)
)
;; ============================================================
;; OPTION 3: KETTENENDE AUS LAUFENDER VF-EINHEIT (ein Motor)
;; ============================================================
;; Zerlegt den verbleibenden GERADEN Lauf bis zum Ziel-ES mit der bewaehrten
;; STANDARD-Logik (wie aussen), aber fuer den Mid-Body-Fall:
;; - Der Einlauf (AS + GF1 + Separator + Umlenk) ist bereits gebaut und wird
;; NICHT beruecksichtigt -> aus-dx/aus-dz = 0.
;; - Fest vor dem ES stehen nur Motor(500) + Auslauf-Separator(300)
;; -> FESTE_HORIZONTAL = 800.
;; Ergebnis der Standard-Logik (GF2 = GF, VARIIERT mit dem Winkel):
;; steiler als 3 Grad / steigend -> gewinkeltes VF + GF2 (Winkeltabelle),
;; flacher als 3 Grad -> horizontales VF + GF2 (Rest-Laenge ueber
;; das horizontale Stueck ausgeglichen).
;; Die Hoehe kommt also aus VF-Winkel/horizontal + GF2, die ueberschuessige
;; Laenge aus dem horizontalen VF - genau die zweistufige Logik.
;; Rueckgabe: (typ winkel L_GF2 L_VF) - typ "GF"/"VF"/nil (wie
;; vfl-segment-entscheidung; L_GF2 varriert, Mindestwert siehe vfl-body-abschluss).
;; Sammelt Diagnosetext fuer einen fehlgeschlagenen Stufe-2-Versuch (siehe
;; vfl-stufe2-diagnose) - der Aufrufer (vfl-body-abschluss) haengt/zeigt ihn
;; NACH der endgueltigen FEHLER-Meldung an. Grund: Stufe 2 ist nur ein (evtl.
;; scheiternder) ERSTER Versuch, danach folgt oft noch Stufe 1 (Winkel-
;; Suche) - stuende die Stufe-2-Diagnose weiterhin sofort im Log, wuerde sie
;; sich zwischen ">>> Kettenende-Modus" und die Stufe-1-Winkeltabelle
;; schieben, obwohl sie inhaltlich zur (evtl. erst ganz am Ende sichtbaren)
;; FEHLER-Zeile gehoert. Gelingt Stufe 1 danach doch noch, bleibt der
;; gesammelte Text ungenutzt (kein "typ=nil" in vfl-body-abschluss) - so wird
;; ein erfolgreicher Log nicht mit Details eines verworfenen Zwischenversuchs
;; zugemuellt.
(defun vfl-diag-add (txt)
(setq *vfl-stufe2-diagnose-text*
(strcat (if *vfl-stufe2-diagnose-text* *vfl-stufe2-diagnose-text* "") txt)))
(defun vfl-stufe2-diagnose (deltaL deltaH horizontal-info schon-flach /
rad3 mass1 mass2 bogen-x1 bogen-z1 bogen-x2 bogen-z2
gf-total gueltig fixed-x k-term minh minl-mitgf
fest-planar fest-hoehe)
(vfl-diag-add (ssg-textf "vfl-stufe2-diagnose"
(list (rtos deltaL 2 0) (rtos deltaH 2 0)
(if horizontal-info (rtos (car horizontal-info) 2 1) "?")
(if horizontal-info (rtos (cadr horizontal-info) 2 1) "?")
(if (and horizontal-info (caddr horizontal-info)) "GUELTIG" "UNGUELTIG"))))
(setq rad3 (* *vfk-gefaelle-winkel* (/ pi 180.0)))
(setq mass2 (get-bogen-mass bogen-ab (fix *vfk-gefaelle-winkel*)))
(setq bogen-x2 (car mass2) bogen-z2 (caddr mass2))
(if schon-flach
(setq bogen-x1 0.0 bogen-z1 0.0)
(progn
(setq mass1 (get-bogen-mass bogen-auf (fix *vfk-gefaelle-winkel*)))
(setq bogen-x1 (+ (* (car mass1) (cos rad3)) (* (caddr mass1) (sin rad3))))
(setq bogen-z1 (+ (* (- (car mass1)) (sin rad3)) (* (caddr mass1) (cos rad3))))
)
)
(vfl-diag-add (ssg-textf "vfl-stufe2-diagnose-details"
(list (if schon-flach "JA" "NEIN")
(rtos aus-dx 2 1)
(rtos ein-dx 2 1) (rtos ein-dz 2 1)
(rtos bogen-x1 2 1) (rtos bogen-z1 2 1)
(rtos bogen-x2 2 1) (rtos bogen-z2 2 1)
(rtos FESTE_HORIZONTAL 2 1))))
;; Explizite Anzeige: wieviel Laenge/Hoehe der feste "Schwanz" (Stationen +
;; Separator + ES, OHNE die optionalen Uebergangsboegen) allein beansprucht -
;; direkte Antwort auf "was braucht Motorstation+Separator+ES". Das Label
;; haengt vom Budget-Kontext ab: FESTE_HORIZONTAL=800 (Kettenende, nur
;; Motor+Separator - der Eingang/Umlenkstation ist zu diesem Zeitpunkt schon
;; gebaut) vs. 1300 (frischer VF-Einheit-Eingang, zusaetzlich Umlenkstation -
;; siehe *vfk-feste-horizontal-kettenende*/-linienzug in vf_konstanten.lsp).
(setq fest-planar (+ ein-dx (* FESTE_HORIZONTAL (cos rad3))))
(setq fest-hoehe (+ ein-dz (- (* FESTE_HORIZONTAL (sin rad3)))))
(vfl-diag-add (ssg-textf
(if (<= (abs (- FESTE_HORIZONTAL *vfk-feste-horizontal-kettenende*)) 0.5)
"vfl-stufe2-fest-kettenende"
"vfl-stufe2-fest-eingang")
(list (rtos fest-planar 2 0) (rtos fest-hoehe 2 0))))
(setq gueltig (and horizontal-info (caddr horizontal-info)))
;; *vfl-stufe2-minl-vorschlag*/*vfl-stufe2-minh-vorschlag*: kurze Zahlenwerte
;; (kein Text) fuer die knappe Alert-Zusammenfassung in vfl-body-abschluss -
;; minh ist immer definiert (reine Funktion der festen Geometrie), minl nur,
;; wenn Variante 1 (GF nach Zielhoehe) ueberhaupt eine nicht-negative GF
;; ergibt - siehe Kommentar oben.
(setq *vfl-stufe2-minl-vorschlag* nil *vfl-stufe2-minh-vorschlag* nil)
(if (not gueltig)
(progn
(setq gf-total (if horizontal-info (car horizontal-info) nil))
(setq fixed-x (+ aus-dx ein-dx bogen-x1 bogen-x2 (* FESTE_HORIZONTAL (cos rad3))))
(setq k-term (+ aus-dz ein-dz (- (* FESTE_HORIZONTAL (sin rad3))) bogen-z1 bogen-z2))
(setq minh (- k-term))
(setq *vfl-stufe2-minh-vorschlag* minh)
;; Variante 1: Mindest-Streckenlaenge (L_VF=0, GF nach Zielhoehe)
(if (and gf-total (>= gf-total 0))
(progn
(setq minl-mitgf (+ fixed-x (* gf-total (cos rad3))))
(setq *vfl-stufe2-minl-vorschlag* minl-mitgf)
(vfl-diag-add (ssg-textf "vfl-stufe2-minl" (list (rtos minl-mitgf 2 0) (rtos deltaL 2 0)))))
(vfl-diag-add (ssg-textf "vfl-stufe2-minl-unmoeglich" (list (rtos minh 2 0) (rtos deltaH 2 0))))
)
;; Variante 2: Mindest-Hoehenaenderung (GF=0, Rest als gerader VF-Anteil)
(if (>= deltaL fixed-x)
(vfl-diag-add (ssg-textf "vfl-stufe2-minh" (list (rtos minh 2 0) (rtos deltaH 2 0))))
(vfl-diag-add (ssg-textf "vfl-stufe2-minh-unmoeglich" (list (rtos fixed-x 2 0) (rtos deltaL 2 0))))
)
)
)
(princ))
;; Stufe-3-Zusatzfrage: Position des horizontalen Fuellstuecks (vor ODER nach
;; dem S-Bogen) - wird NUR gestellt, wenn Stufe 3 tatsaechlich einen Treffer
;; liefert (seltener Fallback, siehe vfl-winkel-stufe-entscheidung). Generischer
;; vflw_wahl-Popup-Dialog ueber vfl-menu (Muster wie die AUS-Element-Seite-
;; Frage) - keine neue DCL-Sonderloesung noetig. Selbstbeschreibender String
;; "vor"/"nach" statt roher Index-Antwort (Projektkonvention).
(defun vfl-frage-stufe3-position (/ antwort)
(princ (ssg-text "vfl-frage-stufe3-position"))
(princ (ssg-text "vfl-opt-stufe3-vor"))
(princ (ssg-text "vfl-opt-stufe3-nach"))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-opt-stufe3-vor")
(ssg-text "vfl-opt-stufe3-nach")) 1
"vfl-frage-stufe3-position"))
(if (= antwort "2") "nach" "vor"))
;; Gemeinsamer Bau-Helfer fuer EINEN VF-Koerper, optional MIT zusaetzlichem
;; horizontalem Fuellstueck (Stufe 3, siehe vfl-winkel-stufe-entscheidung/
;; vfl-stufe3-winkel-horizontal in vf_standard.lsp). Ersetzt den bis dahin an
;; zwei Stellen identisch duplizierten Aufruf (vfl-body-abschluss-bauen UND
;; vfl-vf-einheit): (vfl-frame-3grad (vfs-vf-koerper ... hzn) hzn) +
;; vfl-acc-vf-seg + Zaehler-Erhoehung.
;; L_hor/position nil oder L_hor<=0: unveraendertes Verhalten (ein einzelner
;; vfs-vf-koerper-Aufruf) - reiner Drop-in-Ersatz fuer Stufe 1/2, KEINE
;; Verhaltensaenderung dort.
;; Sonst: zwei vfs-vf-koerper-Aufrufe (S-Bogen bei winkel/L_VF, Fuellstueck
;; bei winkel=0/L_hor - baut sein eigenes auf_3/ab_3-Uebergangsbogenpaar
;; SELBST, siehe vfs-vf-koerper best-winkel=0-Sonderfall in vf_standard.lsp),
;; Reihenfolge nach position ("vor"/"nach" dem S-Bogen, vom Anwender gewaehlt).
;; Frame wird zwischen den Teilschritten jedesmal per vfl-frame-3grad neu
;; aufgebaut, jedes Teilstueck einzeln journalisiert (vfl-acc-vf-seg) - sonst
;; fehlt es im Export/Report.
;; vfl-nach-3grad (falls noetig) bleibt Sache des AUFRUFERS, wie bisher.
;; Rueckgabe: (list frame cnt-erhoehung), cnt-erhoehung = Anzahl eingefuegter
;; Koerper (1 ohne Fuellstueck, 2 mit).
(defun vfl-baue-vf-koerper-mit-fuellstueck (frame richtn winkel L_VF L_hor position hzn / cnt)
(setq cnt 0)
(if (and L_hor (> L_hor 1.0) position)
(if (= position "vor")
(progn
(setq frame (vfl-frame-3grad (vfs-vf-koerper (car frame) richtn 0 L_hor hzn) hzn))
(vfl-acc-vf-seg "horizontal" 0 L_hor)
(setq cnt (1+ cnt))
(setq frame (vfl-frame-3grad (vfs-vf-koerper (car frame) richtn winkel L_VF hzn) hzn))
(vfl-acc-vf-seg richtn winkel L_VF)
(setq cnt (1+ cnt))
)
(progn
(setq frame (vfl-frame-3grad (vfs-vf-koerper (car frame) richtn winkel L_VF hzn) hzn))
(vfl-acc-vf-seg richtn winkel L_VF)
(setq cnt (1+ cnt))
(setq frame (vfl-frame-3grad (vfs-vf-koerper (car frame) richtn 0 L_hor hzn) hzn))
(vfl-acc-vf-seg "horizontal" 0 L_hor)
(setq cnt (1+ cnt))
)
)
(progn
(setq frame (vfl-frame-3grad (vfs-vf-koerper (car frame) richtn winkel L_VF hzn) hzn))
(vfl-acc-vf-seg richtn winkel L_VF)
(setq cnt (1+ cnt))
)
)
(list frame cnt)
)
;; Vereinheitlichte Stufe-1(Winkel-Tabelle)/Stufe-2(horizontal+GF)-Entscheidung.
;; Genutzt von ZWEI Aufrufern mit unterschiedlichem Fest-Budget:
;; - vfl-body-zerlegung (Kettenende innerhalb einer laufenden VF-Einheit
;; ODER "Neue Linie BIS Kettenende" mit als VF-Familie erkanntem
;; Startpunkt, siehe linie-ende-familie/vfl-body-abschluss - beide Faelle
;; brauchen KEINE eigene Umlenkstation):
;; feste-hz=*vfk-feste-horizontal-kettenende* (800, nur Motor+Separator -
;; der Eingang ist zu diesem Zeitpunkt schon gebaut bzw. entfaellt).
;; - vfl-vf-entscheidung ("Neue Linie: Ab/Auf VF" bzw. "BIS Kettenende" mit
;; NICHT eindeutig zuordenbarem Startpunkt, Eingang einer FRISCHEN
;; VF-Einheit): feste-hz=*vfl-feste-horizontal* (1300, Umlenkstation+
;; Separator+Motorstation).
;; schon-flach: nur fuer den Kettenende-Fortsetzungsfall relevant (Frame kann
;; dort schon flach sein, siehe vfl-baue-horizontal-koerper) - am Eingang
;; einer frischen VF-Einheit immer nil (Frame kommt frisch von der
;; Umlenkstation bei 3 Grad).
;; Rueckgabe: (typ winkel L_GF L_VF) bzw. bei einem Stufe-3-Treffer (typ
;; winkel L_GF L_VF L_hor position), typ="GF"/"VF"/nil (winkel=0 bei "VF"
;; heisst horizontales Stufe-2-Ergebnis). L_hor/position sind nur bei Stufe 3
;; belegt (siehe vfl-stufe3-winkel-horizontal, vf_standard.lsp) - fehlende
;; Listenelemente liefern ueber (nth ...) automatisch nil, alle Konsumenten
;; lesen ausschliesslich per nth. KEINE eigene Mindestlaengen-Pruefung - die
;; bleibt Sache der Aufrufer (unterschiedliche Schwellwerte: 800/1000).
(defun vfl-winkel-stufe-entscheidung (deltaL deltaH richtung schon-flach feste-hz /
save-ausdx save-ausdz save-feste
winkel-natuerlich wahl horizontal-info
stufe3-erg res)
(if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek))
;; Gesammelten Stufe-2-Diagnosetext + Alert-Vorschlagswerte aus einem evtl.
;; vorherigen Aufruf verwerfen - der Aufrufer haengt/zeigt sie nur an, wenn
;; DIESER Aufruf scheitert (typ=nil).
(setq *vfl-stufe2-diagnose-text* nil)
(setq *vfl-stufe2-minl-vorschlag* nil *vfl-stufe2-minh-vorschlag* nil)
;; Budget-Globals temporaer auf das Aufrufer-Budget umbiegen (mit Restore).
(setq save-ausdx aus-dx save-ausdz aus-dz save-feste FESTE_HORIZONTAL)
(setq aus-dx 0.0 aus-dz 0.0 FESTE_HORIZONTAL feste-hz)
(setq res
(cond
;; praktisch flach: horizontales VF + GF2
((< deltaH 1.0)
(setq horizontal-info (berechne-horizontale-mitte deltaL deltaH "Ab" schon-flach))
(vfl-stufe2-diagnose deltaL deltaH horizontal-info schon-flach)
(if (and horizontal-info (caddr horizontal-info))
(list "VF" 0 (car horizontal-info) (cadr horizontal-info))
(list nil nil nil nil)))
;; steigend: Stufe 1 (gewinkeltes VF) zuerst; scheitert das, Stufe 2
;; (horizontales VF + GF2) als Fallback. GF2 wird dabei GENAUSO gebaut
;; wie im "Ab"-Fall (vfs-vf-exit, fest +3 Grad, gleiche Neigung wie
;; Motorstation/Umlenkstation/Separator) - KEIN Vorzeichenwechsel, keine
;; Bau-Aenderung noetig. Das Ausgleichsprinzip: Motorstation+GF2 senken
;; IMMER ab; wird bei knapper Laenge weniger/kein GF2 verbaut (kuerzer
;; als das volle Laengenbudget), sinkt die Kette insgesamt weniger stark
;; ab und die dadurch freiwerdende Laenge wird mit einem horizontalen
;; VF-Stueck (0 Grad, keine weitere Hoehenaenderung) aufgefuellt statt
;; mit mehr GF2 - der Uebergangsbogen (ab_3, bereits in berechne-
;; horizontale-mitte eingerechnet) bleibt dabei unveraendert.
((= richtung "Auf")
(princ (ssg-text "vfl-stufe1-start"))
(setq wahl (vfl-waehle-winkel (nth 3 (berechne-alle-winkel deltaL deltaH "Auf" feste-hz schon-flach))))
(if wahl
(list "VF" (nth 0 wahl) (nth 1 wahl) (nth 2 wahl))
(progn
(setq horizontal-info (berechne-horizontale-mitte deltaL deltaH "Auf" schon-flach))
(vfl-stufe2-diagnose deltaL deltaH horizontal-info schon-flach)
(if (and horizontal-info (caddr horizontal-info))
(list "VF" 0 (car horizontal-info) (cadr horizontal-info))
(progn
;; Stufe 1 UND Stufe 2 gescheitert: Stufe 3 (steiler Tabellen-
;; winkel + horizontales Fuellstueck, vfl-stufe3-winkel-
;; horizontal in vf_standard.lsp) als letzter Fallback vor der
;; Ablehnung. Position (vor/nach dem S-Bogen) fragt der
;; Anwender NUR, wenn Stufe 3 tatsaechlich einen Treffer liefert.
(princ (ssg-text "vfl-stufe3-start"))
(setq stufe3-erg (vfl-stufe3-winkel-horizontal deltaL deltaH "Auf" feste-hz schon-flach))
(if (nth 4 stufe3-erg)
(list "VF" (nth 0 stufe3-erg) (nth 1 stufe3-erg) (nth 2 stufe3-erg)
(nth 3 stufe3-erg) (vfl-frage-stufe3-position))
(list nil nil nil nil)))))))
;; fallend: natuerlichen Neigungswinkel gegen die 3-Grad-Eigenneigung pruefen
(t
(setq winkel-natuerlich (* (atan (/ deltaH deltaL)) (/ 180.0 pi)))
(cond
;; KEIN "~3 Grad -> reine GF2 ohne Rechnung"-Kurzschluss mehr - frueher
;; hier: (list "GF" *vfk-gefaelle-winkel* nil nil) - L_GF/L_VF blieben
;; nil und vfl-body-abschluss musste GF2 dann selbst aus deltaL ALLEIN
;; herleiten - deltaH wurde dabei NIE geprueft, der Austrittsbogen
;; (bogen-x2) NIE abgezogen. Ergebnis: ein REALER Hoehenfehler (in
;; einem Livetest 2026-09-10 z.B. dZ=-40.5mm), sobald der natuerliche
;; Winkel nicht EXAKT 3,000 Grad war (nur nahe dran, per Toleranz
;; *vfl-gf-winkel-toleranz*=0.5 Grad). Stufe 1 (Tabellenwinkel 3 Grad
;; ist selbst ein Tabelleneintrag) loest deltaL UND deltaH exakt
;; simultan (vf-winkel-solve) und deckt den ~3-Grad-Fall daher
;; automatisch und PRAEZISE mit ab - kein separater Zweig noetig.
;; steiler als 3 Grad -> Stufe 1: gewinkeltes VF + GF2 (GF2 variiert mit
;; Winkel) zuerst; scheitert das (z.B. bei knapper Streckenlaenge -
;; "steiler als 3 Grad" heisst nur, dass der NATUERLICHE Winkel ueber
;; 3 Grad liegt, nicht dass die Tabelle bei JEDEM Winkel passt), Stufe
;; 2 (horizontales VF + GF2) als Fallback, analog zur "flacher"-
;; Verzweigung unten und zum "Auf"-Fallback oben.
((> winkel-natuerlich *vfk-gefaelle-winkel*)
(princ (ssg-text "vfl-stufe1-start"))
(setq wahl (vfl-waehle-winkel (nth 3 (berechne-alle-winkel deltaL deltaH "Ab" feste-hz schon-flach))))
(if wahl
(list "VF" (nth 0 wahl) (nth 1 wahl) (nth 2 wahl))
(progn
(setq horizontal-info (berechne-horizontale-mitte deltaL deltaH "Ab" schon-flach))
(vfl-stufe2-diagnose deltaL deltaH horizontal-info schon-flach)
(if (and horizontal-info (caddr horizontal-info))
(list "VF" 0 (car horizontal-info) (cadr horizontal-info))
(list nil nil nil nil)))))
;; flacher als 3 Grad -> Stufe 2: horizontales VF + GF2
(t
(setq horizontal-info (berechne-horizontale-mitte deltaL deltaH "Ab" schon-flach))
(vfl-stufe2-diagnose deltaL deltaH horizontal-info schon-flach)
(if (and horizontal-info (caddr horizontal-info))
(list "VF" 0 (car horizontal-info) (cadr horizontal-info))
(progn
(vfl-diag-add (ssg-text "vfl-stufe2-ungueltig"))
(princ (ssg-text "vfl-stufe1-start"))
(setq wahl (vfl-waehle-winkel (nth 3 (berechne-alle-winkel deltaL deltaH "Ab" feste-hz schon-flach))))
(if wahl (list "VF" (nth 0 wahl) (nth 1 wahl) (nth 2 wahl)) (list nil nil nil nil)))))))))
;; Budget-Globals zuruecksetzen
(setq aus-dx save-ausdx aus-dz save-ausdz FESTE_HORIZONTAL save-feste)
res)
;; Duenner Wrapper: eigene Mindestlaenge (kuerzer als das Budget = kein
;; Abschluss baubar), sonst delegiert an vfl-winkel-stufe-entscheidung mit dem
;; Kettenende-Budget. es-gewuenscht=nil senkt das Budget von 800 (Motor+
;; Separator) auf 500 (nur Motor) - siehe vfl-body-budget.
(defun vfl-body-zerlegung (deltaL deltaH richtung schon-flach es-gewuenscht /
budget)
(setq budget (vfl-body-budget es-gewuenscht))
(if (< deltaL budget)
(list nil nil nil nil)
(vfl-winkel-stufe-entscheidung deltaL deltaH richtung schon-flach budget)))
;; Fragt Zielpunkt (XY) + Zielhoehe (Z) ab, zerlegt den Rest (vfl-body-
;; zerlegung, Standard-Logik) und baut den Koerper VOR dem Motor: gewinkeltes
;; VF (winkel>0) oder horizontales VF (winkel=0). GF2 (variiert mit dem Winkel,
;; Mindestwert *vfl-gf-min-laenge*=400) sitzt hinter dem Motor und wird als
;; Laenge zurueckgegeben (der Aufrufer setzt sie beim Auslauf ein und schliesst
;; mit Motor -> GF2 -> Separator -> ES ab). Speichert den Soll-Zielpunkt in
;; *vfl-ziel-punkt* fuer den Ist-Ziel-Report.
;; Rueckgabe: (frame anzahl-koerper letzt-hz gf2-laenge) oder nil.
;; --- Kettenende-Abschluss: reiner Bauteil ---
;; Zerlegt die Reststrecke und baut Koerper + Motor-Vorbereitung. Fragt
;; nichts mehr - die einzige Rueckfrage steckt in vfl-body-zerlegung
;; (vfl-waehle-winkel, nur wenn mehrere Winkel gueltig sind) und ist ueber
;; *vfl-winkel-idx-vorgabe* vorab beantwortbar.
;; Rueckgabe: (frame anzahl-koerper hz gf2-laenge) oder nil, wenn der Rest
;; nicht baubar ist.
;;
;; Signatur schlanker als im Fahrplan skizziert: letzt-hz und es-gewuenscht
;; werden hier nicht gebraucht. es-gewuenscht wirkt beim Aufrufer, der
;; ein-dx/ein-dz um den Aufruf herum nullt (kein ES-Fussabdruck reservieren);
;; letzt-hz wurde nur einer wirkungslosen Zuweisung zugefuehrt (siehe unten).
(defun vfl-body-abschluss-bauen (frame p-umlenk dL hzn hn schon-flach es-gewuenscht /
dH richtn rad3 dec typ w lgf lvf lhor position
bau-erg gf2 cnt
p0 tx ty letzt-hz antwort-gf2)
(setq dH (- hn (caddr (car frame))))
(setq richtn (if (>= dH 0.0) "Auf" "Ab"))
(setq dH (abs dH))
(setq rad3 (* (float (ssg-cfg-or "vario" "gefaelle_winkel" 3)) (/ pi 180.0)))
(princ (ssg-textf "vfl-kettenende-modus-ziel"
(list (rtos dL 2 0) (rtos dH 2 0) richtn)))
(setq dec (vfl-body-zerlegung dL dH richtn schon-flach es-gewuenscht))
(setq typ (nth 0 dec) w (nth 1 dec) lgf (nth 2 dec) lvf (nth 3 dec)
lhor (nth 4 dec) position (nth 5 dec))
(if (null typ)
(progn
(princ (ssg-text "vfl-fehler-rest-nicht-baubar"))
;; Gesammelte Stufe-2-Diagnose (siehe vfl-stufe2-diagnose/vfl-diag-add)
;; erst HIER, NACH der eigentlichen FEHLER-Zeile zeigen - sie gehoert
;; inhaltlich zur Begruendung, warum nichts gebaut werden konnte.
(if *vfl-stufe2-diagnose-text* (princ *vfl-stufe2-diagnose-text*))
(dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Kettenende-Abschluss nicht baubar (dL="
(rtos dL 2 0) " dH=" (rtos dH 2 0) " richtung=" richtn ")"))
nil)
(progn
(dbgmsg (strcat "GEOMETRIE: vfl-body-zerlegung ERFOLG (dL=" (rtos dL 2 0)
" dH=" (rtos dH 2 0) " richtung=" richtn
" typ=" typ " winkel=" (rtos (float w) 2 1)
" L_GF=" (if lgf (rtos lgf 2 0) "nil")
" L_VF=" (if lvf (rtos lvf 2 0) "nil") ")"))
;; Soll-ES-Punkt fuer den Ist-Ziel-Report merken (Startpunkt +
;; projizierter Laenge entlang der Fahrtrichtung).
(setq p0 (car frame))
(setq tx (+ (car p0) (* dL (cos (* hzn (/ pi 180.0))))))
(setq ty (+ (cadr p0) (* dL (sin (* hzn (/ pi 180.0))))))
(setq *vfl-ziel-punkt* (list tx ty hn))
(setq cnt 0)
;; Koerper VOR dem Motor: winkel 0 = horizontales VF, sonst gewinkeltes VF.
;; typ "GF" -> kein Koerper (Rest laeuft komplett ueber GF2).
(cond
;; Schon flach + horizontales Mittelstueck: OHNE vfs-vf-koerper
;; (das wuerde unbedingt auf_3+ab_3 einbauen) direkt als reine
;; Verlaengerung der bereits flachen Zone bauen - kein
;; ueberfluessiges Bogenpaar. Frame bleibt flach; der spaeter
;; ohnehin noetige Uebergang auf 3 Grad vor der Motorstation
;; (Ausgang in vfl-vf-einheit) erledigt den einzigen wirklich
;; noetigen Bogen.
((and schon-flach (= typ "VF") (= w 0) lvf (> lvf 1.0))
(setq frame (make-frame-from-dir
(insert-inclined-scaled-block "Staustrecke_SP_1000_mm" (car frame) lvf 0 hzn)
(hz-winkel->xu hzn 0.0)))
(vfl-acc-vf-seg "horizontal" 0 lvf)
(setq cnt (1+ cnt)))
;; Normalfall: gewinkelter ODER horizontaler Koerper - vfs-vf-koerper
;; braucht dafuer zwingend eine 3-Grad-Basis als Eingang. War der
;; Frame noch flach (schon-flach, aber ein echter Winkel w<>0
;; noetig), wird hier genau EINMAL konvertiert - kein Vorgriff mehr
;; am Funktionsanfang, daher kein doppeltes Bogenpaar.
;; BEKANNTE LUECKE: vfl-nach-3grad deckt nur flach->fallend (0->+3)
;; ab. Kommt der Frame hier STEIGEND an (-3 Grad, z.B. direkt nach
;; einer per Zweig C gebauten steigenden GF OHNE zwischenzeitliches
;; Kettenende), waere ein 6-Grad-Uebergang (vfl-bogen-uebergang,
;; wie im Linie-GF-Abschluss) noetig, den es hier noch NICHT gibt -
;; der Frame bliebe faelschlich steigend, vfs-vf-koerper baut dann
;; auf falscher Basis. schon-flach deckt nur den (haeufigeren)
;; flachen Fall ab (siehe berechne-alle-winkel/entry-dx/entry-dz).
((and (= typ "VF") lvf (> lvf 1.0))
(setq frame (vfl-nach-3grad frame))
(setq bau-erg (vfl-baue-vf-koerper-mit-fuellstueck frame richtn w lvf lhor position hzn))
(setq frame (car bau-erg) cnt (+ cnt (cadr bau-erg))))
)
;; ohne Wirkung: letzt-hz ist lokal und wird nicht mehr gelesen -
;; die Fahrtrichtung verlaesst die Funktion ueber die Rueckgabe.
;; Beim Umzug bewusst wortwoertlich uebernommen.
(setq letzt-hz hzn)
(vfl-20m-check p-umlenk (car frame))
;; GF2-Laenge (hinter dem Motor): aus der Zerlegung (variiert mit Winkel),
;; sonst (typ "GF") aus der 3-Grad-Geometrie abgeleitet. Mindestwert 400.
(setq gf2
(cond ((and lgf (> lgf 0.0)) lgf)
((= typ "GF")
(max 0.0 (- (/ (- dL (abs (if ein-dx ein-dx *vfk-as-es-fallback-dx*))) (cos rad3)) *vfk-feste-horizontal-kettenende*)))
(t 0.0)))
;; Nicht mehr stillschweigend auffuellen (fuehrte zu einer teils
;; grossen Zielpunkt-Abweichung, siehe Ist-Ziel-Report) - stattdessen
;; fragen: mit dem exakt berechneten (kleineren) Wert bauen (trifft
;; den Zielpunkt exakt, Default) ODER auf die uebliche Mindestlaenge
;; auffuellen (Zielpunkt verschiebt sich entsprechend weiter).
(if (and (> gf2 0.0) (< gf2 *vfl-gf-min-laenge*))
(progn
(princ (ssg-textf "vfl-frage-gf2-minimum"
(list (rtos gf2 2 0) (rtos *vfl-gf-min-laenge* 2 0))))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-nein"))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-ja-nein-impl
(list (ssg-textf "vfl-frage-gf2-minimum"
(list (rtos gf2 2 0) (rtos *vfl-gf-min-laenge* 2 0))))))
(setq antwort-gf2 (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 1
"vfl-frage-gf2-minimum"))
(if (= antwort-gf2 "2") (setq gf2 *vfl-gf-min-laenge*))))
(dbgmsg (strcat "GEOMETRIE ERFOLG: Kettenende-Abschluss gebaut (cnt=" (itoa cnt)
" gf2=" (rtos gf2 2 0) "), neue Position=" (dbg-tostring (car frame))))
(list frame cnt hzn gf2)
)
)
)
;; --- Kettenende-Abschluss: Frage-Schale (Name unveraendert) ---
;; "Alles fragen, dann alles bauen" geht hier NICHT: vfl-neue-linie-messen
;; braucht den Frame NACH vfl-nach-3grad, und die Hoehenvorschlaege brauchen
;; die gemessene Laenge und die aktuelle Kettenhoehe. Darum in dieser
;; Reihenfolge: flache Zone abschliessen -> Laenge messen -> Vorschlaege
;; berechnen -> Zielhoehe fragen -> bauen.
;; vorab-ziel-punkt (optional, Default nil): der ABSOLUTE Zielpunkt (Liste
;; x y z) steht bereits fest - z.B. weil er schon VOR dem Winkel-Solve der
;; VF-Einheit gepickt wurde (siehe vfl-vf-einheit, vorab-antwort="3"). Dann
;; NICHT nochmal per vfl-neue-linie-messen/vfl-hoehe-endpunkt-abfragen fragen
;; (der sonst noetige, fehleranfaellige zweite Klick auf denselben Punkt -
;; Nutzer-Wunsch), sondern Restlaenge/-hoehe direkt aus der aktuellen
;; Frame-Position zu diesem Punkt herleiten (vfl-projiziere-distanz, dieselbe
;; Technik wie bei der AS-Restlaengen-Korrektur). letzt-hz bleibt dabei die
;; Fahrtrichtung (unveraendert seit dem urspruenglichen Pick).
(defun vfl-body-abschluss (frame letzt-hz p-umlenk es-gewuenscht vorab-ziel-punkt /
linie-mess dL hzn hn schon-flach ergebnis dH richtn
hoehe-vorschlaege hoehe-ab hoehe-auf gueltig)
;; Schon-flach VOR jeder Konvertierung feststellen - NICHT mehr blind auf
;; 3 Grad zwingen, bevor feststeht, ob die Zerlegung ohnehin ein
;; horizontales Mittelstueck ergibt (dann waere die Konvertierung + der
;; Wiedereintritt in die flache Zone via vfs-vf-koerper ein ueberfluessiges
;; auf_3/ab_3-Bogenpaar - siehe Kommentar bei vfl-winkel-stufe-entscheidung).
;; Die Positions-Messung selbst funktioniert unabhaengig von der Neigung.
(setq schon-flach (vfl-frame-flach-p frame))
(setq gueltig t)
(if vorab-ziel-punkt
(setq dL (vfl-projiziere-distanz (car frame) vorab-ziel-punkt letzt-hz)
hzn letzt-hz
hn (caddr vorab-ziel-punkt))
(progn
(setq linie-mess (vfl-neue-linie-messen (car frame) letzt-hz))
(if (null linie-mess)
(setq gueltig nil)
(progn
(setq dL (car linie-mess) hzn (cadr linie-mess))
;; Baubare Vorschlaege (Ab/Auf) statt der unveraenderten Kettenhoehe
;; als Default - sonst waere dH=0 ("Auf", da (>= 0.0 0.0)) und der
;; Rest wird von vfl-body-zerlegung sofort als "nicht baubar"
;; abgelehnt (kleineres Budget als vfl-vf-entscheidung: nur
;; Motor+Separator, 800mm statt 1300mm) - siehe
;; vfl-body-hoehe-vorschlaege/vfl-body-machbar-p.
(setq hoehe-vorschlaege (vfl-body-hoehe-vorschlaege (caddr (car frame)) dL es-gewuenscht schon-flach))
(setq hoehe-ab (nth 0 hoehe-vorschlaege) hoehe-auf (nth 1 hoehe-vorschlaege))
(setq hn (vfl-hoehe-endpunkt-abfragen hoehe-ab hoehe-auf (caddr (car frame)) "kettenende"))
)
)
)
)
;; Nutzer-Entscheidung (2026-09-15): dL/hzn bleiben unveraendert entlang der
;; urspruenglich gemessenen/vorgegebenen Fahrtrichtung - KEINE Drehung mehr,
;; um den seitlichen Fussabdruck eines drehenden ES-Elements (ein-dy)
;; auszugleichen (siehe vfl-richtung-mit-drehendem-fussabdruck, jetzt nicht
;; mehr hier verwendet). Ein drehendes ES-Element (30/90 Grad) landet
;; dadurch mit einem natuerlichen, unvermeidbaren seitlichen Versatz neben
;; dem Zielpunkt - erwartetes Verhalten (Hauptstrang bleibt gerade), keine
;; zu behebende Abweichung.
(if (not gueltig)
(progn (princ (ssg-text "vfl-abgebrochen-kein-ziel")) nil)
(progn
(setq ergebnis (vfl-body-abschluss-bauen frame p-umlenk dL hzn hn schon-flach es-gewuenscht))
;; Klare Rueckmeldung statt nur der einfachen Bau-Fehlermeldung (die
;; steht schon in vfl-body-abschluss-bauen): die tatsaechlich gewaehlte
;; Zielhoehe + Strecke nennen, UND (falls vorhanden) die bereits VOR der
;; Hoehe-Abfrage berechneten baubaren Vorschlaege (hoehe-ab/hoehe-auf)
;; hier NOCHMAL zeigen - der Nutzer sieht sie sonst nur einmal vor der
;; Eingabe und muss beim Fehler wieder hochscrollen. hoehe-ab/hoehe-auf
;; sind nur hier (Frage-Schale) bekannt, darum die Zusatzmeldung hier
;; statt im reinen Bauteil.
(if (null ergebnis)
(progn
(setq dH (- hn (caddr (car frame))) richtn (if (>= dH 0.0) "Auf" "Ab"))
(setq dH (abs dH))
(if (or hoehe-ab hoehe-auf)
(princ (ssg-textf "vfl-fehler-rest-nicht-baubar-mit-vorschlag"
(list (rtos hn 2 0) (rtos dL 2 0) (rtos dH 2 0) richtn
(cond
((and hoehe-ab hoehe-auf)
(ssg-textf "vfl-vorschlag-ab-auf"
(list (rtos hoehe-ab 2 0) (rtos hoehe-auf 2 0))))
(hoehe-ab (ssg-textf "vfl-vorschlag-nur-ab" (list (rtos hoehe-ab 2 0))))
(t (ssg-textf "vfl-vorschlag-nur-auf" (list (rtos hoehe-auf 2 0))))))))
(princ (ssg-textf "vfl-fehler-rest-nicht-baubar-ohne-vorschlag"
(list (rtos hn 2 0) (rtos dL 2 0) (rtos dH 2 0) richtn))))
;; Kurze 2-Punkte-Zusammenfassung zusaetzlich als Meldung (nicht nur
;; im Konsolen-Log) - zweiter Punkt nur, wenn Stufe 2 ueberhaupt
;; versucht wurde (siehe *vfl-stufe2-min*-vorschlag*, von
;; vfl-stufe2-diagnose gesetzt). vfl-meldung statt alert: im Bau-Pfad
;; darf keine modale Box einen Batch-/Headless-Lauf blockieren (zeigt
;; das Alert nur bei erlaubter GUI, sonst princ - siehe vfl-meldung).
(vfl-meldung
(strcat
(ssg-textf "vfl-alert-kettenende-p1" (list (rtos dL 2 0) (rtos dH 2 0) richtn))
(cond
((and *vfl-stufe2-minl-vorschlag* *vfl-stufe2-minh-vorschlag*)
(ssg-textf "vfl-alert-kettenende-p2-beide"
(list (rtos *vfl-stufe2-minl-vorschlag* 2 0) (rtos *vfl-stufe2-minh-vorschlag* 2 0))))
(*vfl-stufe2-minh-vorschlag*
(ssg-textf "vfl-alert-kettenende-p2-nur-minh" (list (rtos *vfl-stufe2-minh-vorschlag* 2 0))))
(*vfl-stufe2-minl-vorschlag*
(ssg-textf "vfl-alert-kettenende-p2-nur-minl" (list (rtos *vfl-stufe2-minl-vorschlag* 2 0))))
(t ""))))
)
)
ergebnis
)
)
)
;; --- VF-Einheit: Eingang (reiner Bauteil) ---
;; GF1 + Einlauf-Separator + Umlenkstation. Keine Frage, kein Dialog.
;; Rueckgabe: neuer Frame (Ausgang der Umlenkstation, 3-Grad-Basis).
;; Der Separator-Zaehler wird hier erhoeht, weil vfs-vf-entry den Separator
;; mitbaut - die *vfl-acc-*-Aufrufe muessen an derselben Stelle in derselben
;; Reihenfolge bleiben, sonst vertauschen sich die Komma-Listen L_VF_m/L_GF_m
;; im Sivas-Export (in der Zeichnung unsichtbar).
(defun vfl-vf-eingang-bauen (frame hz1 L_GF1-bau eingang-modus)
(setq frame (vfl-frame-3grad
(vfs-vf-entry (car frame) L_GF1-bau hz1 (equal eingang-modus "separator"))
hz1))
(if (> L_GF1-bau 0.1)
(vfl-acc-gf-seg L_GF1-bau (ssg-cfg-or "vario" "gefaelle_winkel" 3)))
(if (not (equal eingang-modus "separator"))
(setq *vfl-acc-separator* (1+ *vfl-acc-separator*))) ; Einlauf-Separator (in vfs-vf-entry)
frame)
;; --- VF-Einheit: Ausgang (reiner Bauteil) ---
;; Motorstation [+ GF2], KEIN Separator - der sitzt erst vor dem ES-Element
;; bzw. optional zwischen zwei Foerderern.
;; gf2-eff kommt FERTIG herein (siehe Aufrufstelle).
(defun vfl-vf-ausgang-bauen (frame letzt-hz gf2-eff)
;; Motorseite erfassen: derzeit immer "rechts" (nur diese DWG vorhanden;
;; die "links"-Einzel-DWG wird spaeter ergaenzt).
(setq *vfl-acc-motorseite* (append *vfl-acc-motorseite* (list "rechts")))
;; Motorstation braucht 3-Grad-Basis: falls die Kette gerade flach endet
;; (horizontales Stueck ohne ab_3), hier den ab_3-Uebergang nachholen.
(setq frame (vfl-nach-3grad frame))
(setq frame (vfl-frame-3grad
(vfs-vf-exit (car frame) gf2-eff letzt-hz nil) letzt-hz))
(if (> gf2-eff 0.1)
(vfl-acc-gf-seg gf2-eff (ssg-cfg-or "vario" "gefaelle_winkel" 3)))
frame)
;; Rueckgabe: (frame anzahl-koerper ziel-ende)
;; vorab-antwort/vorab-es-gewuenscht/vorab-es-seite (optional, Default nil):
;; wurde "Ist der Endpunkt der Foerderer?" (+ ggf. "ES-Element setzen?") vom
;; Aufrufer bereits VOR diesem Aufruf gestellt (siehe vf-linienzug-modus,
;; "Linie-VF"-Zweig) - dann NICHT in der Fortsetzungsschleife nochmal fragen,
;; sondern die Antwort direkt fuer die ERSTE Runde uebernehmen. Nur fuer den
;; geneigten Erstkoerper relevant (winkel1<>0) - der horizontale Fall
;; (winkel1=0) hat sein eigenes vor-antwort ueber vfl-baue-horizontal-koerper.
;; vorab-ziel-punkt (nur bei vorab-antwort="3" relevant): der bereits VOR dem
;; Winkel-Solve absolut gepickte Zielpunkt (x y z) - wird 1:1 an
;; vfl-body-abschluss durchgereicht, damit dort NICHT nochmal ein zweiter
;; Endpunkt abgefragt wird (Nutzer-Wunsch: "der zweite Klick ist unnoetig").
;; L_hor1/L_hor1-position: nur bei einem Stufe-3-Treffer fuer das ERSTE
;; Koerper-Sub-Segment belegt (siehe vfl-vf-einheit-abschluss), sonst nil.
;; Die interne "weiterbauen"-Fortsetzung (ent aus vfl-segment-entscheidung,
;; s.u.) hat ihr EIGENES, unabhaengiges lhor/position - andere Entscheidung,
;; anderer Solve-Aufruf.
(defun vfl-vf-einheit (frame hz1 richtung1 winkel1 L_GF1 L_VF1 L_hor1 L_hor1-position
gf-am-ausgang eingang-modus
vorab-antwort vorab-es-gewuenscht vorab-es-seite vorab-ziel-punkt /
p-umlenk vf-count antwort L_GF-eff L_GF1-bau L_GF2-bau
linie-mess dL dH hn hzn richtn ent typ w lgf lvf lhor position bau-erg
letzt-hz fertig ziel-ende ziel-gf2 res3 gf2-eff entry-start
res-h vor-antwort es-gewuenscht erg-es save-eindx save-eindz
hoehe-vorschlaege hoehe-ab hoehe-auf
schon-flach winkel-hoehen-erg es-seite)
;; Vorbelegung: ES gewuenscht (Default) - nur bei "3 - Kettenende definieren"
;; wird explizit gefragt und ggf. auf nil gesetzt (siehe dort).
(setq es-gewuenscht t)
;; Gesamte Staustrecke (mind. *vfl-gf-min-laenge*) auf Einlauf/Ausgang
;; verteilen - AUSSER eingang-modus "gf"/"separator"/"keine": dort ist kein
;; GF1-Stumpf noetig (siehe vfl-eingang-modus-bestimmen).
(if (member eingang-modus '("gf" "separator" "keine"))
(setq L_GF-eff 0.0)
(setq L_GF-eff (max L_GF1 *vfl-gf-min-laenge*)))
(if gf-am-ausgang
(setq L_GF1-bau (/ L_GF-eff 2.0) L_GF2-bau (/ L_GF-eff 2.0)) ; halbe/halbe
(setq L_GF1-bau L_GF-eff L_GF2-bau 0.0)) ; alles am Einlauf
;; --- Eingang: [GF1] + [Separator] + Umlenkstation ---
;; entry-start/p-umlenk sind der Stand VOR bzw. NACH dem Eingang - die
;; horizontale Erstkoerper-Rechnung braucht beide (gemessener Fussabdruck).
;; eingang-modus "keine": Vorgaenger ist bereits Teil einer virtuellen
;; VF-Kette OHNE eigene Umlenkstation (z.B. Vario-Kurve als erstes
;; Kettenglied) - eine zweite Umlenkstation waere mechanisch sinnlos, der
;; Koerper knuepft direkt am uebergebenen frame an.
(setq entry-start (car frame))
(if (equal eingang-modus "keine")
(setq p-umlenk (car frame))
(progn
(setq frame (vfl-vf-eingang-bauen frame hz1 L_GF1-bau eingang-modus))
(setq p-umlenk (car frame))
)
)
;; --- Erstes Koerper-Sub-Segment ---
;; winkel1=0 => horizontaler Anfang (Option 3): mit Separator-vor/nach-Abfrage.
;; L_VF1 wird bei winkel1=0 vom Aufrufer (Modus 1, Option 3) als GESAMT-
;; Zielstrecke ab entry-start (dem gepickten Endpunkt) verstanden - abgezogen
;; werden daher:
;; 1) der reale Eingang-Fussabdruck (GF1+Separator+Umlenkstation, GEMESSEN
;; statt geschaetzt, ueber entry-start/p-umlenk)
;; 2) der Fussabdruck fuer einen MOEGLICHEN sofortigen Abschluss direkt
;; nach diesem Stueck: Vario_Bogen_ab_3 (Ausgangs-Uebergang zurueck auf
;; 3 Grad, Tabellen-Mass wie in vfl-nach-3grad) + Motorstation (500mm).
;; Falls die Kette hier NICHT sofort endet, wird dieser Fussabdruck
;; trotzdem reserviert - konsistent mit dem "Kettenende"-Verhalten an
;; anderer Stelle (lieber vorsichtig reservieren als ueberschiessen).
(if (= (fix winkel1) 0)
(progn
;; ziel-modus=T: Separator VOR/NACH + "Ist der Endpunkt der Foerderer?"
;; werden INNERHALB von vfl-baue-horizontal-koerper VOR der Laengen-
;; berechnung gestellt (siehe dortiger Kommentar) - die Antwort kommt
;; hier zurueck, damit die Schleife unten sie nicht nochmal erfragt.
(setq res-h (vfl-baue-horizontal-koerper frame hz1
(max *vfk-restlaenge-min-clamp* (- L_VF1 (vfl-projiziere-distanz entry-start p-umlenk hz1)))
t L_GF2-bau))
(setq frame (car res-h) vor-antwort (cadr res-h) vf-count 1)
)
(progn
;; vfs-vf-koerper braucht die 3-Grad-Basis als Eintritt - im Normalfall
;; liefert die Umlenkstation die bereits (vfl-nach-3grad ist dann No-op);
;; bei eingang-modus "keine" kann frame noch flach sein (z.B. direkt
;; nach einer Vario-Kurve) und muss hier erst konvertiert werden.
(setq frame (vfl-nach-3grad frame))
(setq bau-erg (vfl-baue-vf-koerper-mit-fuellstueck frame richtung1 winkel1 L_VF1 L_hor1 L_hor1-position hz1))
(setq frame (car bau-erg) vf-count (cadr bau-erg))
(setq vor-antwort vorab-antwort)
)
)
(setq letzt-hz hz1)
(vfl-20m-check p-umlenk (car frame))
;; --- Fortsetzungs-Schleife bis Foerderer-Ende ---
(setq fertig nil)
(while (not fertig)
;; War die Frage schon in vfl-baue-horizontal-koerper (ziel-modus) oder
;; bei der letzten Runde "Horizontaler Foerderer" beantwortet, hier NICHT
;; erneut fragen - sonst normal abfragen.
(if vor-antwort
(setq antwort vor-antwort vor-antwort nil)
(setq antwort (vfl-frage-ist-endpunkt))
)
(if (= antwort "1")
(setq fertig t)
(if (= antwort "3")
;; --- Option 3: Kettenende exakt am Zielpunkt (nur EIN Motor) ---
(progn
;; ES-Element hier gewuenscht (+ falls ja: Winkel/Seite) - kombinierter
;; Dialog wie AS-Element (vflw-gruppe-as-impl). Winkel/Seite werden
;; JETZT (nicht erst spaeter in vfl-vf-einheit-abschluss) erfragt, weil
;; sie den tatsaechlichen ES-Fussabdruck (ein-dx/ein-dz, per
;; vfl-es-masse-setzen INNERHALB von vfl-frage-es-seite gesetzt)
;; bestimmen - der muss VOR vfl-body-abschluss feststehen, sonst
;; rechnet die Streckenzerlegung mit dem falschen (alten) Fussabdruck
;; und die Kette trifft den Zielpunkt nicht exakt. Falls nein: ein-dx/
;; ein-dz waehrend der Zerlegung temporaer nullen, damit KEIN
;; ES-Fussabdruck reserviert wird - die Kette endet dann direkt am
;; Zielpunkt ohne Separator+ES.
;; War das schon VOR dem Winkel-Solve entschieden (vorab-antwort="3",
;; siehe Funktionskopf) - dann NICHT nochmal fragen (waere doppelt
;; journalisiert), ein-dx/ein-dz NICHT anfassen (stehen bereits korrekt,
;; der Aufrufer hat sie vor dem Solve gesetzt) UND vfl-body-abschluss
;; GAR NICHT mehr aufrufen: die schon VOR diesem Solve bekannte
;; Fahrtrichtungs-Korrektur (vfl-richtung-mit-drehendem-fussabdruck im
;; Aufrufer) macht den ersten Winkel-Solve bereits vollstaendig ES-
;; bewusst (laengs UND quer) - die dabei schon berechnete GF2-Laenge
;; (L_GF2-bau, aus der GF-Verteilungs-Frage) trifft den Zielpunkt daher
;; bereits exakt. Eine zweite, unabhaengige Loesung (vfl-body-abschluss)
;; war nur noetig, SOLANGE der erste Solve das ES-Element noch nicht
;; kannte - real beobachteter Bug: genau diese zweite, unabhaengige
;; Loesung konnte scheitern (z.B. "kein passender Winkel"), obwohl die
;; ERSTE Wahl bereits eine vollstaendig gueltige Gesamtloesung war.
(if (equal vorab-antwort "3")
(setq es-gewuenscht vorab-es-gewuenscht es-seite vorab-es-seite
ziel-gf2 L_GF2-bau ziel-ende t fertig t)
(progn
(setq erg-es (vfl-es-element-abfragen))
(setq es-gewuenscht (nth 0 erg-es) es-seite (nth 1 erg-es)
save-eindx (nth 2 erg-es) save-eindz (nth 3 erg-es))
(setq res3 (vfl-body-abschluss frame letzt-hz p-umlenk es-gewuenscht nil))
(if (not es-gewuenscht) (setq ein-dx save-eindx ein-dz save-eindz))
(if res3
(setq frame (nth 0 res3)
vf-count (+ vf-count (nth 1 res3))
letzt-hz (nth 2 res3)
ziel-gf2 (nth 3 res3)
ziel-ende t
fertig t))))
)
(progn
(princ (ssg-text "vfl-naechstes-element-vf"))
(princ (ssg-text "vfl-opt-horizontaler-foerderer"))
(princ (ssg-text "vfl-opt-vario-kurve"))
(princ (ssg-text "vfl-opt-auf-ab-foerderer"))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-naechstes-im-vf-impl nil))
(setq antwort (vfl-menu (ssg-text "vfl-prompt-wahl-1-3-def3")
(list (ssg-text "vfl-opt-horizontaler-foerderer")
(ssg-text "vfl-opt-vario-kurve")
(ssg-text "vfl-opt-auf-ab-foerderer")) 3
"vfl-naechstes-element-vf"))
(cond
;; --- Vario-Kurve (aendert hz) ---
((= antwort "2")
(setq frame (vfl-insert-vario-kurve frame))
(setq letzt-hz (car (frame->hz-winkel frame)))
)
;; --- Horizontaler Foerderer (folgt der aktuellen Fahrtrichtung) ---
((= antwort "1")
(setq linie-mess (vfl-neue-linie-messen (car frame) letzt-hz))
(if linie-mess
(progn
(setq dL (car linie-mess) hzn (cadr linie-mess))
(if (> dL 1.0)
(progn
;; ziel-modus=T: Separator VOR/NACH + "Ist der Endpunkt der
;; Foerderer?" werden VOR der Laengenberechnung gestellt
;; (siehe vfl-baue-horizontal-koerper) - Antwort kommt hier
;; zurueck und wird in der naechsten Schleifen-Runde
;; verwendet, statt erneut zu fragen.
(setq res-h (vfl-baue-horizontal-koerper frame hzn dL t L_GF2-bau))
(setq frame (car res-h) vor-antwort (cadr res-h))
(setq vf-count (1+ vf-count) letzt-hz hzn)
(vfl-20m-check p-umlenk (car frame))
)
(princ (ssg-text "vfl-linie-zu-kurz-uebersprungen"))
)
)
)
)
;; --- Auf/Ab-Foerderer (folgt der aktuellen Fahrtrichtung) ---
(t
;; Punkt 7: kommt die Kette hier bereits FLACH an (z.B. nach
;; Horizontal-VF/Vario-Kurve), gibt es keine sinnvolle Zielhoehe zum
;; Anpeilen - Auf/Ab wird dann direkt erfragt und aus einer
;; Winkel/Hoehe-Tabelle gewaehlt (vfl-waehle-winkel-hoehen-tabelle),
;; statt eine Zielhoehe abzufragen. schon-flach MUSS vor der
;; vfl-nach-3grad-Wandlung ermittelt werden.
(setq schon-flach (vfl-frame-flach-p frame))
;; gewinkeltes VF braucht 3-Grad-Basis -> flache Zone ggf. mit ab_3 beenden
(setq frame (vfl-nach-3grad frame))
(setq linie-mess (vfl-neue-linie-messen (car frame) letzt-hz))
(if (null linie-mess) (progn (princ (ssg-text "vfl-abgebrochen")) (exit)))
(if linie-mess
(progn
(setq dL (car linie-mess) hzn (cadr linie-mess))
(if (> dL 1.0)
(if schon-flach
(progn
(princ (ssg-text "vfl-frage-auf-ab-richtung"))
(princ (ssg-text "vfl-opt-richtung-auf"))
(princ (ssg-text "vfl-opt-richtung-ab"))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-richtung-impl
(list (ssg-text "vfl-frage-auf-ab-richtung"))))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-opt-richtung-auf")
(ssg-text "vfl-opt-richtung-ab")) 1
"vfl-frage-auf-ab-richtung"))
(setq richtn (if (= antwort "2") "Ab" "Auf"))
(setq winkel-hoehen-erg
(vfl-waehle-winkel-hoehen-tabelle dL richtn (caddr (car frame))))
(if winkel-hoehen-erg
(progn
(setq w (nth 0 winkel-hoehen-erg) lvf (nth 2 winkel-hoehen-erg))
;; Ist-Ziel-Vergleich: vorhergesagte Endhoehe sichern, um
;; nach dem echten Bau (vfs-vf-koerper, unabhaengig
;; verifiziert) auf Abweichung zu pruefen - Sicherheitsnetz
;; fuer die neue Formel, ohne die reale Geometrie zu beeinflussen.
(setq hn (+ (caddr (car frame)) (nth 1 winkel-hoehen-erg)))
(setq frame (vfl-frame-3grad (vfs-vf-koerper (car frame) richtn w lvf hzn) hzn))
(setq vf-count (1+ vf-count) letzt-hz hzn)
(vfl-acc-vf-seg richtn w lvf)
(vfl-20m-check p-umlenk (car frame))
(if (> (abs (- (caddr (car frame)) hn)) 5.0)
(princ (ssg-textf "vfl-winkel-hoehen-abweichung"
(list (rtos hn 2 0) (rtos (caddr (car frame)) 2 0)))))
)
(princ (ssg-text "vfl-linie-zu-kurz-simple"))
)
)
(progn
;; NUR-VF-Vorschlaege (nicht vfl-segment-hoehe-vorschlaege):
;; dieser Zweig setzt eine bestehende VF-Kette fort und lehnt
;; ein hier zurueckkommendes typ="GF" explizit ab (siehe
;; "vfl-segment-gf-nicht-erlaubt" unten) - der GF-Vorschlag
;; (3-Grad-Formel) waere hier also gerade NICHT baubar.
(setq hoehe-vorschlaege (vfl-vf-hoehe-vorschlaege (caddr (car frame)) dL))
(setq hoehe-ab (nth 0 hoehe-vorschlaege) hoehe-auf (nth 1 hoehe-vorschlaege))
(setq hn (vfl-hoehe-endpunkt-abfragen hoehe-ab hoehe-auf (caddr (car frame)) "endpunkt"))
(setq dH (- hn (caddr (car frame))))
(setq richtn (if (>= dH 0.0) "Auf" "Ab"))
(setq dH (abs dH))
(setq ent (vfl-segment-entscheidung dL dH richtn))
(setq typ (nth 0 ent) w (nth 1 ent) lgf (nth 2 ent) lvf (nth 3 ent)
lhor (nth 4 ent) position (nth 5 ent))
(if (and typ (= typ "VF"))
(progn
(setq bau-erg (vfl-baue-vf-koerper-mit-fuellstueck frame richtn w lvf lhor position hzn))
(setq frame (car bau-erg))
(setq vf-count (+ vf-count (cadr bau-erg)) letzt-hz hzn)
(vfl-20m-check p-umlenk (car frame))
)
(princ (ssg-text "vfl-segment-gf-nicht-erlaubt"))
)
)
)
(princ (ssg-text "vfl-linie-zu-kurz-simple"))
)
)
)
)
)
)
)
)
)
;; --- Ausgang: Motorstation [+ GF2 je nach Wahl], KEIN Separator ---
;; Der Separator sitzt erst vor dem ES-Element (Kettenende) bzw. optional
;; zwischen zwei Foerderern - nicht hier.
;; Motorseite erfassen: derzeit immer "rechts" (nur diese DWG vorhanden;
;; die "links"-Einzel-DWG wird spaeter ergaenzt).
;; Auslauf-GF2: im Kettenende-Modus (Option 3) neu berechnet (ziel-gf2),
;; sonst aus der GF-Verteilungs-Frage (L_GF2-bau). Die Auswahl steht
;; BEWUSST hier und nicht im Bauteil - sie ist die Stelle, an der sonst
;; still die falsche GF2 hinter dem Motor entsteht.
(setq gf2-eff (if ziel-ende ziel-gf2 L_GF2-bau))
(setq frame (vfl-vf-ausgang-bauen frame letzt-hz gf2-eff))
(list frame vf-count ziel-ende es-gewuenscht es-seite)
)
;; Rundet einen Winkel (Grad) auf das naechste 30-Grad-Vielfache DES WELT-
;; KOORDINATENSYSTEMS (absolut, nicht relativ zu einer Vorgaenger-Richtung).
;; Im System sind Fahrtrichtungen immer 30/60/90-Grad-Vielfache relativ zur
;; Zeichnung selbst - jede Abweichung (freier erster Klick, Bogen-Block-
;; Zeichnungsungenauigkeit) wird damit auf den naechsten gueltigen absoluten
;; Wert korrigiert, statt sich ueber die Kette aufzusummieren.
(defun vfl-hz-snappen-absolut (hz / n)
(setq n (/ hz 30.0))
(setq n (if (>= n 0.0) (fix (+ n 0.5)) (fix (- n 0.5))))
(* n 30.0)
)
;; Rotiert einen kompletten Frame (P xu yu zu) um die WELT-Z-Achse um delta
;; Grad - im Gegensatz zu (make-frame-from-dir P neue-xu) bleibt dabei die
;; urspruengliche Rollung (yu/zu, aus der echten Blockgeometrie gemessen)
;; erhalten. WICHTIG: make-frame-from-dir erzeugt yu/zu nach einer generischen
;; Konvention, die bei Bloecken mit eigener, nicht-generischer Ausrichtung
;; (z.B. Vario-Kurve) NICHT zur tatsaechlichen Verkettung passt - das fuehrte
;; empirisch zu einer um 90 Grad verdrehten Motorstation nach einem gesnappten
;; horizontalen Bogen. Eine reine Z-Drehung (nur X/Y von xu/yu/zu betroffen,
;; Z-Komponente unveraendert) behebt die winzige Snapping-Abweichung, ohne die
;; Rollung anzutasten.
(defun vfl-vec-um-z-drehen (v c s)
(list (- (* (car v) c) (* (cadr v) s))
(+ (* (car v) s) (* (cadr v) c))
(caddr v))
)
(defun vfl-frame-um-z-drehen (frame delta / rad c s)
(setq rad (* (float delta) (/ pi 180.0)))
(setq c (cos rad) s (sin rad))
(list (car frame)
(vfl-vec-um-z-drehen (cadr frame) c s)
(vfl-vec-um-z-drehen (caddr frame) c s)
(vfl-vec-um-z-drehen (cadddr frame) c s))
)
;; GF-Bogen (horizontale Kurve, Neigung bleibt wie im aktuellen Frame).
;; Fragt Winkel (30/60/90) und Seite interaktiv ab.
;; Nicht-interaktiver Kern: GF-Bogen mit gegebenem Winkel/Seite einfuegen.
;; Genutzt von der interaktiven Abfrage UND von den Pfad-Modi (Winkel/Seite
;; aus der Geometrie). Rueckgabe: neuer Frame.
(defun vfl-insert-gf-bogen-block (frame bwinkel bseite / blockname neuer-frame hz-gemessen hz-gesnappt)
(setq blockname (gf-bogen-blockname bwinkel bseite))
(princ (ssg-textf "vfl-fuege-block-ein" (list blockname)))
(dbgmsg (strcat "GEOMETRIE: vfl-insert-gf-bogen-block (block=" blockname
" bwinkel=" (itoa bwinkel) " bseite=" bseite ")"))
;; GF-Bogen zaehlen (Seite L/R + Winkel) -> Attribut GF_Bogen_L/R_xx
(setq *vfl-acc-gfbogen*
(vfl-inc-count *vfl-acc-gfbogen*
(strcat (if (= bseite "rechts") "R" "L") "_" (itoa bwinkel))))
(setq neuer-frame (insert-block-ks-to-ks blockname frame))
(if (null neuer-frame)
(dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Block " blockname " nicht eingefuegt"))
(progn
(setq hz-gemessen (car (frame->hz-winkel neuer-frame)))
(setq hz-gesnappt (vfl-hz-snappen-absolut hz-gemessen))
;; Reine Z-Drehung statt make-frame-from-dir-Neuaufbau: erhaelt die aus dem
;; Block gemessene Rollung (yu/zu) - siehe Kommentar bei vfl-frame-um-z-drehen.
(if (> (abs (- hz-gemessen hz-gesnappt)) 0.0001)
(setq neuer-frame (vfl-frame-um-z-drehen neuer-frame (- hz-gesnappt hz-gemessen))))
(dbgmsg (strcat "GEOMETRIE ERFOLG: GF-Bogen gebaut, neue Position="
(dbg-tostring (car neuer-frame))))
)
)
neuer-frame
)
;; Interaktiv (i18n): GF-Bogen-Winkel + Seite abfragen, dann Kern aufrufen.
(defun vfl-insert-gf-bogen (frame / bwinkel bseite antwort)
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-winkel-seite-impl
(list "vfl-gf-bogen-winkel-header"
(list (ssg-text "vfl-winkel-30") (ssg-text "vfl-winkel-60") (ssg-text "vfl-winkel-90"))
*vfk-gf-bogen-winkel*)))
(princ (ssg-text "vfl-gf-bogen-winkel-header"))
(princ (ssg-text "vfl-winkel-30"))
(princ (ssg-text "vfl-winkel-60"))
(princ (ssg-text "vfl-winkel-90"))
(setq bwinkel (vfl-menu-winkel (ssg-text "vfl-prompt-wahl-winkel-30-60-90")
(list (ssg-text "vfl-winkel-30") (ssg-text "vfl-winkel-60")
(ssg-text "vfl-winkel-90"))
*vfk-gf-bogen-winkel* 90 "vfl-gf-bogen-winkel-header"))
(princ (ssg-text "vfl-gf-bogen-seite-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
"vfl-gf-bogen-seite-header"))
(setq bseite (if (= antwort "2") "rechts" "links"))
(vfl-insert-gf-bogen-block frame bwinkel bseite)
)
;; Vario-Kurve-Blockname (links/rechts x 30/60/90 x TEF aussen/innen).
(defun vfl-kurve-blockname (kwinkel kseite kvariante)
(strcat "Vario_Kurve_" kseite "_" (itoa kwinkel) "_TEF_" kvariante)
)
;; Nicht-interaktiver Kern: Vario-Kurve mit gegebenem Winkel/Seite/Variante
;; einfuegen. Genutzt von der interaktiven Abfrage UND von den Pfad-Modi
;; (Winkel/Seite aus der Geometrie, Variante abgefragt). Rueckgabe: neuer Frame.
;; Standard sind KS_EIN/KS_AUS; historische Bestandsbloecke koennen noch
;; KSYS_EIN/KSYS_AUS fuehren - extract-ks-from-block-raw normalisiert das
;; (siehe ks-normalize-name).
(defun vfl-insert-vario-kurve-block (frame kwinkel kseite kvariante /
blockname hz flach-frame neuer-frame
hz-gemessen hz-gesnappt)
(setq blockname (vfl-kurve-blockname kwinkel kseite kvariante))
(princ (ssg-textf "vfl-fuege-block-ein" (list blockname)))
;; Vario-Kurve zaehlen (Variante A=aussen/I=innen + Winkel) -> VF_Bogen_A/I_xx
(setq *vfl-acc-variokurve*
(vfl-inc-count *vfl-acc-variokurve*
(strcat (if (= kvariante "aussen") "A" "I") "_" (itoa kwinkel))))
;; Vario-Kurve ist ein HORIZONTALER Richtungswechsel (KEINE Neigung).
;; Der aktuelle Frame traegt die 3-Grad-Basisneigung - fuer die Kurve wird er
;; daher auf 0 Grad abgeflacht (Position + Fahrtrichtung bleiben erhalten).
(setq hz (car (frame->hz-winkel frame)))
(setq flach-frame (make-frame-from-dir (car frame) (hz-winkel->xu hz 0.0)))
(setq neuer-frame (insert-block-ks-to-ks blockname flach-frame))
;; Auf den absoluten 30-Grad-Raster snappen (siehe vfl-hz-snappen-absolut) -
;; gleiche Zeichnungsungenauigkeit wie bei GF-Bogen moeglich. Reine Z-Drehung
;; statt make-frame-from-dir-Neuaufbau: erhaelt die aus dem Block gemessene
;; Rollung (yu/zu) - siehe Kommentar bei vfl-frame-um-z-drehen. Ein Neuaufbau
;; ueber make-frame-from-dir fuehrte hier empirisch zu einer um 90 Grad
;; verdrehten Motorstation NACH der Vario-Kurve.
(setq hz-gemessen (car (frame->hz-winkel neuer-frame)))
(setq hz-gesnappt (vfl-hz-snappen-absolut hz-gemessen))
(if (> (abs (- hz-gemessen hz-gesnappt)) 0.0001)
(setq neuer-frame (vfl-frame-um-z-drehen neuer-frame (- hz-gesnappt hz-gemessen))))
neuer-frame
)
;; Vario-Kurve (horizontale Kurve, Neigung bleibt wie im aktuellen Frame).
;; Standard sind KS_EIN/KS_AUS; historische Bestandsbloecke koennen noch
;; KSYS_EIN/KSYS_AUS fuehren - extract-ks-from-block-raw normalisiert das
;; (siehe ks-normalize-name).
;; Interaktiv (i18n): Vario-Kurve-Winkel + Seite + Variante abfragen, dann Kern.
;; Die Vario-Kurve bekommt - wie das ES-Element (vfl-frage-es-seite) - einen
;; EIGENEN STEP-Marker "Vario-Kurve", obwohl sie INNERHALB der VF-Einheit-
;; Schleife gebaut wird und keinen eigenen Hauptschleifen-Durchlauf hat. Damit
;; wird sie zu einem regulaeren, waehlbaren Glied und ist ueber die bestehende
;; Glied-Maschinerie (vfl-journal-glieder/-slice/-splice) editierbar. Der Marker
;; steht VOR den drei Eingaben (Winkel INT, Seite STR, Variante STR), sodass
;; diese als Glied-Inhalt darauf folgen. Beim Replay ueberspringt vfl-replay-pop
;; den Marker automatisch; die drei Werte poppen danach regulaer. Der Glied-Edit
;; liest allein aus dem Ketten-Journal (Segment-XDATA ist nur additive Deko),
;; daher ist KEIN Umbau der Segment-XDATA-Sicherung noetig.
(defun vfl-insert-vario-kurve (frame / kwinkel kseite kvariante antwort)
(vfl-journal-mark "Vario-Kurve")
(if (vfl-wizard-aktiv) (vl-catch-all-apply 'vflw-gruppe-variokurve-impl nil))
(princ (ssg-text "vfl-variokurve-winkel-header"))
(princ (ssg-text "vfl-opt3-30grad"))
(princ (ssg-text "vfl-opt2-60grad"))
(princ (ssg-text "vfl-opt1-90grad"))
(setq kwinkel (vfl-menu-winkel (ssg-text "vfl-prompt-wahl-winkel-30-60-90")
(list (ssg-text "vfl-opt3-30grad") (ssg-text "vfl-opt2-60grad")
(ssg-text "vfl-opt1-90grad"))
*vfk-gf-bogen-winkel* 90 "vfl-variokurve-winkel-header"))
(princ (ssg-text "vfl-variokurve-seite-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
"vfl-variokurve-seite-header"))
(setq kseite (if (= antwort "2") "rechts" "links"))
(princ (ssg-text "vfl-variokurve-variante-header"))
(princ (ssg-text "vfl-variante-aussen"))
(princ (ssg-text "vfl-variante-innen"))
(setq antwort (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-variante-aussen") (ssg-text "vfl-variante-innen")) 2
"vfl-variokurve-variante-header"))
(setq kvariante (if (= antwort "1") "aussen" "innen"))
(vfl-insert-vario-kurve-block frame kwinkel kseite kvariante)
)
;; ============================================================
;; TEIL 3: KETTENANFANG (AS) / KETTENENDE (ES)
;; ============================================================
;; AS-Element am Kettenanfang. Wird IMMER FLACH eingefuegt (KS_EIN ohne
;; Neigung) - per Einzel-Insert-Test in BricsCAD bestaetigt (2026-07-24):
;; das AS-Element selbst hat keine Neigung, die Neigung beginnt erst im
;; nachfolgenden GF-Koerper (vfl-insert-gf-segment). typ/winkel sind aus
;; Call-Kompatibilitaet weiterhin Parameter, werden aber fuer die AS-eigene
;; Ausrichtung nicht mehr verwendet (KEIN vertikaler Tilt mehr, unabhaengig
;; vom folgenden Segmenttyp).
;; Rueckgabe: Frame am AS-Ausgang (KS_AUS).
(defun vfl-insert-as-element (typ startpunkt hz winkel as-seite /
ein-hz-as rad-h xu-ein as-turn)
;; Schwenk aus dem Block messen -> KS_EIN so drehen, dass KS_AUS entlang hz
;; zeigt (Grundriss-Versatz des 30-Grad-Elements bleibt beruecksichtigt).
(setq as-turn (vf-element-plan-turn (strcat "AS_Element_" (vfl-as-winkel) "_" as-seite)))
(setq ein-hz-as (- hz as-turn))
(setq rad-h (* (float ein-hz-as) (/ pi 180.0)))
(setq xu-ein (list (cos rad-h) (sin rad-h) 0.0))
(insert-block-mixed-to-ks
(strcat "AS_Element_" (vfl-as-winkel) "_" as-seite)
(make-frame-from-dir startpunkt xu-ein)
(caddr startpunkt) nil "KS_EIN")
)
;; Kettenanfang-Baustein: falls as-vorhanden, AS-Element einfuegen und die
;; GF/VF-Restlaenge um den tatsaechlichen AS-Fussabdruck kuerzen. Falls NICHT
;; as-vorhanden (Nutzer hat "Kein AS-Element" gewaehlt), beginnt die Kette
;; direkt am Startpunkt - deltaL bleibt die gemessene Rohlaenge (kein
;; Alle seit checkpoint (Ergebnis von (entlast), NIL falls Zeichnung vorher leer
;; war) neu erzeugten Entities wieder loeschen - Gegenteil von
;; vfl-modus-abbruch-sichern (das wickelt statt zu loeschen). Genutzt, wenn ein
;; Baustein (z.B. VF-Einheit-Eingang) sich im Nachhinein als sinnlos erweist
;; (zu kurz, Nutzer verzichtet auf "trotzdem bauen") - erst alle enames
;; sammeln, dann loeschen (entdel veraendert waehrend der Iteration die
;; Datenbank-Reihenfolge, siehe SSG_LIB_Commands.lsp).
(defun vfl-loesche-seit (checkpoint / e liste)
(setq liste '())
(setq e (if checkpoint (entnext checkpoint) (entnext)))
(while e (setq liste (cons e liste)) (setq e (entnext e)))
(foreach e liste (entdel e))
)
;; KS_AUS-Ursprung UND -Richtung (hz-Winkel in Grad) eines platzierten Blocks -
;; Massstab-bereinigte Kopie wie vfl-kette-ks-ursprung (Explode auf einer
;; skalierungsneutralen Kopie, dann geloescht), zusaetzlich mit der KS_AUS-
;; Achsrichtung: die Laengen-Streckung (XScaleFactor) verschiebt nur den
;; KS_AUS-URSPRUNG entlang der Blocklaenge (gegenueber KS_EIN), die
;; Achsrichtung des kleinen KS_AUS-Unterblocks selbst bleibt davon unberuehrt
;; (an der skalierungsneutralen Kopie unverfaelscht messbar). Rueckgabe:
;; (punkt . hz) oder nil (kein KS_AUS gefunden).
(defun vfl-kette-ks-aus-richtung (block-obj / sx copyobj ksdata kez kaz-raw kax-raw kaz hz)
(setq sx (vla-get-XScaleFactor block-obj))
(setq copyobj (vla-Copy block-obj))
(vla-put-XScaleFactor copyobj 1.0)
(vla-put-YScaleFactor copyobj 1.0)
(vla-put-ZScaleFactor copyobj 1.0)
(setq ksdata (extract-ks-from-block-raw copyobj))
(if (not (vlax-erased-p copyobj)) (vl-catch-all-apply 'vla-Delete (list copyobj)))
(setq kez (if (assoc "KS_EIN" ksdata) (car (cadr (assoc "KS_EIN" ksdata))) nil))
(setq kaz-raw (if (assoc "KS_AUS" ksdata) (car (cadr (assoc "KS_AUS" ksdata))) nil))
(setq kax-raw (if (assoc "KS_AUS" ksdata) (cadr (cadr (assoc "KS_AUS" ksdata))) nil))
(if (and kez kaz-raw kax-raw)
(progn
(setq kaz
(if (/= sx 1.0)
(list (+ (car kez) (* sx (- (car kaz-raw) (car kez))))
(+ (cadr kez) (* sx (- (cadr kaz-raw) (cadr kez))))
(+ (caddr kez) (* sx (- (caddr kaz-raw) (caddr kez)))))
kaz-raw))
(setq hz (/ (* (atan (- (cadr kax-raw) (cadr kaz-raw)) (- (car kax-raw) (car kaz-raw))) 180.0) pi))
(cons kaz hz)
)
nil
)
)
;; Sucht am gegebenen Punkt (Toleranz 1mm) einen KS_AUS eines vorhandenen
;; Vario-Kette-Bausteins der Zeichnung (lose Teile UND VF_n-Wrapper - gleicher
;; Scan wie vfl-kette-sammle-alle/Vario_Kette_Merge) und liefert dessen
;; Richtung (hz in Grad). Genutzt fuer den Kettenanfang bei GF-Bogen/Vario-
;; Kurve OHNE AS-Element: setzt der Nutzer den Startpunkt exakt auf den KS_AUS
;; eines bestehenden Bausteins (z.B. Fortsetzung einer schon gebauten Kette),
;; soll die neue Kurve dessen Richtung uebernehmen statt der Welt-X-Achse.
;; Rueckgabe: (hz . typ) oder nil, wenn kein KS_AUS in Toleranz gefunden
;; wurde. typ ist das vfl-kette-typ-Ergebnis des GEFUNDENEN Bausteins (bei
;; einem Wrapper-Kind dessen EIGENER Blockname, nicht "WRAPPER") - Basis fuer
;; vfl-kette-typ-familie (GF/VF/uneindeutig), genutzt von
;; vfl-keine-umlenkstation-p.
(defun vfl-ks-aus-hz-an-punkt (pt / tol ss i ename bname typ obj kinder kind
erg bester-d d hz-treffer typ-treffer kind-bname kind-typ)
(setq tol 1.0)
(setq bester-d nil hz-treffer nil typ-treffer nil)
(setq ss (ssget "X" '((0 . "INSERT"))))
(if ss
(progn
(setq i 0)
(while (< i (sslength ss))
(setq ename (ssname ss i))
(setq bname (cdr (assoc 2 (entget ename))))
(setq typ (vfl-kette-typ bname))
(cond
((= typ "WRAPPER")
(setq obj (vlax-ename->vla-object ename))
(setq kinder (vlax-invoke obj 'Explode))
(foreach kind kinder
(if (and (not (vlax-erased-p kind))
(= (vla-get-ObjectName kind) "AcDbBlockReference"))
(progn
(setq kind-bname (vla-get-Name kind))
(setq kind-typ (vfl-kette-typ kind-bname))
(setq erg (vfl-kette-ks-aus-richtung kind))
(if erg
(progn
(setq d (distance pt (car erg)))
(if (and (< d tol) (or (null bester-d) (< d bester-d)))
(setq bester-d d hz-treffer (cdr erg) typ-treffer kind-typ))
)
)
)
)
)
(foreach kind kinder (if (not (vlax-erased-p kind)) (vla-Delete kind)))
)
((/= typ "UNBEKANNT")
(setq obj (vlax-ename->vla-object ename))
(setq erg (vfl-kette-ks-aus-richtung obj))
(if erg
(progn
(setq d (distance pt (car erg)))
(if (and (< d tol) (or (null bester-d) (< d bester-d)))
(setq bester-d d hz-treffer (cdr erg) typ-treffer typ))
)
)
)
)
(setq i (1+ i))
)
)
)
(if hz-treffer (cons hz-treffer typ-treffer) nil)
)
;; Grobe GF-/VF-Familie-Klassifikation eines vfl-kette-typ-Ergebnisses fuer die
;; Umlenkstations-Entscheidung am echten Kettenanfang (siehe
;; vfl-eingang-modus-bestimmen): AS/ES/STRECKE/SEP werden von BEIDEN Familien
;; genutzt (eine GF-Gerade UND das horizontale VF-Mittelstueck sind derselbe
;; Block "Staustrecke_SP_1000_mm") und bleiben daher absichtlich uneindeutig
;; (nil) - der Scan kann ein reines GF-Geraden-Ende grundsaetzlich NICHT vom
;; Ende eines VF-Horizontalstuecks unterscheiden.
(defun vfl-kette-typ-familie (typ)
(cond
((member typ '("WRAPPER" "KURVE" "UMLENK" "MOTOR" "BOGENAUF" "BOGENAB")) "VF")
((member typ '("GFBOGEN" "GFVERTBOGEN")) "GF")
(t nil)
)
)
;; Wie vfl-kette-typ-familie, aber fuer den SESSION-LOKALEN "letzter-typ"-
;; String (statt eines per Blockname erkannten XDATA-Typs) - fuer eine
;; laufende Fortsetzung (kettenanfang=nil) ist der Vorgaengertyp bereits
;; ohne KS_AUS-Scan bekannt. "Separator" zaehlt als GF-Familie (er folgt in
;; diesem Ablauf immer auf ein GF-Segment, siehe "Linie-GF"-Zweig).
;; WICHTIG: "VF" (eine gerade FERTIG gebaute VF-Einheit, Motorstation schon
;; eingefuegt) zaehlt hier BEWUSST NICHT zur "VF"-Familie - eine neue
;; VF-Einheit direkt danach braucht sehr wohl eine frische Umlenkstation
;; (siehe vfl-eingang-modus-bestimmen: nur "Vario-Kurve" mappt dort auf
;; eingang-modus="keine", "VF" fehlt dort bewusst und faellt auf den
;; Default/volle-Eingang-Fall zurueck). Nur "Vario-Kurve" hat keine eigene
;; Umlenkstation/keinen Abschluss - eine zweite waere mechanisch sinnlos.
(defun vfl-letzter-typ-familie (letzter-typ)
(cond
((equal letzter-typ "Vario-Kurve") "VF")
((member letzter-typ '("GF" "Separator")) "GF")
(t nil)
)
)
;; Eingang-Modus + L_GF-Stumpf fuer eine neue VF-Einheit bestimmen - EINE
;; gemeinsame Entscheidung fuer "Neue VF mit horizontalem Anfang" UND "Neue
;; Linie: Ab/Auf VF" (Punkt 5). Reihenfolge:
;; Fall 1 - letzter-typ="Separator"/"GF" (session-lokal bekannt, reduzierter
;; aber vorhandener Eingang - kein GF1-Stumpf, ggf. kein neuer Separator).
;; Fall 2 - letzter-typ="Vario-Kurve": die hat keine eigene Umlenkstation/
;; keinen Abschluss - eine zweite waere mechanisch sinnlos -> KEINE
;; Umlenkstation.
;; Fall 3 - echter Kettenanfang OHNE AS-Element (kettenanfang UND NICHT
;; as-vorhanden): am Startpunkt wird per KS_AUS-Scan (vfl-ks-aus-hz-an-
;; punkt) nachgesehen, was dort schon steht:
;; - eindeutig VF-Familie erkannt -> KEINE Umlenkstation.
;; - eindeutig GF-Familie (GF-Bogen) erkannt -> voller Eingang.
;; - NICHTS Eindeutiges (auch bei einer reinen GF-Geraden/VF-Horizontal-
;; stueck, die per Blockname nicht unterscheidbar sind - siehe
;; vfl-kette-typ-familie) -> der automatische Scan kann hier prinzipiell
;; nicht entscheiden, also EINE flache 3-Optionen-Frage an den Nutzer:
;; Option Eins - direkt Umlenkstation (kein GF-Stumpf); Option Zwei - GF-
;; Stumpf+Umlenkstation; Option Drei - ohne Umlenkstation (VF-Fortsetzung).
;; Fall 4 - sonst voller Eingang (GF1+Separator+Umlenkstation).
;; Rueckgabe: (lgf . eingang-modus).
(defun vfl-eingang-modus-bestimmen (letzter-typ kettenanfang as-vorhanden p-aktuell /
erg familie antwort)
(cond
((equal letzter-typ "Separator") (cons 0.0 "separator"))
((equal letzter-typ "GF") (cons 0.0 "gf"))
((equal letzter-typ "Vario-Kurve") (cons 0.0 "keine"))
((and kettenanfang (not as-vorhanden))
(setq erg (vfl-ks-aus-hz-an-punkt p-aktuell))
(setq familie (if erg (vfl-kette-typ-familie (cdr erg)) nil))
(cond
((equal familie "VF") (cons 0.0 "keine"))
((equal familie "GF") (cons *vfl-gf-min-laenge* nil))
(t
(princ (ssg-textf "vfl-frage-startpunkt-typ" (list (rtos *vfl-gf-min-laenge* 2 0))))
(princ (ssg-text "vfl-opt-eingang-direkt"))
(princ (ssg-textf "vfl-opt-eingang-mit-stumpf" (list (rtos *vfl-gf-min-laenge* 2 0))))
(princ (ssg-text "vfl-opt-eingang-ohne-umlenk"))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-startpunkt-typ-impl nil))
(setq antwort (vfl-menu (ssg-text "vfl-prompt-wahl-1-3-def1")
(list (ssg-text "vfl-opt-eingang-direkt")
(ssg-textf "vfl-opt-eingang-mit-stumpf" (list (rtos *vfl-gf-min-laenge* 2 0)))
(ssg-text "vfl-opt-eingang-ohne-umlenk")) 1
"vfl-frage-startpunkt-typ"))
(cond
((= antwort "2") (cons *vfl-gf-min-laenge* nil))
((= antwort "3") (cons 0.0 "keine"))
(t (cons 0.0 "gf"))
)
)
)
)
(t (cons *vfl-gf-min-laenge* nil))
)
)
;; Kettenanfang-Baustein: falls as-vorhanden, AS-Element einfuegen und die
;; GF/VF-Restlaenge um den tatsaechlichen AS-Fussabdruck kuerzen. Falls NICHT
;; as-vorhanden (Nutzer hat "Kein AS-Element" gewaehlt), beginnt die Kette
;; direkt am Startpunkt - deltaL bleibt die gemessene Rohlaenge (kein
;; Fussabdruck abzuziehen). Rueckgabe: (frame deltaL).
;;
;; deltaL kommt als reine, bereits auf hz-neu projizierte Distanz vom
;; UNVERAENDERTEN p-aktuell herein (siehe vfl-in-abstand/vfl-neue-linie-
;; messen - dort wird NIE mehr ein absoluter Punkt gefuehrt). Der reale
;; AS-Austritt (car frame) liegt exakt auf derselben Achse hz-neu (das
;; AS-Element wird flach IN hz-neu eingefuegt, siehe vfl-insert-as-element) -
;; die noetige Restlaenge ist damit einfach die urspruengliche Distanz MINUS
;; die tatsaechliche (gemessene) AS-Fussabdrucklaenge, keine Punkt-Projektion
;; noetig (Ersatz fuer das fruehere vfl-projiziere-distanz auf pick-punkt).
(defun vfl-kettenanfang-baustein (p-aktuell hz-neu deltaL as-seite as-vorhanden / frame fussabdruck)
(if as-vorhanden
(progn
(setq frame (vfl-insert-as-element "GF" p-aktuell hz-neu 0.0 as-seite))
(setq fussabdruck (vfl-planar-dist p-aktuell (car frame)))
(setq deltaL (- deltaL fussabdruck))
(princ (ssg-textf "vfl-info-as-eingefuegt" (list (rtos deltaL 2 1))))
)
(progn
(setq frame (make-frame-from-dir p-aktuell (hz-winkel->xu hz-neu 0.0)))
(princ (ssg-text "vfl-info-kein-as"))
)
)
(list frame deltaL)
)
;; ES-Winkel (30/90) + Seite erst am Kettenende abfragen. Setzt *vfl-es-winkel*
;; und die ES-Masse (ein-dx/dz) fuer die Variante. Rueckgabe: Seite (links/rechts).
;;
;; Das ES-Element bekommt einen EIGENEN STEP-Marker "ES" (vfl-journal-mark),
;; obwohl es nicht in einem eigenen Hauptschleifen-Durchlauf, sondern am Ende
;; des letzten Segments gebaut wird. Damit wird ES zu einem regulaeren "Glied"
;; und ist ueber die bestehende Glied-Maschinerie (vfl-journal-glieder/-slice/
;; -splice) genauso editierbar wie ein GF-Bogen - ohne fehleranfaelligen
;; Tail-Sonderfall. Der Marker steht VOR den beiden ES-Eingaben (Winkel, Seite),
;; sodass diese als Glied-Inhalt darauf folgen. Beim Replay wird der Marker in
;; vfl-replay-pop automatisch uebersprungen; die beiden STR-Werte poppen danach
;; regulaer.
;; ES-Masse setzen (kein Bau, nur Globals): Winkel merken und die
;; ein-dx/ein-dz-Masse des ES-Elements laden. Aus der Frage-Schale gezogen,
;; damit der Daten-Pfad sie ohne Frage setzen kann.
;; *vfl-es-winkel* wird LAZY von den Blocknamen-Bauern gelesen (vfl-es-winkel
;; weiter oben) - es MUSS also vor dem ersten ES-Insert stehen, sonst waehlt
;; der Bau den falschen Block.
(defun vfl-es-masse-setzen (winkel seite)
(setq *vfl-es-winkel* winkel)
(vf-set-es-masse winkel seite)
seite)
;; Punkt (XY) + Hoehe (Z) abfragen und zu einem 3D-Punkt zusammenfuehren -
;; gemeinsame Schale fuer die frueher in vf-linienzug-modus/-modus2/-modus3
;; strukturgleich ausgeschriebene Startpunkt-Praeambel (Wizard-Hook, vfl-in-point,
;; vfl-in-real fuer die Hoehe, Z in die Koordinate uebernehmen). Die einzigen
;; Unterschiede sind die drei ssg-Textschluessel (Punkt-Prompt, Hoehen-Prompt,
;; Abbruch-Text) + der Wizard-Prompt - daher Parameter. Bricht bei nil-Punkt
;; per (exit) ab (wie zuvor an jeder Aufrufstelle; laeuft im Modus-Frame).
;; Rueckgabe: (x y z) mit z = abgefragte Hoehe (Default = gepickte Z).
(defun vfl-frage-punkt-hoehe (punkt-prompt-key hoehe-prompt-key abbruch-key wizard-hoehe-prompt / pkt hoehe)
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-punkt-hoehe-impl
(list punkt-prompt-key wizard-hoehe-prompt)))
(setq pkt (vfl-in-point nil (ssg-text punkt-prompt-key)))
(if (null pkt) (progn (princ (ssg-text abbruch-key)) (exit)))
(setq hoehe (vfl-in-real (ssg-textf hoehe-prompt-key (list (rtos (caddr pkt) 2 1)))))
(if (null hoehe) (setq hoehe (caddr pkt)))
(list (car pkt) (cadr pkt) hoehe))
;; AS-Winkel (30/90) + Seite abfragen. Gemeinsame Schale fuer die frueher
;; wortgleich in vf-linienzug-modus/-modus2/-modus3 UND vfl-qc-linie-mit-gf
;; ausgeschriebene AS-Praeambel (Winkel-Frage 30/90, Seiten-Frage links/rechts,
;; vf-set-as-masse) - Gegenstueck zu vfl-frage-es-seite. Setzt *vfl-as-winkel*
;; und die AS-Masse (aus-dx/dz) fuer die Variante. Rueckgabe: Seite (links/rechts).
;;
;; seiten-headerkey ist der EINZIGE Unterschied zwischen den Aufrufstellen
;; (modus1: "vfl-aus-seite-header", modus2: "gf-seite-aus-header") und daher
;; Parameter. Die je Modus unterschiedliche VORANGEHENDE Wizard-Vorbereitung
;; (vflw-gruppe-as-impl "AS setzen?" bzw. vflw-gruppe-winkel-seite-impl) bleibt
;; bewusst an der Aufrufstelle - die abzudeckende Winkel/Seite-Frage selbst ist
;; ueberall identisch. vf-frage-element-winkel/vflw-wahl zeigen den Wizard-
;; Winkeldialog bei aktivem Wizard-Modus schon selbst.
(defun vfl-frage-as-winkel-seite (seiten-headerkey / antwort as-seite)
(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"))))) ; 30/90 vor Seite
(princ (ssg-text seiten-headerkey))
(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
seiten-headerkey))
(setq as-seite (if (= antwort "2") "rechts" "links"))
(vf-set-as-masse *vfl-as-winkel* as-seite) ; Masse fuer Variante
as-seite)
;; ES-Winkel (30/90) + Seite abfragen - MARKER-FREIE Schale fuer die Modi 2/3,
;; die das ES-Element als eigene Praeambel (Start/Ziel bereits fest) abfragen
;; und ihr eigenes Journal-/Masse-Schema fuehren. Unterscheidet sich bewusst
;; von vfl-frage-es-seite (Modus 1), das zusaetzlich einen Journal-Marker "ES"
;; setzt und ueber vfl-es-masse-setzen laeuft - das ist Teil des Modus-1-
;; Kettenglied-Replays und darf hier NICHT feuern. Setzt *vfl-es-winkel* und
;; die ES-Masse (ein-dx/dz). Rueckgabe: Seite (links/rechts).
(defun vfl-frage-es-winkel-seite (seiten-headerkey / antwort es-seite)
(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"))))) ; 30/90 vor Seite
(princ (ssg-text seiten-headerkey))
(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
seiten-headerkey))
(setq es-seite (if (= antwort "2") "rechts" "links"))
(vf-set-es-masse *vfl-es-winkel* es-seite) ; Masse fuer Variante
es-seite)
;; dialog-schon-gezeigt: T, wenn Winkel+Seite BEREITS ueber einen vorherigen
;; kombinierten Dialog (vflw-gruppe-as-impl, "ES-Element setzen?" + Winkel +
;; Seite in einem Bildschirm) abgefragt und in die Pending-Queue gelegt
;; wurden - dann NICHT nochmal den eigenen Winkel+Seite-Dialog zeigen (sonst
;; doppelter Dialog), nur die bereits wartenden Werte konsumieren. Sonst
;; (normaler Fall) nil - eigener Dialog wie bisher.
(defun vfl-frage-es-seite (dialog-schon-gezeigt / antwort es-seite)
(vfl-journal-mark "ES")
(if (and (vfl-wizard-aktiv) (not dialog-schon-gezeigt))
(vl-catch-all-apply 'vflw-gruppe-winkel-seite-impl
(list "vf-winkel-ein-header"
(list (ssg-text "vf-winkel-90") (ssg-text "vf-winkel-30"))
(list "90" "30"))))
(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"))))) ; 30/90 vor Seite
(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"))
(vfl-es-masse-setzen *vfl-es-winkel* es-seite)
es-seite
)
;; Separator (300mm) an der aktuellen Stelle einfuegen (bei 3-Grad-Neigung,
;; Frame-Kettung ueber KS). Rueckgabe: neuer Frame am Separator-Ausgang.
;; Genutzt fuer den optionalen Zwischen-Separator zwischen zwei Foerderern.
(defun vfl-insert-separator (frame / hz w sep-endpunkt)
(setq hz (car (frame->hz-winkel frame)))
(setq w (ssg-cfg-or "vario" "gefaelle_winkel" 3))
(setq sep-endpunkt
(gf-insert-hz-with-ks "Staustrecke_Separator_SP_300_mm" (car frame) hz w *vfk-separator-laenge* 0))
(ssg-sep-typ-xdata-schreiben (entlast) "VF")
(setq *vfl-acc-separator* (1+ *vfl-acc-separator*))
(make-frame-from-dir sep-endpunkt (hz-winkel->xu hz w))
)
;; ES-Element am Kettenende: Separator (300mm) + ES-Element.
;; typ="GF": Neigung = winkel des GF-Segments.
;; typ="VF": Neigung = 3 Grad (Auslauf einer VF-Einheit endet auf 3 Grad).
;; Das ES-Element wird rein per KS-zu-KS an den Separator angekettet
;; (`insert-block-ks-to-ks`) - sein KS_EIN folgt exakt dem Separator-Ausgang
;; (Position + Neigung). KEIN Z-Ziel (anders als im Standalone-Gefaelle, wo eine
;; feste Endhoehe erzwungen wird): der Linienzug laeuft frei aus, ein Z-Ziel
;; wuerde die ES-Hoehe kuenstlich verschieben -> Versatz. hoehe-ziel wird daher
;; hier nicht mehr verwendet (bleibt fuer Signatur-Kompatibilitaet).
(defun vfl-insert-es-element (typ frame hz winkel hoehe-ziel es-seite /
w-eff sep-endpunkt sep-frame)
(setq w-eff (if (= typ "GF") winkel (ssg-cfg-or "vario" "gefaelle_winkel" 3)))
(setq sep-endpunkt
(gf-insert-hz-with-ks "Staustrecke_Separator_SP_300_mm" (car frame) hz w-eff *vfk-separator-laenge* 0))
(ssg-sep-typ-xdata-schreiben (entlast) typ)
(setq sep-frame (make-frame-from-dir sep-endpunkt (hz-winkel->xu hz w-eff)))
(if (boundp '*vfl-acc-separator*)
(setq *vfl-acc-separator* (1+ *vfl-acc-separator*))) ; Separator vor ES
(insert-block-ks-to-ks (strcat "ES_Element_" (vfl-es-winkel) "_" es-seite) sep-frame)
)
;; ============================================================
;; TEIL 4: BLOCK-ERSTELLUNG (Nummerierung + Attribute)
;; ============================================================
(defun vfl-block-erstellen (vfl-nummer anzahl-gf anzahl-vf hoehe-von hoehe-bis
delta-l-total as-seite es-seite startpunkt lastEnt /
vfl-bname vfl-insert typ-str)
;; TYP: einsegmentige Gefaellestrecke ohne Bogen -> "Gefaellestrecke";
;; mit VF, Bogen oder mehreren GF-Segmenten -> "Streckengruppe".
(setq typ-str
(if (or (> anzahl-vf 0)
(> anzahl-gf 1)
(> (length *vfl-acc-gfbogen*) 0)
(> (length *vfl-acc-variokurve*) 0))
"Streckengruppe"
"Gefaellestrecke"))
(setq vfl-bname (strcat "VF_" (itoa vfl-nummer)))
;; Gemeinsamer Wrapper-Rumpf (ATTDEF/Sweep/Sepliste/Wrap/Layer/ID/Dim, siehe
;; vf-wrapper-bauen in vf_core). Die *vfl-acc-*-Attributliste baut hier der
;; Aufrufer (linienzug-spezifische Komma-Listen), der Rumpf kennt sie nicht.
(setq vfl-insert
(vf-wrapper-bauen vfl-nummer typ-str startpunkt lastEnt
(list
(cons "Bezeichnung" vfl-bname)
(cons "MONTAGEHOEHE_m" (rtos (/ (+ hoehe-von hoehe-bis) 2000.0) 2 3))
(cons "HOEHE_VON_mm" (itoa (fix hoehe-von)))
(cons "HOEHE_BIS_mm" (itoa (fix hoehe-bis)))
(cons "DELTA_H_mm" (itoa (fix (abs (- hoehe-bis hoehe-von)))))
(cons "DELTA_L_mm" (itoa (fix delta-l-total)))
(cons "TYP" typ-str)
(cons "SEITE_AS" as-seite)
(cons "WINKEL_AS" (if (boundp '*vfl-as-winkel*) *vfl-as-winkel* ""))
(cons "SEITE_ES" es-seite)
(cons "WINKEL_ES" (if (boundp '*vfl-es-winkel*) *vfl-es-winkel* ""))
;; ANZAHL_GF = alle GF-Stuecke: eigenstaendige GF-Segmente UND die GF1/GF2
;; jeder VF-Einheit (alle ueber vfl-acc-gf-seg in *vfl-acc-lgf* gesammelt).
(cons "ANZAHL_GF" (itoa (length *vfl-acc-lgf*)))
(cons "L_GF_m" (vfl-join-komma *vfl-acc-lgf*))
(cons "GF_WINKEL" (vfl-join-komma *vfl-acc-gfwinkel*))
;; GF-Boegen (Richtungswechsel im GF-Teil), gezaehlt nach Seite+Winkel
(cons "GF_Bogen_L_90" (itoa (vfl-get-count *vfl-acc-gfbogen* "L_90")))
(cons "GF_Bogen_L_60" (itoa (vfl-get-count *vfl-acc-gfbogen* "L_60")))
(cons "GF_Bogen_L_30" (itoa (vfl-get-count *vfl-acc-gfbogen* "L_30")))
(cons "GF_Bogen_R_90" (itoa (vfl-get-count *vfl-acc-gfbogen* "R_90")))
(cons "GF_Bogen_R_60" (itoa (vfl-get-count *vfl-acc-gfbogen* "R_60")))
(cons "GF_Bogen_R_30" (itoa (vfl-get-count *vfl-acc-gfbogen* "R_30")))
(cons "ANZAHL_VF" (itoa anzahl-vf))
(cons "MOTORSEITE" (vfl-join-komma *vfl-acc-motorseite*))
(cons "L_VF_m" (vfl-join-komma *vfl-acc-lvf*))
(cons "ANTRIEBFAHRTRICHTUNG" (vfl-join-komma *vfl-acc-richtung*))
(cons "VF_WINKEL" (vfl-join-komma *vfl-acc-winkel*))
;; Vario-Kurven (Richtungswechsel im VF-Teil), A=aussen / I=innen
(cons "VF_Bogen_A_90" (itoa (vfl-get-count *vfl-acc-variokurve* "A_90")))
(cons "VF_Bogen_A_60" (itoa (vfl-get-count *vfl-acc-variokurve* "A_60")))
(cons "VF_Bogen_A_30" (itoa (vfl-get-count *vfl-acc-variokurve* "A_30")))
(cons "VF_Bogen_I_90" (itoa (vfl-get-count *vfl-acc-variokurve* "I_90")))
(cons "VF_Bogen_I_60" (itoa (vfl-get-count *vfl-acc-variokurve* "I_60")))
(cons "VF_Bogen_I_30" (itoa (vfl-get-count *vfl-acc-variokurve* "I_30")))
(cons "ANZAHL_SEPARATOR" (itoa *vfl-acc-separator*)))))
(if vfl-insert (princ (ssg-textf "vfl-block-erstellt" (list vfl-bname typ-str))))
vfl-insert
)
;; Baut eine komplette VarioFoerderer-Einheit und schliesst sie ab:
;; GF-Verteilung fragen -> vfl-vf-einheit (Umlenk..Motor, mehrsegmentig)
;; -> Kettenende? Ja: Separator+ES (Ende); Nein: optionaler Zwischen-Separator.
;; winkel1=0 => horizontaler Anfangs-Koerper (Option "Neue horizontal VF").
;; auto-ende: T => keine Kettenende-Frage, es wird direkt Separator + ES
;; gesetzt (Option "Neue Linie BIS Kettenende").
;; Rueckgabe: (frame anzahl-koerper ende-flag es-seite-oder-nil).
;; Soll-Ist-Vergleich am Kettenende ausgeben (Zielpunkt aus dem
;; Modus-1-Zielmodus, gesetzt in vfl-body-abschluss). Stand vorher zweimal
;; wortgleich im Abschluss - einmal fuer "Kettenende ohne ES", einmal fuer
;; "Kettenende mit ES"; die Faelle unterscheiden sich nur darin, WANN sie
;; gemeldet werden, nicht WAS.
;; Loescht *vfl-ziel-punkt* danach: der Report gehoert zu genau einem
;; Kettenende, ein zweiter Aufruf soll nichts mehr melden.
(defun vfl-ziel-report (frame)
(if *vfl-ziel-punkt*
(progn
(princ (ssg-text "vfl-ist-ziel-vergleich-header"))
(princ (ssg-textf "vfl-soll-xyz"
(list (rtos (car *vfl-ziel-punkt*) 2 1)
(rtos (cadr *vfl-ziel-punkt*) 2 1)
(rtos (caddr *vfl-ziel-punkt*) 2 1))))
(princ (ssg-textf "vfl-ist-xyz"
(list (rtos (car (car frame)) 2 1)
(rtos (cadr (car frame)) 2 1)
(rtos (caddr (car frame)) 2 1))))
(princ (ssg-textf "vfl-abweichung-xyz"
(list (rtos (- (car (car frame)) (car *vfl-ziel-punkt*)) 2 1)
(rtos (- (cadr (car frame)) (cadr *vfl-ziel-punkt*)) 2 1)
(rtos (- (caddr (car frame)) (caddr *vfl-ziel-punkt*)) 2 1))))
(setq *vfl-ziel-punkt* nil)))
(princ))
;; vorab-antwort/vorab-es-gewuenscht/vorab-es-seite/vorab-ziel-punkt: siehe
;; vfl-vf-einheit (Default nil - unveraendertes Verhalten fuer alle Aufrufer,
;; die diese Vorab-Entscheidung nicht kennen).
;; L_hor/L_hor-position: nur bei einem Stufe-3-Treffer belegt (siehe
;; vfl-winkel-stufe-entscheidung/vfl-stufe3-winkel-horizontal), sonst nil -
;; unveraendertes Verhalten fuer alle anderen Aufrufer.
(defun vfl-vf-einheit-abschluss (frame hz richtung winkel L_GF L_VF L_hor L_hor-position
auto-ende gf-verteilung-fest eingang-modus
vorab-antwort vorab-es-gewuenscht vorab-es-seite vorab-ziel-punkt /
antwort res es-s ende es-gewuenscht es-seite-vorab)
;; GF-Verteilung: halbe Staustrecke am Ausgang (GF2) oder alles am Einlauf.
;; gf-verteilung-fest: Aufrufer erzwingt die Antwort (z.B. "2", wenn L_GF
;; wegen eingang-modus ohnehin 0.0 ist - die Frage waere dann sinnlos).
(if gf-verteilung-fest
(setq antwort gf-verteilung-fest)
(progn
(princ (ssg-text "vfl-gf-verteilung-header"))
(princ (ssg-text "vfl-gf-verteilung-haelfte"))
(princ (ssg-text "vfl-gf-verteilung-ganz-einlauf"))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-gf-verteilung-impl (list (ssg-text "vfl-gf-verteilung-header") "1" "2")))
(setq antwort (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-gf-verteilung-haelfte")
(ssg-text "vfl-gf-verteilung-ganz-einlauf")) 2
"vfl-gf-verteilung-header"))))
(setq res (vfl-vf-einheit frame hz richtung winkel L_GF L_VF L_hor L_hor-position (= antwort "1") eingang-modus
vorab-antwort vorab-es-gewuenscht vorab-es-seite vorab-ziel-punkt))
(setq frame (nth 0 res) es-gewuenscht (nth 3 res) es-seite-vorab (nth 4 res))
;; Kettenende? Bei auto-ende (Kettenende-Modus) ohne Frage direkt ES setzen.
;; ziel-ende (Option 3 IN der VF-Einheit) OHNE ES-Wunsch (dort abgefragt,
;; siehe vfl-vf-einheit): Kette endet direkt hier, kein Separator+ES, keine
;; weitere Frage. ziel-ende MIT ES-Wunsch ODER auto-ende (Option 4): ohne
;; Frage direkt Separator + ES. Sonst normal fragen - zwischen einem AS und
;; ES koennen mehrere Foerderer liegen.
(if (and (nth 2 res) (not es-gewuenscht))
(setq antwort "kein-es")
(if (or auto-ende (nth 2 res))
(setq antwort "1")
(progn
(princ (ssg-text "vfl-ist-kettenende-frage"))
(princ (ssg-text "vfl-ja-separator-es"))
(princ (ssg-text "vfl-nein-weiterbauen"))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-ist-kettenende-2opt-impl nil))
(setq antwort (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-ja-separator-es")
(ssg-text "vfl-nein-weiterbauen")) 2
"vfl-ist-kettenende-frage"))
)
)
)
(cond
((= antwort "kein-es")
(setq ende t)
;; Ist-Ziel-Report auch ohne ES (Vergleich Soll-Zielpunkt vs. tatsaechliches
;; Kettenende nach GF2/Motor).
(vfl-ziel-report frame)
)
((= antwort "1")
;; War die ES-Seite (+Winkel) schon in vfl-vf-einheit erfragt (Option 3,
;; ziel-ende - dort VOR vfl-body-abschluss noetig, siehe dortiger
;; Kommentar), hier NICHT erneut fragen (waere doppelt journalisiert und
;; ein-dx/ein-dz wurden dort schon fuer die Zerlegung genutzt).
(setq es-s (if es-seite-vorab es-seite-vorab (vfl-frage-es-seite nil)))
(setq frame (vfl-insert-es-element "VF" frame
(car (frame->hz-winkel frame)) 0.0
(caddr (car frame)) es-s))
(setq ende t)
;; Ist-Ziel-Report (Option 3): Soll-ES (aus vfl-body-abschluss) vs. Ist-ES.
(vfl-ziel-report frame)
)
(t
(princ (ssg-text "vfl-sep-an-stelle-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-nein"))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-ja-nein-impl (list (ssg-text "vfl-sep-an-stelle-frage"))))
(setq antwort (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2
"vfl-sep-an-stelle-frage"))
(if (= antwort "1") (setq frame (vfl-insert-separator frame)))
)
)
(list frame (nth 1 res) ende es-s)
)
;; ============================================================
;; TEIL 5: HAUPTBEFEHL - MODUS 1 (MANUELLE EINGABE)
;; ============================================================
(defun vf-linienzug-modus ( / startpunkt start-hoehe as-seite es-seite antwort wahl
p-aktuell linie-mess hoehe-neu hoehe-bis deltaL deltaH richtung hz-neu
entscheidung typ winkel L_GF L_VF lhor stufe3-position vf-einheit-res
frame letzter-typ fertig linie-ende-modus rad3
anzahl-gf anzahl-vf vfl-nummer lastEnt seg-lastEnt
gf-max-winkel gf-ok kettenanfang
as-vorhanden erg old-error vfl-ins dbg-an
hoehe-vorschlaege hoehe-ab hoehe-auf
gf-laenge-modus gf-steigend-modus sep-vor-gf sep-nach-gf
sep-anzahl erg-bogen vf-eingang-lgf vf-eingang-modus
eingang-laenge winkel-hoehen-erg
bogen-mass fest-drop fest-laenge eingang-fertig
linie-ende-familie linie-ende-untermodus
res3 save-eindx save-eindz es-gewuenscht
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)
;; "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-*-
;; Aufruf dieses NEUEN Laufs sofort wieder abbrechen (siehe zentraler Check
;; in vfl-in-value/-value-p). Wird ausserdem in vfl-acc-reset erneut
;; zurueckgesetzt (spaeter im Ablauf) sowie unmittelbar vor jedem "Zurueck"-
;; Neuaufruf in vfl-modus-abbruch-sichern.
(setq *vfl-wizard-abbruch-grund* nil)
;; *vfl-journal* VOR dem allerersten vfl-in-*-Aufruf zuruecksetzen (nicht
;; *vfl-replay-queue* - ein REPLAY-getriebener Aufrufer wie vsp-bau-aus-spec
;; hat sie ueber vfl-journal-replay-start bereits VOR diesem Aufruf befuellt,
;; siehe Kommentar bei vfl-journal-reset-lauf). Frueher stand dieser Reset
;; ERST kurz vor der Hauptschleife (nach Startpunkt/Hoehe/AS-Praeambel) -
;; realer Bug: die bereits aufgezeichnete Praeambel wurde dadurch selbst
;; wieder geloescht, das gespeicherte Journal begann mit dem ERSTEN
;; STEP-Marker statt mit PT (sichtbar als vsp-bau-aus-spec-DESYNC "Token 1:
;; erwartet PT..., gebaut STEP...", betraf ausnahmslos JEDE Kette).
(vfl-journal-reset-lauf)
(princ "\n\n=========================================")
(princ (ssg-text "vfl-modus1-header"))
(princ "\n=========================================")
(princ (ssg-text "vfl-modus1-beschreibung"))
;; --- Debug-Session (Schalter "vfl-modus1", DEFAULT AN) ------------------
;; (dbg-schalter-off "vfl-modus1") deaktiviert bei Bedarf das Loggen.
;; Geloggt wird ueber vfl-journal-record/-mark/-steplabel (siehe dort) -
;; das erfasst automatisch JEDE interaktive Eingabe dieses Modus (Punkte,
;; Hoehen, Winkel, Menue-Auswahlen) samt Glied-Uebergaengen, ohne dass jede
;; einzelne Aufrufstelle im Bau-Ablauf separat instrumentiert werden muss.
(setq dbg-an (vfl-dbg-session-start "vfl-modus1" "vfl_modus1.dbg"
"vf-linienzug-modus" "=== SESSION Modus 1 (Manuelle Eingabe) START ==="))
;; Modus-1-Zusatz: falls dieser Lauf ein Replay ist (Editieren/Fortsetzen,
;; *vfl-replay-queue* gesetzt), die EINGEHENDE Replay-Queue als JSON ausgeben -
;; VOR dem Neuaufbau, daher auch bei einem Absturz mitten im Replay sichtbar
;; (anders als das XDATA-JOURNAL-JSON in vfl-journal-xdata-schreiben, das erst
;; nach erfolgreichem Bau laeuft). So laesst sich ein Desync direkt an der
;; Eingangs-Sequenz ablesen. (M1-spezifisch, daher nicht im Session-Helfer.)
(if dbg-an
(progn
(if *vfl-replay-queue*
(dbgmsg (strcat "REPLAY-QUEUE-JSON " (vfl-journal->json *vfl-replay-queue*)))
(dbgmsg "LIVE-BAU (kein Replay)"))
(dbgflush)))
(vfl-gf-abhaengigkeit-sicherstellen "vfl-alert-gf-modul-fehlt")
;; --- Abbruch-Sicherung scharf schalten (VOR dem ersten Prompt!) ---
;; Ab hier kann jederzeit Geometrie entstehen. Ein *error*-Handler faengt
;; jeden Abbruch (ESC / (exit) / Laufzeitfehler) ab und WICKELT die bereits
;; eingefuegte Teil-Geometrie (alles nach lastEnt) zu einem VF_n-Block mit
;; dem vollen aktuellen Eingabe-Journal als XDATA (vfl-modus-abbruch-sichern)
;; - die Geometrie bleibt stehen und ist per Doppelklick sofort weiter
;; editierbar/fortsetzbar (kein Rollback mehr, kein separater
;; "Fortsetzen"-Mechanismus noetig).
;; Die Installation MUSS vor dem allerersten vfl-in-*-Aufruf erfolgen: ein
;; echtes ESC (nicht ein leeres Enter) loest bei JEDEM get*-Aufruf sofort
;; *error* aus (nicht nil) - ohne den Handler wuerde ein ESC in diesem
;; Fenster auf den vorherigen Handler zurueckfallen (im Editier-Pfad: der
;; ssg-start-Handler ohne Wickeln - der bereits geloeschte Original-Block
;; waere dann ersatzlos weg). lastEnt/old-error/vfl-nummer/anzahl-gf/
;; anzahl-vf/startpunkt/frame/as-seite/es-seite sind zur Aufrufzeit
;; dynamisch gebunden und daher im Handler-Lambda sichtbar (AutoLISP
;; dynamic scoping, gilt fuer die gesamte Laufzeit dieses Aufrufs, auch
;; tief verschachtelt z.B. in vfl-vf-einheit).
(setq vfl-nummer (vf-next-number))
(setq lastEnt (vf-lastent-ohne-attribute))
(setq anzahl-gf 0 anzahl-vf 0 frame nil)
(setq old-error *error*)
;; Scharfen Abbruch-Wickel-Handler installieren (gemeinsam fuer alle drei
;; Modi, siehe vfl-abbruch-handler-installieren). old-error/lastEnt/vfl-nummer/
;; anzahl-gf/anzahl-vf/startpunkt/frame/as-seite/es-seite/dbg-an sind hier als
;; Locals gebunden und werden vom Handler zur Abbruchzeit dynamisch gesehen.
(vfl-abbruch-handler-installieren "linienzug")
;; Startpunkt + Hoehe (gemeinsame Praeambel-Schale, siehe vfl-frage-punkt-hoehe).
(setq startpunkt (vfl-frage-punkt-hoehe "vfl-prompt-startpunkt-kette"
"vfl-prompt-hoehe-startpunkt-kette" "vfl-abgebrochen" "Starthoehe (Z, mm):"))
;; Startpunkt-Erkennung VOR der AS-Frage: liegt der Punkt auf dem KS_AUS
;; EINES beliebigen bestehenden Kettenbausteins (GF ODER VF, beliebiges
;; Segment), ist ein AS-Element ueberfluessig - die Kette knuepft direkt
;; an. WICHTIG: der Gate ist bewusst "erg gefunden", NICHT "Familie
;; aufgeloest" - viele Bausteine (Staustrecke_SP_1000_mm/Separator) sind
;; per Blockname allein GF/VF-uneindeutig (vfl-kette-typ-familie liefert
;; dafuer nil), zaehlen aber trotzdem eindeutig als "hier steht schon ein
;; Kettenbaustein" - genau dieser Fall (Kettenende = ein gewoehnliches
;; GF-Geradenstueck) fragte bisher faelschlich noch nach dem AS-Element.
;; startpunkt-familie bleibt nil in diesem uneindeutigen Fall; die Meldung
;; 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.
(setq erg (vfl-ks-aus-hz-an-punkt startpunkt))
(setq startpunkt-familie (if erg (vfl-kette-typ-familie (cdr erg)) nil))
(if erg
(progn
(princ (ssg-textf "vfl-as-auto-erkannt" (list (if startpunkt-familie startpunkt-familie (ssg-text "vfl-familie-neutral")))))
(setq as-vorhanden nil))
(progn
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-as-setzen-frage")))
(princ (ssg-text "vfl-as-setzen-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-as-nein"))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-as-nein")) 1
"vfl-as-setzen-frage"))
(setq as-vorhanden (/= antwort "2"))))
;; *vfl-as-winkel*/*vfl-es-winkel* sind Globals (fuer vfl-as-winkel/-es-winkel-
;; Zugriff aus AS-/ES-Einfuegefunktionen) und muessen pro Kette zurueckgesetzt
;; 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)
(if as-vorhanden
(setq as-seite (vfl-frage-as-winkel-seite "vfl-aus-seite-header"))
)
;; 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)
;; *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
;; dieses Laufs wieder loeschen).
(while (not fertig)
;; --- Naechstes Element bestimmen ---
;; Nach einem GF-Segment ODER einer geschlossenen VF-Einheit (beide enden
;; auf 3-Grad-Neigung) darf ein GF-Bogen oder eine neue Linie folgen. Nur
;; am Kettenanfang folgt direkt eine neue Linie. Vario-Kurven werden
;; INNERHALB der VF-Einheit behandelt (vfl-vf-einheit).
;; Menue immer anzeigen; GF-Bogen nur, wenn bereits ein Frame existiert
;; (am Kettenanfang gibt es keinen Vorgaenger-Rahmen fuer einen Bogen).
;; Lueckenlose Nummerierung: GF-Bogen (nur mit Frame) ist immer Option 1;
;; ohne Frame ruecken die uebrigen Optionen auf 1..4 auf.
;; "Neue Linie" ist in zwei explizite Zweige aufgeteilt (GF / Ab-Auf-VF) -
;; der Nutzer legt den Segmenttyp direkt fest, keine Geometrie-Automatik
;; mehr wie bei "Neue Linie BIS Kettenende" (die bleibt unveraendert und
;; nutzt weiterhin vfl-segment-entscheidung).
(if (not *vfl-forciere-qc-modus*) (princ (ssg-text "vfl-naechstes-element-header")))
(setq linie-ende-modus nil)
;; Glied-Checkpoint am Iterationskopf setzen; Label wird nach der Auswahl
;; nachgetragen (vfl-journal-steplabel), damit jedes Glied seine eigene
;; Menue-Auswahl + Segment-Eingaben vollstaendig umschliesst (fuer den
;; gliedweisen Ruecksprung beim Editieren).
;; seg-lastEnt: Schnappschuss VOR diesem Glied, fuer die Segment-XDATA
;; (vfl-segment-xdata-sichern, siehe Ende der cond unten) - dieselbe
;; Hilfsfunktion wie beim Ketten-lastEnt (oben), damit ATTRIB/SEQEND-
;; Ketten eines vorherigen INSERTs konsistent uebersprungen werden.
(setq seg-lastEnt (vf-lastent-ohne-attribute))
(vfl-journal-mark "?")
;; Erzwungener QC-Modus (Linienzug-Modus 4/5 im Typ-Dispatcher
;; c:VarioFoerderer, *vfl-forciere-qc-modus* = "4"/"5"): das Menue
;; "Naechstes Element waehlen" komplett ueberspringen - diese Ketten
;; bestehen IMMER nur aus dem einen "Linie-GF"-Glied (QC baut AS bis ES
;; selbst und setzt fertig). Nur am allerersten Glied wirksam (kettenanfang);
;; das Consumer-seitige Zuruecksetzen erfolgt in vfl-qc-linie-mit-gf.
(if *vfl-forciere-qc-modus*
(setq wahl "Linie-GF")
(progn
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-naechstes-element-impl
(list (null frame) (if (null frame) startpunkt-familie nil))))
(if frame
(progn
(princ (ssg-text "vfl-menu-gf-bogen-1"))
(princ (ssg-text "vfl-menu-linie-gf-2"))
(princ (ssg-text "vfl-menu-linie-vf-3"))
(princ (ssg-text "vfl-menu-horizontal-vf-4"))
(princ (ssg-text "vfl-menu-linie-ende-5"))
(setq antwort (vfl-menu (ssg-text "vfl-prompt-wahl-1-5-def2")
(list (ssg-text "vfl-menu-gf-bogen-1")
(ssg-text "vfl-menu-linie-gf-2")
(ssg-text "vfl-menu-linie-vf-3")
(ssg-text "vfl-menu-horizontal-vf-4")
(ssg-text "vfl-menu-linie-ende-5")) 2
"vfl-naechstes-element-header"))
(setq wahl (cond ((= antwort "1") "GF-Bogen")
((= antwort "3") "Linie-VF")
((= antwort "4") "Horizontal-VF")
((= antwort "5") (setq linie-ende-modus t) "Linie")
(t "Linie-GF"))) ; "2" oder leer
)
(progn
;; Kettenanfang: 6 Optionen statt 4 - GF-Bogen und Vario-Kurve sind
;; hier ZUSAETZLICH waehlbar (stueckweiser Aufbau kann auch mit einer
;; horizontalen Kurve beginnen, z.B. Fortsetzung einer bereits an
;; anderer Stelle gebauten Teilkette). Beide brauchen dafuer keine
;; eigene Laengenmessung, nur eine Referenzrichtung - siehe die
;; KS_AUS-Erkennung (vfl-ks-aus-hz-an-punkt) in den beiden Zweigen
;; unten.
(princ (ssg-text "vfl-menu-gf-bogen-ka-1"))
(princ (ssg-text "vfl-menu-variokurve-ka-2"))
(princ (ssg-text "vfl-menu-linie-gf-ka-3"))
(princ (ssg-text "vfl-menu-linie-vf-ka-4"))
(princ (ssg-text "vfl-menu-horizontal-vf-ka-5"))
(princ (ssg-text "vfl-menu-linie-ende-ka-6"))
(setq antwort (vfl-menu (ssg-text "vfl-prompt-wahl-1-6-def3")
(list (ssg-text "vfl-menu-gf-bogen-ka-1")
(ssg-text "vfl-menu-variokurve-ka-2")
(ssg-text "vfl-menu-linie-gf-ka-3")
(ssg-text "vfl-menu-linie-vf-ka-4")
(ssg-text "vfl-menu-horizontal-vf-ka-5")
(ssg-text "vfl-menu-linie-ende-ka-6")) 3
"vfl-naechstes-element-header"))
(setq wahl (cond ((= antwort "1") "GF-Bogen")
((= antwort "2") "Vario-Kurve")
((= antwort "4") "Linie-VF")
((= antwort "5") "Horizontal-VF")
((= antwort "6") (setq linie-ende-modus t) "Linie")
(t "Linie-GF"))) ; "3" oder leer
)
)
)) ;; Ende (progn ...) / (if *vfl-forciere-qc-modus* ...)
;; Segmenttyp steht fest -> Glied-Label nachtragen (fuer die Edit-Liste).
;; Die Auswahl selbst wurde bereits automatisch echot (vfl-echo-antwort,
;; zentral in vfl-in-value/-value-p verankert).
(vfl-journal-steplabel wahl)
(progn
(cond
((= wahl "GF-Bogen")
;; Als ALLERERSTES Kettenglied (frame=nil) braucht eine Kurve technisch
;; eine Referenzrichtung, von der aus Winkel+Seite gemessen werden -
;; eine zusaetzliche Punktwahl nur zur Richtungsbestimmung ist
;; ueberfluessig (kein eigenes Segment, keine Laenge wird dabei
;; gemessen). Liegt der gewaehlte Startpunkt exakt auf dem KS_AUS eines
;; bestehenden Vario-Kette-Bausteins (Fortsetzung einer schon gebauten
;; Kette), wird dessen Richtung uebernommen; sonst Standard Welt-0
;; Grad/+X-Achse. Das AS-Element wird ganz normal ueber
;; vfl-kettenanfang-baustein eingefuegt (deltaL dabei irrelevant, da
;; hier kein Segment gebaut wird - Wert wird verworfen).
(if (null frame)
(progn
(setq hz-neu (car (vfl-ks-aus-hz-an-punkt p-aktuell)))
(if (null hz-neu) (setq hz-neu 0.0))
(setq erg (vfl-kettenanfang-baustein p-aktuell hz-neu 0.0 as-seite as-vorhanden))
(setq frame (nth 0 erg))
)
)
(setq frame (vfl-insert-gf-bogen frame))
(setq p-aktuell (car frame))
)
((= wahl "Vario-Kurve")
;; Gleiche Kettenanfang-Behandlung wie GF-Bogen oben (KS_AUS-
;; Richtungsuebernahme bei Fortsetzung, sonst Welt-0 Grad).
(if (null frame)
(progn
(setq hz-neu (car (vfl-ks-aus-hz-an-punkt p-aktuell)))
(if (null hz-neu) (setq hz-neu 0.0))
(setq erg (vfl-kettenanfang-baustein p-aktuell hz-neu 0.0 as-seite as-vorhanden))
(setq frame (nth 0 erg))
)
)
(setq frame (vfl-insert-vario-kurve frame))
(setq p-aktuell (car frame))
;; letzter-typ merken: eine Vario-Kurve hat KEINE eigene Umlenkstation
;; und ist mechanisch Teil einer VF-Kette - ein direkt folgendes
;; "Neue Linie: Ab/Auf VF"/"Horizontal-VF" baut daher (via
;; vfl-eingang-modus-bestimmen, eingang-modus="keine") KEINE zweite
;; Umlenkstation.
(setq letzter-typ "Vario-Kurve")
)
;; --- Neue horizontal VF: VF-Einheit mit horizontalem Anfangs-Koerper ---
((= wahl "Horizontal-VF")
(setq linie-mess (vfl-neue-linie-messen p-aktuell
(if frame (car (frame->hz-winkel frame)) nil)))
(if (null linie-mess) (progn (princ (ssg-text "vfl-abgebrochen")) (exit)))
(setq deltaL (car linie-mess) hz-neu (cadr linie-mess))
(setq kettenanfang (null frame))
;; Kettenanfang: AS-Element (falls gewuenscht) SOFORT einfuegen, reale
;; Restlaenge aus dem tatsaechlichen KS_AUS nachrechnen (siehe
;; vfl-kettenanfang-baustein).
(if kettenanfang
(progn
(setq erg (vfl-kettenanfang-baustein p-aktuell hz-neu deltaL as-seite as-vorhanden))
(setq frame (nth 0 erg) deltaL (nth 1 erg) p-aktuell (car frame))
)
)
;; KEINE pauschale "Umlenk+Motor"-Mindestlaenge mehr hier: der noetige
;; Fussabdruck haengt davon ab, ob gleich eine Motorstation folgt oder
;; weitergebaut wird - genau das fragt vfl-baue-horizontal-koerper
;; ("Ist der Endpunkt der Foerderer?") und rechnet mit dem REAL
;; gemessenen Fussabdruck (siehe vfl-vf-einheit). Hier bleibt nur der
;; Degenerationsschutz wie bei "Neue Linie: GF".
(if (< deltaL 1.0)
(progn
(princ (ssg-text "vfl-fehler-linie-zu-kurz"))
(if dbg-an
(dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Horizontal-VF zu kurz (deltaL="
(rtos deltaL 2 0) ")"))))
(progn
;; Eingang haengt vom VORHERIGEN Kettenglied ab (letzter-typ) bzw.
;; am echten Kettenanfang vom KS_AUS-Scan - siehe
;; vfl-eingang-modus-bestimmen (gemeinsam mit "Neue Linie: Ab/Auf
;; VF").
(setq erg (vfl-eingang-modus-bestimmen letzter-typ kettenanfang as-vorhanden p-aktuell))
(setq vf-eingang-lgf (car erg) vf-eingang-modus (cdr erg))
;; Bei einem vollen Neuaufbau des Eingangs (modus=nil, GF1 wird
;; frisch gebaut) wird GF1 nicht pauschal auf *vfl-gf-min-laenge*
;; gesetzt, sondern aus einer abgefragten Zielhoehe berechnet - der
;; Rest bleibt wie bisher horizontal. Bei den anderen Eingangs-Modi
;; ("separator"/"gf"/"keine" - Fortsetzung nach einem Vorglied) wird
;; ohnehin kein neues GF1 gebaut, daher unveraendert.
(if (null vf-eingang-modus)
(progn
(if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek))
(setq rad3 (* *vfk-gefaelle-winkel* (/ pi 180.0)))
(setq bogen-mass (get-bogen-mass bogen-auf (fix *vfk-gefaelle-winkel*)))
;; Hoehen- bzw. Laengenanteil der FIXEN Elemente (Separator +
;; Umlenkstation + Uebergangsbogen), OHNE GF1 - empirisch
;; bestaetigt: jedes Element traegt Schraeglaenge*sin(3 Grad) an
;; Hoehe bei, der Uebergangsbogen einen festen, winkelunabhaengigen
;; dx/dz-Fussabdruck aus der Bogen-Tabelle.
(setq fest-drop (+ (* *vfk-separator-laenge* (sin rad3))
(* *vfk-stations-laenge* (sin rad3))
(caddr bogen-mass)))
(setq fest-laenge (+ *vfk-separator-laenge* *vfk-stations-laenge* (car bogen-mass)))
(setq eingang-fertig nil)
(while (not eingang-fertig)
(setq eingang-fertig t)
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-ziel-hoehe-impl
(list (- (caddr p-aktuell) fest-drop))))
(setq hoehe-neu
(vfl-in-real (ssg-textf "vfl-prompt-hoehe-eingang-gf1"
(list (rtos (- (caddr p-aktuell) fest-drop) 2 1)))))
(if (null hoehe-neu) (setq hoehe-neu (- (caddr p-aktuell) fest-drop)))
(setq deltaH (- (caddr p-aktuell) hoehe-neu))
(cond
((<= deltaH 0.0)
(vfl-meldung (ssg-text "vfl-alert-eingang-kann-nicht-steigen"))
(setq vf-eingang-lgf nil))
((< deltaH fest-drop)
(vfl-meldung (ssg-textf "vfl-alert-eingang-hoehe-zu-klein" (list (rtos fest-drop 2 0))))
(setq eingang-fertig nil))
(t
(setq vf-eingang-lgf (/ (- deltaH fest-drop) (sin rad3)))
(if (> (+ vf-eingang-lgf fest-laenge) deltaL)
(progn
(vfl-meldung (ssg-textf "vfl-alert-eingang-hoehe-zu-gross"
(list (rtos (- (caddr p-aktuell) fest-drop
(* (- deltaL fest-laenge) (sin rad3))) 2 0))))
(setq vf-eingang-lgf nil))
)
)
)
)
)
)
;; VF-Einheit mit horizontalem ersten Koerper (winkel=0, L_VF=deltaL) -
;; nur bauen, wenn vf-eingang-lgf gueltig ist (s.o.). Ist es nil (Abbruch
;; wegen unpassender Zielhoehe), wird NICHTS gebaut - die aeussere
;; Schleife zeigt danach automatisch wieder "Naechstes Element waehlen".
(if vf-eingang-lgf
(progn
(if dbg-an
(dbgmsg (strcat "GEOMETRIE: vfl-vf-einheit-abschluss horizontal (deltaL=" (rtos deltaL 2 0)
" L_GF=" (rtos vf-eingang-lgf 2 0) " L_VF=" (rtos deltaL 2 0)
" eingang-modus=" (if vf-eingang-modus vf-eingang-modus "voll") ")")))
;; GF-Verteilungsfrage entfaellt hier ("2" = alles am Einlauf, kein
;; GF2 fest vorgegeben): L_GF ist nur der strukturelle Mindest-
;; Eingang, das Ausgangs-Ende regelt allein die mit/ohne-
;; Motorstation-Frage (siehe vfl-baue-horizontal-koerper).
(setq vf-einheit-res
(vfl-vf-einheit-abschluss frame hz-neu "Ab" 0 vf-eingang-lgf deltaL nil nil nil "2" vf-eingang-modus
nil nil nil nil))
(setq frame (nth 0 vf-einheit-res))
(setq anzahl-vf (+ anzahl-vf (nth 1 vf-einheit-res)))
(if (nth 2 vf-einheit-res) (setq fertig t))
(if (nth 3 vf-einheit-res) (setq es-seite (nth 3 vf-einheit-res)))
(setq letzter-typ "VF")
(setq p-aktuell (car frame))
(if dbg-an
(dbgmsg (strcat "GEOMETRIE ERFOLG: Horizontal-VF-Einheit gebaut, neue Position="
(dbg-tostring p-aktuell))))
)
)
)
)
)
;; --- Neue Linie: GF (Nutzer legt den Typ explizit fest) ---
((= wahl "Linie-GF")
(setq kettenanfang (null frame))
(setq gf-max-winkel (float (ssg-cfg-or "vario" "gefaelle_winkel" 3)))
(setq gf-ok nil sep-vor-gf nil sep-nach-gf nil)
;; NEU: Vorfrage "Wie soll die Laenge bestimmt werden?" - IMMER
;; gezeigt. gf-laenge-modus="2" => Zielhoehe zuerst (Zweig B), "3" =>
;; steigende GF (Sonderfall, Zweig C, KEIN Separator), sonst (Default
;; "1") Endpunkt waehlen (Zweig A, heutiges Verhalten + Separator).
(setq gf-laenge-modus "1")
;; Erzwungener QC-Modus (Linienzug-Modus 4/5 im Typ-Dispatcher
;; c:VarioFoerderer, *vfl-forciere-qc-modus*): Frage komplett
;; ueberspringen, gf-laenge-modus direkt auf den erzwungenen Wert
;; setzen. Flag NACH Verbrauch zuruecksetzen (gilt nur fuer dieses
;; eine, erste Glied).
(if *vfl-forciere-qc-modus*
(progn
(setq gf-laenge-modus *vfl-forciere-qc-modus*)
(setq *vfl-forciere-qc-modus* nil)
)
(progn
(if (vfl-wizard-aktiv) (vl-catch-all-apply 'vflw-gruppe-gf-laenge-modus-impl nil))
(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"))
(setq gf-laenge-modus (vfl-menu (ssg-text "prompt-wahl-1-2")
(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"))
)
)
;; Option 4 ("QC Linie mit GF"): eigenstaendiger, vollstaendig
;; berechneter AS->ES-Ablauf (vfl-qc-linie-mit-gf) - baut Kettenende
;; SELBST (setzt fertig direkt), braucht daher NICHT die gemeinsame
;; Kettenende-/ES-Vorfrage und NICHT den Zweig-A/B/C-Abschluss unten
;; (der von einer normal weiterlaufenden GF ausgeht). Nur am echten
;; Kettenanfang sinnvoll (braucht eine eigene AS-Seite) - siehe Gate
;; in vfl-qc-linie-mit-gf. Option 5 ("QC Linie mit GF und U-Form",
;; vfl-qc-linie-mit-gf-uform) funktioniert nach demselben Muster -
;; ebenfalls eigenstaendig, baut Kettenende selbst.
(cond
((= gf-laenge-modus "4") (vfl-qc-linie-mit-gf))
((= gf-laenge-modus "5") (vfl-qc-linie-mit-gf-uform))
(t
(progn
;; NEU: Kettenende/ES JETZT fragen - VOR der Laengen-/Gefaelle-
;; Berechnung -, damit ein gewuenschtes ES-Element (Antwort "1") mit
;; seinem echten Fussabdruck (ein-dx/ein-dz, ueber vf-set-as-masse-
;; Gegenstueck vfl-es-masse-setzen INNERHALB von vfl-frage-es-seite
;; gesetzt) SOFORT von der Ziel-Laenge/-Hoehe abgezogen wird (siehe die
;; drei Zweige unten) - statt es nach dem fertigen Bau nur noch
;; anzuhaengen, wodurch die Kette bisher um genau diesen Fussabdruck
;; ueber den gepickten Punkt/die Zielhoehe hinausschoss. Analog zur
;; Separator VOR/NACH-Reservierung, die schon vorher erfolgt.
;; gf-kettenende-antwort wird im gemeinsamen Abschluss unten
;; wiederverwendet statt die Frage dort ein zweites Mal zu stellen.
;; BEKANNTE RESTUNGENAUIGKEIT (Zweig C, steigend): der Ausgleichsbogen
;; zurueck auf die fallende Basis (vfl-bogen-uebergang im Abschluss,
;; NACH dem Bau dieser GF) hat einen eigenen Fussabdruck, der hier
;; NICHT mitgerechnet wird (analoge Luecke zu vfl-nach-3grad am
;; Kettenende, siehe dortiger Kommentar) - die ein-dx/ein-dz-Reservierung
;; unten ist daher fuer Zweig C eine Naeherung, kein exakter Treffer.
(setq gf-kettenende-antwort (vfl-frage-ist-kettenende))
(setq es-seite nil)
(if (= gf-kettenende-antwort "1")
(setq es-seite (vfl-frage-es-seite nil)))
(cond
((= gf-laenge-modus "3")
;; --- Zweig C: Steigende GF (Sonderfall, bis gf-max-winkel Grad) ---
;; Eigenstaendig: KEIN Separator (gilt nur fuer die normale
;; fallende GF). Innerhalb dieses Zweigs nochmal dieselbe Wahl
;; (Endpunkt vs. Zielhoehe), mit Ziel-Richtung "Auf" statt "Ab".
;; Der noetige Uebergangsbogen (falls die Vorgaenger-Neigung nicht
;; schon zur steigenden Basis passt) wird zentral im gemeinsamen
;; Abschluss unten ueber vfl-bogen-uebergang eingefuegt.
(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"))
(if (vfl-wizard-aktiv) (vl-catch-all-apply 'vflw-gruppe-gf-steigend-untermodus-impl nil))
(setq gf-steigend-modus (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-gf-laenge-modus-opt-endpunkt")
(ssg-text "vfl-gf-laenge-modus-opt-hoehe")) 1
"vfl-gf-laenge-modus-header"))
(if (= gf-steigend-modus "2")
(progn
;; --- Zweig C2: Zielhoehe zuerst (steigend) ---
(if kettenanfang
(progn
(setq hz-neu (vfl-in-richtung p-aktuell (ssg-text "vfl-prompt-richtung-waehlen")))
(if (null hz-neu) (progn (princ (ssg-text "vfl-abgebrochen")) (exit)))
(setq erg (vfl-kettenanfang-baustein p-aktuell hz-neu 1.0e9 as-seite as-vorhanden))
(setq frame (nth 0 erg) p-aktuell (car frame))
)
(setq hz-neu (car (frame->hz-winkel frame)))
)
(setq gf-ok t)
(setq hoehe-neu
(vfl-in-real (ssg-textf "vfl-prompt-hoehe-linienendpunkt"
(list (rtos (caddr p-aktuell) 2 1)))))
(if (null hoehe-neu) (setq hoehe-neu (caddr p-aktuell)))
(setq deltaH (- hoehe-neu (caddr p-aktuell)))
;; ES-Fussabdruck (Hoehen-Anteil) VORAB reservieren, siehe
;; Kommentar bei gf-kettenende-antwort oben.
(if (= gf-kettenende-antwort "1")
(setq deltaH (max 0.0 (- deltaH (abs ein-dz)))))
(if (< deltaH 0.0)
(progn
(vfl-meldung (ssg-text "vfl-alert-gf-steigend-falsche-richtung"))
(setq gf-ok nil))
(progn
(setq winkel
(vfl-in-real (ssg-textf "vfl-prompt-neigungswinkel-steigend" (list (rtos gf-max-winkel 2 1)))))
(if (null winkel) (setq winkel gf-max-winkel))
(if (or (< winkel 0.0) (> winkel gf-max-winkel))
(progn
(vfl-meldung (ssg-textf "vfl-alert-winkel-ungueltig-steigend"
(list (rtos winkel 2 1) (rtos gf-max-winkel 2 1))))
(setq gf-ok nil))
(if (and (< winkel 1.0e-6) (> deltaH 1.0e-6))
(progn
(vfl-meldung (ssg-textf "vfl-alert-winkel-ungueltig-steigend"
(list (rtos winkel 2 1) (rtos gf-max-winkel 2 1))))
(setq gf-ok nil))
(progn
(setq deltaL
(if (< winkel 1.0e-6) 0.0
(/ deltaH (/ (sin (* winkel (/ pi 180.0))) (cos (* winkel (/ pi 180.0)))))))
(if (< deltaL 1.0)
(progn (princ (ssg-text "vfl-fehler-linie-zu-kurz")) (setq gf-ok nil)))
(setq winkel (- winkel))
)
)
)
)
)
)
(progn
;; --- Zweig C1: Endpunkt waehlen (steigend) ---
(setq linie-mess (vfl-neue-linie-messen p-aktuell
(if frame (car (frame->hz-winkel frame)) nil)))
(if (null linie-mess) (progn (princ (ssg-text "vfl-abgebrochen")) (exit)))
(setq deltaL (car linie-mess) hz-neu (cadr linie-mess))
(if (< deltaL 1.0)
(princ (ssg-text "vfl-fehler-linie-zu-kurz"))
(progn
(if kettenanfang
(progn
(setq erg (vfl-kettenanfang-baustein p-aktuell hz-neu deltaL as-seite as-vorhanden))
(setq frame (nth 0 erg) deltaL (nth 1 erg) p-aktuell (car frame))
)
)
;; ES-Fussabdruck VORAB reservieren, siehe Kommentar bei
;; gf-kettenende-antwort oben.
(if (= gf-kettenende-antwort "1")
(setq deltaL (- deltaL (abs ein-dx))))
(if (< deltaL 1.0)
(princ (ssg-text "vfl-fehler-linie-zu-kurz"))
(progn
(setq gf-ok t)
(setq winkel
(vfl-in-real (ssg-textf "vfl-prompt-neigungswinkel-steigend" (list (rtos gf-max-winkel 2 1)))))
(if (null winkel) (setq winkel gf-max-winkel))
(if (or (< winkel 0.0) (> winkel gf-max-winkel))
(progn
(vfl-meldung (ssg-textf "vfl-alert-winkel-ungueltig-steigend"
(list (rtos winkel 2 1) (rtos gf-max-winkel 2 1))))
(setq gf-ok nil))
(setq winkel (- winkel))
)
)
)
)
)
)
)
)
((= gf-laenge-modus "2")
;; --- Zweig B: Zielhoehe zuerst festlegen, Laenge wird berechnet ---
(if kettenanfang
(progn
(setq hz-neu (vfl-in-richtung p-aktuell (ssg-text "vfl-prompt-richtung-waehlen")))
(if (null hz-neu) (progn (princ (ssg-text "vfl-abgebrochen")) (exit)))
(setq erg (vfl-kettenanfang-baustein p-aktuell hz-neu 1.0e9 as-seite as-vorhanden))
(setq frame (nth 0 erg) p-aktuell (car frame))
)
(setq hz-neu (car (frame->hz-winkel frame)))
)
(setq rad3 (* gf-max-winkel (/ pi 180.0)))
(if (vfl-wizard-aktiv) (vl-catch-all-apply 'vflw-gruppe-sep-gf-impl nil))
(princ (ssg-text "vfl-sep-vor-gf-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-nein"))
(setq sep-vor-gf (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2
"vfl-sep-vor-gf-frage") "1"))
(princ (ssg-text "vfl-sep-nach-gf-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-nein"))
(setq sep-nach-gf (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2
"vfl-sep-nach-gf-frage") "1"))
;; ES-Element (vfl-insert-es-element) bringt seinen eigenen
;; Separator VOR dem ES-Block schon mit - ein zusaetzlicher
;; Separator NACH waere ein zweiter, direkt anschliessender
;; Separator (real beobachteter Bug). gf-kettenende-antwort steht
;; schon vor dieser Frage fest (siehe oben).
(if (and sep-nach-gf (= gf-kettenende-antwort "1"))
(progn
(vfl-meldung (ssg-text "vfl-sep-nach-durch-es-ueberschrieben"))
(setq sep-nach-gf nil)))
(setq sep-anzahl (+ (if sep-vor-gf 1 0) (if sep-nach-gf 1 0)))
(setq gf-ok t)
(setq hoehe-neu
(vfl-in-real (ssg-textf "vfl-prompt-hoehe-linienendpunkt"
(list (rtos (vfl-gf-hoehe-vorschlag (caddr p-aktuell)) 2 1)))))
(if (null hoehe-neu) (setq hoehe-neu (vfl-gf-hoehe-vorschlag (caddr p-aktuell))))
(setq deltaH (- hoehe-neu (caddr p-aktuell)))
(setq richtung (if (>= deltaH 0.0) "Auf" "Ab"))
(setq deltaH (abs deltaH))
(if (= richtung "Auf")
(progn
(vfl-meldung (ssg-text "vfl-alert-gf-kann-nicht-steigen"))
(if dbg-an
(dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Linie-GF (Zielhoehe zuerst) kann nicht steigen"
" (deltaH=" (rtos deltaH 2 0) ")")))
(setq gf-ok nil))
(progn
(if (> sep-anzahl 0)
(setq deltaH (max 0.0 (- deltaH (* sep-anzahl *vfk-separator-laenge* (sin rad3))))))
;; ES-Fussabdruck VORAB reservieren, siehe Kommentar bei
;; gf-kettenende-antwort oben.
(if (= gf-kettenende-antwort "1")
(setq deltaH (max 0.0 (- deltaH (abs ein-dz)))))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-gefaelle-impl
(list (vfl-gf-hoehe-vorschlag (caddr p-aktuell)) t nil)))
(princ (ssg-text "vfl-gefaelle-festlegen-header"))
(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))
(if (or (<= winkel 0.0) (> winkel gf-max-winkel))
(progn
(vfl-meldung (ssg-textf "vfl-alert-winkel-ungueltig"
(list (rtos winkel 2 1) (rtos gf-max-winkel 2 1))))
(setq gf-ok nil))
(progn
(setq deltaL (/ deltaH (/ (sin (* winkel (/ pi 180.0))) (cos (* winkel (/ pi 180.0))))))
(if (< deltaL 1.0)
(progn (princ (ssg-text "vfl-fehler-linie-zu-kurz")) (setq gf-ok nil)))
)
)
)
)
)
(t
;; --- Zweig A: Endpunkt waehlen (heutiges Verhalten + Separator) ---
(setq linie-mess (vfl-neue-linie-messen p-aktuell
(if frame (car (frame->hz-winkel frame)) nil)))
(if (null linie-mess) (progn (princ (ssg-text "vfl-abgebrochen")) (exit)))
(setq deltaL (car linie-mess) hz-neu (cadr linie-mess))
(if (< deltaL 1.0)
(princ (ssg-text "vfl-fehler-linie-zu-kurz"))
(progn
;; Kettenanfang: AS-Element (falls gewuenscht) SOFORT einfuegen
;; (Richtung hz-neu jetzt bekannt) und die GF-Restlaenge aus dem
;; TATSAECHLICHEN KS_AUS nachrechnen (siehe vfl-kettenanfang-
;; baustein). Keine Fussabdruck-Schaetzung mehr: sowohl KS_EIN-
;; als auch Ursprung-basierte Vorab-Messung ignorieren die
;; Drehteller-Rotation, die insert-block-mixed-to-ks beim
;; echten Einfuegen anwendet, und liefern daher einen falschen
;; Wert (empirisch bestaetigt: ~210mm Schaetzung vs.
;; tatsaechlich ~420mm noetiger Versatz).
(if kettenanfang
(progn
(setq erg (vfl-kettenanfang-baustein p-aktuell hz-neu deltaL as-seite as-vorhanden))
(setq frame (nth 0 erg) deltaL (nth 1 erg) p-aktuell (car frame))
)
)
;; Nach dem AS-Einfuegen kann die reale Restlaenge (aus dem
;; tatsaechlichen KS_AUS neu gemessen) kleiner als der
;; urspruenglich gepickte Rohwert sein - bei einem knapp
;; gepickten Punkt sogar NEGATIV. Sofortiger Abbruch DIESES
;; Zweigs (kein Weiterfragen fuer ein Segment, das ohnehin
;; nicht gebaut werden kann).
(if (< deltaL 1.0)
(princ (ssg-text "vfl-fehler-linie-zu-kurz"))
(progn
(setq gf-ok t)
;; Separator VOR/NACH JETZT fragen, BEVOR Hoehe/Winkel
;; berechnet wird - gueltige Trennstellen GF-GF bzw.
;; GF-Umlenkstation. Beide Laengen (genaehert mit dem
;; Referenzwinkel gf-max-winkel, der tatsaechliche
;; GF-Winkel steht erst danach fest) werden SOFORT vom
;; gepickten deltaL abgezogen, damit die Kette exakt am
;; gepickten Punkt endet statt danach ueberzustehen.
(setq rad3 (* gf-max-winkel (/ pi 180.0)))
(if (vfl-wizard-aktiv) (vl-catch-all-apply 'vflw-gruppe-sep-gf-impl nil))
(princ (ssg-text "vfl-sep-vor-gf-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-nein"))
(setq sep-vor-gf (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2
"vfl-sep-vor-gf-frage") "1"))
(princ (ssg-text "vfl-sep-nach-gf-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-nein"))
(setq sep-nach-gf (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2
"vfl-sep-nach-gf-frage") "1"))
;; ES-Element bringt seinen eigenen Separator VOR dem
;; ES-Block schon mit - siehe Kommentar im Zweig B oben.
(if (and sep-nach-gf (= gf-kettenende-antwort "1"))
(progn
(vfl-meldung (ssg-text "vfl-sep-nach-durch-es-ueberschrieben"))
(setq sep-nach-gf nil)))
(setq sep-anzahl (+ (if sep-vor-gf 1 0) (if sep-nach-gf 1 0)))
(if (> sep-anzahl 0)
(setq deltaL (- deltaL (* sep-anzahl *vfk-separator-laenge* (cos rad3)))))
;; ES-Fussabdruck VORAB reservieren, siehe Kommentar bei
;; gf-kettenende-antwort oben.
(if (= gf-kettenende-antwort "1")
(setq deltaL (- deltaL (abs ein-dx))))
(if (< deltaL 1.0)
(progn (princ (ssg-text "vfl-fehler-linie-zu-kurz")) (setq gf-ok nil)))
(if gf-ok
(progn
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-gefaelle-impl
(list (vfl-gf-hoehe-vorschlag (caddr p-aktuell)) nil t)))
(princ (ssg-text "vfl-gefaelle-festlegen-header"))
(princ (ssg-text "vfl-gefaelle-opt-hoehe"))
(princ (ssg-text "vfl-gefaelle-opt-winkel"))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-gefaelle-opt-hoehe")
(ssg-text "vfl-gefaelle-opt-winkel")) 2
"vfl-gefaelle-festlegen-header"))
(if (= antwort "2")
(progn
;; Winkel direkt vorgeben - deltaH ergibt sich aus
;; deltaL*tan(winkel). deltaL ist hier schon um
;; Separator VOR/NACH reduziert.
(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))
(if (or (<= winkel 0.0) (> winkel gf-max-winkel))
(progn
(vfl-meldung (ssg-textf "vfl-alert-winkel-ungueltig"
(list (rtos winkel 2 1) (rtos gf-max-winkel 2 1))))
(setq gf-ok nil))
(progn
(setq richtung "Ab")
(setq deltaH (* deltaL (/ (sin (* winkel (/ pi 180.0)))
(cos (* winkel (/ pi 180.0))))))
)
)
)
(progn
;; Gegebene Hoehe - Winkel wird daraus abgeleitet
;; und gegen den GF-Maximalwinkel geprueft.
(setq hoehe-neu
(vfl-in-real (ssg-textf "vfl-prompt-hoehe-linienendpunkt"
(list (rtos (vfl-gf-hoehe-vorschlag (caddr p-aktuell)) 2 1)))))
(if (null hoehe-neu) (setq hoehe-neu (vfl-gf-hoehe-vorschlag (caddr p-aktuell))))
(setq deltaH (- hoehe-neu (caddr p-aktuell)))
(setq richtung (if (>= deltaH 0.0) "Auf" "Ab"))
(setq deltaH (abs deltaH))
(vfl-echo-endpunkt-hoehe p-aktuell deltaL hz-neu hoehe-neu deltaH richtung)
(if (> sep-anzahl 0)
(setq deltaH (max 0.0 (- deltaH (* sep-anzahl *vfk-separator-laenge* (sin rad3))))))
(cond
((= richtung "Auf")
(vfl-meldung (ssg-text "vfl-alert-gf-kann-nicht-steigen"))
(if dbg-an
(dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Linie-GF kann nicht steigen"
" (deltaL=" (rtos deltaL 2 0) " deltaH=" (rtos deltaH 2 0) ")")))
(setq gf-ok nil))
(t
(setq winkel (* (atan (/ deltaH deltaL)) (/ 180.0 pi)))
(if (> winkel gf-max-winkel)
(progn
(vfl-meldung (ssg-textf "vfl-alert-gefaelle-zu-steil"
(list (rtos winkel 2 1) (rtos gf-max-winkel 2 1))))
(if dbg-an
(dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Linie-GF zu steil (deltaL="
(rtos deltaL 2 0) " deltaH=" (rtos deltaH 2 0)
" winkel=" (rtos winkel 2 1) " max=" (rtos gf-max-winkel 2 1) ")")))
(setq gf-ok nil))
)
)
)
)
)
)
)
)
)
)
)
)
)
;; --- Gemeinsamer Abschluss fuer ALLE drei Zweige ---
(if gf-ok
(progn
;; Uebergangsbogen(n) IMMER pruefen (vfl-gf-bogen-uebergang ist ein
;; No-Op, wenn die aktuelle Frame-Neigung schon zur Ziel-Neigung
;; passt - eigene Pruefung intern via t0=basis-ziel). Frueher nur
;; fuer den Steigend-Sonderfall (winkel<0) und "Vorgaenger war
;; steigend" ausgeloest - das uebersah reale Faelle wie flach->
;; fallend (z.B. nach einem horizontalen GF-Stueck) und fallend->
;; flach (z.B. vor einem horizontalen GF-Stueck), die genauso einen
;; Bogen brauchen (Nutzer-Befund 2026-09-12: fehlende 3-Grad-Boegen
;; zwischen Separator/fallender GF und einer horizontalen GF).
(progn
(setq erg-bogen (vfl-gf-bogen-uebergang frame winkel))
(setq frame (car erg-bogen))
(setq deltaL (- deltaL (cadr erg-bogen)))
(if (< deltaL 1.0)
(progn (princ (ssg-text "vfl-fehler-linie-zu-kurz")) (setq gf-ok nil)))
)
(if gf-ok
(progn
;; Separator VOR (Antwort aus der Vorab-Frage oben) - VOR
;; dem GF-Segment selbst einfuegen, am GF-Winkel.
(if sep-vor-gf
(progn
(setq frame (make-frame-from-dir
(gf-insert-hz-with-ks "Staustrecke_Separator_SP_300_mm"
(car frame) hz-neu winkel *vfk-separator-laenge* 0)
(hz-winkel->xu hz-neu winkel)))
(ssg-sep-typ-xdata-schreiben (entlast) "GF")
(setq *vfl-acc-separator* (1+ *vfl-acc-separator*))
)
)
(if dbg-an
(dbgmsg (strcat "GEOMETRIE: vfl-insert-gf-segment (deltaL=" (rtos deltaL 2 0)
" hz=" (rtos hz-neu 2 1) " winkel=" (rtos winkel 2 1) ")")))
(setq frame (vfl-insert-gf-segment (car frame) hz-neu deltaL winkel))
(setq anzahl-gf (1+ anzahl-gf))
(vfl-acc-gf-seg (/ deltaL (cos (* (float winkel) (/ pi 180.0)))) winkel)
(setq letzter-typ "GF")
(setq p-aktuell (car frame))
(if dbg-an
(dbgmsg (strcat "GEOMETRIE ERFOLG: GF-Segment gebaut, neue Position="
(dbg-tostring p-aktuell))))
;; Separator NACH (Antwort aus der Vorab-Frage oben) - DIREKT
;; nach dem GF-Segment einfuegen, noch VOR der Kettenende-
;; Frage.
(if sep-nach-gf
(progn
(setq frame (make-frame-from-dir
(gf-insert-hz-with-ks "Staustrecke_Separator_SP_300_mm"
(car frame) hz-neu winkel *vfk-separator-laenge* 0)
(hz-winkel->xu hz-neu winkel)))
(ssg-sep-typ-xdata-schreiben (entlast) "GF")
(setq *vfl-acc-separator* (1+ *vfl-acc-separator*))
(setq letzter-typ "Separator")
(setq p-aktuell (car frame))
)
)
;; Kettenende/ES: BEREITS vor der Laengen-/Gefaelle-Berechnung
;; erfragt (gf-kettenende-antwort/es-seite oben) - hier NICHT
;; nochmal fragen (waere doppelt journalisiert und ein-dx/
;; ein-dz wurden dort schon fuer die Reservierung genutzt).
(cond
((= gf-kettenende-antwort "1")
;; ES-Element-Hardware gibt es nur fuer die Anfahrt auf der
;; STANDARD Fallend-Basis (+gefaelle-winkel) - kommt die
;; Kette hier steigend ODER FLACH an (z.B. Zweig C mit
;; winkel=0), fehlt sonst der Uebergangsbogen und das
;; ES-Element wuerde in der falschen Orientierung
;; angehaengt. IMMER pruefen (nicht nur bei winkel<0) -
;; vfl-gf-bogen-uebergang ist bei einer bereits fallenden
;; Ankunft ein No-Op (Nutzer-Befund 2026-09-12: horizontal
;; vor ES fehlte der 3-Grad-Bogen).
(setq erg-bogen (vfl-gf-bogen-uebergang frame *vfk-gefaelle-winkel*))
(setq frame (car erg-bogen))
(setq winkel *vfk-gefaelle-winkel*)
(setq frame (vfl-insert-es-element "GF" frame hz-neu winkel
(caddr (car frame)) es-seite))
(setq fertig t))
((= gf-kettenende-antwort "2") (setq fertig t))
)
)
)
)
)
) ;; Ende (progn ...) Zweig A/B/C (gf-laenge-modus <> "4"/"5")
) ;; Ende (t ...)
) ;; Ende (cond (gf-laenge-modus "4"/"5"/sonst) ...)
)
;; --- Neue Linie: Ab/Auf VF (Nutzer legt den Typ explizit fest) ---
((= wahl "Linie-VF")
(setq linie-mess (vfl-neue-linie-messen p-aktuell
(if frame (car (frame->hz-winkel frame)) nil)))
(if (null linie-mess) (progn (princ (ssg-text "vfl-abgebrochen")) (exit)))
(setq deltaL (car linie-mess) hz-neu (cadr linie-mess))
(setq kettenanfang (null frame))
;; Kettenanfang: AS-Element (falls gewuenscht) SOFORT einfuegen, reale
;; Restlaenge aus dem tatsaechlichen KS_AUS nachrechnen (siehe
;; vfl-kettenanfang-baustein).
(if kettenanfang
(progn
(setq erg (vfl-kettenanfang-baustein p-aktuell hz-neu deltaL as-seite as-vorhanden))
(setq frame (nth 0 erg) deltaL (nth 1 erg) p-aktuell (car frame))
)
)
(if (< deltaL *vfk-vf-mindestlaenge*)
(princ (ssg-text "vfl-fehler-vf-kurz"))
(progn
;; Eingang haengt vom VORHERIGEN Kettenglied ab (letzter-typ) bzw.
;; am echten Kettenanfang vom KS_AUS-Scan - siehe
;; vfl-eingang-modus-bestimmen (gemeinsam mit "Neue VF mit
;; horizontalem Anfang").
(setq erg (vfl-eingang-modus-bestimmen letzter-typ kettenanfang as-vorhanden p-aktuell))
(setq L_GF (car erg) vf-eingang-modus (cdr erg))
(if vf-eingang-modus
;; Reduzierter Eingang (Separator/GF/keine): L_GF ist danach immer
;; 0.0 (vfl-vf-einheit zerlegt L_GF-eff sowieso auf 0.0) - keine
;; frei waehlbare GF-Laenge mehr, mit der ein 2-Unbekannten-Solve
;; (vfl-vf-entscheidung) eine beliebige Zielhoehe treffen koennte.
;; Daher direkt Richtung+Winkel/Hoehen-Tabelle statt Zielhoehe-
;; Abfrage (siehe vfl-eingang-modus-bestimmen).
(progn
(princ (ssg-text "vfl-frage-auf-ab-richtung"))
(princ (ssg-text "vfl-opt-richtung-auf"))
(princ (ssg-text "vfl-opt-richtung-ab"))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-richtung-impl
(list (ssg-text "vfl-frage-auf-ab-richtung"))))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-opt-richtung-auf")
(ssg-text "vfl-opt-richtung-ab")) 1
"vfl-frage-auf-ab-richtung"))
(setq richtung (if (= antwort "2") "Ab" "Auf"))
;; Anders als bei "keine" (Vario-Kurve, dort wird NICHTS gebaut)
;; wird bei "gf"/"separator" tatsaechlich noch Separator+
;; Umlenkstation (bzw. nur Umlenkstation) eingefuegt, BEVOR der
;; Kletter-Koerper beginnt - das verbraucht reale Laenge/Hoehe
;; (beide auf 3-Grad-Basis), die hier VOR dem Tabellen-Aufruf
;; abgezogen werden muss, sonst waere die Tabelle zu optimistisch.
(setq eingang-laenge
(cond
((equal vf-eingang-modus "gf") (+ *vfk-separator-laenge* *vfk-stations-laenge*))
((equal vf-eingang-modus "separator") *vfk-stations-laenge*)
(t 0.0)))
(setq winkel-hoehen-erg
(vfl-waehle-winkel-hoehen-tabelle
(- deltaL (* eingang-laenge (cos (* *vfk-gefaelle-winkel* (/ pi 180.0)))))
richtung
(- (caddr p-aktuell) (* eingang-laenge (sin (* *vfk-gefaelle-winkel* (/ pi 180.0)))))))
(if (null winkel-hoehen-erg)
(princ (ssg-text "vfl-linie-zu-kurz-simple"))
(progn
(setq winkel (nth 0 winkel-hoehen-erg) L_VF (nth 2 winkel-hoehen-erg))
(if dbg-an
(dbgmsg (strcat "GEOMETRIE: vfl-vf-einheit-abschluss reduzierter Eingang (deltaL="
(rtos deltaL 2 0) " richtung=" richtung
" winkel=" (rtos (float winkel) 2 1)
" L_VF=" (rtos L_VF 2 0)
" eingang-modus=" vf-eingang-modus ")")))
(setq vf-einheit-res
(vfl-vf-einheit-abschluss frame hz-neu richtung winkel L_GF L_VF nil nil nil "2" vf-eingang-modus
nil nil nil nil))
(setq frame (nth 0 vf-einheit-res))
(setq anzahl-vf (+ anzahl-vf (nth 1 vf-einheit-res)))
(if (nth 2 vf-einheit-res) (setq fertig t))
(if (nth 3 vf-einheit-res) (setq es-seite (nth 3 vf-einheit-res)))
(setq letzter-typ "VF")
(setq p-aktuell (car frame))
(if dbg-an
(dbgmsg (strcat "GEOMETRIE ERFOLG: VF-Einheit reduzierter Eingang gebaut, neue Position="
(dbg-tostring p-aktuell))))
)
)
)
(progn
;; Erst fragen, ob DIESER Endpunkt schon das Kettenende ist (+
;; falls ja: ES-Element setzen), DANN erst Zielhoehe/Winkel
;; berechnen - analog zu vfl-baue-horizontal-koerper (horizontaler
;; Erstkoerper). Vorher wurde die Frage erst NACH dem Bau des
;; ersten (Winkel-)Segments gestellt (in der Fortsetzungsschleife
;; von vfl-vf-einheit) - dann war ein evtl. noetiger ES-
;; Fussabdruck (Motorstation+Separator+ES) beim Winkel-Solve noch
;; nicht reserviert, der gewaehlte Winkel konnte zu viel Strecke
;; verbrauchen und der zwingend zweite, separate Endpunkt-Pick
;; fuer den "Schwanz" schlug dann mangels Reststrecke fehl (Nutzer-
;; Ruecksprache). ein-dx/ein-dz (ES-Fussabdruck) wirken bereits
;; automatisch in vfl-vf-hoehe-vorschlaege/vfl-vf-entscheidung
;; (ueber vfl-winkel-machbar-p) - hier muss nur die Reihenfolge
;; stimmen.
(setq vorab-antwort (vfl-frage-ist-endpunkt))
(setq vorab-es-gewuenscht nil vorab-es-seite nil
save-eindx-vorab nil save-eindz-vorab nil)
(setq sep-eindx-add 0.0 sep-eindz-add 0.0)
(if (= vorab-antwort "3")
(progn
(setq erg-es (vfl-es-element-abfragen))
(setq vorab-es-gewuenscht (nth 0 erg-es) vorab-es-seite (nth 1 erg-es)
save-eindx-vorab (nth 2 erg-es) save-eindz-vorab (nth 3 erg-es))
;; vfl-insert-es-element baut IMMER einen eigenen Separator
;; (300mm, 3 Grad) VOR dem ES-Block - das ist NICHT Teil des
;; ueber vf-set-es-masse gemessenen ein-dx/ein-dz (die messen
;; nur den ES-Block selbst, siehe Bibliotheks-Init-Log
;; "ES_90: dx=436 dz=-65"). Fuer den Solve zusaetzlich
;; reservieren, sonst fehlt genau dieser Fussabdruck im
;; Ergebnis - real gemessener Bug (BricsCAD-Test): dX=-299.6mm
;; dZ=-15.7mm, exakt der Separator-Fussabdruck bei 3 Grad
;; (300*cos3=299.6, 300*sin3=15.7). Nach dem Solve/Bau wieder
;; abgezogen (siehe Restaurierung unten), damit ein-dx/ein-dz
;; danach wieder den reinen ES-Block-Fussabdruck zeigen.
(if vorab-es-gewuenscht
(progn
(setq rad3-sep (* (float (ssg-cfg-or "vario" "gefaelle_winkel" 3)) (/ pi 180.0)))
(setq sep-eindx-add (* *vfk-separator-laenge* (cos rad3-sep))
sep-eindz-add (* *vfk-separator-laenge* (sin rad3-sep)))
(setq ein-dx (+ ein-dx sep-eindx-add)
ein-dz (- ein-dz sep-eindz-add))))))
(setq hoehe-vorschlaege (vfl-vf-hoehe-vorschlaege (caddr p-aktuell) deltaL))
(setq hoehe-ab (nth 0 hoehe-vorschlaege) hoehe-auf (nth 1 hoehe-vorschlaege))
(setq hoehe-neu (vfl-hoehe-endpunkt-abfragen hoehe-ab hoehe-auf (caddr p-aktuell) "linienendpunkt"))
(setq deltaH (- hoehe-neu (caddr p-aktuell)))
(setq richtung (if (>= deltaH 0.0) "Auf" "Ab"))
(setq deltaH (abs deltaH))
(vfl-echo-endpunkt-hoehe p-aktuell deltaL hz-neu hoehe-neu deltaH richtung)
;; Absoluter Zielpunkt dieses (einzigen) Picks - nur bei "3"
;; gebraucht: wird bis nach vfl-vf-einheit durchgereicht, damit
;; vfl-body-abschluss NICHT nochmal denselben Punkt abfragt
;; (siehe vfl-vf-einheit/vfl-body-abschluss, vorab-ziel-punkt).
(setq vorab-ziel-punkt
(if (= vorab-antwort "3")
(list (+ (car p-aktuell) (* deltaL (cos (* (float hz-neu) (/ pi 180.0)))))
(+ (cadr p-aktuell) (* deltaL (sin (* (float hz-neu) (/ pi 180.0)))))
hoehe-neu)
nil))
;; Nutzer-Entscheidung (2026-09-15): der Hauptstrang (GF1/Klettern/
;; GF2/Motorstation) behaelt IMMER die urspruenglich gepickte
;; Fahrtrichtung (hz-neu/deltaL bleiben unveraendert) - KEINE
;; Vorab-Drehung mehr, um den seitlichen ES-Versatz (ein-dy)
;; auszugleichen. Ein drehendes ES-Element (30/90 Grad) landet
;; dadurch mit einem natuerlichen, unvermeidbaren seitlichen
;; Versatz neben dem urspruenglich gepickten Punkt - das ist
;; erwartetes Verhalten (Prinzip: Hauptstrang bleibt gerade, ES
;; faellt "natuerlich" dorthin, wohin sein eigener Fussabdruck
;; fuehrt), keine zu behebende Abweichung. ein-dx/ein-dz (Laengs/
;; Hoehe) werden weiterhin ganz normal ueber vfl-winkel-machbar-p
;; reserviert (siehe vfl-vf-entscheidung).
(setq entscheidung (vfl-vf-entscheidung deltaL deltaH richtung))
(setq typ (nth 0 entscheidung) winkel (nth 1 entscheidung)
L_GF (nth 2 entscheidung) L_VF (nth 3 entscheidung)
lhor (nth 4 entscheidung) stufe3-position (nth 5 entscheidung))
(if (null typ)
(progn
(vfl-meldung (ssg-textf "vfl-alert-vf-nicht-baubar"
(list (rtos deltaL 2 0) (rtos deltaH 2 0) richtung)))
;; Gesammelte Stufe-2-Diagnose (falls versucht) + kompaktes
;; 2-Punkte-Alert mit Mindestwerten - analog vfl-body-abschluss.
(if *vfl-stufe2-diagnose-text* (princ *vfl-stufe2-diagnose-text*))
(vfl-meldung
(strcat
(ssg-textf "vfl-alert-vf-entscheidung-p1" (list (rtos deltaL 2 0) (rtos deltaH 2 0) richtung))
(cond
((and *vfl-stufe2-minl-vorschlag* *vfl-stufe2-minh-vorschlag*)
(ssg-textf "vfl-alert-vf-entscheidung-p2-beide"
(list (rtos *vfl-stufe2-minl-vorschlag* 2 0) (rtos *vfl-stufe2-minh-vorschlag* 2 0))))
(*vfl-stufe2-minh-vorschlag*
(ssg-textf "vfl-alert-vf-entscheidung-p2-nur-minh" (list (rtos *vfl-stufe2-minh-vorschlag* 2 0))))
(*vfl-stufe2-minl-vorschlag*
(ssg-textf "vfl-alert-vf-entscheidung-p2-nur-minl" (list (rtos *vfl-stufe2-minl-vorschlag* 2 0))))
(t ""))))
(if dbg-an
(dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Linie-VF nicht baubar (deltaL="
(rtos deltaL 2 0) " deltaH=" (rtos deltaH 2 0)
" richtung=" richtung ")"))))
(progn
(if dbg-an
(dbgmsg (strcat "GEOMETRIE: vfl-vf-einheit-abschluss (deltaL=" (rtos deltaL 2 0)
" deltaH=" (rtos deltaH 2 0) " richtung=" richtung
" winkel=" (rtos (float winkel) 2 1)
" L_GF=" (rtos L_GF 2 0) " L_VF=" (rtos L_VF 2 0) ")")))
(setq vf-einheit-res
(vfl-vf-einheit-abschluss frame hz-neu richtung winkel L_GF L_VF lhor stufe3-position nil nil nil
vorab-antwort vorab-es-gewuenscht vorab-es-seite
vorab-ziel-punkt))
(setq frame (nth 0 vf-einheit-res))
(setq anzahl-vf (+ anzahl-vf (nth 1 vf-einheit-res)))
(if (nth 2 vf-einheit-res) (setq fertig t))
(if (nth 3 vf-einheit-res) (setq es-seite (nth 3 vf-einheit-res)))
(setq letzter-typ "VF")
(setq p-aktuell (car frame))
(if dbg-an
(dbgmsg (strcat "GEOMETRIE ERFOLG: VF-Einheit gebaut, neue Position="
(dbg-tostring p-aktuell))))
)
)
;; ein-dx/ein-dz (ES-Fussabdruck) nach diesem gesamten Aufruf
;; wiederherstellen - sowohl im Erfolgs- als auch im "nicht
;; baubar"-Fehlerfall, sonst wuerde eine genullte/aufaddierte
;; ES-Groesse in eine spaetere, unabhaengige ES-Entscheidung an
;; anderer Stelle in dieser Kette durchsickern: bei "ES nein" auf
;; den Zustand VOR der Frage zuruecksetzen, bei "ES ja" die oben
;; addierte Separator-Reservierung wieder abziehen (ein-dx/ein-dz
;; zeigen danach wieder den reinen ES-Block-Fussabdruck).
(if (= vorab-antwort "3")
(if vorab-es-gewuenscht
(setq ein-dx (- ein-dx sep-eindx-add) ein-dz (+ ein-dz sep-eindz-add))
(setq ein-dx save-eindx-vorab ein-dz save-eindz-vorab)))
)
)
)
)
)
((= wahl "Linie") ;; --- Neue Linie BIS Kettenende ---
;; GF/VF-Entscheidung: wird der Startpunkt eindeutig einer bestehenden
;; VF- oder GF-Kette zugeordnet (linie-ende-familie), entscheidet DAS
;; die Bau-Logik - nur wenn nicht eindeutig, greift die automatische
;; Machbarkeits-Entscheidung (vfl-segment-entscheidung).
(setq kettenanfang (null frame))
;; Startpunkt-Typ erkennen: bei einer laufenden Fortsetzung ist der
;; Vorgaengertyp schon bekannt (letzter-typ); am echten Kettenanfang
;; OHNE AS-Element wird wie bei vfl-eingang-modus-bestimmen per
;; KS_AUS-Scan erkannt (Rueckgabe (hz . typ) - typ wird bei VF-Familie
;; unten fuer die Flach/3-Grad-Unterscheidung gebraucht). Steht schon
;; ein AS-Element (as-vorhanden), ist der Anfang per Definition
;; neutral/nicht zuordenbar.
(setq erg (if (and kettenanfang (not as-vorhanden))
(vfl-ks-aus-hz-an-punkt p-aktuell)
nil))
(setq linie-ende-familie
(if kettenanfang
(if erg (vfl-kette-typ-familie (cdr erg)) nil)
(vfl-letzter-typ-familie letzter-typ)))
(cond
;; --- Startpunkt als VF-Familie erkannt: KEINE eigene Umlenkstation
;; noetig (Vorgaenger Vario-Kurve/Umlenk/Motor/Bogen bzw. laufende
;; VF-Fortsetzung). Strukturell IDENTISCH zu "Option 3: Kettenende
;; exakt am Zielpunkt" INNERHALB einer laufenden VF-Einheit
;; (vfl-vf-einheit): derselbe Stufe-1/Stufe-2-Diagnose-Baustein
;; (vfl-body-abschluss, Budget OHNE Umlenkstation -
;; *vfk-feste-horizontal-kettenende*) + Motorstation/GF2
;; (vfl-vf-ausgang-bauen) + ES-Element - KEIN Rueckfall auf eine
;; reine Winkeltabelle noetig (anders als bei "Neue Linie: Ab/Auf
;; VF" mit reduziertem Eingang): hier gibt es keinen GF1-Stumpf,
;; dessen Wegfall die 2-Unbekannten-Loesung stoeren wuerde - die
;; volle Freiheit (Winkel + GF2) bleibt erhalten, exakte Zielhoehe
;; ist also weiterhin moeglich.
((equal linie-ende-familie "VF")
(if kettenanfang
;; Kettenanfang auf einer bestehenden VF-Kette: Frame
;; konstruieren (Vario-Kurve endet flach, alle anderen VF-Typen
;; auf der 3-Grad-Basis) - der eigentliche Endpunkt-Pick erfolgt
;; gleich INNERHALB von vfl-body-abschluss.
(setq frame (make-frame-from-dir p-aktuell
(hz-winkel->xu (car erg)
(if (equal (cdr erg) "KURVE") 0.0 *vfk-gefaelle-winkel*)))))
;; ES-Element hier gewuenscht (+ falls ja: Winkel/Seite) - wie bei
;; "Option 3" (vfl-vf-einheit) VOR vfl-body-abschluss erfragt, weil
;; der tatsaechliche ES-Fussabdruck (ein-dx/ein-dz) die
;; Streckenzerlegung beeinflusst.
(princ (ssg-text "vfl-es-setzen-frage"))
(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")))
(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"))
(setq es-gewuenscht (/= antwort "2"))
(if es-gewuenscht
(setq es-seite (vfl-frage-es-seite t))
(progn (setq save-eindx ein-dx save-eindz ein-dz) (setq ein-dx 0.0 ein-dz 0.0)))
(setq res3 (vfl-body-abschluss frame (car (frame->hz-winkel frame)) (car frame) es-gewuenscht nil))
(if (not es-gewuenscht) (setq ein-dx save-eindx ein-dz save-eindz))
(if (null res3)
(if dbg-an
(dbgmsg "GEOMETRIE FEHLGESCHLAGEN: Linie (VF-Familie, Kettenende) nicht baubar"))
(progn
(setq anzahl-vf (+ anzahl-vf (nth 1 res3)))
(setq frame (vfl-vf-ausgang-bauen (nth 0 res3) (nth 2 res3) (nth 3 res3)))
(if es-gewuenscht
(setq frame (vfl-insert-es-element "VF" frame (car (frame->hz-winkel frame)) 0.0
(caddr (car frame)) es-seite)))
(vfl-ziel-report frame)
(setq letzter-typ "VF" p-aktuell (car frame) fertig t)
(if dbg-an
(dbgmsg (strcat "GEOMETRIE ERFOLG: Linie (VF-Familie, Kettenende) gebaut, neue Position="
(dbg-tostring p-aktuell))))
)
)
)
;; --- Startpunkt als GF-Familie erkannt ODER nicht eindeutig ---
(t
;; Fahrtrichtung folgt dem vorherigen Element (Frame); nur das
;; erste Segment (frame=nil) definiert die Richtung frei.
(setq linie-mess (vfl-neue-linie-messen p-aktuell
(if frame (car (frame->hz-winkel frame)) nil)))
(if (null linie-mess) (progn (princ (ssg-text "vfl-abgebrochen")) (exit)))
(setq deltaL (car linie-mess) hz-neu (cadr linie-mess))
(if (< deltaL 1.0)
(princ (ssg-text "vfl-fehler-linie-zu-kurz"))
(progn
;; Kettenanfang: AS-Element (falls gewuenscht) SOFORT einfuegen
;; (immer flach, typ-unabhaengig - siehe vfl-insert-as-element;
;; welcher Segmenttyp folgt, steht hier noch nicht fest), reale
;; Restlaenge aus dem tatsaechlichen KS_AUS nachrechnen (siehe
;; vfl-kettenanfang-baustein).
(if kettenanfang
(progn
(setq erg (vfl-kettenanfang-baustein p-aktuell hz-neu deltaL as-seite as-vorhanden))
(setq frame (nth 0 erg) deltaL (nth 1 erg) p-aktuell (car frame))
)
)
;; Nach dem AS-Einfuegen kann die reale Restlaenge (aus dem
;; tatsaechlichen KS_AUS neu gemessen) kleiner als der urspruenglich
;; gepickte Rohwert sein - bei einem knapp gepickten Punkt sogar
;; NEGATIV. Ohne diese zweite Pruefung wuerde die Kurzsegment-
;; Automatik unten (< 1000mm) mit negativem deltaL trotzdem ein
;; "erfolgreiches" GF-Segment bauen (entartete/unsichtbare Strecke,
;; das naechste Element - z.B. ein GF-Bogen - erscheint dann direkt
;; am AS-Element, obwohl der Nutzer davor noch eine Linie wollte).
(if (< deltaL 1.0)
(princ (ssg-text "vfl-fehler-linie-zu-kurz"))
(progn
;; Segment-Typ bestimmen. Kurzsegment (deltaL < 1000 mm): KEINE
;; Hoehenabfrage - automatisch 3-Grad-Gefaellestrecke. Grund: ein VF
;; braucht >= 1000 mm deltaL (Umlenk- + Motorstation = 500+500 mm),
;; und eine GF ist mindestens 3 Grad geneigt. Die Endhoehe ergibt
;; sich damit fest aus deltaH = deltaL*tan(3 Grad).
;; Im Kettenende-Modus (Option 4) wird IMMER nach der Zielhoehe
;; gefragt (Kurzsegment-Automatik hier ueberspringen).
(if (and (< deltaL *vfk-vf-mindestlaenge*) (not linie-ende-modus))
(progn
(setq typ "GF" winkel *vfk-gefaelle-winkel* richtung "Ab" L_GF nil L_VF nil)
(setq deltaH (* deltaL (/ (sin (* *vfk-gefaelle-winkel* (/ pi 180.0)))
(cos (* *vfk-gefaelle-winkel* (/ pi 180.0))))))
(setq hoehe-neu (- (caddr p-aktuell) deltaH))
(princ (ssg-textf "vfl-info-kurzes-segment"
(list (rtos deltaL 2 0) (rtos deltaH 2 1))))
)
(progn
(setq hoehe-vorschlaege (vfl-segment-hoehe-vorschlaege (caddr p-aktuell) deltaL))
(setq hoehe-ab (nth 0 hoehe-vorschlaege) hoehe-auf (nth 1 hoehe-vorschlaege))
(setq hoehe-neu (vfl-hoehe-endpunkt-abfragen hoehe-ab hoehe-auf (caddr p-aktuell) "linienendpunkt"))
(setq deltaH (- hoehe-neu (caddr p-aktuell)))
(setq richtung (if (>= deltaH 0.0) "Auf" "Ab"))
(setq deltaH (abs deltaH))
(vfl-echo-endpunkt-hoehe p-aktuell deltaL hz-neu hoehe-neu deltaH richtung)
;; Kettenende-Modus (Option 6, "Neue Linie BIS Kettenende"):
;; ES-Element wird HIER IMMER gebaut (das verspricht schon
;; der Menuetext) - Winkel/Seite deshalb JETZT erfragen
;; (vfl-frage-es-seite, setzt ein-dx/ein-dz via
;; vf-set-es-masse), BEVOR die Fussabdruck-Reservierung
;; unten laeuft - vorher liefen beide mit veralteten/
;; zufaelligen ein-dx/ein-dz (die echte Frage kam bisher
;; erst spaeter, redundant, INNERHALB von vfl-vf-einheit -
;; Nutzer-Ruecksprache: derselbe Fehler wie bei "Neue
;; Linie: Ab/Auf VF", hier nur eine Stufe frueher versteckt).
;; vorab-ziel-punkt (der bereits gepickte Zielpunkt) wird bis
;; zu vfl-vf-einheit durchgereicht, damit fuer den "Auf"-Fall
;; (VF-Einheit noetig) KEIN zweiter Klick mehr noetig ist.
(setq vorab-antwort "3" vorab-es-gewuenscht t)
(setq vorab-es-seite (vfl-frage-es-seite nil))
(setq vorab-ziel-punkt
(list (+ (car p-aktuell) (* deltaL (cos (* (float hz-neu) (/ pi 180.0)))))
(+ (cadr p-aktuell) (* deltaL (sin (* (float hz-neu) (/ pi 180.0)))))
hoehe-neu))
;; Kettenende-Modus (Option 4): Der abgefragte Endpunkt (XY+Z)
;; ist das exakte Ende der Kette NACH Separator + ES-Element.
;; Deren Footprint (Separator 300 mm bei ~3 Grad + ES-Element
;; ein-dx/ein-dz) muss vom baubaren GF/VF-Segment reserviert
;; werden, damit das ES-Element genau am Zielpunkt endet.
;; Naeherung: Separator-Neigung = 3 Grad (Auslauf).
;; LAENGE: reiner Budget-Verbrauch, IMMER abziehen (Separator+ES
;; nehmen unabhaengig von der Richtung ein Stueck der Gesamt-
;; Spannweite weg).
;; HOEHE: Separator+ES senken IMMER ab (Ausgleichsprinzip,
;; unabhaengig von "Auf"/"Ab"). Bei "Ab" darf man das wie
;; bisher von deltaH ABZIEHEN (Kletterer+GF muessen dann
;; entsprechend WENIGER absinken, der Rest erledigt der
;; Schwanz). Bei "Auf" ist das GENAU VERKEHRT: der Schwanz
;; senkt NACH dem Solve nochmal ab, also muss Kletterer+GF
;; VORHER entsprechend MEHR steigen, um das wieder
;; auszugleichen - hier muss ADDIERT werden. Exakt dieselbe
;; Auf/Ab-Asymmetrie wie in berechne-alle-winkel selbst
;; (dort +feste-hz*sin3 bei Auf, -feste-hz*sin3 bei Ab -
;; siehe vf_standard.lsp). Realer Bug (falsches Vorzeichen
;; bei "Auf"): ES landete ca. 2*(Separator+ES-Hoehe) zu tief
;; (Livetest 2026-09-18, Abweichung 161mm bei Separator*sin3+
;; |ein-dz| = 15.7+65 = 80.7mm - genau die Haelfte).
(if linie-ende-modus
(progn
(setq rad3 (* (float (ssg-cfg-or "vario" "gefaelle_winkel" 3))
(/ pi 180.0)))
(setq deltaL (max 1.0 (- deltaL (* *vfk-separator-laenge* (cos rad3))
(abs (if ein-dx ein-dx 0.0)))))
(setq deltaH
(if (= richtung "Auf")
(+ deltaH (* *vfk-separator-laenge* (sin rad3))
(abs (if ein-dz ein-dz 0.0)))
(max 0.0 (- deltaH (* *vfk-separator-laenge* (sin rad3))
(abs (if ein-dz ein-dz 0.0))))))
(princ (ssg-textf "vfl-info-kettenende-footprint"
(list (rtos deltaL 2 0) (rtos deltaH 2 0))))
)
)
;; ein-dx/ein-dz waehrend der Typ-/Winkel-Entscheidung
;; (vfl-vf-entscheidung/vfl-segment-entscheidung, nur im
;; "Auf"-Fall relevant) temporaer nullen: der ES-Fussabdruck
;; wurde oben BEREITS von deltaL/deltaH abgezogen - eine
;; zweite Reservierung INNERHALB des Solvers waere doppelt
;; (analog zu vfl-vf-winkel, das aus-dx/aus-dz aus demselben
;; Grund temporaer nullt). Nach der Entscheidung sofort
;; wiederhergestellt - der spaetere Bau (vfl-vf-einheit)
;; braucht die echten Werte nicht mehr (er nutzt L_GF2-bau
;; direkt), aber andere Stellen in dieser Kette schon.
(setq save-eindx-vorab ein-dx save-eindz-vorab ein-dz)
(setq ein-dx 0.0 ein-dz 0.0)
;; Aus einer fruehren Schleifen-Iteration stehengebliebene
;; Stufe-3-Werte NICHT versehentlich fuer dieses Segment
;; wiederverwenden - nur die beiden entscheidung-Zweige
;; unten setzen sie ggf. neu.
(setq lhor nil stufe3-position nil)
(if (equal linie-ende-familie "GF")
(progn
;; --- Startpunkt als GF-Familie erkannt ---
;; Dieselbe Endpunkt-/Zielhoehe-Wahl wie "Neue
;; Linie: GF" (Zweig A/B) statt der generischen
;; Automatik - eine reine Gefaellestrecke kann
;; nicht steigen. Stufe-2-Diagnosevariablen
;; zuruecksetzen: dieser Zweig ruft
;; vfl-vf-entscheidung NICHT auf, ein evtl. noch
;; von einem frueheren Versuch gesetzter Text/
;; Vorschlag darf bei einem hier fehlschlagenden
;; GF-Segment nicht faelschlich mit angezeigt
;; werden (siehe gemeinsamer "typ=nil"-
;; Meldungsblock unten).
(setq *vfl-stufe2-diagnose-text* nil)
(setq *vfl-stufe2-minl-vorschlag* nil *vfl-stufe2-minh-vorschlag* nil)
(if (= richtung "Auf")
;; Steigend: wie im "nicht eindeutig"-Zweig
;; (vfl-segment-entscheidung) NICHT sofort
;; aufgeben - eine bestehende GF-Kette darf
;; durchaus in eine frische VF-Einheit (eigene
;; Umlenk-/Motorstation) uebergehen. Direkt
;; vfl-vf-entscheidung aufrufen (identisch zu dem,
;; was vfl-segment-entscheidung fuer "Auf" ohnehin
;; getan haette) - der gemeinsame Baubereich unten
;; (typ="GF" vs. sonst) baut ein typ="VF"-Ergebnis
;; bereits unveraendert ueber vfl-vf-einheit-
;; abschluss (mit eigener Umlenkstation), genau wie
;; bei "Neue Linie: Ab/Auf VF". Realer Bug-Fall:
;; vorher wurde hier IMMER typ=nil gesetzt, obwohl
;; ein baubarer VF-Weg existierte (Nutzer-Meldung).
(setq entscheidung (vfl-vf-entscheidung deltaL deltaH richtung)
typ (nth 0 entscheidung) winkel (nth 1 entscheidung)
L_GF (nth 2 entscheidung) L_VF (nth 3 entscheidung)
lhor (nth 4 entscheidung) stufe3-position (nth 5 entscheidung))
(progn
(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"))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-gf-steigend-untermodus-impl nil))
(setq linie-ende-untermodus
(vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-gf-laenge-modus-opt-endpunkt")
(ssg-text "vfl-gf-laenge-modus-opt-hoehe")) 1
"vfl-gf-laenge-modus-header"))
(if (= linie-ende-untermodus "2")
(progn
(setq winkel *vfk-gefaelle-winkel*)
(setq deltaL
(/ deltaH (/ (sin (* winkel (/ pi 180.0))) (cos (* winkel (/ pi 180.0))))))
(if (< deltaL 1.0)
(setq typ nil)
(setq typ "GF" L_GF nil L_VF nil)))
(progn
(setq winkel (* (atan (/ deltaH deltaL)) (/ 180.0 pi)))
(if (> winkel *vfk-gefaelle-winkel*)
(setq typ nil)
(setq typ "GF" L_GF nil L_VF nil)))
)
)
)
)
;; --- Nicht eindeutig zuordenbar: bisherige Automatik ---
(progn
(setq entscheidung (vfl-segment-entscheidung deltaL deltaH richtung))
(setq typ (nth 0 entscheidung) winkel (nth 1 entscheidung)
L_GF (nth 2 entscheidung) L_VF (nth 3 entscheidung)
lhor (nth 4 entscheidung) stufe3-position (nth 5 entscheidung)))
)
)
)
;; ein-dx/ein-dz wiederherstellen (siehe Nullung oben).
(setq ein-dx save-eindx-vorab ein-dz save-eindz-vorab)
(if (null typ)
(progn
(vfl-meldung (ssg-textf "vfl-alert-segment-nicht-baubar"
(list (rtos deltaL 2 0) (rtos deltaH 2 0) richtung)))
;; Gesammelte Stufe-2-Diagnose (falls versucht) + kompaktes
;; 2-Punkte-Alert mit Mindestwerten - analog vfl-body-abschluss.
(if *vfl-stufe2-diagnose-text* (princ *vfl-stufe2-diagnose-text*))
(vfl-meldung
(strcat
(ssg-textf "vfl-alert-vf-entscheidung-p1" (list (rtos deltaL 2 0) (rtos deltaH 2 0) richtung))
(cond
((and *vfl-stufe2-minl-vorschlag* *vfl-stufe2-minh-vorschlag*)
(ssg-textf "vfl-alert-vf-entscheidung-p2-beide"
(list (rtos *vfl-stufe2-minl-vorschlag* 2 0) (rtos *vfl-stufe2-minh-vorschlag* 2 0))))
(*vfl-stufe2-minh-vorschlag*
(ssg-textf "vfl-alert-vf-entscheidung-p2-nur-minh" (list (rtos *vfl-stufe2-minh-vorschlag* 2 0))))
(*vfl-stufe2-minl-vorschlag*
(ssg-textf "vfl-alert-vf-entscheidung-p2-nur-minl" (list (rtos *vfl-stufe2-minl-vorschlag* 2 0))))
(t ""))))
(if dbg-an
(dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Linie (automatisch) nicht baubar"
" (deltaL=" (rtos deltaL 2 0) " deltaH=" (rtos deltaH 2 0)
" richtung=" richtung ")"))))
(progn
(if (= typ "GF")
(progn
;; --- reine Gefaellestrecke ---
(if dbg-an
(dbgmsg (strcat "GEOMETRIE: vfl-insert-gf-segment (deltaL=" (rtos deltaL 2 0)
" hz=" (rtos hz-neu 2 1) " winkel=" (rtos (float winkel) 2 1) ")")))
(setq frame (vfl-insert-gf-segment (car frame) hz-neu deltaL winkel))
(setq anzahl-gf (1+ anzahl-gf))
;; GF-Segment erfassen (Schraeglaenge = deltaL/cos(winkel))
(vfl-acc-gf-seg (/ deltaL (cos (* (float winkel) (/ pi 180.0)))) winkel)
(setq letzter-typ "GF")
(setq p-aktuell (car frame))
(if dbg-an
(dbgmsg (strcat "GEOMETRIE ERFOLG: GF-Segment gebaut, neue Position="
(dbg-tostring p-aktuell))))
;; Kettenende-Modus (Option 6): ES-Element direkt setzen,
;; Winkel/Seite wurden bereits VOR dem Solve erfragt
;; (vorab-es-seite) - kein "Ist Kettenende?" noetig,
;; linie-ende-modus ist in diesem "Linie"-Zweig immer T.
(setq es-seite vorab-es-seite)
(setq frame (vfl-insert-es-element "GF" frame hz-neu winkel
(caddr (car frame)) es-seite))
(setq fertig t)
)
(progn
;; --- VarioFoerderer-Einheit (Umlenk..Motor, mehrsegmentig) ---
;; linie-ende-modus=T -> direkt Kettenende (Separator + ES).
;; vorab-antwort/-es-gewuenscht/-es-seite/-ziel-punkt (oben
;; VOR dem Solve erfragt/berechnet) werden durchgereicht,
;; damit vfl-vf-einheit weder "Ist der Endpunkt?" nochmal
;; fragt noch einen zweiten Klick braucht (Nutzer-Wunsch,
;; identischer Fix wie bei "Neue Linie: Ab/Auf VF").
(if dbg-an
(dbgmsg (strcat "GEOMETRIE: vfl-vf-einheit-abschluss (deltaL=" (rtos deltaL 2 0)
" deltaH=" (rtos deltaH 2 0) " richtung=" richtung
" winkel=" (rtos (float winkel) 2 1)
" L_GF=" (rtos L_GF 2 0) " L_VF=" (rtos L_VF 2 0)
" linie-ende-modus=" (if linie-ende-modus "T" "nil") ")")))
(setq vf-einheit-res
(vfl-vf-einheit-abschluss frame hz-neu richtung winkel L_GF L_VF lhor stufe3-position
linie-ende-modus nil nil
vorab-antwort vorab-es-gewuenscht vorab-es-seite
vorab-ziel-punkt))
(setq frame (nth 0 vf-einheit-res))
(setq anzahl-vf (+ anzahl-vf (nth 1 vf-einheit-res)))
(if (nth 2 vf-einheit-res) (setq fertig t))
(if (nth 3 vf-einheit-res) (setq es-seite (nth 3 vf-einheit-res)))
(setq letzter-typ "VF")
(setq p-aktuell (car frame))
(if dbg-an
(dbgmsg (strcat "GEOMETRIE ERFOLG: VF-Einheit gebaut, neue Position="
(dbg-tostring p-aktuell))))
)
)
)
)
)
)
)
)
)
)
)
)
;; Segment-XDATA: alle seit seg-lastEnt neu erzeugten Entities dieses
;; Glieds mit ihrer eigenen Journal-Teilsequenz markieren (rein additiv,
;; siehe vfl-segment-xdata-sichern) - muss laufen, WAEHREND die Entities
;; noch frei in der Zeichnung liegen (vor dem -BLOCK-Sweep in
;; vfl-block-erstellen unten).
(vl-catch-all-apply 'vfl-segment-xdata-sichern (list seg-lastEnt wahl))
;; Fortschritts-Echo: aktuelle Kettenposition nach diesem Glied (egal
;; welcher wahl-Zweig lief) - siehe vfl-echo-position.
(vfl-echo-position p-aktuell)
)
)
(setq hoehe-bis (caddr (car frame)))
;; DELTA_L: planare Gesamtdistanz Start -> Kettenende
(setq vfl-ins
(vfl-block-erstellen vfl-nummer anzahl-gf anzahl-vf (caddr startpunkt) hoehe-bis
(vfl-planar-dist startpunkt (car frame)) as-seite es-seite
startpunkt lastEnt))
;; Eingabe-Journal am fertigen Block persistieren (Marker "linienzug" +
;; Chunks) -> spaeter per Doppelklick editierbar (vfl-edit-ent).
(if vfl-ins (vfl-journal-xdata-schreiben vfl-ins "linienzug"))
;; Erfolgreicher Abschluss: *error* zuruecksetzen.
(setq *error* old-error)
(princ "\n\n=========================================")
(princ (ssg-text "vfl-kette-eingefuegt"))
(princ "\n=========================================")
(if dbg-an
(progn
(dbgmsg (strcat "ERGEBNIS: VF_" (itoa vfl-nummer)
" (GF=" (itoa anzahl-gf) " VF=" (itoa anzahl-vf) ")"))
(dbgmsg "=== SESSION Modus 1 ENDE ===")
(dbgreturn (list "VF" vfl-nummer))
(dbgclose)))
(princ)
)
;; ============================================================
;; EDITIEREN: gliedweise zuruecknehmen + Neuaufbau per Replay
;; ============================================================
;; Aufgerufen aus c:VARIOFOERDERER_EDIT (Doppelklick auf VF_n mit XDATA-Marker
;; "linienzug", egal ob fertiggestellt oder durch einen Abbruch gewickelt).
;; Liest das Eingabe-Journal vom Block, listet die Glieder, laesst per
;; DCL-Dialog (vfl-dlg-position) eine Sektion waehlen, auf die zurueckgesetzt
;; wird, loescht den alten Block und baut die Kette neu: die behaltenen
;; Glieder werden stumm abgespielt, danach laeuft die Eingabe interaktiv fuer
;; die geaenderten/neuen Glieder weiter. Vorbelegung im Dialog = voller Stand
;; -> Doppelklick + sofort OK setzt einen abgebrochenen Bau nahtlos fort.
;; Marker-Dispatch: "linienzug" (Modus 1) -> abschnittsweises Zuruecksetzen
;; wie bisher; "linienzug2" (Modus 2) -> voller Reset ohne Sektionswahl
;; (vfl-edit-ent2) - Modus 2s Klassifizierungs-Schleife traegt keine
;; Glied-Marker, ein Sektions-Dialog wie bei Modus 1 ist dafuer (noch) nicht
;; vorgesehen.
;; ============================================================
;; EINZELSEGMENT-BEARBEITUNG (ein Glied aendern, Rest behalten)
;; ============================================================
;; Idee: das Ketten-Journal ist die vollstaendige, wiederabspielbare Bau-
;; Anleitung. Ein einzelnes Glied editieren = dessen Journal-Teilsequenz durch
;; eine neue ersetzen (vfl-journal-splice) und danach das GESAMTE ersetzte
;; Journal still abspielen (vfl-journal-replay-start + vf-linienzug-modus) -
;; exakt derselbe Mechanismus wie der Sektions-Ruecksprung, nur mit chirurgisch
;; ersetztem statt abgeschnittenem Journal. Die Glieder VOR dem geaenderten
;; spielen identisch wie zuvor ab (gleicher Frame), das geaenderte Glied mit
;; neuen Werten, die Glieder DANACH mit ihren ORIGINAL-Werten - aber ab dem
;; neuen Austritts-Frame des geaenderten Glieds neu verkettet. Das leistet die
;; bestehende Replay-Schleife von allein (jedes Glied baut relativ zum Frame
;; des Vorgaengers), ohne Zusatzlogik.
;;
;; Umfang: sauber einzeln editierbar sind Glieder mit reiner Winkel-/Seiten-
;; Entscheidung ohne Punkt-/Hoehen-Eingabe: GF-Bogen (Winkel 30/60/90 + Seite),
;; AS-Element (Winkel 30/90 + Seite, liegt in der Praeambel) und ES-Element
;; (Winkel 30/90 + Seite, eigenes STEP-Glied seit vfl-frage-es-seite). Andere
;; Glieder (Linie-GF/-VF, Horizontal-VF, Linie-bis-Ende) enthalten Punktwahl +
;; Hoehen und teils VF-interne Vario-Kurven - fuer die ist der Sektions-
;; Ruecksprung ("rueck") der passende Weg. Sie werden hier mit einem Hinweis
;; abgewiesen (statt eine halbfertige In-Place-Bearbeitung anzubieten).
;; Neue Journal-Teilsequenz fuer einen GF-Bogen aus den im Dialog gewaehlten
;; Werten bauen. Aufbau exakt wie vom Live-Bau erzeugt: STEP-Eintrag, dann
;; ("STR" . "1") fuer die "Naechstes Element waehlen"-Menueantwort (Option 1 =
;; GF-Bogen, siehe vf-linienzug-modus Hauptschleife - vfl-journal-mark setzt
;; den STEP-Checkpoint VOR dieser Menuefrage, vfl-journal-steplabel schreibt
;; danach nur das Label um, fuegt aber KEINEN neuen Eintrag ein; die
;; Menueantwort bleibt also als eigener Journal-Eintrag direkt nach dem STEP
;; stehen), dann ("INT" . winkel) + ("STR" . seite-code) aus
;; vfl-insert-gf-bogen (vfl-menu-winkel Winkel, vfl-menu Seite). winkel ist der
;; ECHTE Winkelwert 30/60/90 (kein Index mehr), seite-code "1"=links,
;; "2"=rechts. Ein GF-Bogen kann nie das erste Kettenglied sein (am
;; Kettenanfang bietet das Menue "GF-Bogen" gar nicht erst an, siehe
;; Hauptschleife) - die Menueantwort ist daher immer aus dem 5er-Menue (frame
;; vorhanden) und immer "1". Rueckgabe: forward-order Journal-Teilsequenz.
;; Struktur kommt aus *vfl-glied-schema* ("GF-Bogen": menue winkel seite) -
;; hier nur der Adapter (Aufrufsignatur bleibt winkel/seite-code).
(defun vfl-gf-bogen-slice-bauen (winkel seite-code)
(vfl-schema-slice-bauen "GF-Bogen" (list "1" winkel seite-code)))
;; Aktuellen (Winkel, Seiten-Code) aus einer bestehenden GF-Bogen-Slice lesen
;; (fuer die Dialog-Vorbelegung). Liest ueber das Schema (menue/winkel/seite)
;; und wirft das menue-Feld weg. Der Winkel wird zusaetzlich ueber
;; vfl-winkel-normieren auf 30/60/90 gebracht (faengt auch Alt-Journals mit
;; Index 1/2/3 ab: idx-Werte werden zum Default 90 normiert - solche Altbloecke
;; sind ohnehin selten und der Nutzer sieht/korrigiert den Winkel im
;; vorbelegten Dialog). Defaults: menue "1", Winkel 90, Seite links "1".
(defun vfl-gf-bogen-slice-werte (slice / werte w s)
(setq werte (vfl-schema-slice-werte "GF-Bogen" slice (list "1" 90 "1")))
(setq w (cadr werte) s (caddr werte))
(list (vfl-winkel-normieren w *vfk-gf-bogen-winkel* 90) s))
;; Pre-fill-faehiger Winkel+Seite-Dialog fuer den GF-Bogen (nutzt das
;; bestehende vflw_winkel_seite-Tile aus dem Wizard-DCL, aber mit Vorbelegung
;; auf die aktuellen Werte - anders als vflw-gruppe-winkel-seite-impl, das
;; immer bei 0 startet und daher nicht wiederverwendet werden kann, ohne den
;; Live-Bau zu beruehren). Rueckgabe: (winkel seite-code) oder nil bei
;; Abbruch. winkel 30/60/90 (echter Wert), seite-code "1"/"2".
(defun vfl-glied-gf-bogen-dialog (vor-winkel vor-seite-code /
dat dcl-pfad ergebnis gwinkel gseite)
(setq dcl-pfad (strcat (getenv "DXFM_DCL") "/vf_linienzug_wizard.dcl"))
(setq dat (load_dialog dcl-pfad))
(if (not (new_dialog "vflw_winkel_seite" dat))
(progn (if (and dat (>= dat 0)) (unload_dialog dat))
(alert (ssg-textf "vfl-edit-dialog-fehlt" (list dcl-pfad))) nil)
(progn
(set_tile "kopf" (ssg-text "vfl-gf-bogen-winkel-header"))
(set_tile (strcat "winkel_" (itoa vor-winkel)) "1")
(set_tile (if (= vor-seite-code "2") "seite_rechts" "seite_links") "1")
(action_tile "accept"
"(setq gwinkel (cond ((= (get_tile \"winkel_30\") \"1\") 30)
((= (get_tile \"winkel_60\") \"1\") 60)
(t 90)))
(setq gseite (if (= (get_tile \"seite_rechts\") \"1\") \"2\" \"1\"))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (= ergebnis 1)
(list gwinkel gseite)
nil))))
;; Pre-fill-faehiger Winkel+Seite+Variante-Dialog fuer die Vario-Kurve (nutzt
;; das bestehende vflw_variokurve-Tile mit Vorbelegung). Wizard-Optionsreihen-
;; folge: Winkel (30 60 90), Seite (links rechts), Variante (aussen innen) -
;; genau wie vflw-gruppe-variokurve-impl. vor-* sind die aktuellen Werte:
;; vor-winkel echter Wert 30/60/90, vor-seite-code/vor-variante-code je "1"/"2".
;; Rueckgabe: (winkel seite-code variante-code) oder nil bei Abbruch.
(defun vfl-glied-vario-dialog (vor-winkel vor-seite-code vor-variante-code /
dat dcl-pfad ergebnis gwinkel gseite gvariante)
(setq dcl-pfad (strcat (getenv "DXFM_DCL") "/vf_linienzug_wizard.dcl"))
(setq dat (load_dialog dcl-pfad))
(if (not (new_dialog "vflw_variokurve" dat))
(progn (if (and dat (>= dat 0)) (unload_dialog dat))
(alert (ssg-textf "vfl-edit-dialog-fehlt" (list dcl-pfad))) nil)
(progn
(set_tile "kopf" (ssg-text "vfl-variokurve-winkel-header"))
(set_tile (strcat "winkel_" (itoa vor-winkel)) "1")
(set_tile (if (= vor-seite-code "2") "seite_rechts" "seite_links") "1")
(set_tile (if (= vor-variante-code "2") "variante_innen" "variante_aussen") "1")
(action_tile "accept"
"(setq gwinkel (cond ((= (get_tile \"winkel_30\") \"1\") 30)
((= (get_tile \"winkel_60\") \"1\") 60)
(t 90)))
(setq gseite (if (= (get_tile \"seite_rechts\") \"1\") \"2\" \"1\"))
(setq gvariante (if (= (get_tile \"variante_innen\") \"1\") \"2\" \"1\"))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (= ergebnis 1)
(list gwinkel gseite gvariante)
nil))))
;; Pre-fill-faehiger Winkel+Seite-Dialog fuer AS/ES-Element: nur ZWEI
;; Winkel-Optionen (90/30, in genau dieser Listen-Reihenfolge - wie
;; vf-frage-element-winkel/vflw-gruppe-winkel-seite-impl), Winkel als STRING
;; "90"/"30" (nicht Index). kopf-key waehlt die Kopfzeile (AS: vf-winkel-aus-
;; header, ES: vf-winkel-ein-header). Rueckgabe: (winkel-str seite-code) oder
;; nil bei Abbruch. seite-code "1"=links / "2"=rechts.
(defun vfl-glied-as-es-dialog (kopf-key vor-winkel-str vor-seite-code /
dat dcl-pfad ergebnis gwinkel gseite)
(setq dcl-pfad (strcat (getenv "DXFM_DCL") "/vf_linienzug_wizard.dcl"))
(setq dat (load_dialog dcl-pfad))
(if (not (new_dialog "vflw_winkel_seite_30_90" dat))
(progn (if (and dat (>= dat 0)) (unload_dialog dat))
(alert (ssg-textf "vfl-edit-dialog-fehlt" (list dcl-pfad))) nil)
(progn
(set_tile "kopf" (ssg-text kopf-key))
(set_tile (if (= vor-winkel-str "30") "winkel_30" "winkel_90") "1")
(set_tile (if (= vor-seite-code "2") "seite_rechts" "seite_links") "1")
(action_tile "accept"
"(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)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (= ergebnis 1)
(list gwinkel gseite)
nil))))
;; --- AS-/ES-Element (Winkel 30/90 + Seite links/rechts) ---
;; AS und ES sind KEINE ueber das Hauptmenue gewaehlten Glieder, ihre Slice
;; traegt daher KEINE fuehrende Menue-Antwort (anders als der GF-Bogen). Aufbau:
;; AS: gehoert in die PRAEAMBEL (vor dem 1. STEP), Eintraege: STR(winkel)
;; STR(seite) - siehe vf-linienzug-modus Kettenanfang. AS ist damit KEIN
;; STEP-Glied und wird ueber die Praeambel-Splice (vfl-journal-splice-as)
;; bearbeitet.
;; ES: hat seit vfl-frage-es-seite einen EIGENEN STEP-Marker "ES" und ist
;; damit ein regulaeres Glied. Slice: STEP("ES") STR(winkel) STR(seite).
;; winkel-str ist bei beiden "30" oder "90" (Rueckgabe vf-frage-element-winkel,
;; via vfl-in-value "STR" journalisiert), seite-code "1"=links / "2"=rechts.
;; Aktuelle (winkel-str seite-code) aus einer ES-Glied-Slice lesen (2 STR nach
;; dem STEP: erst Winkel, dann Seite). Struktur/Defaults aus *vfl-glied-schema*
;; ("ES": winkel seite). Defaults 90/links.
(defun vfl-es-slice-werte (slice)
(vfl-schema-slice-werte "ES" slice (list "90" "1")))
;; Neue ES-Glied-Slice bauen: STEP("ES") + STR(winkel) + STR(seite), Struktur
;; aus dem Schema.
(defun vfl-es-slice-bauen (winkel-str seite-code)
(vfl-schema-slice-bauen "ES" (list winkel-str seite-code)))
;; --- Vario-Kurve (Winkel 30/60/90 + Seite + Variante) --------------------
;; Slice: STEP("Vario-Kurve") INT(winkel) STR(seite) STR(variante), Struktur aus
;; *vfl-glied-schema* ("Vario-Kurve"). winkel echter Wert 90/60/30 (kein Index),
;; seite/variante als Menue-Code "1"/"2" (seite: 1=links/2=rechts, variante:
;; 1=aussen/2=innen - genau wie im Live-Bau vfl-insert-vario-kurve
;; journalisiert). Aktuelle Werte aus einer bestehenden Slice lesen; Defaults
;; 90/links/innen (innen = Live-Bau-Default, vfl-menu-Vorgabe 2).
(defun vfl-vario-slice-werte (slice / werte)
(setq werte (vfl-schema-slice-werte "Vario-Kurve" slice (list 90 "1" "2")))
;; Winkel gegen Fremdwerte absichern (analog GF-Bogen).
(list (vfl-winkel-normieren (car werte) *vfk-gf-bogen-winkel* 90)
(cadr werte) (caddr werte)))
;; Neue Vario-Kurve-Slice bauen.
(defun vfl-vario-slice-bauen (winkel seite-code variante-code)
(vfl-schema-slice-bauen "Vario-Kurve" (list winkel seite-code variante-code)))
;; Neue Vario-Kurve-Slice bauen, die den SCHWANZ der alten Slice erhaelt.
;; WICHTIG - Sonderfall gegenueber GF-Bogen/ES: Die Vario-Kurve ist das EINZIGE
;; STEP-Glied, das MITTEN in einem umschliessenden Konstrukt (der VF-Einheit)
;; steht. Nach ihren drei eigenen Werten (INT winkel, STR seite, STR variante)
;; folgen im Journal - noch VOR dem naechsten STEP - weitere Antworten der
;; VF-Einheit-Schleife ("Ist Endpunkt?", "Naechstes VF-Element?" usw.). Ein
;; naiver vfl-journal-splice (der die GANZE Slice bis zum naechsten STEP
;; ersetzt) wuerde diese nachfolgenden VF-Antworten VERWERFEN und beim Replay
;; die restliche Kette verschieben (Bug, adversarial verifiziert). Daher hier:
;; nur die drei Vario-Felder direkt nach dem STEP ersetzen, ALLE weiteren
;; Eintraege der alten Slice unveraendert anhaengen. alt-slice = die vom
;; vfl-journal-slice gelieferte Original-Slice dieses Vario-Glieds.
;; Rueckgabe: neue Slice (STEP INT STR STR + originaler Schwanz).
(defun vfl-vario-slice-splicen (alt-slice winkel seite-code variante-code /
schwanz rest n)
;; Schwanz = alt-slice OHNE das fuehrende STEP und die naechsten drei
;; Eintraege (winkel/seite/variante). Robust ueber die feste Feldzahl (3)
;; des Vario-Schemas statt ueber kind-Rateraten.
(setq rest (cdr alt-slice)) ; STEP weg
(setq n 0)
(while (and rest (< n 3)) ; die drei Vario-Felder weg
(setq rest (cdr rest) n (1+ n)))
(setq schwanz rest) ; alles Weitere (VF-Antworten) bleibt
(append (vfl-vario-slice-bauen winkel seite-code variante-code) schwanz))
;; AS-Werte aus der PRAEAMBEL lesen: die beiden STR direkt VOR dem 1. STEP, die
;; auf den AS-ja/nein-STR folgen (Reihenfolge im Journal: PT REAL STR(ja/nein)
;; [STR(winkel) STR(seite)]). Nur gueltig, wenn AS vorhanden (as-ja /= "2").
;; Rueckgabe: (winkel-str seite-code) oder nil, wenn kein AS.
(defun vfl-as-werte-lesen (journal / praeambel str-liste)
(setq praeambel (vfl-journal-praeambel journal))
(setq str-liste '())
(foreach e praeambel
(if (= (car e) "STR") (setq str-liste (cons (cdr e) str-liste))))
(setq str-liste (reverse str-liste)) ; [ja/nein, winkel, seite] in Reihenfolge
(if (and (>= (length str-liste) 3) (/= (car str-liste) "2"))
(list (cadr str-liste) (caddr str-liste))
nil))
;; Praeambel (alle Eintraege vor dem 1. STEP) als Liste in Reihenfolge.
(defun vfl-journal-praeambel (journal / out done)
(setq out '() done nil)
(foreach e journal
(if (and (not done) (= (car e) "STEP")) (setq done t))
(if (not done) (setq out (cons e out))))
(reverse out))
;; AS-Winkel/Seite in der Praeambel ersetzen (die beiden STR nach dem
;; ja/nein-STR), Rest des Journals unveraendert. Erwartet, dass AS vorhanden
;; ist (3 STR in der Praeambel). Gibt das komplette neue Journal zurueck.
(defun vfl-journal-splice-as (journal winkel-str seite-code / out str-idx done)
(setq out '() str-idx 0 done nil)
(foreach e journal
(cond
(done (setq out (cons e out))) ; ab 1. STEP: unveraendert
((= (car e) "STEP") (setq done t) (setq out (cons e out)))
((= (car e) "STR")
(setq str-idx (1+ str-idx))
(cond
((= str-idx 2) (setq out (cons (cons "STR" winkel-str) out))) ; AS-Winkel
((= str-idx 3) (setq out (cons (cons "STR" seite-code) out))) ; AS-Seite
(t (setq out (cons e out))))) ; ja/nein bleibt
(t (setq out (cons e out))))) ; PT/REAL bleiben
(reverse out))
;; Einzelnes Glied bearbeiten. glieder = Glied-Label-Liste (Bau-Reihenfolge,
;; enthaelt jetzt ggf. das synthetische "AS" ganz vorne und "ES" als regulaeres
;; Glied). journal = volles Ketten-Journal. Waehlt per Liste ein Glied, laesst
;; dessen Eigenschaften aendern (GF-Bogen, AS, ES), ersetzt die Teilsequenz und
;; baut die Kette komplett neu auf (stummer Voll-Replay). Bricht der Neuaufbau
;; ab, wickelt der bestehende *error*-Handler in vf-linienzug-modus den
;; Zwischenstand - kein Sonderfall noetig.
;;
;; glieder-alle = die dem Picker angezeigte Liste (inkl. evtl. "AS" an Position
;; 1); pos ist 1-basiert darauf. STEP-Glieder (GF-Bogen, ES, ...) werden ueber
;; ihren STEP-Index angesprochen - dieser ist pos MINUS 1, falls "AS" als
;; synthetischer erster Listeneintrag vorhanden ist (AS ist kein STEP).
(defun vfl-edit-glied (ent journal glieder-alle has-as / pos label
step-idx alt-slice vorwerte neuwerte neu-slice spliced)
(setq pos (vfl-dlg-position glieder-alle))
(if (null pos)
(progn (princ (ssg-text "vfl-edit-abgebrochen")) (exit)))
(setq label (nth (1- pos) glieder-alle))
;; STEP-Index eines gewaehlten STEP-Glieds: bei vorhandenem "AS"-Listenkopf
;; um 1 nach vorne verschoben (AS belegt Listenposition 1, ist aber kein STEP).
(setq step-idx (if has-as (1- pos) pos))
(cond
;; --- AS-Element (Praeambel, kein STEP) ---
((= label "AS")
(setq vorwerte (vfl-as-werte-lesen journal))
(if (null vorwerte)
(progn (princ (ssg-text "vfl-edit-abgebrochen")) (exit)))
(setq neuwerte (vfl-glied-as-es-dialog "vf-winkel-aus-header"
(car vorwerte) (cadr vorwerte)))
(if (null neuwerte)
(progn (princ (ssg-text "vfl-edit-abgebrochen")) (exit)))
(setq spliced (vfl-journal-splice-as journal (car neuwerte) (cadr neuwerte)))
(entdel ent)
(princ (ssg-textf "vfl-glied-geaendert" (list (itoa pos))))
(vfl-journal-replay-start spliced)
(vf-linienzug-modus))
;; --- ES-Element (eigenes STEP-Glied) ---
((= label "ES")
(setq alt-slice (vfl-journal-slice journal step-idx))
(setq vorwerte (vfl-es-slice-werte alt-slice))
(setq neuwerte (vfl-glied-as-es-dialog "vf-winkel-ein-header"
(car vorwerte) (cadr vorwerte)))
(if (null neuwerte)
(progn (princ (ssg-text "vfl-edit-abgebrochen")) (exit)))
(setq neu-slice (vfl-es-slice-bauen (car neuwerte) (cadr neuwerte)))
(setq spliced (vfl-journal-splice journal step-idx neu-slice))
(entdel ent)
(princ (ssg-textf "vfl-glied-geaendert" (list (itoa pos))))
(vfl-journal-replay-start spliced)
(vf-linienzug-modus))
;; --- GF-Bogen (STEP-Glied mit fuehrender Menue-Antwort) ---
((= label "GF-Bogen")
(setq alt-slice (vfl-journal-slice journal step-idx))
(setq vorwerte (vfl-gf-bogen-slice-werte alt-slice))
(setq neuwerte (vfl-glied-gf-bogen-dialog (car vorwerte) (cadr vorwerte)))
(if (null neuwerte)
(progn (princ (ssg-text "vfl-edit-abgebrochen")) (exit)))
(setq neu-slice (vfl-gf-bogen-slice-bauen (car neuwerte) (cadr neuwerte)))
(setq spliced (vfl-journal-splice journal step-idx neu-slice))
(entdel ent)
(princ (ssg-textf "vfl-glied-geaendert" (list (itoa pos))))
;; Voller stummer Replay des gespliceten Journals: die Queue beschreibt
;; die Kette bis zum Ende, daher faellt vf-linienzug-modus nicht in den
;; Live-Modus, sondern baut 1:1 mit dem geaenderten Glied fertig.
(vfl-journal-replay-start spliced)
(vf-linienzug-modus))
;; --- Vario-Kurve (STEP-Glied INNERHALB einer VF-Einheit) ---
;; Gleiche Slice/Splice/Replay-Maschinerie wie GF-Bogen; die Vario-Kurve hat
;; seit vfl-insert-vario-kurve einen eigenen STEP-Marker und ist damit ein
;; regulaeres Glied. Aendert Winkel/Seite/Variante, Rest der Kette bleibt.
((= label "Vario-Kurve")
(setq alt-slice (vfl-journal-slice journal step-idx))
(setq vorwerte (vfl-vario-slice-werte alt-slice))
(setq neuwerte (vfl-glied-vario-dialog (car vorwerte) (cadr vorwerte) (caddr vorwerte)))
(if (null neuwerte)
(progn (princ (ssg-text "vfl-edit-abgebrochen")) (exit)))
;; Schwanz-erhaltende Slice: die Vario-Kurve steht MITTEN in der VF-Einheit,
;; nach ihren 3 Werten folgen weitere VF-Antworten in derselben Slice. Nur
;; die 3 Vario-Felder ersetzen, den Rest behalten (siehe
;; vfl-vario-slice-splicen) - sonst verschiebt sich beim Replay die Kette.
(setq neu-slice (vfl-vario-slice-splicen alt-slice
(car neuwerte) (cadr neuwerte) (caddr neuwerte)))
(setq spliced (vfl-journal-splice journal step-idx neu-slice))
(entdel ent)
(princ (ssg-textf "vfl-glied-geaendert" (list (itoa pos))))
(vfl-journal-replay-start spliced)
(vf-linienzug-modus))
(t
(alert (ssg-textf "vfl-glied-nicht-editierbar"
(list (vfl-glied-label-text label))))
(princ (ssg-text "vfl-edit-abgebrochen"))))
(princ))
;; ============================================================
;; Nicht-interaktiver Dimensions-Konverter (fuer die Batch-Umschaltung
;; SSG_DIM_ALL_2D / SSG_DIM_ALL_3D, aufgerufen aus ssg-dim-alle-umwandeln).
;; ============================================================
;; Baut einen Linienzug-VF_n-Block (Modus 1 "linienzug" ODER Modus 2
;; "linienzug2") in der gewuenschten Zieldimension neu auf, indem das komplette
;; Eingabe-Journal STUMM (Replay-Queue, keine DCL-/Konsolen-Fragen) mit
;; erzwungenem *ssg-ils-dim*-Override abgespielt wird. Muster: vf-konvertiere-ent
;; (vf_standard.lsp), aber ohne Attribut-Reimport - der Startpunkt und alle
;; Eingaben stecken vollstaendig im Journal (erster PT-Eintrag = Startpunkt).
;;
;; Das Dim-XDATA wird NICHT hier geschrieben, sondern automatisch von
;; vfl-block-erstellen (Phase A) auf den NEUEN Block - explizites Nachschreiben
;; wuerde auf den bereits geloeschten alten Ename zielen.
;;
;; vl-catch-all-apply schuetzt vor (exit)/Fehlern im Bau (z.B. KS-Problem bei
;; einem 2D-Koerperblock ohne gueltiges KS), damit die Batch-Schleife weiterlaeuft
;; und der Override sicher zurueckgesetzt wird. Rueckgabe: T bei Erfolg, sonst nil.
;; Bekannte Grenze (nur Modus 2): das Replay von vfl-in-selection braucht die
;; Original-Pfadobjekte; wurden sie geloescht, schlaegt der Konverter fuer diesen
;; Block sauber fehl (sichtbar am nicht erhoehten Batch-Zaehler). Modus 3
;; ("linienzug3") hat diese Grenze NICHT: es replayt die Segment-Essenz (SEGS)
;; aus dem Journal, nicht die Objekt-Handles - unabhaengig von den Original-LINE/ARC.
(defun vfl-konvertiere-ent (ent ziel-dim / bname xd marker journal ok)
(setq bname (cdr (assoc 2 (entget ent))))
(setq xd (vfl-journal-xdata-lesen ent)) ; (marker . journal) oder nil
(setq marker (car xd) journal (cdr xd))
(if (and (wcmatch bname "VF_*")
(member marker '("linienzug" "linienzug2" "linienzug3"))
journal
;; Alte OBJS-"linienzug2"-Bloecke (ohne SEGS) NICHT konvertieren -
;; ihr Replay ist mit dem SEGS-Umbau nicht mehr fahrbar (harter
;; Schnitt, wie im Doppelklick-Pfad vfl-edit-ent). Sie ueberspringen
;; (nil zurueck), damit die Batch-Schleife sauber weiterlaeuft.
(or (/= marker "linienzug2") (vfl-journal-hat-segs-p journal)))
(progn
(setq *ssg-ils-dim* ziel-dim) ; Zieldim erzwingen
(entdel ent) ; alten Block weg
(vfl-journal-replay-start journal) ; volle Queue, KEIN Truncate
(setq ok (vl-catch-all-apply
(cond ((= marker "linienzug2") 'vf-linienzug-modus2)
((= marker "linienzug3") 'vf-linienzug-modus3)
(t 'vf-linienzug-modus))))
(setq *ssg-ils-dim* nil) ; Override zuruecksetzen
;; Fehlgeschlagene Konvertierung NICHT stumm verschlucken: der alte Block
;; ist per entdel schon weg, die Teil-Geometrie liegt frei in der
;; Zeichnung (der *error*-Handler in vf-linienzug-modus kann hier nicht
;; greifen, weil vl-catch-all-apply den Abbruch vorher abfaengt - das ist
;; hier gewollt, damit die Batch-Schleife weiterlaeuft). Meldung mit
;; Blockname, damit der betroffene Block auffindbar bleibt.
(if (vl-catch-all-error-p ok)
(progn
(princ (ssg-textf "vfl-konvert-fehler"
(list bname (vl-catch-all-error-message ok))))
nil)
T))
nil))
(defun vfl-edit-ent (ent / xd marker journal glieder glieder-alle has-as n i pos trunc edit-modus dim)
(setq xd (vfl-journal-xdata-lesen ent))
(if (null xd)
(progn (alert (ssg-text "vfl-edit-kein-journal")) (exit)))
(setq marker (car xd) journal (cdr xd))
;; Dimension des Bestandsblocks merken, damit der Doppelklick-Neubau
;; dimensionstreu bleibt (sonst baut er in der globalen DXFM_DIM, weil beim
;; Doppelklick kein *ssg-ils-dim*-Override aktiv ist). Wird VOR dem entdel
;; gelesen; der Override greift beim anschliessenden Replay/Weiterbau.
(setq dim (ssg-dim-xdata-lesen ent))
(cond
((= marker "linienzug2")
;; Harter Schnitt: nur SEGS-Bloecke (neue Fassung) sind per Doppelklick
;; neu aufbaubar. Alte OBJS-Bloecke (Objekt-Handles statt Segment-Essenz)
;; wuerden im Replay falsche Daten in den Bau geben - klar abweisen statt
;; zu crashen.
(if (vfl-journal-hat-segs-p journal)
(vfl-edit-ent2 ent journal)
(alert (ssg-text "vfl-edit-m2-obsolet"))))
((= marker "linienzug3") (vfl-edit-ent3 ent journal))
(t
(progn
(setq glieder (vfl-journal-glieder journal))
(setq n (length glieder))
;; AS ist kein STEP-Glied (steht in der Praeambel). Fuer den Glied-Edit
;; wird es als synthetischer erster Listeneintrag "AS" vorangestellt,
;; falls in der Kette ein AS vorhanden ist (vfl-as-werte-lesen liefert
;; dann nicht nil). has-as steuert in vfl-edit-glied den STEP-Index-
;; Versatz (AS belegt Listenposition 1, ist aber kein STEP). Der Sektions-
;; Ruecksetz-Modus nutzt weiterhin die reine STEP-Liste (glieder), ES ist
;; darin bereits als eigenes Glied enthalten.
(setq has-as (and (vfl-as-werte-lesen journal) t))
(setq glieder-alle (if has-as (cons "AS" glieder) glieder))
;; NEU: zuerst Modus waehlen (Sektion zuruecksetzen ODER ein einzelnes
;; Glied bearbeiten). Alte Bloecke ohne Segment-XDATA funktionieren in
;; beiden Zweigen, da beide allein aus dem Ketten-Journal arbeiten.
(setq edit-modus (vfl-dlg-modus n))
(cond
((null edit-modus)
(princ (ssg-text "vfl-edit-abgebrochen")))
((= edit-modus "glied")
(vfl-edit-glied ent journal glieder-alle has-as))
(t
;; --- bestehender Ablauf: Sektion zuruecksetzen (unveraendert) ---
(princ (ssg-textf "vfl-edit-kette-header" (list (itoa n))))
(setq i 1)
(foreach g glieder
(princ (strcat "\n " (itoa i) ") " (vfl-glied-label-text g)))
(setq i (1+ i)))
(setq pos (vfl-dlg-position glieder))
(if (null pos)
(progn (princ (ssg-text "vfl-edit-abgebrochen")) (exit)))
(setq trunc (vfl-journal-truncate journal pos))
;; Alten Block entfernen (wie Standard/Etage: entdel + kompletter Neuaufbau).
;; Bricht der Neuaufbau selbst ab, wickelt vfl-modus-abbruch-sichern den
;; Zwischenstand automatisch zu einem neuen Block - kein Sonderfall noetig.
(entdel ent)
(princ (ssg-textf "vfl-edit-abgespielt" (list (itoa pos) (itoa (- n pos)))))
;; Behaltene Glieder abspielen, dann live weiterbauen. Dimensionstreu:
;; Override auf die Dimension des Bestandsblocks, danach zuruecksetzen.
(if dim (setq *ssg-ils-dim* dim))
(vfl-journal-replay-start trunc)
;; WICHTIG: DIREKTER Aufruf, KEIN vl-catch-all-apply!
;; Der Neuaufbau ist interaktiv - bricht der Nutzer ihn ab (ESC ->
;; (exit) im Bau-Ablauf), MUSS der *error*-Handler in
;; vf-linienzug-modus laufen und die bis dahin gebaute Teil-Geometrie
;; zu einem VF_n-Block wickeln. Ein vl-catch-all-apply faengt den
;; Abbruch VOR *error* ab: der Handler kaeme nie zum Zug, der alte
;; Block ist durch das entdel oben aber schon weg - die ganze Kette
;; bliebe als loses Einzelteil-Haufen ohne Block in der Zeichnung
;; liegen (nicht mehr per Doppelklick editierbar). Genau das war der
;; Fehler "VF-Block aufgebrochen".
;; Das Abraeumen des Dim-Overrides uebernimmt im Abbruchfall der
;; Handler selbst (dort ebenfalls (setq *ssg-ils-dim* nil)), denn
;; die naechste Zeile wird dann nicht mehr erreicht.
(vf-linienzug-modus)
(setq *ssg-ils-dim* nil))))))
(princ))
;; T, wenn das Journal einen SEGS-Eintrag traegt (Modus 2 NEU / Modus 3 - die
;; Kette ist per Segment-Essenz selbst-enthalten). Alte Modus-2-Bloecke tragen
;; hier stattdessen einen OBJS-Eintrag (Objekt-Handles) - fuer die ist der
;; SEGS-Replay nicht mehr fahrbar (harter Schnitt, siehe vfl-edit-ent).
(defun vfl-journal-hat-segs-p (journal)
(and (vl-some (function (lambda (e) (equal (car e) "SEGS"))) journal) t))
;; Modus-2/3-Block per Doppelklick zuruecksetzen: voller Reset - die komplette
;; gespeicherte Eingabe wird 1:1 stumm abgespielt, identischer Neuaufbau. Kein
;; Sektions-Dialog; der Nutzer kann den neu entstandenen Block danach ganz
;; normal weiterbearbeiten oder loeschen.
;;
;; bau-fn = die Modus-Bau-Funktion, die das Journal abspielt: 'vf-linienzug-modus2
;; fuer Marker "linienzug2" (Pfad-Objekte per OBJS-Handles) bzw.
;; 'vf-linienzug-modus3 fuer "linienzug3" (Segment-Essenz per SEGS - unabhaengig
;; von den Original-Objekten). Beide Modi haben eine flache Klassifizierungs-
;; Schleife ohne editierbare Einzelglieder, daher derselbe volle Reset.
(defun vfl-edit-ent-voll (ent journal bau-fn / dim)
;; Dimension des Bestandsblocks VOR dem entdel merken und beim Neubau als
;; Override erzwingen (dimensionstreuer Doppelklick-Neubau, sonst globale
;; DXFM_DIM). Danach Override zuruecksetzen.
(setq dim (ssg-dim-xdata-lesen ent))
(entdel ent)
(princ (ssg-text "vfl-edit2-reset-info"))
(if dim (setq *ssg-ils-dim* dim))
(vfl-journal-replay-start journal)
;; DIREKTER Aufruf, KEIN vl-catch-all-apply - identische Begruendung wie im
;; Sektions-Zweig von vfl-edit-ent (siehe dortiger Kommentar): der alte Block
;; ist per entdel schon weg, bei einem Abbruch muss der *error*-Handler der
;; Modus-Funktion die Teil-Geometrie wickeln. Den Dim-Override raeumt im
;; Abbruchfall der Handler ab. bau-fn ist ein Funktions-SYMBOL (quoted beim
;; Aufruf) - ueber apply aufrufen, nicht als (bau-fn) (das riefe die Funktion
;; namens "bau-fn" auf, nicht den durchgereichten Wert).
(apply bau-fn nil)
(setq *ssg-ils-dim* nil)
(princ))
;; Rueckwaertskompatibler Wrapper (Modus 2). vfl-edit-ent3 fuer Modus 3.
(defun vfl-edit-ent2 (ent journal) (vfl-edit-ent-voll ent journal 'vf-linienzug-modus2))
(defun vfl-edit-ent3 (ent journal) (vfl-edit-ent-voll ent journal 'vf-linienzug-modus3))
;; ============================================================
;; RECOVERY: Kette aus der Sektions-XDATA der Zeichnung neu aufbauen
;; ============================================================
;; Wofuer: bleibt eine Linienzug-Kette als LOSE Geometrie ohne VF_n-Block in
;; der Zeichnung liegen (der Block war schon per entdel weg, der Neuaufbau
;; wurde abgebrochen und konnte nicht gewickelt werden), ist sie nicht mehr per
;; Doppelklick editierbar. Die Eingaben sind aber nicht verloren: jedes Entity
;; traegt den Journal-Abschnitt der Iteration, in der es entstand
;; (SSG_VF_EDIT_SEG, indiziert mit dem ERSTEN Glied-Index darin - siehe
;; vfl-segment-xdata-sichern), und die Entities der ersten Iteration
;; zusaetzlich die Praeambel (SSG_VF_EDIT_PRE). Daraus laesst sich das
;; Ketten-Journal zusammensetzen und die Kette identisch neu bauen - diesmal
;; sauber als VF_n-Block mit Ketten-XDATA.
;;
;; Quelle ist AUSSCHLIESSLICH die Zeichnung selbst. Die .dbg-Dateien sind
;; reine Ausgabe und werden hier bewusst NICHT gelesen (sie werden bei jedem
;; Lauf neu angelegt, siehe dbgopen).
;;
;; Ablauf: Geometrie waehlen -> Sektionen sammeln und nach Glied-Index sortieren
;; -> Journal (Praeambel + Glieder 1..k) stumm abspielen -> danach normal
;; weiterbauen. Die alte Geometrie wird erst NACH einem erfolgreichen Neuaufbau
;; geloescht (bricht der Neuaufbau ab, bleibt sie samt ihrer XDATA stehen und
;; der Versuch ist wiederholbar).
;; Praeambel interaktiv erfragen - nur fuer Altbestand ohne SSG_VF_EDIT_PRE
;; (Ketten, die vor der Einfuehrung der Praeambel-XDATA gebaut wurden).
;; Reihenfolge/Arten MUESSEN dem Kopf von vf-linienzug-modus entsprechen:
;; PT (Startpunkt), REAL (Starthoehe), STR (AS ja="1"/nein="2") und bei AS
;; zusaetzlich STR (Winkel "30"/"90") + STR (Seite links="1"/rechts="2").
;; Rueckgabe: Praeambel-Journal oder nil (Abbruch).
(defun vfl-praeambel-erfragen ( / p h as w s out)
(princ (ssg-text "vfl-restore-keine-praeambel"))
(setq p (getpoint (ssg-text "vfl-prompt-startpunkt-kette")))
(if (null p)
nil
(progn
(setq h (getreal (ssg-textf "vfl-prompt-hoehe-startpunkt-kette"
(list (rtos (caddr p) 2 1)))))
(if (null h) (setq h (caddr p)))
(initget "Ja Nein")
(setq as (getkword (ssg-text "vfl-restore-frage-as")))
(if (null as) (setq as "Ja"))
(setq out (list (cons "PT" p) (cons "REAL" h)
(cons "STR" (if (= as "Nein") "2" "1"))))
(if (/= as "Nein")
(progn
;; gleiche Frage wie im Bau-Ablauf (liefert "30"/"90")
(princ (ssg-text "vf-winkel-aus-header"))
(princ (ssg-text "vf-winkel-90"))
(princ (ssg-text "vf-winkel-30"))
(setq w (vf-frage-element-winkel "vf-winkel-aus-header"))
(initget "Links Rechts")
(setq s (getkword (ssg-text "vfl-restore-frage-as-seite")))
(if (null s) (setq s "Links"))
(setq out (append out (list (cons "STR" w)
(cons "STR" (if (= s "Rechts") "2" "1")))))))
out)))
;; Sektionen aus einer Auswahl sammeln. Rueckgabe: Liste (idx . slice),
;; aufsteigend nach idx, jeder Index nur EINMAL (alle Entities eines Gliedes
;; tragen dieselbe Slice).
(defun vfl-sektionen-sammeln (ss / i n ent seg idx out)
(setq out '() i 0 n (sslength ss))
(while (< i n)
(setq ent (ssname ss i))
(setq seg (vfl-journal-xdata-lesen-seg ent))
(if seg
(progn
(setq idx (car seg))
(if (not (assoc idx out)) (setq out (cons (cons idx (cdr seg)) out)))))
(setq i (1+ i)))
(vl-sort out (function (lambda (a b) (< (car a) (car b))))))
;; Praeambel aus der Auswahl lesen (erstes Entity, das eine traegt).
(defun vfl-praeambel-sammeln (ss / i n pre)
(setq i 0 n (sslength ss) pre nil)
(while (and (< i n) (null pre))
(setq pre (vfl-journal-xdata-lesen-pre (ssname ss i)))
(setq i (1+ i)))
pre)
(defun c:VF_SEKTION_RESTORE ( / ss sektionen pre journal teile k erwartet luecke
i n sek e alt-liste)
(princ (ssg-text "vfl-restore-prompt-auswahl"))
(setq ss (ssget))
(cond
((null ss) (princ (ssg-text "vfl-abgebrochen")))
((null (setq sektionen (vfl-sektionen-sammeln ss)))
(princ (ssg-text "vfl-restore-keine-sektionen")))
(t
;; Sektionen aneinanderhaengen, solange sie LUECKENLOS ab Glied 1
;; anschliessen. Ein Sektions-Record ist mit dem Index seines ERSTEN
;; Gliedes abgelegt und kann mehrere Glieder enthalten (VF-Einheit mit
;; Vario-Kurven) - der naechste erwartete Index ist daher
;; erster-Index + Anzahl der Glieder darin, nicht einfach +1.
;; Bei einer Luecke wird nur der Anfang wiederhergestellt: ein fehlendes
;; Glied in der Mitte wuerde alle folgenden an die falsche Position setzen.
(setq teile '() k 0 erwartet 1 luecke nil)
(foreach sek sektionen
(if (and (not luecke) (= (car sek) erwartet))
(progn
(setq teile (cons (cdr sek) teile))
(setq k (+ k (vfl-steps-zaehlen (cdr sek))))
(setq erwartet (1+ k)))
(setq luecke t)))
(if luecke
(princ (ssg-textf "vfl-restore-luecke" (list (itoa erwartet) (itoa k)))))
(if (= k 0)
(princ (ssg-text "vfl-restore-keine-sektionen"))
(progn
;; Praeambel: aus der Zeichnung, sonst erfragen (Altbestand ohne
;; SSG_VF_EDIT_PRE).
(setq pre (vfl-praeambel-sammeln ss))
(if (null pre) (setq pre (vfl-praeambel-erfragen)))
(if (null pre)
(princ (ssg-text "vfl-abgebrochen"))
(progn
(setq journal (apply 'append (cons pre (reverse teile))))
(princ (ssg-textf "vfl-restore-gefunden"
(list (itoa k) (itoa (length journal)))))
;; Enames der alten Geometrie merken - geloescht wird ERST nach
;; einem erfolgreichen Neuaufbau (siehe unten).
(setq alt-liste '() i 0 n (sslength ss))
(while (< i n)
(setq alt-liste (cons (ssname ss i) alt-liste))
(setq i (1+ i)))
;; Neu bauen. DIREKTER Aufruf: bricht der Bau ab, wickelt der
;; *error*-Handler in vf-linienzug-modus den Zwischenstand zu
;; einem Block - und die Zeilen danach (Loeschen) laufen NICHT
;; mehr. Die alte Geometrie samt ihrer Sektions-XDATA bleibt also
;; erhalten und der Versuch ist wiederholbar.
(vfl-journal-replay-start journal)
(vf-linienzug-modus)
;; Erfolg: alte lose Geometrie entfernen. Sie liegt VOR lastEnt und
;; ist deshalb nicht im neuen Block gelandet (vgl.
;; vfl-block-erstellen), wuerde aber deckungsgleich darunter
;; liegenbleiben.
(foreach e alt-liste (if e (entdel e)))
(princ (ssg-textf "vfl-restore-geloescht"
(list (itoa (length alt-liste)))))))))))
(princ))
;; DCL-Dialog nach Doppelklick: Editier-Modus waehlen.
;; n = Anzahl Glieder (nur fuer die Kopfzeile). Vorbelegung "rueck" (das
;; bisherige Verhalten, geringste Ueberraschung). Rueckgabe: "rueck", "glied"
;; oder nil (Abbrechen/Escape). Bei fehlendem/kaputtem DCL faellt der Dialog
;; auf "rueck" zurueck (statt die Bearbeitung ganz zu blockieren) - so bleibt
;; der bestehende Editierpfad auch dann erreichbar.
(defun vfl-dlg-modus (n / dcl-pfad dat wahl ergebnis)
(setq dcl-pfad (strcat (getenv "DXFM_DCL") "/vfl_edit_modus.dcl"))
(setq dat (load_dialog dcl-pfad))
(if (not (new_dialog "vfl_edit_modus" dat))
(progn
(if dat (unload_dialog dat))
(alert (ssg-textf "vfl-edit-dialog-fehlt" (list dcl-pfad)))
"rueck") ; Fallback: bisheriges Verhalten
(progn
;; Nur der Kopftext wird zur Laufzeit gesetzt; die Optionslabels stehen
;; statisch (deutsch) im DCL - siehe vfl_edit_modus.dcl. get_tile "modus"
;; liefert den key des gewaehlten radio_button. Default "modus_rueck" ist
;; im DCL per value="1" vorbelegt.
(set_tile "kopf" (ssg-textf "vfl-dlg-modus-kopf" (list (itoa n))))
(action_tile "accept" "(setq wahl (get_tile \"modus\")) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((/= ergebnis 1) nil)
((= wahl "modus_glied") "glied")
(t "rueck"))))) ; modus_rueck (o. leer) = zuruecksetzen
;; DCL-Dialog: Sektion waehlen, auf die die Kette zurueckgesetzt wird.
;; glieder = Liste der Glied-Labels (interne Tokens, vfl-journal-glieder), in
;; Bau-Reihenfolge. Vorbelegung auf den letzten Eintrag (voller Stand = reines
;; Fortsetzen ohne Kuerzung). Rueckgabe: gewaehlte Position (1-basiert) oder
;; nil bei Abbrechen/Escape.
(defun vfl-dlg-position (glieder / n dcl-pfad dat idx ergebnis i)
(setq n (length glieder))
(setq dcl-pfad (strcat (getenv "DXFM_DCL") "/vfl_edit.dcl"))
(setq dat (load_dialog dcl-pfad))
(if (not (new_dialog "vfl_edit" dat))
(progn (alert (ssg-textf "vfl-edit-dialog-fehlt" (list dcl-pfad))) nil)
(progn
(set_tile "kopf" (ssg-textf "vfl-dlg-kopf-sektionen" (list (itoa n))))
(start_list "position")
(setq i 1)
(foreach g glieder
(add_list (strcat (itoa i) " - " (vfl-glied-label-text g)))
(setq i (1+ i)))
(end_list)
(set_tile "position" (itoa (1- n))) ; letzter Eintrag vorbelegt (0-basierter Index)
(action_tile "accept" "(setq idx (atoi (get_tile \"position\"))) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (= ergebnis 1) (1+ idx) nil)
)
)
)
;; ============================================================
;; DIAGNOSE: Blockstruktur (KS_EIN/KS_AUS) untersuchen
;; ============================================================
;; Zeigt, wie ein Block intern aufgebaut ist - insbesondere, ob KS_EIN/KS_AUS
;; auf der ersten Explode-Ebene als Unterbloecke liegen und welche Laengen ihre
;; Achslinien haben (ks-line-axis erwartet X~1/~100, Y~2, Z~3). Damit laesst
;; sich klaeren, warum extract-ks-from-block bei den Vario_Kurve-Bloecken
;; "KS_EIN/KS_AUS fehlen" meldet.
;; Aufruf in BricsCAD: VFL_KS_DIAG -> Blockname eingeben.
(defun c:VFL_KS_DIAG ( / bname obj subs s nm inner il ilnm ps pe len)
(setq bname (getstring "\nBlockname fuer KS-Diagnose: "))
(setq bname (ensure-block-loaded bname))
(if (not (tblsearch "BLOCK" bname))
(progn (princ (strcat "\nBlock '" bname "' nicht gefunden.")) (exit)))
(setq obj (vla-InsertBlock modelspace (vlax-3D-point '(0 0 0)) bname 1.0 1.0 1.0 0))
(princ (strcat "\n=================================================="))
(princ (strcat "\n=== Struktur von '" bname "' (Ebene 1) ==="))
(setq subs (vlax-invoke obj 'Explode))
(foreach s subs
(if (not (vlax-erased-p s))
(progn
(setq nm (vla-get-ObjectName s))
(if (= nm "AcDbBlockReference")
(progn
(princ (strcat "\n BlockRef: '" (vla-get-Name s) "' (Ebene 2:)"))
(setq inner (vlax-invoke s 'Explode))
(foreach il inner
(if (not (vlax-erased-p il))
(progn
(setq ilnm (vla-get-ObjectName il))
(cond
((= ilnm "AcDbLine")
(setq ps (vlax-safearray->list (vlax-variant-value (vla-get-StartPoint il))))
(setq pe (vlax-safearray->list (vlax-variant-value (vla-get-EndPoint il))))
(setq len (vec-length (list (- (car pe)(car ps))
(- (cadr pe)(cadr ps))
(- (caddr pe)(caddr ps)))))
(princ (strcat "\n Line len=" (rtos len 2 3)
" axis=" (if (ks-line-axis len) (ks-line-axis len) "?"))))
((= ilnm "AcDbBlockReference")
(princ (strcat "\n BlockRef(verschachtelt): '" (vla-get-Name il) "'")))
(t (princ (strcat "\n " ilnm)))
)
(vla-Delete il)
)
)
)
)
(princ (strcat "\n " nm))
)
(vla-Delete s)
)
)
)
(princ "\n=== Ende Diagnose ===")
(princ "\n==================================================")
(princ)
)
;; ============================================================
;; REGISTRIERUNG
;; ============================================================
;; berechne-fn/einfuege-fn werden fuer "linienzug" NICHT im normalen Schema
;; verwendet (eigener Befehlsablauf, siehe Kommentar am Dateianfang) - beide
;; sind daher nur Platzhalter, die c:VarioFoerderer nie aufruft (Dispatch
;; erfolgt dort direkt auf vf-linienzug-modus).
(defun vfl-berechne-platzhalter (deltaL deltaH richtung seite)
(princ "\n[vf_linienzug] FEHLER: berechne-fn sollte fuer Typ 'linienzug' nie aufgerufen werden.")
(list nil nil nil nil)
)
(defun vfl-einfuege-platzhalter (deltaL deltaH richtung best-winkel L_GF1 L_GF2 L_VF startpunkt seite hz)
(princ "\n[vf_linienzug] FEHLER: einfuege-fn sollte fuer Typ 'linienzug' nie aufgerufen werden.")
startpunkt
)
;; ============================================================
;; MODUS 2: 3D-OBJEKTE WAEHLEN (Meilenstein 1 - GF-Teil)
;; ============================================================
;; Baut die Kette aus einem GEZEICHNETEN Pfad (LINE/ARC). Ablauf:
;; Objekte waehlen -> gf-sortiere-objekte -> gf-analysiere-kette
;; -> Eck-Winkel-Vorpruefung -> segmentweise (Variante B) live bauen -> ES.
;; In diesem Meilenstein ist NUR der GF-Teil aktiv (GF-Gerade mit eigener
;; Neigung <=3 Grad, GF-Bogen). Die VF-Zweige sind als TODO (Meilenstein 2)
;; markiert und werden uebersprungen.
;; Bekannte Naeherungen (in BricsCAD verifizieren):
;; - Eck-Trimmung nur an erster/letzter Geraden (-aus-dx bzw. -300-ein-dx,
;; wie gf-linienzug-modus); Separatoren VOR GF-Boegen noch NICHT gesetzt.
;; - GF-Bogen hat feste Eigen-Neigung -> bei unterschiedlichen Nachbar-
;; Neigungen entsteht ein (akzeptierter) Knick.
;; Alle Eck-Winkel des Pfades gegen 30/60/90 Grad pruefen (Toleranz tol).
;; Rueckgabe: nil (alle ok) oder der erste abweichende Winkel (Grad).
(defun vfl2-pruefe-eckwinkel (kette tol / item obj sa ea sweep bad)
(setq bad nil)
(foreach item kette
(setq obj (car item))
(if (and (null bad) (= (vla-get-ObjectName obj) "AcDbArc"))
(progn
(setq sa (vla-get-StartAngle obj) ea (vla-get-EndAngle obj))
(setq sweep (if (>= ea sa) (- ea sa) (+ (- ea sa) (* 2.0 pi))))
(setq sweep (* sweep (/ 180.0 pi)))
(if (not (or (< (abs (- sweep 30.0)) tol)
(< (abs (- sweep 60.0)) tol)
(< (abs (- sweep 90.0)) tol)))
(setq bad sweep))
)
)
)
bad
)
;; AS-Element fuer Modus 2 (GF): gemischte Verankerung.
;; - Laengsrichtung (entlang hz): KS_EIN auf den Startpunkt (Laengsposition)
;; - Querrichtung (senkrecht hz): KS_AUS auf den Pfad (Kettenmittellinie)
;; Umsetzung: (1) AS mit KS_EIN am Startpunkt platzieren, (2) Querversatz des
;; KS_AUS zum Pfad messen, (3) AS senkrecht zu hz um -Versatz verschieben.
;; Ergebnis: KS_EIN.X = Startpunkt.X, KS_EIN quer um das AS-Y-Mass versetzt,
;; KS_AUS liegt exakt auf dem Pfad -> die ganze Kette folgt der Mittellinie.
;; Rueckgabe: (korrigierter) Frame am KS_AUS.
;; winkel-Parameter bleibt aus Call-Kompatibilitaet bestehen (vfl2-insert-as-vf
;; ruft mit 0.0), wird aber NICHT mehr fuer eine Kippung verwendet: das AS-
;; Element hat keine eigene Neigung (per Einzel-Insert-Test in BricsCAD
;; bestaetigt, siehe vfl-insert-as-element in Modus 1) - unabhaengig vom
;; folgenden Segmenttyp (GF/VF) wird es IMMER flach eingefuegt.
(defun vfl2-insert-as-gf (startpunkt hz winkel as-seite /
ein-hz-as rad-h xu-ein frame blk ks-aus
rad-hz perp-x perp-y d-perp sx sy as-turn
rad-ein de-x de-y de-dot-np t-shift)
;; Schwenk aus dem AS-Block MESSEN (30/90/gerade), KS_EIN so drehen, dass
;; KS_AUS entlang hz zeigt: ein-hz = hz - plan-turn.
(setq as-turn (vf-element-plan-turn (strcat "AS_Element_" (vfl-as-winkel) "_" as-seite)))
(setq ein-hz-as (- hz as-turn))
(setq rad-h (* (float ein-hz-as) (/ pi 180.0)))
(setq xu-ein (list (cos rad-h) (sin rad-h) 0.0))
;; (1) KS_EIN auf den Startpunkt (Laengsposition korrekt)
(setq frame (insert-block-mixed-to-ks
(strcat "AS_Element_" (vfl-as-winkel) "_" as-seite)
(make-frame-from-dir startpunkt xu-ein)
(caddr startpunkt) "KS_EIN" "KS_EIN"))
(setq blk (entlast))
;; (2) Querversatz des KS_AUS zum Pfad (Einheits-Querrichtung senkrecht zu hz)
(setq ks-aus (car frame))
(setq rad-hz (* (float hz) (/ pi 180.0)))
(setq perp-x (- (sin rad-hz)) perp-y (cos rad-hz)) ; Normale zum Pfad
(setq d-perp (+ (* (- (car ks-aus) (car startpunkt)) perp-x)
(* (- (cadr ks-aus) (cadr startpunkt)) perp-y)))
;; (3) AS ENTLANG der KS_EIN-Achse verschieben (nicht senkrecht zum Pfad):
;; damit landet KS_AUS auf dem Pfad UND der Startpunkt bleibt auf der KS_EIN-
;; Achse. Beim 90-Grad-Element ist die KS_EIN-Achse senkrecht zum Pfad -> das
;; ist identisch zum bisherigen Querschub.
(setq rad-ein (* (float ein-hz-as) (/ pi 180.0)))
(setq de-x (cos rad-ein) de-y (sin rad-ein)) ; KS_EIN-Achsrichtung
(setq de-dot-np (+ (* de-x perp-x) (* de-y perp-y)))
(setq t-shift (if (> (abs de-dot-np) 1e-6) (/ d-perp de-dot-np) 0.0))
(setq sx (* (- t-shift) de-x) sy (* (- t-shift) de-y))
(if (> (abs t-shift) 1e-6)
(vla-Move (vlax-ename->vla-object blk)
(vlax-3D-point '(0.0 0.0 0.0)) (vlax-3D-point (list sx sy 0.0))))
;; (4) korrigierten KS_AUS-Frame zurueckgeben (Richtung unveraendert)
(list (list (+ (car ks-aus) sx) (+ (cadr ks-aus) sy) (caddr ks-aus))
(cadr frame) (caddr frame) (cadddr frame))
)
;; VF-Start-AS (Modus 2): identische Platzierung wie beim GF-Start, aber FLACH
;; (0 Grad Neigung). Das AS wird mit KS_EIN auf den Startpunkt gesetzt und dann
;; senkrecht zu hz auf den Pfad geschoben (reine KS-Kettung, KS_AUS auf Pfad).
;; Damit liegt auch eine mit VF beginnende Kette auf dem gezeichneten Pfad.
(defun vfl2-insert-as-vf (startpunkt hz as-seite)
(vfl2-insert-as-gf startpunkt hz 0.0 as-seite))
;; VF-Einheit OEFFNEN (Modus 2): GF1 (Basis-Staustrecke) + Separator +
;; Umlenkstation. Rueckgabe: Frame nach der Umlenkstation (Beginn reiner VF).
(defun vfl2-vf-open (frame hz / f)
(setq f (vfl-frame-3grad (vfs-vf-entry (car frame) *vfl-gf-min-laenge* hz nil) hz))
(if (> *vfl-gf-min-laenge* 0.1)
(vfl-acc-gf-seg *vfl-gf-min-laenge* (ssg-cfg-or "vario" "gefaelle_winkel" 3)))
f)
;; VF-Einheit SCHLIESSEN (Modus 2): Motorstation + GF2 (Basis), KEIN Separator
;; (der sitzt erst am Kettenende bzw. optional zwischen Foerderern). Rueckgabe:
;; Frame nach GF2.
(defun vfl2-vf-close (frame hz / f)
(setq *vfl-acc-motorseite* (append *vfl-acc-motorseite* (list "rechts")))
(setq f (vfl-frame-3grad (vfs-vf-exit (car frame) *vfl-gf-min-laenge* hz nil) hz))
(if (> *vfl-gf-min-laenge* 0.1)
(vfl-acc-gf-seg *vfl-gf-min-laenge* (ssg-cfg-or "vario" "gefaelle_winkel" 3)))
f)
;; Vario-Winkel (Vertikalbogen) auf die verfuegbaren Werte 3..51 einrasten.
(defun vfl2-snap-vfwinkel (w / liste best bd d)
(setq liste (ssg-cfg-or "vario" "bogen_winkel" '(3 6 9 12 15 18 21 27 33 39 45 51)))
(setq best (car liste) bd 1e9)
(foreach x liste (setq d (abs (- x w))) (if (< d bd) (setq bd d best x)))
best)
;; --- Segment-Essenz einer selektierten Pfad-Kette (Modus 3) --------------
;; Fuehrt die 5-Feld-Bau-Segmente aus gf-analysiere-kette mit den absoluten
;; Start-/Endpunkten aus gf-sortiere-objekte (kette: (ent sp ep umk)) zu der
;; selbstbeschreibenden 9-Feld-Form zusammen, die das SEGS-Journal traegt:
;; (typ hz laenge bwinkel bseite spx spy epx epy)
;; Beide Listen sind gleich lang und in derselben Reihenfolge (segmente wurde
;; genau aus kette abgeleitet). spx/spy/epx/epy sind die planaren XY der
;; Segment-Enden - sie ersetzen den spaeteren direkten kette-Zugriff (AS-
;; Trimmung am ersten Segment, Soll-Ende-Report), sodass der Bau OHNE die
;; Original-Entities auskommt und beim Replay aus dem Journal gespeist wird.
(defun vfl-kette->segmente9 (kette segmente / out i seg item sp ep)
(setq out '() i 0)
(foreach seg segmente
(setq item (nth i kette) sp (cadr item) ep (caddr item))
(setq out (cons (list (nth 0 seg) (nth 1 seg) (nth 2 seg) (nth 3 seg) (nth 4 seg)
(car sp) (cadr sp) (car ep) (cadr ep))
out))
(setq i (1+ i)))
(reverse out))
;; 2D-Laenge eines Pfad-Objekts: LINE = Abstand Start-Endpunkt, ARC = Sehne
;; (Abstand Start-Endpunkt, dasselbe Mass wie gf-analysiere-kette es fuer die
;; Segmentlaenge nutzt). Nicht-LINE/ARC liefern 0.0 (werden dadurch als "zu
;; kurz" ausgewertet und verworfen). Rein lesend, ohne Nebeneffekt.
(defun vfl-obj-2d-laenge (obj / objname sp ep)
(setq objname (vla-get-ObjectName obj))
(if (or (= objname "AcDbLine") (= objname "AcDbArc"))
(progn
(setq sp (vlax-safearray->list (vlax-variant-value (vla-get-StartPoint obj))))
(setq ep (vlax-safearray->list (vlax-variant-value (vla-get-EndPoint obj))))
(gf-abstand-2d sp ep))
0.0))
;; Aus einer Objektliste alle Elemente unter *vfk-pfad-min-segmentlaenge*
;; herauswerfen (Geister-/Artefakt-Segmente, z.B. von einer Rechteck-Auswahl
;; miterfasste Mini-Objekte). Rueckgabe: (behalten-liste . verworfen-anzahl).
;; Reine Filterung, keine Meldung - der Aufrufer entscheidet ueber die Warnung.
(defun vfl-filter-mini-objekte (obj-liste / behalten verworfen)
(setq behalten '() verworfen 0)
(foreach obj obj-liste
(if (< (vfl-obj-2d-laenge obj) *vfk-pfad-min-segmentlaenge*)
(setq verworfen (1+ verworfen))
(setq behalten (cons obj behalten))))
(cons (reverse behalten) verworfen))
;; Objektauswahl + Analyse (Live) ODER Segment-Essenz aus dem Journal (Replay).
;; Gegenstueck zu vfl-in-selection, aber persistiert NICHT die Entity-Handles,
;; sondern die abgeleitete 9-Feld-Segmentliste (Journal-Art "SEGS"). Damit ist
;; der Modus-3-Block im XDATA vollstaendig beschrieben und der Doppelklick-Neubau
;; unabhaengig davon, ob die urspruenglichen LINE/ARC noch existieren.
;; Rueckgabe: die 9-Feld-Segmentliste oder nil (Abbruch/leer). filter/startpunkt
;; wie bei der Live-Auswahl; ecke-tol wird nur im Live-Zweig fuer die
;; Eck-Vorpruefung gebraucht (Replay-Daten sind bereits geprueft eingefroren).
(defun vfl-in-segmente (filter startpunkt ecke-tol / popped ss k obj-liste kette segmente ecke-bad segs9 filter-erg verworfen)
(setq popped (if *vfl-replay-queue* (vfl-replay-pop) nil))
(if popped
;; --- Replay: fertige Segment-Essenz direkt aus dem Journal ---
(progn
(setq segs9 (car popped))
(if (null segs9)
(if (vfl-headless-p) (vfl-headless-abbruch "SEGS-leer")))
segs9)
;; --- Live: Objekte waehlen, sortieren, analysieren, dann journalisieren ---
(progn
(if (vfl-headless-p) (vfl-headless-abbruch "SEGS"))
(setq ss (ssget filter))
(if (null ss)
(progn (vfl-journal-record "NIL" nil) nil)
(progn
;; Auswahlsatz -> VLA-Objektliste (wie im frueheren vfl-in-selection).
(setq obj-liste '() k 0)
(while (< k (sslength ss))
(setq obj-liste (cons (vlax-ename->vla-object (ssname ss k)) obj-liste))
(setq k (1+ k)))
(setq obj-liste (reverse obj-liste))
;; Geister-/Mini-Objekte VOR der Sortierung ausfiltern: ein von einer
;; Rechteck-Auswahl miterfasstes winziges Segment (unter
;; *vfk-pfad-min-segmentlaenge*) wuerde sonst per Nearest-Neighbor
;; stumm in die Kette gehaengt und ein Geister-Bauteil erzeugen (realer
;; Bug: 14-mm-Bogen am Kettenende). Verworfene mit Grund melden.
(setq filter-erg (vfl-filter-mini-objekte obj-liste))
(setq obj-liste (car filter-erg) verworfen (cdr filter-erg))
(if (> verworfen 0)
(vfl-meldung (ssg-textf "vfl-m3-mini-objekte-verworfen"
(list (itoa verworfen) (rtos *vfk-pfad-min-segmentlaenge* 2 0)))))
(setq kette (if obj-liste (gf-sortiere-objekte obj-liste startpunkt) nil))
(if (null kette)
(progn (princ (ssg-text "vfl-m3-fehler-nicht-sortierbar"))
(vfl-journal-record "NIL" nil) nil)
(progn
(setq segmente (gf-analysiere-kette kette))
(setq ecke-bad (vfl2-pruefe-eckwinkel kette ecke-tol))
(if ecke-bad
(progn
(vfl-meldung (ssg-textf "vfl-vwnb-alert-eckwinkel" (list (rtos ecke-bad 2 1))))
(vfl-journal-record "NIL" nil) nil)
(progn
(setq segs9 (vfl-kette->segmente9 kette segmente))
(vfl-journal-record "SEGS" segs9)
segs9)))))))))
(defun vf-linienzug-modus3 ( / ss obj-liste startpunkt start-hoehe as-seite
es-seite kette segmente segmente9 n i seg seg-ent typ hz laenge
bwinkel bseite frame antwort winkel deltaL
anzahl-gf anzahl-vf vfl-nummer lastEnt hoehe-bis
ecke-bad first-line last-line letzt-winkel
aus ein soll-ende ist-ende run-typ richtn best-w
kvariante L_VF ent-fp rad3 just-open
dbg-an old-error dbg-old-error vfl-ins)
(princ "\n\n=========================================")
(princ (ssg-text "vfl-vwnb-titel"))
(princ "\n=========================================")
;; --- Debug-Session (Schalter "vfl-modus3", DEFAULT AN) -------------------
;; Bei Bedarf abschaltbar: (dbg-schalter-off "vfl-modus3") in der Konsole.
;; Alle interaktiven Eingaben (Punkte, Hoehen, Seiten, Winkel, Menue-
;; Auswahlen) laufen ueber die vfl-in-*-Wrapper und werden dadurch
;; automatisch ueber vfl-journal-record mitgeloggt (siehe dort) - hier nur
;; noch Session-Start/-Ende + Zwischenergebnisse, die NICHT direkt aus einer
;; Nutzereingabe stammen (Analyse-Ergebnis, Segment-Header, Bau-Ergebnis).
;; *vfl-journal* wird - wie bei Modus 1/2 - als XDATA (Marker "linienzug3")
;; persistiert und ist damit per Doppelklick editierbar (vfl-edit-ent3, voller
;; Reset-Replay). Statt der Objekt-Handles traegt es die Segment-ESSENZ (SEGS,
;; siehe vfl-in-segmente): der Block ist selbst-enthalten und der Neubau
;; unabhaengig von den urspruenglichen LINE/ARC.
;; FRUEHER *error*-Handler: schliesst nur die dbg-Datei sauber, falls waehrend
;; der Pfad-/Praeambel-Phase (noch keine Geometrie) abgebrochen wird. Der
;; eigentliche Abbruch-WICKLER (Teil-Geometrie zu einem VF_n-Block sichern)
;; wird erst nach dem Setup scharf geschaltet (siehe unten), sobald
;; vfl-nummer/lastEnt feststehen - analog Modus 2.
(setq dbg-an (vfl-dbg-session-start "vfl-modus3" "vfl_modus3.dbg"
"vf-linienzug-modus3" "=== SESSION Modus 3 (Vorwaerts-Nachbau) START ==="))
(vfl-dbg-hook-installieren)
(vfl-gf-abhaengigkeit-sicherstellen "vfl-m3-alert-gf-modul")
;; --- 1./3. Pfad-Objekte + Segment-Essenz: siehe Schritt 3 unten. ---
;; Auswahl UND Ableitung laufen zusammen in vfl-in-segmente, weil die
;; Sortierung den Startpunkt braucht (Schritt 2) - die Objektauswahl steht
;; daher NACH der Startpunkt-/AS-Praeambel, nicht mehr davor. Journalisiert
;; wird nur noch die abgeleitete Segment-Essenz (SEGS), nicht die Handles.
;; --- 2. Startpunkt (hoehere Seite) + Hoehe + AS-Seite ---
;; Startpunkt + Hoehe (gemeinsame Praeambel-Schale, siehe vfl-frage-punkt-hoehe).
(setq startpunkt (vfl-frage-punkt-hoehe "vfl-m3-prompt-startpunkt"
"vfl-prompt-hoehe-startpunkt" "vfl-abgebrochen" "Starthoehe (Z, mm):"))
;; AS-Element (in Modus 3 immer gesetzt): Winkel(30/90) + Seite als EIN
;; Gruppen-Dialog. vflw-gruppe-winkel-seite-impl legt Winkel ("90"/"30")
;; und Seite ("1"/"2") in *vflw-pending* ab - genau die Reihenfolge, die
;; die beiden Reads unten erwarten. AS-Winkel ueber vfl-in-value (pending-
;; aware; vf-frage-element-winkel selbst ist es NICHT), damit der vom Dialog
;; abgelegte Wert konsumiert wird statt live erneut zu fragen.
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-winkel-seite-impl
(list "vf-winkel-aus-header"
(list (ssg-text "vf-winkel-90") (ssg-text "vf-winkel-30"))
(list "90" "30"))))
(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"))))) ; 30/90 vor Seite
(if dbg-an (progn (dbgmsg "AS-Winkel:") (dbg '*vfl-as-winkel*) (dbgflush)))
(princ (ssg-text "vfl-vwnb-aus-seite-menu"))
(setq as-seite (if (= (vfl-in-string (ssg-text "prompt-wahl-1-2")) "2") "rechts" "links"))
(vf-set-as-masse *vfl-as-winkel* as-seite) ; Masse fuer Variante
;; --- 3. Pfad waehlen + Sortieren + Analysieren + Eck-Vorpruefung ---
;; vfl-in-segmente kapselt Live (ssget -> gf-sortiere-objekte ->
;; gf-analysiere-kette -> Eck-Vorpruefung -> 9-Feld-Essenz) UND Replay
;; (fertige Essenz aus dem SEGS-Journal). Persistiert die Segment-Essenz
;; statt der Objekt-Handles - damit ist der Block selbst-enthalten und der
;; Doppelklick-Neubau (vfl-edit-ent3) braucht die Original-LINE/ARC nicht.
(princ (ssg-text "vfl-m3-pfad-waehlen"))
(setq segmente9 (vfl-in-segmente '((0 . "LINE,ARC")) startpunkt *vfk-eckwinkel-toleranz*))
(if (null segmente9) (progn (princ (ssg-text "vfl-m3-keine-objekte")) (exit)))
;; segmente = 5-Feld-Bausicht (typ hz laenge bwinkel bseite), abgeleitet aus
;; der 9-Feld-Essenz - der bestehende Bau unten arbeitet unveraendert damit.
(setq segmente (mapcar (function (lambda (s) (list (nth 0 s) (nth 1 s) (nth 2 s) (nth 3 s) (nth 4 s))))
segmente9))
(if dbg-an
(progn
(dbgmsg (strcat "ANALYSE: " (itoa (length segmente)) " Segment(e) erkannt"))
(dbg 'segmente)
(dbgflush)))
;; --- 5. Setup ---
(setq aus (if aus-dx aus-dx *vfk-as-es-fallback-dx*) ein (if ein-dx ein-dx *vfk-as-es-fallback-dx*))
(setq rad3 (* (float (ssg-cfg-or "vario" "gefaelle_winkel" 3)) (/ pi 180.0)))
;; Entry-Fussabdruck (planar) einer VF-Einheit: GF1(Basis)+Separator(300)+Umlenk(500)
(setq ent-fp (* (+ *vfl-gf-min-laenge* *vfk-separator-laenge* *vfk-stations-laenge*) (cos rad3)))
(setq run-typ nil)
(setq vfl-nummer (vf-next-number))
(setq lastEnt (vf-lastent-ohne-attribute))
(vfl-acc-reset)
(setq anzahl-gf 0 anzahl-vf 0 frame nil)
;; --- Abbruch-Sicherung ab hier scharf (analog Modus 1/2) ---
;; Ab Schritt 6 kann jederzeit Geometrie entstehen. Der Wickler sichert die
;; bis zum Abbruch gebaute Teil-Geometrie als VF_n-Block mit dem vollen
;; Journal (Marker "linienzug3") - sie bleibt stehen und ist per Doppelklick
;; fortsetz-/editierbar. Loest den fruehen dbg-Hook ab (Ruecksprungziel bleibt
;; dbg-old-error) und uebernimmt das dbgclose. *ssg-ils-dim* zuruecksetzen:
;; Begruendung siehe gleichlautende Stelle in vf-linienzug-modus.
(setq old-error dbg-old-error)
;; Scharfen Abbruch-Wickel-Handler installieren (gemeinsam, siehe
;; vfl-abbruch-handler-installieren).
(vfl-abbruch-handler-installieren "linienzug3")
(setq n (length segmente))
;; erste/letzte "Linie" fuer die Trimmung bestimmen
(setq first-line -1 last-line -1 i 0)
(while (< i n)
(if (= (car (nth i segmente)) "Linie")
(progn (if (< first-line 0) (setq first-line i)) (setq last-line i)))
(setq i (1+ i)))
(if (< first-line 0)
(progn (vfl-meldung (ssg-text "vfl-vwnb-alert-keine-gerade")) (exit)))
;; --- 6. Bau-Schleife (Meilenstein 2: GF + VF-Laeufe, Run-State-Machine) ---
;; run-typ: nil / "GF" / "VF". Beim Wechsel GF<->VF wird die VF-Einheit
;; geoeffnet (GF1+Separator+Umlenk) bzw. geschlossen (Motor+GF2).
;; v1-Naeherung Laenge: Entry-Fussabdruck wird vom ersten VF-Koerper abgezogen;
;; der Exit (Motor+GF2) wird beim Schliessen ANGEHAENGT (verlaengert den VF-Lauf
;; ggue. dem gezeichneten Pfad; feinere Laengen-Reservierung -> spaeter).
(setq i 0)
(while (< i n)
(setq seg (nth i segmente) typ (car seg) hz (cadr seg))
;; seg-ent: KEINE Live-Entity mehr - Modus 3 baut jetzt aus der Segment-
;; Essenz (SEGS), nicht aus durchgereichten LINE/ARC. Damit entfaellt das
;; kosmetische Pfad-Segment-Highlight (vfl-seg-highlight ist nil-sicher).
;; Der Wegfall ist bewusst: die Essenz macht den Bau/Replay unabhaengig von
;; den Original-Objekten, ein Highlight haette beim Doppelklick-Neubau ohnehin
;; keine Entity mehr zum Hervorheben.
(setq seg-ent nil)
;; Wizard: alle Fragen dieses Segments in EINEM Dialog erfassen (die
;; unveraenderten vfl-in-*-Aufrufe unten konsumieren die Antworten aus
;; *vflw-pending*). Bei einem Bogen ist Vario-Kurve nur im offenen VF-Lauf
;; erlaubt - deshalb die Variante-Antwort nur mitschicken, wenn run-typ="VF"
;; (sonst konsumiert der Aufrufer sie nicht und sie wuerde ins naechste
;; Segment lecken).
(if (vfl-wizard-aktiv)
(progn
(vfl-seg-highlight seg-ent T)
(if (= typ "Linie")
(vflw-seg-linie-m3-impl
(vfl-seg-kopf (1+ i) n
(strcat "Gerade L=" (rtos (caddr seg) 2 0) " mm hz=" (rtos hz 2 1)))
*vfk-gefaelle-winkel* 3)
(vflw-seg-bogen-impl
(vfl-seg-kopf (1+ i) n
(strcat "Eck/Bogen " (itoa (nth 3 seg)) " Grad " (nth 4 seg)))
(= run-typ "VF"))) ; Modus 3: Vario nur im offenen VF-Lauf
(vfl-seg-highlight seg-ent nil)))
(cond
;; ===================== GERADE =====================
((= typ "Linie")
(setq laenge (caddr seg))
(princ (ssg-textf "vfl-m3-seg-gerade"
(list (itoa (1+ i)) (itoa n) (rtos laenge 2 0) (rtos hz 2 1))))
(if dbg-an
(dbgmsg (strcat "SEGMENT " (itoa (1+ i)) "/" (itoa n)
" GERADE (laenge=" (rtos laenge 2 0) " hz=" (rtos hz 2 1) ")")))
(princ (ssg-text "vfl-vwnb-typ-gerade"))
(setq antwort (vfl-in-string (ssg-text "prompt-wahl-1-2-3-4")))
(cond
;; --- GF-Gerade ---
((or (= antwort "") (= antwort "1"))
(if (= run-typ "VF") ; offenen VF-Lauf schliessen
(progn (setq frame (vfl2-vf-close frame (car (frame->hz-winkel frame))))
(setq run-typ nil)))
(setq winkel (vfl-in-real (ssg-text "vfl-m3-prompt-gf-neigung")))
(if (null winkel) (setq winkel *vfk-gefaelle-winkel*))
(if (> winkel *vfk-gefaelle-winkel*)
(progn (princ (ssg-text "vfl-m3-hinweis-gf-max")) (setq winkel *vfk-gefaelle-winkel*)))
(if (null frame)
(setq frame (vfl2-insert-as-gf startpunkt hz winkel as-seite)))
(setq deltaL laenge)
;; erste Gerade: AS-Fussabdruck ENTLANG des Pfads exakt aus KS_AUS abziehen
(if (= i first-line)
(setq deltaL (- deltaL
(+ (* (- (car (car frame)) (car startpunkt)) (cos (* hz (/ pi 180.0))))
(* (- (cadr (car frame)) (cadr startpunkt)) (sin (* hz (/ pi 180.0))))))))
(if (= i last-line) (setq deltaL (- deltaL *vfk-separator-laenge* ein)))
(setq deltaL (max *vfk-restlaenge-min-clamp* deltaL))
(setq frame (vfl-insert-gf-segment (car frame) hz deltaL winkel))
(vfl-acc-gf-seg (/ deltaL (cos (* winkel (/ pi 180.0)))) winkel)
(setq anzahl-gf (1+ anzahl-gf) letzt-winkel winkel run-typ "GF"))
;; --- VF-Gerade (Ab / Auf / Horizontal) ---
(t
(setq richtn (cond ((= antwort "3") "Auf") ((= antwort "4") "horizontal") (t "Ab")))
(setq just-open nil)
(if (null frame) ; AS (flach) am Kettenanfang, KS_AUS auf Pfad
(setq frame (vfl2-insert-as-vf startpunkt hz as-seite)))
(if (not (= run-typ "VF")) ; VF-Einheit oeffnen
(progn (setq frame (vfl2-vf-open frame hz))
(setq run-typ "VF" just-open t)))
(if (= richtn "horizontal")
(setq best-w 0)
(progn
(setq best-w (vfl-in-int (ssg-text "vfl-vwnb-prompt-vario-winkel")))
(if (null best-w) (setq best-w 3))
(setq best-w (vfl2-snap-vfwinkel best-w))
(if dbg-an (progn (dbgmsg "Vario-Winkel (gesnapt):") (dbg 'best-w) (dbgflush)))))
(setq L_VF laenge)
(if just-open (setq L_VF (- L_VF ent-fp))) ; Entry-Fussabdruck reservieren
(setq L_VF (max *vfk-restlaenge-min-clamp* L_VF))
(setq frame (vfl-frame-3grad (vfs-vf-koerper (car frame) richtn best-w L_VF hz) hz))
(vfl-acc-vf-seg richtn best-w L_VF)
(setq anzahl-vf (1+ anzahl-vf) run-typ "VF"))))
;; ===================== ECK / BOGEN =====================
((= typ "Bogen")
(setq bwinkel (nth 3 seg) bseite (nth 4 seg))
(princ (ssg-textf "vfl-m3-seg-bogen"
(list (itoa (1+ i)) (itoa n) (itoa bwinkel) bseite)))
(if dbg-an
(dbgmsg (strcat "SEGMENT " (itoa (1+ i)) "/" (itoa n)
" BOGEN (bwinkel=" (itoa bwinkel) " bseite=" bseite ")")))
(princ (ssg-text "vfl-m3-typ-bogen"))
(setq antwort (vfl-in-string (ssg-text "prompt-wahl-1-2")))
(if (= antwort "2")
;; --- Vario-Kurve (nur im VF-Lauf) ---
(if (not (= run-typ "VF"))
(princ (ssg-text "vfl-vwnb-kurve-nur-vf"))
(progn
(princ (ssg-text "vfl-m3-variante-frage"))
(setq kvariante (if (= (vfl-in-string (ssg-text "vfl-prompt-wahl-1-2-def2")) "1") "aussen" "innen"))
(setq frame (vfl-insert-vario-kurve-block frame bwinkel bseite kvariante))))
;; --- GF-Bogen ---
(progn
(if (null frame)
(progn (vfl-meldung (ssg-text "vfl-m3-alert-beginnt-bogen")) (exit)))
(if (= run-typ "VF") ; VF-Lauf vor GF-Bogen schliessen
(progn (setq frame (vfl2-vf-close frame (car (frame->hz-winkel frame))))
(setq run-typ nil)))
(setq frame (vfl-insert-gf-bogen-block frame bwinkel bseite))
(setq run-typ "GF"))))
)
(setq i (1+ i)))
;; --- 7. Kettenende: offenen VF-Lauf schliessen, dann Separator + ES ---
(if (null frame) (progn (princ (ssg-text "vfl-m3-nichts-gebaut")) (exit)))
;; vfl-frage-es-seite nutzt intern bereits vfl-in-value/vfl-menu - wird also
;; automatisch mitgeloggt, kein manueller dbgmsg noetig.
(setq es-seite (vfl-frage-es-seite nil))
(if (= run-typ "VF")
(progn
(setq frame (vfl2-vf-close frame (car (frame->hz-winkel frame))))
(setq frame (vfl-insert-es-element "VF" frame (car (frame->hz-winkel frame))
0.0 (caddr (car frame)) es-seite)))
(setq frame (vfl-insert-es-element "GF" frame (car (frame->hz-winkel frame))
(if letzt-winkel letzt-winkel *vfk-gefaelle-winkel*) (caddr (car frame)) es-seite)))
;; --- 8. Block + Journal-XDATA + Ist-Ziel-Report ---
(setq hoehe-bis (caddr (car frame)))
(setq vfl-ins
(vfl-block-erstellen vfl-nummer anzahl-gf anzahl-vf (caddr startpunkt) hoehe-bis
(vfl-planar-dist startpunkt (car frame)) as-seite es-seite startpunkt lastEnt))
;; Eingabe-Journal am fertigen Block persistieren (Marker "linienzug3") ->
;; spaeter per Doppelklick editierbar (vfl-edit-ent3: voller Reset-Replay wie
;; Modus 2, aus der Segment-Essenz statt aus Objekt-Handles).
(if vfl-ins (vfl-journal-xdata-schreiben vfl-ins "linienzug3"))
;; Endpunkt des letzten gezeichneten Segments - aus der Segment-Essenz
;; (epx/epy des letzten Segments), nicht mehr aus den Original-Entities.
;; Damit stimmt der Soll-Ende-Report auch beim Doppelklick-Neubau, wenn die
;; urspruenglichen LINE/ARC laengst weg sind.
(setq soll-ende (list (nth 7 (last segmente9)) (nth 8 (last segmente9))))
(setq ist-ende (car frame)) ; ES-KS_AUS
(princ "\n\n=========================================")
(princ (ssg-text "vfl-vwnb-eingefuegt"))
(princ (ssg-textf "vfl-vwnb-soll-ende"
(list (rtos (car soll-ende) 2 1) (rtos (cadr soll-ende) 2 1))))
(princ (ssg-textf "vfl-vwnb-ist-ende"
(list (rtos (car ist-ende) 2 1) (rtos (cadr ist-ende) 2 1) (rtos (caddr ist-ende) 2 1))))
(princ (ssg-textf "vfl-vwnb-abweichung-xy"
(list (rtos (- (car ist-ende) (car soll-ende)) 2 1)
(rtos (- (cadr ist-ende) (cadr soll-ende)) 2 1))))
(princ "\n=========================================")
;; --- Debug-Session sauber abschliessen ---
(setq *error* old-error)
(if dbg-an
(progn
(dbgmsg (strcat "ERGEBNIS: VF_" (itoa vfl-nummer)
" (GF=" (itoa anzahl-gf) " VF=" (itoa anzahl-vf) ")"))
(dbg 'soll-ende)
(dbg 'ist-ende)
(dbgmsg "=== SESSION Modus 3 ENDE ===")
(dbgreturn (list "VF" vfl-nummer))
(dbgclose)))
(princ)
)
;; ============================================================
;; MODUS 3: Pfad + Ziel-Hoehe (Randwert-Solver) - M3a
;; Schritt 1+2: Geruest + Klassifizierung + Anker-Report. NOCH KEIN Bauen.
;; Details siehe doc/VarioFoerderer_Linienzug_Prinzipien.md, Abschnitt 14.
;; ============================================================
;; Signierte Z-Aenderung (mm) einer GF-Geraden: negativ = Abfall.
;; l-planar = XY-Planlaenge (aus Pfad), winkel = Neigung in Grad (fallend).
;; Ueber die Planlaenge gilt dz = -L * tan(winkel) (Fussabdruck bleibt L,
;; die 3D-Laenge waechst mit L/cos, siehe Doc 14.1).
(defun vfl3-gf-dz (l-planar winkel / rad)
(setq rad (* (float winkel) (/ pi 180.0)))
(- (* (float l-planar) (/ (sin rad) (cos rad)))))
;; Signierte Z-Aenderung eines Plan-Eintrags.
;; GF-Gerade -> -L*tan(winkel)
;; GF-Bogen -> dz aus gf-bogen-masse (Block-KS)
;; Vario-Kurve -> 0 (auf 0 Grad geflacht)
;; VF-Gerade -> nil (unbekannt = Teil der Bruecke)
(defun vfl3-seg-dz (e)
(cond
((= (car e) "Linie")
(if (= (nth 3 e) "GF") (vfl3-gf-dz (caddr e) (nth 4 e)) nil))
((= (car e) "Bogen")
(if (= (nth 5 e) "Vario-Kurve")
0.0
(cadr (gf-bogen-masse (nth 3 e) (nth 4 e)))))
(t 0.0)))
;; Loest die VF-Bruecke ueber den Modus-1-Solver berechne-alle-winkel.
;; Die Bruecke sitzt MITTIG in der Kette -> KEIN terminales AS/ES
;; (aus-dx/aus-dz/ein-dx/ein-dz = 0), feste-hz = *vfl-feste-horizontal* (1300:
;; Umlenk 500 + Motor 500 + Einlauf-Separator 300). GF1/GF2 bleiben fest 3 Grad,
;; ihre Laenge variiert (das ist der Ausgleich, siehe Doc 14.3).
;; dH-signiert: negativ = Auf (steigt), positiv = Ab (faellt).
;; Rueckgabe: (winkel L_GF L_VF richtung) oder nil (kein passender Winkel).
(defun vfl3-solve-bruecke (span dH-signiert extra-fest /
o-adx o-adz o-eix o-eiz richtung fh res winkel)
(setq richtung (if (< dH-signiert 0) "Auf" "Ab"))
(setq fh (+ (if (boundp '*vfl-feste-horizontal*) *vfl-feste-horizontal* 1300.0)
(if extra-fest extra-fest 0.0)))
;; terminale AS/ES-Masse fuer die mittige Bruecke ausblenden (Save/Restore)
(setq o-adx aus-dx o-adz aus-dz o-eix ein-dx o-eiz ein-dz)
(setq aus-dx 0.0 aus-dz 0.0 ein-dx 0.0 ein-dz 0.0)
(setq res (berechne-alle-winkel span (abs dH-signiert) richtung fh nil))
(setq aus-dx o-adx aus-dz o-adz ein-dx o-eix ein-dz o-eiz)
(setq winkel (car res))
(if winkel (list winkel (cadr res) (caddr res) richtung) nil))
;; GF-Bogen-dz, wenn der Bogen bei Neigung theta (Grad) KS-gekettet wird:
;; der lokale (dx,dz) des Blocks wird um theta gekippt.
;; Z-Anteil = -dx*sin(theta) + dz*cos(theta) (negativ = Abfall)
(defun vfl3-bogen-dz-incl (bwinkel bseite theta / m dx dz rad)
(setq m (gf-bogen-masse bwinkel bseite) dx (car m) dz (cadr m))
(setq rad (* (float theta) (/ pi 180.0)))
(+ (* (- dx) (sin rad)) (* dz (cos rad))))
;; Exakter Gesamt-Abstieg (positiv, mm) des BACK-Laufs (Plan-Segmente ab
;; start-idx bis n-1) PLUS Auslauf-Separator (300 mm, bei aktueller Neigung) +
;; ES-Eigen-dz. Spiegelt den tatsaechlichen Bau (Trimmung der letzten Geraden um
;; 300+ein-fp, GF-Bogen bei Neigung). Eintritts-Neigung = 3 Grad (die Bruecke
;; endet mit GF2 auf 3 Grad). So wird die Junction-Hoehe exakt statt geschaetzt.
(defun vfl3-dback (plan start-idx n last-idx ein-fp / i seg drop inc w dL rad)
(setq drop 0.0 inc *vfk-gefaelle-winkel* i start-idx)
(while (< i n)
(setq seg (nth i plan))
(cond
((= (car seg) "Linie")
(setq w (nth 4 seg) dL (caddr seg))
(if (= i last-idx) (setq dL (- dL *vfk-separator-laenge* ein-fp)))
(setq dL (max *vfk-restlaenge-min-clamp* dL))
(setq rad (* (float w) (/ pi 180.0)))
(setq drop (+ drop (* dL (/ (sin rad) (cos rad)))))
(setq inc w))
((= (car seg) "Bogen")
(if (= (nth 5 seg) "GF-Bogen")
(setq drop (- drop (vfl3-bogen-dz-incl (nth 3 seg) (nth 4 seg) inc))))))
(setq i (1+ i)))
;; Auslauf-Separator (300 mm bei aktueller Neigung inc) + ES-Eigen-dz
(setq rad (* (float inc) (/ pi 180.0)))
(setq drop (+ drop (* *vfk-separator-laenge* (/ (sin rad) (cos rad)))))
(setq drop (+ drop (abs (if ein-dz ein-dz 65.0))))
drop)
;; Feste (von der Kletterlaenge UNABHAENGIGE) Z-Aenderung einer VF-Einheit bei
;; Kletterwinkel w (ohne GF2, das wird gemessen): GF1+Sep(300)+Umlenk(500)+
;; Motor(500) (alle 3 Grad, senkend) + je Kletterer zwei Boegen + je
;; Horizontal-Mitte-Koerper zwei 3-Grad-Uebergangsboegen. Einbau-Rotationen wie
;; in vfs-vf-koerper; Bogen-dz_eff = -dx*sin(rot) + dz_roh*cos(rot) (am Log
;; verifiziert). So ist die Hoehe rein rechnerisch bestimmt -> Kletterlaenge folgt.
(defun vfl3-einheit-fix-dz (w n-climb n-hor gf1 richtung /
pi180 rad3 s3 tot m1 m2 r1 r2 dz1 dz2)
(setq pi180 (/ pi 180.0) rad3 (* *vfk-gefaelle-winkel* pi180) s3 (sin rad3) tot 0.0)
;; feste 3-Grad-Teile senken immer ab (GF1 + Separator + Umlenk + Motor)
(setq tot (- tot (* (+ (float gf1) *vfk-separator-laenge* *vfk-stations-laenge* *vfk-stations-laenge*) s3)))
;; Kletterer-Boegen (Rotationen wie vfs-vf-koerper: 1. Bogen @3, 2. Bogen @(3-w)/(w+3))
(if (= richtung "Auf")
(setq m1 (get-bogen-mass bogen-auf w) r1 rad3
m2 (get-bogen-mass bogen-ab w) r2 (* (- *vfk-gefaelle-winkel* w) pi180))
(setq m1 (get-bogen-mass bogen-ab w) r1 rad3
m2 (get-bogen-mass bogen-auf w) r2 (* (+ w *vfk-gefaelle-winkel*) pi180)))
(setq dz1 (+ (* (- (car m1)) (sin r1)) (* (caddr m1) (cos r1))))
(setq dz2 (+ (* (- (car m2)) (sin r2)) (* (caddr m2) (cos r2))))
(setq tot (+ tot (* n-climb (+ dz1 dz2))))
;; Horizontal-Mitte-Koerper: auf_3@3 + ab_3@0
(setq m1 (get-bogen-mass bogen-auf (fix *vfk-gefaelle-winkel*)) m2 (get-bogen-mass bogen-ab (fix *vfk-gefaelle-winkel*)))
(setq dz1 (+ (* (- (car m1)) (sin rad3)) (* (caddr m1) (cos rad3))))
(setq dz2 (caddr m2))
(setq tot (+ tot (* n-hor (+ dz1 dz2))))
tot)
;; Fester PLANARER (XY-)Fussabdruck einer VF-Einheit bei Kletterwinkel w
;; (ohne die Kletter-Strecken selbst): Stationen (GF1+Sep+Umlenk+Motor, @3 Grad)
;; + je Kletterer die zwei Boegen + je Horizontal-Mitte-Koerper die zwei
;; 3-Grad-Boegen. dx_eff = dx*cos(rot) + dz_roh*sin(rot) (am Log verifiziert).
;; Dient der Winkelwahl: Fussabdruck + Kletter-Planlaenge soll die Lauflaenge treffen.
(defun vfl3-einheit-fix-dx (w n-climb n-hor gf1 richtung /
pi180 rad3 tot m1 m2 r1 r2 dx1 dx2)
(setq pi180 (/ pi 180.0) rad3 (* *vfk-gefaelle-winkel* pi180) tot 0.0)
(setq tot (* (+ (float gf1) *vfk-separator-laenge* *vfk-stations-laenge* *vfk-stations-laenge*) (cos rad3))) ; Stationen planar
(if (= richtung "Auf")
(setq m1 (get-bogen-mass bogen-auf w) r1 rad3
m2 (get-bogen-mass bogen-ab w) r2 (* (- *vfk-gefaelle-winkel* w) pi180))
(setq m1 (get-bogen-mass bogen-ab w) r1 rad3
m2 (get-bogen-mass bogen-auf w) r2 (* (+ w *vfk-gefaelle-winkel*) pi180)))
(setq dx1 (+ (* (car m1) (cos r1)) (* (caddr m1) (sin r1))))
(setq dx2 (+ (* (car m2) (cos r2)) (* (caddr m2) (sin r2))))
(setq tot (+ tot (* n-climb (+ dx1 dx2))))
(setq m1 (get-bogen-mass bogen-auf (fix *vfk-gefaelle-winkel*)) m2 (get-bogen-mass bogen-ab (fix *vfk-gefaelle-winkel*)))
(setq dx1 (+ (* (car m1) (cos rad3)) (* (caddr m1) (sin rad3)))) ; auf_3 @ 3
(setq dx2 (car m2)) ; ab_3 @ 0
(setq tot (+ tot (* n-hor (+ dx1 dx2))))
tot)
;; Uebergang von der 3-Grad-Kletterbasis in die FLACHE Zone (0 Grad): EIN auf_3-Bogen
;; (Rotation 3 Grad). Rueckgabe: neuer Punkt.
(defun vfl3-flach-ein (pt hz / m)
(setq m (get-bogen-mass bogen-auf 3))
(insert-rotated-block-with-ks "Vario_Bogen_auf_3_TEF_rechts" pt 3 (car m) (caddr m) hz))
;; Uebergang aus der flachen Zone (0 Grad) zurueck auf 3-Grad-Basis: EIN ab_3-Bogen
;; (Rotation 0 Grad). Rueckgabe: neuer Punkt.
(defun vfl3-flach-aus (pt hz / m)
(setq m (get-bogen-mass bogen-ab 3))
(insert-rotated-block-with-ks "Vario_Bogen_ab_3_TEF_rechts" pt 0 (car m) (caddr m) hz))
;; Kletter-Segment loesen und Winkel WAEHLEN LASSEN (Modus-1-Solver + vfl-waehle-winkel).
;; Mittige Bruecke -> KEIN terminales AS/ES (Masse auf 0, Save/Restore). feste = fester
;; Horizontal-Anteil des Kletter-Segments (Umlenk+Separator = 800; Motor sitzt spaeter
;; am Kettenende). Rueckgabe: (winkel L_GF L_VF richtung) oder nil.
(defun vfl3-waehle-winkel (span dH-signiert feste /
o-adx o-adz o-eix o-eiz richtung res wahl)
(setq richtung (if (< dH-signiert 0) "Auf" "Ab"))
(setq o-adx aus-dx o-adz aus-dz o-eix ein-dx o-eiz ein-dz)
(setq aus-dx 0.0 aus-dz 0.0 ein-dx 0.0 ein-dz 0.0)
(setq res (berechne-alle-winkel span (abs dH-signiert) richtung feste nil))
(setq aus-dx o-adx aus-dz o-adz ein-dx o-eix ein-dz o-eiz)
(setq wahl (vfl-waehle-winkel (nth 3 res)))
(if wahl (list (car wahl) (cadr wahl) (caddr wahl) richtung) nil))
;; ES-Element-Fussabdruck ENTLANG/QUER zu einer Fahrtrichtung seg-hz (Welt-
;; Komponenten), aus der REALEN Blockgeometrie (vf-element-ks-info) berechnet.
;; Kernstueck von vfl3-es-fueller, hier ausgelagert, damit AUCH die feste-vf-
;; Berechnung in vf-linienzug-modus2 (Fall hor-pairs=0, OHNE flache
;; Zone/vfl3-es-fueller) denselben Wert nutzen kann, statt den ES-Laengsanteil
;; im Laengenbudget des Solvers stillschweigend zu vergessen (realer Bug:
;; Kette ueberschoss um ungefaehr along-es+Separator, siehe dortiger Kommentar).
;; Rueckgabe: (list along-es perp-es) oder nil, wenn der Block nicht lesbar
;; ist (Aufrufer faellt dann auf eine grobe Naeherung zurueck).
(defun vfl3-es-vektor (es-block seg-hz / info theta dpx dpy npx npy phi vx vy vwx vwy)
(setq info (vf-element-ks-info es-block))
(if (null info)
nil
(progn
(setq theta (* seg-hz (/ pi 180.0)))
(setq dpx (cos theta) dpy (sin theta)) ; seg-hz Richtung
(setq npx (- (sin theta)) npy (cos theta)) ; senkrecht zu seg-hz
(setq phi (* (- seg-hz (car info)) (/ pi 180.0))) ; Block -> Welt
(setq vx (caddr info) vy (cadddr info))
(setq vwx (- (* vx (cos phi)) (* vy (sin phi)))) ; V_es (Welt)
(setq vwy (+ (* vx (sin phi)) (* vy (cos phi))))
(list (+ (* vwx dpx) (* vwy dpy)) (+ (* vwx npx) (* vwy npy)))
)
)
)
;; Letzte Fueller-Laenge so, dass der Endpunkt auf der ES-KS_AUS-Achse liegt.
;; pt = Fueller-Start (flach 0 Grad), seg-hz = letzte Richtung, es-block = ES-Block,
;; gf2 = erwartete GF2-Laenge, rad3 = 3 Grad (rad). Modell: der Schwanz (Fueller +
;; ab_3 + Motor + GF2 + Separator + ES) verschiebt sich starr entlang seg-hz.
;; KS_AUS = pt + (fill + C)*dp + perp-es*np
;; C = 196 (ab_3) + (Motor 500 + GF2 + Sep 300)*cos3 + along-es
;; Achse u_a = seg-hz + (KS_AUS.xu - KS_EIN.xu)_Block ; (Endpunkt-KS_AUS)*n_a=0 -> fill
(defun vfl3-es-fueller (pt endp seg-hz es-block gf2 rad3 /
vek theta dpx dpy npx npy info
along-es perp-es ua na-x na-y cc dp-na np-na ep-na)
(setq vek (vfl3-es-vektor es-block seg-hz))
(if (null vek)
(- (+ (* (- (car endp) (car pt)) (cos (* seg-hz (/ pi 180.0))))
(* (- (cadr endp) (cadr pt)) (sin (* seg-hz (/ pi 180.0)))))
(+ 196.0 (* (+ *vfk-feste-horizontal-kettenende* gf2) (cos rad3)))) ; Fallback: Along-Naeherung
(progn
(setq info (vf-element-ks-info es-block))
(setq theta (* seg-hz (/ pi 180.0)))
(setq dpx (cos theta) dpy (sin theta)) ; seg-hz Richtung
(setq npx (- (sin theta)) npy (cos theta)) ; senkrecht zu seg-hz
(setq along-es (car vek) perp-es (cadr vek))
(setq ua (* (+ seg-hz (- (cadr info) (car info))) (/ pi 180.0))) ; KS_AUS-Achse
(setq na-x (- (sin ua)) na-y (cos ua)) ; senkrecht zur KS_AUS-Achse
(setq cc (+ 196.0 (* (+ *vfk-feste-horizontal-kettenende* gf2) (cos rad3)) along-es))
(setq dp-na (+ (* dpx na-x) (* dpy na-y)))
(setq np-na (+ (* npx na-x) (* npy na-y)))
(setq ep-na (+ (* (- (car endp) (car pt)) na-x) (* (- (cadr endp) (cadr pt)) na-y)))
(if (> (abs dp-na) 1e-6)
(- (/ (- ep-na (* perp-es np-na)) dp-na) cc)
100.0))))
(defun vf-linienzug-modus2 ( / ss k obj-liste startpunkt start-hoehe as-seite
endpunkt end-hoehe es-seite kette segmente segmente9 n i seg
seg-ent
typ hz laenge bwinkel bseite antwort winkel klass
plan e ecke-bad vf-start vf-ende dz z-front
z-junction dH-bruecke span-bruecke dH-gesamt
vf-count in-vf richtn req-w loesung
aus ein first-line last-line frame deltaL
br-winkel br-gf1 br-gf2 br-lvf br-richtn pt
letzt-winkel vfl-nummer lastEnt anzahl-gf anzahl-vf
hoehe-bis soll-ende ist-ende old-error vfl-ins
vf-run-member carrier-idx kv-variante seg-hz letzt-koerper-hz
vf-first-line vf-last-line climber-span mid-hor
br-lvf this-lvf
nach-kurve nach-vf sep-vor-gf sep-nach-gf
n-climb n-hor target-climb winkel-list
fdz fdx wslope lvf planar-len ang-diff best-diff w
climb-thresh longest-idx climbers nonclimber-len
nonclimber-cnt nkurve kurve-chords run-span
hor-koerper-len hor-pairs flat-p flat-zone-erste fill-len wahl3
rad3v feste-vf dH-adj dir-x dir-y fill-D end-hz
es-vek-fix es-along-fix
gf-total br-gf-mode br-gf2-exp filler-A-len filler-a-done
as-vorhanden es-vorhanden dbg-an dbg-old-error
m2-lauf-antwort m2-offenes-ende es-frueh-abgelehnt
m2-front-antwort m2-offener-anfang m2-kein-gf1-vorne entry-fixed)
(princ "\n\n=========================================")
(princ (ssg-text "vfl-m3-titel"))
(princ "\n=========================================")
;; *vfl-journal* VOR dem allerersten vfl-in-*-Aufruf zuruecksetzen (nicht
;; *vfl-replay-queue*, siehe Kommentar bei vfl-journal-reset-lauf und die
;; analoge Korrektur in vf-linienzug-modus). Frueher stand dieser Reset erst
;; kurz vor Phase B (nach der Pfadauswahl UND der Startpunkt/Hoehe/AS-
;; Praeambel) und loeschte damit die bereits aufgezeichnete Praeambel dieses
;; Laufs selbst wieder.
(vfl-journal-reset-lauf)
;; --- Debug-Session (Schalter "vfl-modus2", DEFAULT AN) ------------------
;; (dbg-schalter-off "vfl-modus2") deaktiviert bei Bedarf. Alle Eingaben
;; laufen bereits durchgehend ueber die vfl-in-*-Wrapper (siehe unten) und
;; werden dadurch automatisch ueber vfl-journal-record mitgeloggt (analog
;; Modus 1). Ein FRUEHER, duenner *error*-Hook schliesst die Datei bereits
;; sauber, falls waehrend Phase A (Klassifizierungsfragen, noch keine
;; Geometrie) abgebrochen wird - die eigentliche Abbruch-Sicherung (Wickeln
;; der Teil-Geometrie) wird erst spaeter zu Beginn von Phase B installiert
;; (siehe dortiger Kommentar) und uebernimmt das Schliessen dann von hier.
(setq dbg-an (vfl-dbg-session-start "vfl-modus2" "vfl_modus2.dbg"
"vf-linienzug-modus2" "=== SESSION Modus 2 (3D-Objekte + Ziel-Hoehe) START ==="))
(vfl-dbg-hook-installieren)
(vfl-gf-abhaengigkeit-sicherstellen "vfl-m3-alert-gf-modul")
;; --- 1./4. Pfad-Objekte + Segment-Essenz: siehe Schritt 4 unten. ---
;; Auswahl UND Ableitung laufen zusammen in vfl-in-segmente (Live: ssget ->
;; gf-sortiere-objekte -> gf-analysiere-kette -> Eck-Vorpruefung -> SEGS;
;; Replay: fertige Essenz aus dem Journal). Persistiert die Segment-Essenz
;; statt der Objekt-Handles - der Block ist damit selbst-enthalten und der
;; Doppelklick-Neubau unabhaengig von den Original-LINE/ARC (behebt den
;; frueheren OBJS-Replay-Drift). Da vfl-in-segmente den startpunkt fuer die
;; Sortierung braucht, steht die Auswahl NACH der Startpunkt-Praeambel.
;; --- 2. Startpunkt + Z + AS-Seite ---
;; Startpunkt + Hoehe (gemeinsame Praeambel-Schale). start-hoehe wird spaeter
;; vom Solver gebraucht (z-front/dH-gesamt) - aus der Z-Koordinate ziehen.
(setq startpunkt (vfl-frage-punkt-hoehe "vfl-m3-prompt-startpunkt"
"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")))
(princ (ssg-text "vfl-as-setzen-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-m3-as-nein"))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-m3-as-nein")) 1
"vfl-as-setzen-frage"))
(setq as-vorhanden (/= antwort "2"))
;; Globals pro Lauf zuruecksetzen (siehe Kommentar in vf-linienzug-modus).
(setq *vfl-as-winkel* nil *vfl-es-winkel* nil)
(if as-vorhanden
(setq as-seite (vfl-frage-as-winkel-seite "gf-seite-aus-header"))
)
;; --- 3. Endpunkt + Z + ES-Seite (NEU in Modus 3) ---
;; Endpunkt + Zielhoehe (gemeinsame Praeambel-Schale). end-hoehe wird spaeter
;; vom Solver gebraucht (z-junction/dH-gesamt) - aus der Z-Koordinate ziehen.
(setq endpunkt (vfl-frage-punkt-hoehe "vfl-m3-prompt-endpunkt"
"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")))
(princ (ssg-text "vfl-es-setzen-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-es-nein-zielpunkt"))
(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"))
(setq es-vorhanden (/= antwort "2"))
(if es-vorhanden
(setq es-seite (vfl-frage-es-winkel-seite "gf-seite-ein-header"))
)
;; --- 4. Pfad waehlen + Sortieren + Analysieren + Eck-Vorpruefung ---
;; vfl-in-segmente kapselt Live + Replay und persistiert die Segment-Essenz
;; (SEGS) statt der Objekt-Handles - siehe Schritt 1 oben und Modus 3.
(princ (ssg-text "vfl-m3-pfad-waehlen"))
(setq segmente9 (vfl-in-segmente '((0 . "LINE,ARC")) startpunkt *vfk-eckwinkel-toleranz*))
(if (null segmente9) (progn (princ (ssg-text "vfl-m3-keine-objekte")) (exit)))
;; segmente = 5-Feld-Bausicht (typ hz laenge bwinkel bseite) aus der 9-Feld-
;; Essenz - die Klassifizierungs-Schleife unten arbeitet unveraendert damit.
(setq segmente (mapcar (function (lambda (s) (list (nth 0 s) (nth 1 s) (nth 2 s) (nth 3 s) (nth 4 s))))
segmente9))
;; --- 5. Klassifizierung (Phase A: nur speichern, nichts bauen) ---
;; Plan-Eintrag Gerade: ("Linie" hz laenge klass winkel) klass=GF/VF
;; Plan-Eintrag Bogen : ("Bogen" hz chord bwinkel bseite klass)
(setq n (length segmente) i 0 plan '())
(while (< i n)
(setq seg (nth i segmente) typ (car seg) hz (cadr seg) laenge (caddr seg))
;; seg-ent: KEINE Live-Entity mehr - Modus 2 baut jetzt aus der Segment-
;; Essenz (SEGS), nicht aus durchgereichten LINE/ARC (siehe Modus 3). Damit
;; entfaellt das kosmetische Pfad-Segment-Highlight (vfl-seg-highlight ist
;; nil-sicher). Bewusst: die Essenz macht Bau/Replay unabhaengig von den
;; Original-Objekten, ein Highlight haette beim Doppelklick-Neubau ohnehin
;; keine Entity mehr.
(setq seg-ent nil)
;; Wizard: alle Fragen dieses Segments in EINEM Dialog erfassen (die
;; unveraenderten vfl-menu/vfl-in-*-Aufrufe unten konsumieren die Antworten
;; aus *vflw-pending*). Der nach-kurve-Fall stellt keine Frage (auto-VF) ->
;; kein Dialog. Ebenso nach-vf bei einem Bogen: Vario-Kurve ist dann
;; erzwungen (eine VF-Einheit kann nicht in einen GF-Bogen auslaufen).
(if (and (vfl-wizard-aktiv)
(not (and (= typ "Linie") nach-kurve))
(not (and (= typ "Bogen") nach-vf)))
(progn
(vfl-seg-highlight seg-ent T)
(if (= typ "Linie")
(vflw-seg-linie-m2-impl
(vfl-seg-kopf (1+ i) n
(strcat "Gerade L=" (rtos laenge 2 0) " mm hz=" (rtos hz 2 1)))
*vfk-gefaelle-winkel*)
(vflw-seg-bogen-impl
(vfl-seg-kopf (1+ i) n
(strcat "Eck/Bogen " (itoa (nth 3 seg)) " Grad " (nth 4 seg)))
T)) ; Modus 2: Vario-Kurve immer erlaubt
(vfl-seg-highlight seg-ent nil)))
(cond
((= typ "Linie")
(princ (ssg-textf "vfl-m3-seg-gerade"
(list (itoa (1+ i)) (itoa n) (rtos laenge 2 0) (rtos hz 2 1))))
(cond
;; Direkt nach einer Vario-Kurve: automatisch VF (keine Abfrage) -
;; die Kurve sitzt mitten in der VF-Einheit, es MUSS VF folgen.
(nach-kurve
(princ (ssg-text "vfl-m3-auto-vf"))
(setq plan (cons (list "Linie" hz laenge "VF" nil) plan))
(setq nach-kurve nil nach-vf t))
(t
(princ (ssg-text "vfl-m3-typ-gerade"))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list "GF (Neigungswinkel)" "VF (Bruecke)") 1
"vfl-m3-typ-gerade"))
(if (= antwort "2")
(progn
(setq plan (cons (list "Linie" hz laenge "VF" nil) plan))
(setq nach-vf t))
(progn
(setq winkel (vfl-in-real (ssg-text "vfl-m3-prompt-gf-neigung")))
(if (null winkel) (setq winkel *vfk-gefaelle-winkel*))
(if (> winkel *vfk-gefaelle-winkel*)
(progn (princ (ssg-text "vfl-m3-hinweis-gf-max")) (setq winkel *vfk-gefaelle-winkel*)))
;; Separator VOR/NACH diesem GF-Stueck (analog Modus 1) - die
;; Planlaenge bleibt hier UNVERAENDERT (reale Pfadlaenge, fuer
;; die Anker-Vorschau noetig); die Trennung in GF-Koerper+
;; Separator passiert erst beim Bauen (Phase B).
(princ (ssg-text "vfl-sep-vor-gf-frage"))
(setq sep-vor-gf (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2
"vfl-sep-vor-gf-frage") "1"))
(princ (ssg-text "vfl-sep-nach-gf-frage"))
(setq sep-nach-gf (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2
"vfl-sep-nach-gf-frage") "1"))
(setq plan (cons (list "Linie" hz laenge "GF" winkel sep-vor-gf sep-nach-gf) plan))
(setq nach-vf nil))))))
((= typ "Bogen")
(setq bwinkel (nth 3 seg) bseite (nth 4 seg))
(princ (ssg-textf "vfl-m3-seg-bogen"
(list (itoa (1+ i)) (itoa n) (itoa bwinkel) bseite)))
(if nach-vf
(setq antwort "2") ; nach VF-Gerade: Vario-Kurve erzwungen, keine Typ-Frage
(progn
(princ (ssg-text "vfl-m3-typ-bogen"))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list "GF-Bogen" "Vario-Kurve") 1
"vfl-m3-typ-bogen"))))
(setq nach-vf nil)
(if (= antwort "2")
(progn
(princ (ssg-text "vfl-m3-variante-frage"))
(setq kv-variante (if (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-variante-aussen") (ssg-text "vfl-variante-innen")) 2
"vfl-m3-variante-frage")
"1") "aussen" "innen"))
(setq plan (cons (list "Bogen" hz laenge bwinkel bseite "Vario-Kurve" kv-variante) plan))
(setq nach-kurve t)) ; naechste Gerade automatisch VF
(progn
(setq plan (cons (list "Bogen" hz laenge bwinkel bseite "GF-Bogen" nil) plan))
(setq nach-kurve nil)))))
(setq i (1+ i)))
(setq plan (reverse plan))
;; --- 6. VF-Lauf finden: VF-Geraden UND Vario-Kurven bilden EINEN Lauf ---
;; (Vario-Kurve gehoert in die VF-Einheit, unterbricht den Lauf also NICHT).
(setq vf-start -1 vf-ende -1 vf-count 0 in-vf nil i 0)
(foreach e plan
;; NICHT "member" nennen: eine gleichnamige Lokale verdeckt in AutoLISP das
;; Builtin member, das weiter unten im selben Scope gebraucht wird.
(setq vf-run-member (or (and (= (car e) "Linie") (= (nth 3 e) "VF"))
(and (= (car e) "Bogen") (= (nth 5 e) "Vario-Kurve"))))
(if vf-run-member
(progn
(if (not in-vf) (setq vf-count (1+ vf-count) vf-start i in-vf t))
(setq vf-ende i))
(setq in-vf nil))
(setq i (1+ i)))
(if (/= vf-count 1)
(progn
;; Konsolen-Diagnose (fuer den Log / dbg-Nachvollzug) ...
(princ (ssg-textf "vfl-m3-vf-laeufe" (list (itoa vf-count))))
(princ (ssg-text "vfl-m3-vf-lauf-noetig"))
(princ (ssg-text "vfl-m3-vf-lauf-m3b"))
;; ... UND als sichtbare Meldung, damit die Abweisung nicht in der
;; Konsole untergeht (vfl-meldung zeigt bei erlaubter GUI ein alert,
;; headless nur princ - blockiert also einen Batch-Lauf nicht).
(vfl-meldung (ssg-textf "vfl-m3-vf-lauf-alert" (list (itoa vf-count))))
(exit)))
;; Traeger-Stueck = laengste VF-Gerade im Lauf (traegt die ganze Hoehe).
(setq carrier-idx -1 i vf-start)
(while (<= i vf-ende)
(setq seg (nth i plan))
(if (and (= (car seg) "Linie") (= (nth 3 seg) "VF"))
(if (or (< carrier-idx 0) (> (caddr seg) (caddr (nth carrier-idx plan))))
(setq carrier-idx i)))
(setq i (1+ i)))
(if (< carrier-idx 0)
(progn (princ (ssg-text "vfl-m3-vf-lauf-ohne-gerade")) (exit)))
;; --- 7. Anker rechnen ---
;; Vorwaerts: Start-Z durch alle Front-Segmente (Index < vf-start).
(setq z-front (float start-hoehe) i 0)
(while (< i vf-start)
(setq dz (vfl3-seg-dz (nth i plan)))
(if dz (setq z-front (+ z-front dz)))
(setq i (1+ i)))
;; Rueckwaerts: Ziel-Z durch alle Back-Segmente (Index > vf-ende).
;; Vorwaerts gilt Z_next = Z_prev + dz -> rueckwaerts Z_prev = Z_next - dz.
(setq z-junction (float end-hoehe) i (1- n))
(while (> i vf-ende)
(setq dz (vfl3-seg-dz (nth i plan)))
(if dz (setq z-junction (- z-junction dz)))
(setq i (1- i)))
;; Bruecken-Spannweite = Summe der VF-Geraden-Planlaengen im Lauf.
(setq span-bruecke 0.0 i vf-start)
(while (<= i vf-ende)
(setq seg (nth i plan))
(if (= (car seg) "Linie") (setq span-bruecke (+ span-bruecke (caddr seg))))
(setq i (1+ i)))
(setq dH-bruecke (- z-front z-junction))
(setq dH-gesamt (- (float start-hoehe) (float end-hoehe)))
;; --- 8. Anker-Report (noch kein Bauen) ---
(princ "\n\n=========================================")
(princ (ssg-text "vfl-m3-anker-vorschau-header"))
(princ "\n=========================================")
(princ (ssg-textf "vfl-m3-start-hoehe" (list (rtos (float start-hoehe) 2 1))))
(princ (ssg-textf "vfl-m3-ziel-hoehe" (list (rtos (float end-hoehe) 2 1))))
(princ (ssg-textf "vfl-m3-delta-h-gesamt" (list (rtos dH-gesamt 2 1))))
(princ (ssg-textf "vfl-m3-vf-lauf-segmente" (list (itoa (1+ vf-start)) (itoa (1+ vf-ende)))))
(princ (ssg-textf "vfl-m3-front-anker-vorschau" (list (rtos z-front 2 1))))
(princ (ssg-textf "vfl-m3-junction-anker-vorschau" (list (rtos z-junction 2 1))))
;; Vorzeichen = Richtung (VF ist angetrieben, kann steigen UND fallen).
(setq richtn (cond ((< dH-bruecke -0.1) (ssg-text "vfl-m3-richtung-auf"))
((> dH-bruecke 0.1) (ssg-text "vfl-m3-richtung-ab"))
(t (ssg-text "vfl-m3-richtung-horizontal"))))
(setq req-w (if (> span-bruecke 1.0)
(* (atan (/ (abs dH-bruecke) span-bruecke)) (/ 180.0 pi))
0.0))
(princ (ssg-textf "vfl-m3-bruecke-dh" (list (rtos dH-bruecke 2 1) (rtos span-bruecke 2 0))))
(princ (ssg-textf "vfl-m3-bruecke-richtung" (list richtn)))
(princ (ssg-text "vfl-m3-vf-angetrieben"))
(princ (ssg-textf "vfl-m3-mittlere-neigung" (list (rtos req-w 2 1))))
(if (> req-w *vfk-vario-winkel-max*)
(princ (ssg-text "vfl-m3-warnung-zu-steil"))
(princ (ssg-text "vfl-m3-vario-bereich-ok")))
(princ (ssg-text "vfl-m3-anker-hinweis"))
(princ "\n=========================================")
;; --- Lauf-Anfang waehlen, NUR wenn der VF-Lauf der Pfadanfang ist (kein
;; Front-Lauf davor) - sonst bleibt die Steuerung komplett unveraendert (AS-
;; Element aus der Upfront-Frage, Umlenkstation+GF1+Separator immer gebaut).
;; Muss VOR der Phase-B2-Fussabdruck-Berechnung (entry-fixed) feststehen.
(setq m2-offener-anfang nil m2-kein-gf1-vorne nil)
(if (= vf-start 0)
(progn
(princ (ssg-text "vfl-m3-lauf-anfang-header"))
(princ (ssg-text "vfl-m3-lauf-anfang-opt-offen"))
(princ (ssg-text "vfl-m3-lauf-anfang-opt-umlenk"))
(princ (ssg-text "vfl-m3-lauf-anfang-opt-umlenk-gf1"))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-m2-lauf-anfang-impl (list (ssg-text "vfl-m3-lauf-anfang-header"))))
(setq m2-front-antwort (vfl-menu (ssg-text "vfl-prompt-wahl-1-3-def3")
(list (ssg-text "vfl-m3-lauf-anfang-opt-offen")
(ssg-text "vfl-m3-lauf-anfang-opt-umlenk")
(ssg-text "vfl-m3-lauf-anfang-opt-umlenk-gf1")) 3
"vfl-m3-lauf-anfang-header"))
(cond
((= m2-front-antwort "1")
(setq as-vorhanden nil m2-offener-anfang t)
(princ (ssg-text "vfl-m3-lauf-anfang-offen-hinweis")))
((= m2-front-antwort "2") (setq m2-kein-gf1-vorne t)))))
;; --- Lauf-Abschluss waehlen, NUR wenn der VF-Lauf das Pfadende ist (kein
;; Back-Lauf danach) - sonst bleibt die Steuerung komplett unveraendert
;; (es-vorhanden aus der Upfront-Frage, Motorstation immer gebaut). "Ohne
;; Motorstation" muss VOR der Phase-B2-Zielhoehen-Berechnung feststehen,
;; da sie die Separator/ES-Naeherung in vfl3-dback wieder aufhebt.
(setq m2-offenes-ende nil)
(if (= vf-ende (1- n))
(progn
;; es-frueh-abgelehnt: die Upfront-Frage ("ES-Element setzen?", vor der
;; Segment-Klassifizierung) wurde bereits mit "Nein" beantwortet - dann
;; hier NICHT nochmal die volle 3-Optionen-Auswahl anbieten (verwirrend,
;; siehe Nutzer-Feedback: Option 3 widerspricht der schon getroffenen
;; Entscheidung). Console UND Wizard bekommen dieselbe reduzierte Liste.
(setq es-frueh-abgelehnt (not es-vorhanden))
(princ (ssg-text "vfl-m3-lauf-abschluss-header"))
(princ (ssg-text "vfl-m3-lauf-abschluss-opt-offen"))
(princ (ssg-text "vfl-m3-lauf-abschluss-opt-motor"))
(if (not es-frueh-abgelehnt) (princ (ssg-text "vfl-m3-lauf-abschluss-opt-motor-es")))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-m2-lauf-abschluss-impl
(list (ssg-text "vfl-m3-lauf-abschluss-header") es-frueh-abgelehnt)))
(setq m2-lauf-antwort (vfl-menu (ssg-text (if es-frueh-abgelehnt
"vfl-prompt-wahl-1-2-def2"
"vfl-prompt-wahl-1-3-def2"))
(if es-frueh-abgelehnt
(list (ssg-text "vfl-m3-lauf-abschluss-opt-offen")
(ssg-text "vfl-m3-lauf-abschluss-opt-motor"))
(list (ssg-text "vfl-m3-lauf-abschluss-opt-offen")
(ssg-text "vfl-m3-lauf-abschluss-opt-motor")
(ssg-text "vfl-m3-lauf-abschluss-opt-motor-es"))) 2
"vfl-m3-lauf-abschluss-header"))
(cond
((= m2-lauf-antwort "1")
(setq es-vorhanden nil m2-offenes-ende t)
(princ (ssg-text "vfl-m3-lauf-abschluss-offen-hinweis")))
((and (= m2-lauf-antwort "3") (not es-frueh-abgelehnt))
(setq es-vorhanden t)
(if (null es-seite) (setq es-seite (vfl-frage-es-seite nil))))
(t (setq es-vorhanden nil)))))
;; ================= PHASE B: BAUEN (messen statt schaetzen) =================
;; VF-Lauf darf mehrsegmentig sein (VF-Geraden + Vario-Kurven, EINE VF-Einheit).
;; Nummerierung + Akkumulatoren + AS/ES-Fussabdruecke. Ohne AS-Element (aus-
;; vorhanden=nil) faellt der Fussabdruck weg - die Kette beginnt direkt am
;; Startpunkt, also 0 statt Block-/Fallback-Mass.
(setq aus (if as-vorhanden (if aus-dx aus-dx *vfk-as-es-fallback-dx*) 0.0)
ein (if es-vorhanden (if ein-dx ein-dx *vfk-as-es-fallback-dx*) 0.0))
(setq vfl-nummer (vf-next-number))
(setq lastEnt (vf-lastent-ohne-attribute))
(vfl-acc-reset)
;; *vfl-journal*-Reset steht jetzt GANZ AM ANFANG der Funktion (vor der
;; Pfadauswahl/Praeambel) - siehe Kommentar dort. Hier NICHT mehr noetig
;; (und schaedlich: wuerde die bereits aufgezeichnete Praeambel dieses
;; Laufs wieder loeschen).
(setq anzahl-gf 0 anzahl-vf 0 frame nil)
;; --- Abbruch-Sicherung ab hier scharf (analog Modus 1, siehe dortiger
;; Kommentar) - Phase A (oben) baut noch keine Geometrie, ein Abbruch
;; waehrend der Klassifizierungsfragen haette also ohnehin nichts zu
;; wickeln. Ab Phase B kann jederzeit Geometrie entstehen.
;; *error* ist hier bereits der fruehe dbg-Hook vom Funktionsanfang - wir
;; loesen ihn komplett ab (Ruecksprungziel ist dbg-old-error, NICHT der
;; dbg-Hook selbst) und uebernehmen das dbgclose gleich mit, sonst wuerde
;; bei einem Abbruch ab hier doppelt geschlossen.
(setq old-error dbg-old-error)
;; Scharfen Abbruch-Wickel-Handler installieren (loest den fruehen dbg-Hook ab,
;; Ruecksprungziel bleibt dbg-old-error via old-error). Gemeinsam fuer alle
;; Modi - siehe vfl-abbruch-handler-installieren.
(vfl-abbruch-handler-installieren "linienzug2")
;; erste/letzte Gerade fuer AS/ES-Trimmung bestimmen
(setq first-line -1 last-line -1 i 0)
(while (< i n)
(if (= (car (nth i plan)) "Linie")
(progn (if (< first-line 0) (setq first-line i)) (setq last-line i)))
(setq i (1+ i)))
;; --- B1: FRONT-Lauf bauen (Segmente 0 .. vf-start-1) -> Front-Anker MESSEN ---
(princ (ssg-text "vfl-m3-phase-b1"))
(setq i 0)
(while (< i vf-start)
(setq seg (nth i plan) typ (car seg) hz (cadr seg))
(cond
((= typ "Linie")
(setq winkel (nth 4 seg) sep-vor-gf (nth 5 seg) sep-nach-gf (nth 6 seg))
;; AS zuerst platzieren (falls gewuenscht), damit der echte KS_AUS bekannt
;; ist. Ohne AS-Element beginnt die Kette direkt am Startpunkt (flach) -
;; die nachfolgende Fussabdruck-Trimmung wird dann automatisch zu 0, da
;; (car frame) bereits gleich startpunkt ist.
(if (null frame)
(setq frame (if as-vorhanden
(vfl2-insert-as-gf startpunkt hz winkel as-seite)
(make-frame-from-dir startpunkt (hz-winkel->xu hz 0.0)))))
(setq deltaL (caddr seg))
;; erste Gerade: AS-Fussabdruck ENTLANG des Pfads exakt aus KS_AUS abziehen
;; (das Block-Maß aus-dx stimmt nach dem Achs-Versatz nicht mehr).
(if (= i first-line)
(setq deltaL (- deltaL
(+ (* (- (car (car frame)) (car startpunkt)) (cos (* hz (/ pi 180.0))))
(* (- (cadr (car frame)) (cadr startpunkt)) (sin (* hz (/ pi 180.0))))))))
;; Separator(en) VOR/NACH aus der Klassifizierung (Punkt 1): die reale
;; Planlaenge (deltaL, oben aus dem gemessenen Pfad) bleibt fuer die
;; Anker-Vorschau unangetastet - erst HIER, beim tatsaechlichen Bauen,
;; wird der GF-Koerper um die Separator-Laenge(n) verkuerzt.
(if sep-vor-gf (setq deltaL (- deltaL *vfk-separator-laenge*)))
(if sep-nach-gf (setq deltaL (- deltaL *vfk-separator-laenge*)))
(setq deltaL (max *vfk-restlaenge-min-clamp* deltaL))
(if sep-vor-gf
(progn
(setq frame (make-frame-from-dir
(gf-insert-hz-with-ks "Staustrecke_Separator_SP_300_mm"
(car frame) hz winkel *vfk-separator-laenge* 0)
(hz-winkel->xu hz winkel)))
(ssg-sep-typ-xdata-schreiben (entlast) "GF")
(setq *vfl-acc-separator* (1+ *vfl-acc-separator*))))
(setq frame (vfl-insert-gf-segment (car frame) hz deltaL winkel))
(vfl-acc-gf-seg (/ deltaL (cos (* winkel (/ pi 180.0)))) winkel)
(setq anzahl-gf (1+ anzahl-gf) letzt-winkel winkel)
(if sep-nach-gf
(progn
(setq frame (make-frame-from-dir
(gf-insert-hz-with-ks "Staustrecke_Separator_SP_300_mm"
(car frame) hz winkel *vfk-separator-laenge* 0)
(hz-winkel->xu hz winkel)))
(ssg-sep-typ-xdata-schreiben (entlast) "GF")
(setq *vfl-acc-separator* (1+ *vfl-acc-separator*)))))
((= typ "Bogen")
(setq bwinkel (nth 3 seg) bseite (nth 4 seg) klass (nth 5 seg))
(if (= klass "GF-Bogen")
(progn
(if (null frame)
(progn (vfl-meldung (ssg-text "vfl-m3-alert-beginnt-bogen")) (exit)))
(setq frame (vfl-insert-gf-bogen-block frame bwinkel bseite)))
(princ (ssg-text "vfl-m3-kurve-front-uebersprungen")))))
(setq i (1+ i)))
;; Front-Anker (gemessen). Falls die VF-Bruecke ganz vorne liegt: AS (falls
;; gewuenscht) jetzt setzen.
(setq hz (cadr (nth vf-start plan)))
(if (null frame)
(setq frame (if as-vorhanden
(vfl2-insert-as-vf startpunkt hz as-seite)
(make-frame-from-dir startpunkt (hz-winkel->xu hz 0.0)))))
(setq z-front (caddr (car frame)))
;; --- B2: Back-Abstieg -> Junction; Kletterer + Horizontal-Mitte bestimmen ---
(setq z-junction (+ (float end-hoehe)
(vfl3-dback plan (1+ vf-ende) n last-line ein)))
;; Offenes Ende (kein Motor/Separator/ES danach) ODER Motor+GF2 OHNE ES
;; danach (Lauf-Abschluss "Motorstation", es-vorhanden nil - Motor selbst
;; ist bereits ueber dH-adj/*vfk-bruecke-korrektur* separat eingerechnet,
;; siehe Phase B weiter unten): vfl3-dback's Tail-Naeherung (reserviert
;; IMMER einen Separator+ES-Fussabdruck) ist in BEIDEN Faellen falsch - die
;; Zielhoehe fuer den Loeser ist exakt end-hoehe. Realer Bug, bis dahin nur
;; fuer m2-offenes-ende behandelt: "Motorstation ohne ES" fiel durch dieselbe
;; Luecke und bekam einen unkorrigierten Hoehenfehler in Groesse des
;; faelschlich angenommenen Separator+ES-Abstiegs.
(if (or m2-offenes-ende (not es-vorhanden)) (setq z-junction (float end-hoehe)))
(setq dH-bruecke (- z-front z-junction))
;; Modell: EIN Vario mit Horizontal-Mitte. Nur VF-Geraden, die LANG GENUG sind
;; (Vertikalboegen brauchen viel Platz), tragen die Hoehe (Kletterer, gleicher
;; Winkel). Zu kurze VF-Geraden werden horizontal (nur Anschluss/Motor).
;; Mindestens die laengste VF-Gerade klettert immer.
(setq climb-thresh (if (boundp '*vfl-min-climber-laenge*) *vfl-min-climber-laenge* 3000.0))
(setq longest-idx -1 i vf-start)
(while (<= i vf-ende)
(setq seg (nth i plan))
(if (and (= (car seg) "Linie") (= (nth 3 seg) "VF"))
(if (or (< longest-idx 0) (> (caddr seg) (caddr (nth longest-idx plan))))
(setq longest-idx i)))
(setq i (1+ i)))
(setq climbers '() climber-span 0.0 nonclimber-len 0.0 nonclimber-cnt 0
nkurve 0 kurve-chords 0.0 i vf-start)
(while (<= i vf-ende)
(setq seg (nth i plan))
(cond
((and (= (car seg) "Linie") (= (nth 3 seg) "VF"))
(if (or (= i longest-idx) (>= (caddr seg) climb-thresh))
(setq climbers (cons i climbers) climber-span (+ climber-span (caddr seg)))
(setq nonclimber-len (+ nonclimber-len (caddr seg)) nonclimber-cnt (1+ nonclimber-cnt))))
((= (car seg) "Bogen")
(setq nkurve (1+ nkurve) kurve-chords (+ kurve-chords (caddr seg)))))
(setq i (1+ i)))
(setq climbers (reverse climbers) n-climb (length climbers))
;; Flache Zone (Kurve + horizontale Fueller) hat GENAU EIN Uebergangspaar
;; (ein auf_3 rein, ein ab_3 raus), unabhaengig von der Zahl der Fueller/Kurven.
(setq hor-pairs (if (or (> nkurve 0) (> nonclimber-cnt 0)) 1 0))
(setq run-span (+ climber-span nonclimber-len kurve-chords))
;; Laengen-Obergrenze fuer eine angetriebene VF-Einheit (Konstruktions-
;; grenze, siehe *vfk-vf-laenge-max*) - nur Hinweis, kein Abbruch.
(if (> run-span *vfk-vf-laenge-max*)
(vfl-meldung (ssg-textf "vfl-m3-alert-vf-zu-lang"
(list (rtos (/ run-span 1000.0) 2 1) (rtos (/ *vfk-vf-laenge-max* 1000.0) 2 1)))))
(setq br-gf1 *vfl-gf-min-laenge*) ; feste kleine GF1
(setq br-richtn (if (< dH-bruecke 0) "Auf" "Ab"))
(setq target-climb (- z-junction z-front)) ; noetige Netto-Hoehe (Auf>0)
;; Kletter-Segment als Standard-Vario loesen; GF wird BERECHNET (L_GF) und der
;; Winkel WAEHLBAR (mehrere gueltige -> Nutzer waehlt). feste = 800 (Umlenk+Sep;
;; Motor sitzt am Kettenende). Die flache Zone gleicht danach die Laenge aus.
(princ (ssg-text "vfl-m3-standard-vario-header"))
(princ (ssg-textf "vfl-m3-front-anker-gemessen" (list (rtos z-front 2 1))))
(princ (ssg-textf "vfl-m3-junction-exakt" (list (rtos z-junction 2 1))))
(princ (ssg-textf "vfl-m3-kletter-info"
(list (rtos target-climb 2 1) (itoa n-climb) (itoa nkurve) (itoa nonclimber-cnt))))
;; feste-Horizontal + Hoehen-Anpassung fuer den Solver:
;; - Mit flacher Zone: Motor sitzt am Kettenende (nicht im Kletter-Segment) ->
;; feste = entry-fixed (Umlenk+Sep, ODER weniger bei Lauf-Anfang-Wahl).
;; berechne rechnet Motor-/Uebergangs-Abstieg NICHT, daher Kletterhoehe
;; um diese Abstiege anpassen (GF2 bleibt ~0) - ausser bei offenem Ende.
;; - Ohne flache Zone (Einzel-Bruecke): feste = entry-fixed + Motor (0 bei
;; offenem Ende), keine Hoehen-Anpassung.
(setq rad3v (* *vfk-gefaelle-winkel* (/ pi 180.0)))
;; Eingangs-Fussabdruck haengt von der Lauf-Anfang-Wahl ab (siehe oben) -
;; Default (beide Flags nil) ergibt exakt die bisherigen 800/1300.
(setq entry-fixed
(cond (m2-offener-anfang 0.0)
(m2-kein-gf1-vorne *vfk-stations-laenge*)
(t (+ *vfk-stations-laenge* *vfk-separator-laenge*))))
(setq es-along-fix 0.0)
(if (> hor-pairs 0)
(setq feste-vf entry-fixed
dH-adj (- dH-bruecke (+ (* *vfk-stations-laenge* (sin rad3v)) *vfk-bruecke-korrektur*))) ; Motor + auf_3/ab_3
(progn
;; OHNE flache Zone gibt es KEIN vfl3-es-fueller, das den Separator+ES-
;; Laengsanteil selbststaendig gegen den echten Zielpunkt korrigiert -
;; beide fehlten hier bislang komplett im Laengenbudget des Solvers
;; (realer Bug: die Kette ueberschoss um ungefaehr Separator+along-es,
;; am ES-Turn als Querabweichung zur KS_AUS-Achse sichtbar geworden).
;; WICHTIG: NUR die Laenge korrigieren, NICHT ueber feste-vf/dH-adj -
;; feste-vf fliesst in berechne-alle-winkel per sin(3°) AUCH in die
;; Hoehenbilanz ein, waehrend die reale Hoehe von Separator+ES bereits
;; VOLLSTAENDIG ueber z-junction/vfl3-dback erfasst ist (Separator dort
;; per tan(3°), ES-Eigenhoehe per ein-dz - siehe dortiger Kommentar).
;; Ueber feste-vf haette das die Hoehe ein zweites Mal (und fuer
;; along-es sogar physikalisch falsch, da along-es keinen sin(3°)-
;; Hoehenanteil hat) veraendert - realer Folgefehler beim ersten
;; Test dieses Fixes (dZ=38.6mm, vorher exakt 0).
(setq feste-vf (+ entry-fixed (if m2-offenes-ende 0.0 *vfk-stations-laenge*))
dH-adj dH-bruecke)
(if es-vorhanden
(progn
(setq es-vek-fix (vfl3-es-vektor
(strcat "ES_Element_" (vfl-es-winkel) "_" es-seite)
(cadr (nth vf-ende plan))))
(setq es-along-fix
(+ (* *vfk-separator-laenge* (cos rad3v)) (if es-vek-fix (car es-vek-fix) 0.0)))))))
;; Offenes Ende (m2-offenes-ende): kein Motor wird gebaut, die obige
;; Motor-Abstiegs-Korrektur darf dann NICHT abgezogen werden - der Solver
;; soll die volle dH-bruecke exakt treffen (Ziel = z-junction = end-hoehe).
(if m2-offenes-ende (setq dH-adj dH-bruecke))
;; es-along-fix (reine Laenge, siehe oben) NUR aus dem Laengenbudget (span)
;; abziehen - NICHT aus feste-vf, sonst floss es faelschlich ueber sin(3°)
;; auch in die Hoehenbilanz ein (siehe Kommentar oben).
(setq wahl3 (vfl3-waehle-winkel (- climber-span es-along-fix) dH-adj feste-vf))
(if (null wahl3)
(progn
;; Stufe 2: KEIN Tabellenwinkel passt (der/die Kletterer sind bei JEDER
;; Neigung zu kurz fuer Umlenkstation+2 Vertikalboegen) - ganzer Lauf wird
;; flach gebaut, wie die bereits vorhandenen "kurzen" VF-Geraden. Die
;; komplette Hoehe wandert in GF1 (reine Kompensation, darf legitim
;; (nahe) 0 sein).
(princ (ssg-text "vfl-m3-kletter-zu-kurz-flach"))
(setq nonclimber-len (+ nonclimber-len climber-span)
nonclimber-cnt (+ nonclimber-cnt n-climb))
(setq climbers '() n-climb 0 climber-span 0.0)
;; hor-pairs war ggf. 0 (reine Einzel-Bruecke ohne Kurve/Fueller) - jetzt
;; gibt es garantiert mindestens einen Fueller (den ex-Kletterer), also
;; feste-vf/dH-adj auf die flache-Zone-Variante nachziehen.
(if (<= hor-pairs 0)
(progn
(setq hor-pairs 1)
(setq feste-vf entry-fixed
dH-adj (- dH-bruecke (+ (* *vfk-stations-laenge* (sin rad3v)) *vfk-bruecke-korrektur*)))))
(if m2-offenes-ende (setq dH-adj dH-bruecke))
(setq br-winkel (fix *vfk-gefaelle-winkel*) br-lvf 0.0
br-richtn (if (< dH-adj 0) "Auf" "Ab")
gf-total (/ (abs dH-adj) (/ (sin rad3v) (cos rad3v))))
(princ (ssg-textf "vfl-m3-kletter-flach-gf" (list (rtos gf-total 2 0)))))
(setq br-winkel (car wahl3) gf-total (cadr wahl3) br-lvf (caddr wahl3) br-richtn (cadddr wahl3)))
;; GF-Verteilung: 1 = alles am Einlauf (GF1); 2 = 1/2 GF1 + 1/2 GF2; 3 = alles
;; am Auslauf (GF2) - erzwungen (kein GF1-Platz), wenn der Lauf-Anfang ohne
;; GF1 gewaehlt wurde (offener Anfang ODER Umlenkstation direkt). Bei 1/2/1/2
;; wird der im Kletter-Segment durch das halbe GF1 frei werdende Platz mit
;; einem horizontalen Fueller-A gefuellt; GF2 (~1/2 L_GF) sitzt hinter dem
;; Motor, der ES-Laengen-Abschluss zieht seinen Fussabdruck ab (Fueller-B
;; wird kuerzer).
(princ (ssg-text "vfl-m3-gf-verteilung"))
(if (or m2-offener-anfang m2-kein-gf1-vorne)
(progn
(setq br-gf-mode 3 br-gf1 0.0 br-gf2-exp gf-total filler-A-len 0.0)
(princ (ssg-text "vfl-m3-gf-verteilung-alles-auslauf")))
(progn
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-gf-verteilung-impl (list (ssg-text "vfl-m3-gf-verteilung") "2" "1")))
(setq br-gf-mode
(if (= (vfl-menu (ssg-text "prompt-wahl-1-2")
(list "Alles am Einlauf (GF1)" "1/2 GF1 + 1/2 GF2") 1
"vfl-m3-gf-verteilung") "2") 2 1))
(if (= br-gf-mode 2)
(setq br-gf1 (/ gf-total 2.0) br-gf2-exp (/ gf-total 2.0)
filler-A-len (* (/ gf-total 2.0) (cos rad3v)))
(setq br-gf1 gf-total br-gf2-exp 0.0 filler-A-len 0.0))))
(setq filler-a-done nil)
(princ (ssg-textf "vfl-m3-vario-ergebnis"
(list (itoa br-winkel)
(if (= br-richtn "Auf") (ssg-text "vfl-m3-richtung-auf-kurz")
(ssg-text "vfl-m3-richtung-ab-kurz"))
(rtos br-lvf 2 0)
(rtos gf-total 2 0)
(cond ((= br-gf-mode 2) (ssg-text "vfl-m3-vert-halb"))
((= br-gf-mode 3) (ssg-text "vfl-m3-vert-auslauf"))
(t (ssg-text "vfl-m3-vert-ganz"))))))
;; --- B3: Standard-Vario (Klettern, 3-Grad-Basis) + flache Zone (0 Grad) ---
;; Die flache Zone (Vario-Kurve + horizontaler Fueller) haengt EINMAL ueber auf_3
;; ein und EINMAL ueber ab_3 aus; Kurve und Fueller sind bei 0 Grad DIREKT
;; verbunden (keine Zwischen-Boegen). Der horizontale Fueller gleicht die
;; Restlaenge aus (letztes Stueck vor dem Motor getrimmt).
(setq pt (car frame) letzt-koerper-hz (cadr (nth vf-start plan)) flat-p nil)
;; Lauf-Anfang: offener Anfang ueberspringt Umlenkstation komplett; sonst
;; GF1 (0, wenn m2-kein-gf1-vorne oder GF-Verteilung das so ergab) + ggf.
;; Separator (uebersprungen bei m2-kein-gf1-vorne) + Umlenk (3 Grad).
(if (not m2-offener-anfang)
(progn
(setq pt (vfs-vf-entry pt br-gf1 letzt-koerper-hz m2-kein-gf1-vorne))
(vfl-acc-gf-seg br-gf1 3)))
(setq i vf-start)
(while (<= i vf-ende)
(setq seg (nth i plan) seg-hz (cadr seg))
(cond
;; --- Kletterer (geneigt, 3-Grad-Basis) ---
((and (= (car seg) "Linie") (member i climbers))
(if flat-p (progn (setq pt (vfl3-flach-aus pt seg-hz)) (setq flat-p nil)))
(setq this-lvf (max *vfk-restlaenge-min-clamp* (* br-lvf (/ (caddr seg) climber-span))))
(setq pt (vfs-vf-koerper pt br-richtn br-winkel this-lvf seg-hz))
(vfl-acc-vf-seg br-richtn br-winkel this-lvf)
(setq anzahl-vf (1+ anzahl-vf) letzt-koerper-hz seg-hz))
;; --- kurze VF-Gerade -> horizontaler Fueller (0 Grad) ---
((= (car seg) "Linie")
(setq flat-zone-erste (not flat-p))
(if (not flat-p)
(progn (setq pt (vfl3-flach-ein pt letzt-koerper-hz)) (setq flat-p t)
;; Fueller-A: der im Kletter-Segment durch 1/2 GF1 frei werdende Platz
(if (and (> filler-A-len 0.1) (not filler-a-done))
(progn (setq pt (insert-inclined-scaled-block "Staustrecke_SP_1000_mm"
pt filler-A-len 0 letzt-koerper-hz))
(vfl-acc-vf-seg "horizontal" 0 filler-A-len)
(setq anzahl-vf (1+ anzahl-vf) filler-a-done t)))))
(if (= i vf-ende)
;; letzter Fueller, MIT ES-Element (unveraendert): Laenge so, dass der
;; ENDPUNKT auf der ES-KS_AUS-ACHSE liegt (Spiegel der AS-Regel). Der
;; Schwanz (Fueller + ab_3 + Motor + GF2 + Separator + ES) verschiebt
;; sich starr entlang seg-hz mit dem Fueller; KS_AUS = pt + (fill + C)*
;; dp + perp-es*np, Achse u_a = seg-hz + ES-Turn. Aus (Endpunkt -
;; KS_AUS)*n_a = 0 folgt fill (n_a senkrecht zu u_a). OHNE ES-Element
;; (Nutzerwunsch): keine Achsen-Korrektur noetig, da nach Motor+GF2
;; nichts mehr gebaut wird - Fueller zielt direkt auf den Endpunkt.
(setq fill-len
(if es-vorhanden
(vfl3-es-fueller pt endpunkt seg-hz
(strcat "ES_Element_" (vfl-es-winkel) "_" es-seite)
br-gf2-exp rad3v)
(- (+ (* (- (car endpunkt) (car pt)) (cos (* seg-hz (/ pi 180.0))))
(* (- (cadr endpunkt) (cadr pt)) (sin (* seg-hz (/ pi 180.0)))))
(* (+ *vfk-stations-laenge* br-gf2-exp) (cos rad3v)))
)
)
(setq fill-len (caddr seg)))
;; Kritische Fueller-Position (direkt nach Umlenkstation ODER direkt vor
;; Motorstation) zu kurz fuer den geforderten Wert - statt still zu
;; klemmen, den Nutzer warnen (Zielhoehe/-position wird dann nicht exakt
;; erreicht, siehe vfl-m3-warnung-gf2-negativ fuer die Hoehen-Seite).
(if (and (< fill-len *vfk-restlaenge-min-clamp*) (or flat-zone-erste (= i vf-ende)))
(vfl-meldung (ssg-textf
(if flat-zone-erste "vfl-m3-warnung-fueller-eingang-zu-kurz"
"vfl-m3-warnung-fueller-ausgang-zu-kurz")
(list (rtos fill-len 2 0) (rtos *vfk-restlaenge-min-clamp* 2 0)))))
(setq fill-len (max *vfk-restlaenge-min-clamp* fill-len))
(setq pt (insert-inclined-scaled-block "Staustrecke_SP_1000_mm" pt fill-len 0 seg-hz))
(vfl-acc-vf-seg "horizontal" 0 fill-len) (setq anzahl-vf (1+ anzahl-vf))
(setq letzt-koerper-hz seg-hz))
;; --- Vario-Kurve (flach, direkt bei 0 Grad) ---
((= (car seg) "Bogen")
(if (not flat-p)
(progn (setq pt (vfl3-flach-ein pt letzt-koerper-hz)) (setq flat-p t)
;; Fueller-A: der im Kletter-Segment durch 1/2 GF1 frei werdende Platz
(if (and (> filler-A-len 0.1) (not filler-a-done))
(progn (setq pt (insert-inclined-scaled-block "Staustrecke_SP_1000_mm"
pt filler-A-len 0 letzt-koerper-hz))
(vfl-acc-vf-seg "horizontal" 0 filler-A-len)
(setq anzahl-vf (1+ anzahl-vf) filler-a-done t)))))
(setq bwinkel (nth 3 seg) bseite (nth 4 seg) kv-variante (nth 6 seg))
(setq frame (make-frame-from-dir pt (hz-winkel->xu letzt-koerper-hz 0.0)))
(setq frame (vfl-insert-vario-kurve-block frame bwinkel bseite
(if kv-variante kv-variante "innen")))
(setq pt (car frame))
(if (< i vf-ende) (setq letzt-koerper-hz (cadr (nth (1+ i) plan))))))
(setq i (1+ i)))
(if m2-offenes-ende
;; Bewusst offenes Ende (kein Motor/Separator/ES) - Lauf bleibt flach
;; (0 Grad) genau dort stehen, wo die Bau-Schleife endete; KEIN
;; Rueck-Uebergang auf 3 Grad, KEINE Motorstation/GF2.
(setq frame (make-frame-from-dir pt (hz-winkel->xu letzt-koerper-hz 0.0)))
(progn
(if flat-p (progn (setq pt (vfl3-flach-aus pt letzt-koerper-hz)) (setq flat-p nil))) ; zurueck 3 Grad
;; Motorstation + GF2 in EINEM Schritt mit dem im Solve (vfl3-waehle-
;; winkel/GF-Verteilung, siehe br-gf2-exp oben) bereits geplanten Wert -
;; NICHT mehr unabhaengig aus der gemessenen Resthoehe neu herleiten.
;; Die alte Zwei-Loesungen-Variante (Motor mit L_GF2=0.0, GF2 danach aus
;; z-aftermotor/z-junction gemessen) widersprach der Fueller-Position
;; (fill-len, vfl3-es-fueller), die bereits br-gf2-exp als GF2-Laenge
;; angenommen hatte - jede Differenz zwischen den beiden GF2-Werten
;; verschob das Kettenende entlang der Fahrtrichtung (realer Bug:
;; "letztes Segment baut zu lang"). Identische Fehlerklasse wie der
;; bereits behobene Modus-1-Bug (vfl-body-abschluss, siehe Memory
;; project_vf_einheit_ist_endpunkt_reihenfolge).
;; Bekannte Restunsicherheit: *vfk-bruecke-korrektur* (vf_konstanten.lsp,
;; empirischer Fixwert fuer Motor+auf_3/ab_3) bestimmt indirekt die
;; Genauigkeit von br-gf2-exp - sollte trotzdem noch ein kleiner
;; Hoehen-Restfehler auftreten (nicht das urspruengliche Laengs-
;; Ueberschiessen), ist dieser Fixwert der erste Verdaechtige.
(setq pt (vfs-vf-exit pt br-gf2-exp letzt-koerper-hz nil))
(setq *vfl-acc-motorseite* (append *vfl-acc-motorseite* (list "rechts")))
(if (> br-gf2-exp 0.1)
(vfl-acc-gf-seg (/ br-gf2-exp (cos (* *vfk-gefaelle-winkel* (/ pi 180.0)))) (fix *vfk-gefaelle-winkel*)))
(setq frame (vfl-frame-3grad pt letzt-koerper-hz))))
(setq hz letzt-koerper-hz)
;; --- B4: BACK-Lauf bauen (Segmente vf-ende+1 .. n-1) ---
(setq i (1+ vf-ende))
(while (< i n)
(setq seg (nth i plan) typ (car seg) hz (cadr seg))
(cond
((= typ "Linie")
(setq winkel (nth 4 seg) deltaL (caddr seg)
sep-vor-gf (nth 5 seg) sep-nach-gf (nth 6 seg))
;; Footprint fuer Separator(300)+ES nur reservieren, wenn ES-Element
;; gewuenscht ist (ein=0 sonst, siehe oben) - ohne ES entfaellt auch
;; der abschliessende Separator.
(if (= i last-line) (setq deltaL (- deltaL (if es-vorhanden *vfk-separator-laenge* 0.0) ein)))
;; Separator(en) VOR/NACH aus der Klassifizierung (Punkt 1, wie B1).
(if sep-vor-gf (setq deltaL (- deltaL *vfk-separator-laenge*)))
(if sep-nach-gf (setq deltaL (- deltaL *vfk-separator-laenge*)))
(setq deltaL (max *vfk-restlaenge-min-clamp* deltaL))
(if sep-vor-gf
(progn
(setq frame (make-frame-from-dir
(gf-insert-hz-with-ks "Staustrecke_Separator_SP_300_mm"
(car frame) hz winkel *vfk-separator-laenge* 0)
(hz-winkel->xu hz winkel)))
(ssg-sep-typ-xdata-schreiben (entlast) "GF")
(setq *vfl-acc-separator* (1+ *vfl-acc-separator*))))
(setq frame (vfl-insert-gf-segment (car frame) hz deltaL winkel))
(vfl-acc-gf-seg (/ deltaL (cos (* winkel (/ pi 180.0)))) winkel)
(setq anzahl-gf (1+ anzahl-gf) letzt-winkel winkel)
(if sep-nach-gf
(progn
(setq frame (make-frame-from-dir
(gf-insert-hz-with-ks "Staustrecke_Separator_SP_300_mm"
(car frame) hz winkel *vfk-separator-laenge* 0)
(hz-winkel->xu hz winkel)))
(ssg-sep-typ-xdata-schreiben (entlast) "GF")
(setq *vfl-acc-separator* (1+ *vfl-acc-separator*)))))
((= typ "Bogen")
(setq bwinkel (nth 3 seg) bseite (nth 4 seg) klass (nth 5 seg))
(if (= klass "GF-Bogen")
(setq frame (vfl-insert-gf-bogen-block frame bwinkel bseite))
(princ (ssg-text "vfl-m3-kurve-back-uebersprungen")))))
(setq i (1+ i)))
;; --- B5: Kettenende Separator + ES (nur falls gewuenscht) ---
(if (null frame) (progn (princ (ssg-text "vfl-m3-nichts-gebaut")) (exit)))
(if es-vorhanden
(setq frame (vfl-insert-es-element "GF" frame (car (frame->hz-winkel frame))
(if letzt-winkel letzt-winkel *vfk-gefaelle-winkel*) (caddr (car frame)) es-seite)))
;; ---------- Block + Ist-Ziel-Report ----------
(setq hoehe-bis (caddr (car frame)))
(setq vfl-ins
(vfl-block-erstellen vfl-nummer anzahl-gf anzahl-vf (caddr startpunkt) hoehe-bis
(vfl-planar-dist startpunkt (car frame)) as-seite es-seite startpunkt lastEnt))
;; Eingabe-Journal am fertigen Block persistieren (Marker "linienzug2") ->
;; spaeter per Doppelklick editierbar (vfl-edit-ent2: voller Reset, kein
;; Sektions-Dialog wie bei Modus 1 - siehe dortiger Kommentar).
(if vfl-ins (vfl-journal-xdata-schreiben vfl-ins "linienzug2"))
;; Erfolgreicher Abschluss: *error* zuruecksetzen.
(setq *error* old-error)
(setq soll-ende endpunkt ist-ende (car frame))
(princ "\n\n=========================================")
(princ (ssg-text "vfl-m3-fertig-header"))
(princ (ssg-textf "vfl-m3-ziel-soll"
(list (rtos (car soll-ende) 2 1) (rtos (cadr soll-ende) 2 1) (rtos (caddr soll-ende) 2 1))))
(princ (ssg-textf "vfl-m3-es-ist"
(list (rtos (car ist-ende) 2 1) (rtos (cadr ist-ende) 2 1) (rtos (caddr ist-ende) 2 1))))
(princ (ssg-textf "vfl-m3-abweichung"
(list (rtos (- (car ist-ende) (car soll-ende)) 2 1)
(rtos (- (cadr ist-ende) (cadr soll-ende)) 2 1)
(rtos (- (caddr ist-ende) (caddr soll-ende)) 2 1))))
;; Zerlegung bezogen auf die ES-KS_AUS-ACHSE: Quer (senkrecht zur Achse) soll
;; ~0 sein (Endpunkt liegt auf der KS_AUS-Achse), Laengs = ES-Ausladung.
(setq end-hz (car (frame->hz-winkel frame))) ; KS_AUS-Achsrichtung
(setq dir-x (cos (* end-hz (/ pi 180.0))) dir-y (sin (* end-hz (/ pi 180.0))))
(princ (ssg-textf "vfl-m3-laengs-quer"
(list (rtos (+ (* (- (car ist-ende) (car soll-ende)) dir-x)
(* (- (cadr ist-ende) (cadr soll-ende)) dir-y)) 2 1)
(rtos (+ (* (- (car ist-ende) (car soll-ende)) (- dir-y))
(* (- (cadr ist-ende) (cadr soll-ende)) dir-x)) 2 1))))
(princ (ssg-text "vfl-m3-achse-hinweis"))
(princ "\n=========================================")
(if dbg-an
(progn
(dbgmsg (strcat "ERGEBNIS: VF_" (itoa vfl-nummer)
" (GF=" (itoa anzahl-gf) " VF=" (itoa anzahl-vf) ")"))
(dbg 'soll-ende)
(dbg 'ist-ende)
(dbgmsg "=== SESSION Modus 2 ENDE ===")
(dbgreturn (list "VF" vfl-nummer))
(dbgclose)))
(princ)
)
;; ============================================================
;; TEIL 5: KETTE ZUSAMMENFUEHREN
;; ============================================================
;; Vario_Kette_Merge: eine Kette aus bereits in der Zeichnung liegenden
;; Vario-Foerderer-Bausteinen (lose Einzelteile WIE AUCH bereits fertig
;; gewickelte VF_n-Bloecke, in beliebiger Mischung) wird ab einem gewaehlten
;; Start-Baustein ueber die reale KS_AUS->KS_EIN-Nachbarschaft verfolgt und zu
;; EINEM neuen Gesamt-VF_n-Block verschmolzen. Die Attribute werden dabei aus
;; der gefundenen Bausteinfolge NEU hergeleitet (dieselben Akkumulatoren/
;; Formeln wie beim interaktiven Bau in Modus 1/2 - vfl-acc-*,
;; ssg-strecke-attrib-defs -, nur rueckwirkend nach dem Auffinden statt
;; waehrend des Bauens gefuellt). Nur VORWAERTS ab dem gewaehlten
;; Start-Baustein (keine Rueckwaerts-Suche).
;;
;; Alle beteiligten Bausteintypen tragen laut Nutzerbestaetigung ein eigenes
;; KS_EIN/KS_AUS (auch Umlenk-/Motorstation, Vertikalboegen, das gestreckte
;; Zwischenstueck und der Separator) - die Verkettung laeuft daher komplett
;; ueber KS-Nachbarschaft, keine Bounding-Box-Geometrie noetig.
;;
;; Ein angetroffener VF_n-Wrapper wird beim Erfassen SOFORT (temporaer) in
;; seine Sub-Elemente aufgeloest, die dann wie von Anfang an lose Bauteile
;; behandelt werden (vfl-kette-sammle-alle) - eine fruehere Fassung versuchte
;; stattdessen, fuer einen Wrapper EINEN Gesamt-KS_EIN/KS_AUS per "welcher
;; Punkt hat kein Gegenstueck"-Heuristik zu bestimmen; das schlug bei langen
;; bestehenden Ketten durch akkumulierte Rundungsungenauigkeit fehl (siehe
;; [[project_vario_kette_merge]] Update 6).
;; Toleranzen (mm) fuer die Nachbarschaftspruefung zwischen zwei Bausteinen:
;; <= tol-eng -> gilt als sauber verbunden, Kette geht weiter
;; <= tol-weit -> sieht verbunden aus, ist es aber nicht -> Warnung, Kettenende
;; > tol-weit -> kein Zusammenhang, normales (stilles) Kettenende
(if (null *vfl-kette-tol-eng*) (setq *vfl-kette-tol-eng* 2.0))
(if (null *vfl-kette-tol-weit*) (setq *vfl-kette-tol-weit* 100.0))
;; Kleine Feld-Zugriffe auf einen Ketten-Datensatz
;; (ename bname ks-ein-punkt ks-aus-punkt wrapper-ename)
;; ename = nil und wrapper-ename gesetzt: Sub-Element eines aufgeloesten
;; VF_n-Wrappers (wrapper-ename = dessen Original-Ename). Sonst: eigenstaen-
;; diges Bauteil (ename gesetzt, wrapper-ename nil).
(defun vfl-kette-rec-ename (rec) (nth 0 rec))
(defun vfl-kette-rec-bname (rec) (nth 1 rec))
(defun vfl-kette-rec-ein (rec) (nth 2 rec))
(defun vfl-kette-rec-aus (rec) (nth 3 rec))
(defun vfl-kette-rec-wrapper (rec) (nth 4 rec))
;; String an einem Trennzeichen aufteilen (reine Teilstring-Suche, keine
;; Wildcards). Bei "_"-Trennung eines Bausteinnamens liefert das die
;; Namensteile unabhaengig vom Dimensions-Suffix (_2D/_3D) - der steht immer
;; am Ende und wird von den (nur von vorne indizierenden) Zugriffen unten
;; ignoriert.
(defun vfl-kette-split (str delim / pos ergebnis rest)
(setq ergebnis '() rest str)
(while (setq pos (vl-string-search delim rest))
(setq ergebnis (append ergebnis (list (substr rest 1 pos))))
(setq rest (substr rest (+ pos 1 (strlen delim))))
)
(append ergebnis (list rest))
)
(defun vfl-kette-teil (bname idx) (nth idx (vfl-kette-split bname "_")))
;; Bausteintyp aus dem Blocknamen ableiten. Scanner/Separator_SP (manuelle
;; Sensor-Bloecke, siehe count_sep_scan.lsp) gehoeren NICHT zur Kette selbst
;; und tauchen hier bewusst nicht auf.
;; VF- vs. GF-Vertikalboegen: BEIDE Familien teilen den Praefix
;; "Vario_Bogen_auf/ab_" - die VF-Bibliothek (init-bibliothek, Winkel 3-51)
;; traegt zusaetzlich "_TEF_" im Namen, die GF-eigenen Boegen (nur 3/6 Grad,
;; data/ils/Vario_Bogen_ab/auf_3/6_rechts.dwg, siehe vfl-gf-bogen-init) NICHT -
;; die "_TEF_"-Pruefung MUSS daher vor der generischen Pruefung stehen (cond
;; nimmt den ERSTEN Treffer). Damit erkennt ein KS_AUS-Scan (vfl-ks-aus-hz-an-
;; punkt/vfl-kette-typ-familie) eine mit einem GF-eigenen Bogen endende Kette
;; weiterhin korrekt als GF-Familie, nicht als VF.
(defun vfl-kette-typ (bname)
(cond
((wcmatch bname "VF_*") "WRAPPER")
((wcmatch bname "AS_Element_*") "AS")
((wcmatch bname "ES_Element_*") "ES")
((wcmatch bname "Gefaellebogen_*") "GFBOGEN")
((wcmatch bname "Vario_Kurve_*") "KURVE")
((wcmatch bname "Vario_Umlenkstation_*") "UMLENK")
((wcmatch bname "Vario_Motorstation_*") "MOTOR")
((wcmatch bname "Vario_Bogen_auf_*_TEF_*") "BOGENAUF")
((wcmatch bname "Vario_Bogen_ab_*_TEF_*") "BOGENAB")
((wcmatch bname "Vario_Bogen_auf_*") "GFVERTBOGEN")
((wcmatch bname "Vario_Bogen_ab_*") "GFVERTBOGEN")
((wcmatch bname "Staustrecke_SP_1000_mm*") "STRECKE")
((wcmatch bname "Staustrecke_Separator_SP_300_mm*") "SEP")
(t "UNBEKANNT")
)
)
;; Gemessene Neigung (Grad, positiv=abwaerts wie frame->hz-winkel) zwischen
;; zwei Welt-Punkten.
(defun vfl-kette-neigung (ein aus / dx dy dz horiz)
(setq dx (- (car aus) (car ein)) dy (- (cadr aus) (cadr ein)) dz (- (caddr aus) (caddr ein)))
(setq horiz (sqrt (+ (* dx dx) (* dy dy))))
(if (> horiz 1e-6) (* (atan (- dz) horiz) (/ 180.0 pi)) 0.0)
)
(defun vfl-kette-round (x) (atoi (rtos x 2 0)))
;; Attribut-Wert auf "" erzwingen. ssg-attrib-set-on ueberspringt leere Werte
;; bewusst als "keine Ueberschreibung" (ATTDEF-Default bleibt stehen) - hier
;; soll das Feld aber ABSICHTLICH geleert werden (z.B. SEITE_AS/SEITE_ES,
;; wenn die Kette kein AS-/ES-Element hat und der ATTDEF-Default "rechts"
;; sonst faelschlich stehen bliebe).
(defun vfl-kette-attrib-leeren (ent tag / obj ed typ etag)
(setq obj (entnext ent))
(while obj
(setq ed (entget obj))
(setq typ (cdr (assoc 0 ed)))
(if (equal typ "SEQEND")
(setq obj nil)
(progn
(if (and (equal typ "ATTRIB") (equal (cdr (assoc 2 ed)) tag))
(progn (entmod (subst (cons 1 "") (assoc 1 ed) ed)) (entupd obj)))
(setq obj (entnext obj))
)
)
)
)
;; KS_EIN/KS_AUS-URSPRUNGSPUNKTE (Welt-Koordinaten, keine Richtung) eines
;; Bausteins ermitteln - robust gegen NICHT-UNIFORME Skalierung (z.B.
;; Staustrecke_SP_1000_mm, das per insert-inclined-scaled-block auf die
;; reale Segmentlaenge gestreckt wird). extract-ks-from-block-raw
;; (vf_core.lsp) klassifiziert die 3 Achslinien im KS-Sub-Block ueber ihre
;; ABSOLUTE LAENGE (ks-line-axis, feste Baender ~1/~100 Einheiten) - wird die
;; Fahrtrichtungs-Achslinie mitgestreckt, faellt sie aus diesem Raster und
;; die Extraktion schlaegt still fehl (empirisch bestaetigt: bei den meisten
;; Staustrecke-Instanzen "KS_EIN/KS_AUS FEHLT"). Uebernimmt daher die in
;; ks_segmente.lsp (kseg-collect-lose) bereits bewaehrte Methode: an einer
;; KOPIE die Skalierung auf 1:1:1 zuruecksetzen (Marker wieder nominal lang,
;; Extraktion funktioniert normal), KS_EIN/KS_AUS dort lesen, danach den
;; KS_AUS-Versatz mit dem echten XScaleFactor zurueckrechnen (die Streckung
;; wirkt lokal rein auf der Block-X-Achse/Docking-Richtung; nach Rotation ins
;; Weltsystem hat der Versatzvektor i.A. X-/Y-/Z-Anteile, die Streckung wirkt
;; aber auf alle drei mit demselben Faktor sx - Y/Z-Skalierung bleibt bei
;; dieser Teilefamilie immer 1.0). Kopie wird sofort wieder geloescht -
;; block-obj selbst bleibt unveraendert.
;; Rueckgabe: (("KS_EIN" . punkt) ("KS_AUS" . punkt)) - je nur wenn gefunden.
(defun vfl-kette-ks-ursprung (block-obj / sx copyobj ksdata kez kaz ergebnis)
(setq sx (vla-get-XScaleFactor block-obj))
(setq copyobj (vla-Copy block-obj))
(vla-put-XScaleFactor copyobj 1.0)
(vla-put-YScaleFactor copyobj 1.0)
(vla-put-ZScaleFactor copyobj 1.0)
(setq ksdata (extract-ks-from-block-raw copyobj))
(if (not (vlax-erased-p copyobj)) (vl-catch-all-apply 'vla-Delete (list copyobj)))
(setq kez (if (assoc "KS_EIN" ksdata) (car (cadr (assoc "KS_EIN" ksdata))) nil))
(setq kaz (if (assoc "KS_AUS" ksdata) (car (cadr (assoc "KS_AUS" ksdata))) nil))
(if (and kez kaz (/= sx 1.0))
(setq kaz (list
(+ (car kez) (* sx (- (car kaz) (car kez))))
(+ (cadr kez) (* sx (- (cadr kaz) (cadr kez))))
(+ (caddr kez) (* sx (- (caddr kaz) (caddr kez))))
))
)
(setq ergebnis '())
(if kez (setq ergebnis (cons (cons "KS_EIN" kez) ergebnis)))
(if kaz (setq ergebnis (cons (cons "KS_AUS" kaz) ergebnis)))
ergebnis
)
;; Alle Vario-Kette-Bausteine der Zeichnung einsammeln (lose Einzelteile UND
;; die Sub-Elemente bereits gewickelter VF_n - siehe vfl-kette-typ). Ein
;; angetroffener VF_n-Wrapper wird SOFORT (temporaer) in seine Sub-Elemente
;; aufgeloest und JEDES Sub-Element als eigener Datensatz mit direkt lesbarem
;; KS_EIN/KS_AUS erfasst - keine "welcher Punkt hat kein Gegenstueck"-
;; Heuristik fuer einen Gesamt-Punkt mehr noetig (die schlug bei langen
;; bestehenden Ketten durch akkumulierte Rundungsungenauigkeit fehl, siehe
;; [[project_vario_kette_merge]]). Die Kettenverfolgung faedelt die
;; Sub-Elemente danach ueber ihre echten KS-Punkte in der richtigen
;; Reihenfolge auf - unabhaengig von der Explode-Reihenfolge.
;; ename = nil fuer ein Sub-Element eines Wrappers (die temporaere
;; Explode-Kopie wurde bereits wieder geloescht); wrapper-ename identifiziert
;; in diesem Fall den ORIGINAL-Wrapper (fuer den finalen Merge-Schritt).
;; Rueckgabe: Liste von (ename bname ein aus wrapper-ename).
(defun vfl-kette-sammle-alle ( / ss i ename bname typ obj kinder kind
sub-ks child-bname child-ks records)
(setq records '())
(setq ss (ssget "X" '((0 . "INSERT"))))
(if ss
(progn
(setq i 0)
(while (< i (sslength ss))
(setq ename (ssname ss i))
(setq bname (cdr (assoc 2 (entget ename))))
(setq typ (vfl-kette-typ bname))
(cond
((= typ "WRAPPER")
(setq obj (vlax-ename->vla-object ename))
(setq kinder (vlax-invoke obj 'Explode))
(foreach kind kinder
(if (and (not (vlax-erased-p kind))
(= (vla-get-ObjectName kind) "AcDbBlockReference"))
(progn
(setq child-bname (vla-get-Name kind))
(setq child-ks (vfl-kette-ks-ursprung kind))
(setq records
(cons (list nil child-bname
(cdr (assoc "KS_EIN" child-ks))
(cdr (assoc "KS_AUS" child-ks))
ename)
records))
)
)
)
(foreach kind kinder (if (not (vlax-erased-p kind)) (vla-Delete kind)))
)
((/= typ "UNBEKANNT")
(setq obj (vlax-ename->vla-object ename))
(setq sub-ks (vfl-kette-ks-ursprung obj))
(setq records
(cons (list ename bname
(cdr (assoc "KS_EIN" sub-ks))
(cdr (assoc "KS_AUS" sub-ks))
nil)
records))
)
)
(setq i (1+ i))
)
)
)
records
)
;; Naechsten Datensatz in rest-liste suchen, dessen KS_EIN am naechsten am
;; gegebenen KS_AUS-Punkt liegt. Rueckgabe: (rec . abstand) oder nil.
(defun vfl-kette-naechster (aus-punkt rest-liste / rec bester bester-d d)
(setq bester nil bester-d nil)
(foreach rec rest-liste
(if (vfl-kette-rec-ein rec)
(progn
(setq d (distance aus-punkt (vfl-kette-rec-ein rec)))
(if (or (null bester-d) (< d bester-d))
(progn (setq bester rec) (setq bester-d d)))
)
)
)
(if bester (cons bester bester-d) nil)
)
;; Kette ab start-rec NUR VORWAERTS verfolgen. Rueckgabe: (list kette warnung)
;; kette = Liste der Datensaetze in Ketten-Reihenfolge (mind. start-rec)
;; warnung = (letzter-bname kandidat-bname abstand) wenn eine Luecke im
;; tol-weit-Band gefunden wurde, sonst nil.
;; letzter-fund = (rec . abstand) des zuletzt geprueften (aber verworfenen)
;; Kandidaten - Diagnose-Hilfe, wenn die Kette bei Laenge 1
;; endet (dann kein warnung, aber evtl. trotzdem ein Fund
;; ausserhalb von tol-weit).
;; verdaechtig = Liste (vorgaenger-bname naechster-bname abstand) fuer jeden
;; Anschluss, dessen Abstand > 0.5mm war - eine echte, absichtlich
;; gebaute KS-zu-KS-Verbindung sollte praktisch bei 0mm liegen;
;; ein spuerbarer (aber noch innerhalb tol-eng liegender)
;; Abstand deutet auf eine ZUFAELLIG nahe, aber NICHT wirklich
;; zusammengehoerige Stelle hin (z.B. zwei unabhaengige Linien,
;; die im Layout nahe beieinander liegen/sich kreuzen).
(defun vfl-kette-verfolgen (start-rec alle-records / rest kette aktuell fund fertig warnung letzter-fund verdaechtig)
(setq rest (vl-remove start-rec alle-records))
(setq kette (list start-rec))
(setq aktuell start-rec)
(setq warnung nil)
(setq letzter-fund nil)
(setq verdaechtig '())
(setq fertig nil)
(while (not fertig)
(setq fund (vfl-kette-naechster (vfl-kette-rec-aus aktuell) rest))
(setq letzter-fund fund)
(cond
((null fund) (setq fertig T))
((<= (cdr fund) *vfl-kette-tol-eng*)
(if (> (cdr fund) *vfk-kette-anschluss-warnung*)
(setq verdaechtig (cons (list (vfl-kette-rec-bname aktuell) (vfl-kette-rec-bname (car fund)) (cdr fund)) verdaechtig)))
(setq aktuell (car fund))
(setq kette (append kette (list aktuell)))
(setq rest (vl-remove aktuell rest))
(setq letzter-fund nil)
)
((<= (cdr fund) *vfl-kette-tol-weit*)
(setq warnung (list (vfl-kette-rec-bname aktuell) (vfl-kette-rec-bname (car fund)) (cdr fund)))
(setq fertig T)
)
(t (setq fertig T))
)
)
(list kette warnung letzter-fund verdaechtig)
)
;; Attribute des neuen Gesamt-Blocks aus der gefundenen Bausteinfolge
;; herleiten: dieselben Akkumulatoren (vfl-acc-*) wie beim interaktiven Bau,
;; hier rueckwirkend anhand der Bausteinfolge gefuellt statt waehrend des
;; Bauens. Sub-Elemente eines bereits gewickelten VF_n wurden beim Erfassen
;; (vfl-kette-sammle-alle) bereits aufgeloest und durchlaufen hier dieselbe
;; Typ-Klassifikation wie von Anfang an lose Bauteile - lose Einzelteile und
;; fertige VF_n lassen sich dadurch beliebig mischen. chain-start/chain-end =
;; Gesamt-KS_EIN/KS_AUS der ganzen Kette (Welt-Z liefert Hoehe-von/-bis
;; direkt, kein erneutes Auslesen noetig).
;; Rueckgabe: (list attribut-alist typ-str).
(defun vfl-kette-baue-attribute (kette neuer-bname chain-start chain-end /
rec typ bname anzahl-vf phase entry-info gemessen betrag richtung
as-seite es-seite as-winkel es-winkel delta-l erster letzter ergebnis typ-str
bname-winkel)
(vfl-acc-reset)
(setq anzahl-vf 0)
(setq phase "gf")
(setq entry-info nil)
(setq delta-l 0.0)
;; Leer (nicht "rechts") als Default: falls die Kette (Ausnahmefall) ohne
;; eigenes AS-/ES-Element beginnt/endet, soll das im Attribut auch als
;; "keins vorhanden" erkennbar bleiben statt eine falsche Seite vorzutaeuschen.
(setq as-seite "" es-seite "")
(setq as-winkel "" es-winkel "")
(setq erster (car kette))
(setq letzter (car (reverse kette)))
(foreach rec kette
(setq bname (vfl-kette-rec-bname rec))
(setq typ (vfl-kette-typ bname))
(if (and (vfl-kette-rec-ein rec) (vfl-kette-rec-aus rec))
(setq delta-l (+ delta-l (distance (vfl-kette-rec-ein rec) (vfl-kette-rec-aus rec)))))
(cond
;; Wrapper-Records gibt es nicht mehr - ein angetroffener VF_n wurde
;; bereits beim Erfassen (vfl-kette-sammle-alle) in seine Sub-Elemente
;; aufgeloest; jedes davon durchlaeuft hier dieselbe Typ-Klassifikation
;; wie ein von Anfang an loses Bauteil.
((= typ "AS")
(if (equal rec erster)
(progn
(setq as-seite (vfl-kette-teil bname 3))
(setq as-winkel (vfl-kette-teil bname 2)))))
((= typ "ES")
(if (equal rec letzter)
(progn
(setq es-seite (vfl-kette-teil bname 3))
(setq es-winkel (vfl-kette-teil bname 2)))))
((= typ "GFBOGEN")
;; Gefaellebogen_<seite>_<winkel>_R500
(setq *vfl-acc-gfbogen*
(vfl-inc-count *vfl-acc-gfbogen*
(strcat (if (= (vfl-kette-teil bname 1) "rechts") "R" "L") "_" (vfl-kette-teil bname 2)))))
((= typ "KURVE")
;; Vario_Kurve_<seite>_<winkel>_TEF_<variante>
(setq *vfl-acc-variokurve*
(vfl-inc-count *vfl-acc-variokurve*
(strcat (if (= (vfl-kette-teil bname 5) "aussen") "A" "I") "_" (vfl-kette-teil bname 3)))))
((= typ "UMLENK")
(setq phase "vf")
(setq anzahl-vf (1+ anzahl-vf))
(setq entry-info nil))
((= typ "MOTOR")
;; Vario_Motorstation_500mm_<seite>
(setq *vfl-acc-motorseite* (append *vfl-acc-motorseite* (list (vfl-kette-teil bname 3))))
(setq phase "gf")
(setq entry-info nil))
((or (= typ "BOGENAUF") (= typ "BOGENAB"))
(if (null entry-info)
(setq entry-info rec) ; erster Bogen des Koerper-Tripels
(progn
;; Winkel bevorzugt DIREKT aus dem Blocknamen des ersten Bogens
;; lesen (z.B. "Vario_Bogen_auf_15_TEF_rechts" -> "15") - fuer
;; jeden echten Tabellenwinkel > 3 Grad eindeutig. NUR bei "3"
;; bleibt eine Messung noetig, weil auf_3/ab_3 SOWOHL fuer einen
;; echten 3-Grad-S-Bogen ALS AUCH fuer die Stufe-2-Horizontal-
;; Mitte (winkel=0, vfs-vf-koerper-Sonderfall) verwendet werden.
;; Die vorherige Chord-Messung (Anfang Bogen1 bis Ende Bogen2)
;; verwaesserte bei echten S-Boegen >3 Grad den gemessenen
;; Winkel Richtung der 3-Grad-Basis (gewichteter Mittelwert ueber
;; Bogen1+Gerade+Bogen2 statt des reinen Geraden-Winkels) - realer
;; Bug: 15 Grad im Modell wurden nach Vario_Kette_Merge zu 11
;; Grad (Livetest 2026-09-18).
(setq bname-winkel (vfl-kette-teil (vfl-kette-rec-bname entry-info) 3))
(if (and bname-winkel (/= bname-winkel "3"))
(progn
(setq betrag (atoi bname-winkel))
(setq richtung (if (wcmatch (vfl-kette-rec-bname entry-info) "*_auf_*") "Auf" "Ab")))
(progn
(setq gemessen (vfl-kette-neigung (vfl-kette-rec-ein entry-info) (vfl-kette-rec-aus rec)))
(setq betrag (vfl-kette-round (abs gemessen)))
(setq richtung (if (< gemessen 0.0) "Auf" "Ab"))))
(vfl-acc-vf-seg richtung betrag
(distance (vfl-kette-rec-aus entry-info) (vfl-kette-rec-ein rec)))
(setq entry-info nil)
)
)
)
((= typ "STRECKE")
(if (= phase "gf")
(vfl-acc-gf-seg
(distance (vfl-kette-rec-ein rec) (vfl-kette-rec-aus rec))
(abs (vfl-kette-neigung (vfl-kette-rec-ein rec) (vfl-kette-rec-aus rec))))
;; Innerhalb eines Koerper-Tripels traegt das Zwischenstueck selbst
;; nichts direkt bei - Laenge/Neigung werden beim schliessenden
;; Bogen (s.o.) aus der Gesamtspanne des Tripels gemessen.
)
)
((= typ "SEP")
(setq *vfl-acc-separator* (1+ *vfl-acc-separator*)))
)
)
(setq typ-str
(if (or (> anzahl-vf 0) (> (length *vfl-acc-lgf*) 1)
(> (length *vfl-acc-gfbogen*) 0) (> (length *vfl-acc-variokurve*) 0))
"Streckengruppe" "Gefaellestrecke"))
(setq ergebnis
(list
(cons "Bezeichnung" neuer-bname)
(cons "ARTINR" "6220")
(cons "MONTAGEHOEHE_m" (rtos (/ (+ (caddr chain-start) (caddr chain-end)) 2000.0) 2 3))
(cons "HOEHE_VON_mm" (itoa (fix (caddr chain-start))))
(cons "HOEHE_BIS_mm" (itoa (fix (caddr chain-end))))
(cons "DELTA_H_mm" (itoa (fix (abs (- (caddr chain-end) (caddr chain-start))))))
(cons "DELTA_L_mm" (itoa (fix delta-l)))
(cons "TYP" typ-str)
(cons "SEITE_AS" as-seite)
(cons "WINKEL_AS" as-winkel)
(cons "SEITE_ES" es-seite)
(cons "WINKEL_ES" es-winkel)
(cons "ANZAHL_GF" (itoa (length *vfl-acc-lgf*)))
(cons "L_GF_m" (vfl-join-komma *vfl-acc-lgf*))
(cons "GF_WINKEL" (vfl-join-komma *vfl-acc-gfwinkel*))
(cons "GF_Bogen_L_90" (itoa (vfl-get-count *vfl-acc-gfbogen* "L_90")))
(cons "GF_Bogen_L_60" (itoa (vfl-get-count *vfl-acc-gfbogen* "L_60")))
(cons "GF_Bogen_L_30" (itoa (vfl-get-count *vfl-acc-gfbogen* "L_30")))
(cons "GF_Bogen_R_90" (itoa (vfl-get-count *vfl-acc-gfbogen* "R_90")))
(cons "GF_Bogen_R_60" (itoa (vfl-get-count *vfl-acc-gfbogen* "R_60")))
(cons "GF_Bogen_R_30" (itoa (vfl-get-count *vfl-acc-gfbogen* "R_30")))
(cons "ANZAHL_VF" (itoa anzahl-vf))
(cons "MOTORSEITE" (vfl-join-komma *vfl-acc-motorseite*))
(cons "L_VF_m" (vfl-join-komma *vfl-acc-lvf*))
(cons "ANTRIEBFAHRTRICHTUNG" (vfl-join-komma *vfl-acc-richtung*))
(cons "VF_WINKEL" (vfl-join-komma *vfl-acc-winkel*))
(cons "VF_Bogen_A_90" (itoa (vfl-get-count *vfl-acc-variokurve* "A_90")))
(cons "VF_Bogen_A_60" (itoa (vfl-get-count *vfl-acc-variokurve* "A_60")))
(cons "VF_Bogen_A_30" (itoa (vfl-get-count *vfl-acc-variokurve* "A_30")))
(cons "VF_Bogen_I_90" (itoa (vfl-get-count *vfl-acc-variokurve* "I_90")))
(cons "VF_Bogen_I_60" (itoa (vfl-get-count *vfl-acc-variokurve* "I_60")))
(cons "VF_Bogen_I_30" (itoa (vfl-get-count *vfl-acc-variokurve* "I_30")))
(cons "ANZAHL_SEPARATOR" (itoa *vfl-acc-separator*))
)
)
(list ergebnis typ-str)
)
(defun c:Vario_Kette_Merge ( / sel start-ename start-bname alle-records start-rec
start-obj start-ins kandidaten bester-d d r
lauf kette warnung naechster-fund merge-ss leaf-enames aufgeloeste-wrapper
chain-start chain-end neuer-bname
rec kinder kind erg aggregiert typ-str neuer-insert def
leaf-ename leaf-obj leaf-noch-da verdaechtig v)
(ssg-start "Vario_Kette_Merge" nil)
(princ (ssg-text "vfl-kette-titel"))
(setq sel (entsel (ssg-text "vfl-kette-start-waehlen")))
(if (null sel)
(progn (princ (ssg-text "vfl-kette-kein-objekt")) (ssg-end) (exit)))
(setq start-ename (car sel))
(setq start-bname (cdr (assoc 2 (entget start-ename))))
(if (= (vfl-kette-typ (if start-bname start-bname "")) "UNBEKANNT")
(progn
(princ (ssg-textf "vfl-kette-kein-vf-block" (list (if start-bname start-bname "?"))))
(ssg-end) (exit)))
(setq alle-records (vfl-kette-sammle-alle))
(setq start-rec nil)
(foreach rec alle-records (if (equal (vfl-kette-rec-ename rec) start-ename) (setq start-rec rec)))
(if (and (null start-rec) (= (vfl-kette-typ start-bname) "WRAPPER"))
(progn
;; Nutzer hat einen VF_n-Wrapper direkt gewaehlt - der wurde beim
;; Erfassen bereits in seine Sub-Elemente aufgeloest (keins davon
;; traegt mehr diesen ename). Das Sub-Element mit KS_EIN am naechsten
;; zum InsertionPoint des Wrappers gilt als dessen Kettenanfang - das
;; ist per Bauart (ssg-block-wrap-welt bekommt immer den Kettenanfang
;; als Basispunkt) exakt der richtige Punkt.
(setq start-obj (vlax-ename->vla-object start-ename))
(setq start-ins (vlax-safearray->list (vlax-variant-value (vla-get-InsertionPoint start-obj))))
(setq kandidaten (vl-remove-if-not
(function (lambda (r) (and (equal (vfl-kette-rec-wrapper r) start-ename) (vfl-kette-rec-ein r))))
alle-records))
(setq bester-d nil)
(foreach r kandidaten
(setq d (distance start-ins (vfl-kette-rec-ein r)))
(if (or (null bester-d) (< d bester-d)) (progn (setq start-rec r) (setq bester-d d)))
)
)
)
(if (null start-rec)
(progn
(princ (ssg-textf "vfl-kette-start-nicht-gefunden-diag"
(list (itoa (length alle-records)) (if start-bname start-bname "?")
(itoa (length (vl-remove-if-not
(function (lambda (r) (equal (vfl-kette-rec-bname r) start-bname)))
alle-records))))))
(ssg-end) (exit)))
(if (null (vfl-kette-rec-ein start-rec))
(progn (princ (ssg-text "vfl-kette-start-ohne-ks-ein")) (ssg-end) (exit)))
(setq lauf (vfl-kette-verfolgen start-rec alle-records))
(setq kette (car lauf))
(setq warnung (cadr lauf))
(setq naechster-fund (caddr lauf))
(setq verdaechtig (nth 3 lauf))
(if (< (length kette) 2)
(progn
(princ (ssg-textf "vfl-kette-diag-startbaustein"
(list (vfl-kette-rec-bname start-rec)
(rtos (car (vfl-kette-rec-aus start-rec)) 2 1)
(rtos (cadr (vfl-kette-rec-aus start-rec)) 2 1)
(rtos (caddr (vfl-kette-rec-aus start-rec)) 2 1))))
(if naechster-fund
(princ (ssg-textf "vfl-kette-diag-naechster-kandidat"
(list (vfl-kette-rec-bname (car naechster-fund))
(rtos (cdr naechster-fund) 2 1)
(rtos *vfl-kette-tol-eng* 2 1)
(rtos *vfl-kette-tol-weit* 2 1))))
(princ (ssg-textf "vfl-kette-diag-kein-anderer" (list (itoa (length alle-records))))))
(princ (ssg-text "vfl-kette-nur-ein-baustein")) (ssg-end) (exit)))
(princ (ssg-textf "vfl-kette-gefunden" (list (itoa (length kette)))))
(foreach rec kette (princ (strcat "\n " (vfl-kette-rec-bname rec))))
(if warnung
(princ (ssg-textf "vfl-kette-luecke-warnung"
(list (car warnung) (cadr warnung) (rtos (caddr warnung) 2 1)))))
;; DIAGNOSE: Anschluesse mit spuerbarem (>0.5mm), aber noch innerhalb
;; tol-eng liegendem Abstand - Verdacht auf zufaellig nahe, aber nicht
;; wirklich zusammengehoerige Bauteile (z.B. zwei unabhaengige Linien, die
;; im Layout nahe beieinander liegen).
(if verdaechtig
(foreach v verdaechtig
(princ (ssg-textf "vfl-kette-diag-verdaechtig"
(list (car v) (cadr v) (rtos (caddr v) 2 3)))))
)
;; Attribute aus der Bausteinfolge herleiten, bevor irgendetwas an der
;; Zeichnung veraendert wird (chain-start/chain-end kommen aus dem ersten/
;; letzten Datensatz in kette).
(setq neuer-bname (strcat "VF_" (itoa (vf-next-number))))
(setq chain-start (vfl-kette-rec-ein (car kette)))
(setq chain-end (vfl-kette-rec-aus (car (reverse kette))))
(setq erg (vfl-kette-baue-attribute kette neuer-bname chain-start chain-end))
(setq aggregiert (car erg))
(setq typ-str (cadr erg))
;; Aufgeloeste Wrapper-Sub-Elemente: der ORIGINAL-Wrapper wird (einmal je
;; distinktem wrapper-ename, da er i.d.R. mehrere Sub-Element-Datensaetze
;; in kette beisteuert) jetzt fuer REAL explodiert - Sub-Elemente bleiben
;; als Bloecke erhalten, ATTRIB-Textreste werden verworfen, Original
;; geloescht. Lose Einzel-Bausteine werden unveraendert direkt uebernommen (_.-BLOCK
;; unten SOLLTE sie beim Wickeln konsumieren, wie bei frisch gebauter
;; Geometrie - leaf-enames wird trotzdem mitgefuehrt, um sie nach dem
;; Wickeln sicherheitshalber explizit zu entfernen, falls _.-BLOCK sie aus
;; irgendeinem Grund NICHT aus dem Modellraum nimmt (doppelte/ueberlappende
;; Geometrie waere sonst die Folge).
(setq merge-ss (ssadd))
(setq leaf-enames '())
(setq aufgeloeste-wrapper '())
(foreach rec kette
(if (vfl-kette-rec-wrapper rec)
(if (not (member (vfl-kette-rec-wrapper rec) aufgeloeste-wrapper))
(progn
(setq aufgeloeste-wrapper (cons (vfl-kette-rec-wrapper rec) aufgeloeste-wrapper))
(setq kinder (vlax-invoke (vlax-ename->vla-object (vfl-kette-rec-wrapper rec)) 'Explode))
(foreach kind kinder
(if (not (vlax-erased-p kind))
(if (= (vla-get-ObjectName kind) "AcDbBlockReference")
(ssadd (vlax-vla-object->ename kind) merge-ss)
(vla-Delete kind)
)
)
)
(vla-Delete (vlax-ename->vla-object (vfl-kette-rec-wrapper rec)))
)
)
(progn
(setq leaf-enames (cons (vfl-kette-rec-ename rec) leaf-enames))
(ssadd (vfl-kette-rec-ename rec) merge-ss)
)
)
)
;; Frische ATTDEFs am Kettenanfang (wie vfl-block-erstellen), neu wickeln.
(foreach def (ssg-strecke-attrib-defs typ-str)
(entmake
(list '(0 . "ATTDEF")
(cons 10 chain-start)
(cons 11 chain-start)
'(40 . 50.0)
(cons 1 (cadr def))
(cons 2 (car def))
(cons 3 (car def))
'(70 . 1)
'(72 . 0)
'(74 . 0)))
(ssadd (entlast) merge-ss)
)
(setq neuer-insert (ssg-block-wrap-welt neuer-bname chain-start merge-ss))
;; Ziel-Layer der Baugruppe aus cfg/layer.cfg (ohne Eintrag bleibt die
;; Referenz auf dem aktuellen Layer, wie vor der Umstellung).
(ssg-layer-setzen neuer-insert "variofoerderer")
(ssg-attrib-set-on neuer-insert aggregiert)
;; SEITE_AS/SEITE_ES explizit leeren, wenn die Kette (Ausnahmefall) ohne
;; eigenes AS-/ES-Element beginnt/endet - ssg-attrib-set-on wuerde einen
;; leeren Wert sonst als "keine Ueberschreibung" behandeln und den
;; ATTDEF-Default "rechts" faelschlich stehen lassen.
(if (= (cdr (assoc "SEITE_AS" aggregiert)) "") (vfl-kette-attrib-leeren neuer-insert "SEITE_AS"))
(if (= (cdr (assoc "SEITE_ES" aggregiert)) "") (vfl-kette-attrib-leeren neuer-insert "SEITE_ES"))
(if (car (atoms-family 1 '("SSG-ID-GENERATE"))) (ssg-id-generate neuer-insert))
;; Sicherheitsnetz: lose Einzelteile, die _.-BLOCK aus irgendeinem Grund NICHT
;; aus dem Modellraum entfernt hat, jetzt explizit loeschen - verhindert
;; doppelte/ueberlappende Geometrie (altes Original + neuer Gesamt-Block).
(setq leaf-noch-da 0)
(foreach leaf-ename leaf-enames
(setq leaf-obj (vl-catch-all-apply 'vlax-ename->vla-object (list leaf-ename)))
(if (and (not (vl-catch-all-error-p leaf-obj)) leaf-obj (not (vlax-erased-p leaf-obj)))
(progn
(setq leaf-noch-da (1+ leaf-noch-da))
(vla-Delete leaf-obj)
)
)
)
(if (> leaf-noch-da 0)
(princ (strcat "\n [Diagnose] " (itoa leaf-noch-da)
" lose Einzelteil(e) waren nach _.-BLOCK noch im Modellraum vorhanden - jetzt nachtraeglich entfernt.")))
(princ (ssg-textf "vfl-kette-fertig" (list neuer-bname (itoa (length kette)))))
(ssg-end)
(princ)
)
(vf-typ-registrieren
"linienzug"
'vfl-berechne-platzhalter
'vfl-einfuege-platzhalter
(ssg-text "vfc-typ-linienzug-beschreibung"))
(princ)