[MERGE] Lager branch in main

This commit is contained in:
2026-10-30 11:44:19 +01:00
96 changed files with 9707 additions and 727 deletions
+762 -2
View File
@@ -399,6 +399,342 @@
)
;; ------------------------------------------------------------
;; TESTS: vf_linienzug.lsp - "Parallel zum Kreisel" (Rechenkerne)
;; ------------------------------------------------------------
;; Die Bau-Funktion vfl-gf-parallel-kreisel braucht eine Zeichnung (sie misst
;; AS und Bogen an echten Bloecken); ihre beiden Rechenkerne sind davon frei
;; und stehen deshalb hier:
;; vfl-par-bogen-waehlen - welcher Bogen hebt den AS-Schwenk auf?
;; 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 _plan2)
(princ "\n[vf_linienzug Parallel zum Kreisel]")
;; --- Bogenwahl: der Bogen muss den AS-Schwenk GENAU aufheben ---
;; Rechts-Boegen schwenken negativ, Links-Boegen positiv.
(setq _b30 '(30 . -30.0) _b60 '(60 . -60.0) _b90 '(90 . -90.0))
(setq _kand (list _b30 _b60 _b90))
;; AS links 30 Grad (Schwenk +30) -> 30-Grad-Bogen nach rechts
(tu-eq "vfl-par-bogen/as30-rechts" 30 (vfl-par-bogen-waehlen 30.0 _kand 2.0))
;; AS links 90 Grad (Schwenk +90) -> 90-Grad-Bogen nach rechts
(tu-eq "vfl-par-bogen/as90-rechts" 90 (vfl-par-bogen-waehlen 90.0 _kand 2.0))
;; Generisch: auch ein 60-Grad-Schwenk findet seinen Bogen - die Zuordnung
;; folgt den gemessenen Bloecken, nicht einer gepflegten Tabelle.
(tu-eq "vfl-par-bogen/as60-rechts" 60 (vfl-par-bogen-waehlen 60.0 _kand 2.0))
;; kleine Messabweichung bleibt in der Toleranz
(tu-eq "vfl-par-bogen/toleranz-ok" 30 (vfl-par-bogen-waehlen 31.2 _kand 2.0))
;; ausserhalb der Toleranz: lieber nil als ein schiefer Bogen
(tu-nil "vfl-par-bogen/zu-weit-weg" (vfl-par-bogen-waehlen 45.0 _kand 2.0))
(tu-nil "vfl-par-bogen/leere-liste" (vfl-par-bogen-waehlen 30.0 '() 2.0))
;; Gegenseite: Links-Boegen heben einen negativen AS-Schwenk auf
(setq _kand (list '(30 . 30.0) '(60 . 60.0) '(90 . 90.0)))
(tu-eq "vfl-par-bogen/as-rechts30-links" 30 (vfl-par-bogen-waehlen -30.0 _kand 2.0))
;; --- GF-Laenge: abstand = perp-as + L*k + perp-bogen ---
;; Glatter Fall: k = 1 (AS schwenkt 90 Grad aus der Achse), feste Anteile
;; zusammen 500 -> fuer 2000 mm Abstand bleiben 1500 mm GF.
(tu-eqf "vfl-par-laenge/k1" 1500.0 (vfl-par-gf-laenge 2000.0 200.0 300.0 1.0))
;; Gegenseite: k negativ -> der Abstand zaehlt auf der anderen Seite, der
;; Betrag der Eingabe bleibt derselbe (Seite kommt aus der AS-Variante).
(tu-eqf "vfl-par-laenge/k-negativ" 1500.0
(vfl-par-gf-laenge 2000.0 -200.0 -300.0 -1.0))
;; Schraege Ausschleusung: k = sin(60 Grad)
(setq _l (vfl-par-gf-laenge 2000.0 0.0 0.0 (sin (/ (* 60.0 pi) 180.0))))
(tu-eqf "vfl-par-laenge/k-sin60" (/ 2000.0 (sin (/ (* 60.0 pi) 180.0))) _l)
;; Abstand kleiner als der feste Anteil -> negative Laenge (der Aufrufer
;; meldet das als "Abstand zu klein", die Rechnung selbst raet nicht).
(tu-true "vfl-par-laenge/zu-klein-negativ"
(< (vfl-par-gf-laenge 100.0 200.0 300.0 1.0) 0.0))
;; AS schwenkt gar nicht aus der Achse -> keine Loesung
(tu-nil "vfl-par-laenge/kein-versatz" (vfl-par-gf-laenge 2000.0 0.0 0.0 0.0))
;; --- Grenzen einer VF-Einheit (Modus 2) ---
;; Zwischen Umlenk- und Motorstation: hoechstens 2 Vario-Kurven und 25 m.
;; Plan-Eintraege: ("Linie" hz laenge ...) / ("Bogen" hz chord ...) - nur
;; Typ und Laenge (3. Feld) zaehlen hier.
(setq _plan (list '("Linie" 0.0 9000.0 "VF") '("Bogen" 0.0 500.0)
'("Linie" 0.0 8000.0 "VF") '("Bogen" 0.0 500.0)
'("Linie" 0.0 6000.0 "VF")))
;; 24000 mm, 2 Kurven -> alles im Rahmen
(setq _g (vfl-m2-lauf-grenzen _plan 0 4 25000.0 2))
(tu-eqf "vfl-m2-grenzen/laenge" 24000.0 (nth 0 _g))
(tu-eq "vfl-m2-grenzen/kurven" 2 (nth 1 _g))
(tu-nil "vfl-m2-grenzen/ok" (nth 2 _g))
;; Dritte Kurve -> an ihr (Index 5) teilen, obwohl die Laenge noch passt
(setq _plan3 (append _plan (list '("Bogen" 0.0 400.0) '("Linie" 0.0 300.0 "VF"))))
(tu-eq "vfl-m2-grenzen/dritte-kurve" 5 (nth 2 (vfl-m2-lauf-grenzen _plan3 0 6 25000.0 2)))
;; Zu lang -> am ersten Bogen teilen, nach dem die Grenze reisst (Index 3:
;; bis dahin sind 9000+500+8000 = 17500 drin, die Grenze liegt bei 15000).
(tu-eq "vfl-m2-grenzen/zu-lang" 3 (nth 2 (vfl-m2-lauf-grenzen _plan 0 4 15000.0 2)))
;; Eine einzige lange Gerade: zu lang, aber kein Bogen zum Teilen -> -1
(tu-eq "vfl-m2-grenzen/unteilbar" -1
(nth 2 (vfl-m2-lauf-grenzen (list '("Linie" 0.0 30000.0 "VF")) 0 0 25000.0 2)))
;; --- Vorgabewert aus einem Prompt ziehen (Dialog-Feld vorbelegen) ---
;; Die Konsolen-Prompts tragen ihre Vorgabe in Klammern; der Zahlen-Dialog
;; liest sie daraus. Gesucht ist die LETZTE Klammergruppe MIT Zahl - die
;; echten Texte enthalten davor "<" ohne Partner ("0 < Winkel <= 3") und
;; runde Klammern mit Fliesstext, beides darf nicht stoeren.
(tu-eq "vflw-vorgabe/neigungswinkel" "3"
(vflw-vorgabe-aus-prompt "\nNeigungswinkel (Grad, 0 < Winkel <= 3) [3]: "))
(tu-eq "vflw-vorgabe/neigungswinkel-berechnet" "3"
(vflw-vorgabe-aus-prompt
"\nNeigungswinkel (Grad, 0 < Winkel <= 3) [3] (deltaL wird berechnet und eingefuegt): "))
(tu-eq "vflw-vorgabe/spitze-klammer" "2154.0"
(vflw-vorgabe-aus-prompt "\nGewuenschte Hoehe am Kettenanfang (mm) <2154.0>: "))
(tu-eq "vflw-vorgabe/negativ" "-12.5"
(vflw-vorgabe-aus-prompt "Hoehendifferenz <-12.5>: "))
;; Ohne Klammergruppe bleibt das Feld leer (nil), nicht geraten.
(tu-nil "vflw-vorgabe/ohne-vorgabe"
(vflw-vorgabe-aus-prompt "\nAbstand zum Kreisel (mm, senkrecht zur Achse): "))
(tu-nil "vflw-vorgabe/keine-zahl"
(vflw-vorgabe-aus-prompt "Bitte Namen eingeben [frei]: "))
;; --- Modus-2-Lauf-Anfang: Journal-Wert normieren ---
;; Die Antwort wird als selbstbeschreibender Wert gefuehrt, damit ein
;; Umsortieren/Umnummerieren der Optionen bestehende Ketten nicht
;; umdeutet. Alte Ketten tragen dort noch die Ziffern der ALTEN Folge -
;; die Zuordnung dafuer steht als Tabelle in *vfl-m2-anfang-alt* und wird
;; hier komplett durchlaufen, statt einzelne Faelle abzuschreiben.
(foreach _p *vfl-m2-anfang-alt*
(tu-eq (strcat "vfl-m2-anfang/alt-" (car _p))
(cdr _p) (vfl-m2-anfang-norm (car _p))))
;; Jeder Tabellenwert muss auch ein gueltiger heutiger Wert sein - sonst
;; laeuft eine Alt-Kette in den Unbekannt-Abbruch.
(foreach _p *vfl-m2-anfang-alt*
(tu-true (strcat "vfl-m2-anfang/alt-ziel-gueltig-" (car _p))
(and (member (cdr _p) *vfl-m2-anfang-werte*) t)))
;; Neue Werte gehen unveraendert durch.
(foreach _w *vfl-m2-anfang-werte*
(tu-eq (strcat "vfl-m2-anfang/neu-" _w) _w (vfl-m2-anfang-norm _w)))
;; Unbekanntes wird NICHT geraten - nil heisst fuer den Aufrufer Abbruch.
(tu-nil "vfl-m2-anfang/unbekannte-ziffer" (vfl-m2-anfang-norm "4"))
(tu-nil "vfl-m2-anfang/leer" (vfl-m2-anfang-norm ""))
(tu-nil "vfl-m2-anfang/tippfehler" (vfl-m2-anfang-norm "mit_gf1"))
(tu-nil "vfl-m2-anfang/kein-string" (vfl-m2-anfang-norm 3))
(tu-nil "vfl-m2-anfang/nil" (vfl-m2-anfang-norm nil))
;; --- Eck-Korrektur: gezeichneter Bogen gegen Eck-Bauteil ---
;; Die Ecke des Pfads wird durch ein reales Bauteil ersetzt, dessen
;; Fussabdruck abweichen darf. Die Differenz wird auf die Richtungen der
;; beiden angrenzenden Geraden zerlegt. Werte aus dem echten Lauf vom
;; 2026-10-26: 90-Grad-Ecke, Pfad R500 (Sehne 707.1 -> 500/-500), gebaute
;; Vario-Kurve wirkt wie R406.2 -> je 93.78 mm auf beide Nachbargeraden.
(setq _ez (vfl-eck-zerlegen 500.0 -500.0 406.22 -406.22 (- (/ pi 2.0))))
(tu-eqf "vfl-eck-zerlegen/90-grad-davor" 93.78 (car _ez))
(tu-eqf "vfl-eck-zerlegen/90-grad-danach" 93.78 (cadr _ez))
;; 30-Grad-Ecke links, Gefaellebogen R500 trifft den Pfad fast exakt:
;; nur 0.85 mm, und zwar ausschliesslich auf der Geraden DAVOR.
(setq _ez (vfl-eck-zerlegen 250.0 66.99 249.15 66.99 (/ pi 6.0)))
(tu-eqf "vfl-eck-zerlegen/30-grad-davor" 0.85 (car _ez))
(tu-eqf "vfl-eck-zerlegen/30-grad-danach" 0.0 (cadr _ez))
;; Deckungsgleiches Bauteil -> kein Zuschlag auf keiner Seite.
(setq _ez (vfl-eck-zerlegen 500.0 -500.0 500.0 -500.0 (- (/ pi 2.0))))
(tu-eqf "vfl-eck-zerlegen/gleich-davor" 0.0 (car _ez))
(tu-eqf "vfl-eck-zerlegen/gleich-danach" 0.0 (cadr _ez))
;; Ohne Schwenk ist die Zerlegung nicht eindeutig - nil statt Division durch 0.
(tu-nil "vfl-eck-zerlegen/ohne-schwenk"
(vfl-eck-zerlegen 100.0 0.0 90.0 0.0 0.0))
;; GF-Boegen werden GENEIGT eingebaut: ihr Grundriss-Fussabdruck ist kuerzer
;; als flach gemessen, und genau diese Differenz muss in die Eck-Korrektur.
;; Werte aus dem echten Lauf 2026-10-26: Gefaellebogen_rechts_90_R500 flach
;; dx=500 dz=-28.6, bei 3 Grad eingebaut 497.8 - die 2.2 mm blieben vorher
;; als Rest am Kettenende stehen. Negatives dz muss dabei VERKUERZEN.
(tu-eqf "vfl-projiziere-neigung/gf-bogen-3-grad"
(- (* 500.0 (cos (* 3.0 (/ pi 180.0))))
(* 28.6 (sin (* 3.0 (/ pi 180.0)))))
(car (vfl-projiziere-neigung 500.0 -28.6 3.0)))
(tu-true "vfl-projiziere-neigung/gf-bogen-kuerzer-als-flach"
(< (car (vfl-projiziere-neigung 500.0 -28.6 3.0)) 500.0))
;; Waagerecht eingebaut (Vario-Kurve) bleibt der Fussabdruck unveraendert.
(tu-eqf "vfl-projiziere-neigung/waagerecht" 500.0
(car (vfl-projiziere-neigung 500.0 -28.6 0.0)))
;; --- Bau-Funktion aus dem Journal ableiten (vf_spec.lsp) ---
;; vsp-bau-journal waehlt die Modus-Funktion anhand des Journals, statt sie
;; als zusaetzliches Argument zu bekommen (exakte Argumentzahl, siehe
;; CLAUDE.md). Kein SEGS = Modus 1; SEGS mit der Formatmarke "m2v2" =
;; Modus 2 (nur vf-linienzug-modus2 schreibt sie); SEGS ohne = Modus 3.
(tu-true "vsp-bau-fn/ohne-segs-ist-modus1"
(eq (vsp-bau-fn-fuer-journal
(list (cons "PT" '(0.0 0.0 0.0)) (cons "STR" "1")))
'vf-linienzug-modus))
(tu-true "vsp-bau-fn/segs-mit-marke-ist-modus2"
(eq (vsp-bau-fn-fuer-journal
(list (cons "PT" '(0.0 0.0 0.0)) (cons "SEGS" '(("Linie")))
(cons "STR" "m2v2") (cons "STR" "2")))
'vf-linienzug-modus2))
(tu-true "vsp-bau-fn/segs-ohne-marke-ist-modus3"
(eq (vsp-bau-fn-fuer-journal
(list (cons "PT" '(0.0 0.0 0.0)) (cons "SEGS" '(("Linie")))
(cons "STR" "2")))
'vf-linienzug-modus3))
;; Die Marke zaehlt nur als STR-Eintrag - ein gleichlautender Wert in einem
;; anderen Eintragstyp darf nicht faelschlich auf Modus 2 schliessen lassen.
(tu-true "vsp-bau-fn/marke-nur-als-str"
(eq (vsp-bau-fn-fuer-journal
(list (cons "SEGS" '(("Linie"))) (cons "INT" "m2v2")))
'vf-linienzug-modus3))
;; --- Segment-Klassifizierung: Wert statt Menue-Ziffer ---
;; Gemeinsamer Normalisierer fuer alle selbstbeschreibenden Antworten.
;; Bestandsketten tragen die Alt-Ziffer, neue Ketten den Wert; beide Mengen
;; sind disjunkt. Unbekanntes gibt nil - der Aufrufer bricht dann ab.
(foreach _p *vfl-m2-linie-alt*
(tu-eq (strcat "vfl-wert-norm/linie-alt-" (car _p)) (cdr _p)
(vfl-wert-norm (car _p) *vfl-m2-linie-werte* *vfl-m2-linie-alt*)))
(foreach _p *vfl-m2-bogen-alt*
(tu-eq (strcat "vfl-wert-norm/bogen-alt-" (car _p)) (cdr _p)
(vfl-wert-norm (car _p) *vfl-m2-bogen-werte* *vfl-m2-bogen-alt*)))
(foreach _p *vfl-m2-variante-alt*
(tu-eq (strcat "vfl-wert-norm/variante-alt-" (car _p)) (cdr _p)
(vfl-wert-norm (car _p) *vfl-m2-variante-werte* *vfl-m2-variante-alt*)))
;; Neue Werte gehen unveraendert durch.
(foreach _w *vfl-m2-bogen-werte*
(tu-eq (strcat "vfl-wert-norm/bogen-neu-" _w) _w
(vfl-wert-norm _w *vfl-m2-bogen-werte* *vfl-m2-bogen-alt*)))
;; Jeder Alt-Zielwert muss ein gueltiger heutiger Wert sein, sonst laeuft
;; eine Bestandskette in den Unbekannt-Abbruch.
(foreach _p *vfl-m2-linie-alt*
(tu-true (strcat "vfl-wert-norm/linie-ziel-gueltig-" (car _p))
(and (member (cdr _p) *vfl-m2-linie-werte*) t)))
;; Typ und Variante benutzten frueher BEIDE "1"/"2" und waren im Journal
;; nicht zu unterscheiden - die neuen Wertevorraete muessen disjunkt sein.
(tu-nil "vfl-wert-norm/bogen-und-variante-disjunkt"
(vl-some (function (lambda (w) (if (member w *vfl-m2-variante-werte*) w)))
*vfl-m2-bogen-werte*))
(tu-nil "vfl-wert-norm/unbekannt-gibt-nil"
(vfl-wert-norm "3" *vfl-m2-linie-werte* *vfl-m2-linie-alt*))
(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
;; Querversatz stehen. Werte aus dem echten Lauf 2026-10-26:
;; ES_Element_30_rechts an einer Geraden mit hz=-60, Schwenk -30 Grad,
;; Fussabdruck (laengs 575.84 | quer -52.54). Reserve 484.8 statt 575.8 -
;; die Gerade wird also rund 91 mm laenger und hebt den Querversatz auf.
(tu-eqf "vfl-es-achs-reserve/echt-30-grad" 484.838050570331
(vfl-es-achs-reserve 575.84 -52.54 -60.0 -30.0))
;; Ohne Querversatz bleibt es beim reinen Laengsanteil.
(tu-eqf "vfl-es-achs-reserve/ohne-quer" 500.0
(vfl-es-achs-reserve 500.0 0.0 -60.0 -30.0))
;; Ohne Schwenk faellt die Achse mit der Fahrtrichtung zusammen - dann gibt
;; es keinen aufhebbaren Querversatz und der Laengsanteil ist richtig
;; (Nenner 0, Rueckfall statt Division durch null).
(tu-eqf "vfl-es-achs-reserve/ohne-schwenk" 575.84
(vfl-es-achs-reserve 575.84 -52.54 -60.0 0.0))
;; Richtungsunabhaengig: dieselbe Geometrie um 90 Grad gedreht gibt
;; denselben Wert - die Reserve haengt an den Winkeln zueinander, nicht
;; an der Lage im Welt-KS.
(tu-eqf "vfl-es-achs-reserve/gedreht" 484.838050570331
(vfl-es-achs-reserve 575.84 -52.54 30.0 -30.0))
;; --- Rueckwaerts bauen: Punkt entgegen der Fahrtrichtung zuruecksetzen ---
;; vfl-rueck-punkt ist der Rechenkern des Rueckwaerts-Baus: zu einem
;; BEKANNTEN Endpunkt den Startpunkt eines Bausteins finden. Vorwaerts gilt
;; P_next = P + (L*cos hz, L*sin hz, -L*tan w) - hier also mit umgekehrtem
;; Vorzeichen. Die Hoehe muss STEIGEN (der Vorgaenger liegt hoeher).
(tu-eqf "vfl-rueck-punkt/ost-flach"
(list 0.0 0.0 0.0) (vfl-rueck-punkt '(1000.0 0.0 0.0) 0.0 1000.0 0.0))
(tu-eqf "vfl-rueck-punkt/nord-flach"
(list 0.0 0.0 0.0) (vfl-rueck-punkt '(0.0 1000.0 0.0) 90.0 1000.0 0.0))
;; 45 Grad Gefaelle: 1000 mm horizontal zurueck = 1000 mm hoeher
(tu-eqf "vfl-rueck-punkt/45-grad"
(list 0.0 0.0 1000.0) (vfl-rueck-punkt '(1000.0 0.0 0.0) 0.0 1000.0 45.0))
;; Rundlauf: vorwaerts wieder auf den Ausgangspunkt (3 Grad, schraeg)
(setq _l (vfl-rueck-punkt '(5000.0 3000.0 1800.0) 30.0 2000.0 3.0))
(tu-eqf "vfl-rueck-punkt/rundlauf-x" 5000.0
(+ (car _l) (* 2000.0 (cos (/ (* 30.0 pi) 180.0)))))
(tu-eqf "vfl-rueck-punkt/rundlauf-y" 3000.0
(+ (cadr _l) (* 2000.0 (sin (/ (* 30.0 pi) 180.0)))))
(tu-eqf "vfl-rueck-punkt/rundlauf-z" 1800.0
(- (caddr _l) (* 2000.0 (/ (sin (/ (* 3.0 pi) 180.0)) (cos (/ (* 3.0 pi) 180.0))))))
)
;; ------------------------------------------------------------
;; TESTS: vf_linienzug.lsp - Laenge aus einer Ziellinie (X/Y)
;; ------------------------------------------------------------
@@ -446,6 +782,86 @@
(+ (cadr _ks) (sin (/ pi 6.0))) 0.0)))
(tu-true "vfl-laenge-bis-ziellinie/realfall-plausibel"
(and _l (> _l 1430.0) (< _l 1431.0)))
;; --- AS-Seite aus der Lage des naechsten Punkts zur Basislinie ---
;; Basislinie zeigt nach +X; ein Ziel mit +Y liegt links davon, mit -Y rechts.
(tu-eq "vfl-seite-aus-lage/links"
"links" (vfl-seite-aus-lage '(0.0 0.0 0.0) '(1000.0 0.0 0.0) '(500.0 800.0 0.0)))
(tu-eq "vfl-seite-aus-lage/rechts"
"rechts" (vfl-seite-aus-lage '(0.0 0.0 0.0) '(1000.0 0.0 0.0) '(500.0 -800.0 0.0)))
;; Richtung der Basislinie umdrehen -> Seite dreht mit
(tu-eq "vfl-seite-aus-lage/basislinie-umgedreht"
"rechts" (vfl-seite-aus-lage '(1000.0 0.0 0.0) '(0.0 0.0 0.0) '(500.0 800.0 0.0)))
;; schraege Basislinie (45 Grad): Ziel oberhalb davon liegt links
(tu-eq "vfl-seite-aus-lage/schraeg"
"links" (vfl-seite-aus-lage '(0.0 0.0 0.0) '(1000.0 1000.0 0.0) '(0.0 1000.0 0.0)))
;; Z spielt keine Rolle (rein planare Entscheidung)
(tu-eq "vfl-seite-aus-lage/z-ignoriert"
"rechts" (vfl-seite-aus-lage '(0.0 0.0 5000.0) '(1000.0 0.0 -200.0) '(500.0 -800.0 999.0)))
;; --- VF-Hoehe je SEGMENT (Attribut VF_HOEHE_m, Sivas 4433-4437) ---
;; Ein Eintrag je GERADEM VF-Abschnitt: Mittelwert aus dessen KS_EIN und
;; KS_AUS, in METERN mit 3 Nachkommastellen. Zwischen Umlenk- und
;; Motorstation koennen mehrere Abschnitte liegen (auf/ab/horizontal), die
;; Liste laeuft darum parallel zu L_VF_m und VF_WINKEL.
(vf-vfhoehe-reset)
(tu-eq "vf-vfhoehe/leer-nach-reset" "" (vfl-join-komma *vf-acc-vfhoehe*))
;; 2000 mm und 3000 mm -> Mittel 2500 mm -> "2.500"
(vf-vfhoehe-segment 2000.0 3000.0)
(tu-eq "vf-vfhoehe/mittelwert-meter" "2.500" (vfl-join-komma *vf-acc-vfhoehe*))
;; Drei Abschnitte wie im realen Block (L_VF_m hatte dort 3 Eintraege):
;; horizontal, horizontal, steigend - alle drei bekommen einen Wert.
(vf-vfhoehe-reset)
(vf-vfhoehe-segment 2076.0 2076.0) ; horizontal
(vf-vfhoehe-segment 2076.0 2076.0) ; horizontal
(vf-vfhoehe-segment 2076.0 2600.0) ; steigend -> Mittel 2338
(tu-eq "vf-vfhoehe/drei-abschnitte"
"2.076,2.076,2.338" (vfl-join-komma *vf-acc-vfhoehe*))
;; Unvollstaendige Messung haengt NICHTS an - sonst verschoebe sich die
;; Liste gegen L_VF_m und eine Hoehe landete beim falschen Foerderer.
(vf-vfhoehe-reset)
(vf-vfhoehe-segment 1000.0 2000.0)
(vf-vfhoehe-segment nil 3000.0)
(vf-vfhoehe-segment 4000.0 nil)
(tu-eq "vf-vfhoehe/unvollstaendig-ignoriert" "1.500" (vfl-join-komma *vf-acc-vfhoehe*))
;; negative Hoehen (Kette unter Null) muessen durchgehen
(vf-vfhoehe-reset)
(vf-vfhoehe-segment -500.0 -1500.0)
(tu-eq "vf-vfhoehe/negativ" "-1.000" (vfl-join-komma *vf-acc-vfhoehe*))
(vf-vfhoehe-reset)
;; vf-meter-3: GENAU 3 Nachkommastellen, unabhaengig von DIMZIN.
;; (rtos x 2 3) allein kuerzt nachfolgende Nullen ("2.5", "-1") - real im
;; Testlauf aufgetreten, deshalb eigene Formatierung.
(tu-eq "vf-meter-3/glatt" "2.500" (vf-meter-3 2500.0))
(tu-eq "vf-meter-3/ganzzahlig" "3.000" (vf-meter-3 3000.0))
(tu-eq "vf-meter-3/negativ" "-1.000" (vf-meter-3 -1000.0))
(tu-eq "vf-meter-3/null" "0.000" (vf-meter-3 0.0))
(tu-eq "vf-meter-3/drei-stellen" "1.234" (vf-meter-3 1234.0))
;; --- SONDERFALL Standard/Etage: Hoehe ueber die GANZE Kette ---
;; Nur ein Motor und nur ein gerader VF-Abschnitt, darum zaehlt dort
;; AS-KS_EIN .. ES-KS_AUS statt des Segmentwerts (Nutzer-Vorgabe 2026-10-21).
(vf-vfhoehe-reset)
(vf-vfhoehe-kette 1000.0 3000.0)
(tu-eq "vf-vfhoehe-kette/mittelwert" "2.000" (vfl-join-komma *vf-acc-vfhoehe*))
;; ERSETZT den beim Bau gesammelten Segmentwert, haengt ihn NICHT an -
;; sonst stuenden fuer eine Standard-Anlage zwei Hoehen in der Liste.
(vf-vfhoehe-reset)
(vf-vfhoehe-segment 5000.0 5000.0) ; Segmentwert aus vfs-vf-koerper
(vf-vfhoehe-kette 1000.0 3000.0)
(tu-eq "vf-vfhoehe-kette/ersetzt-segmentwert" "2.000" (vfl-join-komma *vf-acc-vfhoehe*))
;; fehlender Endpunkt -> lieber leer als geraten
(vf-vfhoehe-reset)
(vf-vfhoehe-kette 1000.0 nil)
(tu-eq "vf-vfhoehe-kette/ohne-endpunkt-leer" "" (vfl-join-komma *vf-acc-vfhoehe*))
(vf-vfhoehe-reset)
)
@@ -2278,6 +2694,349 @@
)
)
;; ------------------------------------------------------------
;; TESTS: KettenFoerderer.lsp + ssg_ks_insert.lsp (KS-Orientierung)
;; ------------------------------------------------------------
;; Reine Rechenkerne des Kettenfoerderers: Hoehenbilanz, Massstab,
;; Grenzpruefung, KS-Winkel. Alles ohne Zeichnung und ohne Blockdateien -
;; die zeichnungsabhaengigen Teile (kf-geometrie-messen, kf-kette-bauen)
;; brauchen BricsCAD und bleiben dem Befehl KF_KS_PRUEFEN vorbehalten.
(defun tu-tests-kf ( / _l _s _defs)
(princ "\n[KettenFoerderer]")
(if (not (car (atoms-family 1 '("KF-LAENGE-BERECHNEN"))))
(tu-record nil "KettenFoerderer-Modul geladen"
"KettenFoerderer.lsp nicht geladen - siehe tests/test_unit.scr")
(progn
;; --- ks-winkel-normalisieren: auf -180..180 falten ---
(tu-eqf "ks-winkel-norm/190" -170.0 (ks-winkel-normalisieren 190.0))
(tu-eqf "ks-winkel-norm/-190" 170.0 (ks-winkel-normalisieren -190.0))
(tu-eqf "ks-winkel-norm/26" 26.0 (ks-winkel-normalisieren 26.0))
(tu-eqf "ks-winkel-norm/360" 0.0 (ks-winkel-normalisieren 360.0))
(tu-nil "ks-winkel-norm/nil" (ks-winkel-normalisieren nil))
;; --- ks-xy-winkel: X-Achse eines KS-Eintrags (origin x-end y-end z-end)
(tu-eqf "ks-xy-winkel/entlang-X" 0.0
(ks-xy-winkel '((0.0 0.0 0.0) (1.0 0.0 0.0) (0.0 2.0 0.0) (0.0 0.0 3.0))))
(tu-eqf "ks-xy-winkel/entlang-Y" 90.0
(ks-xy-winkel '((0.0 0.0 0.0) (0.0 1.0 0.0) (-2.0 0.0 0.0) (0.0 0.0 3.0))))
;; versetzter Ursprung: nur die RICHTUNG zaehlt, nicht die Lage
(tu-eqf "ks-xy-winkel/versetzt-45" 45.0
(ks-xy-winkel (list '(100.0 50.0 0.0) '(101.0 51.0 0.0) '(99.0 51.0 0.0) '(100.0 50.0 1.0))))
;; senkrechte X-Achse (keine XY-Laenge) -> nil statt Division durch 0
(tu-nil "ks-xy-winkel/senkrecht"
(ks-xy-winkel '((0.0 0.0 0.0) (0.0 0.0 1.0) (0.0 2.0 0.0) (0.0 0.0 3.0))))
;; --- kf-hz-folge: Folgebaustein an KS_AUS des Vorgaengers ausrichten ---
;; Vorgaenger zeigt 26 Grad, Folgebaustein ist waagerecht gezeichnet (0)
(tu-eqf "kf-hz-folge/26-auf-0" 26.0 (kf-hz-folge 26.0 0.0))
;; Folgebaustein ist selbst schon um 26 Grad gezeichnet -> keine Drehung
(tu-eqf "kf-hz-folge/26-auf-26" 0.0 (kf-hz-folge 26.0 26.0))
;; fehlende Eigenrichtung -> Baustein uebernimmt die Vorgaengerrichtung
(tu-eqf "kf-hz-folge/ohne-beta" 26.0 (kf-hz-folge 26.0 nil))
(tu-nil "kf-hz-folge/ohne-phi" (kf-hz-folge nil 0.0))
;; --- kf-dy-eingebaut: Hoehenbeitrag nach Rz(hz) und X-Massstab ---
;; waagerechter Baustein, waagerecht eingebaut -> kein Hoehengewinn
(tu-eqf "kf-dy/0-grad" 0.0 (kf-dy-eingebaut 1000.0 0.0 0.0 1.0))
;; 1000 mm unter 30 Grad -> 500 mm (sin 30 = 0.5)
(tu-eqf "kf-dy/30-grad" 500.0 (kf-dy-eingebaut 1000.0 0.0 30.0 1.0))
;; derselbe Baustein doppelt skaliert -> doppelter Hoehengewinn
(tu-eqf "kf-dy/30-grad-skaliert-2" 1000.0 (kf-dy-eingebaut 1000.0 0.0 30.0 2.0))
;; senkrecht eingebaut -> die volle Laenge als Hoehe
(tu-eqf "kf-dy/90-grad" 1000.0 (kf-dy-eingebaut 1000.0 0.0 90.0 1.0))
;; --- kf-laenge-berechnen: Hoehenbilanz nach der Laenge aufloesen ---
;; H=1000, Stationen tragen nichts bei, 30 Grad -> L = 1000/sin30 = 2000
(tu-eqf "kf-laenge/30-grad-glatt" 2000.0
(kf-laenge-berechnen 1000.0 0.0 0.0 0.0 30.0))
;; Stationen tragen 100+150 bei -> nur 750 mm bleiben fuer das Stueck
(tu-eqf "kf-laenge/mit-stationen" 1500.0
(kf-laenge-berechnen 1000.0 100.0 150.0 0.0 30.0))
;; Gegenprobe: die errechnete Laenge ergibt wieder genau H
(setq _l (kf-laenge-berechnen 1000.0 100.0 150.0 0.0 30.0))
(tu-eqf "kf-laenge/rundlauf-ergibt-H" 1000.0
(+ 100.0 150.0 (kf-dy-eingebaut _l 0.0 30.0 1.0)))
;; bereits geneigt gezeichnetes Zwischenstueck: dy-nativ wird abgezogen
;; (dy-nativ skaliert NICHT mit, nur X wird skaliert)
(tu-eqf "kf-laenge/mit-dy-nativ" 1800.0
(kf-laenge-berechnen 1000.0 0.0 0.0 (/ 100.0 (cos (/ pi 6.0))) 30.0))
;; waagerecht -> keine Hoehe erreichbar
(tu-nil "kf-laenge/0-grad-unloesbar"
(kf-laenge-berechnen 1000.0 0.0 0.0 0.0 0.0))
;; --- kf-skala-berechnen ---
(tu-eqf "kf-skala/2000-von-1000" 2.0 (kf-skala-berechnen 2000.0 1000.0))
(tu-nil "kf-skala/ohne-nativlaenge" (kf-skala-berechnen 2000.0 0.0))
(tu-nil "kf-skala/ohne-laenge" (kf-skala-berechnen nil 1000.0))
;; --- kf-laenge-pruefen: Grenzen aus der JSON ---
(tu-nil "kf-pruefen/in-ordnung" (kf-laenge-pruefen 2000.0 100.0 20000.0))
(tu-eq "kf-pruefen/zu-kurz" "kf-fehler-laenge-unter-min"
(kf-laenge-pruefen 50.0 100.0 20000.0))
(tu-eq "kf-pruefen/zu-lang" "kf-fehler-laenge-ueber-max"
(kf-laenge-pruefen 99999.0 100.0 20000.0))
;; negative Laenge = Stationen bringen mehr Hoehe mit als gefordert
(tu-eq "kf-pruefen/negativ" "kf-fehler-hoehe-zu-klein"
(kf-laenge-pruefen -10.0 100.0 20000.0))
(tu-eq "kf-pruefen/nil" "kf-fehler-winkel-null"
(kf-laenge-pruefen nil 100.0 20000.0))
;; --- kf-zahl: JSON-Wert als Zahl (Parser liefert Zahl ODER String) ---
(tu-eqf "kf-zahl/int" 26.0 (kf-zahl 26))
(tu-eqf "kf-zahl/real" 26.0 (kf-zahl 26.0))
(tu-eqf "kf-zahl/string" 26.0 (kf-zahl "26"))
(tu-nil "kf-zahl/nil" (kf-zahl nil))
;; --- kf-attrib-defs: VF-Schema + KF-Zusatz, KEIN Tag doppelt ---
(setq _defs (kf-attrib-defs))
(tu-true "kf-attrib-defs/hat-ID" (assoc "ID" _defs))
(tu-true "kf-attrib-defs/hat-FOERDERERTYP" (assoc "FOERDERERTYP" _defs))
(tu-true "kf-attrib-defs/hat-WINKEL" (assoc "WINKEL" _defs))
(tu-true "kf-attrib-defs/hat-STANDARD_STEUERUNG"
(assoc "STANDARD_STEUERUNG" _defs))
;; Reihenfolge wie im Sivas-Merkmalstamm: 361 folgt direkt auf den Typ,
;; STANDARD_STEUERUNG steht also unmittelbar hinter WINKEL
(tu-eq "kf-attrib-defs/361-folgt-auf-winkel" "STANDARD_STEUERUNG"
(car (nth (1+ (kf-tag-index "WINKEL" _defs)) _defs)))
;; FOERDERERTYP ist die BAUART im Klartext, der Winkel steht in WINKEL
(tu-eq "kf-attrib-defs/FOERDERERTYP-ist-bauart" "Schraegfoerderer Stahl"
(cadr (assoc "FOERDERERTYP" _defs)))
(tu-true "kf-attrib-defs/hat-MOVIMOT" (assoc "MOVIMOT" _defs))
;; Farbe und Antriebsmotor sind ZWEI Eigenschaften (Sivas 354 bzw. 355), nicht
;; ein kombiniertes AUSFUEHRUNG-Attribut
(tu-true "kf-attrib-defs/hat-FARBE" (assoc "FARBE" _defs))
(tu-true "kf-attrib-defs/hat-ANTRIEBSMOTOR" (assoc "ANTRIEBSMOTOR" _defs))
(tu-nil "kf-attrib-defs/kein-AUSFUEHRUNG" (assoc "AUSFUEHRUNG" _defs))
;; Der Kettenfoerderer traegt sich selbst - die Geruest-Tags aus dem
;; VF-Schema werden bewusst NICHT mitgefuehrt
(tu-nil "kf-attrib-defs/kein-GERUEST_EINZELMODUL"
(assoc "GERUEST_EINZELMODUL" _defs))
(tu-nil "kf-attrib-defs/kein-GERUEST_TYP" (assoc "GERUEST_TYP" _defs))
;; Jeder Tag darf nur EINMAL vorkommen (zwei ATTDEFs gleichen Tags waeren
;; im Block ungueltig, ssg-attrib-set-on traefe nur den ersten).
(setq _s 0)
(foreach _d _defs (if (= (car _d) "FOERDERERTYP") (setq _s (1+ _s))))
(tu-eq "kf-attrib-defs/FOERDERERTYP-nur-einmal" 1 _s)
(setq _s 0)
(foreach _d _defs (if (= (car _d) "ARTINR") (setq _s (1+ _s))))
(tu-eq "kf-attrib-defs/ARTINR-nur-einmal" 1 _s)
(setq _s 0)
(foreach _d _defs (if (= (car _d) "ARTINR") (setq _s (1+ _s))))
(tu-eq "kf-attrib-defs/ARTINR-nur-einmal" 1 _s)
;; Der Winkel steht ALLEIN in FOERDERERTYP - kein zweites Winkel-Tag
(tu-nil "kf-attrib-defs/kein-KF_WINKEL" (assoc "KF_WINKEL" _defs))
(tu-nil "kf-attrib-defs/kein-KF_TYP" (assoc "KF_TYP" _defs))
;; Das Schema ist EIGENSTAENDIG, nicht vom VF-Streckenschema geerbt -
;; keines der VF-Tags darf durchschlagen
(tu-nil "kf-attrib-defs/kein-SEITE_AS" (assoc "SEITE_AS" _defs))
(tu-nil "kf-attrib-defs/kein-ANZAHL_VF" (assoc "ANZAHL_VF" _defs))
(tu-nil "kf-attrib-defs/kein-TEILEART" (assoc "TEILEART" _defs))
(tu-eq "kf-attrib-defs/LAYER-ist-F-3" "F-3" (cadr (assoc "LAYER" _defs)))
;; --- kf-variante-finden: ueber den Winkel, auch als String ---
(tu-eq "kf-variante-finden/26" "A"
(cdr (assoc "name"
(kf-variante-finden 26
'((("winkel" . 26) ("name" . "A"))
(("winkel" . 30) ("name" . "B")))))))
(tu-eq "kf-variante-finden/30-als-string" "B"
(cdr (assoc "name"
(kf-variante-finden 30
'((("winkel" . "26") ("name" . "A"))
(("winkel" . "30") ("name" . "B")))))))
(tu-nil "kf-variante-finden/unbekannt"
(kf-variante-finden 45 '((("winkel" . 26) ("name" . "A")))))
;; --- kf-antriebsart (Merkmal 357): Dropdown mit EINER Option ---
;; Der Wert kommt aus dem ERSTEN Listeneintrag - nicht aus dem Code.
(tu-eq "kf-antriebsart/aus-daten" "1.1 kW"
(kf-antriebsart '(("antriebsart" (("name" . "1.1 kW") ("wert" . "1.1 kW"))))))
;; eine zweite Option aendert die Vorgabe NICHT (Index 0 bleibt massgebend)
(tu-eq "kf-antriebsart/erster-eintrag-gewinnt" "1.1 kW"
(kf-antriebsart '(("antriebsart" (("name" . "1.1 kW") ("wert" . "1.1 kW"))
(("name" . "0.55 kW") ("wert" . "0.55 kW"))))))
;; Liste fehlt -> Rueckfall, aber als STRING (nicht T/nil)
(tu-eq "kf-antriebsart/ohne-eintrag" "1.1 kW" (kf-antriebsart '(("titel" . "x"))))
(tu-true "kf-antriebsart/ist-string"
(= (type (kf-antriebsart '(("titel" . "x")))) 'STR))
;; Dezimalpunkt, kein Komma (Nutzervorgabe)
(tu-nil "kf-antriebsart/kein-komma"
(vl-string-search "," (kf-antriebsart '(("titel" . "x")))))
;; --- kf-drehung-gesamt: Drehwinkel fuer KF_EDIT ---
;; Regression: der Drehwinkel wird beim Bau in die Geometrie eingerechnet,
;; die Blockreferenz selbst hat IMMER Drehung 0 (ssg-block-wrap-welt).
;; KF_EDIT las nur Gruppencode 50 und baute eine mit 90 Grad gebaute
;; Kette ungedreht neu. Der gebaute Winkel steht jetzt als XDATA.
(tu-eqf "kf-drehung/nur-xdata" 90.0 (kf-drehung-gesamt 90.0 0.0))
;; Referenz nachtraeglich von Hand um 90 Grad gedreht (Gruppencode 50 in
;; Bogenmass) -> beides addiert sich
(tu-eqf "kf-drehung/xdata-plus-referenz" 180.0
(kf-drehung-gesamt 90.0 (/ pi 2.0)))
;; Altbestand ohne XDATA: nur die Referenzdrehung (bisheriges Verhalten)
(tu-eqf "kf-drehung/ohne-xdata" 45.0 (kf-drehung-gesamt nil (/ pi 4.0)))
;; nichts bekannt -> 0, kein Absturz und kein nil
(tu-eqf "kf-drehung/nichts" 0.0 (kf-drehung-gesamt nil nil))
(tu-true "kf-drehung/ist-zahl" (numberp (kf-drehung-gesamt nil nil)))
;; Summe ueber 180 Grad wird auf -180..180 gefaltet (270 = -90)
(tu-eqf "kf-drehung/normalisiert" -90.0
(kf-drehung-gesamt 180.0 (/ pi 2.0)))
;; ganze Zahl aus der XDATA (nicht real) darf nicht abstuerzen
(tu-eqf "kf-drehung/ganzzahl" 30.0 (kf-drehung-gesamt 30 0.0))
;; --- kf-neigung-aus-punkten: Kern der 3D-Aufrichtung ---
;; Liefert (Schraeglaenge Neigungswinkel) aus Ketten-Start und -Ende.
;; Die Kette wird AUFGERICHTET eingebaut, die Hoehe laeuft also in Z.
;; 3-4-5-Dreieck: 3000 waagerecht, 4000 hoch -> 5000 schraeg
(tu-eqf "kf-neigung/3-4-5-laenge" 5000.0
(car (kf-neigung-aus-punkten '(0.0 0.0 0.0) '(3000.0 0.0 4000.0) 0.0)))
;; 45 Grad: gleiche Betraege waagerecht und hoch
(tu-eqf "kf-neigung/45-grad" 45.0
(cadr (kf-neigung-aus-punkten '(0.0 0.0 0.0) '(1000.0 0.0 1000.0) 0.0)))
;; waagerecht -> Winkel 0, Laenge = Horizontalabstand
(tu-eqf "kf-neigung/waagerecht" '(2000.0 0.0)
(kf-neigung-aus-punkten '(0.0 0.0 0.0) '(2000.0 0.0 0.0) 0.0))
;; Der Horizontalanteil wird auf die KETTENRICHTUNG projiziert, nicht
;; auf die Welt-X-Achse: bei 90 Grad Basisdrehung laeuft die Kette in Y.
;; Ohne Projektion kaeme hier 0 heraus und der Winkel waere 90 statt 45.
(tu-eqf "kf-neigung/gedreht-90" '(1414.213562 45.0)
(kf-neigung-aus-punkten '(0.0 0.0 0.0) '(0.0 1000.0 1000.0) 90.0))
;; fallende Kette -> negativer Winkel
(tu-eqf "kf-neigung/fallend" -45.0
(cadr (kf-neigung-aus-punkten '(0.0 0.0 1000.0) '(1000.0 0.0 0.0) 0.0)))
;; Start = Ende -> nil statt Division durch 0
(tu-nil "kf-neigung/nulllaenge"
(kf-neigung-aus-punkten '(5.0 5.0 5.0) '(5.0 5.0 5.0) 0.0))
;; --- kf-matrix-aufrecht: Y_lokal wird zu Z_welt ---
;; Das ist die Kernaussage der Aufrichtung. Geprueft wird sie an den
;; Bildern der Einheitsvektoren, nicht an den Matrixeintraegen - so
;; bleibt der Test lesbar und unabhaengig von der Schreibweise.
(tu-eqf "kf-matrix/Y-lokal-wird-Z-welt" '(0.0 0.0 1.0)
(mat3-mul-vec3 (kf-matrix-aufrecht 0.0 0.0) '(0.0 1.0 0.0)))
;; Die Fahrtrichtung (X) bleibt bei Basisdrehung 0 die Welt-X-Achse
(tu-eqf "kf-matrix/X-bleibt-X" '(1.0 0.0 0.0)
(mat3-mul-vec3 (kf-matrix-aufrecht 0.0 0.0) '(1.0 0.0 0.0)))
;; Basisdrehung 90 Grad dreht die Fahrtrichtung in die Welt-Y-Achse,
;; die Hoehe bleibt dabei in Z
(tu-eqf "kf-matrix/basis-90-dreht-X-nach-Y" '(0.0 1.0 0.0)
(mat3-mul-vec3 (kf-matrix-aufrecht 90.0 0.0) '(1.0 0.0 0.0)))
(tu-eqf "kf-matrix/basis-90-haelt-Z" '(0.0 0.0 1.0)
(mat3-mul-vec3 (kf-matrix-aufrecht 90.0 0.0) '(0.0 1.0 0.0)))
;; --- Standard-Variante: steht in den DATEN, nicht im Code ---
(tu-true "kf-variante-standard-p/markiert"
(kf-variante-standard-p '(("winkel" . 26) ("standard" . T))))
(tu-nil "kf-variante-standard-p/ohne-feld"
(kf-variante-standard-p '(("winkel" . 30))))
;; Index fuer die Vorauswahl - auch wenn der Standard NICHT der erste ist
(tu-eq "kf-standard-index/zweiter" 1
(kf-standard-index '((("winkel" . 26))
(("winkel" . 30) ("standard" . T)))))
(tu-eq "kf-standard-index/erster" 0
(kf-standard-index '((("winkel" . 26) ("standard" . T))
(("winkel" . 30)))))
;; keine Variante markiert -> erste, statt nil (der Dialog braucht einen Index)
(tu-eq "kf-standard-index/keiner" 0
(kf-standard-index '((("winkel" . 26)) (("winkel" . 30)))))
;; --- kf-punkt-differenzen: zweite Hoehen-Eingabeart (2 Punkte) ---
;; Gemessen wird die Differenz in Z - die Baugruppe steht aufgerichtet im
;; Modell. Zurueckgegeben wird (dz dy): die HOEHE zuerst, dY nur zur
;; Kontrolle (wer in einer Draufsicht pickt, sieht dz = 0).
(tu-eqf "kf-punkt-diff/dz" '(1500.0 0.0)
(kf-punkt-differenzen '(0.0 0.0 0.0) '(2000.0 0.0 1500.0)))
;; Vorzeichen egal - die Reihenfolge der Punkte darf keine Rolle spielen
(tu-eqf "kf-punkt-diff/umgekehrt" '(1500.0 0.0)
(kf-punkt-differenzen '(2000.0 0.0 1500.0) '(0.0 0.0 0.0)))
;; in einer Draufsicht gepickt: dz = 0, dY gross
(tu-eqf "kf-punkt-diff/draufsicht" '(0.0 1200.0)
(kf-punkt-differenzen '(0.0 0.0 0.0) '(3000.0 1200.0 0.0)))
;; Punkte ohne Z (2D-Pick) duerfen nicht abstuerzen: dz ist dann 0
(tu-eqf "kf-punkt-diff/ohne-z" '(0.0 800.0)
(kf-punkt-differenzen '(0.0 0.0) '(100.0 800.0)))
(tu-nil "kf-punkt-diff/nil" (kf-punkt-differenzen nil '(1.0 2.0 3.0)))
;; --- kf-auswahl-wert: "wert" vor "name", Vorgabe bei leerem Index ---
(tu-eq "kf-auswahl-wert/wert" "Nein"
(kf-auswahl-wert '((("name" . "Nein") ("wert" . "Nein"))) 0 "X"))
(tu-eq "kf-auswahl-wert/ausserhalb" "X"
(kf-auswahl-wert '((("name" . "Nein") ("wert" . "Nein"))) 5 "X"))
)
)
)
;; ------------------------------------------------------------
;; TESTS: check_hoehen.lsp - Phase 3 (Buehnen-Familien), reine Kerne
;; ------------------------------------------------------------
(defun tu-tests-hoehen-fam ( / _els)
(princ "\n[check_hoehen/Phase 3 Familien]")
;; --- ch-familien: transitive Verbindung, Sortierung nach ID-Nummer ---
(tu-eq "ch-familien/einzeln-und-kette"
'(("b") ("c" "a" "d") ("e"))
(ch-familien '(("a" 3) ("b" 1) ("c" 2) ("d" 5) ("e" 4))
'(("a" "c") ("c" "d") ("x" "a"))))
(tu-eq "ch-familien/kette-ueber-mittleren"
'(("a" "b" "c"))
(ch-familien '(("a" 1) ("b" 2) ("c" 3)) '(("a" "b") ("b" "c"))))
(tu-eq "ch-familien/ohne-kanten"
'(("a") ("b"))
(ch-familien '(("a" 1) ("b" 2)) nil))
(tu-eq "ch-familien/ring"
'(("a" "b" "c"))
(ch-familien '(("a" 1) ("b" 2) ("c" 3)) '(("a" "b") ("b" "c") ("c" "a"))))
;; --- ch-kreuz-abzug ---
(tu-eq "ch-kreuz-abzug/kein" '("kein" . 0.0) (ch-kreuz-abzug 0.0 0.0 5000.0 5000.0 1500.0))
(tu-eq "ch-kreuz-abzug/konflikt" '("konflikt" . 0.0) (ch-kreuz-abzug 1.5 0.0 5000.0 5000.0 1500.0))
(tu-eq "ch-kreuz-abzug/konflikt2" '("konflikt" . 0.0) (ch-kreuz-abzug 1.5 2.0 5000.0 5000.0 1500.0))
(tu-eq "ch-kreuz-abzug/hoehe" '("hoehe" . 0.0) (ch-kreuz-abzug 1.5 1.5 5000.0 6600.0 1500.0))
(tu-eq "ch-kreuz-abzug/grenze" '("abzug" . 1.5) (ch-kreuz-abzug 1.5 1.5 5000.0 6500.0 1500.0))
(tu-eq "ch-kreuz-abzug/abzug" '("abzug" . 1.5) (ch-kreuz-abzug 1.5 1.5 5000.0 5200.0 1500.0))
;; --- ch-strecke-abzug (Familiensaetze: idx handles z b) ---
(tu-eq "ch-strecke-abzug/innen"
'("innen" . 1.5)
(ch-strecke-abzug '(1 ("a") 5000.0 1.5) '(1 ("a") 5000.0 1.5) 1500.0))
(tu-eq "ch-strecke-abzug/innen-ohne-buehne"
'("innen" . 0.0)
(ch-strecke-abzug '(1 ("a") 5000.0 0.0) '(1 ("a") 5000.0 0.0) 1500.0))
(tu-eq "ch-strecke-abzug/zwischen"
'("abzug" . 1.5)
(ch-strecke-abzug '(1 ("a") 5000.0 1.5) '(2 ("b") 5400.0 1.5) 1500.0))
;; --- ch-schleife-p: gleicher Kreisel + gleiche Hoehe ---
(tu-true "ch-schleife-p/gleicher-kreisel-flach" (ch-schleife-p "K1" "K1" 5000.0 5000.0 1.0))
(tu-true "ch-schleife-p/toleranz" (ch-schleife-p "K1" "K1" 5000.0 5000.8 1.0))
(tu-nil "ch-schleife-p/hoehenunterschied" (ch-schleife-p "K1" "K1" 5000.0 4800.0 1.0))
(tu-nil "ch-schleife-p/anderer-kreisel" (ch-schleife-p "K1" "K2" 5000.0 5000.0 1.0))
(tu-nil "ch-schleife-p/kein-kreisel" (ch-schleife-p nil nil 5000.0 5000.0 1.0))
;; --- ch-abzug-anwenden ---
(tu-eqf "ch-abzug-anwenden/normal" 2.5 (ch-abzug-anwenden 3.5 1.0))
(tu-nil "ch-abzug-anwenden/gleich" (ch-abzug-anwenden 3.5 3.5))
(tu-nil "ch-abzug-anwenden/groesser" (ch-abzug-anwenden 3.5 4.0))
(tu-nil "ch-abzug-anwenden/null" (ch-abzug-anwenden 3.5 0.0))
;; --- ch-kreisel-finden: Kreisel r=400, Achse (400,0)-(2400,0) bzw. (400,5000)-(2400,5000) ---
(setq _els (list (tu-bp-el "KREISEL" 0.0 0.0 5000.0 0.0 2000.0 "K1")
(tu-bp-el "KREISEL" 0.0 5000.0 5000.0 0.0 2000.0 "K2")))
(tu-eq "ch-kreisel-finden/auf-achse"
"K1" (ch-kreisel-finden '(1000.0 100.0 5000.0) 5000.0 _els 300.0 100.0))
(tu-eq "ch-kreisel-finden/zweiter"
"K2" (ch-kreisel-finden '(1000.0 4900.0 5000.0) 5000.0 _els 300.0 100.0))
(tu-eq "ch-kreisel-finden/hinter-dem-rad"
"K1" (ch-kreisel-finden '(-200.0 0.0 5000.0) 5000.0 _els 300.0 100.0))
(tu-nil "ch-kreisel-finden/zu-weit"
(ch-kreisel-finden '(1000.0 1000.0 5000.0) 5000.0 _els 300.0 100.0))
(tu-nil "ch-kreisel-finden/falsche-hoehe"
(ch-kreisel-finden '(1000.0 0.0 5300.0) 5300.0 _els 300.0 100.0))
)
;; ------------------------------------------------------------
;; TESTS: ssg_core.lsp - Polygon-Helfer (reine 2D-Berechnung)
;; ------------------------------------------------------------
@@ -2720,11 +3479,12 @@
(princ "\n================================================================")
(foreach fn '("tu-tests-core" "tu-tests-lang" "tu-tests-id" "tu-tests-vfcore"
"tu-tests-etage" "tu-tests-vfl-es" "tu-tests-vfl-ziellinie"
"tu-tests-etage" "tu-tests-vfl-es" "tu-tests-vfl-parallel"
"tu-tests-vfl-ziellinie"
"tu-tests-export" "tu-tests-hoehen" "tu-tests-gf" "tu-tests-omni" "tu-tests-kreisel" "tu-tests-kreisel-bp"
"tu-tests-sepliste" "tu-tests-json" "tu-tests-geruest"
"tu-tests-sepscan" "tu-tests-vfl-segs" "tu-tests-vfl-abbruch"
"tu-tests-werkzeuge" "tu-tests-poly" "tu-tests-lager")
"tu-tests-werkzeuge" "tu-tests-poly" "tu-tests-lager" "tu-tests-kf" "tu-tests-hoehen-fam")
(tu-run-block fn))
(princ "\n\n----------------------------------------------------------------")