fix: Lauf-Anfang als selbstbeschreibender Wert, Dialog-Hinweise lesbar

Die Frage "Lauf beginnen mit" (Modus 2) journalisiert jetzt den WERT
("mit-gf1"/"direkt"/"ohne") statt der Auswahl-Ziffer. Anlass war die
Umstellung der Optionsreihenfolge auf Regelfall zuerst: mit einer Ziffer
haette dieselbe "3" vor und nach der Umstellung Verschiedenes bedeutet,
eine bestehende Kette waere beim Doppelklick still mit einer ANDEREN
Anfangsvariante neu gebaut worden. Alte Ketten laufen ueber die Tabelle
*vfl-m2-anfang-alt* weiter; ein Wert, der weder Alt-Ziffer noch gueltiger
Wert ist, bricht den Aufbau ab und nennt Kette und Wert, statt eine
Variante zu raten.

Weiter aus der Modus-2-Arbeit:
- Laengenbudget je VF-Einheit (max. 25 m, max. 2 Vario-Kurven,
  *vfk-vf-kurven-max*), bei Ueberschreitung automatischer Schnitt
- nach einer VF-Geraden ist der Bogentyp waehlbar (GF-Bogen/Vario-Kurve)
- Segmentnummer steht im Kopf jedes Dialogs
- "Separator NACH" ist am letzten GF-Stueck vor einem ES gesperrt, der
  automatische Separator wird dort erklaert
- "Ohne Umlenkstation" ist mit AS-Element gesperrt
- Fuellerlaenge nur noch dann aus dem Rueckanker, wenn der VF-Lauf das
  Pfadende ist

Dialoge: Hinweis steht in Klammern hinter dem Ankreuzfeld statt darunter,
Lauf-Anfang-Optionen tragen die Nummern der Konsolenliste, die
Begruendungszeile ist breit genug und wird nicht mehr abgeschnitten.

14 neue Unit-Tests zur Wert-Normierung (durchlaufen die Alt-Tabelle).
Sprachdateien 1205/1205 synchron.

