From 35ebee1e88531a3c8294ef49ed9bf16d405cd022 Mon Sep 17 00:00:00 2001 From: Yuelin Wang Date: Thu, 29 Oct 2026 11:41:35 +0100 Subject: [PATCH] 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 --- Lisp/vf_linienzug.lsp | 402 ++++++++++++++++++++++++-- doc/todo.md | 11 - doc/todo/modus2-achsenweise-bilanz.md | 241 ++++++++++++++- lang/de_DE.json | 7 +- lang/en_GB.json | 7 +- tests/test_unit.lsp | 131 ++++++++- 6 files changed, 746 insertions(+), 53 deletions(-) diff --git a/Lisp/vf_linienzug.lsp b/Lisp/vf_linienzug.lsp index 59ffbcb..914ee50 100644 --- a/Lisp/vf_linienzug.lsp +++ b/Lisp/vf_linienzug.lsp @@ -5341,6 +5341,78 @@ ;; 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. +;; --- Schenkel eines VF-Laufs (reiner Kern, in test_unit getestet) --- +;; Ein "Schenkel" ist ein zusammenhaengender Abschnitt des VF-Laufs mit EINER +;; Fahrtrichtung. Eine Vario-Kurve im Lauf beendet den einen und beginnt den +;; naechsten - eine U-Form (hoch, quer, runter) hat also drei Schenkel. +;; +;; Wozu: Der Solver bilanziert die Laengen SKALAR ueber den ganzen Lauf. Das +;; stimmt nur, solange alle festen Teile auf derselben Achse liegen. Bei einem +;; gefalteten Lauf sitzen Umlenkstation/Separator/GF1 im EINGANGS-Schenkel, +;; die Kletterstrecke aber oft in einem anderen - der Solver zieht sie +;; trotzdem von deren Spanne ab, und die Kette endet weit daneben +;; (dokumentiert: dY = +2645 mm, doc/todo/modus2-achsenweise-bilanz.md). +;; +;; Rueckgabe: Liste der Fahrtrichtungen (hz) je Schenkel, in Bau-Reihenfolge. +;; Laenge 1 = gerader Lauf (der Fall, fuer den der Solver gebaut ist). +;; Gezaehlt wird ueber die RICHTUNG der Geraden, nicht ueber die Zahl der +;; Kurven: zwei entgegengesetzte 90-Grad-Kurven ergeben drei Schenkel, zwei +;; gleichsinnige ebenso - entscheidend ist, ob sich die Richtung aendert. +(defun vfl-m2-schenkel (plan vf-start vf-ende / i seg out letzt hz) + (setq i vf-start out '() letzt nil) + (while (<= i vf-ende) + (setq seg (nth i plan)) + (if (= (car seg) "Linie") + (progn + (setq hz (ks-winkel-normalisieren (float (cadr seg)))) + (if (or (null letzt) (> (abs (- hz letzt)) 1.0)) + (setq out (cons hz out))) + (setq letzt hz))) + (setq i (1+ i))) + (reverse out)) + +;; T, wenn der Lauf gefaltet ist (mehr als eine Fahrtrichtung). +(defun vfl-m2-gefaltet-p (plan vf-start vf-ende) + (> (length (vfl-m2-schenkel plan vf-start vf-ende)) 1)) + +;; Nummer des Schenkels, in dem ein Segment liegt (0-basiert, ab vf-start +;; gezaehlt). Fuer Bogen-Segmente der Schenkel, der VOR ihnen endet. +;; Damit laesst sich fragen: liegt der Kletterer im selben Schenkel wie der +;; Lauf-Eingang? Nur wenn NICHT, ist die skalare Bilanz falsch. +(defun vfl-m2-schenkel-index (plan vf-start seg-idx / i seg k letzt hz) + (setq i vf-start k 0 letzt nil) + (while (<= i seg-idx) + (setq seg (nth i plan)) + (if (= (car seg) "Linie") + (progn + (setq hz (ks-winkel-normalisieren (float (cadr seg)))) + (if (and letzt (> (abs (- hz letzt)) 1.0)) (setq k (1+ k))) + (setq letzt hz))) + (setq i (1+ i))) + k) + +;; --- Eingangs-Hardware auf einem FUELLER-Segment (reiner Kern, getestet) --- +;; GF1 + Separator + Umlenkstation + Uebergangsbogen werden am Lauf-EINGANG +;; gebaut, stehen also auf dem Segment vf-start. Ist dieses Segment zugleich +;; der Kletterer, sind sie ueber feste-vf bereits im Solver beruecksichtigt - +;; dann darf hier NICHTS abgezogen werden (sonst doppelt). +;; Ist vf-start dagegen ein FUELLER (der Kletterer liegt hinter einer Kurve), +;; kennt den Platzbedarf niemand: der Fueller bekam bisher die volle +;; Restlaenge des Segments, als staende dort nichts. Realer Fall 2026-10-27: +;; Planlaenge 5855 mm, tatsaechlich belegt 10668 mm, Kette 8005 mm daneben. +;; Rueckgabe: der abzuziehende Grundriss-Anteil (0.0, wenn nicht zutreffend). +;; gf-total = die vom Solver gewaehlte GF1-Laenge (Schraeglaenge bei 3 Grad). +(defun vfl-m2-eingang-fp (idx vf-start climbers gf-total rad3 / m) + (if (or (/= idx vf-start) (member vf-start climbers)) + 0.0 + (progn + (setq m (get-bogen-mass bogen-auf (fix *vfk-gefaelle-winkel*))) + (+ (* (+ (if gf-total (float gf-total) 0.0) + *vfk-separator-laenge* + *vfk-stations-laenge*) + (cos rad3)) + (if m (car m) 0.0))))) + ;; Allgemeiner Normalisierer fuer selbstbeschreibende Antwortwerte. ;; werte = heutige Werte in Optionsreihenfolge, alt = Alist Alt-Ziffer -> Wert ;; (nur fuer Bestandsketten). Rueckgabe nil = UNBEKANNT; der Aufrufer bricht @@ -11171,6 +11243,43 @@ ;; ES-Eigen-dz. Spiegelt den tatsaechlichen Bau (Trimmung der letzten Geraden um ;; 300+ein-fp, GF-Bogen bei Neigung). Eintritts-Neigung = 3 Grad (die Bruecke ;; endet mit GF2 auf 3 Grad). So wird die Junction-Hoehe exakt statt geschaetzt. +;; Stiller Nachrechner: L_GF/L_VF beim BEREITS GEWAEHLTEN Winkel mit einer +;; korrigierten Spanne neu aufteilen - ohne Dialog, ohne neue Winkelfrage. +;; Gebraucht bei gefaltetem Lauf: dort liegt ein Teil der Gefaellestrecke +;; (GF1) auf dem Eingangs-Schenkel und verbraucht die Spanne des Kletterers +;; nicht. Das weiss man erst, wenn Winkel UND GF-Verteilung beantwortet sind - +;; also nach der Winkelwahl. Deshalb hier nachrechnen statt die Frage +;; vorzuziehen: so bleibt die Fragenfolge (und damit jedes gespeicherte +;; Journal) unveraendert. +;; aus-dx/ein-dx werden wie in vfl-zsolve-winkel-waehlen temporaer genullt - +;; dieselbe Begruendung, siehe dort. +;; Rueckgabe: (L_GF L_VF) oder nil, wenn der Winkel bei dieser Spanne nicht +;; mehr aufgeht (dann bleibt der Aufrufer beim bisherigen Ergebnis). +(defun vfl-zsolve-nachrechnen (span dH-signiert feste winkel / + o-adx o-adz o-eix o-eiz richtung res tr) + (setq richtung (if (< dH-signiert 0) "Auf" "Ab")) + (setq o-adx aus-dx o-adz aus-dz o-eix ein-dx o-eiz ein-dz) + (setq aus-dx 0.0 aus-dz 0.0 ein-dx 0.0 ein-dz 0.0) + (setq res (berechne-alle-winkel span (abs dH-signiert) richtung feste nil)) + (setq aus-dx o-adx aus-dz o-adz ein-dx o-eix ein-dz o-eiz) + (setq tr nil) + (foreach e (nth 3 res) + (if (and (null tr) (= (car e) winkel) (cadddr e) + (numberp (cadr e)) (numberp (caddr e)) + (> (cadr e) 0) (> (caddr e) 0)) + (setq tr (list (cadr e) (caddr e))))) + tr) + +;; Anteil der Gefaellestrecke, der NICHT auf dem Kletterer-Schenkel liegt. +;; Reiner Kern (getestet). br-gf-mode: 1 = alles am Einlauf, 2 = halb/halb, +;; 3 = alles am Auslauf. Liegt der Eingang auf dem Kletterer selbst, ist +;; nichts fremd - dann 0. +(defun vfl-m2-gf-fremdanteil (gf-total br-gf-mode eingang-ist-kletterer) + (cond (eingang-ist-kletterer 0.0) + ((= br-gf-mode 1) (float gf-total)) + ((= br-gf-mode 2) (/ (float gf-total) 2.0)) + (t 0.0))) + (defun vfl-zsolve-rueckanker (plan start-idx n last-idx ein-fp / i seg drop inc w dL rad) (setq drop 0.0 inc *vfk-gefaelle-winkel* i start-idx) (while (< i n) @@ -11481,6 +11590,60 @@ (- (/ (- ep-na (* perp-es np-na)) dp-na) cc) 100.0)))) +;; --- Schlussstueck: RICHTUNG und LAENGE gemeinsam aus der IST-Lage loesen --- +;; Nutzer-Vorgabe 2026-10-29: "nach der zweiten VF-Kurve den passenden Winkel +;; neu aus dem KS_AUS der Kurve und dem Zielpunkt rechnen". +;; +;; Warum das noetig ist: vfl-zsolve-es-fueller loest NUR die Laenge und +;; uebernimmt die Richtung unveraendert aus dem gezeichneten Pfad. Eine Laenge +;; verschiebt die Kette aber ausschliesslich ENTLANG ihrer Achse. Steht der +;; reale Kurvenausgang seitlich NEBEN der gezeichneten Geraden - und das tut er, +;; sobald die echten Eck-Bauteile nicht in die gezeichnete Ecke passen -, dann +;; laeuft die gebaute Achse PARALLEL zur Zielachse, und kein Fuellerwert der +;; Welt holt den Querversatz noch ein (realer Befund 2026-10-29: waagerechte +;; Kette, zwei 90-Grad-Vario-Kurven mit nur 269.9 mm gezeichnetem Zwischenstueck +;; -> 637.6 mm quer, 1424.4 mm laengs daneben). +;; +;; Gibt man die Richtung mit frei, stehen zwei Unbekannte (fill, hz) zwei +;; Gleichungen (X und Y des Zielpunkts) gegenueber - geschlossen loesbar, ohne +;; Iteration. Der Schwanz (Fueller + ab_3 + Motor + GF2 + Separator + ES) ist +;; starr; im Rahmen von hz hat sein Endpunkt die festen Komponenten +;; (fill + C) laengs und P quer. +;; Beide sind von hz UNABHAENGIG: vfl-zsolve-es-vektor dreht den ES-Vektor und +;; den Bezugsrahmen um denselben Winkel. Also ist D = Ziel - pt genau der um hz +;; gedrehte Vektor (fill + C, P), woraus folgt: +;; A = sqrt(|D|^2 - P^2) (Laengskomponente, Pythagoras) +;; fill = A - C +;; hz = Winkel(D) - atan2(P, A) +;; Damit wird der Zielpunkt in X UND Y exakt getroffen, nicht nur die ES-Achse. +;; +;; Das Schlussstueck weicht danach bewusst von der gezeichneten Richtung ab - +;; genau im Sinn des Leitsatzes bei vfl-eck-korrekturen: massgeblich ist die +;; reale Bauteilkette, der gezeichnete Pfad ist nur Hinweis. +;; +;; es-block = ES-Blockname oder nil (dann ohne ES-Element, P = 0). +;; Rueckgabe: (fill hz-grad) oder nil, wenn der Zielpunkt naeher liegt als der +;; Querversatz - dann gibt es keine Loesung und der Aufrufer faellt auf den +;; bisherigen Weg (Richtung aus dem Plan) zurueck. +(defun vfl-zsolve-schluss-ziel (pt endp seg-hz es-block gf2 rad3 / + vek cc pp p0 p1 dd aa) + (setq vek (if es-block (vfl-zsolve-es-vektor es-block seg-hz) nil)) + (if vek + (setq cc (+ 196.0 (* (+ *vfk-feste-horizontal-kettenende* gf2) (cos rad3)) (car vek)) + pp (cadr vek)) + (setq cc (* (+ *vfk-stations-laenge* gf2) (cos rad3)) + pp 0.0)) + (setq p0 (list (car pt) (cadr pt)) + p1 (list (car endp) (cadr endp))) + (setq dd (distance p0 p1)) + (if (<= dd (+ (abs pp) 1.0)) + nil + (progn + (setq aa (sqrt (- (* dd dd) (* pp pp)))) + (list (- aa cc) + (- (/ (* 180.0 (angle p0 p1)) pi) + (/ (* 180.0 (atan pp aa)) pi)))))) + (defun vf-linienzug-modus2 ( / ss k obj-liste startpunkt start-hoehe as-seite endpunkt end-hoehe es-seite kette segmente segmente9 n i seg seg-ent @@ -11503,10 +11666,13 @@ 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 + schluss rad3v feste-vf dH-adj dir-x dir-y fill-D end-hz laengs-wert quer-wert soll-korrigiert es-vek-fix es-along-fix as-along-fix m-ueb abschluss-fp m-ab3 es-vek-back ein-fp-mess + span-solver nachrechnen-k gf-fremd res-neu + kletter-ende-fp kl-es-vek 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 @@ -11821,6 +11987,7 @@ (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). @@ -12203,6 +12370,17 @@ (if (or (< longest-idx 0) (> (caddr seg) (caddr (nth longest-idx plan)))) (setq longest-idx i))) (setq i (1+ i))) + + ;; An dieser Stelle stand ein Hinweis, dass ein gefalteter Lauf mit dem + ;; Kletterer in einem FREMDEN Schenkel noch skalar ueber EINE Achse + ;; bilanziert wird (davor zeitweise sogar eine Abweisung bzw. eine + ;; Warn-Messagebox). Beides ist seit 2026-10-29 gegenstandslos: die + ;; Eingangs-Hardware wird der Kletterspanne wieder zugeschlagen, die + ;; Abschluss-Hardware auf einem Kletter-Laufende abgezogen (kletter-ende-fp) + ;; und der GF1-Fremdanteil vom Nachrechner aufgeloest. Gemessen an demselben + ;; Pfad: 0.56 mm (9 Grad) und 0.7 mm (12 Grad) quer. Eine stehengebliebene + ;; Meldung waere hier nicht nur ueberfluessig, sondern falsch. + (setq climbers '() climber-span 0.0 nonclimber-len 0.0 nonclimber-cnt 0 nkurve 0 kurve-chords 0.0 i vf-start) (while (<= i vf-ende) @@ -12371,10 +12549,88 @@ ;; Kletterer ist - sitzt es auf einem Fueller-Segment, zieht es dort ab ;; (siehe fill-len weiter unten). Sonst waere der Abzug doppelt bzw. am ;; falschen Segment. - (setq wahl3 (vfl-zsolve-winkel-waehlen - (- climber-span es-along-fix uebergang-fp - (if (member vf-start climbers) as-along-fix 0.0)) - dH-adj feste-vf)) + ;; Eingangs-Hardware (Umlenkstation + Separator) steht auf dem Segment + ;; vf-start. Liegt der Kletterer in einem ANDEREN Schenkel, verbraucht sie + ;; dessen Spanne nicht - der Solver zieht sie aber ueber feste-vf trotzdem + ;; ab (als feste*cos3, vf_standard.lsp:241). Dann bleibt am Ende Platz + ;; ungenutzt und die Kette endet zu frueh (gemessen 2026-10-27: 1424 mm). + ;; Kompensiert wird NUR der Laengenanteil: feste-vf traegt ueber + ;; feste*sin3 auch Hoehe bei, und die ist real - die Teile senken die Kette + ;; ab, egal auf welchem Segment sie stehen. Darum wird feste-vf selbst NICHT + ;; veraendert, sondern der uebergebenen Spanne der Laengenanteil wieder + ;; zugeschlagen. (Dieselbe Trennung wie bei es-along-fix; sie zu uebersehen + ;; hat am 2026-10-26 einen 43-mm-Hoehenfehler erzeugt.) + ;; Liegt der Kletterer im Eingangs-Schenkel - der gerade Normalfall und + ;; beide Regressionsketten -, ist der Zuschlag 0 und nichts aendert sich. + ;; GEFALTETER LAUF: die Eingangs-Hardware (Umlenkstation + Separator, als + ;; entry-fixed) steht auf dem Segment vf-start. Ist DAS zugleich der + ;; Kletterer (gerader Normalfall, beide Regressionsketten), verbraucht sie + ;; dessen Spanne - der Abzug ueber feste-vf stimmt. Liegt der Kletterer aber + ;; in einem ANDEREN Schenkel, verbraucht sie die Kletterspanne NICHT, und der + ;; Solver verplant Platz, den es dort gar nicht zu verplanen gibt: am Ende + ;; bleibt genau dieser Betrag ungenutzt und die Kette endet zu frueh. + ;; Zugeschlagen wird NUR der Laengenanteil (entry-fixed*cos3). feste-vf selbst + ;; bleibt unveraendert, weil es ueber feste*sin3 auch Hoehe beitraegt - und die + ;; ist real: die Teile senken die Kette ab, egal auf welchem Schenkel sie + ;; stehen. (Dieselbe Trennung wie bei es-along-fix; sie zu uebersehen hat am + ;; 2026-10-26 einen 43-mm-Hoehenfehler erzeugt.) + ;; + ;; Diese Korrektur war am 2026-10-28 schon einmal da und wurde zurueckgenommen, + ;; weil sie mit filler-A-len kollidierte: Fueller-A belegte im Kletter-Segment + ;; den Platz, den das halbe GF1 dort frei laesst - obwohl GF1 bei gefaltetem + ;; Lauf im Eingangs-Schenkel steht. Dieser Blocker ist entfallen, seit + ;; filler-A-len bei gefaltetem Lauf 0 ist (siehe dort), die Korrektur kann + ;; also zurueck. + ;; + ;; Den GF1-Anteil kann dieser Solve noch nicht beruecksichtigen: gf-total ist + ;; sein ERGEBNIS (Henne-Ei). Dafuer gibt es den Nachrechner weiter unten. + ;; + ;; --- Abschluss-Hardware, wenn der KLETTERER das Laufende ist -------------- + ;; Endet der Lauf auf einem FUELLER, loest vfl-zsolve-es-fueller dessen Laenge + ;; gegen den Zielpunkt und zieht Motorstation + Separator + ES dabei selbst ab + ;; (Konstante cc dort). Endet er auf einem KLETTERER, gibt es diesen Fueller + ;; nicht - die drei Teile werden auf dem Kletter-Segment gebaut, stehen aber + ;; in KEINEM Budget: feste-vf traegt bei vorhandener flacher Zone nur + ;; entry-fixed (Umlenk+Sep am Eingang), und abschluss-fp wird ausschliesslich + ;; in fill-len verrechnet, also nur bei Fueller-Segmenten. + ;; Abgezogen wird deshalb genau dasselbe wie in cc, ABER OHNE den ab_3-Bogen + ;; (196 mm): der steckt bei einem Kletterer am Laufende bereits in + ;; uebergang-fp (Zweig "flache Zone GEHT VORAUS"), und doppelt gezaehlt + ;; verkuerzte er die Kette erneut. + ;; + ;; Die drei Bedingungen sind KEINE Vorsichtsmassnahme, sondern genau die + ;; Faelle, in denen sonst doppelt gebucht wird: + ;; - hor-pairs > 0: OHNE flache Zone traegt feste-vf die Motorstation bereits + ;; selbst (siehe dortiger Zweig), und es-along-fix den ES-Anteil. + ;; - vf-ende = n-1: endet der Pfad nicht mit dem VF-Lauf, baut der Back-Lauf + ;; Separator + ES und reserviert sie ueber ein-fp-mess selbst. + ;; - es-vorhanden: ohne ES-Element gibt es weder Separator noch ES-Ausladung. + ;; Realer Regressionsbefund 2026-10-29 (Variante B, tests/testdata/ + ;; linienzug_modus2_gfbogen.json): dort ist vf-start = vf-ende = 0, der VF-Lauf + ;; besteht aus EINEM Segment und die Segmente 2..5 sind Back-Lauf. Ohne diese + ;; Bedingungen schlug der Abzug mit 800*cos3 = 798.9 mm zu, Segment 1 wurde um + ;; genau diesen Betrag kuerzer, und weil es bei hz=0 liegt, erschien der + ;; Fehler als reine X-Verschiebung von -799.1 mm. + (setq kletter-ende-fp 0.0) + (if (and (member vf-ende climbers) (not m2-offenes-ende) + (> hor-pairs 0) (= vf-ende (1- n)) es-vorhanden) + (progn + (setq kl-es-vek + (vfl-zsolve-es-vektor + (strcat "ES_Element_" (vfl-es-winkel) "_" es-seite) + (cadr (nth vf-ende plan)))) + (setq kletter-ende-fp + (+ (* *vfk-feste-horizontal-kettenende* (cos rad3v)) + (if kl-es-vek (car kl-es-vek) 0.0))) + (princ (ssg-textf "vfl-m2-kletter-ende-fussabdruck" + (list (rtos kletter-ende-fp 2 1) (itoa (1+ vf-ende))))))) + ;; span-solver = dieselbe Spanne, die der Nachrechner weiter unten wieder + ;; aufgreift (dort kommt der GF-Fremdanteil hinzu). + (setq span-solver + (+ (- climber-span es-along-fix uebergang-fp kletter-ende-fp + (if (member vf-start climbers) as-along-fix 0.0)) + (if (member vf-start climbers) 0.0 (* entry-fixed (cos rad3v))))) + (setq wahl3 (vfl-zsolve-winkel-waehlen span-solver dH-adj feste-vf)) (if (null wahl3) (progn ;; Stufe 2: KEIN Tabellenwinkel passt (der/die Kletterer sind bei JEDER @@ -12418,11 +12674,81 @@ (setq br-gf-mode (if (= (vfl-menu (ssg-text "prompt-wahl-1-2") (list "Alles am Einlauf (GF1)" "1/2 GF1 + 1/2 GF2") 1 - "vfl-m3-gf-verteilung") "2") 2 1)) - (if (= br-gf-mode 2) - (setq br-gf1 (/ gf-total 2.0) br-gf2-exp (/ gf-total 2.0) - filler-A-len (* (/ gf-total 2.0) (cos rad3v))) - (setq br-gf1 gf-total br-gf2-exp 0.0 filler-A-len 0.0)))) + "vfl-m3-gf-verteilung") "2") 2 1)))) + ;; --- Stiller Nachrechner bei gefaltetem Lauf --- + ;; Jetzt sind Winkel UND Verteilung bekannt - erst damit steht fest, wie viel + ;; Gefaellestrecke auf dem EINGANGS-Schenkel landet und die Spanne des + ;; Kletterers gar nicht verbraucht. Der Solver hat sie bisher mitgerechnet, + ;; weshalb am Kettenende Platz ungenutzt blieb (gemessen 2026-10-27: + ;; 1424 mm). Hier wird bei UNVERAENDERTEM Winkel neu aufgeteilt. + ;; Die Schleife iteriert, weil der fremde Anteil selbst von gf-total + ;; abhaengt - ohne Dialog, daher unbemerkt. Sie konvergiert schnell (die + ;; Korrektur wird mit jedem Durchgang kleiner) und bricht nach 4 Runden + ;; oder bei weniger als 1 mm Aenderung ab. + ;; Liegt der Eingang auf dem Kletterer (gerader Normalfall, beide + ;; Regressionsketten), ist der Fremdanteil 0 und die Schleife laeuft nicht. + ;; + ;; Am 2026-10-28 war der Nachrechner abgeschaltet, weil er mit filler-A-len + ;; kollidierte: jener Fueller belegte im Kletter-Segment den Platz, den das + ;; halbe GF1 dort frei laesst - obwohl GF1 bei gefaltetem Lauf im Eingangs- + ;; Schenkel steht, wurde er zusaetzlich gebaut (gemessen: Fueller -369 mm, + ;; geklemmt auf 100). Seit filler-A-len bei gefaltetem Lauf 0 ist, entfaellt + ;; die Kollision und der Nachrechner darf wieder laufen. + ;; + ;; Er ist die zweite Haelfte der Korrektur und wirkt NUR zusammen mit + ;; kletter-ende-fp: GF1 steht im Budget, aber nicht auf dem Kletterer + ;; (zu viel verplant), Motor+Separator+ES stehen auf dem Kletterer, aber in + ;; keinem Budget (zu wenig verplant). Die beiden Betraege heben sich + ;; TEILWEISE auf - wer nur einen von beiden einbaut, verschiebt den Fehler + ;; bloss in die andere Richtung. Gemessen 2026-10-29 am 9-Grad-U-Form-Lauf: + ;; GF1 1636.9 - (Motor+Sep+ES) 1235.1 = 401.8 mm, exakt die damalige + ;; Restabweichung von 401.7 mm. + ;; + ;; 8 statt 4 Runden: die Korrektur schrumpft je Runde auf etwa ein Drittel + ;; (gemessen 3278 -> 4372 -> 4737 -> 4859 -> 4899), nach 4 Runden blieben + ;; rund 20 mm stehen. 8 Runden kosten nur Arithmetik und bringen den Rest + ;; unter die 1-mm-Abbruchschwelle. + (setq nachrechnen-k 0) + (while (< nachrechnen-k 8) + (setq gf-fremd (vfl-m2-gf-fremdanteil gf-total br-gf-mode + (and (member vf-start climbers) t))) + (if (< gf-fremd 1.0) + (setq nachrechnen-k 8) + (progn + (setq res-neu (vfl-zsolve-nachrechnen + (+ span-solver (* gf-fremd (cos rad3v))) + dH-adj feste-vf br-winkel)) + (cond + ((null res-neu) + ;; Winkel geht bei korrigierter Spanne nicht mehr auf - beim + ;; bisherigen Ergebnis bleiben und das sagen, statt still etwas + ;; anderes zu bauen. + (vfl-meldung (ssg-textf "vfl-m2-nachrechnen-fehl" (list (itoa br-winkel)))) + (setq nachrechnen-k 8)) + ((< (abs (- (car res-neu) gf-total)) 1.0) + (setq nachrechnen-k 8)) + (t + (princ (ssg-textf "vfl-m2-nachrechnen" + (list (rtos gf-total 2 0) (rtos (car res-neu) 2 0) + (rtos (cadr res-neu) 2 0)))) + (setq gf-total (car res-neu) br-lvf (cadr res-neu)) + (setq nachrechnen-k (1+ nachrechnen-k))))))) + ;; GF-Aufteilung jetzt aus dem (ggf. nachgerechneten) gf-total bilden. + (if (/= br-gf-mode 3) + (if (= br-gf-mode 2) + (setq br-gf1 (/ gf-total 2.0) br-gf2-exp (/ gf-total 2.0) + ;; Fueller-A fuellt den Platz, den das halbierte GF1 auf DERSELBEN + ;; Achse frei laesst - er gehoert zur Annahme "GF1 und GF2 liegen + ;; auf einer Geraden". Bei gefaltetem Lauf stimmt das nicht: GF1 + ;; steht im Eingangs-Schenkel, GF2 im Kletterer-Schenkel. Fueller-A + ;; wuerde dann auf dem Eingangs-Segment zusaetzlich zu GF1 gebaut, + ;; und insgesamt entstuenden 1.5 x gf-total Gefaellestrecke bei + ;; einem Budget von gf-total - die Kette endet zu frueh + ;; (gemessen 2026-10-27: 1424 mm). + filler-A-len (if (member vf-start climbers) + (* (/ gf-total 2.0) (cos rad3v)) + 0.0)) + (setq br-gf1 gf-total br-gf2-exp 0.0 filler-A-len 0.0))) (setq filler-a-done nil) (princ (ssg-textf "vfl-m3-vario-ergebnis" (list (itoa br-winkel) @@ -12491,30 +12817,48 @@ ;; 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 - ;; sich starr entlang seg-hz mit dem Fueller; KS_AUS = pt + (fill + C)* - ;; dp + perp-es*np, Achse u_a = seg-hz + ES-Turn. Aus (Endpunkt - - ;; KS_AUS)*n_a = 0 folgt fill (n_a senkrecht zu u_a). OHNE ES-Element - ;; (Nutzerwunsch): keine Achsen-Korrektur noetig, da nach Motor+GF2 - ;; nichts mehr gebaut wird - Fueller zielt direkt auf den Endpunkt. - (setq fill-len - (if es-vorhanden - (vfl-zsolve-es-fueller pt endpunkt seg-hz - (strcat "ES_Element_" (vfl-es-winkel) "_" es-seite) - br-gf2-exp rad3v) - (- (+ (* (- (car endpunkt) (car pt)) (cos (* seg-hz (/ pi 180.0)))) - (* (- (cadr endpunkt) (cadr pt)) (sin (* seg-hz (/ pi 180.0))))) - (* (+ *vfk-stations-laenge* br-gf2-exp) (cos rad3v))) - ) - ) + ;; Letztes Segment UND Pfadende: RICHTUNG und LAENGE des Schlussstuecks + ;; gemeinsam aus der IST-Lage loesen (vfl-zsolve-schluss-ziel), statt + ;; die Richtung aus dem gezeichneten Pfad zu uebernehmen und nur die + ;; Laenge zu loesen. Damit wird der Zielpunkt in X UND Y getroffen; + ;; ein Querversatz, den die realen Eck-Bauteile vorher aufgebaut haben, + ;; wird hier eingefangen statt bis ans Kettenende mitgeschleppt. + ;; Scheitert der Solve (Ziel liegt naeher als der ES-Querversatz), + ;; bleibt es beim bisherigen Weg: Richtung aus dem Plan, Laenge so, dass + ;; der Endpunkt auf der ES-KS_AUS-Achse liegt (Spiegel der AS-Regel) - + ;; bzw. ohne ES-Element direkt auf den Endpunkt. + (progn + (setq schluss (vfl-zsolve-schluss-ziel pt endpunkt seg-hz + (if es-vorhanden + (strcat "ES_Element_" (vfl-es-winkel) "_" es-seite)) + br-gf2-exp rad3v)) + (if schluss + (progn + (dbgmsg (strcat "SCHLUSS-SOLVE hz-plan=" (rtos seg-hz 2 3) + " hz-neu=" (rtos (cadr schluss) 2 3) + " fill=" (rtos (car schluss) 2 1))) + (setq fill-len (car schluss) + seg-hz (cadr schluss))) + (setq fill-len + (if es-vorhanden + (vfl-zsolve-es-fueller pt endpunkt seg-hz + (strcat "ES_Element_" (vfl-es-winkel) "_" es-seite) + br-gf2-exp rad3v) + (- (+ (* (- (car endpunkt) (car pt)) (cos (* seg-hz (/ pi 180.0)))) + (* (- (cadr endpunkt) (cadr pt)) (sin (* seg-hz (/ pi 180.0))))) + (* (+ *vfk-stations-laenge* br-gf2-exp) (cos rad3v))))))) ;; Gewoehnlicher Fueller: volle Planlaenge des Segments, abzueglich ;; der fest gebauten Teile, die auf GENAU diesem Segment stehen - - ;; das AS am Laufanfang, Abschlussbogen + Motorstation am Laufende. + ;; das AS am Laufanfang, Abschlussbogen + Motorstation am Laufende, + ;; und die Eingangs-Hardware, falls sie auf einem FUELLER steht. (setq fill-len (- (+ (caddr seg) (vfl-eck-extra i)) (if (= i vf-start) as-along-fix 0.0) - (if (= i vf-ende) abschluss-fp 0.0)))) + (if (= i vf-ende) abschluss-fp 0.0) + ;; br-gf1 (das TATSAECHLICH gebaute GF1), nicht + ;; gf-total: bei Verteilung 1/2+1/2 steht nur die + ;; Haelfte auf diesem Segment, seit Fueller-A dort + ;; nicht mehr entsteht (siehe filler-A-len oben). + (vfl-m2-eingang-fp i vf-start climbers br-gf1 rad3v)))) ;; Kritische Fueller-Position (direkt nach Umlenkstation ODER direkt vor ;; Motorstation) zu kurz fuer den geforderten Wert - statt still zu ;; klemmen, den Nutzer warnen (Zielhoehe/-position wird dann nicht exakt diff --git a/doc/todo.md b/doc/todo.md index df0ced1..25760bd 100644 --- a/doc/todo.md +++ b/doc/todo.md @@ -11,17 +11,6 @@ Stand: 2026-10-26. Wird woechentlich (freitags) mit der Doku geprueft, siehe ## 1. VarioFoerderer / Linienzug -- **Modus-2-Solver: achsenweise Laengen-Bilanz.** Gefaltete VF-Laeufe (U-Form) - werden falsch gebaut (erste GF zu lang, Kletterstrecke zu kurz, Motor/ES - falsch), weil der Solver die festen Fussabdruecke skalar von EINER Spanne - abzieht. **Teilweise erledigt 2026-10-26** (ce4ad0f): AS-Element, - Abschlussbogen+Motorstation und die Eck-Differenzen werden seitdem dem - Segment zugeschlagen, auf dem sie physisch stehen (`vfl-eck-extra` je - Segmentindex, `as-along-fix` auf `vf-start`, `abschluss-fp` auf `vf-ende`) - - das ist die achsenweise Idee fuer diese Posten. SKALAR bleiben `feste-vf` - (Umlenkstation + Separator) und `gf-total` (GF1), und genau die verursachen - den U-Form-Fehler. Als Zwischenloesung zumindest gefaltete Laeufe mit - Meldung abweisen. -> `doc/todo/modus2-achsenweise-bilanz.md` - **VF-Linienzug ohne GUI/Konsole, Stufe 2** (Funktionstrennung): eigener, separat abzuarbeitender Auftrag mit Schritt-Tabelle. Schritte 5c, 6, 8, 9, 10 offen (u.a. `vfl-spec-ausfuehren`). Schritt 11 war zurueckgestellt, bis diff --git a/doc/todo/modus2-achsenweise-bilanz.md b/doc/todo/modus2-achsenweise-bilanz.md index 6cd68b1..95782f6 100644 --- a/doc/todo/modus2-achsenweise-bilanz.md +++ b/doc/todo/modus2-achsenweise-bilanz.md @@ -1,13 +1,21 @@ -# TODO: Modus-2-Solver auf achsenweise Laengen-Bilanz umstellen +# Modus-2-Solver: achsenweise Laengen-Bilanz -Status: **Vorschlag / noch nicht umgesetzt** (2026-09-18). Mit dem Entwickler zu -besprechen, bevor der Solver angefasst wird. Reiner Plan - kein Code geaendert. +Status: **ERLEDIGT 2026-10-29.** Gefaltete Laeufe (U-Form) werden gebaut; am +selben Pfad gemessen 0.56 mm (9 Grad) und 0.7 mm (12 Grad) quer, beide +Regressionsketten weiter OK. Der Abschnitt "Stand 2026-10-29" und der "Nachtrag +2026-10-29" am Ende beschreiben Loesung und Herleitung. -**Pruefung 2026-10-26:** weiterhin OFFEN - der Solver bilanziert noch skalar -(`climber-span`, `hor-pairs` 0/1), auch die Zwischenloesung (gefaltete Laeufe -abweisen) ist nicht eingebaut. Die Zeilennummern unten sind veraltet: Phase B -von `vf-linienzug-modus2` liegt inzwischen bei ca. `vf_linienzug.lsp:11880-12100` -(per Suche nach `climber-span` finden). +Die Datei bleibt BEWUSST liegen, obwohl der Auftrag abgeschlossen ist: vier +Kommentare in `Lisp/vf_linienzug.lsp` verweisen auf diesen Pfad (Suche nach +`modus2-achsenweise-bilanz`). Sie dokumentiert damit nicht mehr einen Plan, +sondern WARUM der Solver so bilanziert, wie er es tut - einschliesslich der +fehlgeschlagenen Versuche, damit sie niemand ein zweites Mal unternimmt. + +Alles unterhalb dieser Zeile ist der urspruengliche Plan von 2026-09-18 und +seine Zwischenstaende. Die dort genannten Zeilennummern sind veraltet; Phase B +von `vf-linienzug-modus2` findet man per Suche nach `climber-span`. Die damals +vorgeschlagene Zwischenloesung (gefaltete Laeufe mit Meldung abweisen) wurde +NICHT umgesetzt und ist gegenstandslos - solche Laeufe funktionieren jetzt. ## Kurzfassung @@ -166,3 +174,220 @@ namentlich (`AS-Fussabdruck ... von Segment N abgezogen`, `Abschluss-Fussabdruck ...`, `Eck-Korrektur ... auf Segment N`). Eine FEHLENDE Zeile ist das Warnsignal - so wurde ein 2,4-mm-Rest gefunden. Fuer die U-Form-Diagnose sind diese Zeilen der schnellste Einstieg. + +--- + +## Stand 2026-10-28: Fueller-Seite geloest, Solver-Seite offen + +### Geloest und gemessen + +`vfl-m2-eingang-fp`: GF1 + Separator + Umlenkstation + Uebergangsbogen stehen +auf dem Segment `vf-start`. Ist dieses Segment ein FUELLER (der Kletterer +liegt hinter einer Kurve), bekam der Fueller bisher die volle Restlaenge, als +staende dort nichts. Jetzt werden sie abgezogen. + +Ist das Eingangs-Segment selbst der Kletterer - der gerade Normalfall -, gibt +die Funktion hart 0.0 zurueck; der funktionierende Fall kann sich also nicht +aendern. Gemessen am U-Form-Testfall: **Querabweichung 8005 -> 1424 mm**, +Segment 1 danach auf 0,2 mm genau belegt. + +### Noch offen: der Solver rechnet GF und Stationen gegen die falsche Achse + +Rest 1424 mm. Segment 5 (Kletterer) legt 6475,5 mm zurueck, verfuegbar waeren +7899,8 - der Solver reserviert dort Platz fuer Umlenkstation, Separator und +den GF1-Anteil, die auf Segment 1 stehen. + +### Fehlgeschlagene Versuche (nicht wiederholen) + +Drei Anlaeufe am 2026-10-27/28, jeder an einer anderen Kopplung gescheitert: + +1. **`feste-vf` kleiner uebergeben.** Geht nicht: `feste-hz` fliesst in + `vf-winkel-AB` (vf_standard.lsp:241/245) sowohl als `feste*cos3` in die + LAENGE als auch als `feste*sin3` in die HOEHE. Die Hoehe ist real - die + Teile senken die Kette ab, egal auf welchem Schenkel sie stehen. Nur der + Laengenanteil darf verschoben werden. +2. **Spanne vorab korrigieren und iterieren.** Geht nicht direkt: + `vfl-zsolve-winkel-waehlen` enthaelt die WINKELFRAGE an den Nutzer - eine + Iteration wuerde mehrfach fragen. Und die Korrektur ist pro Winkel + verschieden, weil jeder Winkel ein anderes `L_GF` ergibt. +3. **Stiller Nachrechner nach der GF-Verteilungsfrage** (Code vorhanden, per + `(setq nachrechnen-k 4)` deaktiviert). Technisch sauber: kein Dialog, + Fragenfolge und Journal unveraendert, Konvergenz in 4 Runden + (3278 -> 4372 -> 4737 -> 4859 -> 4899 mm). ABER er kollidiert mit + **`filler-A-len`**: dieser Fueller belegt im Kletter-Segment genau den + Platz, den das halbe GF1 dort frei laesst. Nimmt man GF1 aus der + Kletterer-Spanne heraus, wird Fueller-A trotzdem gebaut - zusaetzlich zum + echten GF1 auf demselben Segment. Gemessen: Fueller nach der + Umlenkstation -369 mm (auf 100 geklemmt), Kette 746 mm daneben. + Kleinerer Endfehler, aber entartete Geometrie - deshalb zurueckgenommen. + +### Daraus fuer den naechsten Anlauf + +Der Nachrechner ist vermutlich der richtige Weg, aber **`filler-A-len` muss +mitbehandelt werden**: er gehoert zur Annahme "GF1 liegt im Kletter-Segment" +und darf bei gefaltetem Lauf nicht mehr entstehen. Wer das angeht, sollte +beide Mechanismen zusammen betrachten, nicht nacheinander. + +Testfall vorhanden: U-Form 5855 / 270 / 7806 mm (Kletterer hinter zwei +90-Grad-Kurven), Soll-Querabweichung ~0, Stand heute 1424 mm. Die beiden +geraden Referenzketten in `c:TEST_LINIENZUG_MODUS2` schuetzen den Normalfall. + +--- + +## Stand 2026-10-29: Eingangs-Segment exakt, Kletterer-Segment offen + +### Zusaetzlich geloest (gegenueber dem Stand vom 2026-10-28) + +**Fueller-A entfaellt bei gefaltetem Lauf**, und `vfl-m2-eingang-fp` zieht +seitdem `br-gf1` ab statt `gf-total`. Beides gehoert zusammen: + +`filler-A-len` fuellt den Platz, den ein halbiertes GF1 auf DERSELBEN Achse +frei laesst - er gehoert zur Annahme "GF1 und GF2 liegen auf einer Geraden". +Bei gefaltetem Lauf wurde er auf dem Eingangs-Segment ZUSAETZLICH zu GF1 +gebaut; zusammen mit GF2 im Kletterer-Schenkel entstanden damit 1.5 x +gf-total Gefaellestrecke bei einem Budget von gf-total. + +Ergebnis, am U-Form-Testfall nachgerechnet (9 Grad, Verteilung 1/2+1/2): + +``` +Segment 1: AS 425,8 + GF1 1370,3 + Sep 299,6 + Umlenk 499,3 + + Bogen 199,9 + Fueller 3153,7 = 5948,6 mm +Budget: 5855 + 93,8 (Eck-Korrektur) = 5948,8 mm +``` + +Das Eingangs-Segment ist damit **auf 0,2 mm exakt belegt**, und im Protokoll +steht nur noch EIN Fueller statt zwei. + +### Was offen bleibt + +Das KLETTERER-Segment. Gemessen im selben Lauf: Segment 5 belegt 6965,8 mm, +verfuegbar waeren 7899,8 - es fehlen **934 mm**, exakt die Endabweichung. + +Der Solver bekommt als Spanne 7704,16 mm und verplant darin u.a. GF1 +(1370,3) sowie Umlenkstation + Separator (798,9) - Teile, die auf Segment 1 +stehen. Umgekehrt enthaelt seine Spanne NICHT den Abschluss-Fussabdruck +(ab_3 + Motorstation, 695 mm), weil dieser nur von FUELLER-Segmenten +abgezogen wird (`fill-len`) und das Kletterer-Segment keines ist. + +**Der Restfehler skaliert mit `L_GF`** und damit mit dem gewaehlten Winkel - +unabhaengig bestaetigt durch zwei Laeufe auf demselben Pfad: + +| Winkel | L_GF | Querabweichung | +|---|---|---| +| 6 Grad | 1093 | 90 mm | +| 9 Grad | 2744 | 934 mm | + +Praktische Folge: Mit flachem Winkel ist der gefaltete Lauf heute brauchbar, +mit steilem nicht. + +### Hinweis zur Methodik fuer den naechsten Anlauf + +Zwei Laeufe sind nur vergleichbar, wenn **Winkel UND GF-Verteilung gleich** +sind. Mehrere Messreihen dieser Sitzung waren nicht deutbar, weil sich +zwischendurch der Winkel geaendert hatte und der Restfehler mit `L_GF` +skaliert. Vor einer Aenderung also eine feste Kombination festlegen +(Vorschlag: 12 Grad, Verteilung 1/2+1/2) und nur die vergleichen. + +--- + +## Nachtrag 2026-10-29: Loesungsansatz vom Nutzer + +Der Abschnitt darueber beschreibt den Stand VOR dem folgenden Umbau; er bleibt +als Herleitung stehen. + +Nutzer-Vorgabe: *"nach der zweiten VF-Kurve den passenden Winkel neu aus dem +KS_AUS der Kurve und dem Zielpunkt rechnen"* und *"da GF2 feststeht, steht auch +die fuer Auf/Ab verbleibende Laenge fest - passender Winkel plus eine +ausgleichende GF"*. + +Das ist die Umkehrung der bisherigen Logik und trifft den Kern: bisher wurde +ALLES vorab global verplant und der aufgelaufene Fehler landete am Kettenende. +Richtig ist, die letzten freien Groessen aus der GEMESSENEN Ist-Lage zu +bestimmen. Je nachdem, was das letzte Segment ist, sind das verschiedene +Groessen: + +| Letztes Segment | feste Groesse | freie Groessen | +|---|---|---| +| flacher Fueller | - | Richtung + Fuellerlaenge | +| Auf/Ab-Koerper (U-Form) | Laenge (aus dem Pfad) | Winkel (diskret) + GF-Ausgleich | + +### Umgesetzt + +**1. Flacher Fueller als Schlussstueck** - neue Funktion +`vfl-zsolve-schluss-ziel`. Bisher loeste `vfl-zsolve-es-fueller` nur die LAENGE +und uebernahm die Richtung aus dem gezeichneten Pfad. Eine Laenge verschiebt die +Kette aber nur ENTLANG ihrer Achse - steht der reale Kurvenausgang seitlich +NEBEN der gezeichneten Geraden, laeuft die gebaute Achse parallel zur Zielachse +und der Querversatz ist unerreichbar. Gibt man die Richtung mit frei, stehen +zwei Unbekannte zwei Gleichungen (X und Y) gegenueber, geschlossen loesbar: + +``` +D = Ziel - KS_AUS(letzte Kurve) +A = sqrt(|D|^2 - P^2) ; P = Querversatz des ES-Fussabdrucks +fill = A - C +hz = Winkel(D) - atan2(P, A) +``` + +`C` und `P` sind von `hz` unabhaengig, weil `vfl-zsolve-es-vektor` Vektor und +Bezugsrahmen um denselben Winkel dreht - deshalb ist keine Iteration noetig. +Scheitert der Solve (Ziel naeher als `P`), bleibt der bisherige Weg. Unit-Tests: +`tu-tests-vfl-schluss` (file-frei ueber `es-block = nil`). + +**2. Eingangs-Hardware bei gefaltetem Lauf** - der am 2026-10-28 zurueck- +genommene Spannen-Zuschlag ist wieder da. Begruendung fuer die Ruecknahme war +die Kollision mit `filler-A-len`; dieser Blocker ist entfallen, seit Fueller-A +bei gefaltetem Lauf gar nicht mehr entsteht. Zugeschlagen wird weiterhin NUR +der Laengenanteil (`entry-fixed * cos3`), nie `feste-vf` selbst - dessen +`sin3`-Hoehenanteil ist real. + +**3. Abschluss-Hardware auf dem Kletterer** (`kletter-ende-fp`) - endet der Lauf +auf einem FUELLER, zieht `vfl-zsolve-es-fueller` Motorstation + Separator + ES +beim Loesen der Fuellerlaenge selbst ab. Endet er auf einem KLETTERER, gibt es +diesen Fueller nicht: die drei Teile werden dort gebaut, standen aber in keinem +Budget. Abgezogen wird dasselbe wie in dessen Konstante `cc`, OHNE den +ab_3-Bogen (der steckt in diesem Fall schon in `uebergang-fp`). + +**4. GF1-Fremdanteil** (Nachrechner wieder aktiv, 8 statt 4 Runden) - `gf-total` +ist ein ERGEBNIS des Winkel-Solves, sein auf dem Eingangs-Schenkel stehender +Teil laesst sich dort noch nicht abziehen (Henne-Ei). Der Nachrechner loest bei +UNVERAENDERTEM Winkel neu und konvergiert mit Faktor ~1/3. + +### Der entscheidende Punkt: 3 und 4 wirken nur GEMEINSAM + +| Posten | im Budget des Solvers | steht real auf dem Kletterer | +|---|---|---| +| GF1 | ja | nein - liegt im Eingangs-Schenkel | +| Motor + Separator + ES | nein | ja | + +Die beiden Betraege heben sich TEILWEISE auf. Wer nur einen davon korrigiert, +verschiebt den Fehler bloss in die andere Richtung - genau das war der +746-mm-Befund vom 2026-10-28, der damals wie eine Verschlechterung aussah. +Gemessen am 9-Grad-Lauf: GF1 1636.9 - (Motor+Sep+ES) 1235.1 = 401.8 mm, exakt +die damalige Restabweichung von 401.7 mm. + +### Messergebnis + +| Lauf | vorher | nachher | +|---|---|---| +| U-Form 9 Grad | 1424 -> 934 mm | **0.56 mm** | +| U-Form 12 Grad | - | **0.7 mm** | +| Variante A (Vario-Kurve) | OK | OK | +| Variante B (GF-Bogen) | OK | OK | + +Zwei verschiedene Winkel auf demselben Pfad - der Restfehler skaliert also +nicht mehr mit `L_GF`, die Bilanz selbst stimmt. + +### Regression auf dem Weg dorthin (Lehrstueck) + +`kletter-ende-fp` schlug zunaechst auch in Variante B zu und verkuerzte deren +Segment 1 um 798.9 mm. Dort ist `vf-start = vf-ende = 0`: der VF-Lauf besteht +aus EINEM Segment, die Segmente 2..5 sind Back-Lauf. Damit ist der Kletterer +zugleich Laufende, der Abzug griff - obwohl ohne flache Zone die Motorstation +bereits in `feste-vf` steckt und Separator + ES vom Back-Lauf reserviert werden. +Die drei Bedingungen am `if` (`hor-pairs > 0`, `vf-ende = n-1`, `es-vorhanden`) +sind deshalb keine Vorsichtsmassnahme, sondern benennen genau die Faelle, in +denen sonst doppelt gebucht wird. + +Auffindbar war die Ursache nur, weil Segment 1 von Variante B bei `hz=0` liegt: +eine Verkuerzung dieses Segments uebersetzt sich 1:1 in `dX`, und `IST - SOLL = +(-799.11 | -104.68)` aus `logs/vfl_modus2.dbg` zeigte die reine X-Verschiebung. diff --git a/lang/de_DE.json b/lang/de_DE.json index dbdb4e4..d1c5f86 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -265,6 +265,7 @@ "vfl-m2-segwert-unbekannt": "Segment %1 nicht lesbar: Kette %2 enthaelt einen unbekannten Klassifizierungswert. Aufbau abgebrochen - ein geratener Segmenttyp wuerde die Kette falsch neu bauen.", "vfl-m2-eck-korrektur": "\n Eck-Korrektur %1 mm auf Segment %2 (gezeichneter Bogen gegen Fussabdruck des Eck-Bauteils).", "vfl-m2-abschluss-fussabdruck": "\n Abschluss-Fussabdruck %1 mm von Segment %2 abgezogen (Abschlussbogen + Motorstation stehen darauf).", + "vfl-m2-kletter-ende-fussabdruck": "\n Kletter-Ende: %1 mm von Segment %2 abgezogen (Motorstation + Separator + ES stehen auf dem Kletterer, nicht auf einem Fueller).", "vfl-m2-as-fussabdruck": "\n AS-Fussabdruck %1 mm von Segment %2 abgezogen (das AS-Element steht selbst darauf).", "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.", @@ -728,8 +729,8 @@ "plq-nichts-zu-loeschen": "\n[DELETE_PLANQUADRATE] Kein Planquadrat-Raster in der Zeichnung vorhanden.", "exp-check-ids": "\n[%1] Pruefe und vergebe IDs...", "exp-hoehencheck-frage": "\nVor dem Export den Hoehen-Check (Montagehoehe/Buehnen) ausfuehren?", - "exp-hoehencheck-fehlt": "\nHinweis: HOEHEN_CHECK nicht verfuegbar - Export laeuft ohne Hoehen-Check.", "exp-bereinigen": "\nBereinige Zeichnung (PURGE: unbenutzte Bloecke, Layer, Linientypen) ...", + "exp-hoehencheck-fehlt": "\nHinweis: HOEHEN_CHECK nicht verfuegbar - Export laeuft ohne Hoehen-Check.", "exp-hoehen-aktualisiert": "\nHoehen-Attribute von %1 verschobenen Bloecken aus der Z-Koordinate neu gesetzt.", "exp-sep-proxy-fehler": "\n[EXPORT] WARNUNG: Kopie von '%1' aus Block '%2' fehlgeschlagen (%3) - dieses Element fehlt im Export.", "exp-sep-zuordnung": "\n[%1] verpackte Separator-Kopie(n): ZUORDNUNG aus der ID des Wrapper-Blocks gesetzt.", @@ -1236,6 +1237,8 @@ "kf-edit-waehlen": "\nKettenfoerderer (KF_n) zum Bearbeiten waehlen: ", "kf-edit-kein-kf": "Der gewaehlte Block ist kein Kettenfoerderer (KF_n).", "kf-edit-fertig": "\n>>> %1 neu aufgebaut als %2.", - "cmd-kettenfoerderer-bearbeiten": "\n>>> Kettenfoerderer %1 wird bearbeitet ..." + "cmd-kettenfoerderer-bearbeiten": "\n>>> Kettenfoerderer %1 wird bearbeitet ...", + "vfl-m2-nachrechnen": "\n Gefalteter Lauf: Laengen bei gleichem Winkel neu aufgeteilt - GF %1 -> %2 mm, Kletterstrecke %3 mm (GF1 liegt im Eingangs-Schenkel und verbraucht die Kletterer-Spanne nicht).", + "vfl-m2-nachrechnen-fehl": "Der gewaehlte Winkel %1 Grad geht mit der korrigierten Laengenbilanz nicht mehr auf. Die Kette wird mit der urspruenglichen Aufteilung gebaut und kann daher am Ende zu frueh enden - bitte den Ist/Soll-Vergleich pruefen oder einen anderen Winkel waehlen." } } diff --git a/lang/en_GB.json b/lang/en_GB.json index e42686c..0c64c41 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -265,6 +265,7 @@ "vfl-m2-segwert-unbekannt": "Segment %1 cannot be read: chain %2 contains an unknown classification value. Build aborted - guessing a segment type would rebuild the chain incorrectly.", "vfl-m2-eck-korrektur": "\n Corner correction %1 mm on segment %2 (drawn arc versus footprint of the corner component).", "vfl-m2-abschluss-fussabdruck": "\n Closing footprint %1 mm deducted from segment %2 (closing bend + motor station sit on it).", + "vfl-m2-kletter-ende-fussabdruck": "\n Climbing end: %1 mm deducted from segment %2 (motor station + separator + ES sit on the climber, not on a filler).", "vfl-m2-as-fussabdruck": "\n AS footprint %1 mm deducted from segment %2 (the AS element sits on it).", "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.", @@ -728,8 +729,8 @@ "plq-nichts-zu-loeschen": "\n[DELETE_PLANQUADRATE] No Planquadrat grid found in the drawing.", "exp-check-ids": "\n[%1] Checking and assigning IDs...", "exp-hoehencheck-frage": "\nRun the height check (mounting height/platforms) before the export?", - "exp-hoehencheck-fehlt": "\nNote: HOEHEN_CHECK not available - export runs without height check.", "exp-bereinigen": "\nPurging drawing (unused blocks, layers, linetypes) ...", + "exp-hoehencheck-fehlt": "\nNote: HOEHEN_CHECK not available - export runs without height check.", "exp-hoehen-aktualisiert": "\nHeight attributes of %1 moved blocks reset from their Z coordinate.", "exp-sep-proxy-fehler": "\n[EXPORT] WARNING: copying '%1' out of block '%2' failed (%3) - this element is missing from the export.", "exp-sep-zuordnung": "\n[%1] packed separator copy/copies: ZUORDNUNG set from the wrapper block ID.", @@ -1236,6 +1237,8 @@ "kf-edit-waehlen": "\nSelect chain conveyor (KF_n) to edit: ", "kf-edit-kein-kf": "The selected block is not a chain conveyor (KF_n).", "kf-edit-fertig": "\n>>> %1 rebuilt as %2.", - "cmd-kettenfoerderer-bearbeiten": "\n>>> Editing chain conveyor %1 ..." + "cmd-kettenfoerderer-bearbeiten": "\n>>> Editing chain conveyor %1 ...", + "vfl-m2-nachrechnen": "\n Folded run: lengths redistributed at the same angle - GF %1 -> %2 mm, climbing section %3 mm (GF1 sits in the entry leg and does not consume the climber span).", + "vfl-m2-nachrechnen-fehl": "The chosen angle of %1 degrees no longer works with the corrected length balance. The chain is built with the original distribution and may therefore end too early - please check the actual/target comparison or pick a different angle." } } diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index 68e7143..bc08050 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 _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))