From 833a4b60a675dc8c72684105b4d880c4ad0374d1 Mon Sep 17 00:00:00 2001 From: Yuelin Wang Date: Fri, 23 Oct 2026 15:39:57 +0200 Subject: [PATCH] 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 --- Lisp/vf_konstanten.lsp | 7 + Lisp/vf_linienzug.lsp | 437 ++++++++++++++++++++++++++++++++---- dcl/vf_linienzug_wizard.dcl | 41 +++- lang/de_DE.json | 10 +- lang/en_GB.json | 10 +- tests/test_unit.lsp | 118 +++++++--- 6 files changed, 532 insertions(+), 91 deletions(-) diff --git a/Lisp/vf_konstanten.lsp b/Lisp/vf_konstanten.lsp index c40dcd7..7f4ed78 100644 --- a/Lisp/vf_konstanten.lsp +++ b/Lisp/vf_konstanten.lsp @@ -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) diff --git a/Lisp/vf_linienzug.lsp b/Lisp/vf_linienzug.lsp index 81368eb..f60cdb2 100644 --- a/Lisp/vf_linienzug.lsp +++ b/Lisp/vf_linienzug.lsp @@ -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 diff --git a/dcl/vf_linienzug_wizard.dcl b/dcl/vf_linienzug_wizard.dcl index 9dfdd20..a9cd3db 100644 --- a/dcl/vf_linienzug_wizard.dcl +++ b/dcl/vf_linienzug_wizard.dcl @@ -547,9 +547,19 @@ vflw_sep_gf : dialog { key = "sepvor"; label = "Separator VOR dieser Gefaellestrecke"; } - : toggle { - key = "sepnach"; - label = "Separator NACH dieser Gefaellestrecke"; + // Steht am Kettenende ein ES-Element, ist "Separator NACH" gesperrt - + // der Hinweis dahinter sagt warum, in EINER Zeile mit dem Ankreuzfeld + // (gleiche Anordnung wie in vflw_seg_linie_m2). Sonst leer (set_tile ""). + : row { + : toggle { + key = "sepnach"; + label = "Separator NACH dieser Gefaellestrecke"; + } + : text { + key = "sephinweis"; + label = ""; + width = 62; + } } } @@ -1090,7 +1100,13 @@ vflw_seg_linie_m2 : dialog { : boxed_column { label = "Separator (nur bei GF):"; : toggle { key = "sep_vor"; label = "Separator VOR diesem GF-Stueck"; } - : toggle { key = "sep_nach"; label = "Separator NACH diesem GF-Stueck"; } + // Begruendung, wenn "NACH" gesperrt ist (ES folgt direkt), in EINER + // Zeile hinter dem Ankreuzfeld - sonst liest sie sich wie eine eigene + // Aussage statt wie die Erklaerung zu genau diesem Feld. Sonst leer. + : row { + : toggle { key = "sep_nach"; label = "Separator NACH diesem GF-Stueck"; } + : text { key = "sephinweis"; label = ""; width = 62; } + } } } @@ -1200,13 +1216,24 @@ vflw_m2_lauf_anfang : dialog { label = ""; width = 60; } + // Reihenfolge nach Haeufigkeit (Nutzer-Wunsch 2026-10-23): der + // Regelfall zuerst, der Sonderfall "offener Anfang" zuletzt. Die + // radio_button-KEYS bleiben dabei unveraendert; journalisiert wird der + // am Key haengende WERT ("mit-gf1"/"direkt"/"ohne", siehe + // vfl-m2-anfang-norm), nicht die Position - Umsortieren und Umnummerieren + // sind damit folgenlos fuer bestehende Ketten. : boxed_radio_column { label = "Lauf beginnen mit:"; key = "anfang"; - : radio_button { key = "anfang_offen"; label = "Ohne Umlenkstation (offener Anfang, spaeter fortsetzen)"; } - : radio_button { key = "anfang_umlenk"; label = "Umlenkstation direkt (kein GF1/Separator)"; } - : radio_button { key = "anfang_umlenk_gf1"; label = "Umlenkstation mit GF1 + Separator"; value = "1"; } + // Nummern wie in der Konsolenausgabe, damit Dialog und Protokoll + // dieselbe Option gleich benennen. + : radio_button { key = "anfang_umlenk_gf1"; label = "1 - Umlenkstation mit GF1 + Separator"; value = "1"; } + : radio_button { key = "anfang_umlenk"; label = "2 - Umlenkstation direkt (kein GF1/Separator)"; } + : radio_button { key = "anfang_offen"; label = "3 - Ohne Umlenkstation (offener Anfang, spaeter fortsetzen/verschmelzen)"; } } + // Begruendung, wenn "Ohne Umlenkstation" gesperrt ist (AS gesetzt). + // Breit genug fuer den vollen Satz - bei width = 60 wurde er abgeschnitten. + : text { key = "anfanghinweis"; label = ""; width = 76; } } ok_cancel; diff --git a/lang/de_DE.json b/lang/de_DE.json index 680c0e5..d742073 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -9,6 +9,7 @@ "vfl-as-auto-erkannt": "\n>>> Startpunkt liegt auf KS_AUS einer bestehenden %1-Kette - kein AS-Element noetig.", "vfl-familie-neutral": "GF/VF", "vfl-sep-nach-durch-es-ueberschrieben": "\n>>> Separator NACH ignoriert - das ES-Element bringt beim Kettenende bereits seinen eigenen Separator mit (sonst zwei Separatoren hintereinander).", + "vfl-sep-gf-es-automatisch": "(Ein Separator zwischen GF und ES wird automatisch gesetzt.)", "vfl-echo-position": "\n>>> Position: X=%1 Y=%2 Z=%3", "vfl-echo-wahl": "\n>>> Gewaehlt: %1", "vfl-aus-seite-header": "\n\nAUS-Element (AS_Element_*) - Seite waehlen:", @@ -250,13 +251,18 @@ "vfl-m3-kletter-zu-kurz-flach": "\n Kein Kletter-Winkel passt (Segment zu kurz) -> Lauf wird komplett flach gebaut, Hoehe wandert in GF1.", "vfl-m3-kletter-flach-gf": "\n Flache Zone: GF-Kompensation (GF1) = %1 mm", "vfl-m3-alert-vf-zu-lang": "VF-Lauf ist %1 m lang (Grenze %2 m) - bitte neu planen (z.B. Zwischen-Motorstation einfuegen).", + "vfl-m2-einheit-grenze": "Diese VF-Einheit ist so nicht baubar.\n\nGemessen: %1 m Laenge, %2 Vario-Kurven.\nErlaubt je Einheit (zwischen Umlenk- und Motorstation): hoechstens %3 m und %4 Vario-Kurven.\n\nBitte den Bogen in Segment %5 als GF-Bogen klassifizieren - dann endet die erste Einheit davor mit einer Motorstation und die zweite beginnt danach mit einer Umlenkstation.", + "vfl-m2-einheit-grenze-unteilbar": "Diese VF-Einheit ist so nicht baubar.\n\nGemessen: %1 m Laenge, %2 Vario-Kurven.\nErlaubt je Einheit (zwischen Umlenk- und Motorstation): hoechstens %3 m und %4 Vario-Kurven.\n\nEs gibt keinen Bogen, an dem sich der Lauf teilen liesse - bitte den Pfad kuerzer fassen oder eine Ecke einplanen, an der die Einheit enden kann.", "vfl-m3-warnung-fueller-eingang-zu-kurz": "\n WARNUNG: Fueller direkt nach der Umlenkstation zu kurz (%1 mm < Minimum %2 mm) - nur Minimalabstand gebaut, Zielposition nicht exakt erreicht.", "vfl-m3-warnung-fueller-ausgang-zu-kurz": "\n WARNUNG: Fueller direkt vor der Motorstation zu kurz (%1 mm < Minimum %2 mm) - nur Minimalabstand gebaut, Zielposition nicht exakt erreicht.", "vfl-m3-lauf-anfang-header": "\n\nDer VF-Lauf ist der Anfang des gewaehlten Pfads. Lauf beginnen mit:", - "vfl-m3-lauf-anfang-opt-offen": "\n 1 - Ohne Umlenkstation (offener Anfang, spaeter fortsetzen/verschmelzen)", + "vfl-m3-lauf-anfang-opt-offen": "\n 3 - Ohne Umlenkstation (offener Anfang, spaeter fortsetzen/verschmelzen)", "vfl-m3-lauf-anfang-opt-umlenk": "\n 2 - Umlenkstation direkt (kein GF1/Separator)", - "vfl-m3-lauf-anfang-opt-umlenk-gf1": "\n 3 - Umlenkstation mit GF1 + Separator", + "vfl-m3-lauf-anfang-opt-umlenk-gf1": "\n 1 - Umlenkstation mit GF1 + Separator", + "vfl-m2-anfang-wert-unbekannt": "Lauf-Anfang nicht lesbar: Kette %1 enthaelt den unbekannten Wert %2. Aufbau abgebrochen - eine geratene Anfangsvariante wuerde die Kette falsch neu bauen.", "vfl-m3-lauf-anfang-offen-hinweis": "\n Anfang bleibt bewusst offen - keine Umlenkstation/GF1/Separator gebaut, spaeter fortsetzen oder per Vario_Kette_Merge verbinden.", + "vfl-m2-anfang-offen-gesperrt-zeile": "\n (nicht waehlbar - mit AS-Element braucht der Lauf eine Umlenkstation)", + "vfl-m2-anfang-offen-gesperrt": "Mit AS-Element ist ein offener Anfang nicht moeglich - der Lauf braucht dann eine Umlenkstation.", "vfl-m3-lauf-abschluss-header": "\n\nDer VF-Lauf ist das Ende des gewaehlten Pfads. Lauf beenden mit:", "vfl-m3-lauf-abschluss-opt-offen": "\n 1 - Ohne Motorstation (offenes Ende, spaeter fortsetzen/verschmelzen)", "vfl-m3-lauf-abschluss-opt-motor": "\n 2 - Motorstation setzen (kein ES-Element)", diff --git a/lang/en_GB.json b/lang/en_GB.json index a2f75db..dfb6aae 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -9,6 +9,7 @@ "vfl-as-auto-erkannt": "\n>>> Start point lies on the KS_AUS of an existing %1 chain - no AS element needed.", "vfl-familie-neutral": "GF/VF", "vfl-sep-nach-durch-es-ueberschrieben": "\n>>> Separator AFTER ignored - the ES element already brings its own separator at the chain end (otherwise two separators in a row).", + "vfl-sep-gf-es-automatisch": "(A separator between GF and ES is inserted automatically.)", "vfl-echo-position": "\n>>> Position: X=%1 Y=%2 Z=%3", "vfl-echo-wahl": "\n>>> Selected: %1", "vfl-aus-seite-header": "\n\nOUT element (AS_Element_*) - select side:", @@ -250,13 +251,18 @@ "vfl-m3-kletter-zu-kurz-flach": "\n No climb angle fits (segment too short) -> run is built entirely flat, height is absorbed by GF1.", "vfl-m3-kletter-flach-gf": "\n Flat zone: GF compensation (GF1) = %1 mm", "vfl-m3-alert-vf-zu-lang": "VF run is %1 m long (limit %2 m) - please re-plan (e.g. insert an intermediate motor station).", + "vfl-m2-einheit-grenze": "This VF unit cannot be built like this.\n\nMeasured: %1 m length, %2 vario curves.\nAllowed per unit (between deflection and motor station): at most %3 m and %4 vario curves.\n\nPlease classify the bend in segment %5 as a GF bend - the first unit then ends before it with a motor station and the second one starts after it with a deflection station.", + "vfl-m2-einheit-grenze-unteilbar": "This VF unit cannot be built like this.\n\nMeasured: %1 m length, %2 vario curves.\nAllowed per unit (between deflection and motor station): at most %3 m and %4 vario curves.\n\nThere is no bend at which the run could be split - please shorten the path or plan a corner where the unit can end.", "vfl-m3-warnung-fueller-eingang-zu-kurz": "\n WARNING: filler right after the reversing station is too short (%1 mm < minimum %2 mm) - only the minimum clearance was built, target position not reached exactly.", "vfl-m3-warnung-fueller-ausgang-zu-kurz": "\n WARNING: filler right before the motor station is too short (%1 mm < minimum %2 mm) - only the minimum clearance was built, target position not reached exactly.", "vfl-m3-lauf-anfang-header": "\n\nThe VF run is the start of the selected path. Start the run with:", - "vfl-m3-lauf-anfang-opt-offen": "\n 1 - No reversing station (open start, continue/merge later)", + "vfl-m3-lauf-anfang-opt-offen": "\n 3 - No reversing station (open start, continue/merge later)", "vfl-m3-lauf-anfang-opt-umlenk": "\n 2 - Reversing station directly (no GF1/separator)", - "vfl-m3-lauf-anfang-opt-umlenk-gf1": "\n 3 - Reversing station with GF1 + separator", + "vfl-m3-lauf-anfang-opt-umlenk-gf1": "\n 1 - Reversing station with GF1 + separator", + "vfl-m2-anfang-wert-unbekannt": "Cannot read run start: chain %1 contains the unknown value %2. Build aborted - guessing a start variant would rebuild the chain incorrectly.", "vfl-m3-lauf-anfang-offen-hinweis": "\n Start deliberately left open - no reversing station/GF1/separator built; continue later or join with Vario_Kette_Merge.", + "vfl-m2-anfang-offen-gesperrt-zeile": "\n (not selectable - with an AS element the run needs a reversing station)", + "vfl-m2-anfang-offen-gesperrt": "With an AS element an open start is not possible - the run then needs a reversing station.", "vfl-m3-lauf-abschluss-header": "\n\nThe VF run is the end of the selected path. End the run with:", "vfl-m3-lauf-abschluss-opt-offen": "\n 1 - No motor station (open end, continue/merge later)", "vfl-m3-lauf-abschluss-opt-motor": "\n 2 - Set motor station (no ES element)", diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index 83283bf..48d2266 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -409,7 +409,7 @@ ;; vfl-par-gf-laenge - wie lang wird das GF-Stueck dazwischen? ;; Die Plan-Turn-Werte unten sind die Vorzeichen-Konvention aus ;; vf-element-plan-turn (KS_EIN -> KS_AUS in der XY-Ebene, links positiv). -(defun tu-tests-vfl-parallel ( / _b30 _b60 _b90 _kand _l) +(defun tu-tests-vfl-parallel ( / _b30 _b60 _b90 _kand _l _plan _plan3 _g _p _w) (princ "\n[vf_linienzug Parallel zum Kreisel]") ;; --- Bogenwahl: der Bogen muss den AS-Schwenk GENAU aufheben --- @@ -450,6 +450,28 @@ ;; AS schwenkt gar nicht aus der Achse -> keine Loesung (tu-nil "vfl-par-laenge/kein-versatz" (vfl-par-gf-laenge 2000.0 0.0 0.0 0.0)) + ;; --- Grenzen einer VF-Einheit (Modus 2) --- + ;; Zwischen Umlenk- und Motorstation: hoechstens 2 Vario-Kurven und 25 m. + ;; Plan-Eintraege: ("Linie" hz laenge ...) / ("Bogen" hz chord ...) - nur + ;; Typ und Laenge (3. Feld) zaehlen hier. + (setq _plan (list '("Linie" 0.0 9000.0 "VF") '("Bogen" 0.0 500.0) + '("Linie" 0.0 8000.0 "VF") '("Bogen" 0.0 500.0) + '("Linie" 0.0 6000.0 "VF"))) + ;; 24000 mm, 2 Kurven -> alles im Rahmen + (setq _g (vfl-m2-lauf-grenzen _plan 0 4 25000.0 2)) + (tu-eqf "vfl-m2-grenzen/laenge" 24000.0 (nth 0 _g)) + (tu-eq "vfl-m2-grenzen/kurven" 2 (nth 1 _g)) + (tu-nil "vfl-m2-grenzen/ok" (nth 2 _g)) + ;; Dritte Kurve -> an ihr (Index 5) teilen, obwohl die Laenge noch passt + (setq _plan3 (append _plan (list '("Bogen" 0.0 400.0) '("Linie" 0.0 300.0 "VF")))) + (tu-eq "vfl-m2-grenzen/dritte-kurve" 5 (nth 2 (vfl-m2-lauf-grenzen _plan3 0 6 25000.0 2))) + ;; Zu lang -> am ersten Bogen teilen, nach dem die Grenze reisst (Index 3: + ;; bis dahin sind 9000+500+8000 = 17500 drin, die Grenze liegt bei 15000). + (tu-eq "vfl-m2-grenzen/zu-lang" 3 (nth 2 (vfl-m2-lauf-grenzen _plan 0 4 15000.0 2))) + ;; Eine einzige lange Gerade: zu lang, aber kein Bogen zum Teilen -> -1 + (tu-eq "vfl-m2-grenzen/unteilbar" -1 + (nth 2 (vfl-m2-lauf-grenzen (list '("Linie" 0.0 30000.0 "VF")) 0 0 25000.0 2))) + ;; --- Vorgabewert aus einem Prompt ziehen (Dialog-Feld vorbelegen) --- ;; Die Konsolen-Prompts tragen ihre Vorgabe in Klammern; der Zahlen-Dialog ;; liest sie daraus. Gesucht ist die LETZTE Klammergruppe MIT Zahl - die @@ -470,6 +492,30 @@ (tu-nil "vflw-vorgabe/keine-zahl" (vflw-vorgabe-aus-prompt "Bitte Namen eingeben [frei]: ")) + ;; --- Modus-2-Lauf-Anfang: Journal-Wert normieren --- + ;; Die Antwort wird als selbstbeschreibender Wert gefuehrt, damit ein + ;; Umsortieren/Umnummerieren der Optionen bestehende Ketten nicht + ;; umdeutet. Alte Ketten tragen dort noch die Ziffern der ALTEN Folge - + ;; die Zuordnung dafuer steht als Tabelle in *vfl-m2-anfang-alt* und wird + ;; hier komplett durchlaufen, statt einzelne Faelle abzuschreiben. + (foreach _p *vfl-m2-anfang-alt* + (tu-eq (strcat "vfl-m2-anfang/alt-" (car _p)) + (cdr _p) (vfl-m2-anfang-norm (car _p)))) + ;; Jeder Tabellenwert muss auch ein gueltiger heutiger Wert sein - sonst + ;; laeuft eine Alt-Kette in den Unbekannt-Abbruch. + (foreach _p *vfl-m2-anfang-alt* + (tu-true (strcat "vfl-m2-anfang/alt-ziel-gueltig-" (car _p)) + (and (member (cdr _p) *vfl-m2-anfang-werte*) t))) + ;; Neue Werte gehen unveraendert durch. + (foreach _w *vfl-m2-anfang-werte* + (tu-eq (strcat "vfl-m2-anfang/neu-" _w) _w (vfl-m2-anfang-norm _w))) + ;; Unbekanntes wird NICHT geraten - nil heisst fuer den Aufrufer Abbruch. + (tu-nil "vfl-m2-anfang/unbekannte-ziffer" (vfl-m2-anfang-norm "4")) + (tu-nil "vfl-m2-anfang/leer" (vfl-m2-anfang-norm "")) + (tu-nil "vfl-m2-anfang/tippfehler" (vfl-m2-anfang-norm "mit_gf1")) + (tu-nil "vfl-m2-anfang/kein-string" (vfl-m2-anfang-norm 3)) + (tu-nil "vfl-m2-anfang/nil" (vfl-m2-anfang-norm nil)) + ;; --- Rueckwaerts bauen: Punkt entgegen der Fahrtrichtung zuruecksetzen --- ;; vfl-rueck-punkt ist der Rechenkern des Rueckwaerts-Baus: zu einem ;; BEKANNTEN Endpunkt den Startpunkt eines Bausteins finden. Vorwaerts gilt @@ -2452,41 +2498,6 @@ ) ) -(defun c:TEST_UNIT ( / out-dir report) - (tu-reset) - (princ "\n================================================================") - (princ "\n TEST_UNIT - LISP-Standardfunktionen") - (princ "\n================================================================") - - (foreach fn '("tu-tests-core" "tu-tests-lang" "tu-tests-id" "tu-tests-vfcore" - "tu-tests-etage" "tu-tests-vfl-es" "tu-tests-vfl-parallel" - "tu-tests-vfl-ziellinie" - "tu-tests-export" "tu-tests-hoehen" "tu-tests-gf" "tu-tests-omni" "tu-tests-kreisel" "tu-tests-kreisel-bp" - "tu-tests-sepliste" "tu-tests-json" "tu-tests-geruest" - "tu-tests-sepscan" "tu-tests-vfl-segs" "tu-tests-vfl-abbruch" - "tu-tests-werkzeuge" "tu-tests-kf") - (tu-run-block fn)) - - (princ "\n\n----------------------------------------------------------------") - (princ (strcat "\n Gesamt: " (itoa (+ *tu-pass* *tu-fail*)) - " | PASS: " (itoa *tu-pass*) - " | FAIL: " (itoa *tu-fail*))) - (princ (if (= *tu-fail* 0) "\n ERGEBNIS: ALLE TESTS BESTANDEN" "\n ERGEBNIS: TESTS FEHLGESCHLAGEN")) - (princ "\n----------------------------------------------------------------") - - ;; Report-Datei schreiben (fuer run_tests.bat --lisp) - (setq out-dir (strcat (getenv "DXFMAKRO") "/tests/output")) - (vl-mkdir out-dir) - (setq report (strcat out-dir "/unit_results.txt")) - (if (tu-write-report report) - (princ (strcat "\n Report: " report)) - (princ "\n WARNUNG: Report konnte nicht geschrieben werden.") - ) - (princ) -) - -(princ "\ntest_unit.lsp geladen. Befehl: TEST_UNIT") -(princ) ;; ------------------------------------------------------------ ;; TESTS: KettenFoerderer.lsp + ssg_ks_insert.lsp (KS-Orientierung) ;; ------------------------------------------------------------ @@ -2739,3 +2750,38 @@ ) +(defun c:TEST_UNIT ( / out-dir report) + (tu-reset) + (princ "\n================================================================") + (princ "\n TEST_UNIT - LISP-Standardfunktionen") + (princ "\n================================================================") + + (foreach fn '("tu-tests-core" "tu-tests-lang" "tu-tests-id" "tu-tests-vfcore" + "tu-tests-etage" "tu-tests-vfl-es" "tu-tests-vfl-parallel" + "tu-tests-vfl-ziellinie" + "tu-tests-export" "tu-tests-hoehen" "tu-tests-gf" "tu-tests-omni" "tu-tests-kreisel" "tu-tests-kreisel-bp" + "tu-tests-sepliste" "tu-tests-json" "tu-tests-geruest" + "tu-tests-sepscan" "tu-tests-vfl-segs" "tu-tests-vfl-abbruch" + "tu-tests-werkzeuge" "tu-tests-kf") + (tu-run-block fn)) + + (princ "\n\n----------------------------------------------------------------") + (princ (strcat "\n Gesamt: " (itoa (+ *tu-pass* *tu-fail*)) + " | PASS: " (itoa *tu-pass*) + " | FAIL: " (itoa *tu-fail*))) + (princ (if (= *tu-fail* 0) "\n ERGEBNIS: ALLE TESTS BESTANDEN" "\n ERGEBNIS: TESTS FEHLGESCHLAGEN")) + (princ "\n----------------------------------------------------------------") + + ;; Report-Datei schreiben (fuer run_tests.bat --lisp) + (setq out-dir (strcat (getenv "DXFMAKRO") "/tests/output")) + (vl-mkdir out-dir) + (setq report (strcat out-dir "/unit_results.txt")) + (if (tu-write-report report) + (princ (strcat "\n Report: " report)) + (princ "\n WARNUNG: Report konnte nicht geschrieben werden.") + ) + (princ) +) + +(princ "\ntest_unit.lsp geladen. Befehl: TEST_UNIT") +(princ)