Offen: der Fuellerlaengen-Fix ist in BricsCAD noch nicht nachgemessen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 15:39:57 +02:00
parent 45cb98c5b4
commit 833a4b60a6
6 changed files with 532 additions and 91 deletions
+7
View File
@@ -303,6 +303,13 @@
;; Ueberschreiten, kein Abbruch. Aktiv referenziert.
(if (null *vfk-vf-laenge-max*) (setq *vfk-vf-laenge-max* 25000.0))
;; Hoechstzahl Vario-Kurven INNERHALB einer VF-Einheit (zwischen Umlenk- und
;; Motorstation) - Nutzer-Regel 2026-10-22. Zwei Kurven bedeuten hoechstens
;; drei VF-Geraden im selben Lauf; danach muss die Einheit mit einer
;; Motorstation schliessen und eine neue mit Umlenkstation beginnen.
;; Geprueft in vfl-m2-lauf-grenzen (vf_linienzug.lsp, Modus 2).
(if (null *vfk-vf-kurven-max*) (setq *vfk-vf-kurven-max* 2))
(setq *vfk-konstanten-geladen* t)
(princ)
(princ)
+393 -44
View File
@@ -425,6 +425,10 @@
;; 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")))
;; Grenzen einer VF-Einheit (Modus 2) - Fallback wie oben, falls
;; vf_konstanten.lsp nicht geladen wurde.
(if (null *vfk-vf-laenge-max*) (setq *vfk-vf-laenge-max* 25000.0))
(if (null *vfk-vf-kurven-max*) (setq *vfk-vf-kurven-max* 2))
(setq *vfl-glied-schema*
(list
(cons "AS"
@@ -712,13 +716,36 @@
(setq i (1+ i)))
ergebnis)
;; Segmentkopf fuer die GENERISCHEN Wizard-Dialoge.
;;
;; Die Segment-Gruppendialoge (vflw_seg_*) tragen "Segment i/n: ..." schon in
;; ihrer Kopfzeile. Sobald eine Frage aber auf einen generischen Dialog
;; zurueckfaellt - weil der Gruppendialog uebersprungen wurde oder weil es
;; eine Anschlussfrage ist (Separator vor/nach, Vario-Variante) -, stand dort
;; nur die Frage ohne Bezug: man sah nicht mehr, zu welchem Segment sie
;; gehoert, waehrend die Konsole es die ganze Zeit mitschreibt (Nutzer-Befund
;; 2026-10-22). *vflw-seg-kopf* wird von der Segment-Schleife gesetzt und
;; hier vorangestellt; ausserhalb der Schleife ist es nil und alles bleibt
;; wie bisher.
;; Trenner bewusst ": " und KEIN Zeilenumbruch: der Radio-Dialog baut seine
;; Kopfzeile als DCL-"label", und ein Umbruch im Label-String macht die
;; erzeugte DCL-Datei ungueltig. Die text-Tiles der anderen Dialoge sind
;; ohnehin einzeilig. *vflw-seg-kopf* traegt darum die KURZE Form
;; ("Segment 2/5"), nicht die volle Segmentbeschreibung - sonst wird die
;; Kopfzeile zu lang und schneidet ab.
(if (null *vflw-seg-kopf*) (setq *vflw-seg-kopf* nil))
(defun vflw-kopf-text (prompt)
(if (and (boundp '*vflw-seg-kopf*) *vflw-seg-kopf*)
(strcat *vflw-seg-kopf* ": " (vflw-clean prompt))
(vflw-clean prompt)))
(defun vflw-zahl-impl (prompt / dat dcl-pfad dlg-wert ergebnis vorgabe)
(setq dcl-pfad (vflw-dcl-pfad))
(setq dat (load_dialog dcl-pfad))
(if (not (new_dialog "vflw_zahl" dat))
(progn (if (and dat (>= dat 0)) (unload_dialog dat)) nil)
(progn
(set_tile "kopf" (vflw-clean prompt))
(set_tile "kopf" (vflw-kopf-text prompt))
;; Vorgabe ins Feld schreiben - "Ok Weiter" ohne Aenderung liefert damit
;; denselben Wert wie frueher das leere Feld (dort griff der Default
;; beim Aufrufer).
@@ -749,7 +776,7 @@
(if (not (new_dialog "vflw_wahl" dat))
(progn (if (and dat (>= dat 0)) (unload_dialog dat)) "")
(progn
(set_tile "kopf" (vflw-clean prompt))
(set_tile "kopf" (vflw-kopf-text prompt))
(start_list "opts")
(foreach opt optionen (add_list (vflw-clean opt)))
(end_list)
@@ -795,7 +822,7 @@
(setq zeilen
(append
(list "vflw_radio_dyn : dialog {"
(strcat " label = \"" (ssg-dcl-label-escape (vflw-clean prompt)) "\";"))
(strcat " label = \"" (ssg-dcl-label-escape (vflw-kopf-text prompt)) "\";"))
(vfs-dcl-radio-gruppe-zeilen "vflwopt" "" sauber)
(list " ok_cancel;" "}")))
(setq dcl-pfad (ssg-dcl-temp-schreiben "vflwradio" zeilen))
@@ -1313,7 +1340,12 @@
(set_tile "kopf" (vflw-clean (ssg-text "vfl-sep-gf-header")))
(set_tile "sepvor" "0")
(set_tile "sepnach" "0")
(if es-folgt-sep (mode_tile "sepnach" 1))
;; Gesperrte Kachel ohne Begruendung verwirrt - daher die Hinweiszeile
;; (Nutzer-Wunsch 2026-10-23): sie sagt, dass der Separator zwischen GF
;; und ES automatisch gesetzt wird.
(if es-folgt-sep
(progn (mode_tile "sepnach" 1)
(set_tile "sephinweis" (vflw-clean (ssg-text "vfl-sep-gf-es-automatisch")))))
(action_tile "accept"
"(setq gsepvor (get_tile \"sepvor\")) (setq gsepnach (get_tile \"sepnach\")) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
@@ -1577,20 +1609,36 @@
(princ))
;; --- Modus 2: Lauf-Anfang (NUR wenn der VF-Lauf der Pfadanfang ist) ---
;; "1"=ohne Umlenkstation (offener Anfang) / "2"=Umlenkstation direkt (kein
;; GF1/Separator) / "3"=Umlenkstation mit GF1+Separator (heutiges Standard-
;; verhalten). Plain ok_cancel, analog vflw-m2-lauf-abschluss-impl.
(defun vflw-m2-lauf-anfang-impl (kopf-txt / dat ergebnis gwahl)
;; Legt den selbstbeschreibenden WERT ab, nicht die Auswahl-Ziffer:
;; "mit-gf1" = Umlenkstation mit GF1+Separator (Regelfall, Option 1),
;; "direkt" = Umlenkstation direkt ohne GF1/Separator (Option 2),
;; "ohne" = ohne Umlenkstation, offener Anfang (Option 3).
;; Begruendung und Alt-Zuordnung: siehe vfl-m2-anfang-norm.
;; Plain ok_cancel, analog vflw-m2-lauf-abschluss-impl.
;; as-gesetzt: T, wenn der Nutzer vorher ein AS-Element gewaehlt hat. Dann
;; ist "Ohne Umlenkstation" gesperrt (Nutzer-Vorgabe 2026-10-23) - diese
;; Option setzt intern as-vorhanden wieder auf nil und wuerde das AS also
;; stillschweigend verwerfen; ausserdem braucht ein Lauf mit AS zwingend eine
;; Umlenkstation. Ohne AS bleibt sie waehlbar.
(defun vflw-m2-lauf-anfang-impl (kopf-txt as-gesetzt / dat ergebnis gwahl)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_m2_lauf_anfang" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean kopf-txt))
(set_tile "anfang_umlenk_gf1" "1")
(if as-gesetzt
(progn (mode_tile "anfang_offen" 1)
;; Kurzform in Klammern (wie in der Konsolenliste unter der
;; gesperrten Option) - der volle Satz passte nicht in die
;; Zeile und wurde abgeschnitten. Als Messagebox-Text bleibt
;; er in vfl-m2-anfang-offen-gesperrt stehen.
(set_tile "anfanghinweis"
(vflw-clean (ssg-text "vfl-m2-anfang-offen-gesperrt-zeile")))))
(action_tile "accept"
"(setq gwahl (cond ((= (get_tile \"anfang_offen\") \"1\") \"1\")
((= (get_tile \"anfang_umlenk\") \"1\") \"2\")
(t \"3\")))
"(setq gwahl (cond ((= (get_tile \"anfang_offen\") \"1\") \"ohne\")
((= (get_tile \"anfang_umlenk\") \"1\") \"direkt\")
(t \"mit-gf1\")))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
@@ -1895,7 +1943,18 @@
;; Original-Reihenfolge/-Journal im Loop: vfl-menu (Typ), dann bei GF
;; vfl-in-real (Neigung). Bei VF wird KEIN Neigungswert gefragt -> nur "2"
;; in die Pending-Queue. neigung-default: Vorbelegung Neigungsfeld (Grad).
(defun vflw-seg-linie-m2-impl (kopf-txt neigung-default / dat ergebnis gtyp gwert gsepvor gsepnach)
;; es-folgt-sep: T, wenn direkt hinter diesem Segment das ES-Element sitzt.
;; Dann ist "Separator NACH" gesperrt - vfl-insert-es-element bringt seinen
;; Separator immer selbst mit, ein zweiter stuende unmittelbar daneben
;; (Nutzer-Befund 2026-10-23, im Bau-Log zweimal
;; "Staustrecke_Separator_SP_300_mm" in Folge). Die Sperre gehoert GENAU
;; hierher: in Modus 2 sitzen die Separator-Kacheln in DIESEM Segment-Dialog,
;; nicht im separaten vflw_sep_gf von Modus 1. Ein zusaetzlicher Dialog waere
;; doppelt UND wuerde zwei ueberzaehlige Antworten in die Pending-Queue legen.
;; Die Antwort selbst bleibt erhalten (gesperrte Kachel = "Nein"), das
;; Journal-Format aendert sich also nicht.
(defun vflw-seg-linie-m2-impl (kopf-txt neigung-default es-folgt-sep /
dat ergebnis gtyp gwert gsepvor gsepnach)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_seg_linie_m2" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
@@ -1903,9 +1962,15 @@
(set_tile "kopf" (vflw-clean kopf-txt))
(set_tile "typ_gf" "1")
(set_tile "wert" (rtos (float neigung-default) 2 1))
;; Neigungsfeld + Separator-Toggles nur bei GF aktiv.
(if es-folgt-sep
(progn (mode_tile "sep_nach" 1)
(set_tile "sephinweis" (vflw-clean (ssg-text "vfl-sep-gf-es-automatisch")))))
;; Neigungsfeld + Separator-Toggles nur bei GF aktiv - "NACH" bleibt
;; dabei gesperrt, wenn das ES folgt (sonst machte der Wechsel
;; GF/VF/GF die Sperre wieder zunichte).
(action_tile "typ_gf"
"(mode_tile \"wert\" 0) (mode_tile \"sep_vor\" 0) (mode_tile \"sep_nach\" 0)")
(strcat "(mode_tile \"wert\" 0) (mode_tile \"sep_vor\" 0) (mode_tile \"sep_nach\" "
(if es-folgt-sep "1" "0") ")"))
(action_tile "typ_vf"
"(mode_tile \"wert\" 1) (mode_tile \"sep_vor\" 1) (mode_tile \"sep_nach\" 1)")
(action_tile "accept"
@@ -2299,8 +2364,17 @@
(if (and v (member kind '("STR" "INT")))
(progn
(setq label nil)
(if (and *vflw-menu-optionen*
(setq idx (if (= kind "INT") v (atoi v)))
;; idx MUSS vor dem Vergleich eine Zahl sein. Bei kind "INT" wird der
;; Wert unveraendert durchgereicht - steht dort (z.B. nach einem
;; Queue-Versatz oder aus einem Alt-Journal) ein String oder eine
;; Liste, brach (>= idx 1) frueher mit "bad argument type" ab und riss
;; den ganzen Bau mit (realer Absturz 2026-10-23 in der Winkelwahl).
;; Eine reine ECHO-Funktion darf das nie: im Zweifel wird der Wert
;; eben roh ausgegeben statt als Options-Text.
(setq idx (cond ((= kind "INT") (if (numberp v) v nil))
((= (type v) 'STR) (atoi v))
(t nil)))
(if (and *vflw-menu-optionen* idx (numberp idx)
(>= idx 1) (<= idx (length *vflw-menu-optionen*)))
(setq label (vflw-clean (nth (1- idx) *vflw-menu-optionen*))))
(vfl-echo-wahl (if label label (if (= kind "STR") v (vl-princ-to-string v))))
@@ -2870,8 +2944,20 @@
(defun vfl-journal-replay-start (forward-journal)
(setq *vfl-replay-queue* forward-journal *vfl-journal* '()
*vfl-seg-glied-letzte* 0)
;; Herkunft des Journals zuruecksetzen - der Aufrufer traegt sie direkt
;; danach nach, wenn er einen Block kennt (vfl-replay-kette-setzen).
(setq *vfl-replay-kette* nil)
(if (boundp '*vflw-pending*) (setq *vflw-pending* nil)))
;; Ketten-Fundstelle fuer Meldungen waehrend eines Replays: Blockname der
;; gerade abgespielten Kette, "-" wenn unbekannt (Live-Bau). Die Aufrufer von
;; vfl-journal-replay-start setzen *vfl-replay-kette* direkt danach - den Namen
;; muessen sie VOR dem entdel des Altblocks gelesen haben, aus der Zeichnung
;; ist er danach nicht mehr zu holen. Rein diagnostisch, kein Bau-Pfad liest ihn.
(defun vfl-replay-kette-text ( / k)
(setq k (if (boundp '*vfl-replay-kette*) *vfl-replay-kette* nil))
(if (and k (= (type k) 'STR) (/= k "")) k "-"))
;; --- Glied-Zerlegung fuer den Editier-Dialog ---
;; Labels der Glieder (STEP-Marker) in Bau-Reihenfolge.
(defun vfl-journal-glieder (forward-journal / out)
@@ -5173,6 +5259,91 @@
(vf-set-es-masse *vfl-es-winkel* es-seite)
(if (vfl-frage-fahrtrichtung-p2 startpunkt) es-seite nil))
;; Bau-Grenzen EINER VF-Einheit pruefen (Nutzer-Regel 2026-10-22).
;;
;; Eine VF-Einheit ist alles zwischen Umlenkstation und Motorstation. Sie darf
;; - hoechstens max-kurven Vario-Kurven enthalten (Vorgabe 2, also maximal
;; drei VF-Geraden), und
;; - max-laenge nicht ueberschreiten (Vorgabe *vfk-vf-laenge-max* = 25 m).
;; Gemessen wird die Pfadlaenge der Segmente des Laufs - konservativ, weil
;; GF1/GF2 darin noch mitzaehlen, obwohl sie ausserhalb von Umlenk..Motor
;; liegen. Lieber etwas zu frueh warnen als eine nicht baubare Einheit.
;;
;; Reiner Listen-Kern ohne Zeichnung: plan ist die Klassifizierungs-Liste
;; ("Linie"/"Bogen" ...), vf-start/vf-ende grenzen den Lauf ein.
;;
;; Rueckgabe: (laenge kurven teil-idx)
;; laenge - aufsummierte Pfadlaenge des Laufs
;; kurven - Zahl der Vario-Kurven darin
;; teil-idx - nil, wenn alles in den Grenzen liegt; sonst der Index des
;; BOGENS, an dem geteilt werden sollte (dort einen GF-Bogen
;; setzen, damit zwei Einheiten entstehen). Gibt es keinen
;; geeigneten Bogen, ist teil-idx -1 ("zu lang, nicht teilbar").
(defun vfl-m2-lauf-grenzen (plan vf-start vf-ende max-laenge max-kurven /
i seg summe kurven teil-idx)
(setq i vf-start summe 0.0 kurven 0 teil-idx nil)
(while (<= i vf-ende)
(setq seg (nth i plan))
(if (= (car seg) "Bogen")
(progn
;; An DIESEM Bogen teilen, wenn die Einheit davor schon voll ist -
;; entweder weil sie die erlaubten Kurven schon hat oder weil die
;; Laenge bis hierher die Grenze reisst.
(if (and (null teil-idx)
(or (>= kurven max-kurven) (> summe max-laenge)))
(setq teil-idx i))
(setq kurven (1+ kurven))))
(setq summe (+ summe (caddr seg)))
(setq i (1+ i)))
;; Grenze gerissen, aber kein Bogen zum Teilen gefunden (reine Gerade oder
;; alle Boegen liegen zu frueh): nicht teilbar.
(if (and (null teil-idx) (or (> summe max-laenge) (> kurven max-kurven)))
(setq teil-idx -1))
(list summe kurven teil-idx))
;; Formatmarke der Modus-2-Plan-Phase schreiben bzw. beim Replay erkennen.
;; Rueckgabe: T = neues Format (Bogentyp wird auch nach einer VF-Geraden
;; gefragt), nil = Alt-Journal ohne diese Antwort.
;;
;; Live wird die Marke IMMER geschrieben (ohne Frage an den Nutzer, der
;; livefn liefert sie einfach). Beim Replay wird sie NUR konsumiert, wenn sie
;; auch dasteht - sonst bliebe die Queue aus dem Takt. Dasselbe Muster wie
;; die Richtungswahl in Modus 1, nur dass hier gar keine Nutzerfrage dahinter
;; steckt: es geht allein um die Unterscheidbarkeit der Journal-Formate.
(defun vfl-m2-formatmarke ( / )
(if (and *vfl-replay-queue* (not (equal (vfl-replay-peek-wert) "m2v2")))
nil
(progn (vfl-in-value "STR" (function (lambda () "m2v2"))) t)))
;; --- Modus 2: Lauf-Anfang, Antwortwerte -------------------------------------
;; Die Antwort wird seit 2026-10-23 als selbstbeschreibender WERT journalisiert
;; ("mit-gf1"/"direkt"/"ohne"), nicht mehr als Auswahl-Ziffer. Anlass war die
;; Umstellung der Dialogreihenfolge (Regelfall zuerst): mit einer Ziffer haette
;; dieselbe "3" vor und nach der Umstellung Verschiedenes bedeutet, eine alte
;; Kette waere beim Doppelklick still mit einer ANDEREN Anfangsvariante neu
;; gebaut worden. Mit eigenem Wertevorrat ist jedes kuenftige Umsortieren
;; folgenlos (CLAUDE.md: "Werte selbstbeschreibend fuehren, nicht als
;; positionsabhaengige Indizes/Flags").
;; Reihenfolge = Reihenfolge der Dialog-/Konsolenoptionen 1..3.
(setq *vfl-m2-anfang-werte* '("mit-gf1" "direkt" "ohne"))
;; Alt -> Neu (nur fuer Bestandsketten): die Ziffern der ALTEN Optionsfolge
;; 1 = Ohne Umlenkstation, 2 = Umlenkstation direkt, 3 = Umlenkstation mit GF1.
;; Bewusst als Tabelle und nicht als cond-Kette: die Zuordnung ist aus dem
;; heutigen Code nicht mehr ableitbar, und ein Test kann sie direkt durchlaufen.
(setq *vfl-m2-anfang-alt* '(("1" . "ohne") ("2" . "direkt") ("3" . "mit-gf1")))
;; Journal-/Antwortwert -> kanonischer Wert, nil bei unbekanntem Wert.
;; nil heisst fuer den Aufrufer: ABBRECHEN. Ein Rueckfall auf eine geratene
;; Anfangsvariante waere genau der lautlose Fehlaufbau, den die Wertumstellung
;; verhindern soll - lieber ein harter Abbruch mit Fundstelle.
(defun vfl-m2-anfang-norm (wert / tr)
(cond
((not (and wert (= (type wert) 'STR))) nil)
((member wert *vfl-m2-anfang-werte*) wert)
((setq tr (assoc wert *vfl-m2-anfang-alt*)) (cdr tr))
(t nil)))
;; Punkt um eine Strecke ENTGEGEN der Fahrtrichtung zurueckversetzen.
;; laenge-planar = horizontale (Grundriss-)Laenge, winkel = Gefaelle in Grad
;; (positiv = fallend). Vorwaerts gilt
@@ -9822,6 +9993,7 @@
(setq *ssg-ils-dim* ziel-dim) ; Zieldim erzwingen
(entdel ent) ; alten Block weg
(vfl-journal-replay-start journal) ; volle Queue, KEIN Truncate
(setq *vfl-replay-kette* bname) ; Fundstelle fuer Replay-Meldungen
(setq ok (vl-catch-all-apply
(cond ((= marker "linienzug2") 'vf-linienzug-modus2)
((= marker "linienzug3") 'vf-linienzug-modus3)
@@ -9937,15 +10109,19 @@
;; 'vf-linienzug-modus3 fuer "linienzug3" (Segment-Essenz per SEGS - unabhaengig
;; von den Original-Objekten). Beide Modi haben eine flache Klassifizierungs-
;; Schleife ohne editierbare Einzelglieder, daher derselbe volle Reset.
(defun vfl-edit-ent-voll (ent journal bau-fn / dim)
(defun vfl-edit-ent-voll (ent journal bau-fn / dim bname)
;; Dimension des Bestandsblocks VOR dem entdel merken und beim Neubau als
;; Override erzwingen (dimensionstreuer Doppelklick-Neubau, sonst globale
;; DXFM_DIM). Danach Override zuruecksetzen.
(setq dim (ssg-dim-xdata-lesen ent))
;; Blockname ebenfalls VOR dem entdel sichern - danach ist er weg, eine
;; Meldung aus dem Replay koennte die Kette sonst nicht mehr benennen.
(setq bname (cdr (assoc 2 (entget ent))))
(entdel ent)
(princ (ssg-text "vfl-edit2-reset-info"))
(if dim (setq *ssg-ils-dim* dim))
(vfl-journal-replay-start journal)
(setq *vfl-replay-kette* bname)
;; DIREKTER Aufruf, KEIN vl-catch-all-apply - identische Begruendung wie im
;; Sektions-Zweig von vfl-edit-ent (siehe dortiger Kommentar): der alte Block
;; ist per entdel schon weg, bei einem Abbruch muss der *error*-Handler der
@@ -11080,11 +11256,13 @@
hoehe-bis soll-ende ist-ende old-error vfl-ins
vf-run-member carrier-idx kv-variante seg-hz letzt-koerper-hz
vf-first-line vf-last-line climber-span mid-hor
m2-format-neu grenz-erg letzte-linie es-folgt-sep
uebergang-fp
br-lvf this-lvf
nach-kurve nach-vf sep-vor-gf sep-nach-gf
n-climb n-hor target-climb winkel-list
fdz fdx wslope lvf planar-len ang-diff best-diff w
climb-thresh longest-idx climbers nonclimber-len
longest-idx climbers nonclimber-len
nonclimber-cnt nkurve kurve-chords run-span
hor-koerper-len hor-pairs flat-p flat-zone-erste fill-len wahl3
rad3v feste-vf dH-adj dir-x dir-y fill-D end-hz
@@ -11093,7 +11271,8 @@
gf-total br-gf-mode br-gf2-exp filler-A-len filler-a-done
as-vorhanden es-vorhanden dbg-an dbg-old-error
m2-lauf-antwort m2-offenes-ende es-frueh-abgelehnt
m2-front-antwort m2-offener-anfang m2-kein-gf1-vorne entry-fixed)
m2-front-roh m2-front-antwort m2-offener-anfang
m2-kein-gf1-vorne entry-fixed)
(princ "\n\n=========================================")
(princ (ssg-text "vfl-m3-titel"))
(princ "\n=========================================")
@@ -11194,7 +11373,31 @@
;; --- 5. Klassifizierung (Phase A: nur speichern, nichts bauen) ---
;; Plan-Eintrag Gerade: ("Linie" hz laenge klass winkel) klass=GF/VF
;; Plan-Eintrag Bogen : ("Bogen" hz chord bwinkel bseite klass)
;;
;; FORMATMARKE der Plan-Phase. Seit 2026-10-22 wird der Typ eines Bogens
;; AUCH nach einer VF-Geraden gefragt (vorher Vario-Kurve erzwungen, ohne
;; Journal-Eintrag). Aeltere "linienzug2"-Ketten haben diese Antwort also
;; nicht, und am WERT allein ist das nicht zu erkennen: Bogentyp und
;; Vario-Variante benutzen beide "1"/"2". Darum eine eigene, selbst-
;; beschreibende Marke ("m2v2") vor der Schleife - sie steht im Journal nur
;; bei neu gebauten Ketten und wird beim Replay nur dann konsumiert, wenn
;; sie wirklich da ist. Alles Alte laeuft unveraendert im Zwangs-Modus.
(setq m2-format-neu (vfl-m2-formatmarke))
(setq n (length segmente) i 0 plan '())
;; Index der LETZTEN Geraden im Pfad. Nur dort schliesst das ES-Element
;; direkt an - und das bringt seinen Separator IMMER selbst mit
;; (vfl-insert-es-element). Die Frage "Separator NACH dieser
;; Gefaellestrecke" wird dort darum gesperrt; ohne das standen zwei
;; Separatoren unmittelbar hintereinander vor dem ES (Nutzer-Befund
;; 2026-10-23, im Bau-Log zweimal "Staustrecke_Separator_SP_300_mm" in
;; Folge). Gesperrt wird im Segment-Dialog selbst (vflw-seg-linie-m2-impl,
;; Parameter es-folgt-sep) - Modus 1 macht dasselbe in seinem eigenen
;; Dialog vflw_sep_gf.
(setq letzte-linie -1)
(while (< i n)
(if (= (car (nth i segmente)) "Linie") (setq letzte-linie i))
(setq i (1+ i)))
(setq i 0)
(while (< i n)
(setq seg (nth i segmente) typ (car seg) hz (cadr seg) laenge (caddr seg))
;; seg-ent: KEINE Live-Entity mehr - Modus 2 baut jetzt aus der Segment-
@@ -11204,21 +11407,33 @@
;; Original-Objekten, ein Highlight haette beim Doppelklick-Neubau ohnehin
;; keine Entity mehr.
(setq seg-ent nil)
;; Segmentkopf fuer ALLE Dialoge dieses Segments setzen - auch fuer die,
;; die auf einen generischen Dialog zurueckfallen (Vario-Variante,
;; Separator vor/nach). Ohne ihn stand dort nur die Frage, ohne Bezug zum
;; Segment (Nutzer-Befund 2026-10-22). Siehe vflw-kopf-text.
(setq *vflw-seg-kopf* (strcat "Segment " (itoa (1+ i)) "/" (itoa n)))
;; Schliesst an DIESES Segment direkt das ES-Element an? Dann bringt es
;; seinen Separator selbst mit und "Separator NACH" muss gesperrt sein
;; (siehe letzte-linie oben). Wird sowohl dem Segment-Dialog als auch dem
;; Konsolenweg unten uebergeben.
(setq es-folgt-sep (and es-vorhanden (= i letzte-linie)))
;; Wizard: alle Fragen dieses Segments in EINEM Dialog erfassen (die
;; unveraenderten vfl-menu/vfl-in-*-Aufrufe unten konsumieren die Antworten
;; aus *vflw-pending*). Der nach-kurve-Fall stellt keine Frage (auto-VF) ->
;; kein Dialog. Ebenso nach-vf bei einem Bogen: Vario-Kurve ist dann
;; erzwungen (eine VF-Einheit kann nicht in einen GF-Bogen auslaufen).
;; kein Dialog. Beim Bogen NACH einer VF-Geraden gibt es seit 2026-10-22
;; wieder eine Typ-Frage (siehe unten) - der Dialog kommt also auch dort;
;; nur beim Nachbau eines Alt-Journals bleibt er aus, weil dessen Antwort
;; an der Stelle fehlt.
(if (and (vfl-wizard-aktiv)
(not (and (= typ "Linie") nach-kurve))
(not (and (= typ "Bogen") nach-vf)))
(not (and (= typ "Bogen") nach-vf (not m2-format-neu))))
(progn
(vfl-seg-highlight seg-ent T)
(if (= typ "Linie")
(vflw-seg-linie-m2-impl
(vfl-seg-kopf (1+ i) n
(strcat "Gerade L=" (rtos laenge 2 0) " mm hz=" (rtos hz 2 1)))
*vfk-gefaelle-winkel*)
*vfk-gefaelle-winkel* es-folgt-sep)
(vflw-seg-bogen-impl
(vfl-seg-kopf (1+ i) n
(strcat "Eck/Bogen " (itoa (nth 3 seg)) " Grad " (nth 4 seg)))
@@ -11253,6 +11468,12 @@
;; Planlaenge bleibt hier UNVERAENDERT (reale Pfadlaenge, fuer
;; die Anker-Vorschau noetig); die Trennung in GF-Koerper+
;; Separator passiert erst beim Bauen (Phase B).
;; Hinweis fuer den Konsolenweg; im Wizard sperrt der
;; Segment-Dialog die Kachel "Separator NACH" selbst (siehe
;; vflw-seg-linie-m2-impl, Parameter es-folgt-sep) - ein
;; ZUSAETZLICHER Dialog waere hier falsch, er wuerde zwei
;; ueberzaehlige Antworten in die Pending-Queue legen.
(if es-folgt-sep (princ (ssg-text "vfl-sep-gf-es-automatisch")))
(princ (ssg-text "vfl-sep-vor-gf-frage"))
(setq sep-vor-gf (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2
@@ -11261,18 +11482,38 @@
(setq sep-nach-gf (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2
"vfl-sep-nach-gf-frage") "1"))
;; Sicherheitsnetz fuer den reinen Konsolenpfad (dort gibt es
;; kein Ausgrauen): Antwort verwerfen und melden - wie Modus 1.
(if (and sep-nach-gf es-folgt-sep)
(progn (vfl-meldung (ssg-text "vfl-sep-nach-durch-es-ueberschrieben"))
(setq sep-nach-gf nil)))
(setq plan (cons (list "Linie" hz laenge "GF" winkel sep-vor-gf sep-nach-gf) plan))
(setq nach-vf nil))))))
((= typ "Bogen")
(setq bwinkel (nth 3 seg) bseite (nth 4 seg))
(princ (ssg-textf "vfl-m3-seg-bogen"
(list (itoa (1+ i)) (itoa n) (itoa bwinkel) bseite)))
(if nach-vf
(setq antwort "2") ; nach VF-Gerade: Vario-Kurve erzwungen, keine Typ-Frage
;; Nach einer VF-Geraden wurde der Bogen frueher ZWINGEND zur
;; Vario-Kurve ("eine VF-Einheit kann nicht in einen GF-Bogen
;; auslaufen"). Das stimmt nicht: die VF-Einheit endet mit der
;; Motorstation, danach ist die Kette wieder auf der 3-Grad-Basis, wo
;; ein GF-Bogen das normale Element ist - Modus 1 erlaubt genau das
;; (Nutzer-Korrektur 2026-10-22). Gefragt wird daher immer; nach einer
;; VF-Geraden ist die Vario-Kurve nur noch die VORAUSWAHL.
;; Ein GF-Bogen beendet den Lauf; entstuende dadurch ein ZWEITER
;; VF-Lauf, faengt das die bestehende "genau ein Lauf"-Pruefung ab.
;;
;; ALT-JOURNAL: dort fehlt die Antwort an dieser Stelle (die Frage gab
;; es nicht). Erkannt wird das an der Formatmarke am Anfang der
;; Plan-Phase (m2-format-neu) - am WERT der naechsten Antwort ginge es
;; nicht, denn Typ und Variante benutzen beide "1"/"2".
(if (and nach-vf (not m2-format-neu))
(setq antwort "2") ; Alt-Journal: Vario-Kurve war erzwungen
(progn
(princ (ssg-text "vfl-m3-typ-bogen"))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list "GF-Bogen" "Vario-Kurve") 1
(list "GF-Bogen" "Vario-Kurve")
(if nach-vf 2 1)
"vfl-m3-typ-bogen"))))
(setq nach-vf nil)
(if (= antwort "2")
@@ -11288,6 +11529,9 @@
(setq plan (cons (list "Bogen" hz laenge bwinkel bseite "GF-Bogen" nil) plan))
(setq nach-kurve nil)))))
(setq i (1+ i)))
;; Segmentkopf wieder abschalten - ab hier sind die Fragen nicht mehr einem
;; einzelnen Pfad-Segment zugeordnet (Lauf-Anfang/-Ende, Winkelwahl).
(setq *vflw-seg-kopf* nil)
(setq plan (reverse plan))
;; --- 6. VF-Lauf finden: VF-Geraden UND Vario-Kurven bilden EINEN Lauf ---
@@ -11316,6 +11560,34 @@
;; headless nur princ - blockiert also einen Batch-Lauf nicht).
(vfl-meldung (ssg-textf "vfl-m3-vf-lauf-alert" (list (itoa vf-count))))
(exit)))
;; --- Bau-Grenzen der VF-Einheit (Nutzer-Regel 2026-10-22) ---
;; Zwischen Umlenkstation und Motorstation: hoechstens 2 Vario-Kurven (also
;; maximal drei VF-Geraden) und hoechstens *vfk-vf-laenge-max* (25 m).
;; Bisher war die Laenge nur ein HINWEIS nach dem Solver - die Kette wurde
;; trotzdem gebaut, obwohl sie so nicht herstellbar ist. Jetzt wird vor dem
;; Bau abgebrochen, und zwar MIT der Stelle, an der zu teilen ist: den dort
;; genannten Bogen als GF-Bogen klassifizieren, dann entstehen zwei
;; Einheiten (die automatische Teilung kommt mit der Mehr-Lauf-Stufe M3b,
;; siehe vfl-m3-vf-lauf-m3b).
(setq grenz-erg (vfl-m2-lauf-grenzen plan vf-start vf-ende
*vfk-vf-laenge-max* *vfk-vf-kurven-max*))
(if (nth 2 grenz-erg)
(progn
(if (< (nth 2 grenz-erg) 0)
(vfl-meldung (ssg-textf "vfl-m2-einheit-grenze-unteilbar"
(list (rtos (/ (nth 0 grenz-erg) 1000.0) 2 1)
(itoa (nth 1 grenz-erg))
(rtos (/ *vfk-vf-laenge-max* 1000.0) 2 1)
(itoa (fix *vfk-vf-kurven-max*)))))
(vfl-meldung (ssg-textf "vfl-m2-einheit-grenze"
(list (rtos (/ (nth 0 grenz-erg) 1000.0) 2 1)
(itoa (nth 1 grenz-erg))
(rtos (/ *vfk-vf-laenge-max* 1000.0) 2 1)
(itoa (fix *vfk-vf-kurven-max*))
(itoa (1+ (nth 2 grenz-erg)))))))
(exit)))
;; Traeger-Stueck = laengste VF-Gerade im Lauf (traegt die ganze Hoehe).
(setq carrier-idx -1 i vf-start)
(while (<= i vf-ende)
@@ -11387,22 +11659,44 @@
(setq m2-offener-anfang nil m2-kein-gf1-vorne nil)
(if (= vf-start 0)
(progn
;; Reihenfolge UND Nummerierung wie im Dialog: Regelfall zuerst,
;; Sonderfall zuletzt (Nutzer-Wunsch 2026-10-23). Die Ziffer ist reine
;; Anzeige - journalisiert wird der WERT (vfl-menu-strwert), ein
;; Umsortieren beruehrt bestehende Ketten darum nicht.
(princ (ssg-text "vfl-m3-lauf-anfang-header"))
(princ (ssg-text "vfl-m3-lauf-anfang-opt-offen"))
(princ (ssg-text "vfl-m3-lauf-anfang-opt-umlenk"))
(princ (ssg-text "vfl-m3-lauf-anfang-opt-umlenk-gf1"))
(princ (ssg-text "vfl-m3-lauf-anfang-opt-umlenk"))
(princ (ssg-text "vfl-m3-lauf-anfang-opt-offen"))
;; Begruendung direkt unter der gesperrten Option.
(if as-vorhanden (princ (ssg-text "vfl-m2-anfang-offen-gesperrt-zeile")))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-m2-lauf-anfang-impl (list (ssg-text "vfl-m3-lauf-anfang-header"))))
(setq m2-front-antwort (vfl-menu (ssg-text "vfl-prompt-wahl-1-3-def3")
(list (ssg-text "vfl-m3-lauf-anfang-opt-offen")
(ssg-text "vfl-m3-lauf-anfang-opt-umlenk")
(ssg-text "vfl-m3-lauf-anfang-opt-umlenk-gf1")) 3
"vfl-m3-lauf-anfang-header"))
(vl-catch-all-apply 'vflw-m2-lauf-anfang-impl
(list (ssg-text "vfl-m3-lauf-anfang-header") as-vorhanden)))
(setq m2-front-roh
(vfl-menu-strwert (ssg-text "vfl-prompt-wahl-1-3-def1")
(list (ssg-text "vfl-m3-lauf-anfang-opt-umlenk-gf1")
(ssg-text "vfl-m3-lauf-anfang-opt-umlenk")
(ssg-text "vfl-m3-lauf-anfang-opt-offen"))
*vfl-m2-anfang-werte* 1
"vfl-m3-lauf-anfang-header"))
(setq m2-front-antwort (vfl-m2-anfang-norm m2-front-roh))
(cond
((= m2-front-antwort "1")
;; Unbekannter Wert (weder Alt-Ziffer noch gueltiger Wert): abbrechen
;; statt zu raten - mit Kette und Wert, damit die Fundstelle bleibt.
((null m2-front-antwort)
(vfl-meldung (ssg-textf "vfl-m2-anfang-wert-unbekannt"
(list (vfl-replay-kette-text)
(vl-princ-to-string m2-front-roh))))
(exit))
;; Sicherheitsnetz fuer den Konsolenweg (dort gibt es kein
;; Ausgrauen): "Ohne Umlenkstation" mit gesetztem AS wuerde das
;; AS-Element verwerfen - abweisen und auf den Regelfall gehen.
((and (= m2-front-antwort "ohne") as-vorhanden)
(vfl-meldung (ssg-text "vfl-m2-anfang-offen-gesperrt")))
((= m2-front-antwort "ohne")
(setq as-vorhanden nil m2-offener-anfang t)
(princ (ssg-text "vfl-m3-lauf-anfang-offen-hinweis")))
((= m2-front-antwort "2") (setq m2-kein-gf1-vorne t)))))
((= m2-front-antwort "direkt") (setq m2-kein-gf1-vorne t)))))
;; --- Lauf-Abschluss waehlen, NUR wenn der VF-Lauf das Pfadende ist (kein
;; Back-Lauf danach) - sonst bleibt die Steuerung komplett unveraendert
@@ -11559,11 +11853,29 @@
;; faelschlich angenommenen Separator+ES-Abstiegs.
(if (or m2-offenes-ende (not es-vorhanden)) (setq z-junction (float end-hoehe)))
(setq dH-bruecke (- z-front z-junction))
;; Modell: EIN Vario mit Horizontal-Mitte. Nur VF-Geraden, die LANG GENUG sind
;; (Vertikalboegen brauchen viel Platz), tragen die Hoehe (Kletterer, gleicher
;; Winkel). Zu kurze VF-Geraden werden horizontal (nur Anschluss/Motor).
;; Mindestens die laengste VF-Gerade klettert immer.
(setq climb-thresh (if (boundp '*vfl-min-climber-laenge*) *vfl-min-climber-laenge* 3000.0))
;; Modell: EIN Vario mit Horizontal-Mitte. GENAU EINE VF-Gerade traegt die
;; Hoehe - die LAENGSTE des Laufs; alle anderen werden flache Fueller.
;;
;; REGEL (Nutzer-Vorgabe 2026-10-22): verbindet eine Vario-Kurve zwei
;; VF-Geraden, wird die KUERZERE flach gebaut und die LAENGERE klettert.
;; Bei mehr als zwei Geraden im Lauf ist das genau "nur die laengste
;; klettert".
;;
;; WARUM das keine Geschmacksfrage ist: der Solver unten budgetiert EIN
;; Kletterstueck mit EINEM Paar Vertikalboegen (auf_6/ab_6). Der Bau setzt
;; dieses Paar aber je Kletterer. Frueher klettete zusaetzlich jede
;; VF-Gerade ab *vfl-min-climber-laenge* (3000 mm) - bei zwei Kletterern
;; fehlten dadurch ein zweites Bogenpaar UND die Uebergaenge der zusaetzlich
;; entstehenden flachen Zone im Laengenbudget. Realer Fehler 2026-10-22:
;; zwei Kletterer (9700 + 4296 mm), das erste Segment wurde 1355.9 mm zu
;; lang gebaut, die ganze Kette ueberschoss um 3185 mm - bei korrekter
;; Hoehe (dZ nur -10.2 mm), also rein ein Laengen-, kein Hoehenproblem.
;; Mit genau einem Kletterer passt das Modell wieder zum Bau; die
;; verbleibende Laenge der flachen Zone faengt der letzte Fueller ab, der
;; ohnehin auf den Zielpunkt rechnet (vfl-zsolve-es-fueller).
;;
;; *vfl-min-climber-laenge* wird dadurch fuer die Auswahl nicht mehr
;; gebraucht (siehe doc/VarioFoerderer_Linienzug_Prinzipien.md, Abschnitt 14).
(setq longest-idx -1 i vf-start)
(while (<= i vf-ende)
(setq seg (nth i plan))
@@ -11577,7 +11889,9 @@
(setq seg (nth i plan))
(cond
((and (= (car seg) "Linie") (= (nth 3 seg) "VF"))
(if (or (= i longest-idx) (>= (caddr seg) climb-thresh))
;; NUR die laengste VF-Gerade klettert (siehe Regel oben) - jede
;; weitere wird flacher Fueller.
(if (= i longest-idx)
(setq climbers (cons i climbers) climber-span (+ climber-span (caddr seg)))
(setq nonclimber-len (+ nonclimber-len (caddr seg)) nonclimber-cnt (1+ nonclimber-cnt))))
((= (car seg) "Bogen")
@@ -11653,7 +11967,31 @@
;; es-along-fix (reine Laenge, siehe oben) NUR aus dem Laengenbudget (span)
;; abziehen - NICHT aus feste-vf, sonst floss es faelschlich ueber sin(3°)
;; auch in die Hoehenbilanz ein (siehe Kommentar oben).
(setq wahl3 (vfl-zsolve-winkel-waehlen (- climber-span es-along-fix) dH-adj feste-vf))
;; --- Uebergangsboegen der flachen Zone ins LAENGENbudget ---
;; vfl-zsolve-flach-ein (Vario_Bogen_auf_3) wird mit der Richtung des
;; KLETTERERS eingebaut, liegt also physisch noch in dessen Pfad-Segment -
;; im Solver-Budget kam er bisher gar nicht vor. Realer Befund 2026-10-23:
;; Segment 1 (9700 mm) war mit Einlauf + auf_9 + Koerper + ab_9 exakt
;; gefuellt, der auf_3 schob die Vario-Kurve danach um 199.9 mm hinter die
;; gezeichnete Ecke; alles Folgende war mitverschoben. Spiegelbildlich gilt
;; das fuer vfl-zsolve-flach-aus (Vario_Bogen_ab_3) beim EINTRITT in den
;; Kletterer, wenn eine flache Zone vorausgeht.
;; Nur die LAENGE wird korrigiert - die Hoehe dieser Boegen steckt bereits
;; in *vfk-bruecke-korrektur* (siehe dH-adj oben).
(setq uebergang-fp 0.0)
(if (> n-climb 0)
(progn
(if (< (car climbers) vf-ende) ; flache Zone FOLGT -> flach-ein
(setq uebergang-fp
(+ uebergang-fp
(car (vfl-projiziere-neigung (car (get-bogen-mass bogen-auf 3))
(caddr (get-bogen-mass bogen-auf 3))
(fix *vfk-gefaelle-winkel*))))))
(if (> (car climbers) vf-start) ; flache Zone GEHT VORAUS -> flach-aus
(setq uebergang-fp
(+ uebergang-fp (car (get-bogen-mass bogen-ab 3)))))))
(setq wahl3 (vfl-zsolve-winkel-waehlen
(- climber-span es-along-fix uebergang-fp) dH-adj feste-vf))
(if (null wahl3)
(progn
;; Stufe 2: KEIN Tabellenwinkel passt (der/die Kletterer sind bei JEDER
@@ -11750,7 +12088,18 @@
(vf-vfhoehe-schliesse pt)
(vfl-acc-vf-seg "horizontal" 0 filler-A-len)
(setq anzahl-vf (1+ anzahl-vf) filler-a-done t)))))
(if (= i vf-ende)
;; Der Ziel-Solve des letzten Fuellers gilt NUR, wenn der VF-Lauf
;; zugleich das Pfadende ist. Seine Annahme steht unten im Kommentar:
;; der Schwanz verschiebt sich STARR entlang seg-hz. Folgt nach dem
;; Lauf noch ein Back-Lauf (GF-Bogen und/oder GF-Gerade), stimmt das
;; nicht mehr - die Kette knickt dort ab, und der Fueller wurde mit
;; einem falschen Modell bemessen (realer Befund 2026-10-23: Lauf
;; endete bei Segment 3, danach kamen GF-Bogen + GF; Endpunkt lag
;; 111.6 mm NEBEN der ES-Achse, bei exakter Hoehe). In diesem Fall
;; bekommt der Fueller schlicht die Restlaenge seines Segments; um
;; Separator + ES kuemmert sich der Back-Lauf selbst (siehe dort die
;; last-line-Reservierung).
(if (and (= i vf-ende) (= vf-ende (1- n)))
;; letzter Fueller, MIT ES-Element (unveraendert): Laenge so, dass der
;; ENDPUNKT auf der ES-KS_AUS-ACHSE liegt (Spiegel der AS-Regel). Der
;; Schwanz (Fueller + ab_3 + Motor + GF2 + Separator + ES) verschiebt