From 23d653c4b3f096377906e31f6d0e715b0ccff4a9 Mon Sep 17 00:00:00 2001 From: Yuelin Wang Date: Fri, 11 Sep 2026 14:18:31 +0200 Subject: [PATCH] feat: VF-Linienzug Linie-GF - Separator VOR/NACH, Endpunkt/Zielhoehe-Vorwahl, steigende GF (Sonderfall bis 3 Grad) Auf der neuen Kollegen-Architektur (Stufe 0-2: Spec/Journal/Replay-System in vf_spec.lsp, Frage-Schale/Bauteil-Trennung) neu aufgebaut, nachdem der urspruengliche Stand (Commit e76e24e) auf einer aelteren Basis beruhte und per Rebase-Analyse als eigenstaendige, unabhaengige Erweiterung erkannt wurde (der Kollege hat nichts entfernt - diese Features existierten vorher nur lokal uncommitted). Drei Erweiterungen fuer "Neue Linie: GF" in vf-linienzug-modus: - Separator VOR/NACH direkt vor bzw. nach der Gefaellestrecke einfuegbar. - Aeussere Vorwahl "Wie soll die Laenge bestimmt werden?": Endpunkt waehlen (bisheriges Verhalten) oder Zielhoehe zuerst festlegen (Winkel wird daraus berechnet). - Dritte Option "Steigende GF" (Sonderfall, 0 bis 3 Grad): erlaubt eine GF, die statt zu fallen steigt. Neuer Helfer vfl-bogen-uebergang erkennt anhand der tatsaechlichen Frame-Neigung, ob 0/1/2 Uebergangsboegen (Vario_Bogen_auf_3/ab_3) noetig sind, und baut sie automatisch - im Normalfall (fallend->fallend) ein No-Op. Lisp/vf_spec.lsp (Kollegen-Spec/Headless-Bau-System) um die drei neuen Felder (laenge_modus/sep_vor/sep_nach/steigend_modus) erweitert, mit Defaults, die alte Specs unveraendert lassen - haelt die "Spec-Pfad == interaktiver Pfad"-Invariante seines Systems (TEST_HUNDM05/VF_SPEC_BAU) intakt. tests/test_vf_spec.lsp entsprechend mit neuen Testfaellen ergaenzt. Neuer Regressionstest TEST_LINIENZUG_STUFE2 (linienzug_tests_stufe2.json) sowie Anleitung doc/vf_linienzug_modus1_anleitung.md aus dem urspruenglichen Commit uebernommen; die Gefaelle-Hoehe/Winkel-Optionsreihenfolge im Testdatensatz an die jetzt geltende (Kollegen-)Konvention angepasst. Offen (bewusst zurueckgestellt): lib/vf_spec_export.py (Python-Vorwaerts- Emitter, Pendant zu vf_spec.lsp) noch nicht nachgezogen - siehe Folge-Commit. Co-Authored-By: Claude Sonnet 5 --- Lisp/vf_linienzug.lsp | 572 ++++++++++++++++----- Lisp/vf_spec.lsp | 53 +- doc/vf_linienzug_modus1_anleitung.md | 194 +++++++ lang/de_DE.json | 10 + lang/en_GB.json | 10 + tests/test_linienzug.lsp | 52 +- tests/test_vf_spec.lsp | 54 +- tests/testdata/linienzug_tests_stufe2.json | 295 +++++++++++ 8 files changed, 1092 insertions(+), 148 deletions(-) create mode 100644 doc/vf_linienzug_modus1_anleitung.md create mode 100644 tests/testdata/linienzug_tests_stufe2.json diff --git a/Lisp/vf_linienzug.lsp b/Lisp/vf_linienzug.lsp index 8643009..82c47df 100644 --- a/Lisp/vf_linienzug.lsp +++ b/Lisp/vf_linienzug.lsp @@ -2403,6 +2403,75 @@ (vfl-frame-3grad pt hz)) frame)) +;; Genereller Uebergang zwischen zwei Basis-Neigungen (0/+3/-3 Grad, +;; Vorzeichenkonvention wie frame->hz-winkel: positiv=fallend, negativ= +;; steigend). Die Bibliothek kennt nur einen 3-Grad-Bogen gegenueber FLACH +;; (0 Grad) - ein Wechsel zwischen der fallenden (+3) und der steigenden (-3) +;; Basis braucht daher IMMER zwei Boegen ueber 0 als Zwischenhalt. "auf"- +;; Boegen senken die Neigung numerisch (Richtung steigend), "ab"-Boegen +;; erhoehen sie (Richtung fallend) - siehe vfl-nach-3grad/vfl-hor-koerper- +;; bauen, deren Uebergaenge hier verallgemeinert werden. Ist kein Uebergang +;; noetig (Start-/Zielbasis identisch), no-op. +;; Rueckgabe: (neuer-frame fussabdruck-summe) - fussabdruck-summe (mm, auf hz +;; projiziert) muss der Aufrufer von der geplanten Restlaenge/Zielstrecke +;; abziehen. +(defun vfl-bogen-uebergang (frame ziel-winkel / t0 basis-ziel hz pt m summe vor-pt) + (setq hz (car (frame->hz-winkel frame))) + (setq t0 (cadr (frame->hz-winkel frame))) + (setq t0 (cond ((< (abs t0) *vfk-frame-flach-toleranz*) 0) + ((< t0 0) (- (fix *vfk-gefaelle-winkel*))) + (t (fix *vfk-gefaelle-winkel*)))) + (setq basis-ziel (cond ((< (abs ziel-winkel) *vfl-gf-winkel-toleranz*) 0) + ((< ziel-winkel 0) (- (fix *vfk-gefaelle-winkel*))) + (t (fix *vfk-gefaelle-winkel*)))) + (setq summe 0.0) + (if (/= t0 basis-ziel) + (progn + ;; Schritt 1: t0 -> 0 (falls t0 noch nicht flach ist). + (if (/= t0 0) + (progn + (setq vor-pt (car frame)) + (if (> t0 0) + (progn + (setq m (get-bogen-mass bogen-auf (fix *vfk-gefaelle-winkel*))) + (princ (ssg-text "vfl-bogen-auf3-uebergang")) + (setq pt (insert-rotated-block-with-ks "Vario_Bogen_auf_3_TEF_rechts" vor-pt + (fix *vfk-gefaelle-winkel*) (car m) (caddr m) hz))) + (progn + (setq m (get-bogen-mass bogen-ab (fix *vfk-gefaelle-winkel*))) + (princ (ssg-text "vfl-bogen-ab3-uebergang")) + (setq pt (insert-rotated-block-with-ks "Vario_Bogen_ab_3_TEF_rechts" vor-pt + (- (fix *vfk-gefaelle-winkel*)) (car m) (caddr m) hz))) + ) + (setq summe (+ summe (vfl-projiziere-distanz vor-pt pt hz))) + (setq frame (make-frame-from-dir pt (hz-winkel->xu hz 0))) + ) + ) + ;; Schritt 2: 0 -> basis-ziel (falls das Ziel nicht flach ist). + (if (/= basis-ziel 0) + (progn + (setq vor-pt (car frame)) + (if (> basis-ziel 0) + (progn + (setq m (get-bogen-mass bogen-ab (fix *vfk-gefaelle-winkel*))) + (princ (ssg-text "vfl-bogen-ab3-uebergang")) + (setq pt (insert-rotated-block-with-ks "Vario_Bogen_ab_3_TEF_rechts" vor-pt + 0 (car m) (caddr m) hz))) + (progn + (setq m (get-bogen-mass bogen-auf (fix *vfk-gefaelle-winkel*))) + (princ (ssg-text "vfl-bogen-auf3-uebergang")) + (setq pt (insert-rotated-block-with-ks "Vario_Bogen_auf_3_TEF_rechts" vor-pt + 0 (car m) (caddr m) hz))) + ) + (setq summe (+ summe (vfl-projiziere-distanz vor-pt pt hz))) + (setq frame (make-frame-from-dir pt (hz-winkel->xu hz basis-ziel))) + ) + ) + ) + ) + (list frame summe) +) + ;; 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 @@ -3461,7 +3530,9 @@ anzahl-gf anzahl-vf vfl-nummer lastEnt seg-lastEnt gf-max-winkel gf-ok kettenanfang as-vorhanden erg old-error vfl-ins dbg-an - hoehe-vorschlaege hoehe-ab hoehe-auf hoehe-fallback) + hoehe-vorschlaege hoehe-ab hoehe-auf hoehe-fallback + gf-laenge-modus gf-steigend-modus sep-vor-gf sep-nach-gf + sep-anzahl erg-bogen) (princ "\n\n=========================================") (princ (ssg-text "vfl-modus1-header")) (princ "\n=========================================") @@ -3706,146 +3777,393 @@ ;; --- Neue Linie: GF (Nutzer legt den Typ explizit fest) --- ((= wahl "Linie-GF") - ;; Fahrtrichtung folgt dem vorherigen Element (Frame); nur das erste - ;; Segment (frame=nil) definiert die Richtung frei. - (setq linie-mess (vfl-neue-linie-messen p-aktuell - (if frame (car (frame->hz-winkel frame)) nil))) - (if (null linie-mess) (progn (princ (ssg-text "vfl-abgebrochen")) (exit))) - (setq deltaL (car linie-mess) hz-neu (cadr linie-mess)) (setq kettenanfang (null frame)) (setq gf-max-winkel (float (ssg-cfg-or "vario" "gefaelle_winkel" 3))) - (if (< deltaL 1.0) - (princ (ssg-text "vfl-fehler-linie-zu-kurz")) + (setq gf-ok nil sep-vor-gf nil sep-nach-gf nil) + ;; NEU: Vorfrage "Wie soll die Laenge bestimmt werden?" - IMMER + ;; gezeigt. gf-laenge-modus="2" => Zielhoehe zuerst (Zweig B), "3" => + ;; steigende GF (Sonderfall, Zweig C, KEIN Separator), sonst (Default + ;; "1") Endpunkt waehlen (Zweig A, heutiges Verhalten + Separator). + (princ (ssg-text "vfl-gf-laenge-modus-header")) + (princ (ssg-text "vfl-gf-laenge-modus-opt-endpunkt")) + (princ (ssg-text "vfl-gf-laenge-modus-opt-hoehe")) + (princ (ssg-text "vfl-gf-laenge-modus-opt-steigend")) + (setq gf-laenge-modus (vfl-menu (ssg-text "prompt-wahl-1-2") + (list (ssg-text "vfl-gf-laenge-modus-opt-endpunkt") + (ssg-text "vfl-gf-laenge-modus-opt-hoehe") + (ssg-text "vfl-gf-laenge-modus-opt-steigend")) 1 + "vfl-gf-laenge-modus-header")) + (cond + ((= gf-laenge-modus "3") + ;; --- Zweig C: Steigende GF (Sonderfall, bis gf-max-winkel Grad) --- + ;; Eigenstaendig: KEIN Separator (gilt nur fuer die normale + ;; fallende GF). Innerhalb dieses Zweigs nochmal dieselbe Wahl + ;; (Endpunkt vs. Zielhoehe), mit Ziel-Richtung "Auf" statt "Ab". + ;; Der noetige Uebergangsbogen (falls die Vorgaenger-Neigung nicht + ;; schon zur steigenden Basis passt) wird zentral im gemeinsamen + ;; Abschluss unten ueber vfl-bogen-uebergang eingefuegt. + (princ (ssg-text "vfl-gf-laenge-modus-header")) + (princ (ssg-text "vfl-gf-laenge-modus-opt-endpunkt")) + (princ (ssg-text "vfl-gf-laenge-modus-opt-hoehe")) + (setq gf-steigend-modus (vfl-menu (ssg-text "prompt-wahl-1-2") + (list (ssg-text "vfl-gf-laenge-modus-opt-endpunkt") + (ssg-text "vfl-gf-laenge-modus-opt-hoehe")) 1 + "vfl-gf-laenge-modus-header")) + (if (= gf-steigend-modus "2") + (progn + ;; --- Zweig C2: Zielhoehe zuerst (steigend) --- + (if kettenanfang + (progn + (setq linie-mess (vfl-neue-linie-messen p-aktuell nil)) + (if (null linie-mess) (progn (princ (ssg-text "vfl-abgebrochen")) (exit))) + (setq hz-neu (cadr linie-mess)) + (setq erg (vfl-kettenanfang-baustein p-aktuell hz-neu 1.0e9 as-seite as-vorhanden)) + (setq frame (nth 0 erg) p-aktuell (car frame)) + ) + (setq hz-neu (car (frame->hz-winkel frame))) + ) + (setq gf-ok t) + (setq hoehe-neu + (vfl-in-real (ssg-textf "vfl-prompt-hoehe-linienendpunkt" + (list (rtos (caddr p-aktuell) 2 1))))) + (if (null hoehe-neu) (setq hoehe-neu (caddr p-aktuell))) + (setq deltaH (- hoehe-neu (caddr p-aktuell))) + (if (< deltaH 0.0) + (progn + (vfl-meldung (ssg-text "vfl-alert-gf-steigend-falsche-richtung")) + (setq gf-ok nil)) + (progn + (setq winkel + (vfl-in-real (ssg-textf "vfl-prompt-neigungswinkel-steigend" (list (rtos gf-max-winkel 2 1))))) + (if (null winkel) (setq winkel gf-max-winkel)) + (if (or (< winkel 0.0) (> winkel gf-max-winkel)) + (progn + (vfl-meldung (ssg-textf "vfl-alert-winkel-ungueltig-steigend" + (list (rtos winkel 2 1) (rtos gf-max-winkel 2 1)))) + (setq gf-ok nil)) + (if (and (< winkel 1.0e-6) (> deltaH 1.0e-6)) + (progn + (vfl-meldung (ssg-textf "vfl-alert-winkel-ungueltig-steigend" + (list (rtos winkel 2 1) (rtos gf-max-winkel 2 1)))) + (setq gf-ok nil)) + (progn + (setq deltaL + (if (< winkel 1.0e-6) 0.0 + (/ deltaH (/ (sin (* winkel (/ pi 180.0))) (cos (* winkel (/ pi 180.0))))))) + (if (< deltaL 1.0) + (progn (princ (ssg-text "vfl-fehler-linie-zu-kurz")) (setq gf-ok nil))) + (setq winkel (- winkel)) + ) + ) + ) + ) + ) + ) + (progn + ;; --- Zweig C1: Endpunkt waehlen (steigend) --- + (setq linie-mess (vfl-neue-linie-messen p-aktuell + (if frame (car (frame->hz-winkel frame)) nil))) + (if (null linie-mess) (progn (princ (ssg-text "vfl-abgebrochen")) (exit))) + (setq deltaL (car linie-mess) hz-neu (cadr linie-mess)) + (if (< deltaL 1.0) + (princ (ssg-text "vfl-fehler-linie-zu-kurz")) + (progn + (if kettenanfang + (progn + (setq erg (vfl-kettenanfang-baustein p-aktuell hz-neu deltaL as-seite as-vorhanden)) + (setq frame (nth 0 erg) deltaL (nth 1 erg) p-aktuell (car frame)) + ) + ) + (if (< deltaL 1.0) + (princ (ssg-text "vfl-fehler-linie-zu-kurz")) + (progn + (setq gf-ok t) + (setq winkel + (vfl-in-real (ssg-textf "vfl-prompt-neigungswinkel-steigend" (list (rtos gf-max-winkel 2 1))))) + (if (null winkel) (setq winkel gf-max-winkel)) + (if (or (< winkel 0.0) (> winkel gf-max-winkel)) + (progn + (vfl-meldung (ssg-textf "vfl-alert-winkel-ungueltig-steigend" + (list (rtos winkel 2 1) (rtos gf-max-winkel 2 1)))) + (setq gf-ok nil)) + (setq winkel (- winkel)) + ) + ) + ) + ) + ) + ) + ) + ) + ((= gf-laenge-modus "2") + ;; --- Zweig B: Zielhoehe zuerst festlegen, Laenge wird berechnet --- + (if kettenanfang + (progn + (setq linie-mess (vfl-neue-linie-messen p-aktuell nil)) + (if (null linie-mess) (progn (princ (ssg-text "vfl-abgebrochen")) (exit))) + (setq hz-neu (cadr linie-mess)) + (setq erg (vfl-kettenanfang-baustein p-aktuell hz-neu 1.0e9 as-seite as-vorhanden)) + (setq frame (nth 0 erg) p-aktuell (car frame)) + ) + (setq hz-neu (car (frame->hz-winkel frame))) + ) + (setq rad3 (* gf-max-winkel (/ pi 180.0))) + (princ (ssg-text "vfl-sep-vor-gf-frage")) + (princ (ssg-text "vfl-ja")) + (princ (ssg-text "vfl-nein")) + (setq sep-vor-gf (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2") + (list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2 + "vfl-sep-vor-gf-frage") "1")) + (princ (ssg-text "vfl-sep-nach-gf-frage")) + (princ (ssg-text "vfl-ja")) + (princ (ssg-text "vfl-nein")) + (setq sep-nach-gf (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2") + (list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2 + "vfl-sep-nach-gf-frage") "1")) + (setq sep-anzahl (+ (if sep-vor-gf 1 0) (if sep-nach-gf 1 0))) + (setq gf-ok t) + (setq hoehe-neu + (vfl-in-real (ssg-textf "vfl-prompt-hoehe-linienendpunkt" + (list (rtos (vfl-gf-hoehe-vorschlag (caddr p-aktuell)) 2 1))))) + (if (null hoehe-neu) (setq hoehe-neu (vfl-gf-hoehe-vorschlag (caddr p-aktuell)))) + (setq deltaH (- hoehe-neu (caddr p-aktuell))) + (setq richtung (if (>= deltaH 0.0) "Auf" "Ab")) + (setq deltaH (abs deltaH)) + (if (= richtung "Auf") + (progn + (vfl-meldung (ssg-text "vfl-alert-gf-kann-nicht-steigen")) + (if dbg-an + (dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Linie-GF (Zielhoehe zuerst) kann nicht steigen" + " (deltaH=" (rtos deltaH 2 0) ")"))) + (setq gf-ok nil)) + (progn + (if (> sep-anzahl 0) + (setq deltaH (max 0.0 (- deltaH (* sep-anzahl *vfk-separator-laenge* (sin rad3)))))) + (setq winkel + (vfl-in-real (ssg-textf "vfl-prompt-neigungswinkel-laenge-berechnet" (list (rtos gf-max-winkel 2 1))))) + (if (null winkel) (setq winkel gf-max-winkel)) + (if (or (<= winkel 0.0) (> winkel gf-max-winkel)) + (progn + (vfl-meldung (ssg-textf "vfl-alert-winkel-ungueltig" + (list (rtos winkel 2 1) (rtos gf-max-winkel 2 1)))) + (setq gf-ok nil)) + (progn + (setq deltaL (/ deltaH (/ (sin (* winkel (/ pi 180.0))) (cos (* winkel (/ pi 180.0)))))) + (if (< deltaL 1.0) + (progn (princ (ssg-text "vfl-fehler-linie-zu-kurz")) (setq gf-ok nil))) + ) + ) + ) + ) + ) + (t + ;; --- Zweig A: Endpunkt waehlen (heutiges Verhalten + Separator) --- + (setq linie-mess (vfl-neue-linie-messen p-aktuell + (if frame (car (frame->hz-winkel frame)) nil))) + (if (null linie-mess) (progn (princ (ssg-text "vfl-abgebrochen")) (exit))) + (setq deltaL (car linie-mess) hz-neu (cadr linie-mess)) + (if (< deltaL 1.0) + (princ (ssg-text "vfl-fehler-linie-zu-kurz")) + (progn + ;; Kettenanfang: AS-Element (falls gewuenscht) SOFORT einfuegen + ;; (Richtung hz-neu jetzt bekannt) und die GF-Restlaenge aus dem + ;; TATSAECHLICHEN KS_AUS nachrechnen (siehe vfl-kettenanfang- + ;; baustein). Keine Fussabdruck-Schaetzung mehr: sowohl KS_EIN- + ;; als auch Ursprung-basierte Vorab-Messung ignorieren die + ;; Drehteller-Rotation, die insert-block-mixed-to-ks beim + ;; echten Einfuegen anwendet, und liefern daher einen falschen + ;; Wert (empirisch bestaetigt: ~210mm Schaetzung vs. + ;; tatsaechlich ~420mm noetiger Versatz). + (if kettenanfang + (progn + (setq erg (vfl-kettenanfang-baustein p-aktuell hz-neu deltaL as-seite as-vorhanden)) + (setq frame (nth 0 erg) deltaL (nth 1 erg) p-aktuell (car frame)) + ) + ) + ;; Nach dem AS-Einfuegen kann die reale Restlaenge (aus dem + ;; tatsaechlichen KS_AUS neu gemessen) kleiner als der + ;; urspruenglich gepickte Rohwert sein - bei einem knapp + ;; gepickten Punkt sogar NEGATIV. Sofortiger Abbruch DIESES + ;; Zweigs (kein Weiterfragen fuer ein Segment, das ohnehin + ;; nicht gebaut werden kann). + (if (< deltaL 1.0) + (princ (ssg-text "vfl-fehler-linie-zu-kurz")) + (progn + (setq gf-ok t) + ;; Separator VOR/NACH JETZT fragen, BEVOR Hoehe/Winkel + ;; berechnet wird - gueltige Trennstellen GF-GF bzw. + ;; GF-Umlenkstation. Beide Laengen (genaehert mit dem + ;; Referenzwinkel gf-max-winkel, der tatsaechliche + ;; GF-Winkel steht erst danach fest) werden SOFORT vom + ;; gepickten deltaL abgezogen, damit die Kette exakt am + ;; gepickten Punkt endet statt danach ueberzustehen. + (setq rad3 (* gf-max-winkel (/ pi 180.0))) + (princ (ssg-text "vfl-sep-vor-gf-frage")) + (princ (ssg-text "vfl-ja")) + (princ (ssg-text "vfl-nein")) + (setq sep-vor-gf (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2") + (list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2 + "vfl-sep-vor-gf-frage") "1")) + (princ (ssg-text "vfl-sep-nach-gf-frage")) + (princ (ssg-text "vfl-ja")) + (princ (ssg-text "vfl-nein")) + (setq sep-nach-gf (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2") + (list (ssg-text "vfl-ja") (ssg-text "vfl-nein")) 2 + "vfl-sep-nach-gf-frage") "1")) + (setq sep-anzahl (+ (if sep-vor-gf 1 0) (if sep-nach-gf 1 0))) + (if (> sep-anzahl 0) + (setq deltaL (- deltaL (* sep-anzahl *vfk-separator-laenge* (cos rad3))))) + (if (< deltaL 1.0) + (progn (princ (ssg-text "vfl-fehler-linie-zu-kurz")) (setq gf-ok nil))) + (if gf-ok + (progn + (if (vfl-wizard-aktiv) + (vl-catch-all-apply 'vflw-gruppe-gefaelle-impl + (list (vfl-gf-hoehe-vorschlag (caddr p-aktuell))))) + (princ (ssg-text "vfl-gefaelle-festlegen-header")) + (princ (ssg-text "vfl-gefaelle-opt-hoehe")) + (princ (ssg-text "vfl-gefaelle-opt-winkel")) + (setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2") + (list (ssg-text "vfl-gefaelle-opt-hoehe") + (ssg-text "vfl-gefaelle-opt-winkel")) 1 + "vfl-gefaelle-festlegen-header")) + (if (= antwort "2") + (progn + ;; Winkel direkt vorgeben - deltaH ergibt sich aus + ;; deltaL*tan(winkel). deltaL ist hier schon um + ;; Separator VOR/NACH reduziert. + (setq winkel + (vfl-in-real (ssg-textf "vfl-prompt-neigungswinkel" (list (rtos gf-max-winkel 2 1))))) + (if (null winkel) (setq winkel gf-max-winkel)) + (if (or (<= winkel 0.0) (> winkel gf-max-winkel)) + (progn + (vfl-meldung (ssg-textf "vfl-alert-winkel-ungueltig" + (list (rtos winkel 2 1) (rtos gf-max-winkel 2 1)))) + (setq gf-ok nil)) + (progn + (setq richtung "Ab") + (setq deltaH (* deltaL (/ (sin (* winkel (/ pi 180.0))) + (cos (* winkel (/ pi 180.0)))))) + ) + ) + ) + (progn + ;; Gegebene Hoehe - Winkel wird daraus abgeleitet + ;; und gegen den GF-Maximalwinkel geprueft. + (setq hoehe-neu + (vfl-in-real (ssg-textf "vfl-prompt-hoehe-linienendpunkt" + (list (rtos (vfl-gf-hoehe-vorschlag (caddr p-aktuell)) 2 1))))) + (if (null hoehe-neu) (setq hoehe-neu (vfl-gf-hoehe-vorschlag (caddr p-aktuell)))) + (setq deltaH (- hoehe-neu (caddr p-aktuell))) + (setq richtung (if (>= deltaH 0.0) "Auf" "Ab")) + (setq deltaH (abs deltaH)) + (if (> sep-anzahl 0) + (setq deltaH (max 0.0 (- deltaH (* sep-anzahl *vfk-separator-laenge* (sin rad3)))))) + (cond + ((= richtung "Auf") + (vfl-meldung (ssg-text "vfl-alert-gf-kann-nicht-steigen")) + (if dbg-an + (dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Linie-GF kann nicht steigen" + " (deltaL=" (rtos deltaL 2 0) " deltaH=" (rtos deltaH 2 0) ")"))) + (setq gf-ok nil)) + (t + (setq winkel (* (atan (/ deltaH deltaL)) (/ 180.0 pi))) + (if (> winkel gf-max-winkel) + (progn + (vfl-meldung (ssg-textf "vfl-alert-gefaelle-zu-steil" + (list (rtos winkel 2 1) (rtos gf-max-winkel 2 1)))) + (if dbg-an + (dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Linie-GF zu steil (deltaL=" + (rtos deltaL 2 0) " deltaH=" (rtos deltaH 2 0) + " winkel=" (rtos winkel 2 1) " max=" (rtos gf-max-winkel 2 1) ")"))) + (setq gf-ok nil)) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ;; --- Gemeinsamer Abschluss fuer ALLE drei Zweige --- + (if gf-ok (progn - ;; Kettenanfang: AS-Element (falls gewuenscht) SOFORT einfuegen - ;; (Richtung hz-neu jetzt bekannt) und die GF-Restlaenge aus dem - ;; TATSAECHLICHEN KS_AUS nachrechnen (siehe vfl-kettenanfang- - ;; baustein). Keine Fussabdruck-Schaetzung mehr: sowohl KS_EIN- als - ;; auch Ursprung-basierte Vorab-Messung ignorieren die Dreh­teller- - ;; Rotation, die insert-block-mixed-to-ks beim echten Einfuegen - ;; anwendet, und liefern daher einen falschen Wert (empirisch - ;; bestaetigt: ~210mm Schaetzung vs. tatsaechlich ~420mm noetiger - ;; Versatz). - (if kettenanfang + ;; Uebergangsbogen(n) einfuegen, falls die aktuelle Frame-Neigung + ;; nicht zur Ziel-Neigung dieser Linie passt: noetig fuer den + ;; Steigend-Sonderfall (Zweig C, winkel < 0) UND falls der + ;; VORGAENGER eine steigende GF war und jetzt normal (Zweig A/B) + ;; weitergebaut wird. Im unveraenderten Normalfall (Vorgaenger- + ;; Neigung bereits 0/+3, Ziel +3 wie bisher) ist die Bedingung + ;; falsch - kein Verhaltensunterschied zu vorher. + (if (or (< winkel 0.0) (< (cadr (frame->hz-winkel frame)) (- *vfl-gf-winkel-toleranz*))) (progn - (setq erg (vfl-kettenanfang-baustein p-aktuell hz-neu deltaL as-seite as-vorhanden)) - (setq frame (nth 0 erg) deltaL (nth 1 erg) p-aktuell (car frame)) + (setq erg-bogen (vfl-bogen-uebergang frame winkel)) + (setq frame (car erg-bogen)) + (setq deltaL (- deltaL (cadr erg-bogen))) + (if (< deltaL 1.0) + (progn (princ (ssg-text "vfl-fehler-linie-zu-kurz")) (setq gf-ok nil))) ) ) - ;; Nach dem AS-Einfuegen kann die reale Restlaenge (aus dem - ;; tatsaechlichen KS_AUS neu gemessen) kleiner als der urspruenglich - ;; gepickte Rohwert sein - bei einem knapp gepickten Punkt sogar - ;; NEGATIV (das AS-Element allein braucht bereits mehr Platz als bis - ;; zum Klickpunkt vorhanden war). Ohne diese zweite Pruefung wuerde - ;; vfl-insert-gf-segment mit negativem deltaL "erfolgreich" eine - ;; entartete/unsichtbare GF-Strecke bauen (leerer Sprung zwischen AS - ;; und dem naechsten Element) statt den Nutzer erneut picken zu - ;; lassen - siehe vfl-fehler-linie-zu-kurz oben (dieselbe Meldung). - ;; Sofortiger Abbruch DIESES Zweigs (kein Weiterfragen nach Winkel/ - ;; Hoehe fuer ein Segment, das ohnehin nicht gebaut werden kann) - - ;; gf-ok wird unten sonst mehrfach ueberschrieben. - (if (< deltaL 1.0) - (princ (ssg-text "vfl-fehler-linie-zu-kurz")) + (if gf-ok (progn - (setq gf-ok t) - (if (vfl-wizard-aktiv) - (vl-catch-all-apply 'vflw-gruppe-gefaelle-impl - (list (vfl-gf-hoehe-vorschlag (caddr p-aktuell))))) - (princ (ssg-text "vfl-gefaelle-festlegen-header")) - (princ (ssg-text "vfl-gefaelle-opt-hoehe")) - (princ (ssg-text "vfl-gefaelle-opt-winkel")) - (setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2") - (list (ssg-text "vfl-gefaelle-opt-hoehe") - (ssg-text "vfl-gefaelle-opt-winkel")) 1 - "vfl-gefaelle-festlegen-header")) - (if (= antwort "2") + ;; Separator VOR (Antwort aus der Vorab-Frage oben) - VOR + ;; dem GF-Segment selbst einfuegen, am GF-Winkel. + (if sep-vor-gf (progn - ;; Winkel direkt vorgeben - deltaH ergibt sich aus deltaL*tan(winkel). - (setq winkel - (vfl-in-real (ssg-textf "vfl-prompt-neigungswinkel" (list (rtos gf-max-winkel 2 1))))) - (if (null winkel) (setq winkel gf-max-winkel)) - (if (or (<= winkel 0.0) (> winkel gf-max-winkel)) - (progn - (vfl-meldung (ssg-textf "vfl-alert-winkel-ungueltig" - (list (rtos winkel 2 1) (rtos gf-max-winkel 2 1)))) - (setq gf-ok nil)) - (progn - (setq richtung "Ab") - (setq deltaH (* deltaL (/ (sin (* winkel (/ pi 180.0))) - (cos (* winkel (/ pi 180.0)))))) - ) - ) - ) - (progn - ;; Gegebene Hoehe - wie bisher, aber Winkel wird daraus abgeleitet - ;; und gegen den GF-Maximalwinkel geprueft (GF kann nicht steigen). - ;; p-aktuell ist ab hier immer der reale Referenzpunkt (bei - ;; Kettenanfang das echte KS_AUS des AS-Elements). Vorschlag/ - ;; Enter-Default = vfl-gf-hoehe-vorschlag (siehe dort), NICHT - ;; die unveraenderte Ist-Hoehe - sonst waere deltaH=0 und die - ;; GF-Pruefung wiese den Wert als "kann nicht steigen" ab. - (setq hoehe-neu - (vfl-in-real (ssg-textf "vfl-prompt-hoehe-linienendpunkt" - (list (rtos (vfl-gf-hoehe-vorschlag (caddr p-aktuell)) 2 1))))) - (if (null hoehe-neu) (setq hoehe-neu (vfl-gf-hoehe-vorschlag (caddr p-aktuell)))) - (setq deltaH (- hoehe-neu (caddr p-aktuell))) - (setq richtung (if (>= deltaH 0.0) "Auf" "Ab")) - (setq deltaH (abs deltaH)) - (cond - ((= richtung "Auf") - (vfl-meldung (ssg-text "vfl-alert-gf-kann-nicht-steigen")) - (if dbg-an - (dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Linie-GF kann nicht steigen" - " (deltaL=" (rtos deltaL 2 0) " deltaH=" (rtos deltaH 2 0) ")"))) - (setq gf-ok nil)) - (t - (setq winkel (* (atan (/ deltaH deltaL)) (/ 180.0 pi))) - (if (> winkel gf-max-winkel) - (progn - (vfl-meldung (ssg-textf "vfl-alert-gefaelle-zu-steil" - (list (rtos winkel 2 1) (rtos gf-max-winkel 2 1)))) - (if dbg-an - (dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Linie-GF zu steil (deltaL=" - (rtos deltaL 2 0) " deltaH=" (rtos deltaH 2 0) - " winkel=" (rtos winkel 2 1) " max=" (rtos gf-max-winkel 2 1) ")"))) - (setq gf-ok nil)) - ) - ) - ) + (setq frame (make-frame-from-dir + (gf-insert-hz-with-ks "Staustrecke_Separator_SP_300_mm" + (car frame) hz-neu winkel *vfk-separator-laenge* 0) + (hz-winkel->xu hz-neu winkel))) + (setq *vfl-acc-separator* (1+ *vfl-acc-separator*)) ) ) - (if gf-ok + (if dbg-an + (dbgmsg (strcat "GEOMETRIE: vfl-insert-gf-segment (deltaL=" (rtos deltaL 2 0) + " hz=" (rtos hz-neu 2 1) " winkel=" (rtos winkel 2 1) ")"))) + (setq frame (vfl-insert-gf-segment (car frame) hz-neu deltaL winkel)) + (setq anzahl-gf (1+ anzahl-gf)) + (vfl-acc-gf-seg (/ deltaL (cos (* (float winkel) (/ pi 180.0)))) winkel) + (setq letzter-typ "GF") + (setq p-aktuell (car frame)) + (if dbg-an + (dbgmsg (strcat "GEOMETRIE ERFOLG: GF-Segment gebaut, neue Position=" + (dbg-tostring p-aktuell)))) + ;; Separator NACH (Antwort aus der Vorab-Frage oben) - DIREKT + ;; nach dem GF-Segment einfuegen, noch VOR der Kettenende- + ;; Frage. + (if sep-nach-gf (progn - (if dbg-an - (dbgmsg (strcat "GEOMETRIE: vfl-insert-gf-segment (deltaL=" (rtos deltaL 2 0) - " hz=" (rtos hz-neu 2 1) " winkel=" (rtos winkel 2 1) ")"))) - (setq frame (vfl-insert-gf-segment (car frame) hz-neu deltaL winkel)) - (setq anzahl-gf (1+ anzahl-gf)) - (vfl-acc-gf-seg (/ deltaL (cos (* (float winkel) (/ pi 180.0)))) winkel) - (setq letzter-typ "GF") + (setq frame (make-frame-from-dir + (gf-insert-hz-with-ks "Staustrecke_Separator_SP_300_mm" + (car frame) hz-neu winkel *vfk-separator-laenge* 0) + (hz-winkel->xu hz-neu winkel))) + (setq *vfl-acc-separator* (1+ *vfl-acc-separator*)) + (setq letzter-typ "Separator") (setq p-aktuell (car frame)) - (if dbg-an - (dbgmsg (strcat "GEOMETRIE ERFOLG: GF-Segment gebaut, neue Position=" - (dbg-tostring p-aktuell)))) - (princ (ssg-text "vfl-ist-kettenende-header")) - (princ (ssg-text "vfl-kettenende-opt-ja-es")) - (princ (ssg-text "vfl-kettenende-opt-ja-ohne-es")) - (princ (ssg-text "vfl-kettenende-opt-nein")) - (setq antwort (vfl-menu (ssg-text "vfl-prompt-wahl-1-3-def3") - (list (ssg-text "vfl-kettenende-opt-ja-es") - (ssg-text "vfl-kettenende-opt-ja-ohne-es") - (ssg-text "vfl-kettenende-opt-nein")) 3 - "vfl-ist-kettenende-header")) - (cond - ((= antwort "1") - (setq es-seite (vfl-frage-es-seite)) - (setq frame (vfl-insert-es-element "GF" frame hz-neu winkel - (caddr (car frame)) es-seite)) - (setq fertig t)) - ((= antwort "2") (setq fertig t)) - ) ) ) + (princ (ssg-text "vfl-ist-kettenende-header")) + (princ (ssg-text "vfl-kettenende-opt-ja-es")) + (princ (ssg-text "vfl-kettenende-opt-ja-ohne-es")) + (princ (ssg-text "vfl-kettenende-opt-nein")) + (setq antwort (vfl-menu (ssg-text "vfl-prompt-wahl-1-3-def3") + (list (ssg-text "vfl-kettenende-opt-ja-es") + (ssg-text "vfl-kettenende-opt-ja-ohne-es") + (ssg-text "vfl-kettenende-opt-nein")) 3 + "vfl-ist-kettenende-header")) + (cond + ((= antwort "1") + (setq es-seite (vfl-frage-es-seite)) + (setq frame (vfl-insert-es-element "GF" frame hz-neu winkel + (caddr (car frame)) es-seite)) + (setq fertig t)) + ((= antwort "2") (setq fertig t)) + ) ) ) ) diff --git a/Lisp/vf_spec.lsp b/Lisp/vf_spec.lsp index d9fe841..6b620c8 100644 --- a/Lisp/vf_spec.lsp +++ b/Lisp/vf_spec.lsp @@ -49,6 +49,12 @@ (setq *vsp-tab-seite* '(("links" . "1") ("rechts" . "2"))) (setq *vsp-tab-variante* '(("aussen" . "1") ("innen" . "2"))) (setq *vsp-tab-gefaelle* '(("hoehe" . "1") ("winkel" . "2"))) +;; Vorwahl "Wie soll die Laenge der Gefaellestrecke bestimmt werden?" +;; (vfl-menu in vf-linienzug-modus, wahl="Linie-GF"). Die innere Wahl im +;; Steigend-Sonderfall (Endpunkt/Hoehe) nutzt dieselbe Tabelle - dort sind +;; nur die Codes "1"/"2" gueltig, "steigend" kommt dort nie vor. +(setq *vsp-tab-gf-laenge-modus* '(("endpunkt" . "1") ("hoehe" . "2") + ("steigend" . "3"))) (setq *vsp-tab-verteilung* '(("haelfte" . "1") ("einlauf" . "2"))) (setq *vsp-tab-im-vf* '(("horizontal" . "1") ("vario-kurve" . "2") ("auf-ab" . "3"))) @@ -219,7 +225,7 @@ (vsp-wert-str (ssg-val kopf "as_winkel") "as_winkel") (vsp-code-raus *vsp-tab-seite* (ssg-val kopf "as_seite") "as_seite")))) -(defun vsp-sektion (sek subs erster / label tab code art) +(defun vsp-sektion (sek subs erster / label tab code art modus smodus) (setq label (ssg-val sek "glied")) (setq *vsp-wo* (strcat "Sektion " (if label label "(ohne glied)"))) (cond @@ -250,12 +256,45 @@ (vsp-code-raus *vsp-tab-seite* (ssg-val sek "seite") "seite")) ((= label "Linie-GF") - (vsp-linie sek) - (setq art (ssg-val sek "gefaelle")) - (vsp-code-raus *vsp-tab-gefaelle* art "gefaelle") - (if (= art "hoehe") - (vsp-zahl "REAL" (ssg-val sek "hoehe") "hoehe") - (vsp-zahl "REAL" (ssg-val sek "winkel") "winkel")) + ;; Vorwahl "laenge_modus" (endpunkt/hoehe/steigend, Default + ;; "endpunkt" - deckt alte Specs ohne dieses Feld ab, identisch + ;; zum bisherigen alleinigen Ablauf). "steigend" ist der + ;; Sonderfall (GF bis 3 Grad steigend, KEIN Separator, eigene + ;; innere Endpunkt/Hoehe-Wahl "steigend_modus"). Die DL(+hz)- + ;; Eingabe (vsp-linie) faellt bei "hoehe"/"steigend"+"hoehe" NUR + ;; beim allerersten Segment der Kette an (Kettenanfang braucht + ;; einen Pick fuer die freie Richtung; jedes weitere Segment erbt + ;; die Richtung vom Vorgaenger ohne eigenen Pick) - siehe erster. + (setq modus (ssg-val sek "laenge_modus")) + (if (null modus) (setq modus "endpunkt")) + (vsp-code-raus *vsp-tab-gf-laenge-modus* modus "laenge_modus") + (cond + ((= modus "steigend") + (setq smodus (ssg-val sek "steigend_modus")) + (if (null smodus) (setq smodus "endpunkt")) + (vsp-code-raus *vsp-tab-gf-laenge-modus* smodus "steigend_modus") + (if (= smodus "endpunkt") + (vsp-linie sek) + (if erster (vsp-linie sek))) + (if (= smodus "hoehe") + (vsp-zahl "REAL" (ssg-val sek "hoehe") "hoehe")) + (vsp-zahl "REAL" (ssg-val sek "winkel") "winkel")) + ((= modus "hoehe") + (if erster (vsp-linie sek)) + (vsp-raus "STR" (vsp-ja-code (ssg-val sek "sep_vor"))) + (vsp-raus "STR" (vsp-ja-code (ssg-val sek "sep_nach"))) + (vsp-zahl "REAL" (ssg-val sek "hoehe") "hoehe") + (vsp-zahl "REAL" (ssg-val sek "winkel") "winkel")) + (T ;; "endpunkt" + (vsp-linie sek) + (vsp-raus "STR" (vsp-ja-code (ssg-val sek "sep_vor"))) + (vsp-raus "STR" (vsp-ja-code (ssg-val sek "sep_nach"))) + (setq art (ssg-val sek "gefaelle")) + (vsp-code-raus *vsp-tab-gefaelle* art "gefaelle") + (if (= art "hoehe") + (vsp-zahl "REAL" (ssg-val sek "hoehe") "hoehe") + (vsp-zahl "REAL" (ssg-val sek "winkel") "winkel"))) + ) (vsp-code-raus *vsp-tab-ende3* (ssg-val sek "ende") "ende")) ((= label "Linie-VF") diff --git a/doc/vf_linienzug_modus1_anleitung.md b/doc/vf_linienzug_modus1_anleitung.md new file mode 100644 index 0000000..371b0ba --- /dev/null +++ b/doc/vf_linienzug_modus1_anleitung.md @@ -0,0 +1,194 @@ +# VF-Linienzug Modus 1 - Anleitung anhand eines realen Beispiels + +Diese Anleitung erklaert Modus 1 des VF-Linienzugs ("Manuelle Eingabe", +`vf-linienzug-modus` in `Lisp/vf_linienzug.lsp`) nicht abstrakt, sondern +Schritt fuer Schritt anhand eines echten, in BricsCAD gebauten und +verifizierten Beispiels. Dasselbe Beispiel ist als automatisierter +Regressionstest hinterlegt: `tests/testdata/linienzug_tests_stufe2.json`, +aufrufbar in BricsCAD ueber `TEST_LINIENZUG_STUFE2` (siehe +`tests/test_linienzug.lsp`). Wer die Zahlen selbst nachvollziehen will, kann +diesen Testlauf jederzeit wiederholen. + +Fuer die Architektur-Details (Frame-Kette, Journal/XDATA, Wizard-Dialoge) +siehe `doc/variofoerderer.md` und `doc/VarioFoerderer_Linienzug_Prinzipien.md`. +Diese Anleitung konzentriert sich auf die Bedienung und die typischen +Stolperfallen. + +## 1. Was ist Modus 1? + +Modus 1 baut eine VF-Linienzug-Kette **interaktiv, Element fuer Element**: +Sie klicken/geben nacheinander Punkte, Laengen, Winkel und Ja/Nein- +Entscheidungen ein, und jede Antwort erzeugt sofort ein Stueck Geometrie +(Umlenkstation, Gefaellestrecke, VF-Einheit, Vario-Kurve, ...). Das +unterscheidet Modus 1 von: + +- **Modus 2** (Ziel-Hoehe-Modus): Sie zeichnen vorher einen Pfad (LINE/ARC- + Kette) und eine Zielhoehe, das Programm loest die Kette automatisch. +- **Modus 3** (Vorwaerts-Nachbau): rekonstruiert eine Kette aus einer + bestehenden Zeichnung (Legacy, wird umgebaut). + +**Aufruf:** Befehl `FOERDERANLAGE` (bzw. `VarioFoerderer`) -> Anlagen-Typ +"Linienzug" waehlen -> Modus "1" (Manuelle Eingabe). + +Jede Eingabe wird in einem Journal mitgeschrieben und als XDATA auf den +fertigen `VF_n`-Block geschrieben. Ein Abbruch (ESC) loescht dadurch nichts - +die bis dahin gebaute Teilkette bleibt als Block stehen und ist per +Doppelklick weiter bearbeitbar. + +## 2. Das Beispiel im Ueberblick + +Die Kette aus `linienzug_tests_stufe2.json` sieht so aus (Grundriss, von +oben betrachtet): + +``` +Start (Z=2154) --AS 90 links--> Horizontal-VF --Vario 90 rechts innen--> + --Horizontal-VF--> --Vario 90 rechts innen--> Linie-GF (3 Grad, Separator + vor+nach) --> Horizontal-VF --Vario 30 rechts innen--> Auf 27 Grad + --Vario 30 links aussen--> Kettenende (Motorstation + ES 90 links) +``` + +Kurz gesagt: die Kette startet auf Z=2154, macht zwei 90-Grad-Schwenks im +Grundriss, faellt ueber ein eigenstaendiges GF-Stueck (3 Grad) etwas ab, +steigt dann in einer zweiten VF-Einheit um 27 Grad wieder an, und muss am +Ende exakt auf die Starthoehe (2154mm) zurueckkommen. Genau dieser letzte +Schritt - der Rest-Hoehenausgleich passt zu keinem der Standard-Winkel mehr - +ist der interessante Teil (siehe Abschnitt 4, Stufe 1/Stufe 2). + +## 3. Die Fragen der Reihe nach + +Diese Tabelle zitiert die tatsaechlichen Konsolen-Fragen aus dem Testfall +(Kommentar-Spalte = `kommentar`-Feld im JSON) und erklaert, was jede Frage +bedeutet. + +| # | Frage (sinngemaess) | Antwort im Beispiel | Bedeutung | +|---|---|---|---| +| 1 | Startpunkt der Kette | Klick (X/Y) | Beliebiger Punkt in der Zeichnung | +| 2 | Hoehe (Z) Startpunkt | 2154 | Absolute Einbauhoehe des Kettenanfangs | +| 3 | AS-Element setzen? | Ja | Umlenkstation am Anfang - "AS" = Ausgangs-/Anfangs-Element | +| 4 | AS-Element Winkel | 90 Grad | Baugroesse des Umlenkstations-Blocks (90 oder 30 Grad Kurvenradius) | +| 5 | AS-Seite | links | Auf welcher Seite die Umlenkstation seitlich ausschwenkt | +| 6 | Naechstes Element | Horizontal-VF | Kettenanfang-Menue hat 6 Optionen (GF-Bogen, Vario-Kurve, Linie-GF, Linie-VF, Horizontal-VF, Linienende) | +| 7 | Endpunkt Horizontal-VF | Klick | Laenge/Richtung der ersten VF-Einheit | +| 8 | Ziel-Hoehe Eingang GF1 | 1980 | Hoehe, auf die der Eingangs-GF-Anteil der VF-Einheit einregelt | +| 9 | Ist der Endpunkt der Foerderer? | Nein | Innerhalb der VF-Einheit: "geht die Kette hier zu Ende?" | +| 10-13 | Naechstes im VarioFoerderer -> Vario-Kurve 90 rechts innen | - | Grundriss-Schwenk INNERHALB der laufenden VF-Einheit | +| 14 | Ist der Endpunkt der Foerderer? | Nein | s.o. | +| 15-16 | Horizontaler Foerderer, Endpunkt | Klick | Weiteres gerades VF-Sub-Segment | +| 17 | Ist der Endpunkt der Foerderer? | Nein | s.o. | +| 18-21 | Vario-Kurve 90 rechts innen | - | Zweiter Grundriss-Schwenk | +| 22 | Ist der Endpunkt der Foerderer? | Ja, nur Motorstation | Schliesst die 1. VF-Einheit ab (Motorstation, kein ES) | +| 23 | Ist das Kettenende (ES-Element)? | Nein, weiterbauen | Nach der Motorstation: geht die Kette weiter? | +| 24 | Zusaetzlichen Separator hier? | Nein | Optionaler Separator direkt an der Motorstation | +| 25 | Naechstes Element | Neue Linie: GF | Jetzt folgt ein reines Gefaellestrecke-Element (kein VF) | +| 26 | Laenge bestimmen ueber | Endpunkt waehlen | Alternativ: Zielhoehe zuerst festlegen (siehe `vfl-gf-laenge-modus`) | +| 27 | Endpunkt Gefaellestrecke | Klick | Bestimmt deltaL der GF-Geraden | +| 28-29 | Separator VOR / NACH? | Ja / Ja | GF-Stuecke werden ueber Separatoren mechanisch begrenzt | +| 30-31 | Gefaelle festlegen -> Neigungswinkel | 3 Grad | Direkter Winkel (Alternative: Zielhoehe -> Winkel wird berechnet) | +| 32 | Ist das Kettenende? | Nein, weiterbauen | 3-Optionen-Menue: Ja+ES / Ja ohne ES / Nein | +| 33 | Naechstes Element | Horizontal-VF (2. Einheit) | Neue VF-Einheit beginnt direkt am GF-Ausgang (Separator als Eingang) | +| 34-49 | (wie oben: Vario-Kurven, Auf/Ab-Foerderer 27 Grad) | - | s.o. | +| 50 | Ist der Endpunkt der Foerderer? | Ja, Motorstation + Kettenende | Startet den automatischen Kettenende-Abschluss | +| 51-53 | ES-Element setzen? / Winkel / Seite | Ja / 90 / links | Ausgangs-Element, spiegelbildlich zum AS-Element | +| 54 | Endpunkt Kettenende | Klick | Letzter gewuenschter Punkt der Kette | +| 55 | Ziel-Hoehe Kettenende | 2154 | Muss wieder exakt die Starthoehe sein | +| 56 | (automatisch, keine Eingabe) | - | Stufe-1/Stufe-2-Loeser rechnet, siehe Abschnitt 4 | +| 57 | GF2=86mm unter Mindestlaenge - trotzdem bauen? | Ja | Sicherheitsfrage, siehe Abschnitt 4 | + +**Wichtig zu Frage 9/14/17 ("Ist der Endpunkt der Foerderer?"):** Diese Frage +kommt IMMER, wenn Sie innerhalb einer laufenden VF-Einheit einen Punkt +gepickt haben. "Nein" heisst: der gepickte Punkt ist nur eine +Zwischen-Etappe, es geht in derselben VF-Einheit weiter (Vario-Kurve, +weiteres gerades Stueck, Auf/Ab). "Ja" beendet die VF-Einheit mit einer +Motorstation - optional zusaetzlich mit ES-Element (echtes Kettenende). + +## 4. Stufe 1 / Stufe 2 - wie die letzte Strecke berechnet wird + +Am Kettenende (Frage 55/56) ist die Aufgabe: von der aktuellen Position/ +Hoehe zur gewuenschten Ziel-Hoehe kommen, UND dabei die feste Baugruppe +Motorstation + Separator (+ ES-Element, falls gewaehlt) unterbringen. Das +Programm versucht das in zwei Stufen (`vfl-winkel-stufe-entscheidung`, +`Lisp/vf_linienzug.lsp`): + +- **Stufe 1 - Winkeltabelle:** Es probiert der Reihe nach die festen + Vario-Kurven-Winkel 3/6/9/12/15/18/21/27/33/39/45/51 Grad durch + (`berechne-alle-winkel`) und prueft, ob einer davon eine PASSENDE, + positive VF-Laenge ergibt. Das ist der Normalfall (siehe die Zeilen + "34-49" in der Tabelle: der Auf/Ab-Foerderer in der 2. VF-Einheit findet + so einen passenden Winkel, Auswahl-Index 8 = 27 Grad). +- **Stufe 2 - Horizontale Mitte + GF-Ausgleich:** Wenn KEIN Tabellenwinkel + passt (wie im Beispiel: alle 12 Kandidaten ergeben eine negative + VF-Laenge, die Reststrecke ist einfach zu kurz fuer einen echten + Vertikalbogen), rechnet `berechne-horizontale-mitte` eine andere Loesung: + ein HORIZONTALES VF-Stueck (winkel=0) plus eine kurze Gefaellestrecke + (GF2), die exakt den fehlenden Hoehenunterschied ausgleicht. Im Beispiel: + GF2=86mm, VF=286mm. + +Die Formel dahinter ist simpel und lohnt sich zu verinnerlichen: bei einem +FESTEN Neigungswinkel von 3 Grad braucht ein Hoehenausgleich von `H` +Millimetern eine GF-Laenge von `H / tan(3 Grad) ~= H * 19`. Ein winziger +Hoehenrest von z.B. 49mm braucht demnach bereits ~935mm GF-Laenge - siehe +Abschnitt 5.a. GF2 im Beispiel (86mm) ist kuerzer als die uebliche +Mindestlaenge (400mm, `*vfl-gf-min-laenge*`) - deshalb die Rueckfrage in +Schritt 57 ("trotzdem mit 86mm bauen?"). "Ja" baut trotzdem exakt mit dem +berechneten Wert; "Nein" wuerde den bis dahin gebauten Teil verwerfen (siehe +`vfl-loesche-seit`/`vfl-modus-abbruch-sichern`). + +**Merksatz:** Stufe 1 sucht einen "sauberen" Standard-Winkel; Stufe 2 ist +der Rettungsanker, wenn dafuer schlicht nicht genug Platz ist - er tauscht +einen Vertikalbogen gegen "flach + minimaler GF-Ausgleich". + +## 5. Typische Stolperfallen + +### a) Gefaellestrecke (GF) ist "laengenhungrig" bei 3 Grad + +3 Grad ist ein SEHR flacher Winkel. `tan(3 Grad) ~= 0.0524`, also braucht +jeder Millimeter Hoehenunterschied ungefaehr **19mm** Laenge: + +``` +Laenge = Hoehendifferenz / tan(3 Grad) ~= Hoehendifferenz * 19 +``` + +Wer nur wenige mm bis cm Hoehenunterschied ausgleichen will, bekommt damit +schnell GF-Laengen im halben bis ganzen Meterbereich. Das ist die haeufigste +Ursache fuer "die Reststrecke ist zu kurz"-Meldungen und fuer den +Stufe-2-Fallback aus Abschnitt 4. Falls eine GF partout nicht passt: entweder +mehr Grundriss-Laenge einplanen, oder pruefen, ob ein Vertikalbogen +(Vario-Kurve mit groesserem Winkel, siehe Stufe 1) die bessere Wahl ist. + +### b) Motorstation + Separator + ES-Element haben einen FESTEN Fussabdruck + +Am Kettenende ist nicht die ganze gepickte Reststrecke frei verfuegbar - +ein fester Anteil ist fuer Motorstation und Separator reserviert +(`*vfk-feste-horizontal-kettenende*` = 800mm = 500mm Motorstation + 300mm +Separator), und falls zusaetzlich ein ES-Element gewaehlt wird, kommt dessen +eigener Fussabdruck (abhaengig von 30/90 Grad Bauform) noch oben drauf. Wer +diese festen 800mm (+ ES) nicht einplant, landet schnell bei einer negativen +"freien" Reststrecke - genau der Fall, der in Abschnitt 4 den Stufe-2- +Fallback ausloest. + +### c) Seitlicher Versatz bei 90-Grad-AS/ES-Elementen ist NORMAL + +Ein AS- oder ES-Element mit 90 Grad Bauform dreht die Fahrtrichtung nicht +einfach "auf der Stelle" - sein Block hat eine reale Baugroesse mit einem +seitlichen (quer zur Fahrtrichtung liegenden) Versatz. Im Standard-Testfall +(`linienzug_tests.json`) betraegt dieser Versatz ca. 637mm (`dY`), im +Stufe-2-Testfall ca. 638mm (`dX`, da die Fahrtrichtung dort anders liegt). +**Das ist kein Baufehler** - es ist die tatsaechliche Geometrie des +90-Grad-ES/AS-Blocks. Ein "Ist-Ziel-Report" mit einer Restabweichung von +ca. 638mm quer zur letzten Fahrtrichtung, aber ~0mm in Laengs-/Hoehenrichtung, +ist ein KORREKTES Ergebnis, keine Abweichung, die weiter untersucht werden +muss. + +## 6. Nachvollziehen / Nachbauen + +- Live in BricsCAD: `FOERDERANLAGE` -> Linienzug -> Modus 1, und die + Eingaben aus der Tabelle in Abschnitt 3 der Reihe nach eintippen/klicken. +- Automatisiert (ohne Klicks): Befehl `TEST_LINIENZUG_STUFE2` (nach Laden + von `tests/test_linienzug.lsp`) spielt exakt dieselbe Sequenz aus + `tests/testdata/linienzug_tests_stufe2.json` ab und meldet das + Ist-Ziel-Ergebnis in der Konsole. +- Debug-Log: mit `dbgopen`/dem zentralen Debug-Schalter (siehe + `Lisp/ssg_dbg.lsp`) lassen sich alle `GEOMETRIE:`/`GEOMETRIE ERFOLG:`- + Zeilen in eine `.dbg`-Datei im Log-Verzeichnis mitschreiben - hilfreich, + um genau wie in diesem Beispiel eine reale Sitzung nachtraeglich in + einen Testfall zu verwandeln. diff --git a/lang/de_DE.json b/lang/de_DE.json index 6b41678..8da5ac2 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -32,6 +32,16 @@ "vfl-hinweis-hoehe-vorschlag-fehlt": "\n(Hinweis: In diese Richtung ist bei dieser Segmentlaenge kein baubarer Vorschlag gefunden - bitte Wert manuell pruefen.)", "vfl-alert-gf-kann-nicht-steigen": "Gefaellestrecke (GF) kann nicht steigen - bitte tiefere Zielhoehe waehlen oder Ab/Auf VF benutzen.", "vfl-alert-gefaelle-zu-steil": "Gefaelle zu steil fuer GF: %1 Grad (erlaubt max %2 Grad).\nBitte kleinere Hoehendifferenz waehlen oder Ab/Auf VF benutzen.", + "vfl-gf-laenge-modus-header": "\nWie soll die Laenge dieser Gefaellestrecke bestimmt werden?", + "vfl-gf-laenge-modus-opt-endpunkt": "\n 1 - Endpunkt waehlen (Laenge ergibt sich)", + "vfl-gf-laenge-modus-opt-hoehe": "\n 2 - Zielhoehe festlegen (Laenge wird berechnet)", + "vfl-gf-laenge-modus-opt-steigend": "\n 3 - Steigende GF (Sonderfall, bis 3 Grad)", + "vfl-prompt-neigungswinkel-laenge-berechnet": "\nNeigungswinkel (Grad, 0 < Winkel <= %1) [%1] (deltaL wird berechnet und eingefuegt): ", + "vfl-prompt-neigungswinkel-steigend": "\nSteigungswinkel (Grad, 0 <= Winkel <= %1, 0 = flach) [%1]: ", + "vfl-alert-winkel-ungueltig-steigend": "Steigungswinkel ungueltig: %1 Grad. Erlaubt: 0 <= Winkel <= %2 Grad.", + "vfl-alert-gf-steigend-falsche-richtung": "Steigende Gefaellestrecke: die gewaehlte Zielhoehe liegt unter der aktuellen Hoehe - bitte hoehere Zielhoehe waehlen oder den normalen (fallenden) Modus benutzen.", + "vfl-sep-vor-gf-frage": "\n\nSeparator VOR dieser Gefaellestrecke einfuegen?", + "vfl-sep-nach-gf-frage": "\n\nSeparator NACH dieser Gefaellestrecke einfuegen?", "vfl-ist-kettenende-header": "\nIst das das Kettenende?", "vfl-kettenende-opt-ja-es": "\n 1 - Ja (ES-Element setzen)", "vfl-kettenende-opt-ja-ohne-es": "\n 2 - Ja (ohne ES-Element setzen)", diff --git a/lang/en_GB.json b/lang/en_GB.json index e58028c..53314f5 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -32,6 +32,16 @@ "vfl-hinweis-hoehe-vorschlag-fehlt": "\n(Note: no buildable suggestion found for that direction at this segment length - please check the value manually.)", "vfl-alert-gf-kann-nicht-steigen": "Gravity section (GF) cannot rise - please pick a lower target height or use down/up VF.", "vfl-alert-gefaelle-zu-steil": "Slope too steep for GF: %1 degrees (max allowed %2 degrees).\nPlease pick a smaller height difference or use down/up VF.", + "vfl-gf-laenge-modus-header": "\nHow should the length of this gravity section be determined?", + "vfl-gf-laenge-modus-opt-endpunkt": "\n 1 - Pick end point (length results from it)", + "vfl-gf-laenge-modus-opt-hoehe": "\n 2 - Set target height (length will be calculated)", + "vfl-gf-laenge-modus-opt-steigend": "\n 3 - Rising GF (special case, up to 3 degrees)", + "vfl-prompt-neigungswinkel-laenge-berechnet": "\nIncline angle (degrees, 0 < angle <= %1) [%1] (deltaL will be calculated and inserted): ", + "vfl-prompt-neigungswinkel-steigend": "\nRising angle (degrees, 0 <= angle <= %1, 0 = level) [%1]: ", + "vfl-alert-winkel-ungueltig-steigend": "Rising angle invalid: %1 degrees. Allowed: 0 <= angle <= %2 degrees.", + "vfl-alert-gf-steigend-falsche-richtung": "Rising gravity conveyor: the chosen target height is below the current height - please choose a higher target height or use the normal (falling) mode.", + "vfl-sep-vor-gf-frage": "\n\nInsert separator BEFORE this gravity section?", + "vfl-sep-nach-gf-frage": "\n\nInsert separator AFTER this gravity section?", "vfl-ist-kettenende-header": "\nIs this the chain end?", "vfl-kettenende-opt-ja-es": "\n 1 - Yes (place ES element)", "vfl-kettenende-opt-ja-ohne-es": "\n 2 - Yes (without placing ES element)", diff --git a/tests/test_linienzug.lsp b/tests/test_linienzug.lsp index b101960..5d269df 100644 --- a/tests/test_linienzug.lsp +++ b/tests/test_linienzug.lsp @@ -33,7 +33,10 @@ ;; ;; Aufruf in BricsCAD: ;; (load (strcat (getenv "DXFMAKRO") "/tests/test_linienzug.lsp")) -;; TEST_LINIENZUG +;; TEST_LINIENZUG (Standard-Szenario, linienzug_tests.json, in +;; alltests.json/SSG_RUN_ALL_TESTS registriert) +;; TEST_LINIENZUG_STUFE2 (Stufe-2-Fallback-Regressionstest Kettenende, +;; linienzug_tests_stufe2.json, manueller Aufruf) ;; ============================================================ ;; Naechste Skript-Antwort liefern (FIFO). Bei leerer Queue Warnung + "". @@ -126,8 +129,17 @@ ) ) -(defun c:TEST_LINIENZUG ( / json-datei daten meta schritte-anz rest-anz status - old-vgp old-gs old-gi old-gr err) +;; --- Gemeinsamer Replay-Kern, von allen c:TEST_LINIENZUG*-Befehlen genutzt --- +;; befehl-anzeige: nur fuer die Konsolen-Ueberschrift/ssg-start (String). +;; test-id: Bezeichner fuer das *linienzug-test-results*-JSON (run-all- +;; Konvention, siehe linienzug:export-results). +;; Rueckgabe: (status schritte-anz rest-anz) - status ist "OK"/"ABWEICHUNG"/ +;; "FEHLER". Setzt zusaetzlich *linienzug-test-results* (letzter Aufruf +;; gewinnt - identisch zum bisherigen Verhalten von c:TEST_LINIENZUG, das +;; ebenfalls nur einen einzigen Testfall pro Lauf kennt). +(defun tlz-run-datei (json-datei test-id befehl-anzeige / + daten meta schritte-anz rest-anz status + old-vgp old-gs old-gi old-gr err) (if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek)) (if (null (car (atoms-family 1 '("VF-LINIENZUG-MODUS")))) (progn @@ -138,12 +150,10 @@ ) (princ "\n================================================================") - (princ "\n TEST_LINIENZUG - VF-Linienzug Replay-Test") + (princ (strcat "\n " befehl-anzeige " - VF-Linienzug Replay-Test")) (princ "\n================================================================") ;; --- Eingabe-Skript aus JSON laden --- - (setq json-datei - (strcat (getenv "DXFMAKRO") "\\tests\\testdata\\linienzug_tests.json")) (setq daten (ssg-load-json json-datei)) (if (null daten) (progn @@ -158,7 +168,7 @@ (princ (strcat "\n Eingabe-Schritte: " (itoa schritte-anz))) (if meta (princ (strcat "\n Szenario: " (ssg-val meta "beschreibung")))) - (ssg-start "TEST_LINIENZUG" '(("OSMODE") ("ATTREQ") ("ATTDIA"))) + (ssg-start befehl-anzeige '(("OSMODE") ("ATTREQ") ("ATTDIA"))) (setvar "OSMODE" 0) (setvar "ATTREQ" 0) (setvar "ATTDIA" 0) @@ -192,11 +202,11 @@ (if (vl-catch-all-error-p err) (progn (setq status "FEHLER") - (princ (strcat "\n TEST_LINIENZUG: FEHLER -> " + (princ (strcat "\n " befehl-anzeige ": FEHLER -> " (vl-catch-all-error-message err)))) (progn (setq status (if (= rest-anz 0) "OK" "ABWEICHUNG")) - (princ "\n TEST_LINIENZUG: Kette gebaut.") + (princ (strcat "\n " befehl-anzeige ": Kette gebaut.")) (if (> rest-anz 0) (princ (strcat "\n WARNUNG: " (itoa rest-anz) " Skript-Eintraege NICHT verbraucht (Ablauf weicht ab).")) @@ -214,12 +224,34 @@ ;; --- Ergebnis fuer run-all als JSON-String ablegen --- (setq *linienzug-test-results* (strcat " {\n" - " \"test_id\": \"LZ_Standard\",\n" + " \"test_id\": \"" test-id "\",\n" " \"status\": \"" status "\",\n" " \"schritte_gesamt\": " (itoa schritte-anz) ",\n" " \"schritte_offen\": " (itoa rest-anz) "\n" " }")) (ssg-end) + (list status schritte-anz rest-anz) +) + +(defun c:TEST_LINIENZUG ( / json-datei) + (setq json-datei + (strcat (getenv "DXFMAKRO") "\\tests\\testdata\\linienzug_tests.json")) + (tlz-run-datei json-datei "LZ_Standard" "TEST_LINIENZUG") + (princ) +) + +;; Zweiter Testfall (Regressionstest, in dieser Session neu aufgenommen): +;; Stufe-2-Fallback beim Kettenende (typ=VF winkel=0, horizontales VF + GF- +;; Ausgleich), siehe Beschreibung/Kommentare in linienzug_tests_stufe2.json. +;; Eigenstaendiger Befehl statt zweitem Array-Eintrag in linienzug_tests.json, +;; damit LZ_Standard unangetastet bleibt (jede JSON-Datei bleibt ein +;; einzelnes Szenario, wie von tlz-daten->queue/-meta erwartet). NICHT in +;; tests/alltests.json registriert - manueller Einzelaufruf, bei Bedarf +;; spaeter dort ergaenzen. +(defun c:TEST_LINIENZUG_STUFE2 ( / json-datei) + (setq json-datei + (strcat (getenv "DXFMAKRO") "\\tests\\testdata\\linienzug_tests_stufe2.json")) + (tlz-run-datei json-datei "LZ_Stufe2_Kettenende" "TEST_LINIENZUG_STUFE2") (princ) ) diff --git a/tests/test_vf_spec.lsp b/tests/test_vf_spec.lsp index 4737279..071b63a 100644 --- a/tests/test_vf_spec.lsp +++ b/tests/test_vf_spec.lsp @@ -125,20 +125,63 @@ (defun tsp-tests-glieder ( / spec) ;; --- Linie-GF, Kettenende mit ES --- - ;; Erste Sektion: Menue-Code "1" (4 Optionen, ohne Frame). + ;; Erste Sektion: Menue-Code "1" (4 Optionen, ohne Frame). laenge_modus + ;; "endpunkt" (Default, Zweig A): DL(+hz), dann Separator vor/nach, dann + ;; die Gefaelle-Wahl (Hoehe/Winkel) wie bisher. (setq spec (tsp-spec T (list - (tsp-sek (list (cons "glied" "Linie-GF") (cons "dl" 5500.0) - (cons "hz" 0.0) (cons "gefaelle" "winkel") + (tsp-sek (list (cons "glied" "Linie-GF") (cons "laenge_modus" "endpunkt") + (cons "dl" 5500.0) (cons "hz" 0.0) + (cons "sep_vor" 0) (cons "sep_nach" 0) + (cons "gefaelle" "winkel") (cons "winkel" 3.0) (cons "ende" "ja-mit-es")) nil) (tsp-es "90" "rechts")))) (tsp-journal-eq "Linie-GF + ES" (append (tsp-pre-mit-as) (list (cons "STEP" "Linie-GF") (cons "STR" "1") + (cons "STR" "1") ; laenge_modus: endpunkt (cons "DL" 5500.0) (cons "REAL" 0.0) + (cons "STR" "2") (cons "STR" "2") ; Separator vor / nach (cons "STR" "2") (cons "REAL" 3.0) (cons "STR" "1") (cons "STEP" "ES") (cons "STR" "90") (cons "STR" "2"))) (vsp-journal spec)) + ;; --- Linie-GF, Zielhoehe zuerst (laenge_modus "hoehe") --- + ;; Kein Separator-Paar VOR der Hoehen-/Winkel-Eingabe wie im Endpunkt-Zweig + ;; - hier stehen sie NACH DL(+hz), aber vor Hoehe/Winkel (siehe + ;; vf-linienzug-modus, Zweig B). + (setq spec (tsp-spec T (list + (tsp-sek (list (cons "glied" "Linie-GF") (cons "laenge_modus" "hoehe") + (cons "dl" 100.0) (cons "hz" 0.0) + (cons "sep_vor" 1) (cons "sep_nach" 0) + (cons "hoehe" 1800.0) (cons "winkel" 3.0) + (cons "ende" "nein")) nil)))) + (tsp-journal-eq "Linie-GF, Zielhoehe zuerst" + (append (tsp-pre-mit-as) + (list (cons "STEP" "Linie-GF") (cons "STR" "1") + (cons "STR" "2") ; laenge_modus: hoehe + (cons "DL" 100.0) (cons "REAL" 0.0) + (cons "STR" "1") (cons "STR" "2") ; Separator vor(ja)/nach(nein) + (cons "REAL" 1800.0) (cons "REAL" 3.0) + (cons "STR" "3"))) + (vsp-journal spec)) + + ;; --- Linie-GF, Sonderfall Steigend, Endpunkt-Unterwahl --- + ;; KEIN Separator-Feld (die steigende GF kennt keine Separator-Frage). + (setq spec (tsp-spec T (list + (tsp-sek (list (cons "glied" "Linie-GF") (cons "laenge_modus" "steigend") + (cons "steigend_modus" "endpunkt") + (cons "dl" 800.0) (cons "hz" 0.0) + (cons "winkel" 2.0) (cons "ende" "nein")) nil)))) + (tsp-journal-eq "Linie-GF, Steigend + Endpunkt" + (append (tsp-pre-mit-as) + (list (cons "STEP" "Linie-GF") (cons "STR" "1") + (cons "STR" "3") ; laenge_modus: steigend + (cons "STR" "1") ; steigend_modus: endpunkt + (cons "DL" 800.0) (cons "REAL" 0.0) + (cons "REAL" 2.0) + (cons "STR" "3"))) + (vsp-journal spec)) + ;; --- Horizontal-VF: horizontaler Erstkoerper --- ;; Nur hier stehen Separator vor/nach VOR der Endpunkt-Antwort ;; (vfl-baue-horizontal-koerper im Ziel-Modus). @@ -187,7 +230,8 @@ ;; --- GF-Bogen als ZWEITE Sektion --- ;; Ab der zweiten Sektion gilt die 5-Optionen-Tabelle: Linie-GF ist dort - ;; "2" (nicht mehr "1"), GF-Bogen ist "1". + ;; "2" (nicht mehr "1"), GF-Bogen ist "1". laenge_modus/sep_vor/sep_nach + ;; fehlen absichtlich (Default "endpunkt"/nein/nein, deckt alte Specs ab). (setq spec (tsp-spec T (list (tsp-sek (list (cons "glied" "Linie-GF") (cons "dl" 3000.0) (cons "hz" 180.0) (cons "gefaelle" "hoehe") @@ -197,7 +241,9 @@ (tsp-journal-eq "GF-Bogen als zweite Sektion (Menue-Codes am Frame)" (append (tsp-pre-mit-as) (list (cons "STEP" "Linie-GF") (cons "STR" "1") + (cons "STR" "1") ; laenge_modus: endpunkt (Default) (cons "DL" 3000.0) (cons "REAL" 180.0) + (cons "STR" "2") (cons "STR" "2") ; Separator vor/nach (Default: nein) (cons "STR" "1") (cons "REAL" 2500.0) (cons "STR" "3") (cons "STEP" "GF-Bogen") (cons "STR" "1") (cons "INT" 60) (cons "STR" "2"))) diff --git a/tests/testdata/linienzug_tests_stufe2.json b/tests/testdata/linienzug_tests_stufe2.json new file mode 100644 index 0000000..8e2d56d --- /dev/null +++ b/tests/testdata/linienzug_tests_stufe2.json @@ -0,0 +1,295 @@ +[ + { + "beschreibung": "Stufe-2-Fallback-Regressionstest (Kettenende): Start Z2154 AS 90 Grad links -> Horizontal-VF (2x Vario-Kurve 90 rechts innen) -> Linie-GF 3 Grad (Sep vor+nach) -> Horizontal-VF (Vario-Kurve 30 rechts innen -> Auf 27 Grad -> Vario-Kurve 30 links aussen) -> Kettenende ueber 'Ist der Endpunkt der Foerderer?'-Option 3 mit ES 90 Grad links; GF2 (86mm) liegt unter der Mindestlaenge (400mm), Stufe 1 (Winkeltabelle) scheitert komplett (alle Kandidaten negative Laenge) -> Stufe 2 (vfl-winkel-stufe-entscheidung) liefert typ=VF winkel=0 (horizontales VF + GF-Ausgleich). Reproduziert den in dieser Session gefixten Bug (Stufe-2-Fallback griff im Kettenende-Zweig 'Ab, steiler als 3 Grad' bisher nicht, wenn Stufe 1 scheiterte) - erster erfolgreicher Stufe-2-Bau der Session, live in BricsCAD verifiziert.", + "erwartung_dx_mm": 638, + "erwartung_dy_mm": 0, + "erwartung_dz_mm": 0, + "erwartung_hinweis": "dX~638 = seitlicher Fussabdruck ES_Element_90 quer zur letzten Fahrtrichtung (Bauform des 90-Grad-ES-Elements, kein Baufehler). Kernzweck des Testfalls: Stufe-2-Fallback beim Kettenende (typ=VF winkel=0 im Kettenende-Abschluss, GF2=86mm unter Mindestlaenge -> 'trotzdem mit 86mm bauen?'=Ja)." + }, + { + "typ": "point_abs", + "wert": [24377.817458, -10300, 2067], + "kommentar": "getpoint Startpunkt der Kette (Rohwert aus dem realen BricsCAD-Testlauf)" + }, + { + "typ": "real", + "wert": 2154.0, + "kommentar": "getreal Hoehe (Z) Startpunkt" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring AS-Element setzen? (Ja)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring AS-Element Winkel - Menu-Index 1=90 Grad/2=30 Grad (vf-frage-element-winkel mappt den rohen Index auf den Winkel-STRING '90'/'30'; im Debug-Journal erscheint bereits der gemappte Wert '90', die Konsolen-/Mock-Eingabe selbst bleibt der Index)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring AS-Seite (links)" + }, + { + "typ": "string", + "wert": "5", + "kommentar": "getstring Naechstes Element (Kettenanfang-Menue, 6 Optionen: 5 = Horizontal-VF)" + }, + { + "typ": "point_rel", + "dL": 6082.461, + "hz": 90, + "kommentar": "getpoint Endpunkt Horizontal-VF (freie Richtung am Kettenanfang, hz-vorgabe=nil - hz=90 ist die vom Code gesnappte Richtung, direkt aus dem Debug-Journal (REAL-Eintrag unmittelbar nach dem DL-Eintrag) uebernommen; dieser REAL-Eintrag ist ein reiner Journal-Nebeneffekt von vfl-in-abstand und KEIN eigener Eingabe-Schritt, siehe Lisp/vf_linienzug.lsp vfl-in-abstand" + }, + { + "typ": "real", + "wert": 1980.0, + "kommentar": "getreal Ziel-Hoehe Eingang GF1 (eingang-modus=voll/nil, vollstaendiger Neuaufbau des VF-Eingangs)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (Nein) - erste Frage innerhalb vfl-baue-horizontal-koerper (ziel-modus)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes im VarioFoerderer (Vario-Kurve)" + }, + { + "typ": "int", + "wert": 90, + "kommentar": "getint Vario-Kurve Winkel (90 Grad, roher Winkelwert - KEIN Auswahl-Index, siehe vfl-menu-winkel)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Seite (rechts)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Variante (innen)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (Nein)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes im VarioFoerderer (Horizontaler Foerderer)" + }, + { + "typ": "point_rel", + "dL": 1300, + "hz": 0, + "kommentar": "getpoint Horizontal-Endpunkt innerhalb der VF-Einheit. hz=0 ANNAHME (NICHT direkt im Debug-/Konsolen-Auszug als 'hz=' ausgewiesen, sondern aus der Turn-Arithmetik hergeleitet: Start-hz=90, davor bereits eine 90-Grad-rechts-Vario-Kurve (-90) -> 90-90=0; dieselbe Turn-Konvention [rechts=-Winkel/links=+Winkel] ist an ZWEI anderen Stellen dieses Testfalls unabhaengig durch explizite 'hz='-Werte im Konsolen-Auszug bestaetigt, siehe die Vario_Bogen_ab_3/ab_27-Eintraege). BITTE in BricsCAD live gegenpruefen (einzige nicht direkt beleg-bestaetigte hz-Rekonstruktion dieses Testfalls)." + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (Nein) - innerhalb vfl-baue-horizontal-koerper (ziel-modus)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes im VarioFoerderer (Vario-Kurve)" + }, + { + "typ": "int", + "wert": 90, + "kommentar": "getint Vario-Kurve Winkel (90 Grad)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Seite (rechts)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Variante (innen)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (Ja, nur Motorstation) - schliesst die 1. Horizontal-VF-Einheit ab" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist das Kettenende (ES-Element)? (Nein, weiterbauen) - vfl-vf-einheit-abschluss" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Zusaetzlichen Separator an dieser Stelle einfuegen? (Nein)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes Element (Hauptmenue, 5 Optionen, Frame vorhanden: 2 = Neue Linie: GF)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Wie soll die Laenge dieser Gefaellestrecke bestimmt werden? (1 = Endpunkt waehlen)" + }, + { + "typ": "point_rel", + "dL": 978.394396, + "hz": -90, + "kommentar": "getpoint Endpunkt Gefaellestrecke. hz=-90 DIREKT aus dem Debug-Journal bestaetigt (GEOMETRIE-Zeile 'vfl-insert-gf-segment (deltaL=379 hz=-90 winkel=3)')" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Separator VOR dieser Gefaellestrecke einfuegen? (Ja)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Separator NACH dieser Gefaellestrecke einfuegen? (Ja)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Gefaelle festlegen (1 = Hoehe, 2 = Neigungswinkel eingeben - Reihenfolge im Code seit dieser Sitzung getauscht)" + }, + { + "typ": "real", + "wert": 3.0, + "kommentar": "getreal Neigungswinkel (3 Grad)" + }, + { + "typ": "string", + "wert": "3", + "kommentar": "getstring Ist das Kettenende? (3 = Nein, weiterbauen - 3-Optionen-Menue: 1=Ja+ES, 2=Ja ohne ES, 3=Nein)" + }, + { + "typ": "string", + "wert": "4", + "kommentar": "getstring Naechstes Element (5 Optionen: 4 = Neue VF mit horizontalem Anfang/Horizontal-VF)" + }, + { + "typ": "point_rel", + "dL": 888.101488, + "hz": -90, + "kommentar": "getpoint Endpunkt Horizontal-VF (2. Einheit). hz=-90 unveraendert gegenueber dem vorigen GF-Segment (keine Richtungsaenderung dazwischen); eingang-modus=separator (letztes Glied war der Separator NACH der Gefaellestrecke) -> KEINE Ziel-Hoehe-Frage fuer GF1 (im Unterschied zur 1. Horizontal-VF-Einheit)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (Nein) - innerhalb vfl-baue-horizontal-koerper (ziel-modus)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes im VarioFoerderer (Vario-Kurve)" + }, + { + "typ": "int", + "wert": 30, + "kommentar": "getint Vario-Kurve Winkel (30 Grad)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Seite (rechts)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Variante (innen)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (Nein)" + }, + { + "typ": "string", + "wert": "3", + "kommentar": "getstring Naechstes im VarioFoerderer (Auf/Ab-Foerderer)" + }, + { + "typ": "point_rel", + "dL": 1614.972606, + "hz": -120, + "kommentar": "getpoint Endpunkt Auf/Ab-Foerderer. hz=-120 DIREKT bestaetigt (Konsolen-Auszug zeigt fuer den anschliessenden Vario_Bogen_ab_3_TEF_rechts explizit 'hz=-120 Grad' bei derselben Fahrtrichtung)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Kette kommt flach an - Richtung waehlen (1 = Auf)" + }, + { + "typ": "int", + "wert": 8, + "kommentar": "getint Winkel-/Hoehen-Tabelle: Auswahl-Index (NICHT der Winkel selbst) unter den GUELTIGEN Kandidaten - gueltig sind hier 3/6/9/12/15/18/21/27 Grad (8 Werte), Index 8 = letzter gueltiger Kandidat = 27 Grad (siehe Konsolen-Auszug '5/11: Vario_Bogen_auf_27_TEF_rechts')" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (Nein)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes im VarioFoerderer (Vario-Kurve)" + }, + { + "typ": "int", + "wert": 30, + "kommentar": "getint Vario-Kurve Winkel (30 Grad)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Vario-Kurve Seite (links)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Vario-Kurve Variante (aussen) - Konsolen-Auszug bestaetigt 'Vario_Kurve_links_30_TEF_aussen'" + }, + { + "typ": "string", + "wert": "3", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (3 = Ja, Motorstation setzen + Kettenende definieren) - startet den Stufe-1/Stufe-2-Kettenende-Zweig (vfl-body-abschluss/vfl-body-zerlegung)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring ES-Element setzen? (Ja)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring ES-Element Winkel - Menu-Index 1=90 Grad/2=30 Grad (wie AS-Winkel oben, gleiches Mapping ueber vf-frage-element-winkel)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring ES-Seite (links)" + }, + { + "typ": "point_rel", + "dL": 1802.808856, + "hz": -90, + "kommentar": "getpoint Endpunkt Kettenende (vfl-body-abschluss). hz=-90 DIREKT bestaetigt: die im Debug-Log erreichte Position (25544.710135 -8783.275914 2270.287821) hat exakt dieselbe X- und Z-Koordinate wie die KS_AUS-Position der vorigen Vario-Kurve (25544.71 / 2270.29 lt. Konsolen-Auszug) - nur Y aendert sich, das ist ausschliesslich mit hz=-90 (reine Y-Richtung) vereinbar" + }, + { + "typ": "real", + "wert": 2154.0, + "kommentar": "getreal Ziel-Hoehe Kettenende. Ab hier laeuft vfl-body-zerlegung AUTOMATISCH: Stufe 1 (Winkeltabelle 3/6/9/.../51 Grad) scheitert komplett (alle Kandidaten ergeben negative L_VF), Stufe 2 (vfl-winkel-stufe-entscheidung/berechne-horizontale-mitte) liefert typ=VF winkel=0 L_GF=86 L_VF=286 - KEINE weitere Eingabe fuer diese Entscheidung noetig" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Berechnetes GF2=86mm liegt unter der ueblichen Mindestlaenge (400mm) - trotzdem mit den exakt berechneten 86mm bauen? (Ja) - schliesst die Kette ab (VF_n mit GF=1 VF=5)" + } +]