fix: Laengenbilanz des gefalteten Modus-2-Laufs (U-Form)

Gefaltete VF-Laeufe wurden zu kurz gebaut, weil der Solver Posten dem
falschen Schenkel zurechnete. Vier Aenderungen, alle nach demselben
Leitsatz: die letzten freien Groessen folgen der GEMESSENEN Ist-Lage,
nicht einer vorab verteilten Gesamtplanung.

1. vfl-zsolve-schluss-ziel (neu): endet der Lauf auf einem flachen
   Fueller, werden RICHTUNG und LAENGE gemeinsam gegen den Zielpunkt
   geloest statt nur die Laenge. Geschlossene Loesung ueber
   A = sqrt(|D|^2 - P^2), hz = Winkel(D) - atan2(P, A); C und P sind
   von hz unabhaengig, daher ohne Iteration. Bisher uebernahm der
   Solve die Richtung aus dem gezeichneten Pfad - ein Querversatz der
   realen Eck-Bauteile war damit prinzipiell unerreichbar, weil eine
   Laenge die Kette nur entlang ihrer Achse verschiebt.

2. Eingangs-Hardware bei gefaltetem Lauf: Umlenkstation + Separator
   stehen auf dem Eingangs-Schenkel und verbrauchen die Kletterspanne
   nicht; ihr Laengenanteil wird der Spanne wieder zugeschlagen.
   feste-vf bleibt unveraendert - sein sin3-Hoehenanteil ist real.

3. kletter-ende-fp: endet der Lauf auf einem KLETTERER, gibt es keinen
   Schluss-Fueller, der Motorstation + Separator + ES abzieht - sie
   standen in keinem Budget. Nur bei flacher Zone, Pfadende und
   vorhandenem ES, sonst sind sie bereits anderweitig verbucht.

4. Nachrechner wieder aktiv (8 statt 4 Runden): GF1 steht im Budget,
   aber im Eingangs-Schenkel. Die Kollision mit filler-A-len, wegen
   der er abgeschaltet war, ist entfallen.

3 und 4 wirken NUR gemeinsam - die Betraege heben sich teilweise auf,
eine einzeln eingebaute Korrektur verschiebt den Fehler bloss in die
andere Richtung (GF1 1636.9 - (Motor+Sep+ES) 1235.1 = 401.8 mm).

Gemessen am selben Pfad: 9 Grad 0.56 mm, 12 Grad 0.7 mm quer (vorher
1424 bzw. 934 mm), dZ 0.1 mm. Beide Modus-2-Regressionsketten weiter
OK; TEST_UNIT 904/904 mit 7 neuen file-freien Tests.

