diff --git a/Lisp/vf_core.lsp b/Lisp/vf_core.lsp index 4b6420e..4afba48 100644 --- a/Lisp/vf_core.lsp +++ b/Lisp/vf_core.lsp @@ -682,9 +682,67 @@ (defun vf-set-as-masse (winkel seite / m) (setq m (vf-element-masse (strcat "AS_Element_" winkel "_" seite))) (if m (setq aus-dx (car m) aus-dy (cadr m) aus-dz (caddr m)))) -(defun vf-set-es-masse (winkel seite / m) - (setq m (vf-element-masse (strcat "ES_Element_" winkel "_" seite))) - (if m (setq ein-dx (car m) ein-dy (cadr m) ein-dz (caddr m)))) +;; Fussabdruck eines Elements ENTLANG der Fahrtrichtung (Laengsanteil). +;; +;; Warum das nicht (car (vf-element-masse ...)) ist: vf-element-masse liefert +;; KS_EIN->KS_AUS im BLOCK-Koordinatensystem. Ein Element wird aber so gedreht +;; eingebaut, dass sein KS_EIN auf der Fahrtrichtung liegt (insert-block-ks-to-ks +;; bzw. insert-block-by-ks mit (hz - ein-ang)). Bei einem drehenden Element - +;; ES_Element_30 hat ein-ang = 120 Grad - landet dadurch ein TEIL der +;; Block-Y-Komponente auf der Fahrtrichtung, und die Block-X-Komponente allein +;; unterschaetzt den Laengsverbrauch massiv: beim ES_Element_30_rechts 242.6 mm +;; statt tatsaechlich 576.2 mm (realer Bug, Nutzer-Befund 2026-10-09: die Kette +;; ragte 333.65 mm ueber den gepickten Endpunkt hinaus). +;; +;; Die Projektion ist hz-UNABHAENGIG (der Einbauwinkel kuerzt sich heraus), sie +;; haengt nur am blockinternen ein-ang: +;; laengs = vx*cos(ein-ang) + vy*sin(ein-ang) +;; Gleiche Rechnung wie etage-laengs in vf_etage.lsp mit d = -ein-ang, dort fuer +;; die Rand-Bilanz des Etage-Typs. +;; +;; Rueckgabe: Laenge in mm, oder nil wenn der Block/sein KS fehlt (Aufrufer +;; faellt dann auf das alte Verhalten zurueck). +(defun vf-element-laengs (blockname / i rad) + (setq i (vf-element-ks-info blockname)) + (if (null i) + nil + (progn + (setq rad (* (car i) (/ pi 180.0))) + (+ (* (caddr i) (cos rad)) (* (cadddr i) (sin rad)))))) + +;; Gegenstueck zu vf-element-laengs: Versatz QUER zur Fahrtrichtung. +;; quer = -vx*sin(ein-ang) + vy*cos(ein-ang) +;; Ebenfalls hz-unabhaengig. Beim ES_Element_30 sind das rund -52.5 mm - der +;; seitliche Versatz, mit dem das ES-KS_AUS neben der Fahrtachse landet. Er +;; wird NICHT ausgeglichen (der Hauptstrang bleibt gerade, Nutzer-Entscheidung +;; 2026-09-15), aber er muss BEKANNT sein: die Bahn des ES-KS_AUS verlaeuft um +;; genau diesen Betrag seitlich versetzt, und nur mit ihm trifft die +;; Schnittpunkt-Rechnung gegen eine Ziellinie die richtige Stelle. +(defun vf-element-quer (blockname / i rad) + (setq i (vf-element-ks-info blockname)) + (if (null i) + nil + (progn + (setq rad (* (car i) (/ pi 180.0))) + (+ (* (- (caddr i)) (sin rad)) (* (cadddr i) (cos rad)))))) + +;; Setzt ein-dx/ein-dy/ein-dz (BLOCK-Koordinaten, fuer Quer-/Hoehenrechnungen) +;; und zusaetzlich ein-laengs: den Fussabdruck ENTLANG der Fahrtrichtung. Wer +;; eine Laenge von deltaL reserviert, nimmt ein-laengs - NICHT (abs ein-dx), +;; siehe vf-element-laengs. +(defun vf-set-es-masse (winkel seite / m blk) + (setq blk (strcat "ES_Element_" winkel "_" seite)) + (setq m (vf-element-masse blk)) + (if m (setq ein-dx (car m) ein-dy (cadr m) ein-dz (caddr m))) + ;; Fallback auf das alte (zu kleine) Mass, wenn das KS nicht lesbar ist - + ;; lieber etwas zu kurz reservieren als gar nicht bauen koennen. + (setq ein-laengs (vf-element-laengs blk)) + (if (and (null ein-laengs) m) (setq ein-laengs (abs (car m)))) + ;; ein-quer: seitlicher Versatz des ES-KS_AUS zur Fahrtachse (siehe + ;; vf-element-quer). Wird fuer die Ziellinien-Schnittpunktrechnung gebraucht. + (setq ein-quer (vf-element-quer blk)) + (if (null ein-quer) (setq ein-quer 0.0)) + ein-laengs) ;; ============================================================ diff --git a/Lisp/vf_linienzug.lsp b/Lisp/vf_linienzug.lsp index 9670e33..b7127b8 100644 --- a/Lisp/vf_linienzug.lsp +++ b/Lisp/vf_linienzug.lsp @@ -116,6 +116,36 @@ (defun vfl-as-winkel () (if (boundp '*vfl-as-winkel*) *vfl-as-winkel* "90")) (defun vfl-es-winkel () (if (boundp '*vfl-es-winkel*) *vfl-es-winkel* "90")) +;; Laengs-Fussabdruck des ES-Elements, den ein Segment VORAB von deltaL +;; reservieren muss, damit die Kette am gepickten Punkt endet statt +;; darueber hinauszuragen. +;; +;; ein-laengs setzt vf-set-es-masse (vf_core.lsp) aus dem KS des Blocks; es ist +;; der Anteil ENTLANG der Fahrtrichtung. Frueher stand hier (abs ein-dx) - die +;; Block-X-Komponente - und die unterschaetzt ein DREHENDES ES-Element massiv: +;; beim ES_Element_30_rechts 242.6 mm statt 576.2 mm, die Kette ragte dadurch +;; 333.65 mm zu weit (Nutzer-Befund 2026-10-09). Siehe vf-element-laengs. +;; +;; Der seitliche Versatz (ein-dy) wird hier BEWUSST nicht ausgeglichen - der +;; Hauptstrang bleibt gerade (Nutzer-Entscheidung 2026-09-15, bestaetigt +;; 2026-10-09; vgl. vfl-richtung-mit-drehendem-fussabdruck). +;; +;; WICHTIG - Kopplung an die Null-Konvention: an rund zwei Dutzend Stellen +;; wird ein-dx bewusst auf 0 gesetzt (und spaeter restauriert), um einen +;; DOPPELTEN Abzug zu verhindern, wenn das ES-Element an anderer Stelle schon +;; beruecksichtigt ist. ein-laengs wird dort NICHT mitgefuehrt. Darum gilt +;; ein genulltes ein-dx hier als "nichts reservieren" und gewinnt vor +;; ein-laengs - sonst wuerde dieser Fix genau den Doppelabzug einbauen, den +;; jene Stellen vermeiden. +;; +;; Rueckfallkette, damit eine fehlende Messung nie einen (- deltaL nil)-Absturz +;; ausloest: genulltes ein-dx -> ein-laengs -> (abs ein-dx) -> 0.0. +(defun vfl-es-laengs () + (cond ((and (numberp ein-dx) (< (abs ein-dx) 0.001)) 0.0) + ((numberp ein-laengs) (abs ein-laengs)) + ((numberp ein-dx) (abs ein-dx)) + (t 0.0))) + ;; ============================================================ ;; TEIL 0a-2: MELDUNGEN (statt alert im Bau-Pfad) ;; ============================================================ @@ -332,6 +362,21 @@ (list (cdr e))) nil)) +;; Art des naechsten Replay-Eintrags, OHNE ihn zu verbrauchen ("DL", "STR", +;; "REAL", ...) bzw. nil bei leerer Queue. STEP-Marker werden dabei wie in +;; vfl-replay-pop uebersprungen. +;; +;; Gebraucht fuer Abwaertskompatibilitaet: wird an einer Stelle eine NEUE +;; Frage eingefuehrt, fehlt deren Antwort im Journal aelterer Ketten. Statt +;; blind zu fragen (und dadurch den naechsten Wert als Antwort zu +;; verschlucken - ein Queue-Desync), kann der Aufrufer hier nachsehen, was +;; tatsaechlich als Naechstes ansteht, und die Frage bei einem Altbestand +;; ueberspringen. +(defun vfl-replay-peek-art ( / q) + (setq q *vfl-replay-queue*) + (while (and q (= (car (car q)) "STEP")) (setq q (cdr q))) + (if q (car (car q)) nil)) + ;; ============================================================ ;; TEIL 0b-1b: GLIED-SCHEMA (single source of truth) ;; ============================================================ @@ -671,6 +716,69 @@ (setq r (vl-catch-all-apply 'vflw-wahl-impl (list prompt optionen default-idx))) (if (vl-catch-all-error-p r) "" r)) +;; RADIO-BUTTON-Variante des generischen Wahl-Dialogs. +;; +;; vflw-wahl zeigt eine Listbox; fuer kurze Entscheidungen (2-3 Optionen) sind +;; Radio-Buttons uebersichtlicher - man sieht alle Moeglichkeiten auf einen +;; Blick statt sie in einer Liste zu suchen (Nutzer-Wunsch 2026-10-14). +;; +;; Technik wie bei den Standard-/Etage-Radio-Dialogen (vf_standard.lsp, +;; Kommentar dort): Radio-Labels lassen sich nach new_dialog NICHT per +;; set_tile aendern - darum wird die DCL bei JEDEM Aufruf frisch geschrieben +;; (ssg-dcl-temp-schreiben) und die Labels direkt mit dem schon von ssg-text +;; aufgeloesten String befuellt. Das funktioniert dadurch in jeder Sprache, +;; ohne Sprachweiche. Wiederverwendet werden vfs-dcl-radio-gruppe-zeilen und +;; vfs-dcl-radio-idx-cond (vf_standard wird vor vf_linienzug geladen). +;; +;; Rueckgabe IDENTISCH zu vflw-wahl: Index als String ("1", "2", ...) bzw. "" +;; bei Abbruch/Zurueck (dann ist *vfl-wizard-abbruch-grund* gesetzt). Dadurch +;; ist sie ein direkter Ersatz und die Journal-Semantik bleibt unberuehrt. +(defun vflw-radio-wahl-impl (prompt optionen default-idx / + zeilen dcl-pfad dat idx ergebnis n sauber) + (setq n (length optionen)) + ;; Die Optionstexte kommen als Konsolenzeilen ("\n 1 - Ziellinie ..."): + ;; vflw-clean schneidet Umbruch, fuehrende Leerzeichen und die Nummerierung + ;; weg - im Dialog nummerieren die Radio-Buttons selbst. Genau wie in + ;; vflw-wahl-impl, dort beim Fuellen der Listbox. + (setq sauber (mapcar '(lambda (o) (vflw-clean o)) optionen)) + (setq zeilen + (append + (list "vflw_radio_dyn : dialog {" + (strcat " label = \"" (ssg-dcl-label-escape (vflw-clean prompt)) "\";")) + (vfs-dcl-radio-gruppe-zeilen "vflwopt" "" sauber) + (list " ok_cancel;" "}"))) + (setq dcl-pfad (ssg-dcl-temp-schreiben "vflwradio" zeilen)) + (if (null dcl-pfad) + 'FEHLER + (progn + (setq dat (load_dialog dcl-pfad)) + (if (not (new_dialog "vflw_radio_dyn" dat)) + (progn + (if (and dat (>= dat 0)) (unload_dialog dat)) + (vl-file-delete dcl-pfad) + 'FEHLER) + (progn + ;; Vorauswahl setzen (default-idx ist 1-basiert, Tiles sind 0-basiert) + (set_tile (strcat "vflwopt_" (itoa (max 0 (1- (if default-idx default-idx 1))))) "1") + (action_tile "accept" + (strcat "(setq idx " (vfs-dcl-radio-idx-cond "vflwopt" n) ") (done_dialog 1)")) + (action_tile "cancel" "(done_dialog 0)") + (setq ergebnis (start_dialog)) + (unload_dialog dat) + (vl-file-delete dcl-pfad) + (if (= ergebnis 1) + (itoa (1+ (if idx idx 0))) + (progn (setq *vfl-wizard-abbruch-grund* "abbrechen") ""))))))) + +;; Dispatcher: bevorzugt Radio, faellt bei technischem Fehler (Temp-Datei +;; nicht schreibbar/ladbar) auf die Listbox-Variante zurueck - genau wie die +;; -radio/-popup-Paare in vf_standard.lsp. +(defun vflw-radio-wahl (prompt optionen default-idx / r) + (setq r (vl-catch-all-apply 'vflw-radio-wahl-impl (list prompt optionen default-idx))) + (if (or (vl-catch-all-error-p r) (eq r 'FEHLER)) + (vflw-wahl prompt optionen default-idx) + r)) + ;; ============================================================ ;; TEIL 0b-4: GRUPPEN-DIALOGE - GEMEINSAME PUNKTWAHL-BAUSTEINE ;; ============================================================ @@ -1865,10 +1973,16 @@ (defun vfl-in-string (prompt) (vfl-in-value-p "STR" (function (lambda (v) (eq (type v) 'STR))) - (function (lambda () + (function (lambda ( / txt) (if (and (vfl-wizard-mode-p) *vflw-menu-optionen*) - (vflw-wahl (if *vflw-menu-frage* (ssg-text *vflw-menu-frage*) prompt) - *vflw-menu-optionen* *vflw-menu-default*) + (progn + (setq txt (if *vflw-menu-frage* (ssg-text *vflw-menu-frage*) prompt)) + ;; *vflw-menu-radio* setzt vfl-menu-radio fuer kurze Entscheidungen, + ;; bei denen Radio-Buttons uebersichtlicher sind als eine Listbox. + ;; Alle uebrigen Fragen bleiben unveraendert bei vflw-wahl. + (if (and (boundp '*vflw-menu-radio*) *vflw-menu-radio*) + (vflw-radio-wahl txt *vflw-menu-optionen* *vflw-menu-default*) + (vflw-wahl txt *vflw-menu-optionen* *vflw-menu-default*))) (getstring prompt)))))) (defun vfl-in-real (prompt) @@ -1902,6 +2016,20 @@ (setq *vflw-menu-default* default-idx) (setq *vflw-menu-frage* frage-key) (vfl-in-string prompt)) +;; Wie vfl-menu, zeigt die Auswahl im Wizard-Modus aber als RADIO-BUTTONS +;; statt als Listbox - fuer kurze Entscheidungen (2-3 Optionen) uebersichtlicher +;; (Nutzer-Wunsch 2026-10-14). Journal-Semantik identisch zu vfl-menu: es wird +;; derselbe "STR"-Eintrag mit dem Auswahl-Index geschrieben. Das Flag ist +;; lokal gebunden und gilt daher nur fuer DIESEN einen Aufruf. +(defun vfl-menu-radio (prompt option-labels default-idx frage-key / + *vflw-menu-optionen* *vflw-menu-default* *vflw-menu-frage* + *vflw-menu-radio*) + (setq *vflw-menu-optionen* option-labels) + (setq *vflw-menu-default* default-idx) + (setq *vflw-menu-frage* frage-key) + (setq *vflw-menu-radio* T) + (vfl-in-string prompt)) + (defun vfl-menu-int (prompt option-labels default-idx frage-key / *vflw-menu-optionen* *vflw-menu-default* *vflw-menu-frage*) (setq *vflw-menu-optionen* option-labels) @@ -3206,23 +3334,68 @@ (make-frame-from-dir endpunkt (hz-winkel->xu hz winkel)) ) -;; Ansicht auf Grundriss (Draufsicht, Welt-UCS) zoomen: nach jedem -;; Kettenglied mit Geometrieerzeugung wird die neu entstandene Strecke sonst -;; leicht aus dem sichtbaren Bereich oder in eine schraege 3D-Ansicht -;; hinauslaufen - der naechste Punkt (vfl-neue-linie-messen, siehe unten) -;; waere dann schwer/ungenau anzuwaehlen. _PLAN "_World" aendert nur die -;; ANSICHT, nicht das aktuell aktive BKS. Rein kosmetisch (per -;; vl-catch-all-apply abgesichert) - ein Fehler hier darf den eigentlichen -;; Kettenbau nie unterbrechen. -(defun vfl-view-refresh ( / ) - ;; Headless nur Kosten: die Ansicht sieht niemand, _PLAN/_ZOOM laufen aber - ;; vor JEDER Laengeneingabe (vfl-neue-linie-messen) - im Batch reine Zeit. +;; Blickrichtung ist Draufsicht (Welt-Z)? Dann ist ein _PLAN ueberfluessig. +(defun vfl-draufsicht-p ( / vd) + (setq vd (vl-catch-all-apply 'getvar (list "VIEWDIR"))) + (if (or (vl-catch-all-error-p vd) (null vd)) + t ; unbekannt -> nicht eingreifen + (and (< (abs (car vd)) 1e-6) + (< (abs (cadr vd)) 1e-6) + (> (caddr vd) 0.0)))) + +;; Liegt pt komfortabel im sichtbaren Ausschnitt? VIEWCTR/VIEWSIZE geben Mitte +;; und HOEHE des Viewports, SCREENSIZE das Seitenverhaeltnis. Der Faktor 0.85 +;; laesst einen Rand: ein Punkt direkt an der Fensterkante gilt als "fast +;; draussen", damit auch die noch zu bauende Strecke Platz hat. +;; Im Zweifel (Variable nicht lesbar) T -> lieber nichts tun als springen. +(defun vfl-punkt-sichtbar-p (pt / ctr size scr aspect) + (setq ctr (vl-catch-all-apply 'getvar (list "VIEWCTR")) + size (vl-catch-all-apply 'getvar (list "VIEWSIZE")) + scr (vl-catch-all-apply 'getvar (list "SCREENSIZE"))) + (if (or (vl-catch-all-error-p ctr) (vl-catch-all-error-p size) + (vl-catch-all-error-p scr) (null ctr) (null size) (null pt) + (not (numberp size)) (<= size 0.0)) + t + (progn + (setq aspect (if (and scr (numberp (car scr)) (numberp (cadr scr)) + (> (cadr scr) 0)) + (/ (float (car scr)) (float (cadr scr))) + 1.0)) + (and (< (abs (- (car pt) (car ctr))) (* 0.85 0.5 size aspect)) + (< (abs (- (cadr pt) (cadr ctr))) (* 0.85 0.5 size)))))) + +;; Ansicht fuer die naechste Punktwahl vorbereiten - so WENIG wie moeglich. +;; +;; Frueher lief hier vor JEDER Laengeneingabe hart (command "_.PLAN" "_World") +;; + (command "_.ZOOM" "_Extents"). Bei einer grossen Anlage zoomt Extents auf +;; die GESAMTE Zeichnung: die Ansicht sprang nach jedem Klick weg, und der +;; Nutzer musste sich jedes Mal neu zurechtfinden (Nutzer-Wunsch 2026-10-09: +;; "beim Setzen eines Segments an dieser Position bleiben"). +;; +;; Jetzt wird nur eingegriffen, wenn es noetig ist: +;; - _PLAN nur, wenn die Ansicht gar keine Draufsicht ist (in einer schraegen +;; 3D-Ansicht ist ein XY-Pick mehrdeutig - das war der eigentliche Zweck). +;; - verschoben wird nur, wenn pt aus dem sichtbaren Ausschnitt laeuft, und +;; dann per _ZOOM _Center: der Ausschnitt folgt dem Baupunkt, die +;; ZOOMSTUFE bleibt erhalten (anders als _Extents). +;; Ist der Punkt sichtbar und die Ansicht eine Draufsicht, passiert GAR NICHTS. +;; +;; Rein kosmetisch und per vl-catch-all-apply abgesichert - ein Fehler hier +;; darf den Kettenbau nie unterbrechen. +(defun vfl-view-refresh (pt / size) + ;; Headless nur Kosten: die Ansicht sieht niemand. (if (not (vfl-headless-p)) - (vl-catch-all-apply - (function (lambda () - (command "_.PLAN" "_World") - (command "_.ZOOM" "_Extents"))) - nil)) + (progn + (if (not (vfl-draufsicht-p)) + (vl-catch-all-apply 'command (list "_.PLAN" "_World"))) + (if (and pt (not (vfl-punkt-sichtbar-p pt))) + (progn + (setq size (vl-catch-all-apply 'getvar (list "VIEWSIZE"))) + (if (or (vl-catch-all-error-p size) (not (numberp size))) + (vl-catch-all-apply 'command (list "_.ZOOM" "_Extents")) + ;; Mitte auf den Baupunkt, Hoehe unveraendert = gleiche Zoomstufe. + (vl-catch-all-apply 'command + (list "_.ZOOM" "_Center" (list (car pt) (cadr pt)) size))))))) (princ)) ;; --- Hilfsfunktionen fuer die VF-Einheit --- @@ -3257,6 +3430,65 @@ pt ) +;; Punkt-Abfrage MIT Achsenzwang: der Cursor wird auf die Anschlussachse +;; gezwungen, statt frei in jede Richtung zu laufen. +;; +;; Warum: das KS_AUS des zuletzt gebauten Teils legt die Richtung des KS_EIN +;; des naechsten Teils bereits fest - frei ist nur noch die LAENGE. Bisher +;; durfte der Nutzer irgendwohin klicken und vfl-in-abstand projizierte den +;; Punkt STILL auf die Fahrtrichtung: das Ergebnis entsprach nicht dem Klick, +;; und der Fehler fiel erst an der fertigen Kette auf (Nutzer-Befund +;; 2026-10-09, sichtbarer Knick zwischen zwei Segmenten). +;; +;; Technik: SNAPANG (Rasterwinkel) temporaer auf hz drehen und ORTHOMODE +;; einschalten - ORTHO richtet sich nach SNAPANG, der Cursor rastet dadurch +;; auf der Anschlussachse (und 90 Grad quer dazu) ein. +;; +;; NICHT ueber einen BKS-Wechsel: der erste Versuch drehte das BKS per +;; (command "_.UCS" "_3point" ...) - BricsCAD kennt diese Optionsbezeichnung +;; nicht, der Aufruf scheiterte, und weil er in vl-catch-all-apply steckte, +;; scheiterte er STILL: der Cursor blieb frei und nur die ORTHO-Rastung auf +;; die WELT-Achsen war aktiv (Nutzer-Befund 2026-10-09: "kann immer noch jede +;; Richtung waehlen"). SNAPANG braucht kein Kommando, keine Options-Syntax, +;; kein _Previous und laesst das BKS voellig unberuehrt - getpoint liefert +;; weiterhin dieselben Koordinaten wie bisher, es ist also KEINE +;; Koordinaten-Transformation noetig. +;; +;; Erzwungen wird NUR die horizontale Richtung (Nutzer-Entscheidung +;; 2026-10-09); die Hoehe bleibt frei, weil GF-/VF-Segmente ihre Neigung +;; selbst festlegen. SNAPANG wirkt ausschliesslich in der XY-Ebene - genau +;; das. +;; +;; Ohne base oder hz (erstes Kettensegment: Richtung noch frei) faellt die +;; Funktion unveraendert auf vfl-getpoint zurueck - ebenso ohne GUI bzw. +;; headless: dort gibt es keinen Cursor zu fuehren. +;; Rueckgabe: Punkt wie vfl-getpoint (gleiches Bezugssystem) oder nil. +(defun vfl-getpoint-achse (base prompt hz / alt-snapang alt-ortho pt) + (if (or (null base) (null hz) (not (ssg-gui-p)) (vfl-headless-p)) + (vfl-getpoint base prompt) + (progn + (setq alt-snapang (vl-catch-all-apply 'getvar (list "SNAPANG"))) + (setq alt-ortho (vl-catch-all-apply 'getvar (list "ORTHOMODE"))) + (if (vl-catch-all-error-p alt-snapang) (setq alt-snapang nil)) + (if (vl-catch-all-error-p alt-ortho) (setq alt-ortho nil)) + ;; SNAPANG erwartet RADIANT. + (vl-catch-all-apply 'setvar (list "SNAPANG" (* (float hz) (/ pi 180.0)))) + (vl-catch-all-apply 'setvar (list "ORTHOMODE" 1)) + ;; Gecatcht, damit SNAPANG/ORTHOMODE auch bei ESC oder Fehler sicher + ;; zurueckgesetzt werden - ein verdrehter Rasterwinkel wuerde sonst in + ;; der Zeichnung des Nutzers stehen bleiben. Ein Fehler wird zu nil, + ;; also exakt dem bisherigen Abbruch-Rueckgabewert von vfl-getpoint; + ;; der Aufrufer laeuft damit in seinen vorhandenen nil-Zweig. + (setq pt (vl-catch-all-apply 'vfl-getpoint (list base prompt))) + (if alt-snapang (vl-catch-all-apply 'setvar (list "SNAPANG" alt-snapang))) + (if alt-ortho (vl-catch-all-apply 'setvar (list "ORTHOMODE" alt-ortho)) + (vl-catch-all-apply 'setvar (list "ORTHOMODE" 0))) + (if (vl-catch-all-error-p pt) (setq pt nil)) + pt + ) + ) +) + ;; Vorschlagswert fuer die GF-Zielhoehe: die aktuelle Kettenhoehe (ist-hoehe) ;; UNVERAENDERT als Default anzubieten fuehrt in eine Falle - ein GF-Segment ;; MUSS fallen (0 Grad Gefaelle gibt es nicht), ein direkt uebernommener @@ -3325,7 +3557,12 @@ ;; picken (Gummiband). Der Punkt lebt nur hier lokal, er wird nie ;; journalisiert. (setq popped (vflw-pending-pop)) - (setq p2 (if popped (car popped) (vfl-getpoint p-akt prompt))) + ;; Achsenzwang, sobald die Fahrtrichtung vom Vorgaenger feststeht + ;; (hz-vorgabe): der Cursor rastet auf der Anschlussachse ein, statt + ;; frei zu laufen und spaeter still projiziert zu werden. Beim ERSTEN + ;; Segment (hz-vorgabe=nil) definiert der Klick die Richtung noch selbst + ;; - dort faellt vfl-getpoint-achse von sich aus auf vfl-getpoint zurueck. + (setq p2 (if popped (car popped) (vfl-getpoint-achse p-akt prompt hz-vorgabe))) ;; *vfl-letzter-pick-z*: die Z-Hoehe DIESES Picks, fuer eine unmittelbar ;; nachfolgende Zielhoehe-Frage (siehe vfl-hoehe-endpunkt-abfragen) als ;; Vorschlag verfuegbar machen - vorher bei JEDEM Endpunkt-Pick @@ -3360,6 +3597,101 @@ (vfl-journal-record "REAL" hz-aktuell)) (if deltaL (cons deltaL hz-aktuell) nil)) +;; Laenge aus einer ZIELLINIE statt aus einem freien Klick. +;; +;; Steht die Fahrtrichtung fest (hz), ist die Laenge der letzten Strecke nicht +;; mehr frei: die Bahn des ES-KS_AUS - um quer seitlich versetzt - trifft die +;; angeklickte Linie in genau einem Punkt. Ein Klick kann diesen Punkt nur +;; annaehern (bei weit herausgezoomter Ansicht sind das schnell etliche mm), +;; die Rechnung trifft ihn exakt (Nutzer-Vorgabe 2026-10-09: "nur EIN Winkel, +;; dann den Schnittpunkt auf der Linie anklicken, Z kann ignoriert werden"). +;; +;; Z wird bewusst ignoriert - rein planare X/Y-Rechnung; die Hoehe kommt +;; weiterhin aus dem Gefaellewinkel. +;; +;; JOURNAL: schreibt denselben "DL"-Eintrag wie vfl-in-abstand und liest beim +;; Replay aus derselben Queue. Dadurch ist KEIN neues Journal-Format noetig - +;; Doppelklick-Editieren, Replay und 2D/3D-Konvertierung sehen nur eine +;; (genauere) Laenge und muessen von Ziellinien nichts wissen. Die Bindung an +;; die Linie geht damit allerdings verloren: wird die Linie spaeter +;; verschoben, folgt die Kette NICHT (dafuer muesste die Linie selbst +;; journalisiert werden, wie Modus 2 es mit "OBJS" tut). +;; +;; Rueckgabe: Gesamtlaenge bis zum Schnittpunkt (der Aufrufer zieht davon wie +;; gewohnt Separator-/ES-Fussabdruck ab) oder nil bei Abbruch / wenn keine +;; brauchbare Linie getroffen wurde. +(defun vfl-in-abstand-ziellinie (p-akt hz quer / popped pts L fertig p2 weg) + (if *vfl-wizard-abbruch-grund* + (progn + (dbgmsg (strcat "ABBRUCH-TRIGGER (Ziellinie): " *vfl-wizard-abbruch-grund* " -> (exit)")) + (exit))) + ;; Eigene Replay-Logik (ein Eintrag pro Aufruf) -> eigener Headless-Riegel: + ;; leere Queue ist ein Desync, kein "Nutzer soll waehlen". + (if (and (null *vfl-replay-queue*) (vfl-headless-p)) + (vfl-headless-abbruch "DL")) + ;; --- Wahl des Eingabewegs (zwei gleichwertige Optionen) --- + ;; ABWAERTSKOMPATIBILITAET: Ketten, die VOR der Einfuehrung dieser Frage + ;; gebaut wurden, haben an dieser Stelle kein "STR" im Journal, sondern + ;; direkt den "DL"-Wert. Wuerde hier blind gefragt, verschluckte die + ;; Menue-Abfrage diesen Laengenwert als Antwort -> Queue-Desync und eine + ;; falsch nachgebaute Kette. Darum beim Replay erst nachsehen, was + ;; tatsaechlich ansteht: steht dort ein "DL", ist es ein Altbestand und die + ;; Frage entfaellt (dessen Laenge entstand durch freie Punktwahl). + (if (and *vfl-replay-queue* (= (vfl-replay-peek-art) "DL")) + (setq weg "2") + (progn + (princ (ssg-text "vfl-endpunkt-modus-header")) + (princ (ssg-text "vfl-endpunkt-modus-opt-linie")) + (princ (ssg-text "vfl-endpunkt-modus-opt-punkt")) + (setq weg (vfl-menu-radio (ssg-text "prompt-wahl-1-2") + (list (ssg-text "vfl-endpunkt-modus-opt-linie") + (ssg-text "vfl-endpunkt-modus-opt-punkt")) 1 + "vfl-endpunkt-modus-header")))) + (if *vfl-replay-queue* + (progn + (setq popped (vfl-replay-pop)) + (setq L (if popped (car popped) nil))) + ;; Live: wiederholen, bis eine brauchbare Eingabe vorliegt. Beide Wege + ;; liefern EINE Laenge und schreiben daher denselben einzelnen + ;; "DL"-Journaleintrag - der Replay sieht keinen Unterschied und muss von + ;; Ziellinien nichts wissen. Abbruch (ESC) liefert nil; der Aufrufer + ;; behandelt das wie jeden anderen Eingabe-Abbruch. + (progn + (setq fertig nil) + (while (not fertig) + (if (= weg "2") + ;; --- Option 2: freie Punktwahl (wie bisher) --- + ;; Punkt picken und auf die feststehende Fahrtrichtung projizieren. + ;; Der Achsenzwang (vfl-getpoint-achse) gilt dabei weiter, der + ;; Cursor rastet also auf der Anschlussachse ein. Noetig, wenn gar + ;; keine Ziellinie existiert (Nutzer-Wunsch 2026-10-12). + (progn + (setq p2 (vfl-getpoint-achse p-akt + (ssg-text "vfl-prompt-endpunkt-fahrtrichtung") hz)) + (if (null p2) + (setq L nil fertig t) + (progn + (setq L (vfl-projiziere-distanz p-akt p2 hz)) + (if (< L 1.0) + (vfl-meldung (ssg-text "vfl-ziellinie-hinter-start")) + (setq fertig t))))) + ;; --- Option 1: Ziellinie aus zwei Punkten, Schnittpunkt gerechnet --- + (progn + (setq pts (vfl-ziellinie-zwei-punkte)) + (cond + ;; ESC / Punkte identisch + ((null pts) (setq L nil fertig t)) + (t + (setq L (vfl-laenge-bis-ziellinie p-akt hz quer (car pts) (cadr pts))) + (cond + ;; parallel zur Fahrtrichtung -> kein eindeutiger Schnittpunkt + ((null L) (vfl-meldung (ssg-text "vfl-ziellinie-parallel"))) + ;; Schnittpunkt liegt hinter dem Start -> unbrauchbar + ((< L 1.0) (vfl-meldung (ssg-text "vfl-ziellinie-hinter-start"))) + (t (setq fertig t)))))))))) + (vfl-journal-record (if L "DL" "NIL") L) + L) + ;; Rueckgabe: (deltaL hz) oder nil bei Abbruch. Foerderer-Maximallaenge 25 m: ;; bei Ueberschreitung wird die Eingabe abgelehnt und der Endpunkt erneut ;; abgefragt. @@ -3390,7 +3722,9 @@ (if p2 (vfl-hz-snappen-absolut (vfl-planar-hz p-akt p2)) nil))))) (defun vfl-neue-linie-messen (p-akt hz-vorgabe / erg deltaL hz-aktuell ergebnis fertig) - (vfl-view-refresh) + ;; Ansicht nur nachfuehren, wenn der Baupunkt aus dem Bild laeuft - sonst + ;; bleibt der Ausschnitt stehen (siehe vfl-view-refresh). + (vfl-view-refresh p-akt) (setq fertig nil) (while (not fertig) (setq erg (vfl-in-abstand p-akt hz-vorgabe @@ -3422,6 +3756,92 @@ ;; die Dreh­teller-Rotation, die insert-block-mixed-to-ks beim echten Einfuegen ;; anwendet, und liefern empirisch bestaetigt einen falschen Wert (~210mm ;; Schaetzung vs. ~420mm tatsaechlich noetiger Versatz bei AS_Element_30). +;; ============================================================ +;; ZIELLINIE: X/Y-Lage des Kettenendes aus einer Linie ableiten +;; ============================================================ +;; Steht die Fahrtrichtung fest (Frame des Vorgaengers) und soll die Kette mit +;; einem ES-Element auf einer vorgegebenen Linie enden, ist die Laenge NICHT +;; mehr frei: das ES-KS_AUS laeuft auf einer Geraden PARALLEL zur +;; Fahrtrichtung (um den ES-Querversatz seitlich versetzt), und die trifft die +;; Ziellinie in genau einem Punkt. +;; +;; WICHTIG - das ist bewusst eine REIN PLANARE (X/Y-)Rechnung, kein +;; 3D-Schnittpunkt (Nutzer-Praezisierung 2026-10-09): die HOEHE ergibt sich +;; weiterhin aus dem Gefaellewinkel (3 Grad), die Ziellinie bestimmt +;; ausschliesslich die X/Y-Lage. Die Z-Koordinaten der Linie werden daher +;; ignoriert. +;; +;; 2D-Kreuzprodukt (Determinante) zweier Richtungen - 0 heisst parallel. +(defun vfl-kreuz2 (ax ay bx by) (- (* ax by) (* ay bx))) + +;; Horizontale Laenge L vom Startpunkt bis zu dem Punkt, an dem die um +;; quer seitlich versetzte Bahn in Richtung hz die Ziellinie (durch pa und pb) +;; schneidet. +;; p-start : Startpunkt der Bahn (X/Y zaehlen) +;; hz : Fahrtrichtung in Grad +;; quer : seitlicher Versatz der Bahn (ES-Querversatz; 0 = Achse selbst) +;; pa/pb : zwei Punkte der Ziellinie +;; Rueckgabe: L (kann negativ sein, der Aufrufer prueft das) oder nil, wenn +;; die Ziellinie parallel zur Fahrtrichtung liegt (kein eindeutiger Treffer). +(defun vfl-laenge-bis-ziellinie (p-start hz quer pa pb / + rad ux uy nx ny vx vy wx wy nenner) + (setq rad (* (float hz) (/ pi 180.0))) + (setq ux (cos rad) uy (sin rad)) ; Fahrtrichtung + (setq nx (- uy) ny ux) ; quer dazu + (setq vx (- (car pb) (car pa)) ; Richtung der Ziellinie + vy (- (cadr pb) (cadr pa))) + (setq nenner (vfl-kreuz2 ux uy vx vy)) + (if (< (abs nenner) 1e-9) + nil ; parallel -> kein Schnittpunkt + (progn + ;; w = pa - (p-start + quer*n) (Startpunkt der versetzten Bahn) + (setq wx (- (car pa) (+ (car p-start) (* quer nx))) + wy (- (cadr pa) (+ (cadr p-start) (* quer ny)))) + (/ (vfl-kreuz2 wx wy vx vy) nenner)))) + +;; Ziellinie auswaehlen und als zwei Punkte zurueckgeben. +;; +;; entsel deckt BEIDE vom Nutzer gewuenschten Eingabewege in EINER Interaktion +;; ab (Nutzer-Wunsch 2026-10-09 "kann man nicht beide anbieten?"): es liefert +;; gleichzeitig das getroffene Objekt UND den Pickpunkt. Wird eine Linie +;; getroffen, wird exakt mit ihr gerechnet; wird nichts getroffen, liefert die +;; Funktion nil und der Aufrufer bleibt beim bisherigen Punkt-Verhalten. +;; +;; Statt den Objekttyp zu unterscheiden, wird die TANGENTE am Pickpunkt +;; genommen (vlax-curve-*): fuer eine LINE ist das exakt die Linie selbst, fuer +;; eine Polylinie genau das angeklickte Segment - ohne Segment-Suche von Hand. +;; Rueckgabe: (pa pb) oder nil. +;; Ziellinie durch ZWEI selbst gesetzte Punkte definieren. +;; +;; Allgemeiner als das Anklicken eines vorhandenen Objekts (Nutzer-Idee +;; 2026-10-12): es muss gar keine Linie gezeichnet sein. Existiert doch eine, +;; faengt man mit Objektfang zwei Punkte darauf und hat sie exakt erfasst - +;; dieser Weg deckt den Objekt-Fall also mit ab. +;; +;; WICHTIG - hier KEIN Achsenzwang (vfl-getpoint statt vfl-getpoint-achse): +;; die Ziellinie darf jede Richtung haben. Wuerde der Cursor auf die +;; Fahrtrichtung gezwungen, koennte man nur Linien PARALLEL dazu definieren - +;; und genau die haben keinen Schnittpunkt. +;; +;; Die beiden Punkte werden NICHT journalisiert: ins Journal geht allein die +;; daraus berechnete Laenge (ein "DL", siehe vfl-in-abstand-ziellinie). Darum +;; der rohe vfl-getpoint und nicht vfl-in-point. +;; Rueckgabe: (p1 p2) oder nil bei Abbruch. +(defun vfl-ziellinie-zwei-punkte ( / p1 p2) + (setq p1 (vfl-getpoint nil (ssg-text "vfl-prompt-ziellinie-p1"))) + (if (null p1) + nil + (progn + ;; Gummiband ab p1, damit die entstehende Ziellinie sichtbar ist. + (setq p2 (vfl-getpoint p1 (ssg-text "vfl-prompt-ziellinie-p2"))) + (if (or (null p2) + (and (< (abs (- (car p2) (car p1))) 1e-9) + (< (abs (- (cadr p2) (cadr p1))) 1e-9))) + nil ; abgebrochen oder Punkte identisch + (list p1 p2))))) + +;; Planare Projektionsdistanz von neu-basis zu ziel-punkt entlang hz - siehe +;; unten (unveraendert). (defun vfl-projiziere-distanz (neu-basis ziel-punkt hz / rad) (setq rad (* (float hz) (/ pi 180.0))) (+ (* (- (car ziel-punkt) (car neu-basis)) (cos rad)) @@ -7006,9 +7426,11 @@ ) ) ;; ES-Fussabdruck VORAB reservieren, siehe Kommentar bei - ;; gf-kettenende-antwort oben. + ;; gf-kettenende-antwort oben. ein-laengs = Anteil ENTLANG + ;; der Fahrtrichtung (vf-element-laengs); (abs ein-dx) waere + ;; die Block-X-Komponente und damit viel zu klein. (if (= gf-kettenende-antwort "1") - (setq deltaL (- deltaL (abs ein-dx)))) + (setq deltaL (- deltaL (vfl-es-laengs)))) (if (< deltaL 1.0) (princ (ssg-text "vfl-fehler-linie-zu-kurz")) (progn @@ -7117,10 +7539,26 @@ ) (t ;; --- Zweig A: Endpunkt waehlen (heutiges Verhalten + Separator) --- - (setq linie-mess (vfl-neue-linie-messen p-aktuell - (if frame (car (frame->hz-winkel frame)) nil))) - (if (null linie-mess) (progn (princ (ssg-text "vfl-abgebrochen")) (exit))) - (setq deltaL (car linie-mess) hz-neu (cadr linie-mess)) + ;; Steht die Fahrtrichtung bereits fest (frame) UND endet die Kette + ;; mit einem ES-Element, ist die Laenge nicht mehr frei: dann wird + ;; eine ZIELLINIE angeklickt und der Schnittpunkt exakt gerechnet + ;; (Nutzer-Vorgabe 2026-10-09 - ein Klick trifft den Schnittpunkt + ;; nie genau). Trifft der Klick keine Linie, faellt der Ablauf auf + ;; die gewohnte Punktwahl zurueck, damit kein Weg verbaut wird. + (setq hz-neu (if frame (car (frame->hz-winkel frame)) nil)) + (if (and hz-neu (= gf-kettenende-antwort "1")) + ;; Richtung steht fest UND die Kette endet mit ES -> Ziellinie. + ;; Rueckgabe ist die Gesamtlaenge bis zum Schnittpunkt; der + ;; Separator-/ES-Fussabdruck wird weiter unten wie gewohnt + ;; abgezogen. + (progn + (setq deltaL (vfl-in-abstand-ziellinie p-aktuell hz-neu + (if (numberp ein-quer) ein-quer 0.0))) + (if (null deltaL) (progn (princ (ssg-text "vfl-abgebrochen")) (exit)))) + (progn + (setq linie-mess (vfl-neue-linie-messen p-aktuell hz-neu)) + (if (null linie-mess) (progn (princ (ssg-text "vfl-abgebrochen")) (exit))) + (setq deltaL (car linie-mess) hz-neu (cadr linie-mess)))) (if (< deltaL 1.0) (princ (ssg-text "vfl-fehler-linie-zu-kurz")) (progn @@ -7188,7 +7626,7 @@ ;; anders als die optionalen vor/nach-Separatoren oben ;; NICHT reserviert wurde). (if (= gf-kettenende-antwort "1") - (setq deltaL (- deltaL (* *vfk-separator-laenge* (cos rad3)) (abs ein-dx)))) + (setq deltaL (- deltaL (* *vfk-separator-laenge* (cos rad3)) (vfl-es-laengs)))) (if (< deltaL 1.0) (progn (princ (ssg-text "vfl-fehler-linie-zu-kurz")) (setq gf-ok nil))) (if gf-ok diff --git a/lang/de_DE.json b/lang/de_DE.json index a20a215..91e78a9 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -99,6 +99,13 @@ "vfl-es-setzen-frage": "\n\nES-Element setzen?", "vfl-es-nein-zielpunkt": "\n 2 - Nein (Kette endet direkt am Zielpunkt)", "vfl-prompt-endpunkt-fahrtrichtung": "\n\nEndpunkt entlang Fahrtrichtung waehlen (bestimmt die Laenge): ", + "vfl-endpunkt-modus-header": "\n\nWie soll der Endpunkt bestimmt werden?", + "vfl-endpunkt-modus-opt-linie": "\n 1 - Ziellinie zeichnen (2 Punkte) - der Schnittpunkt bestimmt die Laenge", + "vfl-prompt-ziellinie-p1": "\n\nZiellinie - 1. Punkt (beliebige Richtung, Z wird ignoriert): ", + "vfl-prompt-ziellinie-p2": "\n Ziellinie - 2. Punkt: ", + "vfl-endpunkt-modus-opt-punkt": "\n 2 - Punkt waehlen - entlang der festen Fahrtrichtung", + "vfl-ziellinie-parallel": "\n Die gewaehlte Linie liegt parallel zur Fahrtrichtung - es gibt keinen eindeutigen Schnittpunkt.", + "vfl-ziellinie-hinter-start": "\n Der Schnittpunkt liegt hinter dem Startpunkt der Strecke - bitte eine andere Linie waehlen.", "vfl-prompt-endpunkt-frei": "\n\nEndpunkt der Linie (XY, beliebige Richtung): ", "vfl-fehler-laenge-max": "\n>>> FEHLER: Laenge %1 m ueberschreitet die Foerderer-Maximallaenge von 25 m - bitte anderen Endpunkt waehlen.", "vfl-glied-gf-bogen": "GF-Bogen (horizontale Kurve)", diff --git a/lang/en_GB.json b/lang/en_GB.json index 49b5a18..8520834 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -99,6 +99,13 @@ "vfl-es-setzen-frage": "\n\nPlace ES element?", "vfl-es-nein-zielpunkt": "\n 2 - No (chain ends directly at the target point)", "vfl-prompt-endpunkt-fahrtrichtung": "\n\nSelect end point along travel direction (defines the length): ", + "vfl-endpunkt-modus-header": "\n\nHow should the end point be determined?", + "vfl-endpunkt-modus-opt-linie": "\n 1 - Draw target line (2 points) - the intersection defines the length", + "vfl-prompt-ziellinie-p1": "\n\nTarget line - 1st point (any direction, Z is ignored): ", + "vfl-prompt-ziellinie-p2": "\n Target line - 2nd point: ", + "vfl-endpunkt-modus-opt-punkt": "\n 2 - Select point - along the fixed travel direction", + "vfl-ziellinie-parallel": "\n The selected line is parallel to the travel direction - there is no unique intersection.", + "vfl-ziellinie-hinter-start": "\n The intersection lies behind the start of the run - please select a different line.", "vfl-prompt-endpunkt-frei": "\n\nEnd point of the line (XY, any direction): ", "vfl-fehler-laenge-max": "\n>>> ERROR: Length %1 m exceeds the conveyor maximum length of 25 m - please pick another end point.", "vfl-glied-gf-bogen": "GF arc (horizontal curve)", diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index 1b4c047..48d2738 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -360,6 +360,95 @@ ) +;; ------------------------------------------------------------ +;; TESTS: vf_linienzug.lsp - ES-Fussabdruck entlang der Fahrtrichtung +;; ------------------------------------------------------------ +;; vfl-es-laengs liefert die Laenge, die ein Segment fuer das ES-Element +;; reservieren muss. Getestet wird die Rueckfallkette: ohne sie wuerde ein +;; fehlender Messwert zu (- deltaL nil) und damit zum Absturz fuehren. +(defun tu-tests-vfl-es ( / _s-laengs _s-dx) + (princ "\n[vf_linienzug ES]") + ;; globale Masse sichern - sie gehoeren dem laufenden Bau-Kontext + (setq _s-laengs ein-laengs _s-dx ein-dx) + + ;; Normalfall: ein-laengs ist gesetzt und wird genommen. + ;; 576.2 = ES_Element_30_rechts entlang der Fahrtrichtung; (abs ein-dx) + ;; waere 242.6 - genau die 333.65 mm, um die die Kette frueher zu weit ragte. + (setq ein-laengs 576.2 ein-dx -242.6) + (tu-eqf "vfl-es-laengs/nimmt-laengs-nicht-dx" 576.2 (vfl-es-laengs)) + + ;; Vorzeichen: reserviert wird immer ein Betrag + (setq ein-laengs -576.2) + (tu-eqf "vfl-es-laengs/betrag" 576.2 (vfl-es-laengs)) + + ;; Genulltes ein-dx gewinnt: die Null-Konvention der Bau-Pfade bedeutet + ;; "ES anderswo schon beruecksichtigt - hier nichts reservieren". Ohne diese + ;; Kopplung wuerde der Fix einen doppelten Abzug einbauen. + (setq ein-laengs 576.2 ein-dx 0.0) + (tu-eqf "vfl-es-laengs/genulltes-dx-gewinnt" 0.0 (vfl-es-laengs)) + + ;; KS nicht lesbar -> Rueckfall auf das alte (zu kleine) Blockmass + (setq ein-laengs nil ein-dx -242.6) + (tu-eqf "vfl-es-laengs/fallback-auf-dx" 242.6 (vfl-es-laengs)) + + ;; gar nichts gemessen -> 0.0, niemals nil (Absturzschutz) + (setq ein-dx nil) + (tu-eqf "vfl-es-laengs/fallback-null" 0.0 (vfl-es-laengs)) + + (setq ein-laengs _s-laengs ein-dx _s-dx) +) + + +;; ------------------------------------------------------------ +;; TESTS: vf_linienzug.lsp - Laenge aus einer Ziellinie (X/Y) +;; ------------------------------------------------------------ +;; Steht die Fahrtrichtung fest, ist die Laenge nicht mehr frei: die (ggf. +;; seitlich versetzte) Bahn trifft die Ziellinie in genau einem Punkt. +;; REIN PLANAR - die Hoehe kommt weiterhin aus dem Gefaellewinkel. +(defun tu-tests-vfl-ziellinie ( / _p0 _l _ks) + (princ "\n[vf_linienzug Ziellinie]") + + (tu-eqf "vfl-kreuz2/einheitsachsen" 1.0 (vfl-kreuz2 1.0 0.0 0.0 1.0)) + (tu-eqf "vfl-kreuz2/parallel" 0.0 (vfl-kreuz2 2.0 4.0 1.0 2.0)) + + (setq _p0 '(0.0 0.0 0.0)) + + ;; Fahrtrichtung 0 Grad, senkrechte Ziellinie bei x=1000 -> L=1000 + (tu-eqf "vfl-laenge-bis-ziellinie/senkrecht" + 1000.0 (vfl-laenge-bis-ziellinie _p0 0.0 0.0 '(1000.0 -500.0 0.0) '(1000.0 500.0 0.0))) + + ;; Querversatz verschiebt die BAHN seitlich, nicht laengs: bei einer zur + ;; Fahrtrichtung senkrechten Ziellinie bleibt L deshalb gleich. + (tu-eqf "vfl-laenge-bis-ziellinie/querversatz-ohne-wirkung" + 1000.0 (vfl-laenge-bis-ziellinie _p0 0.0 52.5 '(1000.0 -500.0 0.0) '(1000.0 500.0 0.0))) + + ;; 45-Grad-Ziellinie durch (1000,0): die um +100 versetzte Bahn trifft sie + ;; 100 frueher (y=100 wird bei x=1100 erreicht -> ohne Versatz 1000+100). + (tu-eqf "vfl-laenge-bis-ziellinie/schraege-linie" + 1100.0 (vfl-laenge-bis-ziellinie _p0 0.0 100.0 '(1000.0 0.0 0.0) '(2000.0 1000.0 0.0))) + + ;; Ziellinie PARALLEL zur Fahrtrichtung -> kein eindeutiger Treffer -> nil + (tu-nil "vfl-laenge-bis-ziellinie/parallel" + (vfl-laenge-bis-ziellinie _p0 0.0 0.0 '(0.0 500.0 0.0) '(1000.0 500.0 0.0))) + + ;; Z der Ziellinie ist irrelevant (rein planare Rechnung, Nutzer-Vorgabe): + ;; dieselbe Linie auf anderer Hoehe liefert dasselbe L. + (tu-eqf "vfl-laenge-bis-ziellinie/z-ignoriert" + 1000.0 (vfl-laenge-bis-ziellinie _p0 0.0 0.0 '(1000.0 -500.0 7777.0) '(1000.0 500.0 -99.0))) + + ;; Realfall aus dem Nutzer-Log: nach dem 30-Grad-Bogen steht die Kette bei + ;; (901.21,-2728.97) mit hz=-120; das ES-KS_AUS laeuft 52.51 mm versetzt und + ;; landete bei (231.42,-3994.11). Eine Ziellinie durch diesen Punkt quer zur + ;; Fahrtrichtung muss genau die damals gebaute Gesamtlaenge liefern. + (setq _ks '(231.42 -3994.11 0.0)) + (setq _l (vfl-laenge-bis-ziellinie '(901.21 -2728.97 0.0) -120.0 52.51 + _ks (list (+ (car _ks) (cos (/ pi 6.0))) ; 30 Grad = quer zu -120 + (+ (cadr _ks) (sin (/ pi 6.0))) 0.0))) + (tu-true "vfl-laenge-bis-ziellinie/realfall-plausibel" + (and _l (> _l 1430.0) (< _l 1431.0))) +) + + ;; ------------------------------------------------------------ ;; TESTS: export.lsp - CSV/KOS-Kodierung ;; ------------------------------------------------------------ @@ -2138,7 +2227,7 @@ (princ "\n================================================================") (foreach fn '("tu-tests-core" "tu-tests-lang" "tu-tests-id" "tu-tests-vfcore" - "tu-tests-etage" + "tu-tests-etage" "tu-tests-vfl-es" "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"