Files
dxfmakros/Lisp/vf_linienzug.lsp
T
m.stangl bf0237666b [REFACTOR] VF-Linienzug: AS/ES-Winkel-Wertemenge zentral aus *vfk-as-es-winkel*
Die gueltige AS/ES-Winkel-Menge ("30"/"90") stand als Literal doppelt im
Glied-Schema. Jetzt einmalig als *vfk-as-es-winkel* in vf_konstanten.lsp,
Schema referenziert sie (mit Fallback in vf_linienzug).

BEWUSST bei STRING geblieben (nicht INT): AS/ES ist echt 2-wertig (keine
60-Grad-Bloecke), das Journal fuehrt AS/ES-Winkel als STR - eine Umstellung auf
INT haette Journal-Format + Alt-Block-Editier-Kompatibilitaet beruehrt (2
Cluster) fuer reine Konsistenz. Zentrale Konstante beseitigt die Streuung ohne
dieses Risiko (Entscheidung mit Nutzer abgestimmt).

Die index-basierten Dialog-Mappings (if gwinkel "1" -> "30"/"90") bleiben,
da sie an feste 2-Options-DCL-Layouts gebunden sind (keine Listen-Lookups).

Paren-Balance geprueft.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-29 12:51:09 +02:00

5963 lines
304 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)
;; 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 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).
(defun vfl-journal-reset ()
(setq *vfl-journal* '() *vfl-replay-queue* nil)
(if (boundp '*vflw-pending*) (setq *vflw-pending* nil)))
;; 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)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (and (= ergebnis 1) dlg-wert (> (strlen dlg-wert) 0))
(atof dlg-wert)
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)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (= ergebnis 1) (itoa (1+ (atoi dlg-opt))) ""))))
(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)")
(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)))))
(t (setq fertig t)))
)
)
)
(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")
(start_list "winkel")
(add_list (vflw-clean (ssg-text "vf-winkel-90")))
(add_list (vflw-clean (ssg-text "vf-winkel-30")))
(end_list)
(set_tile "winkel" "0")
(start_list "seite")
(add_list (vflw-clean (ssg-text "gf-seite-links")))
(add_list (vflw-clean (ssg-text "gf-seite-rechts")))
(end_list)
(set_tile "seite" "0")
(action_tile "setzen"
"(mode_tile \"winkel\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"seite\" (if (= (get_tile \"setzen\") \"1\") 0 1))")
(action_tile "accept"
"(setq gsetzen (get_tile \"setzen\"))
(setq gwinkel (get_tile \"winkel\"))
(setq gseite (get_tile \"seite\"))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (= 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" (if (= gwinkel "1") "30" "90") (if (= gseite "1") "2" "1")))
(vflw-pending-push-all (list "2"))))))
(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 gseite)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_winkel_seite" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text kopf-key)))
(start_list "winkel")
(foreach o winkel-optionen (add_list (vflw-clean o)))
(end_list)
(set_tile "winkel" "0")
(start_list "seite")
(add_list (vflw-clean (ssg-text "gf-seite-links")))
(add_list (vflw-clean (ssg-text "gf-seite-rechts")))
(end_list)
(set_tile "seite" "0")
(action_tile "accept"
"(setq gwinkel (get_tile \"winkel\")) (setq gseite (get_tile \"seite\")) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (= ergebnis 1)
(vflw-pending-push-all
(list
(nth (atoi gwinkel) winkel-werte)
(if (= gseite "1") "2" "1"))))))
(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")))
;; Winkel-Reihenfolge vereinheitlicht auf 30/60/90 (wie GF-Bogen);
;; Default 90 -> Listenposition 2.
(start_list "winkel")
(add_list (vflw-clean (ssg-text "vfl-opt3-30grad")))
(add_list (vflw-clean (ssg-text "vfl-opt2-60grad")))
(add_list (vflw-clean (ssg-text "vfl-opt1-90grad")))
(end_list)
(set_tile "winkel" "2")
(start_list "seite")
(add_list (vflw-clean (ssg-text "gf-seite-links")))
(add_list (vflw-clean (ssg-text "gf-seite-rechts")))
(end_list)
(set_tile "seite" "0")
(start_list "variante")
(add_list (vflw-clean (ssg-text "vfl-variante-aussen")))
(add_list (vflw-clean (ssg-text "vfl-variante-innen")))
(end_list)
(set_tile "variante" "1")
(action_tile "accept"
"(setq gwinkel (get_tile \"winkel\"))
(setq gseite (get_tile \"seite\"))
(setq gvariante (get_tile \"variante\"))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (= ergebnis 1)
(vflw-pending-push-all
(list (nth (atoi gwinkel) *vfk-gf-bogen-winkel*)
(if (= gseite "1") "2" "1")
(if (= gvariante "1") "2" "1"))))))
(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).
(defun vflw-gruppe-gefaelle-impl (hoehe-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")))
(start_list "modus")
(add_list (vflw-clean (ssg-text "vfl-gefaelle-opt-hoehe")))
(add_list (vflw-clean (ssg-text "vfl-gefaelle-opt-winkel")))
(end_list)
(set_tile "modus" "0")
(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"
"(if (= (get_tile \"modus\") \"0\")
(progn (set_tile \"wlabel\" \"Zielhoehe (Z, mm):\")
(set_tile \"wert\" (rtos hoehe-default 2 1)))
(progn (set_tile \"wlabel\" \"Neigungswinkel (Grad):\")
(set_tile \"wert\" (rtos (float winkel-default) 2 1))))")
(action_tile "accept"
"(setq gmodus (get_tile \"modus\")) (setq gwert (get_tile \"wert\")) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (= ergebnis 1)
(vflw-pending-push-all
(list (if (= gmodus "1") "2" "1") (atof gwert))))))
(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) mit zwei baubaren Vorschlaegen (siehe
;; vfl-vf-hoehe-vorschlaege/vfl-segment-hoehe-vorschlaege) - dann zeigt der
;; Kopftext beide Richtungen, vorbelegt wird die Ab-Variante (haeufigerer
;; Fall, siehe vfl-segment-entscheidung). Ist hoehe-ab nil (kein baubarer
;; Wert in der sondierten Zone gefunden), 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 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))
(setq vorbelegt (if hoehe-ab hoehe-ab 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
(if (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)))
(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)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (and (= ergebnis 1) dlg-wert (> (strlen dlg-wert) 0))
(vflw-pending-push-all (list (atof dlg-wert))))))
(princ))
;; ------------------------------------------------------------
;; Gruppe "Horizontales Stueck": Separator VOR + Separator NACH +
;; Foerderer-Ende (3 Optionen). Original-Reihenfolge/-Journal (alle in
;; vfl-baue-horizontal-koerper): vfl-in-string (Sep-vor), vfl-in-string
;; (Sep-nach), [nur im Ziel-Modus] vfl-in-string (Ist-Ende). Der Endpunkt
;; wird an der Aufrufstelle bereits separat ueber vfl-neue-linie-messen
;; gemessen - kein Punkt-Button hier (analog Gefaelle-/Ziel-Hoehe-Gruppe).
;; mit-ende: T, wenn (im Ziel-Modus) zusaetzlich die Ist-Ende-Frage
;; mitgestellt werden soll (siehe vfl-baue-horizontal-koerper).
(defun vflw-gruppe-horizontal-impl (mit-ende / dat dcl-pfad ergebnis gsepvor gsepnach gende)
(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" "Horizontales Stueck")
(set_tile "sepvor" "0")
(set_tile "sepnach" "0")
(start_list "istende")
(add_list (vflw-clean (ssg-text "vfl-ja-nur-motorstation")))
(add_list (vflw-clean (ssg-text "vfl-nein-weiterbauen")))
(add_list (vflw-clean (ssg-text "vfl-ja-motorstation-kettenende")))
(end_list)
(set_tile "istende" "1")
(mode_tile "istende" (if mit-ende 0 1))
(action_tile "accept"
"(setq gsepvor (get_tile \"sepvor\"))
(setq gsepnach (get_tile \"sepnach\"))
(setq gende (get_tile \"istende\"))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(if (= ergebnis 1)
(vflw-pending-push-all
(append
(list (if (= gsepvor "1") "1" "2") (if (= gsepnach "1") "1" "2"))
(if mit-ende (list (itoa (1+ (atoi gende)))) nil))))))
(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)
(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)
(alert (ssg-textf "vfl-m3-alert-objekte-fehlen" (list (itoa fehlt))))))
(progn
(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.
(defun vfl-in-value (kind livefn / popped v)
(setq popped (if *vfl-replay-queue* (vfl-replay-pop) nil))
(if popped
(setq v (car popped))
(progn
(setq popped (vflw-pending-pop))
(if popped (setq v (car popped)) (setq v (apply livefn nil)))))
(vfl-journal-record (if v kind "NIL") v)
v)
;; Variante mit explizitem Gueltig-Praedikat (fuer vfl-in-string: "" ist gueltig).
(defun vfl-in-value-p (kind gueltig-p livefn / popped v)
(setq popped (if *vfl-replay-queue* (vfl-replay-pop) nil))
(if popped
(setq v (car popped))
(progn
(setq popped (vflw-pending-pop))
(if popped (setq v (car popped)) (setq v (apply livefn nil)))))
(vfl-journal-record (if (apply gueltig-p (list v)) kind "NIL") 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)))
;; --- 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.
(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 ",")))
(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)))
(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) ",") "]"))
(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")))
(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))
;; 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-journal e cnt slice-label)
(setq forward-journal (reverse *vfl-journal*))
(setq seg-idx (length (vfl-journal-glieder forward-journal)))
(setq cnt 0)
(if (> seg-idx 0)
(progn
(setq seg-journal (vfl-journal-slice forward-journal seg-idx))
;; 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 seg-journal (= (car (car seg-journal)) "STEP"))
(cdr (car seg-journal)) wahl))
(setq e (if seg-lastEnt (entnext seg-lastEnt) (entnext)))
(while e
(vfl-journal-xdata-schreiben-seg e seg-idx seg-journal)
(setq cnt (1+ cnt))
(setq e (entnext e))
)
;; 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-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.
(if (vfl-schema-felder slice-label)
(vfl-schema-log-slice slice-label seg-journal)
(if (member slice-label '("Linie-VF" "Horizontal-VF"))
(vfl-schema-log-vf slice-label seg-journal)))
)
(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.
(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)
(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")))
(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))))
(princ (ssg-textf "vfl-abbruch-gesichert" (list (itoa cnt)))))
)
)
(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* '())
(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 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.
(defun vfl-waehle-winkel (ergebnis-liste / gueltige idx antwort e labels)
(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)))))
(cond
((null gueltige) nil)
((= (length gueltige) 1)
(setq e (car gueltige)) (list (nth 0 e) (nth 1 e) (nth 2 e)))
(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)))
(setq antwort (vfl-menu-int (ssg-textf "vfl-prompt-wahl-bis-n" (list (length gueltige)))
labels 1 "vfl-mehrere-winkel-header"))
(if (or (null antwort) (< antwort 1) (> antwort (length gueltige))) (setq antwort 1))
(setq e (nth (1- antwort) gueltige))
(list (nth 0 e) (nth 1 e) (nth 2 e)))))
;; 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*))))
(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
(defun vfl-vf-entscheidung (deltaL deltaH richtung /
winkel-natuerlich wahl horizontal-info)
(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.
((< deltaL 1000.0) (list nil nil nil nil))
;; Segment ohne messbare Hoehenaenderung: kein sinnvolles VF
((< deltaH 1.0) (list nil nil nil nil))
;; Steigend: bei mehreren gueltigen Winkeln waehlt der Nutzer (vfl-vf-winkel).
((= richtung "Auf")
(setq wahl (vfl-vf-winkel deltaL deltaH "Auf"))
(if wahl
(list "VF" (nth 0 wahl) (nth 1 wahl) (nth 2 wahl))
(list nil nil nil nil)))
;; Fallend: natuerlichen Neigungswinkel bestimmen (atan der Schraege).
;; steiler als 3 Grad -> absteigender VarioFoerderer (VF-ab)
;; sonst -> VF (Horizontale Mitte oder diskreter Winkel)
(t
(setq winkel-natuerlich (* (atan (/ deltaH deltaL)) (/ 180.0 pi)))
(if (> winkel-natuerlich 3.0)
(progn
(setq wahl (vfl-vf-winkel deltaL deltaH "Ab"))
(if wahl
(list "VF" (nth 0 wahl) (nth 1 wahl) (nth 2 wahl))
(list nil nil nil nil)))
(progn
(setq horizontal-info (berechne-horizontale-mitte deltaL deltaH "Ab"))
(if (and horizontal-info (caddr horizontal-info))
(list "VF" 0 (car horizontal-info) (cadr horizontal-info))
(progn
(setq wahl (vfl-vf-winkel deltaL deltaH "Ab"))
(if wahl
(list "VF" (nth 0 wahl) (nth 1 wahl) (nth 2 wahl))
(list nil nil nil nil)))))
)
)
)
)
;; 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 /
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)
(if (null feste-hz) (setq feste-hz FESTE_HORIZONTAL))
(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 (* 3.0 (/ pi 180.0)))
(setq rad7 (* (float (if (= richtung "Auf") (- 3 winkel) (+ winkel 3)))
(/ 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)))
(setq winkel-eff (if (= richtung "Auf") (- winkel 3) (+ winkel 3)))
(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 (- bogen-z1) (- bogen-z2)
(* feste-hz sin3)))
(setq B (+ (- deltaH abs-dz-AUS abs-dz-EIN (* 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 1000.0) 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*))
(setq aus-dx save-ausdx aus-dz save-ausdz)
ergebnis)
(t
(setq horizontal-info (berechne-horizontale-mitte deltaL deltaH "Ab"))
(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*))
(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 3.0))
(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)
)
;; 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.
(defun vfl-body-machbar-p (deltaL deltaH richtung / horizontal-info save-ausdx save-ausdz ergebnis)
(if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek))
(setq save-ausdx aus-dx save-ausdz aus-dz)
(setq aus-dx 0.0 aus-dz 0.0)
(setq ergebnis
(cond
((< deltaL 800.0) nil)
((< deltaH 1.0)
(setq horizontal-info (berechne-horizontale-mitte deltaL deltaH "Ab"))
(and horizontal-info (caddr horizontal-info) t))
((= richtung "Auf")
(vfl-winkel-machbar-p deltaL deltaH "Auf" 800.0))
(t
(setq horizontal-info (berechne-horizontale-mitte deltaL deltaH "Ab"))
(if (and horizontal-info (caddr horizontal-info))
t
(vfl-winkel-machbar-p deltaL deltaH "Ab" 800.0)))
)
)
(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 / schritt grenze h gefunden)
(setq schritt 10.0)
(setq grenze (/ deltaL 3.0))
(setq h schritt gefunden nil)
(while (and (not gefunden) (<= h grenze))
(if (vfl-body-machbar-p deltaL h richtung)
(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) 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 / min-ab min-auf)
(setq min-ab (vfl-body-min-deltah deltaL "Ab"))
(setq min-auf (vfl-body-min-deltah deltaL "Auf"))
(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 1000.0)
(if (= richtung "Auf")
(list nil nil nil nil)
(list "GF" 3.0 nil nil)))
;; Segment ohne messbare Hoehenaenderung: weder Gefaelle noch sinnvolles VF
((< deltaH 1.0) (list nil nil nil nil))
;; Steigend: Gefaelle kann nicht steigen -> nur VF moeglich.
((= 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 (fest 3 Grad)
;; sonst -> VF (vfl-vf-entscheidung)
(t
(setq winkel-natuerlich (* (atan (/ deltaH deltaL)) (/ 180.0 pi)))
(if (<= (abs (- winkel-natuerlich 3.0)) *vfl-gf-winkel-toleranz*)
(list "GF" 3.0 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 ( / )
(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)
(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))
(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)))
(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.
(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 25000.0)
(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))))
;; 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 20000.0)
(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))) 1.5))
;; 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 300 0))
(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))
;; 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.
(defun vfl-baue-horizontal-koerper (frame hz dL ziel-modus gf2-laenge /
pt m1 sep-vor pt-vor-bogen sep-nach ist-ende-antwort)
(setq pt (car frame))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-horizontal-impl (list ziel-modus)))
(princ (ssg-text "vfl-sep-vor-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-nein"))
(setq sep-vor (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2 "vfl-sep-vor-frage") "1"))
(if (and ziel-modus sep-vor) (setq dL (max 100.0 (- dL 300.0))))
;; 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 (max 100.0 (- dL (vfl-projiziere-distanz pt-vor-bogen pt hz))))))
;; Separator NACH abfragen (noch nicht bauen) - im Ziel-Modus VOR der
;; Laengenberechnung, damit der Fussabdruck feststeht.
(princ (ssg-text "vfl-sep-nach-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-nein"))
(setq sep-nach (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2 "vfl-sep-nach-frage") "1"))
(if (and ziel-modus sep-nach) (setq dL (max 100.0 (- dL 300.0))))
;; Im Ziel-Modus: "Ist der Endpunkt der Foerderer?" JETZT abfragen (statt
;; erst danach in der Aufruferschleife) - der Ausgangs-Fussabdruck
;; (Vario_Bogen_ab_3 + Motorstation) wird nur reserviert, wenn tatsaechlich
;; sofort geschlossen wird (Antwort 1 oder 3).
(if ziel-modus
(progn
(princ (ssg-text "vfl-ist-endpunkt-frage"))
(princ (ssg-text "vfl-ja-nur-motorstation"))
(princ (ssg-text "vfl-nein-weiterbauen"))
(princ (ssg-text "vfl-ja-motorstation-kettenende"))
(setq ist-ende-antwort (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"))
(cond
((= ist-ende-antwort "1")
(setq dL (max 100.0 (- dL (car (get-bogen-mass bogen-ab 3)) 500.0
(* (if gf2-laenge gf2-laenge 0.0)
(cos (* 3.0 (/ pi 180.0))))))))
((= ist-ende-antwort "3")
(setq dL (max 100.0 (- dL (car (get-bogen-mass bogen-ab 3)) 500.0))))
)
)
)
;; optionaler Separator VOR - in der horizontalen Ebene (0 Grad)
(if sep-vor (setq pt (vfl-sep-hz pt hz)))
;; horizontale Zwischenstrecke (0 Grad)
(princ (ssg-textf "vfl-horizontale-zwischenstrecke" (list (rtos dL 2 2))))
(setq pt (insert-inclined-scaled-block "Staustrecke_SP_1000_mm" pt dL 0 hz))
(vfl-acc-vf-seg "horizontal" 0 dL)
;; optionaler Separator NACH (jetzt tatsaechlich bauen)
(if sep-nach (setq pt (vfl-sep-hz pt hz)))
;; KEIN ab_3 mehr -> das Stueck ENDET FLACH (0 Grad)
(setq frame (make-frame-from-dir pt (hz-winkel->xu hz 0.0)))
(if ziel-modus (list frame ist-ende-antwort) frame)
)
;; ============================================================
;; 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).
(defun vfl-body-zerlegung (deltaL deltaH richtung /
save-ausdx save-ausdz save-feste
winkel-natuerlich wahl horizontal-info res)
(if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek))
;; Budget-Globals temporaer auf den Mid-Body-Fall 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 800.0)
(setq res
(cond
;; kuerzer als Motor(500)+Separator(300): kein Abschluss baubar
((< deltaL 800.0) (list nil nil nil nil))
;; praktisch flach: horizontales VF + GF2
((< deltaH 1.0)
(setq horizontal-info (berechne-horizontale-mitte deltaL deltaH "Ab"))
(if (and horizontal-info (caddr horizontal-info))
(list "VF" 0 (car horizontal-info) (cadr horizontal-info))
(list nil nil nil nil)))
;; steigend: nur gewinkeltes VF + GF2 moeglich
((= richtung "Auf")
(setq wahl (vfl-waehle-winkel (nth 3 (berechne-alle-winkel deltaL deltaH "Auf" 800.0))))
(if wahl (list "VF" (nth 0 wahl) (nth 1 wahl) (nth 2 wahl)) (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
;; ~3 Grad -> reine GF2 (kein Koerper, GF2 traegt Laenge + 3-Grad-Absenkung)
((<= (abs (- winkel-natuerlich 3.0)) *vfl-gf-winkel-toleranz*)
(list "GF" 3.0 nil nil))
;; steiler als 3 Grad -> Stufe 1: gewinkeltes VF + GF2 (GF2 variiert mit Winkel)
((> winkel-natuerlich 3.0)
(setq wahl (vfl-waehle-winkel (nth 3 (berechne-alle-winkel deltaL deltaH "Ab" 800.0))))
(if wahl (list "VF" (nth 0 wahl) (nth 1 wahl) (nth 2 wahl)) (list nil nil nil nil)))
;; flacher als 3 Grad -> Stufe 2: horizontales VF + GF2
(t
(setq horizontal-info (berechne-horizontale-mitte deltaL deltaH "Ab"))
(if (and horizontal-info (caddr horizontal-info))
(list "VF" 0 (car horizontal-info) (cadr horizontal-info))
(progn
(setq wahl (vfl-waehle-winkel (nth 3 (berechne-alle-winkel deltaL deltaH "Ab" 800.0))))
(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)
;; 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.
(defun vfl-body-abschluss (frame letzt-hz p-umlenk /
linie-mess dL hzn hn dH richtn rad3 dec typ w lgf lvf
gf2 cnt p0 tx ty
hoehe-vorschlaege hoehe-ab hoehe-auf hoehe-fallback)
;; Der Abschluss-Koerper (vfs-vf-koerper) und der Motor gehen von 3-Grad-Basis
;; aus - eine gerade laufende flache Zone hier mit ab_3 abschliessen.
(setq frame (vfl-nach-3grad frame))
(setq linie-mess (vfl-neue-linie-messen (car frame) letzt-hz))
(if (null linie-mess)
(progn (princ (ssg-text "vfl-abgebrochen-kein-ziel")) nil)
(progn
(setq dL (car linie-mess) hzn (cadr linie-mess))
;; Baubare Vorschlaege (Ab/Auf) statt der unveraenderten Kettenhoehe als
;; Default - sonst waere dH=0 ("Auf", da (>= 0.0 0.0)) und der Rest wird
;; von vfl-body-zerlegung sofort als "nicht baubar" abgelehnt (kleineres
;; Budget als vfl-vf-entscheidung: nur Motor+Separator, 800mm statt
;; 1300mm) - siehe vfl-body-hoehe-vorschlaege/vfl-body-machbar-p.
(setq hoehe-vorschlaege (vfl-body-hoehe-vorschlaege (caddr (car frame)) dL))
(setq hoehe-ab (nth 0 hoehe-vorschlaege) hoehe-auf (nth 1 hoehe-vorschlaege))
(setq hoehe-fallback (if hoehe-ab hoehe-ab (caddr (car frame))))
(vfl-hoehe-hinweis-falls-noetig hoehe-ab hoehe-auf)
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-ziel-hoehe-impl
(list (list hoehe-ab hoehe-auf (caddr (car frame))))))
(setq hn (vfl-in-real
(if (and hoehe-ab hoehe-auf)
(ssg-textf "vfl-prompt-hoehe-kettenende-2"
(list (rtos hoehe-ab 2 1) (rtos hoehe-auf 2 1)))
(ssg-textf "vfl-prompt-hoehe-kettenende" (list (rtos hoehe-fallback 2 1))))))
(if (null hn) (setq hn hoehe-fallback))
(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))
(setq typ (nth 0 dec) w (nth 1 dec) lgf (nth 2 dec) lvf (nth 3 dec))
(if (null typ)
(progn
(princ (ssg-text "vfl-fehler-rest-nicht-baubar"))
(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).
(if (and (= typ "VF") lvf (> lvf 1.0))
(progn
(setq frame (vfl-frame-3grad (vfs-vf-koerper (car frame) richtn w lvf hzn) hzn))
(vfl-acc-vf-seg richtn w lvf)
(setq cnt (1+ cnt))))
(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 576.0))) (cos rad3)) 800.0)))
(t 0.0)))
(if (and (> gf2 0.0) (< gf2 *vfl-gf-min-laenge*))
(progn
(princ (ssg-textf "vfl-hinweis-gf2-minimum"
(list (rtos gf2 2 0) (rtos *vfl-gf-min-laenge* 2 0))))
(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)
)
)
)
)
)
;; Rueckgabe: (frame anzahl-koerper ziel-ende)
(defun vfl-vf-einheit (frame hz1 richtung1 winkel1 L_GF1 L_VF1 gf-am-ausgang /
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
letzt-hz fertig ziel-ende ziel-gf2 res3 gf2-eff entry-start
res-h vor-antwort es-gewuenscht es-antwort save-eindx save-eindz
hoehe-vorschlaege hoehe-ab hoehe-auf hoehe-fallback)
;; 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.
(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 ---
(setq entry-start (car frame))
(setq frame (vfl-frame-3grad (vfs-vf-entry (car frame) L_GF1-bau hz1) hz1))
(if (> L_GF1-bau 0.1)
(vfl-acc-gf-seg L_GF1-bau (ssg-cfg-or "vario" "gefaelle_winkel" 3)))
(setq *vfl-acc-separator* (1+ *vfl-acc-separator*)) ; Einlauf-Separator (in vfs-vf-entry)
(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 100.0 (- L_VF1 (vfl-projiziere-distanz entry-start p-umlenk hz1)))
t L_GF2-bau))
(setq frame (car res-h) vor-antwort (cadr res-h))
)
(progn
(setq frame (vfl-frame-3grad (vfs-vf-koerper (car frame) richtung1 winkel1 L_VF1 hz1) hz1))
(vfl-acc-vf-seg richtung1 winkel1 L_VF1)
)
)
(setq vf-count 1 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)
(progn
(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"))
(setq antwort (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"))
)
)
(if (= antwort "1")
(setq fertig t)
(if (= antwort "3")
;; --- Option 3: Kettenende exakt am Zielpunkt (nur EIN Motor) ---
(progn
;; ES-Element hier gewuenscht? Falls nein, ein-dx/ein-dz waehrend der
;; Zerlegung (vfl-body-abschluss -> vfl-body-zerlegung -> berechne-
;; alle-winkel) temporaer nullen, damit KEIN ES-Fussabdruck reserviert
;; wird - die Kette endet dann direkt am Zielpunkt ohne Separator+ES.
(princ (ssg-text "vfl-es-setzen-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-es-nein-zielpunkt"))
(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 (not es-gewuenscht)
(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 letzt-hz p-umlenk))
(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"))
(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
;; 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 linie-mess
(progn
(setq dL (car linie-mess) hzn (cadr linie-mess))
(if (> dL 1.0)
(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 hoehe-fallback (if hoehe-ab hoehe-ab (caddr (car frame))))
(vfl-hoehe-hinweis-falls-noetig hoehe-ab hoehe-auf)
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-ziel-hoehe-impl
(list (list hoehe-ab hoehe-auf (caddr (car frame))))))
(setq hn (vfl-in-real
(if (and hoehe-ab hoehe-auf)
(ssg-textf "vfl-prompt-hoehe-endpunkt-2"
(list (rtos hoehe-ab 2 1) (rtos hoehe-auf 2 1)))
(ssg-textf "vfl-prompt-hoehe-endpunkt"
(list (rtos hoehe-fallback 2 1))))))
(if (null hn) (setq hn hoehe-fallback))
(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))
(if (and typ (= typ "VF"))
(progn
(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))
)
(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).
(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))
;; Auslauf-GF2: im Kettenende-Modus (Option 3) neu berechnet (ziel-gf2),
;; sonst aus der GF-Verteilungs-Frage (L_GF2-bau).
(setq gf2-eff (if ziel-ende ziel-gf2 L_GF2-bau))
(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)))
(list frame vf-count ziel-ende es-gewuenscht)
)
;; 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
;; 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.
(defun vfl-frage-es-seite ( / antwort es-seite)
(vfl-journal-mark "ES")
(if (vfl-wizard-aktiv)
(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"))))
(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"))
(vf-set-es-masse *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 300 0))
(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 300 0))
(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-ss ent 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)))
;; ATTDEFs nach gemeinsamem Strecken-Schema (Reihenfolge!)
(foreach def (ssg-strecke-attrib-defs typ-str)
(entmake
(list '(0 . "ATTDEF")
(cons 10 startpunkt)
(cons 11 startpunkt)
'(40 . 50.0)
(cons 1 (cadr def))
(cons 2 (car def))
(cons 3 (car def))
'(70 . 1)
'(72 . 0)
'(74 . 0)))
)
(setq vfl-ss (ssadd))
(setq ent (if lastEnt (entnext lastEnt) (entnext)))
(while ent
(ssadd ent vfl-ss)
(setq ent (entnext ent))
)
;; Block definieren und einfuegen mit garantiert weltparallelem BKS.
;; startpunkt ist ein Welt-Punkt; ssg-block-wrap-welt setzt das BKS
;; temporaer auf Welt (verhindert den 31.95mm-Z-Versatz bei abweichendem
;; BKS, siehe Kommentar dort) und stellt es danach wieder her.
(setq vfl-insert (ssg-block-wrap-welt vfl-bname startpunkt vfl-ss))
;; Werte (volle Liste; nicht vorhandene Tags ignoriert ssg-attrib-set-on)
(ssg-attrib-set-on vfl-insert
(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 "SEITE_ES" es-seite)
;; 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")))