doc/todo.md: Punkt "Modus-2-Solver: achsenweise Laengen-Bilanz" ist
damit erledigt. Die Detaildatei bleibt liegen, weil vier Kommentare in
vf_linienzug.lsp auf ihren Pfad verweisen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-29 11:41:35 +01:00
parent 9fb43b3881
commit 35ebee1e88
6 changed files with 746 additions and 53 deletions
+130 -1
View File
@@ -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 _plan _plan3 _g _p _w _ez)
(defun tu-tests-vfl-parallel ( / _b30 _b60 _b90 _kand _l _plan _plan3 _g _p _w _ez _plan2)
(princ "\n[vf_linienzug Parallel zum Kreisel]")
;; --- Bogenwahl: der Bogen muss den AS-Schwenk GENAU aufheben ---
@@ -610,6 +610,85 @@
(tu-nil "vfl-wert-norm/kein-string"
(vfl-wert-norm 1 *vfl-m2-linie-werte* *vfl-m2-linie-alt*))
;; --- Gefalteter VF-Lauf erkennen (Schenkel = Fahrtrichtungen) ---
;; Der Modus-2-Solver bilanziert skalar ueber EINE Achse. Liegt der Lauf
;; gefaltet (U-Form), sitzen die festen Teile in einem anderen Schenkel als
;; die Kletterstrecke und die Kette wird sichtbar falsch gebaut. Bis die
;; Bilanz achsenweise rechnet, wird so ein Lauf abgewiesen - diese
;; Erkennung entscheidet darueber.
;; Plan-Eintraege: Linie = ("Linie" hz laenge typ ...), Bogen = ("Bogen" hz ...)
(setq _plan2 (list (list "Linie" 0.0 9700.0 "VF" nil)
(list "Bogen" 0.0 707.1 90 "rechts" "Vario-Kurve" "aussen")
(list "Linie" -90.0 4296.0 "VF" nil)))
(tu-eq "vfl-m2-schenkel/u-form-zwei-richtungen" 2
(length (vfl-m2-schenkel _plan2 0 2)))
(tu-true "vfl-m2-gefaltet/u-form" (vfl-m2-gefaltet-p _plan2 0 2))
;; Gerader Lauf: eine Richtung, auch ueber mehrere Geraden hinweg.
(setq _plan2 (list (list "Linie" 0.0 9700.0 "VF" nil)
(list "Linie" 0.0 2000.0 "VF" nil)))
(tu-eq "vfl-m2-schenkel/gerade-eine-richtung" 1
(length (vfl-m2-schenkel _plan2 0 1)))
(tu-nil "vfl-m2-gefaltet/gerade" (vfl-m2-gefaltet-p _plan2 0 1))
;; Zurueck auf dieselbe Richtung zaehlt als NEUER Schenkel - die Kette ist
;; dann seitlich versetzt, die Laengen liegen nicht mehr auf einer Linie.
(setq _plan2 (list (list "Linie" 0.0 3000.0 "VF" nil)
(list "Bogen" 0.0 707.1 90 "rechts" "Vario-Kurve" "aussen")
(list "Linie" -90.0 1000.0 "VF" nil)
(list "Bogen" -90.0 707.1 90 "links" "Vario-Kurve" "aussen")
(list "Linie" 0.0 3000.0 "VF" nil)))
(tu-eq "vfl-m2-schenkel/u-form-drei" 3
(length (vfl-m2-schenkel _plan2 0 4)))
;; Winkel werden normalisiert: 270 und -90 sind dieselbe Richtung.
(setq _plan2 (list (list "Linie" -90.0 1000.0 "VF" nil)
(list "Linie" 270.0 1000.0 "VF" nil)))
(tu-eq "vfl-m2-schenkel/270-gleich-minus90" 1
(length (vfl-m2-schenkel _plan2 0 1)))
;; --- Eingangs-Hardware auf einem Fueller-Segment ---
;; Steht der Lauf-Eingang (GF1 + Separator + Umlenkstation + Bogen) auf
;; einem FUELLER, muss dessen Laenge abgezogen werden - sonst bekommt der
;; Fueller die volle Restlaenge, als staende dort nichts (real 2026-10-27:
;; 5855 mm Plan, 10668 mm belegt, Kette 8005 mm daneben).
;; Ist das Eingangs-Segment selbst der Kletterer, steckt dieselbe Laenge
;; schon in feste-vf - dann MUSS der Abzug 0 sein, sonst doppelt.
(tu-eqf "vfl-m2-eingang-fp/kletterer-kein-abzug" 0.0
(vfl-m2-eingang-fp 0 0 '(0) 3726.34 (* 3.0 (/ pi 180.0))))
;; Anderes Segment als der Eingang: ebenfalls nichts.
(tu-eqf "vfl-m2-eingang-fp/fremdes-segment" 0.0
(vfl-m2-eingang-fp 2 0 '(2) 3726.34 (* 3.0 (/ pi 180.0))))
;; Eingang ist Fueller (Kletterer liegt woanders): GF1 + 300 + 500,
;; projiziert auf 3 Grad, plus der Uebergangsbogen aus der Tabelle.
;; Die DIFFERENZ zweier Aufrufe prueft den GF-Anteil exakt und kommt ohne
;; die Bogen-Tabelle aus - die ist im file-freien Unit-Test leer
;; (init-bibliothek laeuft hier nicht), der Bogenanteil faellt also weg.
;; Eine absolute Schranke waere dadurch umgebungsabhaengig gewesen.
(tu-eqf "vfl-m2-eingang-fp/gf-anteil-exakt"
(* 3726.34 (cos (* 3.0 (/ pi 180.0))))
(- (vfl-m2-eingang-fp 0 0 '(2) 3726.34 (* 3.0 (/ pi 180.0)))
(vfl-m2-eingang-fp 0 0 '(2) 0.0 (* 3.0 (/ pi 180.0)))))
;; Separator + Umlenkstation sind IMMER dabei, auch ohne GF und ohne Tabelle.
(setq _l (vfl-m2-eingang-fp 0 0 '(2) 0.0 (* 3.0 (/ pi 180.0))))
(tu-true "vfl-m2-eingang-fp/stationen-immer-dabei"
(>= _l (* (+ *vfk-separator-laenge* *vfk-stations-laenge*)
(cos (* 3.0 (/ pi 180.0))))))
;; --- Fremdanteil der Gefaellestrecke (fuer den stillen Nachrechner) ---
;; Wieviel der vom Solver berechneten GF liegt NICHT auf dem Schenkel des
;; Kletterers? Nur das darf dessen Spanne wieder zugeschlagen werden.
;; Liegt der Eingang auf dem Kletterer selbst (gerader Normalfall), ist
;; nichts fremd - dann MUSS 0 herauskommen, sonst wuerde der funktionierende
;; Fall veraendert.
(tu-eqf "vfl-m2-gf-fremd/eingang-ist-kletterer" 0.0
(vfl-m2-gf-fremdanteil 3726.34 1 T))
(tu-eqf "vfl-m2-gf-fremd/alles-einlauf" 3726.34
(vfl-m2-gf-fremdanteil 3726.34 1 nil))
(tu-eqf "vfl-m2-gf-fremd/halb-halb" 1863.17
(vfl-m2-gf-fremdanteil 3726.34 2 nil))
;; Alles am Auslauf (Modus 3, erzwungen bei offenem Anfang): die GF liegt
;; dann beim Kletterer, nichts ist fremd.
(tu-eqf "vfl-m2-gf-fremd/alles-auslauf" 0.0
(vfl-m2-gf-fremdanteil 3726.34 3 nil))
;; --- ES-Reservierung auf die KS_AUS-Achse ---
;; Vorgabe: quer zur KS_AUS-Achse MUSS 0 sein, laengs darf abweichen.
;; Der reine Laengsanteil reicht dafuer NICHT - er laesst den bauteileigenen
@@ -2957,6 +3036,55 @@
)
;; --- Schlussstueck: Richtung UND Laenge aus der IST-Lage ---------------------
;; vfl-zsolve-schluss-ziel loest den letzten Fueller gegen den Zielpunkt, mit der
;; Richtung als zweiter Unbekannter. Mit es-block = nil braucht die Funktion
;; weder Zeichnung noch Blockdatei - der Querversatz P ist dann 0 und uebrig
;; bleibt die reine Geometrie.
;; Bewusst NICHT gegen eine nachgerechnete Konstante C geprueft (das waere die
;; Formel ein zweites Mal hingeschrieben), sondern gegen Eigenschaften, die
;; unabhaengig von C gelten: der Winkel zeigt exakt zum Ziel, und eine
;; Zielverschiebung bzw. ein groesseres GF2 schlaegt 1:1 auf die Laenge durch.
(defun tu-tests-vfl-schluss ( / _rad3 _p0 _a _b _c _d)
(princ "\n[vf_linienzug Schlussstueck]")
(setq _rad3 (/ (* 3.0 pi) 180.0)
_p0 '(0.0 0.0 0.0))
;; Der Winkel zeigt ohne ES-Element exakt zum Zielpunkt (P = 0).
(tu-eqf "vfl-zsolve-schluss-ziel/winkel-ost"
0.0 (cadr (vfl-zsolve-schluss-ziel _p0 '(10000.0 0.0 0.0) 0.0 nil 0.0 _rad3)))
(tu-eqf "vfl-zsolve-schluss-ziel/winkel-nord"
90.0 (cadr (vfl-zsolve-schluss-ziel _p0 '(0.0 10000.0 0.0) 0.0 nil 0.0 _rad3)))
;; 3/4/5-Dreieck: die Richtung folgt dem Ziel, NICHT dem uebergebenen seg-hz.
;; Genau das ist der Kern der Aenderung - darum wird hier ein voellig anderer
;; Planwinkel (-137) hineingegeben.
(tu-eqf "vfl-zsolve-schluss-ziel/winkel-folgt-ziel-nicht-plan"
(/ (* 180.0 (atan 4000.0 3000.0)) pi)
(cadr (vfl-zsolve-schluss-ziel _p0 '(3000.0 4000.0 0.0) -137.0 nil 0.0 _rad3)))
;; Laenge: 1000 mm weiter entferntes Ziel -> 1000 mm mehr Fueller.
(setq _a (car (vfl-zsolve-schluss-ziel _p0 '(10000.0 0.0 0.0) 0.0 nil 0.0 _rad3))
_b (car (vfl-zsolve-schluss-ziel _p0 '(11000.0 0.0 0.0) 0.0 nil 0.0 _rad3)))
(tu-eqf "vfl-zsolve-schluss-ziel/laenge-folgt-ziel" 1000.0 (- _b _a))
;; GF2 steht im Schwanz und liegt unter 3 Grad: 1000 mm GF2 verbrauchen
;; 1000*cos(3) in der Laengsrichtung, der Fueller wird genau darum kuerzer.
(setq _c (car (vfl-zsolve-schluss-ziel _p0 '(10000.0 0.0 0.0) 0.0 nil 1000.0 _rad3)))
(tu-eqf "vfl-zsolve-schluss-ziel/gf2-verkuerzt-fueller"
(* 1000.0 (cos _rad3)) (- _a _c))
;; Die Loesung ist richtungsunabhaengig: dasselbe Ziel, um 90 Grad gedreht,
;; ergibt dieselbe Fuellerlaenge.
(setq _d (car (vfl-zsolve-schluss-ziel _p0 '(0.0 10000.0 0.0) 0.0 nil 0.0 _rad3)))
(tu-eqf "vfl-zsolve-schluss-ziel/laenge-drehinvariant" _a _d)
;; Kein loesbarer Fall: Ziel liegt auf dem Startpunkt -> nil (Aufrufer faellt
;; dann auf den bisherigen Weg zurueck, statt etwas zu raten).
(tu-nil "vfl-zsolve-schluss-ziel/ziel-auf-startpunkt"
(vfl-zsolve-schluss-ziel _p0 '(0.0 0.0 0.0) 0.0 nil 0.0 _rad3))
)
(defun c:TEST_UNIT ( / out-dir report)
(tu-reset)
(princ "\n================================================================")
@@ -2969,6 +3097,7 @@
"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-vfl-schluss"
"tu-tests-werkzeuge" "tu-tests-kf" "tu-tests-hoehen-fam")
(tu-run-block fn))