fix: VF-Linienzug - ES-Fussabdruck laengs korrekt reservieren + Endpunkt per Ziellinie

Drei zusammenhaengende Korrekturen am GF-Zweig mit ES-Element (Modus 1).

1) ES-Fussabdruck wurde massiv unterschaetzt (333.65 mm zu lang)

   Reserviert wurde (abs ein-dx) - die Block-X-Komponente aus
   vf-element-masse. Ein Element wird aber so gedreht eingebaut, dass sein
   KS_EIN auf der Fahrtrichtung liegt; beim drehenden ES_Element_30
   (ein-ang=120 Grad) landet dadurch ein Teil der Block-Y-Komponente auf der
   Fahrtachse. Real verbraucht es laengs 576.2 mm statt der reservierten
   242.6 mm - die Kette ragte um die Differenz ueber den gepickten Endpunkt
   hinaus (Nutzer-Befund 2026-10-09).

   Warum das lange unentdeckt blieb: beim ES_Element_90 ist ein-ang=0, dort
   ist (abs ein-dx) ZUFAELLIG richtig. Der frueher bestaetigende 90-Grad-Test
   konnte den Fehler strukturell nicht finden (Nutzer-Hinweis).

   Neu in vf_core.lsp: vf-element-laengs / vf-element-quer (hz-unabhaengige
   Projektion auf bzw. quer zur Fahrtrichtung), vf-set-es-masse setzt
   zusaetzlich ein-laengs / ein-quer. In vf_linienzug.lsp liest vfl-es-laengs
   den Wert mit Rueckfallkette; ein bewusst genulltes ein-dx gewinnt dabei,
   damit die rund zwei Dutzend Null-/Restore-Stellen keinen Doppelabzug
   bekommen.

   In BricsCAD bestaetigt: Separator+ES verbrauchen 875.83 mm gegen 875.79 mm
   Soll (0.04 mm), Hoehe 151.23 gegen 151.20 mm.

2) Endpunkt per Ziellinie statt per Klick

   Steht die Fahrtrichtung durch den Vorgaenger fest, ist die Laenge nicht
   mehr frei: die (um den ES-Querversatz seitlich versetzte) Bahn des
   ES-KS_AUS trifft eine Ziellinie in genau einem Punkt. Ein Klick kann den
   nur annaehern. Neue Frage mit zwei gleichwertigen Optionen (Radio-Buttons
   im Wizard-Modus): Ziellinie durch ZWEI PUNKTE zeichnen - Schnittpunkt wird
   gerechnet - oder wie bisher einen Punkt waehlen.

   REIN PLANAR: die Hoehe kommt weiterhin aus dem Gefaellewinkel, die
   Ziellinie bestimmt nur X/Y (Nutzer-Praezisierung).

   Beide Wege schreiben denselben einzelnen "DL"-Journaleintrag - Replay,
   Doppelklick-Edit und 2D/3D-Konvertierung muessen von Ziellinien nichts
   wissen. Die Bindung an die Linie besteht nur beim Bauen: wird die Linie
   spaeter verschoben, folgt die Kette NICHT.

   Abwaertskompatibilitaet: die neue Frage erzeugt einen zusaetzlichen
   Journaleintrag, den aeltere Ketten nicht haben. vfl-replay-peek-art sieht
   beim Replay nach, was tatsaechlich ansteht; steht dort ein "DL", ist es
   ein Altbestand und die Frage entfaellt - sonst haette sie den Laengenwert
   als Antwort verschluckt (Queue-Desync).

3) Bedienung: Achsenzwang und ruhige Ansicht

   vfl-getpoint-achse zwingt den Cursor beim Picken auf die Anschlussachse
   (SNAPANG + ORTHOMODE, kein BKS-Wechsel - getpoint liefert daher
   unveraenderte Koordinaten). Nur die horizontale Richtung wird erzwungen,
   die Hoehe bleibt frei.

   vfl-view-refresh zoomte vor JEDER Laengeneingabe hart auf _Extents - die
   Ansicht sprang bei grossen Anlagen nach jedem Klick weg. Jetzt wird nur
   noch eingegriffen, wenn der Baupunkt aus dem sichtbaren Ausschnitt laeuft,
   und dann per _ZOOM _Center unter Beibehaltung der Zoomstufe.

13 neue Unit-Tests (etage-laengs/-quer,
vfl-es-laengs, vfl-kreuz2, vfl-laenge-bis-ziellinie inkl. Realfall aus dem
BricsCAD-Log), Suite 533/533 gruen. lang-Paritaet geprueft.

Offen und bewusst nicht angefasst: vfl-winkel-machbar-p nutzt aus-dx/ein-dx
ebenfalls als Laengsmass und hat damit denselben Fehler (AS_30: 276 mm,
ES_30: 334 mm). Der saubere Fix fuellt ein-dx/aus-dx an der Quelle mit dem
Laengswert - das beruehrt rund 87 Fundstellen samt ihrer Null-/Restore-
Konvention und gehoert separat getestet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 09:04:57 +02:00
parent 8039251bb8
commit 821b81135d
5 changed files with 631 additions and 32 deletions
+61 -3
View File
@@ -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)
;; ============================================================
+465 -27
View File
@@ -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)))
;; 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))
(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
+7
View File
@@ -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)",
+7
View File
@@ -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)",
+90 -1
View File
@@ -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"