This commit is contained in:
2026-10-15 10:35:16 +02:00
20 changed files with 1103 additions and 42 deletions
+2
View File
@@ -203,3 +203,5 @@ cfg/geruest_user.json
# Auswertung ist eingecheckt, die Zeichnung selbst nicht.
/data/polylines.dwg
/data/polylines.dxf
bin/crash_report.txt
bin/*.dmp
+12 -4
View File
@@ -1545,7 +1545,11 @@
;; BERUEHRUNG ("Kreis/Kreis" bzw. "Kreis/Tangente", Namen wie im Python-
;; Export export_neighbors.compute_kreisel_touch_switches).
;; - Beruehren sich nur Laufprofile (Kreisel liegen Seite an Seite) ->
;; KOLLISION "Tangente/Tangente".
;; KOLLISION "Tangente/Tangente". Rad/Rad an beiden Flanken zaehlt nur bei
;; GLEICH ausgerichteten Raedern als Seite an Seite; in Gegenrichtung
;; (Kreisel seitlich versetzt Ende an Ende) ist es eine Weiche.
;; - Toleranz (toleranz_beruehrung, Standard 10 mm) faengt Zeichenungenauig-
;; keiten ab - gilt fuer Spalt UND Ueberlappung.
;; Die reinen Kerne (kreisel-bp-element/-paar/-berechnen) laufen ohne
;; Zeichnung und sind in tests/test_unit.lsp getestet.
@@ -1676,10 +1680,14 @@
(if (<= (abs (- d (+ r (kreisel-bp-get "r" eo)))) tol)
(progn
(setq pt (kreisel-bp-mitte (cadr rad) (cadr rad2)))
;; Beide Kreiselraeder nur an der Flanke = Kreisel Seite an Seite
;; Beide Kreiselraeder nur an der Flanke UND gleich ausgerichtet =
;; Kreisel Seite an Seite. Zeigen die Raeder in Gegenrichtung, stossen
;; die Kreisel seitlich versetzt Ende an Ende (Raeder liegen nebeneinander,
;; die Laufprofile treffen sich nur in einem Punkt) - das ist eine Weiche.
(if (and (kreisel-bp-frei-p rad pt tol) (kreisel-bp-frei-p rad2 pt tol)
(not (and (kreisel-bp-flanke-p rad pt tol)
(kreisel-bp-flanke-p rad2 pt tol))))
(kreisel-bp-flanke-p rad2 pt tol)
(> (kreisel-bp-dot (caddr rad) (caddr rad2)) 0.0))))
(setq res (cons (list pt "Kreis/Kreis" rad
(strcat (car rad) "/" (car rad2)))
res))))))
@@ -1766,7 +1774,7 @@
)
;; Toleranzen aus data/json/component_defaults.json (Sektion kreisel)
(defun kreisel-bp-toleranz () (float (ssg-cfg-or "kreisel" "toleranz_beruehrung" 1.0)))
(defun kreisel-bp-toleranz () (float (ssg-cfg-or "kreisel" "toleranz_beruehrung" 10.0)))
(defun kreisel-bp-toleranz-z () (float (ssg-cfg-or "kreisel" "toleranz_hoehe" 1.0)))
;; Alle KREISEL_n/ECKRAD_n im Modellbereich als Element-Records sammeln.
+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
+1 -1
View File
@@ -13,7 +13,7 @@
"toleranz_kollinear": 0.01,
"toleranz_winkel": 1.0,
"toleranz_min_distanz": 0.001,
"toleranz_beruehrung": 1.0,
"toleranz_beruehrung": 10.0,
"toleranz_hoehe": 1.0,
"weiche_marker_radius": 50.0
},
+1
View File
@@ -426,6 +426,7 @@ ueberspringt sie. Passende Primitive waere `insert-hz-scaled-block-by-ks`
| `mubea.json` | Gesamtanlage: Kreisel, VF Standard/Etage, GF-Template (20 Stueck), Separatoren, Scanner, BTMT, Eingang/Ausgang | `TEST_MUBEA` |
| `dematic_arvato.json` | Vorlage aus `dxf_scan_components.py`: Kreisel, Einzelbloecke, `gerade` (Hoehen fehlen) | `TEST_DEMATIC_ARVATO` (abgeschaltet) |
| `showroom_ils.json` | ILS-3.0-Showroom-Vorlage aus `dxf_scan_components.py` (Kreisel, Gefaellestrecken; Hoehen teils Platzhalter), gleicher Treiber wie `dematic_arvato.json` | `TEST_SHOWROOM_ILS` |
| `test_system.json` | Testanlage aus `dxf_anlage_export.py` (`sps_skel/data/test_system.dxf`): 9 Kreisel, 10 Separatoren, BTMT Beladung, SC Entladung - Vorlage fuer die sps_skel-Pipeline (CSV -> TRO -> SCL), gleicher Treiber wie `dematic_arvato.json` | `TEST_TEST_SYSTEM` |
| `hundm05_linienzug_polylines.json` | Polylinien aus `extract_polylines.py` | kein Treiber (Analyse-Daten) |
| `vfl_journal_hm05_abbruch.json` | rohes Journal (`kind`/`value`) eines abgebrochenen Baus | kein Treiber |
+1
View File
@@ -202,6 +202,7 @@ Aus dem Menue "Tests" bzw. der Kommandozeile; Details in `doc/LispTests.md`.
| `TEST_MUBEA` | Mubea-Nachbau aus `tests/testdata/mubea.json` |
| `TEST_DEMATIC_ARVATO` | Dematic/Arvato-Nachbau (Flowpuffer/Kreisel) |
| `TEST_SHOWROOM_ILS` | ILS-3.0-Showroom-Nachbau (Kreisel, Gefaellestrecken; Hoehen noch Platzhalter) |
| `TEST_TEST_SYSTEM` | Testanlage `test_system` nachbauen (9 Kreisel, Separatoren, BTMT) - Vorlage fuer sps_skel |
---
+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)",
+22
View File
@@ -336,6 +336,27 @@ def einzel_eintrag(e):
])
def kreisel_ids_eindeutig(kreisel, projekt, hinweise):
"""Doppelte Kreisel-IDs aufloesen. Die Nummer kommt aus NUMMER bzw. dem
Blocknamen; in einer Zeichnung, in der Kreisel einzeln gebaut wurden,
beginnt sie jedes Mal neu bei 1 (test_system: vier KREISEL_1_*). Der
erste behaelt seine ID, jeder weitere bekommt die naechste freie Nummer
oberhalb der hoechsten vergebenen. Die Reihenfolge ist die der Eingabe
(Blockname, x, y) und damit stabil."""
muster = re.compile(r"^Kreisel_%s_(\d+)$" % re.escape(projekt))
nummern = [int(m.group(1)) for m in (muster.match(k[sch.F_ID]) for k in kreisel) if m]
naechste = max(nummern, default=0) + 1
vergeben = set()
for k in kreisel:
if k[sch.F_ID] in vergeben:
alt = k[sch.F_ID]
k[sch.F_ID] = "Kreisel_%s_%02d" % (projekt, naechste)
naechste += 1
hinweise.append("%s doppelt - Kreisel bei %.2f/%.2f umbenannt in %s"
% (alt, k[sch.F_X], k[sch.F_Y], k[sch.F_ID]))
vergeben.add(k[sch.F_ID])
def baue_eintraege(inserts, block_inhalt, projekt):
"""Reihenfolge wie mubea.json: Kreisel, VF, GF, Einzelbloecke - die VF
vor den GF, weil gf-init-bibliothek globale Elementmasse umsetzt."""
@@ -344,6 +365,7 @@ def baue_eintraege(inserts, block_inhalt, projekt):
name = e["name"] or ""
if name.startswith("KREISEL_"):
kreisel.append(kreisel_eintrag(e, block_inhalt, projekt))
kreisel_ids_eindeutig(kreisel, projekt, hinweise)
geo = [kreisel_geo(k) for k in kreisel]
for e in inserts:
name = e["name"] or ""
+14
View File
@@ -255,6 +255,14 @@ def build_sensor_merkmale(block):
}
def _sensor_zuordnung_attr(block):
"""Carrier-ID (Kreisel/GF/VF-TeileId) eines Separator-/Scanner-Blocks aus
der Zeichnung: zuordnung_fix (verpackte Kopie, siehe build_sensor_
merkmale) vor dem ZUORDNUNG-Attribut. "" wenn nichts gesetzt ist."""
return (block.get("zuordnung_fix")
or block.get("attribs", {}).get("ZUORDNUNG", "") or "").strip()
def build_element_merkmale(block):
"""Merkmale-Dict fuer eine ausgepackte VF_n/GF_n-Sub-Komponente (Bogen/
Streckenelement/Umlenkstation/Motorstation) - siehe csv:element-proxies-
@@ -1341,6 +1349,11 @@ def process_blocks(blocks, lookup, dbg=None, lisp_weichen=None):
# Zuordnung, die compute_sensor_zuordnung unten nicht
# ueberschreiben darf - siehe build_sensor_merkmale.
"_zuordnung_fix": bool(block.get("zuordnung_fix")),
# Privates Feld: Carrier-ID aus der Zeichnung (zuordnung_fix
# bzw. ZUORDNUNG-Attribut, von cs-zuordnung-lauf in
# Lisp/count_sep_scan.lsp gepflegt) - hat in compute_sensor_
# zuordnung/compute_kreisel_umlauf Vorrang vor der BBox-Suche.
"_zuordnung_attr": _sensor_zuordnung_attr(block),
# Roher INSERT-Punkt (nicht der bbox-Mittelpunkt) - dient
# map_sepliste_kette_items als Koordinaten-FALLBACK, falls der
# sepliste-Eintrag (noch) kein "id"-Feld traegt (Altbestand;
@@ -1368,6 +1381,7 @@ def process_blocks(blocks, lookup, dbg=None, lisp_weichen=None):
# sammelt - csv:block-to-json (export.lsp) haengt den Text als
# "warnung"-Feld an den betroffenen Scanner-Block im JSON an.
"warnung": block.get("warnung", ""),
"_zuordnung_attr": _sensor_zuordnung_attr(block),
**bbox_columns(block),
})
continue
+29
View File
@@ -784,6 +784,9 @@ def compute_sensor_zuordnung(items, tolerance_mm, kreisel_durchmesser_mm=800.0,
gf_entries = []
vf_entries = []
kreisel_entries = []
# TeileIds aller Carrier (GF/VF/Kreisel) - fuer den Vorrang des
# ZUORDNUNG-Attributs ("_zuordnung_attr") vor der BBox-Suche.
carrier_ids = set()
for item in items:
bbox = item.get("_bbox")
@@ -793,9 +796,12 @@ def compute_sensor_zuordnung(items, tolerance_mm, kreisel_durchmesser_mm=800.0,
teileid = item.get("teileid", "")
if teileart == "ILS 2.0 Gefaellestrecke":
gf_entries.append((teileid, _bounds(bbox, half_tol)))
carrier_ids.add(teileid)
elif teileart in ("ILS 2.0 Strecke", "ILS 2.0 Strecke - Modul"):
vf_entries.append((teileid, _bounds(bbox, half_tol)))
carrier_ids.add(teileid)
elif teileart == KREISEL_SPLIT_TEILEART:
carrier_ids.add(teileid)
bbox_links, bbox_rechts = kreisel_half_bboxes(item, kreisel_durchmesser_mm)
kreisel_entries.append((f"{teileid}-L", _bounds(bbox_links, half_tol)))
kreisel_entries.append((f"{teileid}-R", _bounds(bbox_rechts, half_tol)))
@@ -824,6 +830,18 @@ def compute_sensor_zuordnung(items, tolerance_mm, kreisel_durchmesser_mm=800.0,
result.append("")
continue
bounds = _bounds(bbox, half_tol)
# Vorrang: ZUORDNUNG aus der Zeichnung (cs-zuordnung-lauf), sofern sie
# einen vorhandenen Carrier nennt. Die BBox-Suche nimmt sonst den
# ERSTEN Treffer - an einer Kreisel-Kreisel-Weiche ragt ein Separator
# mit Toleranz in beide Kreisel und landete beim falschen (realer
# Fall test_system, Separator 0018: Attribut 0005, BBox -> 0006).
# Bei einem Kreisel wird nur noch die Haelfte (L/R) per BBox bestimmt.
attr = (item.get("_zuordnung_attr") or "").strip()
if attr in carrier_ids:
haelften = [e for e in kreisel_entries
if e[0] in (f"{attr}-L", f"{attr}-R")]
treffer = (_find(bounds, haelften) if haelften else None) or attr
else:
treffer = (_find(bounds, gf_entries)
or _find(bounds, vf_entries)
or _find(bounds, kreisel_entries)
@@ -1383,7 +1401,18 @@ def compute_kreisel_umlauf(items, tolerance_mm, durchmesser_mm=800.0,
continue
bounds = _bounds(bbox, half_tol)
matched_tid = None
# Vorrang: ZUORDNUNG-Attribut des Separators/Scanners, wenn es einen
# Kreisel nennt (siehe compute_sensor_zuordnung) - die BBox-Suche
# unten nimmt den ERSTEN ueberlappenden Kreisel und verwechselt an
# einer Kreisel-Kreisel-Weiche die beiden Kreisel.
attr = (item.get("_zuordnung_attr") or "").strip()
if attr in kreisel_bounds:
punkte_je_kreisel[attr].append(
(teileid, bbox.get("cx", 0.0), bbox.get("cy", 0.0)))
matched_tid = attr
for tid, kb in kreisel_bounds.items():
if matched_tid is not None:
break
if _overlaps(bounds, kb):
punkte_je_kreisel[tid].append(
(teileid, bbox.get("cx", 0.0), bbox.get("cy", 0.0)))
+12
View File
@@ -872,6 +872,12 @@
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_showroom_ils.lsp")) (c:TEST_SHOWROOM_ILS)</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00210">
<Macro>
<Name>Testanlage test_system</Name>
<Command>^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_test_system.lsp")) (c:TEST_TEST_SYSTEM)</Command>
</Macro>
</MenuMacro>
<MenuMacro UID="MM_00189">
<Macro>
<Name>KS 2D/3D pruefen (CSV)</Name>
@@ -1921,6 +1927,12 @@
<MacroRef MenuMacroID="MM_00188"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>Testanlage test_system</NameRef>
<MenuItem>
<MacroRef MenuMacroID="MM_00210"/>
</MenuItem>
</PopMenuItem>
<PopMenuItem>
<NameRef>KS 2D/3D pruefen (CSV)</NameRef>
<MenuItem>
+1
View File
@@ -186,6 +186,7 @@
[HundM05 Kreisel-Abschnitt]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_hundm05.lsp")) (c:TEST_HUNDM05)
[Dematic/Arvato Flowpuffer (Vorlage)]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_dematic_arvato.lsp")) (c:TEST_DEMATIC_ARVATO)
[ILS 3.0 Showroom (Vorlage)]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_showroom_ils.lsp")) (c:TEST_SHOWROOM_ILS)
[Testanlage test_system]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_test_system.lsp")) (c:TEST_TEST_SYSTEM)
[<-KS 2D/3D pruefen (CSV)]^C^C(load (strcat (getenv "DXFMAKRO") "/Lisp/ks_pruefung.lsp")) KS_PRUEFEN
***DOUBLECLICK
+2 -1
View File
@@ -12,5 +12,6 @@
{ "name": "hundm05", "save": "dxf", "module": "VarioFoerderer" },
{ "name": "dematic_arvato", "save": "dxf", "module": "VarioFoerderer", "disabled": true },
{ "name": "hm_recformat", "save": "dxf", "module": "VarioFoerderer", "disabled": true },
{ "name": "showroom_ils", "save": "dxf", "module": "VarioFoerderer", "disabled": true }
{ "name": "showroom_ils", "save": "dxf", "module": "VarioFoerderer", "disabled": true },
{ "name": "test_system", "save": "dxf", "module": "VarioFoerderer" }
]
+15
View File
@@ -145,3 +145,18 @@ def test_reihenfolge_vf_vor_gf():
art = ["K" if "abstand" in e else "GF" if "modus" in e else
"VF" if "typ" in e else e["block"] for e in eintraege]
assert art == ["K", "K", "VF", "GF", "Scanner"]
def test_doppelte_kreisel_nummern_werden_eindeutig():
# test_system.dxf: Kreisel einzeln gebaut, NUMMER beginnt jedes Mal bei 1
eins_a = _insert("KREISEL_1_STANDARD_2300", (-3561.74, 994.84, 2000.0), 0.0,
{"NUMMER": "1"})
eins_b = _insert("KREISEL_1_STANDARD_2900", (4940.59, -2303.25, 0.0), 90.0,
{"NUMMER": "1"})
zwei = _insert("KREISEL_2_STANDARD_4500", (4940.03, -7603.25, 0.0), 90.0,
{"NUMMER": "2"})
eintraege, hinweise = dae.baue_eintraege([eins_a, eins_b, zwei], {}, "T")
ids = {k["id"]: k["x"] for k in eintraege}
assert sorted(ids) == ["Kreisel_T_01", "Kreisel_T_02", "Kreisel_T_03"]
assert ids["Kreisel_T_03"] == 4940.59 # der zweite KREISEL_1 weicht aus
assert any("Kreisel_T_03" in h for h in hinweise)
+48
View File
@@ -538,3 +538,51 @@ class TestAsEsUeberEinlaufpunkt:
umlauf = compute_kreisel_umlauf(
items, tolerance_mm=200.0, durchmesser_mm=DURCHMESSER_MM)
assert "0071" not in [p[0] for p in umlauf["0001"]]
class TestZuordnungAttributVorBBox:
"""Realer Fall test_system, Separator 0018: er sitzt an der Kreisel-
Kreisel-Weiche zweier Kreisel und ragt mit Toleranz in die Box BEIDER.
Die BBox-Suche nahm den ersten Kreisel in der Item-Liste (den falschen);
das ZUORDNUNG-Attribut aus der Zeichnung (cs-zuordnung-lauf) nennt den
richtigen und hat Vorrang."""
def _anlage(self, attr):
# Kreisel 0006 senkrecht (Achse +Y), AN8-Mitte bei (0, 400);
# Kreisel 0005 waagerecht (Achse +X), AN8-Mitte bei (0, -400):
# AN8/AN8 beruehren sich bei (0, 0). Separator auf der Oberkante von
# Kreisel 0005, 615 mm rechts der Weiche.
k6 = _kreisel("0006", 0.0, 0.0, 90.0, abstand=2000.0)
k5 = _kreisel("0005", -400.0, -400.0, 0.0, abstand=8000.0)
sep = _separator("0018", 615.0, 0.0)
sep["_zuordnung_attr"] = attr
sep["merkmale"] = {}
return [sep, k6, k5]
def test_umlauf_folgt_attribut(self):
items = self._anlage("0005")
umlauf = compute_kreisel_umlauf(items, tolerance_mm=200.0,
durchmesser_mm=DURCHMESSER_MM)
assert "0018" in [p[0] for p in umlauf["0005"]]
assert "0018" not in [p[0] for p in umlauf["0006"]]
def test_ohne_attribut_bbox_rueckfall(self):
items = self._anlage("")
umlauf = compute_kreisel_umlauf(items, tolerance_mm=200.0,
durchmesser_mm=DURCHMESSER_MM)
alle = [p[0] for pts in umlauf.values() for p in pts]
assert alle.count("0018") == 1
def test_sensor_zuordnung_folgt_attribut(self):
from export_neighbors import compute_sensor_zuordnung
items = self._anlage("0005")
zuo = compute_sensor_zuordnung(items, 200.0,
kreisel_durchmesser_mm=DURCHMESSER_MM)
assert zuo[0].startswith("0005")
def test_unbekanntes_attribut_bbox_rueckfall(self):
from export_neighbors import compute_sensor_zuordnung
items = self._anlage("9999")
zuo = compute_sensor_zuordnung(items, 200.0,
kreisel_durchmesser_mm=DURCHMESSER_MM)
assert zuo[0] != "9999" and zuo[0] != "nicht zugeordnet"
+61
View File
@@ -0,0 +1,61 @@
;; ============================================================
;; test_test_system.lsp - Nachbau der Testanlage "test_system".
;;
;; Datenquelle: tests/testdata/test_system.json. Die Zeichnung wurde mit
;; unseren Makros gebaut (KREISEL_n-Wrapper, Separatoren, BTMT), die Testdaten
;; kommen darum aus dem Rueckleser, nicht aus dem Scanner:
;; python lib/dxf_anlage_export.py \
;; C:/develop/sps_skel/data/test_system.dxf \
;; tests/testdata/test_system.json --projekt TestSystem
;;
;; Inhalt: 9 Kreisel (STANDARD, Ebenen 0 / 2000 / 3000), 10 Separatoren,
;; BTMT Beladung und SC Entladung - keine Gefaellestrecken, keine VF.
;; - Die Kreisel wurden in der Zeichnung einzeln gebaut, die Nummer begann
;; dabei mehrmals neu bei 1 (vier KREISEL_1_*). dxf_anlage_export.py
;; vergibt fuer die Doppelten Kreisel_TestSystem_07..09 - die Nummern der
;; gebauten Zeichnung weichen also von der Vorlage ab.
;; - Die x-Werte sind NACH dem Rueckleser von Hand korrigiert: in der
;; Zeichnung lagen die Kreisel bis zu 5.5 mm daneben, jetzt beruehren sich
;; alle 8 Weichenpaare exakt (1-9, 9-8, 8-7, 7-2, 2-3, 3-4, 4-5, 5-6;
;; Kreisel 1 fest, Separatoren/BTMT mit ihrem Kreisel verschoben). Ein
;; erneuter Lauf von dxf_anlage_export.py macht das rueckgaengig.
;;
;; Zweck: Vorlage fuer sps_skel. Nachbau -> EXPORTCSV -> CSV, auf die dort
;; material_flow.py / tro_flow.py / scl_skeleton.py angewendet werden.
;;
;; Keine eigene Bau-Logik: Eintragsschema und Lauf wie TEST_DEMATIC_ARVATO
;; (dematic_arvato:bau-datei aus tests/test_dematic_arvato.lsp).
;;
;; Aufruf in BricsCAD:
;; (load (strcat (getenv "DXFMAKRO") "/tests/test_test_system.lsp"))
;; TEST_TEST_SYSTEM
;; ============================================================
(if (not (boundp '*test-system-records*)) (setq *test-system-records* nil))
;; Beim LADEN holen (siehe test_dematic_arvato.lsp: per load definierte
;; Funktionen sind erst nach Rueckkehr zum Top-Level sichtbar).
(if (null (car (atoms-family 1 '("DEMATIC_ARVATO:BAU-DATEI"))))
(load (strcat (getenv "DXFMAKRO") "/tests/test_dematic_arvato.lsp")))
(defun test_system:datei ()
(strcat (getenv "DXFMAKRO") "/tests/testdata/test_system.json"))
(defun c:TEST_TEST_SYSTEM ()
(setq *test-system-records*
(dematic_arvato:bau-datei "TEST_TEST_SYSTEM" "Testanlage test_system"
(test_system:datei)))
(princ))
;; Ergebnis-Export (Konvention <name>:export-results fuer test_run_all.lsp)
(defun test_system:export-results (out-dir / pfad)
(if (null *test-system-records*)
(princ "\n Keine test_system-Ergebnisse vorhanden.")
(progn
(vl-mkdir out-dir)
(setq pfad (strcat out-dir "/test_system_results.json"))
(vsp-results-schreiben *test-system-records* pfad)))
(princ))
(princ "\nTEST_TEST_SYSTEM geladen. Aufruf: TEST_TEST_SYSTEM")
(princ)
+119 -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
;; ------------------------------------------------------------
@@ -846,6 +935,35 @@
(tu-bp-el "ECKRAD" 400.0 800.0 0.0 0.0 0.0 "B"))
1.0 1.0)))
(tu-eq "kreisel-bp/flanke-eckrad" 1 (length _ber))
;; Kreisel seitlich versetzt Ende an Ende (Raeder Flanke an Flanke, aber in
;; Gegenrichtung) -> Weiche, keine Seite-an-Seite-Kollision
(setq _erg (kreisel-bp-berechnen (list (tu-bp-el "KREISEL" 0.0 0.0 0.0 0.0 2000.0 "A")
(tu-bp-el "KREISEL" 2000.0 800.0 0.0 0.0 2000.0 "B"))
1.0 1.0))
(tu-eq "kreisel-bp/ende-an-ende-art" '("Kreis/Kreis") (tu-bp-arten (car _erg)))
(tu-eqf "kreisel-bp/ende-an-ende-punkt" '(2400.0 400.0) (cdr (assoc "p" (car (car _erg)))))
(tu-nil "kreisel-bp/ende-an-ende-keine-kollision" (cadr _erg))
;; Gleich ausgerichtete Raeder Flanke an Flanke (echtes Seite an Seite) -> keine Weiche
(setq _erg (kreisel-bp-berechnen (list (tu-bp-el "KREISEL" 0.0 0.0 0.0 0.0 2000.0 "A")
(tu-bp-el "KREISEL" 0.0 800.0 0.0 0.0 2000.0 "B"))
1.0 1.0))
(tu-nil "kreisel-bp/seite-an-seite-keine-weiche" (car _erg))
;; results/test.dxf (E3/C7): Kreisel 2/3/4 Ende an Ende (0.9 mm Ueberlappung bzw.
;; 5.5 mm), Kreisel 8 vor dem Profil von Kreisel 9 (2.6 mm Spalt) - mit 10 mm
;; Toleranz drei Weichen
(setq _erg (kreisel-bp-berechnen
(list (tu-bp-el "KREISEL" 4940.03 -7603.25 2000.0 90.0 4500.0 "2")
(tu-bp-el "KREISEL" 5739.13 -12898.65 2000.0 90.0 5296.0 "3")
(tu-bp-el "KREISEL" 6533.6 -7603.44 2000.0 90.0 7889.0 "4")
(tu-bp-el "KREISEL" -61.76 -974.08 2000.0 90.0 4000.0 "9")
(tu-bp-el "KREISEL" 340.85 505.79 2000.0 0.0 3400.0 "8"))
10.0 1.0))
(tu-eq "kreisel-bp/test-dxf-paare"
'(("2" "3") ("3" "4") ("9" "8"))
(mapcar '(lambda (tr) (list (cdr (assoc "id" (cdr (assoc "a" tr))))
(cdr (assoc "id" (cdr (assoc "b" tr))))))
(car _erg)))
(tu-nil "kreisel-bp/test-dxf-keine-kollision" (cadr _erg))
)
@@ -2138,7 +2256,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"
+218
View File
@@ -0,0 +1,218 @@
[
{
"id": "Kreisel_TestSystem_01",
"function": "insert",
"x": -3561.74,
"y": 994.84,
"z": 2000.0,
"abstand": 2300.0,
"rotation": 0.0,
"typ": "STANDARD",
"drehrichtung": "UZS",
"comment": "aus Zeichnung: KREISEL_1_STANDARD_2300 (ID 0028), abstand aus Blockdefinition (AN8 -> SP8)"
},
{
"id": "Kreisel_TestSystem_02",
"function": "insert",
"x": 4938.26,
"y": -7603.25,
"z": 2000.0,
"abstand": 4500.0,
"rotation": 90.0,
"typ": "STANDARD",
"drehrichtung": "UZS",
"comment": "aus Zeichnung: KREISEL_2_STANDARD_4500 (ID 0033), abstand aus Blockdefinition (AN8 -> SP8), x auf exakte Beruehrung korrigiert (-1.77)"
},
{
"id": "Kreisel_TestSystem_03",
"function": "insert",
"x": 5738.26,
"y": -12898.65,
"z": 2000.0,
"abstand": 5295.71,
"rotation": 90.0,
"typ": "STANDARD",
"drehrichtung": "GUZ",
"comment": "aus Zeichnung: KREISEL_3_STANDARD_5296 (ID 0034), abstand aus Blockdefinition (AN8 -> SP8), x auf exakte Beruehrung korrigiert (-0.87)"
},
{
"id": "Kreisel_TestSystem_04",
"function": "insert",
"x": 6538.26,
"y": -7603.44,
"z": 2000.0,
"abstand": 7889.42,
"rotation": 90.0,
"typ": "STANDARD",
"drehrichtung": "UZS",
"comment": "aus Zeichnung: KREISEL_4_STANDARD_7889 (ID 0035), abstand aus Blockdefinition (AN8 -> SP8), x auf exakte Beruehrung korrigiert (+4.66)"
},
{
"id": "Kreisel_TestSystem_05",
"function": "insert",
"x": 6138.26,
"y": 1485.99,
"z": 2000.0,
"abstand": 9783.58,
"rotation": 0.0,
"typ": "STANDARD",
"drehrichtung": "GUZ",
"comment": "aus Zeichnung: KREISEL_5_STANDARD_9784 (ID 0036), abstand aus Blockdefinition (AN8 -> SP8), x auf exakte Beruehrung korrigiert (+4.66)"
},
{
"id": "Kreisel_TestSystem_06",
"function": "insert",
"x": 6538.26,
"y": 1885.99,
"z": 2000.0,
"abstand": 3718.63,
"rotation": 90.0,
"typ": "STANDARD",
"drehrichtung": "UZS",
"comment": "aus Zeichnung: KREISEL_6_STANDARD_3719 (ID 0037), abstand aus Blockdefinition (AN8 -> SP8), x auf exakte Beruehrung korrigiert (+4.66)"
},
{
"id": "Kreisel_TestSystem_07",
"function": "insert",
"x": 4938.26,
"y": -2303.25,
"z": 2000.0,
"abstand": 2900.0,
"rotation": 90.0,
"typ": "STANDARD",
"drehrichtung": "GUZ",
"comment": "aus Zeichnung: KREISEL_1_STANDARD_2900 (ID 0031), abstand aus Blockdefinition (AN8 -> SP8), x auf exakte Beruehrung korrigiert (-2.33)"
},
{
"id": "Kreisel_TestSystem_08",
"function": "insert",
"x": 338.26,
"y": 505.79,
"z": 2000.0,
"abstand": 3400.0,
"rotation": 0.0,
"typ": "STANDARD",
"drehrichtung": "UZS",
"comment": "aus Zeichnung: KREISEL_1_STANDARD_3400 (ID 0030), abstand aus Blockdefinition (AN8 -> SP8), x auf exakte Beruehrung korrigiert (-2.59)"
},
{
"id": "Kreisel_TestSystem_09",
"function": "insert",
"x": -61.74,
"y": -974.08,
"z": 2000.0,
"abstand": 4000.0,
"rotation": 90.0,
"typ": "STANDARD",
"drehrichtung": "GUZ",
"comment": "aus Zeichnung: KREISEL_1_STANDARD_4000 (ID 0032), abstand aus Blockdefinition (AN8 -> SP8), x auf exakte Beruehrung korrigiert (+0.02)"
},
{
"function": "insert",
"block": "Separator_SP",
"x": -461.74,
"y": 1466.18,
"z": 2000.0,
"rotation": 90.0,
"comment": "aus Zeichnung: Separator_SP_3D (ID 0015, ZUORDNUNG 0032, ARTINR 200000077), x mit Kreisel 09 verschoben (+0.02)"
},
{
"function": "insert",
"block": "Separator_SP",
"x": 1181.85,
"y": 105.79,
"z": 0.0,
"rotation": 0.0,
"comment": "aus Zeichnung: Separator_SP_3D (ID 0017, ZUORDNUNG 0030, ARTINR 200000077), x mit Kreisel 08 verschoben (-2.59)"
},
{
"function": "insert",
"block": "Separator_SP",
"x": 3756.79,
"y": 905.79,
"z": 2000.0,
"rotation": 180.0,
"comment": "aus Zeichnung: Separator_SP_3D (ID 0018, ZUORDNUNG 0030, ARTINR 200000077), x mit Kreisel 08 verschoben (-2.59)"
},
{
"function": "insert",
"block": "Separator_SP",
"x": 4538.26,
"y": -1553.42,
"z": 2000.0,
"rotation": 90.0,
"comment": "aus Zeichnung: Separator_SP_3D (ID 0019, ZUORDNUNG 0031, ARTINR 200000077), x mit Kreisel 07 verschoben (-2.33)"
},
{
"function": "insert",
"block": "Separator_SP",
"x": 4538.26,
"y": -3109.7,
"z": 2000.0,
"rotation": 270.0,
"comment": "aus Zeichnung: Separator_SP_3D (ID 0020, ZUORDNUNG 0033, ARTINR 200000077), x mit Kreisel 02 verschoben (-1.77)"
},
{
"function": "insert",
"block": "Separator_SP",
"x": 5338.26,
"y": -6594.16,
"z": 2000.0,
"rotation": 90.0,
"comment": "aus Zeichnung: Separator_SP_3D (ID 0021, ZUORDNUNG 0033, ARTINR 200000077), x mit Kreisel 02 verschoben (-1.77)"
},
{
"function": "insert",
"block": "Separator_SP",
"x": 6938.26,
"y": -6678.18,
"z": 2000.0,
"rotation": 90.0,
"comment": "aus Zeichnung: Separator_SP_3D (ID 0022, ZUORDNUNG 0035, ARTINR 200000077), x mit Kreisel 04 verschoben (+4.66)"
},
{
"function": "insert",
"block": "Separator_SP",
"x": 6138.26,
"y": 362.53,
"z": 2000.0,
"rotation": 270.0,
"comment": "aus Zeichnung: Separator_SP_3D (ID 0023, ZUORDNUNG 0035, ARTINR 200000077), x mit Kreisel 04 verschoben (+4.66)"
},
{
"function": "insert",
"block": "Separator_SP",
"x": 7093.89,
"y": 1885.99,
"z": 2000.0,
"rotation": 0.0,
"comment": "aus Zeichnung: Separator_SP_3D (ID 0024, ZUORDNUNG 0036, ARTINR 200000077), x mit Kreisel 05 verschoben (+4.66)"
},
{
"function": "insert",
"block": "Separator_SP",
"x": 6938.26,
"y": 2848.54,
"z": 2000.0,
"rotation": 90.0,
"comment": "aus Zeichnung: Separator_SP_3D (ID 0025, ZUORDNUNG 0037, ARTINR 200000077), x mit Kreisel 06 verschoben (+4.66)"
},
{
"function": "insert",
"block": "BTMT-Beladung",
"x": 14622.43,
"y": 1085.99,
"z": 2000.0,
"rotation": 0.0,
"comment": "aus Zeichnung: BTMT-Beladung_3D (ID 0026, ARTINR 829662012), x mit Kreisel 05 verschoben (+4.66)"
},
{
"function": "insert",
"block": "SC_Entladung",
"x": -2246.43,
"y": 581.63,
"z": 2000.0,
"rotation": 179.49,
"comment": "aus Zeichnung: SC_Entladung_3D (ID 0027, ARTINR 400100156)"
}
]