diff --git a/Lisp/Gefaellestrecke.lsp b/Lisp/Gefaellestrecke.lsp index 80efae6..4f1f5e5 100644 --- a/Lisp/Gefaellestrecke.lsp +++ b/Lisp/Gefaellestrecke.lsp @@ -992,6 +992,7 @@ ss n i j obj obj-liste kette segmente seg startpunkt endpunkt-ref deltaH grade antwort idx as-seite es-seite as-block es-block + gf-as-winkel gf-es-winkel entry-hz exit-hz first-idx last-idx neu-l aktuell-frame nach-aus-pt @@ -1059,22 +1060,27 @@ (setq entry-hz (cadr (car segmente))) (setq exit-hz (gf-exit-hz segmente)) - ;; Seite fuer AUS-Element (vor Winkelberechnung, da Laengenanpassung davon abhaengt) + ;; AUS-Element: 30/90 (vor Seite), dann Seite (vor Winkelberechnung, da die + ;; Laengenanpassung von der AS/ES-Masse abhaengt). Masse fuer Variante setzen. + (setq gf-as-winkel (vf-frage-element-winkel "vf-winkel-aus-header")) (princ (ssg-text "gf-seite-aus-header")) (princ (ssg-text "gf-seite-links")) (princ (ssg-text "gf-seite-rechts")) (setq antwort (getstring (ssg-text "prompt-wahl-1-2"))) (setq as-seite (if (= antwort "2") "rechts" "links")) + (vf-set-as-masse gf-as-winkel as-seite) - ;; Seite fuer EIN-Element + ;; EIN-Element: 30/90 + Seite + (setq gf-es-winkel (vf-frage-element-winkel "vf-winkel-ein-header")) (princ (ssg-text "gf-seite-ein-header")) (princ (ssg-text "gf-seite-links")) (princ (ssg-text "gf-seite-rechts")) (setq antwort (getstring (ssg-text "prompt-wahl-1-2"))) (setq es-seite (if (= antwort "2") "rechts" "links")) + (vf-set-es-masse gf-es-winkel es-seite) - (setq as-block (strcat "AS_Element_90_" as-seite)) - (setq es-block (strcat "ES_Element_90_" es-seite)) + (setq as-block (strcat "AS_Element_" gf-as-winkel "_" as-seite)) + (setq es-block (strcat "ES_Element_" gf-es-winkel "_" es-seite)) ;; Segmentlaengen anpassen. ;; Die gezeichneten Linien enthalten die Laengen von AUS-/EIN-Element UND Separatoren. @@ -1165,8 +1171,9 @@ (while (and (< iter 4) (> (abs (- grade grade-prev)) 0.001)) (setq grade-prev grade) (setq rad-v-korr (* grade (/ pi 180.0))) - ;; AS-Element: KS_EIN zeigt 90 Grad zur Fahrtrichtung - (setq ein-hz-as-k (if (= as-seite "rechts") (+ entry-hz 90.0) (- entry-hz 90.0))) + ;; AS-Element: gemessener Ebenen-Schwenk -> KS_EIN so, dass KS_AUS entlang hz + (setq ein-hz-as-k (- entry-hz + (vf-element-plan-turn (strcat "AS_Element_" gf-as-winkel "_" as-seite)))) (setq rad-h-as-k (* (float ein-hz-as-k) (/ pi 180.0))) (setq xu-as-meas (list (* (cos rad-h-as-k)(cos rad-v-korr)) (* (sin rad-h-as-k)(cos rad-v-korr)) @@ -1226,11 +1233,10 @@ ;; AUS-Element: KS_EIN so ausrichten, dass KS_AUS in Fahrtrichtung entry-hz zeigt. ;; Beweis: KS_AUS.xu = -yu(target-frame). Fuer KS_AUS||entry-hz gilt: - ;; rechts (Rechtskurve): KS_EIN = entry-hz + 90deg - ;; links (Linkskurve): KS_EIN = entry-hz - 90deg - (setq ein-hz-as (if (= as-seite "rechts") - (+ entry-hz 90.0) - (- entry-hz 90.0))) + ;; Gemessener Ebenen-Schwenk des AS-Blocks -> KS_EIN so drehen, dass KS_AUS + ;; entlang der Fahrtrichtung hz zeigt (ein-hz = hz - plan-turn). + (setq ein-hz-as (- entry-hz + (vf-element-plan-turn (strcat "AS_Element_" gf-as-winkel "_" as-seite)))) (setq rad-h-as (* (float ein-hz-as) (/ pi 180.0))) (setq rad-v-as (* (float grade) (/ pi 180.0))) (setq xu-entry (list (* (cos rad-h-as)(cos rad-v-as)) @@ -1519,15 +1525,18 @@ ;; deltaL-total: gesamte Horizontaldistanz (fuer DELTA_L-Attribut) ;; hz: Fahrtrichtung in Grad (0=Ost, 90=Nord, 180=West, 270=Sued) -(defun gefaellestrecke-einfuegen (L_stau winkel startpunkt as-seite es-seite hz deltaL-total / +(defun gefaellestrecke-einfuegen (L_stau winkel startpunkt as-seite es-seite hz deltaL-total + as-winkel es-winkel / as-block es-block endpunkt gf-nummer last-ent rad-hz rad-v xu-incl ein-hz rad-ein xu-ein startframe aktuell-frame stau-endpunkt sep-endpunkt sep-frame gf-deltaH L-gf-str nach-aus-pt gf-insert) - (setq as-block (strcat "AS_Element_90_" as-seite)) - (setq es-block (strcat "ES_Element_90_" es-seite)) + (if (null as-winkel) (setq as-winkel "90")) + (if (null es-winkel) (setq es-winkel "90")) + (setq as-block (strcat "AS_Element_" as-winkel "_" as-seite)) + (setq es-block (strcat "ES_Element_" es-winkel "_" es-seite)) (if (not *lib-initialized*) (gf-init-bibliothek)) ;; GF-Nummer und Entity-Grenze vor erster Einfuegung @@ -1541,11 +1550,9 @@ (* (sin rad-hz)(cos rad-v)) (- (sin rad-v)))) - ;; KS_EIN-Richtung fuer AS-Element: so dass KS_AUS in Fahrtrichtung hz zeigt. - ;; Beweis: KS_AUS.xu = -yu (target-frame), yu = links-senkrecht zu xu. - ;; rechts (Rechtskurve): KS_AUS = KS_EIN - 90deg => KS_EIN = hz + 90deg - ;; links (Linkskurve): KS_AUS = KS_EIN + 90deg => KS_EIN = hz - 90deg - (setq ein-hz (if (= as-seite "rechts") (+ hz 90.0) (- hz 90.0))) + ;; KS_EIN-Richtung fuer AS-Element: gemessener Ebenen-Schwenk des Blocks, so + ;; dass KS_AUS in Fahrtrichtung hz zeigt (ein-hz = hz - plan-turn). + (setq ein-hz (- hz (vf-element-plan-turn (strcat "AS_Element_" as-winkel "_" as-seite)))) (setq rad-ein (* (float ein-hz) (/ pi 180.0))) (setq xu-ein (list (* (cos rad-ein)(cos rad-v)) (* (sin rad-ein)(cos rad-v)) @@ -1618,7 +1625,7 @@ ;; ============================================================ (defun c:GEFAELLESTRECKE ( / antwort eingabe-modus deltaL winkel rad L_stau sep-x - as-seite es-seite + as-seite es-seite gf-as-winkel gf-es-winkel line-points startpunkt endpunkt differenz deltaX deltaY startpunkt-fuer-einfuegen hz-winkel) @@ -1729,21 +1736,25 @@ ) ) - ;; Seite AUS-Element waehlen + ;; AUS-Element: 30/90 (vor Seite) + Seite, dann Masse fuer Variante setzen + (setq gf-as-winkel (vf-frage-element-winkel "vf-winkel-aus-header")) (princ (ssg-text "gf-seite-aus-header")) (princ (ssg-text "gf-seite-links")) (princ (ssg-text "gf-seite-rechts")) (setq antwort (getstring (ssg-text "prompt-wahl-1-2"))) (setq as-seite (if (= antwort "2") "rechts" "links")) + (vf-set-as-masse gf-as-winkel as-seite) - ;; Seite EIN-Element waehlen + ;; EIN-Element: 30/90 + Seite + (setq gf-es-winkel (vf-frage-element-winkel "vf-winkel-ein-header")) (princ (ssg-text "gf-seite-ein-header")) (princ (ssg-text "gf-seite-links")) (princ (ssg-text "gf-seite-rechts")) (setq antwort (getstring (ssg-text "prompt-wahl-1-2"))) (setq es-seite (if (= antwort "2") "rechts" "links")) + (vf-set-es-masse gf-es-winkel es-seite) - ;; Laengenberechnung + ;; Laengenberechnung (aus-dx/ein-dx jetzt fuer die gewaehlte Variante) ;; deltaL = aus_dx + L_stau*cos(rad) + sep*cos(rad) + ein_dx (setq sep-x (* (ssg-cfg-or "gefaelle" "separator_breite" 300.0) (cos rad))) (setq L_stau (/ (- deltaL aus-dx ein-dx sep-x) (cos rad))) @@ -1768,7 +1779,7 @@ ;; Bestaetigung (setq antwort (getstring (ssg-text "gf-prompt-einfuegen"))) (if (not (= antwort "2")) - (gefaellestrecke-einfuegen L_stau (fix winkel) startpunkt-fuer-einfuegen as-seite es-seite hz-winkel deltaL) + (gefaellestrecke-einfuegen L_stau (fix winkel) startpunkt-fuer-einfuegen as-seite es-seite hz-winkel deltaL gf-as-winkel gf-es-winkel) (princ (ssg-text "gf-status-abgebrochen")) ) ) diff --git a/Lisp/KreiselInsert.lsp b/Lisp/KreiselInsert.lsp index 1960aa5..cb8c6a3 100644 --- a/Lisp/KreiselInsert.lsp +++ b/Lisp/KreiselInsert.lsp @@ -73,11 +73,11 @@ ;; Attribut-Definitionen: ((TAG DEFAULT) ...) (setq *kreisel-attrib-defs* '(("NAME" "") - ("DREHRICHTUNG" "UZS") - ("N_SEPARATOREN" "2") - ("KREISELART" "STANDARD") - ("N_SCANNER" "0") - ("N_RAMPEN" "0") + ("DREHRICHTUNG" "UZS") + ("ANZAHL_SEPARATOR" "2") + ("KREISELART" "STANDARD") + ("ANZAHL_SCANNER" "0") + ("N_RAMPEN" "0") ("DREHUNG" "0") ("ABSTAND" "2300") ("NUMMER" "0") diff --git a/Lisp/count_sep_scan.lsp b/Lisp/count_sep_scan.lsp new file mode 100644 index 0000000..c5acbc0 --- /dev/null +++ b/Lisp/count_sep_scan.lsp @@ -0,0 +1,356 @@ +;;; ============================================================ +;;; count_sep_scan.lsp - Eigenstaendiges Zaehl-Werkzeug +;;; ------------------------------------------------------------ +;;; Zaehlt die manuell platzierten Bloecke "Scanner" und +;;; "Separator_SP" je ILS-Block (Kreisel / VarioFoerderer / +;;; Gefaellestrecke) und schreibt die Anzahlen in die Attribute +;;; des jeweiligen ILS-Blocks. Anschliessend QSAVE. +;;; +;;; Zuordnung eines Sensors zu einem ILS-Block: +;;; - Der geometrische Mittelpunkt (X/Y) des Sensors (Zentrum +;;; seiner eigenen Bounding-Box, unabhaengig vom $INSBASE) +;;; muss in der achsparallelen Bounding-Box des ILS-Blocks +;;; liegen (Z wird ignoriert). +;;; - Liegt der Punkt in mehreren Boxen (Ueberlappung), wird +;;; der ILS-Block mit dem naechstgelegenen Box-Zentrum gewaehlt. +;;; - Ein optionaler Toleranzrand (*senstol*) vergroessert die +;;; Box, damit Sensoren knapp am Rand noch erfasst werden. +;;; +;;; Aufruf: ZAEHLE_SEP_SCAN +;;; Laden: per APPLOAD oder (load "count_sep_scan") +;;; ============================================================ + +(vl-load-com) + +;;; ------------------------------------------------------------ +;;; KONFIGURATION (bei Bedarf anpassen) +;;; ------------------------------------------------------------ + +;; Toleranzrand um die ILS-Block-Box in Zeichnungseinheiten (mm). +;; 0.0 = exakte Box. Groesserer Wert = Sensoren knapp ausserhalb +;; werden noch dem ILS-Block zugerechnet. +(if (null *senstol*) (setq *senstol* 0.0)) + +;; Blockname-Muster (wcmatch, Grossschreibung) fuer die manuell +;; platzierten Sensoren. +(setq *scanner-pattern* "SCANNER,SCANNER_*") +(setq *separator-pattern* "SEPARATOR_SP,SEPARATOR_SP_*") + +;; Blockname-Muster fuer die INTERN im ILS-Block (in der Kette) +;; gezeichneten Separatoren. Diese bilden die Basis fuer +;; ANZAHL_SEPARATOR und werden bei jedem Lauf frisch gezaehlt. +(setq *intern-separator-pattern* "STAUSTRECKE_SEPARATOR_SP*") + +;;; ------------------------------------------------------------ +;;; ILS-Block-Erkennung und zugehoerige Attribut-Tags +;;; Alle ILS-Bloecke (Kreisel / VF / GF) verwenden einheitlich +;;; ANZAHL_SCANNER und ANZAHL_SEPARATOR. +;;; Rueckgabe: (scanner-tag separator-tag) oder nil +;;; ------------------------------------------------------------ +(defun cs-carrier-tags (nm / up) + (setq up (strcase nm)) + (if (or (wcmatch up "KREISEL_*") + (wcmatch up "VF_*") + (wcmatch up "GF_*")) + (list "ANZAHL_SCANNER" "ANZAHL_SEPARATOR") + nil + ) +) + +;;; ------------------------------------------------------------ +;;; Sensor-Erkennung +;;; Rueckgabe: 'scanner | 'separator | nil +;;; ------------------------------------------------------------ +(defun cs-sensor-kind (nm / up) + (setq up (strcase nm)) + (cond + ((wcmatch up *scanner-pattern*) 'scanner) + ((wcmatch up *separator-pattern*) 'separator) + (t nil) + ) +) + +;;; ------------------------------------------------------------ +;;; Punkt-Rueckgabe von vla-getboundingbox in eine Liste wandeln. +;;; BricsCAD liefert ein Safearray, AutoCAD ein Variant mit +;;; Safearray - beide Faelle werden hier abgedeckt. +;;; ------------------------------------------------------------ +(defun cs-pt->list (v) + (cond + ((= (type v) 'variant) (vlax-safearray->list (vlax-variant-value v))) + ((= (type v) 'safearray) (vlax-safearray->list v)) + ((listp v) v) + (t nil) + ) +) + +;;; ------------------------------------------------------------ +;;; Bounding-Box eines INSERT holen (WCS, achsparallel). +;;; Erzwingt vorher ein vla-update (Regen des Objekts), da +;;; vla-getboundingbox sonst auf nicht-regenerierten oder auf +;;; eingefrorenen Layern liegenden Bloecken scheitert. +;;; Rueckgabe: (minx miny maxx maxy) oder nil bei Fehler. +;;; *cs-last-error* enthaelt bei Fehler den echten COM-Fehlertext. +;;; ------------------------------------------------------------ +(defun cs-bbox (ent / obj res) + (setq *cs-last-error* nil) + (setq obj (vlax-ename->vla-object ent)) + ;; Objekt regenerieren (Fehler ignorieren) + (vl-catch-all-apply 'vla-update (list obj)) + (setq res + (vl-catch-all-apply + '(lambda ( / ll ur) + (vla-getboundingbox obj 'll 'ur) + (list (cs-pt->list ll) (cs-pt->list ur))))) + (if (vl-catch-all-error-p res) + (progn + (setq *cs-last-error* (vl-catch-all-error-message res)) + nil) + (list (car (car res)) (cadr (car res)) ;; minx miny + (car (cadr res)) (cadr (cadr res))) ;; maxx maxy + ) +) + +;;; ------------------------------------------------------------ +;;; Referenzpunkt eines Sensors: geometrischer Mittelpunkt seiner +;;; eigenen Bounding-Box (X/Y). Unabhaengig vom $INSBASE der +;;; Sensor-DWG. Fallback auf den Einfuegepunkt, falls die Box +;;; nicht ermittelbar ist. +;;; ------------------------------------------------------------ +(defun cs-sensor-point (ent ed / bb) + (setq bb (cs-bbox ent)) + (if bb + (list (/ (+ (nth 0 bb) (nth 2 bb)) 2.0) + (/ (+ (nth 1 bb) (nth 3 bb)) 2.0)) + (cdr (assoc 10 ed)) + ) +) + +;;; ------------------------------------------------------------ +;;; Liegt Punkt pt (X/Y) in ILS-Block-Record rec (mit Toleranz)? +;;; rec = (ename tagScan tagSep minx miny maxx maxy cx cy) +;;; ------------------------------------------------------------ +(defun cs-in-box (pt rec / x y) + (setq x (car pt) y (cadr pt)) + (and (>= x (- (nth 3 rec) *senstol*)) + (<= x (+ (nth 5 rec) *senstol*)) + (>= y (- (nth 4 rec) *senstol*)) + (<= y (+ (nth 6 rec) *senstol*))) +) + +;;; ------------------------------------------------------------ +;;; Sensor-Punkt einem ILS-Block zuordnen. +;;; Rueckgabe: ILS-Block-Record oder nil (keine Zuordnung). +;;; Bei mehreren Treffern: naechstes Box-Zentrum (X/Y). +;;; ------------------------------------------------------------ +(defun cs-assign (pt carriers / matches best bestd d) + (setq matches nil) + (foreach rec carriers + (if (cs-in-box pt rec) (setq matches (cons rec matches)))) + (cond + ((null matches) nil) + ((= (length matches) 1) (car matches)) + (t + (setq best nil bestd nil) + (foreach rec matches + (setq d (distance (list (car pt) (cadr pt)) + (list (nth 7 rec) (nth 8 rec)))) + (if (or (null bestd) (< d bestd)) + (setq bestd d best rec))) + best) + ) +) + +;;; ------------------------------------------------------------ +;;; Zaehler in einer Assoc-Liste (Schluessel = ename) erhoehen. +;;; ------------------------------------------------------------ +(defun cs-bump (alist key / pair) + (setq pair (assoc key alist)) + (if pair + (subst (cons key (1+ (cdr pair))) pair alist) + (cons (cons key 1) alist)) +) + +;;; ------------------------------------------------------------ +;;; Attributwert an einem INSERT setzen (Tag ohne Gross/Klein). +;;; Rueckgabe: T wenn Tag gefunden und gesetzt, sonst nil. +;;; ------------------------------------------------------------ +(defun cs-set-att (ins tag val / e ed done ok) + (setq e (entnext ins) done nil ok nil) + (while (and e (not done)) + (setq ed (entget e)) + (cond + ((= (cdr (assoc 0 ed)) "ATTRIB") + (if (= (strcase (cdr (assoc 2 ed))) (strcase tag)) + (progn + (entmod (subst (cons 1 val) (assoc 1 ed) ed)) + (entupd e) + (setq ok t)))) + ((= (cdr (assoc 0 ed)) "SEQEND") (setq done t)) + ) + (setq e (entnext e)) + ) + ok +) + +;;; ------------------------------------------------------------ +;;; Zaehlt rekursiv, wie oft ein Block (Muster pat) in der +;;; Definition des Blocks bname verwendet wird - inklusive +;;; verschachtelter Unterbloecke. Damit wird die interne Anzahl +;;; der Kette-Separatoren aus der Geometrie ermittelt. +;;; ------------------------------------------------------------ +(defun cs-count-nested (bname pat / e ed nm n stop) + (setq n 0 stop nil) + (setq e (tblobjname "BLOCK" bname)) + (if e + (progn + (setq e (entnext e)) + (while (and e (not stop)) + (setq ed (entget e)) + (cond + ((= (cdr (assoc 0 ed)) "ENDBLK") (setq stop t)) + ((= (cdr (assoc 0 ed)) "INSERT") + (setq nm (cdr (assoc 2 ed))) + (if (wcmatch (strcase nm) pat) + (setq n (1+ n)) + (setq n (+ n (cs-count-nested nm pat))))) + ) + (if (not stop) (setq e (entnext e))) + ) + ) + ) + n +) + +;;; ============================================================ +;;; HAUPTBEFEHL +;;; ============================================================ +(defun c:ZAEHLE_SEP_SCAN ( / ss i ent ed nm tags bb cx cy + carriers scanpts seppts kind pt + scanCounts sepCounts + rec en nm2 ns nsep internSep newS newP + n-scan-unassigned n-sep-unassigned + okS okP) + (setq carriers nil scanpts nil seppts nil) + + ;; Vor der Extents-Berechnung einmal regenerieren, damit + ;; vla-getboundingbox verlaessliche Werte liefert. + (vl-catch-all-apply '(lambda () (command "_.REGEN"))) + + ;; --- 1. Alle INSERTs sammeln und klassifizieren --- + (setq ss (ssget "_X" '((0 . "INSERT")))) + (if (null ss) + (progn (princ "\n>>> Keine Bloecke in der Zeichnung gefunden.") (princ)) + (progn + (setq i 0) + (while (< i (sslength ss)) + (setq ent (ssname ss i)) + (setq ed (entget ent)) + (setq nm (cdr (assoc 2 ed))) + (setq tags (cs-carrier-tags nm)) + (cond + ;; --- ILS-Block --- + (tags + (setq bb (cs-bbox ent)) + (if bb + (progn + (setq cx (/ (+ (nth 0 bb) (nth 2 bb)) 2.0)) + (setq cy (/ (+ (nth 1 bb) (nth 3 bb)) 2.0)) + (setq carriers + (cons (list ent (car tags) (cadr tags) + (nth 0 bb) (nth 1 bb) (nth 2 bb) (nth 3 bb) + cx cy) + carriers))) + (princ (strcat "\n! Bounding-Box fehlgeschlagen fuer ILS-Block '" nm "'" + (if *cs-last-error* + (strcat " (" *cs-last-error* ")") ""))) + ) + ) + ;; --- Sensor --- + (t + (setq kind (cs-sensor-kind nm)) + (if kind + (progn + (setq pt (cs-sensor-point ent ed)) + (if (eq kind 'scanner) + (setq scanpts (cons pt scanpts)) + (setq seppts (cons pt seppts))))) + ) + ) + (setq i (1+ i)) + ) + + ;; --- 2. Sensoren den ILS-Bloecken zuordnen --- + (setq scanCounts nil sepCounts nil + n-scan-unassigned 0 n-sep-unassigned 0) + + (foreach pt scanpts + (setq rec (cs-assign pt carriers)) + (if rec + (setq scanCounts (cs-bump scanCounts (car rec))) + (setq n-scan-unassigned (1+ n-scan-unassigned)))) + + (foreach pt seppts + (setq rec (cs-assign pt carriers)) + (if rec + (setq sepCounts (cs-bump sepCounts (car rec))) + (setq n-sep-unassigned (1+ n-sep-unassigned)))) + + ;; --- 3. Attribute setzen und Bericht ausgeben --- + (princ "\n============================================") + (princ "\n Sensor-Zaehlung je ILS-Block") + (princ "\n============================================") + (if (null carriers) + (princ "\n (Keine ILS-Bloecke KREISEL_/VF_/GF_ gefunden)") + (foreach rec carriers + (setq en (car rec)) + (setq nm2 (cdr (assoc 2 (entget en)))) + (setq ns (cond ((assoc en scanCounts) (cdr (assoc en scanCounts))) (t 0))) + (setq nsep (cond ((assoc en sepCounts) (cdr (assoc en sepCounts))) (t 0))) + ;; interne Kette-Separatoren frisch aus der Blockdefinition zaehlen + (setq internSep (cs-count-nested nm2 *intern-separator-pattern*)) + ;; Scanner = nur neu zugeordnete + ;; Separator = interne Kette-Separatoren + neu zugeordnete + (setq newS ns) + (setq newP (+ internSep nsep)) + ;; gefundene Anzahlen melden + (princ (strcat "\n " nm2 + ": Scanner(neu)=" (itoa ns) + ", Separator intern=" (itoa internSep) + " + manuell=" (itoa nsep))) + ;; Werte schreiben und Ergebnis bestaetigen + (setq okS (cs-set-att en (nth 1 rec) (itoa newS))) + (setq okP (cs-set-att en (nth 2 rec) (itoa newP))) + (if (and okS okP) + (princ (strcat "\n -> geschrieben: " + "ANZAHL_SCANNER=" (itoa newS) + ", ANZAHL_SEPARATOR=" (itoa newP))) + (princ (strcat "\n -> FEHLER beim Schreiben: " + (if okS "" "ANZAHL_SCANNER fehlt ") + (if okP "" "ANZAHL_SEPARATOR fehlt")))) + ) + ) + (princ (strcat "\n--------------------------------------------" + "\n ILS-Bloecke gesamt: " (itoa (length carriers)) + "\n Scanner gesamt: " (itoa (length scanpts)) + " (nicht zugeordnet: " (itoa n-scan-unassigned) ")" + "\n Separator gesamt: " (itoa (length seppts)) + " (nicht zugeordnet: " (itoa n-sep-unassigned) ")")) + (if (or (> n-scan-unassigned 0) (> n-sep-unassigned 0)) + (princ (strcat "\n Hinweis: Nicht zugeordnete Sensoren liegen" + "\n ausserhalb aller ILS-Block-Boxen. Ggf. *senstol*" + "\n erhoehen oder Platzierung pruefen."))) + + ;; --- 4. Zeichnung speichern --- + (princ "\n--------------------------------------------") + (princ "\n Speichere Zeichnung (QSAVE) ...") + (command "_.QSAVE") + (princ "\n>>> Fertig.") + (princ) + ) + ) + (princ) +) + +(princ "\n>>> count_sep_scan.lsp geladen. Befehl: ZAEHLE_SEP_SCAN") +(princ) diff --git a/Lisp/vf_core.lsp b/Lisp/vf_core.lsp index 8fc0274..c2570a5 100644 --- a/Lisp/vf_core.lsp +++ b/Lisp/vf_core.lsp @@ -680,6 +680,88 @@ (rtos (car P-out) 2 2) (rtos (cadr P-out) 2 2) (rtos (caddr P-out) 2 2)))) (list P-out xu-out yu-out zu-out)) +;; ============================================================ +;; AS-/ES-Element: Winkel-Variante (30 / 90 Grad) +;; ============================================================ +;; Masse (KS_EIN->KS_AUS als (dx dy dz)) eines Elements aus dem Block ziehen. +(defun vf-element-masse (blockname / temp-obj ks-data ke ka) + (ensure-block-loaded blockname) + (if (not (tblsearch "BLOCK" blockname)) + nil + (progn + (setq temp-obj (vla-InsertBlock modelspace + (vlax-3D-point '(0 0 0)) blockname 1.0 1.0 1.0 0)) + (setq ks-data (extract-ks-from-block temp-obj)) + (if (not (vlax-erased-p temp-obj)) (vla-Delete temp-obj)) + (setq ke (cadr (assoc "KS_EIN" ks-data)) ka (cadr (assoc "KS_AUS" ks-data))) + (if (and ke ka) + (list (- (caar ka) (caar ke)) + (- (cadr (car ka)) (cadr (car ke))) + (- (caddr (car ka)) (caddr (car ke)))) + nil)))) + +;; Ebenen-Schwenk (Grad) eines Elements: Winkel von KS_EIN.xu nach KS_AUS.xu in +;; der XY-Ebene, direkt aus dem Block gemessen (unabhaengig davon, ob 30/90-Turn +;; oder gerade). Wird genutzt, um KS_EIN so zu drehen, dass KS_AUS entlang hz +;; zeigt: ein-hz = hz - plan-turn. Fallback 90 (Vorgabe fuer 90-Grad-Element). +(defun vf-element-plan-turn (blockname / temp-obj ks-data fe fa) + (ensure-block-loaded blockname) + (if (not (tblsearch "BLOCK" blockname)) + 90.0 + (progn + (setq temp-obj (vla-InsertBlock modelspace + (vlax-3D-point '(0 0 0)) blockname 1.0 1.0 1.0 0)) + (setq ks-data (extract-ks-from-block temp-obj)) + (if (not (vlax-erased-p temp-obj)) (vla-Delete temp-obj)) + (setq fe (ks-frame-extract (cadr (assoc "KS_EIN" ks-data)))) + (setq fa (ks-frame-extract (cadr (assoc "KS_AUS" ks-data)))) + (if (and fe fa) + (- (* (atan (cadr (cadr fa)) (car (cadr fa))) (/ 180.0 pi)) + (* (atan (cadr (cadr fe)) (car (cadr fe))) (/ 180.0 pi))) + 90.0)))) + +;; KS-Info eines Elements (im Block, Einbau-Rotation 0): +;; Rueckgabe (ein-ang aus-ang vx vy) +;; ein-ang / aus-ang : Ebenen-Winkel (Grad) von KS_EIN.xu bzw. KS_AUS.xu +;; vx / vy : KS_AUS.origin - KS_EIN.origin (Block-XY) +;; Damit laesst sich die KS_AUS-Lage + Achsrichtung analytisch vorausberechnen. +(defun vf-element-ks-info (blockname / temp-obj ks-data fe fa) + (ensure-block-loaded blockname) + (if (not (tblsearch "BLOCK" blockname)) + nil + (progn + (setq temp-obj (vla-InsertBlock modelspace + (vlax-3D-point '(0 0 0)) blockname 1.0 1.0 1.0 0)) + (setq ks-data (extract-ks-from-block temp-obj)) + (if (not (vlax-erased-p temp-obj)) (vla-Delete temp-obj)) + (setq fe (ks-frame-extract (cadr (assoc "KS_EIN" ks-data)))) + (setq fa (ks-frame-extract (cadr (assoc "KS_AUS" ks-data)))) + (if (and fe fa) + (list (* (atan (cadr (cadr fe)) (car (cadr fe))) (/ 180.0 pi)) + (* (atan (cadr (cadr fa)) (car (cadr fa))) (/ 180.0 pi)) + (- (car (car fa)) (car (car fe))) + (- (cadr (car fa)) (cadr (car fe)))) + nil)))) + +;; 30/90-Wahl fuer ein AS/ES-Element. Rueckgabe: "30" oder "90" (Vorgabe 90). +;; headerkey: i18n-Key fuer die Kopfzeile ("vf-winkel-aus-header" oder +;; "vf-winkel-ein-header"). Rueckgabe: "30" oder "90" (Default 90). +(defun vf-frage-element-winkel (headerkey) + (princ (ssg-text headerkey)) + (princ (ssg-text "vf-winkel-90")) + (princ (ssg-text "vf-winkel-30")) + (if (= (getstring (ssg-text "prompt-wahl-1-2")) "2") "30" "90")) + +;; AS-/ES-Masse-Globals (aus-dx/dy/dz bzw. ein-dx/dy/dz) fuer die gewaehlte +;; Winkel-/Seiten-Variante neu setzen (fuer berechne-alle-winkel, GF-Winkel, +;; Trimmungen). Ohne gueltigen Block bleiben die Werte unveraendert. +(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)))) + ;; hz: horizontale Fahrtrichtung in Grad (0=Ost/+X, 90=Nord/+Y, ...), ;; winkel: vertikale Neigung in Grad. Rotation = Rz(hz)*Ry(winkel). (defun insert-inclined-scaled-block (blockname startpunkt laenge winkel hz / @@ -1159,7 +1241,17 @@ ;; vf_linienzug.lsp. Dispatcht hier direkt und beendet den Standard-Ablauf. (if (= anlage-typ "linienzug") (progn - (vf-linienzug-modus) + (princ "\n\nLinienzug-Modus waehlen:") + (princ "\n 1 - Manuelle Eingabe (Punkte klicken)") + (princ "\n 2 - 3D-Objekte + Ziel-Hoehe (Randwert-Solver, fertig)") + (princ "\n 3 - 3D-Objekte stueckweise (Vorwaerts-Nachbau, in Arbeit)") + (setq wahl (getint "\nIhre Wahl (1/2/3) [1]: ")) + ;; Konsolen-Nummer -> interne Funktion: + ;; 2 = fertiger Ziel-Hoehe-Modus (intern vf-linienzug-modus3) + ;; 3 = alter Vorwaerts-Nachbau (intern vf-linienzug-modus2, wird umgebaut) + (cond ((= wahl 2) (vf-linienzug-modus3)) + ((= wahl 3) (vf-linienzug-modus2)) + (t (vf-linienzug-modus))) (exit) ) ) diff --git a/Lisp/vf_linienzug.lsp b/Lisp/vf_linienzug.lsp index 8da5240..442cb00 100644 --- a/Lisp/vf_linienzug.lsp +++ b/Lisp/vf_linienzug.lsp @@ -106,6 +106,11 @@ (if first (progn (setq s x) (setq first nil)) (setq s (strcat s "," x)))) s) +;; Gewaehlte AS-/ES-Winkelvariante ("30"/"90"). Vorgabe "90", solange der Modus +;; nichts anderes gesetzt hat (Blocknamen AS_Element__). +(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")) + ;; ============================================================ ;; TEIL 1: SEGMENT-ENTSCHEIDUNG (GF oder VF) ;; ============================================================ @@ -625,7 +630,21 @@ ;; GF-Bogen (horizontale Kurve, Neigung bleibt wie im aktuellen Frame). ;; Fragt Winkel (30/60/90) und Seite interaktiv ab. -(defun vfl-insert-gf-bogen (frame / bwinkel bseite antwort blockname) +;; Nicht-interaktiver Kern: GF-Bogen mit gegebenem Winkel/Seite einfuegen. +;; Genutzt von der interaktiven Abfrage UND von den Pfad-Modi (Winkel/Seite +;; aus der Geometrie). Rueckgabe: neuer Frame. +(defun vfl-insert-gf-bogen-block (frame bwinkel bseite / blockname) + (setq blockname (gf-bogen-blockname bwinkel bseite)) + (princ (ssg-textf "vfl-fuege-block-ein" (list blockname))) + ;; GF-Bogen zaehlen (Seite L/R + Winkel) -> Attribut GF_Bogen_L/R_xx + (setq *vfl-acc-gfbogen* + (vfl-inc-count *vfl-acc-gfbogen* + (strcat (if (= bseite "rechts") "R" "L") "_" (itoa bwinkel)))) + (insert-block-ks-to-ks blockname frame) +) + +;; Interaktiv (i18n): GF-Bogen-Winkel + Seite abfragen, dann Kern aufrufen. +(defun vfl-insert-gf-bogen (frame / bwinkel bseite antwort) (princ (ssg-text "vfl-gf-bogen-winkel-header")) (princ (ssg-text "vfl-winkel-30")) (princ (ssg-text "vfl-winkel-60")) @@ -638,13 +657,7 @@ (princ (ssg-text "gf-seite-rechts")) (setq antwort (getstring (ssg-text "prompt-wahl-1-2"))) (setq bseite (if (= antwort "2") "rechts" "links")) - (setq blockname (gf-bogen-blockname bwinkel bseite)) - (princ (ssg-textf "vfl-fuege-block-ein" (list blockname))) - ;; GF-Bogen zaehlen (Seite L/R + Winkel) -> Attribut GF_Bogen_L/R_xx - (setq *vfl-acc-gfbogen* - (vfl-inc-count *vfl-acc-gfbogen* - (strcat (if (= bseite "rechts") "R" "L") "_" (itoa bwinkel)))) - (insert-block-ks-to-ks blockname frame) + (vfl-insert-gf-bogen-block frame bwinkel bseite) ) ;; Vario-Kurve-Blockname (links/rechts x 30/60/90 x TEF aussen/innen). @@ -652,11 +665,31 @@ (strcat "Vario_Kurve_" kseite "_" (itoa kwinkel) "_TEF_" kvariante) ) +;; Nicht-interaktiver Kern: Vario-Kurve mit gegebenem Winkel/Seite/Variante +;; einfuegen. Genutzt von der interaktiven Abfrage UND von den Pfad-Modi +;; (Winkel/Seite aus der Geometrie, Variante abgefragt). Rueckgabe: neuer Frame. +;; Die Vario_Kurve-Bloecke fuehren KS_EIN/KS_AUS als KSYS_EIN/KSYS_AUS - +;; extract-ks-from-block-raw normalisiert das (siehe ks-normalize-name). +(defun vfl-insert-vario-kurve-block (frame kwinkel kseite kvariante / blockname hz flach-frame) + (setq blockname (vfl-kurve-blockname kwinkel kseite kvariante)) + (princ (ssg-textf "vfl-fuege-block-ein" (list blockname))) + ;; Vario-Kurve zaehlen (Variante A=aussen/I=innen + Winkel) -> VF_Bogen_A/I_xx + (setq *vfl-acc-variokurve* + (vfl-inc-count *vfl-acc-variokurve* + (strcat (if (= kvariante "aussen") "A" "I") "_" (itoa kwinkel)))) + ;; Vario-Kurve ist ein HORIZONTALER Richtungswechsel (KEINE Neigung). + ;; Der aktuelle Frame traegt die 3-Grad-Basisneigung - fuer die Kurve wird er + ;; daher auf 0 Grad abgeflacht (Position + Fahrtrichtung bleiben erhalten). + (setq hz (car (frame->hz-winkel frame))) + (setq flach-frame (make-frame-from-dir (car frame) (hz-winkel->xu hz 0.0))) + (insert-block-ks-to-ks blockname flach-frame) +) + ;; Vario-Kurve (horizontale Kurve, Neigung bleibt wie im aktuellen Frame). ;; Die Vario_Kurve-Bloecke fuehren KS_EIN/KS_AUS als KSYS_EIN/KSYS_AUS - ;; extract-ks-from-block-raw normalisiert das (siehe ks-normalize-name). -(defun vfl-insert-vario-kurve (frame / kwinkel kseite kvariante antwort blockname - hz flach-frame) +;; Interaktiv (i18n): Vario-Kurve-Winkel + Seite + Variante abfragen, dann Kern. +(defun vfl-insert-vario-kurve (frame / kwinkel kseite kvariante antwort) (princ (ssg-text "vfl-variokurve-winkel-header")) (princ (ssg-text "vfl-opt1-90grad")) (princ (ssg-text "vfl-opt2-60grad")) @@ -674,18 +707,7 @@ (princ (ssg-text "vfl-variante-innen")) (setq antwort (getstring (ssg-text "vfl-prompt-wahl-1-2-def2"))) (setq kvariante (if (= antwort "1") "aussen" "innen")) - (setq blockname (vfl-kurve-blockname kwinkel kseite kvariante)) - (princ (ssg-textf "vfl-fuege-block-ein" (list blockname))) - ;; Vario-Kurve zaehlen (Variante A=aussen/I=innen + Winkel) -> VF_Bogen_A/I_xx - (setq *vfl-acc-variokurve* - (vfl-inc-count *vfl-acc-variokurve* - (strcat (if (= kvariante "aussen") "A" "I") "_" (itoa kwinkel)))) - ;; Vario-Kurve ist ein HORIZONTALER Richtungswechsel (KEINE Neigung). - ;; Der aktuelle Frame traegt die 3-Grad-Basisneigung - fuer die Kurve wird er - ;; daher auf 0 Grad abgeflacht (Position + Fahrtrichtung bleiben erhalten). - (setq hz (car (frame->hz-winkel frame))) - (setq flach-frame (make-frame-from-dir (car frame) (hz-winkel->xu hz 0.0))) - (insert-block-ks-to-ks blockname flach-frame) + (vfl-insert-vario-kurve-block frame kwinkel kseite kvariante) ) ;; ============================================================ @@ -698,17 +720,19 @@ ;; Neigung), wie im Standard-VarioFoerderer (vfs-as-teil). ;; Rueckgabe: Frame am AS-Ausgang (KS_AUS). (defun vfl-insert-as-element (typ startpunkt hz winkel as-seite / - ein-hz-as rad-h rad-v xu-ein) + ein-hz-as rad-h rad-v xu-ein as-turn) (if (= typ "GF") (progn - (setq ein-hz-as (if (= as-seite "rechts") (+ hz 90.0) (- hz 90.0))) + ;; Schwenk aus dem Block messen -> KS_EIN so drehen, dass KS_AUS entlang hz + (setq as-turn (vf-element-plan-turn (strcat "AS_Element_" (vfl-as-winkel) "_" as-seite))) + (setq ein-hz-as (- hz as-turn)) (setq rad-h (* (float ein-hz-as) (/ pi 180.0))) (setq rad-v (* (float winkel) (/ pi 180.0))) (setq xu-ein (list (* (cos rad-h)(cos rad-v)) (* (sin rad-h)(cos rad-v)) (- (sin rad-v)))) (insert-block-mixed-to-ks - (strcat "AS_Element_90_" as-seite) + (strcat "AS_Element_" (vfl-as-winkel) "_" as-seite) (make-frame-from-dir startpunkt xu-ein) (caddr startpunkt) nil "KS_EIN") ) @@ -716,13 +740,17 @@ ) ) -;; ES-Seite erst am Kettenende abfragen (Links/Rechts). -(defun vfl-frage-es-seite ( / antwort) +;; ES-Winkel (30/90) + Seite erst am Kettenende abfragen. Setzt *vfl-es-winkel* +;; und die ES-Masse (ein-dx/dz) fuer die Variante. Rueckgabe: Seite (links/rechts). +(defun vfl-frage-es-seite ( / antwort es-seite) + (setq *vfl-es-winkel* (vf-frage-element-winkel "vf-winkel-ein-header")) ; 30/90 vor Seite (princ (ssg-text "gf-seite-ein-header")) (princ (ssg-text "gf-seite-links")) (princ (ssg-text "gf-seite-rechts")) (setq antwort (getstring (ssg-text "prompt-wahl-1-2"))) - (if (= antwort "2") "rechts" "links") + (setq es-seite (if (= antwort "2") "rechts" "links")) + (vf-set-es-masse *vfl-es-winkel* es-seite) + es-seite ) ;; Separator (300mm) an der aktuellen Stelle einfuegen (bei 3-Grad-Neigung, @@ -754,7 +782,7 @@ (setq sep-frame (make-frame-from-dir sep-endpunkt (hz-winkel->xu hz w-eff))) (if (boundp '*vfl-acc-separator*) (setq *vfl-acc-separator* (1+ *vfl-acc-separator*))) ; Separator vor ES - (insert-block-ks-to-ks (strcat "ES_Element_90_" es-seite) sep-frame) + (insert-block-ks-to-ks (strcat "ES_Element_" (vfl-es-winkel) "_" es-seite) sep-frame) ) ;; ============================================================ @@ -948,10 +976,12 @@ (if (null start-hoehe) (setq start-hoehe (caddr startpunkt))) (setq startpunkt (list (car startpunkt) (cadr startpunkt) start-hoehe)) - (princ "\n\nAUS-Element (AS_Element_90_*) - Seite waehlen:") + (setq *vfl-as-winkel* (vf-frage-element-winkel "vf-winkel-aus-header")) ; 30/90 vor Seite + (princ "\n\nAUS-Element (AS_Element_*) - Seite waehlen:") (princ "\n 1 - Links\n 2 - Rechts") (setq antwort (getstring "\nIhre Wahl (1/2) [1]: ")) (setq as-seite (if (= antwort "2") "rechts" "links")) + (vf-set-as-masse *vfl-as-winkel* as-seite) ; Masse fuer Variante ;; ES-Seite wird erst am Kettenende abgefragt (siehe vfl-frage-es-seite), ;; da das ES-Element erst dort eingefuegt wird. @@ -1238,11 +1268,979 @@ (princ "\n[vf_linienzug] FEHLER: einfuege-fn sollte fuer Typ 'linienzug' nie aufgerufen werden.") startpunkt ) +;; ============================================================ +;; MODUS 2: 3D-OBJEKTE WAEHLEN (Meilenstein 1 - GF-Teil) +;; ============================================================ +;; Baut die Kette aus einem GEZEICHNETEN Pfad (LINE/ARC). Ablauf: +;; Objekte waehlen -> gf-sortiere-objekte -> gf-analysiere-kette +;; -> Eck-Winkel-Vorpruefung -> segmentweise (Variante B) live bauen -> ES. +;; In diesem Meilenstein ist NUR der GF-Teil aktiv (GF-Gerade mit eigener +;; Neigung <=3 Grad, GF-Bogen). Die VF-Zweige sind als TODO (Meilenstein 2) +;; markiert und werden uebersprungen. +;; Bekannte Naeherungen (in BricsCAD verifizieren): +;; - Eck-Trimmung nur an erster/letzter Geraden (-aus-dx bzw. -300-ein-dx, +;; wie gf-linienzug-modus); Separatoren VOR GF-Boegen noch NICHT gesetzt. +;; - GF-Bogen hat feste Eigen-Neigung -> bei unterschiedlichen Nachbar- +;; Neigungen entsteht ein (akzeptierter) Knick. + +;; Alle Eck-Winkel des Pfades gegen 30/60/90 Grad pruefen (Toleranz tol). +;; Rueckgabe: nil (alle ok) oder der erste abweichende Winkel (Grad). +(defun vfl2-pruefe-eckwinkel (kette tol / item obj sa ea sweep bad) + (setq bad nil) + (foreach item kette + (setq obj (car item)) + (if (and (null bad) (= (vla-get-ObjectName obj) "AcDbArc")) + (progn + (setq sa (vla-get-StartAngle obj) ea (vla-get-EndAngle obj)) + (setq sweep (if (>= ea sa) (- ea sa) (+ (- ea sa) (* 2.0 pi)))) + (setq sweep (* sweep (/ 180.0 pi))) + (if (not (or (< (abs (- sweep 30.0)) tol) + (< (abs (- sweep 60.0)) tol) + (< (abs (- sweep 90.0)) tol))) + (setq bad sweep)) + ) + ) + ) + bad +) + +;; AS-Element fuer Modus 2 (GF): gemischte Verankerung. +;; - Laengsrichtung (entlang hz): KS_EIN auf den Startpunkt (Laengsposition) +;; - Querrichtung (senkrecht hz): KS_AUS auf den Pfad (Kettenmittellinie) +;; Umsetzung: (1) AS mit KS_EIN am Startpunkt platzieren, (2) Querversatz des +;; KS_AUS zum Pfad messen, (3) AS senkrecht zu hz um -Versatz verschieben. +;; Ergebnis: KS_EIN.X = Startpunkt.X, KS_EIN quer um das AS-Y-Mass versetzt, +;; KS_AUS liegt exakt auf dem Pfad -> die ganze Kette folgt der Mittellinie. +;; Rueckgabe: (korrigierter) Frame am KS_AUS. +(defun vfl2-insert-as-gf (startpunkt hz winkel as-seite / + ein-hz-as rad-h rad-v xu-ein frame blk ks-aus + rad-hz perp-x perp-y d-perp sx sy as-turn + rad-ein de-x de-y de-dot-np t-shift) + ;; Schwenk aus dem AS-Block MESSEN (30/90/gerade), KS_EIN so drehen, dass + ;; KS_AUS entlang hz zeigt: ein-hz = hz - plan-turn. + (setq as-turn (vf-element-plan-turn (strcat "AS_Element_" (vfl-as-winkel) "_" as-seite))) + (setq ein-hz-as (- hz as-turn)) + (setq rad-h (* (float ein-hz-as) (/ pi 180.0))) + (setq rad-v (* (float winkel) (/ pi 180.0))) + (setq xu-ein (list (* (cos rad-h)(cos rad-v)) + (* (sin rad-h)(cos rad-v)) + (- (sin rad-v)))) + ;; (1) KS_EIN auf den Startpunkt (Laengsposition korrekt) + (setq frame (insert-block-mixed-to-ks + (strcat "AS_Element_" (vfl-as-winkel) "_" as-seite) + (make-frame-from-dir startpunkt xu-ein) + (caddr startpunkt) "KS_EIN" "KS_EIN")) + (setq blk (entlast)) + ;; (2) Querversatz des KS_AUS zum Pfad (Einheits-Querrichtung senkrecht zu hz) + (setq ks-aus (car frame)) + (setq rad-hz (* (float hz) (/ pi 180.0))) + (setq perp-x (- (sin rad-hz)) perp-y (cos rad-hz)) ; Normale zum Pfad + (setq d-perp (+ (* (- (car ks-aus) (car startpunkt)) perp-x) + (* (- (cadr ks-aus) (cadr startpunkt)) perp-y))) + ;; (3) AS ENTLANG der KS_EIN-Achse verschieben (nicht senkrecht zum Pfad): + ;; damit landet KS_AUS auf dem Pfad UND der Startpunkt bleibt auf der KS_EIN- + ;; Achse. Beim 90-Grad-Element ist die KS_EIN-Achse senkrecht zum Pfad -> das + ;; ist identisch zum bisherigen Querschub. + (setq rad-ein (* (float ein-hz-as) (/ pi 180.0))) + (setq de-x (cos rad-ein) de-y (sin rad-ein)) ; KS_EIN-Achsrichtung + (setq de-dot-np (+ (* de-x perp-x) (* de-y perp-y))) + (setq t-shift (if (> (abs de-dot-np) 1e-6) (/ d-perp de-dot-np) 0.0)) + (setq sx (* (- t-shift) de-x) sy (* (- t-shift) de-y)) + (if (> (abs t-shift) 1e-6) + (vla-Move (vlax-ename->vla-object blk) + (vlax-3D-point '(0.0 0.0 0.0)) (vlax-3D-point (list sx sy 0.0)))) + ;; (4) korrigierten KS_AUS-Frame zurueckgeben (Richtung unveraendert) + (list (list (+ (car ks-aus) sx) (+ (cadr ks-aus) sy) (caddr ks-aus)) + (cadr frame) (caddr frame) (cadddr frame)) +) + +;; VF-Start-AS (Modus 2): identische Platzierung wie beim GF-Start, aber FLACH +;; (0 Grad Neigung). Das AS wird mit KS_EIN auf den Startpunkt gesetzt und dann +;; senkrecht zu hz auf den Pfad geschoben (reine KS-Kettung, KS_AUS auf Pfad). +;; Damit liegt auch eine mit VF beginnende Kette auf dem gezeichneten Pfad. +(defun vfl2-insert-as-vf (startpunkt hz as-seite) + (vfl2-insert-as-gf startpunkt hz 0.0 as-seite)) + +;; VF-Einheit OEFFNEN (Modus 2): GF1 (Basis-Staustrecke) + Separator + +;; Umlenkstation. Rueckgabe: Frame nach der Umlenkstation (Beginn reiner VF). +(defun vfl2-vf-open (frame hz / f) + (setq f (vfl-frame-3grad (vfs-vf-entry (car frame) *vfl-gf-min-laenge* hz) hz)) + (if (> *vfl-gf-min-laenge* 0.1) + (vfl-acc-gf-seg *vfl-gf-min-laenge* (ssg-cfg-or "vario" "gefaelle_winkel" 3))) + f) + +;; VF-Einheit SCHLIESSEN (Modus 2): Motorstation + GF2 (Basis), KEIN Separator +;; (der sitzt erst am Kettenende bzw. optional zwischen Foerderern). Rueckgabe: +;; Frame nach GF2. +(defun vfl2-vf-close (frame hz / f) + (setq *vfl-acc-motorseite* (append *vfl-acc-motorseite* (list "rechts"))) + (setq f (vfl-frame-3grad (vfs-vf-exit (car frame) *vfl-gf-min-laenge* hz nil) hz)) + (if (> *vfl-gf-min-laenge* 0.1) + (vfl-acc-gf-seg *vfl-gf-min-laenge* (ssg-cfg-or "vario" "gefaelle_winkel" 3))) + f) + +;; Vario-Winkel (Vertikalbogen) auf die verfuegbaren Werte 3..51 einrasten. +(defun vfl2-snap-vfwinkel (w / liste best bd d) + (setq liste (ssg-cfg-or "vario" "bogen_winkel" '(3 6 9 12 15 18 21 27 33 39 45 51))) + (setq best (car liste) bd 1e9) + (foreach x liste (setq d (abs (- x w))) (if (< d bd) (setq bd d best x))) + best) + +(defun vf-linienzug-modus2 ( / ss k obj-liste startpunkt start-hoehe as-seite + es-seite kette segmente n i seg typ hz laenge + bwinkel bseite frame antwort winkel deltaL + anzahl-gf anzahl-vf vfl-nummer lastEnt hoehe-bis + ecke-bad first-line last-line letzt-winkel + aus ein soll-ende ist-ende run-typ richtn best-w + kvariante L_VF ent-fp rad3 just-open) + (princ "\n\n=========================================") + (princ "\n VF-LINIENZUG - Modus 3: 3D-Objekte stueckweise (Vorwaerts-Nachbau, in Arbeit)") + (princ "\n=========================================") + + ;; Abhaengigkeit Gefaellestrecke-Modul (GF-Bausteine) + (if (null (car (atoms-family 1 '("GF-INSERT-HZ-INCL-SCALED")))) + (progn (alert "Gefaellestrecke-Modul nicht geladen!") (exit))) + (if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek)) + + ;; --- 1. Pfad-Objekte waehlen (LINE/ARC) --- + (princ "\n\nPfad-Objekte waehlen (Linien/Boegen):") + (setq ss (ssget '((0 . "LINE,ARC")))) + (if (null ss) (progn (princ "\nKeine Objekte gewaehlt - Abbruch.") (exit))) + (setq obj-liste '() k 0) + (repeat (sslength ss) + (setq obj-liste (cons (vlax-ename->vla-object (ssname ss k)) obj-liste)) + (setq k (1+ k))) + + ;; --- 2. Startpunkt (hoehere Seite) + Hoehe + AS-Seite --- + (setq startpunkt (vfl-getpoint nil "\n\nStartpunkt der Kette (hoehere Seite) waehlen: ")) + (if (null startpunkt) (progn (princ "\nAbgebrochen.") (exit))) + (setq start-hoehe (getreal (strcat "\nHoehe (Z) des Startpunkts [" + (rtos (caddr startpunkt) 2 1) "]: "))) + (if (null start-hoehe) (setq start-hoehe (caddr startpunkt))) + (setq startpunkt (list (car startpunkt) (cadr startpunkt) start-hoehe)) + (setq *vfl-as-winkel* (vf-frage-element-winkel "vf-winkel-aus-header")) ; 30/90 vor Seite + (princ "\n\nAUS-Element - Seite waehlen:\n 1 - Links\n 2 - Rechts") + (setq as-seite (if (= (getstring "\nIhre Wahl (1/2) [1]: ") "2") "rechts" "links")) + (vf-set-as-masse *vfl-as-winkel* as-seite) ; Masse fuer Variante + + ;; --- 3. Sortieren + Analysieren --- + (setq kette (gf-sortiere-objekte obj-liste startpunkt)) + (if (null kette) + (progn (princ "\nFEHLER: Pfad nicht sortierbar (haengt er lueckenlos zusammen?).") (exit))) + (setq segmente (gf-analysiere-kette kette)) + + ;; --- 4. Eck-Winkel-Vorpruefung (vor dem Bau) --- + (setq ecke-bad (vfl2-pruefe-eckwinkel kette 5.0)) + (if ecke-bad + (progn + (alert (strcat "Kein passender Bogen: Eck-Winkel " (rtos ecke-bad 2 1) + " Grad ist nicht ~30/60/90.\n" + "Bitte die Winkel der Linien anpassen.")) + (exit))) + + ;; --- 5. Setup --- + (setq aus (if aus-dx aus-dx 576.0) ein (if ein-dx ein-dx 576.0)) + (setq rad3 (* (float (ssg-cfg-or "vario" "gefaelle_winkel" 3)) (/ pi 180.0))) + ;; Entry-Fussabdruck (planar) einer VF-Einheit: GF1(Basis)+Separator(300)+Umlenk(500) + (setq ent-fp (* (+ *vfl-gf-min-laenge* 300.0 500.0) (cos rad3))) + (setq run-typ nil) + (setq vfl-nummer (vf-next-number)) + (setq lastEnt (vf-lastent-ohne-attribute)) + (vfl-acc-reset) + (setq anzahl-gf 0 anzahl-vf 0 frame nil n (length segmente)) + ;; erste/letzte "Linie" fuer die Trimmung bestimmen + (setq first-line -1 last-line -1 i 0) + (while (< i n) + (if (= (car (nth i segmente)) "Linie") + (progn (if (< first-line 0) (setq first-line i)) (setq last-line i))) + (setq i (1+ i))) + (if (< first-line 0) + (progn (alert "Pfad enthaelt keine Gerade - eine Kette muss mit einer Geraden beginnen.") (exit))) + + ;; --- 6. Bau-Schleife (Meilenstein 2: GF + VF-Laeufe, Run-State-Machine) --- + ;; run-typ: nil / "GF" / "VF". Beim Wechsel GF<->VF wird die VF-Einheit + ;; geoeffnet (GF1+Separator+Umlenk) bzw. geschlossen (Motor+GF2). + ;; v1-Naeherung Laenge: Entry-Fussabdruck wird vom ersten VF-Koerper abgezogen; + ;; der Exit (Motor+GF2) wird beim Schliessen ANGEHAENGT (verlaengert den VF-Lauf + ;; ggue. dem gezeichneten Pfad; feinere Laengen-Reservierung -> spaeter). + (setq i 0) + (while (< i n) + (setq seg (nth i segmente) typ (car seg) hz (cadr seg)) + (cond + ;; ===================== GERADE ===================== + ((= typ "Linie") + (setq laenge (caddr seg)) + (princ (strcat "\n\nSegment " (itoa (1+ i)) "/" (itoa n) + ": Gerade L=" (rtos laenge 2 0) " mm hz=" (rtos hz 2 1))) + (princ "\n Typ? 1 = GF (<=3 Grad) 2 = VF-Ab 3 = VF-Auf 4 = VF-Horizontal") + (setq antwort (getstring "\nIhre Wahl (1/2/3/4) [1]: ")) + (cond + ;; --- GF-Gerade --- + ((or (= antwort "") (= antwort "1")) + (if (= run-typ "VF") ; offenen VF-Lauf schliessen + (progn (setq frame (vfl2-vf-close frame (car (frame->hz-winkel frame)))) + (setq run-typ nil))) + (setq winkel (getreal "\n GF-Neigung in Grad [3]: ")) + (if (null winkel) (setq winkel 3.0)) + (if (> winkel 3.0) + (progn (princ "\n Hinweis: GF max. 3 Grad - auf 3 begrenzt.") (setq winkel 3.0))) + (if (null frame) + (setq frame (vfl2-insert-as-gf startpunkt hz winkel as-seite))) + (setq deltaL laenge) + ;; erste Gerade: AS-Fussabdruck ENTLANG des Pfads exakt aus KS_AUS abziehen + (if (= i first-line) + (setq deltaL (- deltaL + (+ (* (- (car (car frame)) (car startpunkt)) (cos (* hz (/ pi 180.0)))) + (* (- (cadr (car frame)) (cadr startpunkt)) (sin (* hz (/ pi 180.0)))))))) + (if (= i last-line) (setq deltaL (- deltaL 300.0 ein))) + (setq deltaL (max 100.0 deltaL)) + (setq frame (vfl-insert-gf-segment (car frame) hz deltaL winkel)) + (vfl-acc-gf-seg (/ deltaL (cos (* winkel (/ pi 180.0)))) winkel) + (setq anzahl-gf (1+ anzahl-gf) letzt-winkel winkel run-typ "GF")) + ;; --- VF-Gerade (Ab / Auf / Horizontal) --- + (t + (setq richtn (cond ((= antwort "3") "Auf") ((= antwort "4") "horizontal") (t "Ab"))) + (setq just-open nil) + (if (null frame) ; AS (flach) am Kettenanfang, KS_AUS auf Pfad + (setq frame (vfl2-insert-as-vf startpunkt hz as-seite))) + (if (not (= run-typ "VF")) ; VF-Einheit oeffnen + (progn (setq frame (vfl2-vf-open frame hz)) + (setq run-typ "VF" just-open t))) + (if (= richtn "horizontal") + (setq best-w 0) + (progn + (setq best-w (getint "\n Vario-Winkel (3..51 Grad) [3]: ")) + (if (null best-w) (setq best-w 3)) + (setq best-w (vfl2-snap-vfwinkel best-w)))) + (setq L_VF laenge) + (if just-open (setq L_VF (- L_VF ent-fp))) ; Entry-Fussabdruck reservieren + (setq L_VF (max 100.0 L_VF)) + (setq frame (vfl-frame-3grad (vfs-vf-koerper (car frame) richtn best-w L_VF hz) hz)) + (vfl-acc-vf-seg richtn best-w L_VF) + (setq anzahl-vf (1+ anzahl-vf) run-typ "VF")))) + ;; ===================== ECK / BOGEN ===================== + ((= typ "Bogen") + (setq bwinkel (nth 3 seg) bseite (nth 4 seg)) + (princ (strcat "\n\nSegment " (itoa (1+ i)) "/" (itoa n) + ": Eck/Bogen " (itoa bwinkel) " Grad " bseite)) + (princ "\n Typ? 1 = GF-Bogen 2 = Vario-Kurve") + (setq antwort (getstring "\nIhre Wahl (1/2) [1]: ")) + (if (= antwort "2") + ;; --- Vario-Kurve (nur im VF-Lauf) --- + (if (not (= run-typ "VF")) + (princ "\n >>> Vario-Kurve nur innerhalb eines VF-Laufs zulaessig - uebersprungen.") + (progn + (princ "\n Vario-Kurve - Variante? 1 = Aussen 2 = Innen") + (setq kvariante (if (= (getstring "\nIhre Wahl (1/2) [2]: ") "1") "aussen" "innen")) + (setq frame (vfl-insert-vario-kurve-block frame bwinkel bseite kvariante)))) + ;; --- GF-Bogen --- + (progn + (if (null frame) + (progn (alert "Kette beginnt mit einem Bogen - bitte erst eine Gerade.") (exit))) + (if (= run-typ "VF") ; VF-Lauf vor GF-Bogen schliessen + (progn (setq frame (vfl2-vf-close frame (car (frame->hz-winkel frame)))) + (setq run-typ nil))) + (setq frame (vfl-insert-gf-bogen-block frame bwinkel bseite)) + (setq run-typ "GF")))) + ) + (setq i (1+ i))) + + ;; --- 7. Kettenende: offenen VF-Lauf schliessen, dann Separator + ES --- + (if (null frame) (progn (princ "\nNichts gebaut - Abbruch.") (exit))) + (setq es-seite (vfl-frage-es-seite)) + (if (= run-typ "VF") + (progn + (setq frame (vfl2-vf-close frame (car (frame->hz-winkel frame)))) + (setq frame (vfl-insert-es-element "VF" frame (car (frame->hz-winkel frame)) + 0.0 (caddr (car frame)) es-seite))) + (setq frame (vfl-insert-es-element "GF" frame (car (frame->hz-winkel frame)) + (if letzt-winkel letzt-winkel 3.0) (caddr (car frame)) es-seite))) + + ;; --- 8. Block + Ist-Ziel-Report --- + (setq hoehe-bis (caddr (car frame))) + (vfl-block-erstellen vfl-nummer anzahl-gf anzahl-vf (caddr startpunkt) hoehe-bis + (vfl-planar-dist startpunkt (car frame)) as-seite es-seite startpunkt lastEnt) + + (setq soll-ende (caddr (last kette))) ; Endpunkt des letzten gezeichneten Segments + (setq ist-ende (car frame)) ; ES-KS_AUS + (princ "\n\n=========================================") + (princ "\n>>> VF-Linienzug (Modus 2, GF+VF) eingefuegt! <<<") + (princ (strcat "\n Soll-Ende (Pfad): X=" (rtos (car soll-ende) 2 1) + " Y=" (rtos (cadr soll-ende) 2 1))) + (princ (strcat "\n Ist-Ende (ES): X=" (rtos (car ist-ende) 2 1) + " Y=" (rtos (cadr ist-ende) 2 1) " Z=" (rtos (caddr ist-ende) 2 1))) + (princ (strcat "\n Abweichung XY: dX=" (rtos (- (car ist-ende) (car soll-ende)) 2 1) + " dY=" (rtos (- (cadr ist-ende) (cadr soll-ende)) 2 1) " mm")) + (princ "\n=========================================") + (princ) +) + +;; ============================================================ +;; MODUS 3: Pfad + Ziel-Hoehe (Randwert-Solver) - M3a +;; Schritt 1+2: Geruest + Klassifizierung + Anker-Report. NOCH KEIN Bauen. +;; Details siehe doc/VarioFoerderer_Linienzug_Prinzipien.md, Abschnitt 14. +;; ============================================================ + +;; Signierte Z-Aenderung (mm) einer GF-Geraden: negativ = Abfall. +;; l-planar = XY-Planlaenge (aus Pfad), winkel = Neigung in Grad (fallend). +;; Ueber die Planlaenge gilt dz = -L * tan(winkel) (Fussabdruck bleibt L, +;; die 3D-Laenge waechst mit L/cos, siehe Doc 14.1). +(defun vfl3-gf-dz (l-planar winkel / rad) + (setq rad (* (float winkel) (/ pi 180.0))) + (- (* (float l-planar) (/ (sin rad) (cos rad))))) + +;; Signierte Z-Aenderung eines Plan-Eintrags. +;; GF-Gerade -> -L*tan(winkel) +;; GF-Bogen -> dz aus gf-bogen-masse (Block-KS) +;; Vario-Kurve -> 0 (auf 0 Grad geflacht) +;; VF-Gerade -> nil (unbekannt = Teil der Bruecke) +(defun vfl3-seg-dz (e) + (cond + ((= (car e) "Linie") + (if (= (nth 3 e) "GF") (vfl3-gf-dz (caddr e) (nth 4 e)) nil)) + ((= (car e) "Bogen") + (if (= (nth 5 e) "Vario-Kurve") + 0.0 + (cadr (gf-bogen-masse (nth 3 e) (nth 4 e))))) + (t 0.0))) + +;; Loest die VF-Bruecke ueber den Modus-1-Solver berechne-alle-winkel. +;; Die Bruecke sitzt MITTIG in der Kette -> KEIN terminales AS/ES +;; (aus-dx/aus-dz/ein-dx/ein-dz = 0), feste-hz = *vfl-feste-horizontal* (1300: +;; Umlenk 500 + Motor 500 + Einlauf-Separator 300). GF1/GF2 bleiben fest 3 Grad, +;; ihre Laenge variiert (das ist der Ausgleich, siehe Doc 14.3). +;; dH-signiert: negativ = Auf (steigt), positiv = Ab (faellt). +;; Rueckgabe: (winkel L_GF L_VF richtung) oder nil (kein passender Winkel). +(defun vfl3-solve-bruecke (span dH-signiert extra-fest / + o-adx o-adz o-eix o-eiz richtung fh res winkel) + (setq richtung (if (< dH-signiert 0) "Auf" "Ab")) + (setq fh (+ (if (boundp '*vfl-feste-horizontal*) *vfl-feste-horizontal* 1300.0) + (if extra-fest extra-fest 0.0))) + ;; terminale AS/ES-Masse fuer die mittige Bruecke ausblenden (Save/Restore) + (setq o-adx aus-dx o-adz aus-dz o-eix ein-dx o-eiz ein-dz) + (setq aus-dx 0.0 aus-dz 0.0 ein-dx 0.0 ein-dz 0.0) + (setq res (berechne-alle-winkel span (abs dH-signiert) richtung fh)) + (setq aus-dx o-adx aus-dz o-adz ein-dx o-eix ein-dz o-eiz) + (setq winkel (car res)) + (if winkel (list winkel (cadr res) (caddr res) richtung) nil)) + +;; GF-Bogen-dz, wenn der Bogen bei Neigung theta (Grad) KS-gekettet wird: +;; der lokale (dx,dz) des Blocks wird um theta gekippt. +;; Z-Anteil = -dx*sin(theta) + dz*cos(theta) (negativ = Abfall) +(defun vfl3-bogen-dz-incl (bwinkel bseite theta / m dx dz rad) + (setq m (gf-bogen-masse bwinkel bseite) dx (car m) dz (cadr m)) + (setq rad (* (float theta) (/ pi 180.0))) + (+ (* (- dx) (sin rad)) (* dz (cos rad)))) + +;; Exakter Gesamt-Abstieg (positiv, mm) des BACK-Laufs (Plan-Segmente ab +;; start-idx bis n-1) PLUS Auslauf-Separator (300 mm, bei aktueller Neigung) + +;; ES-Eigen-dz. Spiegelt den tatsaechlichen Bau (Trimmung der letzten Geraden um +;; 300+ein-fp, GF-Bogen bei Neigung). Eintritts-Neigung = 3 Grad (die Bruecke +;; endet mit GF2 auf 3 Grad). So wird die Junction-Hoehe exakt statt geschaetzt. +(defun vfl3-dback (plan start-idx n last-idx ein-fp / i seg drop inc w dL rad) + (setq drop 0.0 inc 3.0 i start-idx) + (while (< i n) + (setq seg (nth i plan)) + (cond + ((= (car seg) "Linie") + (setq w (nth 4 seg) dL (caddr seg)) + (if (= i last-idx) (setq dL (- dL 300.0 ein-fp))) + (setq dL (max 100.0 dL)) + (setq rad (* (float w) (/ pi 180.0))) + (setq drop (+ drop (* dL (/ (sin rad) (cos rad))))) + (setq inc w)) + ((= (car seg) "Bogen") + (if (= (nth 5 seg) "GF-Bogen") + (setq drop (- drop (vfl3-bogen-dz-incl (nth 3 seg) (nth 4 seg) inc)))))) + (setq i (1+ i))) + ;; Auslauf-Separator (300 mm bei aktueller Neigung inc) + ES-Eigen-dz + (setq rad (* (float inc) (/ pi 180.0))) + (setq drop (+ drop (* 300.0 (/ (sin rad) (cos rad))))) + (setq drop (+ drop (abs (if ein-dz ein-dz 65.0)))) + drop) + +;; Feste (von der Kletterlaenge UNABHAENGIGE) Z-Aenderung einer VF-Einheit bei +;; Kletterwinkel w (ohne GF2, das wird gemessen): GF1+Sep(300)+Umlenk(500)+ +;; Motor(500) (alle 3 Grad, senkend) + je Kletterer zwei Boegen + je +;; Horizontal-Mitte-Koerper zwei 3-Grad-Uebergangsboegen. Einbau-Rotationen wie +;; in vfs-vf-koerper; Bogen-dz_eff = -dx*sin(rot) + dz_roh*cos(rot) (am Log +;; verifiziert). So ist die Hoehe rein rechnerisch bestimmt -> Kletterlaenge folgt. +(defun vfl3-einheit-fix-dz (w n-climb n-hor gf1 richtung / + pi180 rad3 s3 tot m1 m2 r1 r2 dz1 dz2) + (setq pi180 (/ pi 180.0) rad3 (* 3.0 pi180) s3 (sin rad3) tot 0.0) + ;; feste 3-Grad-Teile senken immer ab (GF1 + Separator + Umlenk + Motor) + (setq tot (- tot (* (+ (float gf1) 300.0 500.0 500.0) s3))) + ;; Kletterer-Boegen (Rotationen wie vfs-vf-koerper: 1. Bogen @3, 2. Bogen @(3-w)/(w+3)) + (if (= richtung "Auf") + (setq m1 (get-bogen-mass bogen-auf w) r1 rad3 + m2 (get-bogen-mass bogen-ab w) r2 (* (- 3 w) pi180)) + (setq m1 (get-bogen-mass bogen-ab w) r1 rad3 + m2 (get-bogen-mass bogen-auf w) r2 (* (+ w 3) pi180))) + (setq dz1 (+ (* (- (car m1)) (sin r1)) (* (caddr m1) (cos r1)))) + (setq dz2 (+ (* (- (car m2)) (sin r2)) (* (caddr m2) (cos r2)))) + (setq tot (+ tot (* n-climb (+ dz1 dz2)))) + ;; Horizontal-Mitte-Koerper: auf_3@3 + ab_3@0 + (setq m1 (get-bogen-mass bogen-auf 3) m2 (get-bogen-mass bogen-ab 3)) + (setq dz1 (+ (* (- (car m1)) (sin rad3)) (* (caddr m1) (cos rad3)))) + (setq dz2 (caddr m2)) + (setq tot (+ tot (* n-hor (+ dz1 dz2)))) + tot) + +;; Fester PLANARER (XY-)Fussabdruck einer VF-Einheit bei Kletterwinkel w +;; (ohne die Kletter-Strecken selbst): Stationen (GF1+Sep+Umlenk+Motor, @3 Grad) +;; + je Kletterer die zwei Boegen + je Horizontal-Mitte-Koerper die zwei +;; 3-Grad-Boegen. dx_eff = dx*cos(rot) + dz_roh*sin(rot) (am Log verifiziert). +;; Dient der Winkelwahl: Fussabdruck + Kletter-Planlaenge soll die Lauflaenge treffen. +(defun vfl3-einheit-fix-dx (w n-climb n-hor gf1 richtung / + pi180 rad3 tot m1 m2 r1 r2 dx1 dx2) + (setq pi180 (/ pi 180.0) rad3 (* 3.0 pi180) tot 0.0) + (setq tot (* (+ (float gf1) 300.0 500.0 500.0) (cos rad3))) ; Stationen planar + (if (= richtung "Auf") + (setq m1 (get-bogen-mass bogen-auf w) r1 rad3 + m2 (get-bogen-mass bogen-ab w) r2 (* (- 3 w) pi180)) + (setq m1 (get-bogen-mass bogen-ab w) r1 rad3 + m2 (get-bogen-mass bogen-auf w) r2 (* (+ w 3) pi180))) + (setq dx1 (+ (* (car m1) (cos r1)) (* (caddr m1) (sin r1)))) + (setq dx2 (+ (* (car m2) (cos r2)) (* (caddr m2) (sin r2)))) + (setq tot (+ tot (* n-climb (+ dx1 dx2)))) + (setq m1 (get-bogen-mass bogen-auf 3) m2 (get-bogen-mass bogen-ab 3)) + (setq dx1 (+ (* (car m1) (cos rad3)) (* (caddr m1) (sin rad3)))) ; auf_3 @ 3 + (setq dx2 (car m2)) ; ab_3 @ 0 + (setq tot (+ tot (* n-hor (+ dx1 dx2)))) + tot) + +;; Uebergang von der 3-Grad-Kletterbasis in die FLACHE Zone (0 Grad): EIN auf_3-Bogen +;; (Rotation 3 Grad). Rueckgabe: neuer Punkt. +(defun vfl3-flach-ein (pt hz / m) + (setq m (get-bogen-mass bogen-auf 3)) + (insert-rotated-block-with-ks "Vario_Bogen_auf_3" pt 3 (car m) (caddr m) hz)) + +;; Uebergang aus der flachen Zone (0 Grad) zurueck auf 3-Grad-Basis: EIN ab_3-Bogen +;; (Rotation 0 Grad). Rueckgabe: neuer Punkt. +(defun vfl3-flach-aus (pt hz / m) + (setq m (get-bogen-mass bogen-ab 3)) + (insert-rotated-block-with-ks "Vario_Bogen_ab_3" pt 0 (car m) (caddr m) hz)) + +;; Kletter-Segment loesen und Winkel WAEHLEN LASSEN (Modus-1-Solver + vfl-waehle-winkel). +;; Mittige Bruecke -> KEIN terminales AS/ES (Masse auf 0, Save/Restore). feste = fester +;; Horizontal-Anteil des Kletter-Segments (Umlenk+Separator = 800; Motor sitzt spaeter +;; am Kettenende). Rueckgabe: (winkel L_GF L_VF richtung) oder nil. +(defun vfl3-waehle-winkel (span dH-signiert feste / + o-adx o-adz o-eix o-eiz richtung res wahl) + (setq richtung (if (< dH-signiert 0) "Auf" "Ab")) + (setq o-adx aus-dx o-adz aus-dz o-eix ein-dx o-eiz ein-dz) + (setq aus-dx 0.0 aus-dz 0.0 ein-dx 0.0 ein-dz 0.0) + (setq res (berechne-alle-winkel span (abs dH-signiert) richtung feste)) + (setq aus-dx o-adx aus-dz o-adz ein-dx o-eix ein-dz o-eiz) + (setq wahl (vfl-waehle-winkel (nth 3 res))) + (if wahl (list (car wahl) (cadr wahl) (caddr wahl) richtung) nil)) + +;; Letzte Fueller-Laenge so, dass der Endpunkt auf der ES-KS_AUS-Achse liegt. +;; pt = Fueller-Start (flach 0 Grad), seg-hz = letzte Richtung, es-block = ES-Block, +;; gf2 = erwartete GF2-Laenge, rad3 = 3 Grad (rad). Modell: der Schwanz (Fueller + +;; ab_3 + Motor + GF2 + Separator + ES) verschiebt sich starr entlang seg-hz. +;; KS_AUS = pt + (fill + C)*dp + perp-es*np +;; C = 196 (ab_3) + (Motor 500 + GF2 + Sep 300)*cos3 + along-es +;; Achse u_a = seg-hz + (KS_AUS.xu - KS_EIN.xu)_Block ; (Endpunkt-KS_AUS)*n_a=0 -> fill +(defun vfl3-es-fueller (pt endp seg-hz es-block gf2 rad3 / + info theta dpx dpy npx npy phi vx vy vwx vwy + along-es perp-es ua na-x na-y cc dp-na np-na ep-na) + (setq info (vf-element-ks-info es-block)) + (if (null info) + (- (+ (* (- (car endp) (car pt)) (cos (* seg-hz (/ pi 180.0)))) + (* (- (cadr endp) (cadr pt)) (sin (* seg-hz (/ pi 180.0))))) + (+ 196.0 (* (+ 800.0 gf2) (cos rad3)))) ; Fallback: Along-Naeherung + (progn + (setq theta (* seg-hz (/ pi 180.0))) + (setq dpx (cos theta) dpy (sin theta)) ; seg-hz Richtung + (setq npx (- (sin theta)) npy (cos theta)) ; senkrecht zu seg-hz + (setq phi (* (- seg-hz (car info)) (/ pi 180.0))) ; Block -> Welt + (setq vx (caddr info) vy (cadddr info)) + (setq vwx (- (* vx (cos phi)) (* vy (sin phi)))) ; V_es (Welt) + (setq vwy (+ (* vx (sin phi)) (* vy (cos phi)))) + (setq along-es (+ (* vwx dpx) (* vwy dpy))) + (setq perp-es (+ (* vwx npx) (* vwy npy))) + (setq ua (* (+ seg-hz (- (cadr info) (car info))) (/ pi 180.0))) ; KS_AUS-Achse + (setq na-x (- (sin ua)) na-y (cos ua)) ; senkrecht zur KS_AUS-Achse + (setq cc (+ 196.0 (* (+ 800.0 gf2) (cos rad3)) along-es)) + (setq dp-na (+ (* dpx na-x) (* dpy na-y))) + (setq np-na (+ (* npx na-x) (* npy na-y))) + (setq ep-na (+ (* (- (car endp) (car pt)) na-x) (* (- (cadr endp) (cadr pt)) na-y))) + (if (> (abs dp-na) 1e-6) + (- (/ (- ep-na (* perp-es np-na)) dp-na) cc) + 100.0)))) + +(defun vf-linienzug-modus3 ( / ss k obj-liste startpunkt start-hoehe as-seite + endpunkt end-hoehe es-seite kette segmente n i seg + typ hz laenge bwinkel bseite antwort winkel klass + plan e ecke-bad vf-start vf-ende dz z-front + z-junction dH-bruecke span-bruecke dH-gesamt + vf-count in-vf richtn req-w loesung + aus ein first-line last-line frame deltaL + br-winkel br-gf1 br-gf2 br-lvf br-richtn pt + letzt-winkel vfl-nummer lastEnt anzahl-gf anzahl-vf + hoehe-bis soll-ende ist-ende + member carrier-idx kv-variante seg-hz letzt-koerper-hz + vf-first-line vf-last-line climber-span mid-hor + z-aftermotor gf2-drop gf2-planar br-lvf this-lvf + nach-kurve n-climb n-hor target-climb winkel-list + fdz fdx wslope lvf planar-len ang-diff best-diff w + climb-thresh longest-idx climbers nonclimber-len + nonclimber-cnt nkurve kurve-chords run-span + hor-koerper-len hor-pairs flat-p fill-len wahl3 + rad3v feste-vf dH-adj dir-x dir-y fill-D end-hz + gf-total br-gf-mode br-gf2-exp filler-A-len filler-a-done) + (princ "\n\n=========================================") + (princ "\n VF-LINIENZUG - Modus 2: Pfad + Ziel-Hoehe (fertig; Anker + Solver + Bau)") + (princ "\n=========================================") + + ;; Abhaengigkeit Gefaellestrecke-Modul + (if (null (car (atoms-family 1 '("GF-INSERT-HZ-INCL-SCALED")))) + (progn (alert "Gefaellestrecke-Modul nicht geladen!") (exit))) + (if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek)) + + ;; --- 1. Pfad-Objekte waehlen --- + (princ "\n\nPfad-Objekte waehlen (Linien/Boegen):") + (setq ss (ssget '((0 . "LINE,ARC")))) + (if (null ss) (progn (princ "\nKeine Objekte gewaehlt - Abbruch.") (exit))) + (setq obj-liste '() k 0) + (repeat (sslength ss) + (setq obj-liste (cons (vlax-ename->vla-object (ssname ss k)) obj-liste)) + (setq k (1+ k))) + + ;; --- 2. Startpunkt + Z + AS-Seite --- + (setq startpunkt (vfl-getpoint nil "\n\nStartpunkt der Kette (hoehere Seite) waehlen: ")) + (if (null startpunkt) (progn (princ "\nAbgebrochen.") (exit))) + (setq start-hoehe (getreal (strcat "\nHoehe (Z) des Startpunkts [" + (rtos (caddr startpunkt) 2 1) "]: "))) + (if (null start-hoehe) (setq start-hoehe (caddr startpunkt))) + (setq startpunkt (list (car startpunkt) (cadr startpunkt) start-hoehe)) + (setq *vfl-as-winkel* (vf-frage-element-winkel "vf-winkel-aus-header")) ; 30/90 vor Seite + (princ "\n\nAUS-Element - Seite waehlen:\n 1 - Links\n 2 - Rechts") + (setq as-seite (if (= (getstring "\nIhre Wahl (1/2) [1]: ") "2") "rechts" "links")) + (vf-set-as-masse *vfl-as-winkel* as-seite) ; Masse fuer Variante + + ;; --- 3. Endpunkt + Z + ES-Seite (NEU in Modus 3) --- + (setq endpunkt (vfl-getpoint nil "\n\nEndpunkt der Kette (Ziel) waehlen: ")) + (if (null endpunkt) (progn (princ "\nAbgebrochen.") (exit))) + (setq end-hoehe (getreal (strcat "\nZiel-Hoehe (Z) des Endpunkts [" + (rtos (caddr endpunkt) 2 1) "]: "))) + (if (null end-hoehe) (setq end-hoehe (caddr endpunkt))) + (setq endpunkt (list (car endpunkt) (cadr endpunkt) end-hoehe)) + (setq *vfl-es-winkel* (vf-frage-element-winkel "vf-winkel-ein-header")) ; 30/90 vor Seite + (princ "\n\nEIN-Element (ES) - Seite waehlen:\n 1 - Links\n 2 - Rechts") + (setq es-seite (if (= (getstring "\nIhre Wahl (1/2) [1]: ") "2") "rechts" "links")) + (vf-set-es-masse *vfl-es-winkel* es-seite) ; Masse fuer Variante + + ;; --- 4. Sortieren + Analysieren + Eck-Vorpruefung --- + (setq kette (gf-sortiere-objekte obj-liste startpunkt)) + (if (null kette) + (progn (princ "\nFEHLER: Pfad nicht sortierbar (haengt er lueckenlos zusammen?).") (exit))) + (setq segmente (gf-analysiere-kette kette)) + (setq ecke-bad (vfl2-pruefe-eckwinkel kette 5.0)) + (if ecke-bad + (progn + (alert (strcat "Kein passender Bogen: Eck-Winkel " (rtos ecke-bad 2 1) + " Grad ist nicht ~30/60/90.\nBitte die Winkel anpassen.")) + (exit))) + + ;; --- 5. Klassifizierung (Phase A: nur speichern, nichts bauen) --- + ;; Plan-Eintrag Gerade: ("Linie" hz laenge klass winkel) klass=GF/VF + ;; Plan-Eintrag Bogen : ("Bogen" hz chord bwinkel bseite klass) + (setq n (length segmente) i 0 plan '()) + (while (< i n) + (setq seg (nth i segmente) typ (car seg) hz (cadr seg) laenge (caddr seg)) + (cond + ((= typ "Linie") + (princ (strcat "\n\nSegment " (itoa (1+ i)) "/" (itoa n) + ": Gerade L=" (rtos laenge 2 0) " mm hz=" (rtos hz 2 1))) + (cond + ;; Direkt nach einer Vario-Kurve: automatisch VF (keine Abfrage) - + ;; die Kurve sitzt mitten in der VF-Einheit, es MUSS VF folgen. + (nach-kurve + (princ "\n (automatisch VF - Segment folgt direkt auf eine Vario-Kurve)") + (setq plan (cons (list "Linie" hz laenge "VF" nil) plan)) + (setq nach-kurve nil)) + (t + (princ "\n Typ? 1 = GF (Eingabe-Winkel) 2 = VF (Bruecke)") + (setq antwort (getstring "\nIhre Wahl (1/2) [1]: ")) + (if (= antwort "2") + (setq plan (cons (list "Linie" hz laenge "VF" nil) plan)) + (progn + (setq winkel (getreal "\n GF-Neigung in Grad [3]: ")) + (if (null winkel) (setq winkel 3.0)) + (if (> winkel 3.0) + (progn (princ "\n Hinweis: GF max. 3 Grad - auf 3 begrenzt.") (setq winkel 3.0))) + (setq plan (cons (list "Linie" hz laenge "GF" winkel) plan))))))) + ((= typ "Bogen") + (setq bwinkel (nth 3 seg) bseite (nth 4 seg)) + (princ (strcat "\n\nSegment " (itoa (1+ i)) "/" (itoa n) + ": Eck/Bogen " (itoa bwinkel) " Grad " bseite)) + (princ "\n Typ? 1 = GF-Bogen 2 = Vario-Kurve") + (setq antwort (getstring "\nIhre Wahl (1/2) [1]: ")) + (if (= antwort "2") + (progn + (princ "\n Vario-Kurve - Variante? 1 = Aussen 2 = Innen") + (setq kv-variante (if (= (getstring "\nIhre Wahl (1/2) [2]: ") "1") "aussen" "innen")) + (setq plan (cons (list "Bogen" hz laenge bwinkel bseite "Vario-Kurve" kv-variante) plan)) + (setq nach-kurve t)) ; naechste Gerade automatisch VF + (progn + (setq plan (cons (list "Bogen" hz laenge bwinkel bseite "GF-Bogen" nil) plan)) + (setq nach-kurve nil))))) + (setq i (1+ i))) + (setq plan (reverse plan)) + + ;; --- 6. VF-Lauf finden: VF-Geraden UND Vario-Kurven bilden EINEN Lauf --- + ;; (Vario-Kurve gehoert in die VF-Einheit, unterbricht den Lauf also NICHT). + (setq vf-start -1 vf-ende -1 vf-count 0 in-vf nil i 0) + (foreach e plan + (setq member (or (and (= (car e) "Linie") (= (nth 3 e) "VF")) + (and (= (car e) "Bogen") (= (nth 5 e) "Vario-Kurve")))) + (if member + (progn + (if (not in-vf) (setq vf-count (1+ vf-count) vf-start i in-vf t)) + (setq vf-ende i)) + (setq in-vf nil)) + (setq i (1+ i))) + + (if (/= vf-count 1) + (progn + (princ (strcat "\n\n[M3a] Gefundene VF-Laeufe: " (itoa vf-count))) + (princ "\n[M3a] Genau EIN VF-Lauf noetig (GF...VF...GF; Vario-Kurven duerfen im Lauf liegen).") + (princ "\n[M3a] Mehrere/keine VF-Laeufe -> M3b spaeter.") + (exit))) + ;; Traeger-Stueck = laengste VF-Gerade im Lauf (traegt die ganze Hoehe). + (setq carrier-idx -1 i vf-start) + (while (<= i vf-ende) + (setq seg (nth i plan)) + (if (and (= (car seg) "Linie") (= (nth 3 seg) "VF")) + (if (or (< carrier-idx 0) (> (caddr seg) (caddr (nth carrier-idx plan)))) + (setq carrier-idx i))) + (setq i (1+ i))) + (if (< carrier-idx 0) + (progn (princ "\n[M3a] VF-Lauf ohne VF-Gerade - Abbruch.") (exit))) + + ;; --- 7. Anker rechnen --- + ;; Vorwaerts: Start-Z durch alle Front-Segmente (Index < vf-start). + (setq z-front (float start-hoehe) i 0) + (while (< i vf-start) + (setq dz (vfl3-seg-dz (nth i plan))) + (if dz (setq z-front (+ z-front dz))) + (setq i (1+ i))) + ;; Rueckwaerts: Ziel-Z durch alle Back-Segmente (Index > vf-ende). + ;; Vorwaerts gilt Z_next = Z_prev + dz -> rueckwaerts Z_prev = Z_next - dz. + (setq z-junction (float end-hoehe) i (1- n)) + (while (> i vf-ende) + (setq dz (vfl3-seg-dz (nth i plan))) + (if dz (setq z-junction (- z-junction dz))) + (setq i (1- i))) + + ;; Bruecken-Spannweite = Summe der VF-Geraden-Planlaengen im Lauf. + (setq span-bruecke 0.0 i vf-start) + (while (<= i vf-ende) + (setq seg (nth i plan)) + (if (= (car seg) "Linie") (setq span-bruecke (+ span-bruecke (caddr seg)))) + (setq i (1+ i))) + + (setq dH-bruecke (- z-front z-junction)) + (setq dH-gesamt (- (float start-hoehe) (float end-hoehe))) + + ;; --- 8. Anker-Report (noch kein Bauen) --- + (princ "\n\n=========================================") + (princ "\n M3a ANKER-VORSCHAU (grob geschaetzt; exakte Werte beim Bau)") + (princ "\n=========================================") + (princ (strcat "\n Start-Hoehe Z: " (rtos (float start-hoehe) 2 1) " mm")) + (princ (strcat "\n Ziel-Hoehe Z: " (rtos (float end-hoehe) 2 1) " mm")) + (princ (strcat "\n delta-H gesamt: " (rtos dH-gesamt 2 1) " mm (positiv = Abfall)")) + (princ (strcat "\n VF-Lauf: Segment " (itoa (1+ vf-start)) ".." (itoa (1+ vf-ende)))) + (princ (strcat "\n Front-Anker Z: " (rtos z-front 2 1) " mm (vorwaerts von AS)")) + (princ (strcat "\n Junction-Anker Z: " (rtos z-junction 2 1) " mm (rueckwaerts von ES)")) + ;; Vorzeichen = Richtung (VF ist angetrieben, kann steigen UND fallen). + (setq richtn (cond ((< dH-bruecke -0.1) "Auf (steigt)") + ((> dH-bruecke 0.1) "Ab (faellt)") + (t "horizontal"))) + (setq req-w (if (> span-bruecke 1.0) + (* (atan (/ (abs dH-bruecke) span-bruecke)) (/ 180.0 pi)) + 0.0)) + (princ (strcat "\n --> Bruecke: delta-H = " (rtos dH-bruecke 2 1) + " mm ueber Spannweite " (rtos span-bruecke 2 0) " mm")) + (princ (strcat "\n Richtung: VF-" richtn)) + (princ "\n (VF ist angetrieben - kann STEIGEN und FALLEN; Vorzeichen = nur Auf/Ab)") + (princ (strcat "\n grobe mittlere Neigung ueber Spannweite: ~" (rtos req-w 2 1) " Grad")) + (if (> req-w 51.0) + (princ "\n WARNUNG: ~Neigung > 51 Grad -> zu steil fuer Vario (Magnitude infeasibel).") + (princ "\n -> innerhalb Vario-Bereich (3..51 Grad), grobe Orientierung.")) + + (princ "\n (Anker oben = grobe Vorschau; exakte Werte werden beim Bau GEMESSEN)") + (princ "\n=========================================") + + ;; ================= PHASE B: BAUEN (messen statt schaetzen) ================= + ;; VF-Lauf darf mehrsegmentig sein (VF-Geraden + Vario-Kurven, EINE VF-Einheit). + ;; Nummerierung + Akkumulatoren + AS/ES-Fussabdruecke + (setq aus (if aus-dx aus-dx 576.0) ein (if ein-dx ein-dx 576.0)) + (setq vfl-nummer (vf-next-number)) + (setq lastEnt (vf-lastent-ohne-attribute)) + (vfl-acc-reset) + (setq anzahl-gf 0 anzahl-vf 0 frame nil) + ;; erste/letzte Gerade fuer AS/ES-Trimmung bestimmen + (setq first-line -1 last-line -1 i 0) + (while (< i n) + (if (= (car (nth i plan)) "Linie") + (progn (if (< first-line 0) (setq first-line i)) (setq last-line i))) + (setq i (1+ i))) + + ;; --- B1: FRONT-Lauf bauen (Segmente 0 .. vf-start-1) -> Front-Anker MESSEN --- + (princ "\n\n>>> Phase B1: Front-Lauf (AS + GF) bauen ...") + (setq i 0) + (while (< i vf-start) + (setq seg (nth i plan) typ (car seg) hz (cadr seg)) + (cond + ((= typ "Linie") + (setq winkel (nth 4 seg)) + ;; AS zuerst platzieren, damit der echte KS_AUS bekannt ist + (if (null frame) + (setq frame (vfl2-insert-as-gf startpunkt hz winkel as-seite))) + (setq deltaL (caddr seg)) + ;; erste Gerade: AS-Fussabdruck ENTLANG des Pfads exakt aus KS_AUS abziehen + ;; (das Block-Maß aus-dx stimmt nach dem Achs-Versatz nicht mehr). + (if (= i first-line) + (setq deltaL (- deltaL + (+ (* (- (car (car frame)) (car startpunkt)) (cos (* hz (/ pi 180.0)))) + (* (- (cadr (car frame)) (cadr startpunkt)) (sin (* hz (/ pi 180.0)))))))) + (setq deltaL (max 100.0 deltaL)) + (setq frame (vfl-insert-gf-segment (car frame) hz deltaL winkel)) + (vfl-acc-gf-seg (/ deltaL (cos (* winkel (/ pi 180.0)))) winkel) + (setq anzahl-gf (1+ anzahl-gf) letzt-winkel winkel)) + ((= typ "Bogen") + (setq bwinkel (nth 3 seg) bseite (nth 4 seg) klass (nth 5 seg)) + (if (= klass "GF-Bogen") + (progn + (if (null frame) + (progn (alert "Kette beginnt mit einem Bogen - bitte erst eine Gerade.") (exit))) + (setq frame (vfl-insert-gf-bogen-block frame bwinkel bseite))) + (princ "\n >>> [M3a] Vario-Kurve im Front-Lauf - uebersprungen.")))) + (setq i (1+ i))) + + ;; Front-Anker (gemessen). Falls die VF-Bruecke ganz vorne liegt: AS jetzt setzen. + (setq hz (cadr (nth vf-start plan))) + (if (null frame) + (setq frame (vfl2-insert-as-vf startpunkt hz as-seite))) + (setq z-front (caddr (car frame))) + + ;; --- B2: Back-Abstieg -> Junction; Kletterer + Horizontal-Mitte bestimmen --- + (setq z-junction (+ (float end-hoehe) + (vfl3-dback plan (1+ vf-ende) n last-line ein))) + (setq dH-bruecke (- z-front z-junction)) + ;; Modell: EIN Vario mit Horizontal-Mitte. Nur VF-Geraden, die LANG GENUG sind + ;; (Vertikalboegen brauchen viel Platz), tragen die Hoehe (Kletterer, gleicher + ;; Winkel). Zu kurze VF-Geraden werden horizontal (nur Anschluss/Motor). + ;; Mindestens die laengste VF-Gerade klettert immer. + (setq climb-thresh (if (boundp '*vfl-min-climber-laenge*) *vfl-min-climber-laenge* 3000.0)) + (setq longest-idx -1 i vf-start) + (while (<= i vf-ende) + (setq seg (nth i plan)) + (if (and (= (car seg) "Linie") (= (nth 3 seg) "VF")) + (if (or (< longest-idx 0) (> (caddr seg) (caddr (nth longest-idx plan)))) + (setq longest-idx i))) + (setq i (1+ i))) + (setq climbers '() climber-span 0.0 nonclimber-len 0.0 nonclimber-cnt 0 + nkurve 0 kurve-chords 0.0 i vf-start) + (while (<= i vf-ende) + (setq seg (nth i plan)) + (cond + ((and (= (car seg) "Linie") (= (nth 3 seg) "VF")) + (if (or (= i longest-idx) (>= (caddr seg) climb-thresh)) + (setq climbers (cons i climbers) climber-span (+ climber-span (caddr seg))) + (setq nonclimber-len (+ nonclimber-len (caddr seg)) nonclimber-cnt (1+ nonclimber-cnt)))) + ((= (car seg) "Bogen") + (setq nkurve (1+ nkurve) kurve-chords (+ kurve-chords (caddr seg))))) + (setq i (1+ i))) + (setq climbers (reverse climbers) n-climb (length climbers)) + ;; Flache Zone (Kurve + horizontale Fueller) hat GENAU EIN Uebergangspaar + ;; (ein auf_3 rein, ein ab_3 raus), unabhaengig von der Zahl der Fueller/Kurven. + (setq hor-pairs (if (or (> nkurve 0) (> nonclimber-cnt 0)) 1 0)) + (setq run-span (+ climber-span nonclimber-len kurve-chords)) + (setq br-gf1 400.0) ; feste kleine GF1 + (setq br-richtn (if (< dH-bruecke 0) "Auf" "Ab")) + (setq target-climb (- z-junction z-front)) ; noetige Netto-Hoehe (Auf>0) + ;; Kletter-Segment als Standard-Vario loesen; GF wird BERECHNET (L_GF) und der + ;; Winkel WAEHLBAR (mehrere gueltige -> Nutzer waehlt). feste = 800 (Umlenk+Sep; + ;; Motor sitzt am Kettenende). Die flache Zone gleicht danach die Laenge aus. + (princ "\n\n --- STANDARD-VARIO (Kletter-Segment) + flache Zone ---") + (princ (strcat "\n Front-Anker (gemessen): " (rtos z-front 2 1) " mm")) + (princ (strcat "\n Junction (exakt): " (rtos z-junction 2 1) " mm")) + (princ (strcat "\n Kletterhoehe " (rtos target-climb 2 1) " mm | Kletterer " + (itoa n-climb) " | Kurven " (itoa nkurve) " | kurze VF " (itoa nonclimber-cnt))) + ;; feste-Horizontal + Hoehen-Anpassung fuer den Solver: + ;; - Mit flacher Zone: Motor sitzt am Kettenende (nicht im Kletter-Segment) -> + ;; feste = 800 (Umlenk+Sep). berechne rechnet Motor-/Uebergangs-Abstieg NICHT, + ;; daher Kletterhoehe um diese Abstiege anpassen (GF2 bleibt ~0). + ;; - Ohne flache Zone (Einzel-Bruecke): feste = 1300 (inkl. Motor), keine Anpassung. + (setq rad3v (* 3.0 (/ pi 180.0))) + (if (> hor-pairs 0) + (setq feste-vf 800.0 + dH-adj (- dH-bruecke (+ (* 500.0 (sin rad3v)) 10.48))) ; Motor + auf_3/ab_3 + (setq feste-vf 1300.0 dH-adj dH-bruecke)) + (setq wahl3 (vfl3-waehle-winkel climber-span dH-adj feste-vf)) + (if (null wahl3) + (progn (princ "\n KEIN passender Vario-Winkel -> infeasibel (Front bereits gebaut).") + (princ) (exit))) + (setq br-winkel (car wahl3) gf-total (cadr wahl3) br-lvf (caddr wahl3) br-richtn (cadddr wahl3)) + ;; GF-Verteilung: 1 = alles am Einlauf (GF1); 2 = 1/2 GF1 + 1/2 GF2. Bei 1/2/1/2 + ;; wird der im Kletter-Segment durch das halbe GF1 frei werdende Platz mit einem + ;; horizontalen Fueller-A gefuellt; GF2 (~1/2 L_GF) sitzt hinter dem Motor, der + ;; ES-Laengen-Abschluss zieht seinen Fussabdruck ab (Fueller-B wird kuerzer). + (princ "\n\n GF-Verteilung: 1 - alles am Einlauf (GF1) 2 - 1/2 GF1 + 1/2 GF2") + (setq br-gf-mode (if (= (getstring "\n Ihre Wahl (1/2) [1]: ") "2") 2 1)) + (if (= br-gf-mode 2) + (setq br-gf1 (/ gf-total 2.0) br-gf2-exp (/ gf-total 2.0) + filler-A-len (* (/ gf-total 2.0) (cos rad3v))) + (setq br-gf1 gf-total br-gf2-exp 0.0 filler-A-len 0.0)) + (setq filler-a-done nil) + (princ (strcat "\n -> Vario " (itoa br-winkel) " (" br-richtn "), L_VF " (rtos br-lvf 2 0) + " mm, GF gesamt " (rtos gf-total 2 0) " mm, Verteilung " + (if (= br-gf-mode 2) "1/2 GF1 + 1/2 GF2" "alles GF1") + " (GF2-Rest = Mess-Feinkorrektur)")) + + ;; --- B3: Standard-Vario (Klettern, 3-Grad-Basis) + flache Zone (0 Grad) --- + ;; Die flache Zone (Vario-Kurve + horizontaler Fueller) haengt EINMAL ueber auf_3 + ;; ein und EINMAL ueber ab_3 aus; Kurve und Fueller sind bei 0 Grad DIREKT + ;; verbunden (keine Zwischen-Boegen). Der horizontale Fueller gleicht die + ;; Restlaenge aus (letztes Stueck vor dem Motor getrimmt). + (setq pt (car frame) letzt-koerper-hz (cadr (nth vf-start plan)) flat-p nil) + (setq pt (vfs-vf-entry pt br-gf1 letzt-koerper-hz)) ; GF1 + Separator + Umlenk (3 Grad) + (vfl-acc-gf-seg br-gf1 3) + (setq i vf-start) + (while (<= i vf-ende) + (setq seg (nth i plan) seg-hz (cadr seg)) + (cond + ;; --- Kletterer (geneigt, 3-Grad-Basis) --- + ((and (= (car seg) "Linie") (member i climbers)) + (if flat-p (progn (setq pt (vfl3-flach-aus pt seg-hz)) (setq flat-p nil))) + (setq this-lvf (max 100.0 (* br-lvf (/ (caddr seg) climber-span)))) + (setq pt (vfs-vf-koerper pt br-richtn br-winkel this-lvf seg-hz)) + (vfl-acc-vf-seg br-richtn br-winkel this-lvf) + (setq anzahl-vf (1+ anzahl-vf) letzt-koerper-hz seg-hz)) + ;; --- kurze VF-Gerade -> horizontaler Fueller (0 Grad) --- + ((= (car seg) "Linie") + (if (not flat-p) + (progn (setq pt (vfl3-flach-ein pt letzt-koerper-hz)) (setq flat-p t) + ;; Fueller-A: der im Kletter-Segment durch 1/2 GF1 frei werdende Platz + (if (and (> filler-A-len 0.1) (not filler-a-done)) + (progn (setq pt (insert-inclined-scaled-block "Staustrecke_SP_1000_mm" + pt filler-A-len 0 letzt-koerper-hz)) + (vfl-acc-vf-seg "horizontal" 0 filler-A-len) + (setq anzahl-vf (1+ anzahl-vf) filler-a-done t))))) + (if (= i vf-ende) + ;; letzter Fueller: Laenge so, dass der ENDPUNKT auf der ES-KS_AUS-ACHSE + ;; liegt (Spiegel der AS-Regel). Der Schwanz (Fueller + ab_3 + Motor + GF2 + ;; + Separator + ES) verschiebt sich starr entlang seg-hz mit dem Fueller; + ;; KS_AUS = pt + (fill + C)*dp + perp-es*np, Achse u_a = seg-hz + ES-Turn. + ;; Aus (Endpunkt - KS_AUS)*n_a = 0 folgt fill (n_a senkrecht zu u_a). + (setq fill-len (vfl3-es-fueller pt endpunkt seg-hz + (strcat "ES_Element_" (vfl-es-winkel) "_" es-seite) + br-gf2-exp rad3v)) + (setq fill-len (caddr seg))) + (setq fill-len (max 100.0 fill-len)) + (setq pt (insert-inclined-scaled-block "Staustrecke_SP_1000_mm" pt fill-len 0 seg-hz)) + (vfl-acc-vf-seg "horizontal" 0 fill-len) (setq anzahl-vf (1+ anzahl-vf)) + (setq letzt-koerper-hz seg-hz)) + ;; --- Vario-Kurve (flach, direkt bei 0 Grad) --- + ((= (car seg) "Bogen") + (if (not flat-p) + (progn (setq pt (vfl3-flach-ein pt letzt-koerper-hz)) (setq flat-p t) + ;; Fueller-A: der im Kletter-Segment durch 1/2 GF1 frei werdende Platz + (if (and (> filler-A-len 0.1) (not filler-a-done)) + (progn (setq pt (insert-inclined-scaled-block "Staustrecke_SP_1000_mm" + pt filler-A-len 0 letzt-koerper-hz)) + (vfl-acc-vf-seg "horizontal" 0 filler-A-len) + (setq anzahl-vf (1+ anzahl-vf) filler-a-done t))))) + (setq bwinkel (nth 3 seg) bseite (nth 4 seg) kv-variante (nth 6 seg)) + (setq frame (make-frame-from-dir pt (hz-winkel->xu letzt-koerper-hz 0.0))) + (setq frame (vfl-insert-vario-kurve-block frame bwinkel bseite + (if kv-variante kv-variante "innen"))) + (setq pt (car frame)) + (if (< i vf-ende) (setq letzt-koerper-hz (cadr (nth (1+ i) plan)))))) + (setq i (1+ i))) + (if flat-p (progn (setq pt (vfl3-flach-aus pt letzt-koerper-hz)) (setq flat-p nil))) ; zurueck 3 Grad + ;; Motorstation (ohne GF2) + (setq pt (vfs-vf-exit pt 0.0 letzt-koerper-hz nil)) + (setq *vfl-acc-motorseite* (append *vfl-acc-motorseite* (list "rechts"))) + ;; GF2 = gemessener exakter Hoehen-Ausgleich (Abstieg bis zur Junction) + (setq z-aftermotor (caddr pt)) + (setq gf2-drop (- z-aftermotor z-junction)) + (if (< gf2-drop 0.0) + (progn + (princ (strcat "\n WARNUNG: Koerper klettert zu wenig - GF2<0 (fehlen " + (rtos (- gf2-drop) 2 1) " mm). Winkel zu flach; dZ nicht exakt.")) + (setq gf2-drop 0.0))) + (setq gf2-planar (/ gf2-drop (/ (sin (* 3.0 (/ pi 180.0))) (cos (* 3.0 (/ pi 180.0)))))) + (if (> gf2-planar 0.1) + (progn + (princ (strcat "\n GF2 (gemessener Ausgleich): L=" (rtos gf2-planar 2 1) " mm")) + (setq frame (vfl-insert-gf-segment pt letzt-koerper-hz gf2-planar 3)) + (vfl-acc-gf-seg (/ gf2-planar (cos (* 3.0 (/ pi 180.0)))) 3)) + (setq frame (vfl-frame-3grad pt letzt-koerper-hz))) + (setq hz letzt-koerper-hz) + + ;; --- B4: BACK-Lauf bauen (Segmente vf-ende+1 .. n-1) --- + (setq i (1+ vf-ende)) + (while (< i n) + (setq seg (nth i plan) typ (car seg) hz (cadr seg)) + (cond + ((= typ "Linie") + (setq winkel (nth 4 seg) deltaL (caddr seg)) + (if (= i last-line) (setq deltaL (- deltaL 300.0 ein))) + (setq deltaL (max 100.0 deltaL)) + (setq frame (vfl-insert-gf-segment (car frame) hz deltaL winkel)) + (vfl-acc-gf-seg (/ deltaL (cos (* winkel (/ pi 180.0)))) winkel) + (setq anzahl-gf (1+ anzahl-gf) letzt-winkel winkel)) + ((= typ "Bogen") + (setq bwinkel (nth 3 seg) bseite (nth 4 seg) klass (nth 5 seg)) + (if (= klass "GF-Bogen") + (setq frame (vfl-insert-gf-bogen-block frame bwinkel bseite)) + (princ "\n >>> [M3a] Vario-Kurve im Back-Lauf - uebersprungen.")))) + (setq i (1+ i))) + + ;; --- B5: Kettenende Separator + ES --- + (if (null frame) (progn (princ "\nNichts gebaut - Abbruch.") (exit))) + (setq frame (vfl-insert-es-element "GF" frame (car (frame->hz-winkel frame)) + (if letzt-winkel letzt-winkel 3.0) (caddr (car frame)) es-seite)) + + ;; ---------- Block + Ist-Ziel-Report ---------- + (setq hoehe-bis (caddr (car frame))) + (vfl-block-erstellen vfl-nummer anzahl-gf anzahl-vf (caddr startpunkt) hoehe-bis + (vfl-planar-dist startpunkt (car frame)) as-seite es-seite startpunkt lastEnt) + (setq soll-ende endpunkt ist-ende (car frame)) + (princ "\n\n=========================================") + (princ "\n>>> VF-Linienzug (Modus 3, M3a) gebaut! <<<") + (princ (strcat "\n Ziel (Soll): X=" (rtos (car soll-ende) 2 1) + " Y=" (rtos (cadr soll-ende) 2 1) " Z=" (rtos (caddr soll-ende) 2 1))) + (princ (strcat "\n ES (Ist): X=" (rtos (car ist-ende) 2 1) + " Y=" (rtos (cadr ist-ende) 2 1) " Z=" (rtos (caddr ist-ende) 2 1))) + (princ (strcat "\n Abweichung: dX=" (rtos (- (car ist-ende) (car soll-ende)) 2 1) + " dY=" (rtos (- (cadr ist-ende) (cadr soll-ende)) 2 1) + " dZ=" (rtos (- (caddr ist-ende) (caddr soll-ende)) 2 1) " mm")) + ;; Zerlegung bezogen auf die ES-KS_AUS-ACHSE: Quer (senkrecht zur Achse) soll + ;; ~0 sein (Endpunkt liegt auf der KS_AUS-Achse), Laengs = ES-Ausladung. + (setq end-hz (car (frame->hz-winkel frame))) ; KS_AUS-Achsrichtung + (setq dir-x (cos (* end-hz (/ pi 180.0))) dir-y (sin (* end-hz (/ pi 180.0)))) + (princ (strcat "\n Laengs KS_AUS-Achse: " + (rtos (+ (* (- (car ist-ende) (car soll-ende)) dir-x) + (* (- (cadr ist-ende) (cadr soll-ende)) dir-y)) 2 1) + " mm | Quer zur Achse (soll ~0): " + (rtos (+ (* (- (car ist-ende) (car soll-ende)) (- dir-y)) + (* (- (cadr ist-ende) (cadr soll-ende)) dir-x)) 2 1) " mm")) + (princ "\n (dZ=Ziel-Hoehe; Quer zur KS_AUS-Achse ~0 => Endpunkt liegt auf der Achse)") + (princ "\n=========================================") + (princ) +) + (vf-typ-registrieren "linienzug" 'vfl-berechne-platzhalter 'vfl-einfuege-platzhalter - "3D-Linienzug (gemischte Gefaellestrecke/VarioFoerderer-Kette, Modus 1: manuelle Eingabe)") + "3D-Linienzug (gemischte GF/VF-Kette; Modus 1 manuell / Modus 2 Ziel-Hoehe fertig / Modus 3 stueckweise in Arbeit)") (princ "\n>>> vf_linienzug.lsp geladen - Typ 'linienzug' registriert") (princ) diff --git a/data/ils/3D/Vario_Motorstation_500mm_links.dwg b/data/ils/3D/Vario_Motorstation_500mm_links.dwg new file mode 100644 index 0000000..643150f Binary files /dev/null and b/data/ils/3D/Vario_Motorstation_500mm_links.dwg differ diff --git a/data/ils/3D/Vario_Motorstation_500mm_rechts.dwg b/data/ils/3D/Vario_Motorstation_500mm_rechts.dwg new file mode 100644 index 0000000..dcc87aa Binary files /dev/null and b/data/ils/3D/Vario_Motorstation_500mm_rechts.dwg differ diff --git a/data/ils/3D/Vario_Umlenkstation_500mm_links.dwg b/data/ils/3D/Vario_Umlenkstation_500mm_links.dwg new file mode 100644 index 0000000..2c79c1f Binary files /dev/null and b/data/ils/3D/Vario_Umlenkstation_500mm_links.dwg differ diff --git a/data/ils/3D/Vario_Umlenkstation_500mm_rechts.dwg b/data/ils/3D/Vario_Umlenkstation_500mm_rechts.dwg new file mode 100644 index 0000000..66e2a56 Binary files /dev/null and b/data/ils/3D/Vario_Umlenkstation_500mm_rechts.dwg differ diff --git a/lang/de_DE.json b/lang/de_DE.json index dd9a524..5f398c9 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -11,6 +11,10 @@ "vfl-sep-vor-frage": "\n\nZusaetzlichen Separator VOR dem horizontalen Stueck?", "vfl-sep-nach-frage": "\n\nZusaetzlichen Separator NACH dem horizontalen Stueck?", "vfl-sep-an-stelle-frage": "\n\nZusaetzlichen Separator an dieser Stelle einfuegen?", + "vf-winkel-aus-header": "\n\nAUS-Element (AS_Element) - Winkel waehlen:", + "vf-winkel-ein-header": "\n\nEIN-Element (ES_Element) - Winkel waehlen:", + "vf-winkel-90": "\n 1 - 90 Grad", + "vf-winkel-30": "\n 2 - 30 Grad", "vfl-gf-bogen-winkel-header": "\n\nGF-Bogen - Winkel waehlen:", "vfl-gf-bogen-seite-header": "\nGF-Bogen - Seite waehlen:", "vfl-winkel-30": "\n 1 - 30 Grad", @@ -432,8 +436,8 @@ "extcall-skript-nicht-gefunden": "\n[SSG_LIB] FEHLER: %1 nicht gefunden!", "vfstart-core-nicht-gefunden": "\n[VarioFoerderer] WARNUNG: vf_core.lsp nicht gefunden - Befehle nicht verfuegbar.", "vfstart-etage-core-nicht-gefunden": "\n[EtageVarioFoerderer] WARNUNG: vf_core.lsp nicht gefunden - Befehle nicht verfuegbar.", - "gf-seite-aus-header": "\n\nAUS-Element (AS_Element_90_*) - Seite waehlen:", - "gf-seite-ein-header": "\n\nEIN-Element (ES_Element_90_*) - Seite waehlen:", + "gf-seite-aus-header": "\n\nAUS-Element (AS_Element_*) - Seite waehlen:", + "gf-seite-ein-header": "\n\nEIN-Element (ES_Element_*) - Seite waehlen:", "gf-seite-links": "\n 1 - Links", "gf-seite-rechts": "\n 2 - Rechts", "prompt-wahl-1-2": "\nIhre Wahl (1/2) [1]: ", diff --git a/lang/en_GB.json b/lang/en_GB.json index ee06e05..31d8f5c 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -11,6 +11,10 @@ "vfl-sep-vor-frage": "\n\nInsert additional separator BEFORE the horizontal section?", "vfl-sep-nach-frage": "\n\nInsert additional separator AFTER the horizontal section?", "vfl-sep-an-stelle-frage": "\n\nInsert additional separator at this point?", + "vf-winkel-aus-header": "\n\nOutput element (AS_Element) - choose angle:", + "vf-winkel-ein-header": "\n\nInput element (ES_Element) - choose angle:", + "vf-winkel-90": "\n 1 - 90 deg", + "vf-winkel-30": "\n 2 - 30 deg", "vfl-gf-bogen-winkel-header": "\n\nGF arc - choose angle:", "vfl-gf-bogen-seite-header": "\nGF arc - choose side:", "vfl-winkel-30": "\n 1 - 30 deg", @@ -432,8 +436,8 @@ "extcall-skript-nicht-gefunden": "\n[SSG_LIB] ERROR: %1 not found!", "vfstart-core-nicht-gefunden": "\n[VarioFoerderer] WARNING: vf_core.lsp not found - commands not available.", "vfstart-etage-core-nicht-gefunden": "\n[EtageVarioFoerderer] WARNING: vf_core.lsp not found - commands not available.", - "gf-seite-aus-header": "\n\nOutput element (AS_Element_90_*) - choose side:", - "gf-seite-ein-header": "\n\nInput element (ES_Element_90_*) - choose side:", + "gf-seite-aus-header": "\n\nOutput element (AS_Element_*) - choose side:", + "gf-seite-ein-header": "\n\nInput element (ES_Element_*) - choose side:", "gf-seite-links": "\n 1 - Left", "gf-seite-rechts": "\n 2 - Right", "prompt-wahl-1-2": "\nYour choice (1/2) [1]: ", diff --git a/lib/export_csv.py b/lib/export_csv.py index 56cca7a..e416c54 100644 --- a/lib/export_csv.py +++ b/lib/export_csv.py @@ -207,9 +207,9 @@ def build_kreisel_merkmale(block): hoehe_m = "0" return { "Abstand (Kreiselachse A - Kreiselachse) in Meter": abstand_m, - "Anzahl der Separatoren": attribs.get("N_SEPARATOREN", "2"), + "Anzahl der Separatoren": attribs.get("ANZAHL_SEPARATOR", attribs.get("N_SEPARATOREN", "2")), "Kreiselart": attribs.get("KREISELART", "STANDARD"), - "Anzahl der Scanner": attribs.get("N_SCANNER", "0"), + "Anzahl der Scanner": attribs.get("ANZAHL_SCANNER", attribs.get("N_SCANNER", "0")), "Anzahl der Rampen": attribs.get("N_RAMPEN", "0"), "Höhe in m": hoehe_m, "Drehrichtung": attribs.get("DREHRICHTUNG", "UZS"), diff --git a/lib/export_sivas.py b/lib/export_sivas.py index e8048f1..288fbc4 100644 --- a/lib/export_sivas.py +++ b/lib/export_sivas.py @@ -224,12 +224,12 @@ def build_kreisel_details(block): except (ValueError, TypeError): hoehe_m = "0" return { - "Abstand (Kreiselachse A - Kreiselachse) in Meter": abstand_m, - "Anzahl der Separatoren": attribs.get("N_SEPARATOREN", "2"), - "Kreiselart": attribs.get("KREISELART", "STANDARD"), - "Anzahl der Scanner": attribs.get("N_SCANNER", "0"), - "Anzahl der Rampen": attribs.get("N_RAMPEN", "0"), "Hoehe in m": hoehe_m, + "Kreiselart": attribs.get("KREISELART", "STANDARD"), + "Abstand (Kreiselachse A - Kreiselachse) in Meter": abstand_m, + "Anzahl der Separatoren": attribs.get("ANZAHL_SEPARATOR", attribs.get("N_SEPARATOREN", "2")), + "Anzahl der Scanner": attribs.get("ANZAHL_SCANNER", attribs.get("N_SCANNER", "0")), + "Anzahl der Rampen": attribs.get("N_RAMPEN", "0"), "Drehrichtung": attribs.get("DREHRICHTUNG", "UZS"), "Drehung": block.get("rotation", 0.0), } @@ -379,7 +379,7 @@ def process_blocks(blocks, lookup): kreisel_list.append(block) attribs = block.get("attribs", {}) counters["anzahl_kreisel"] += 1 - n_sep = int(attribs.get("N_SEPARATOREN", "2") or "2") + n_sep = int(attribs.get("ANZAHL_SEPARATOR", attribs.get("N_SEPARATOREN", "2")) or "2") kreiselart = attribs.get("KREISELART", "STANDARD") if kreiselart == "PIN": counters["anzahl_kreisel_pinband"] += 1 @@ -389,7 +389,7 @@ def process_blocks(blocks, lookup): abstand_mm = float(attribs.get("ABSTAND", "2300") or "2300") counters["laengen_kreisel"] += abstand_mm / 1000.0 counters["anzahl_separatoren"] += n_sep - counters["anzahl_scanner"] += int(attribs.get("N_SCANNER", "0") or "0") + counters["anzahl_scanner"] += int(attribs.get("ANZAHL_SCANNER", attribs.get("N_SCANNER", "0")) or "0") if counters["anzahl_streckengruppen"] == 0: counters["anzahl_streckengruppen"] = 1 continue diff --git a/tests/alltests.json b/tests/alltests.json index a98254e..8b70744 100644 --- a/tests/alltests.json +++ b/tests/alltests.json @@ -1,6 +1,7 @@ [ { "name": "kreisel", "save": "dxf", "module": "KreiselInsert" }, { "name": "foerderer", "save": "dwg", "module": "VarioFoerderer" }, + { "name": "linienzug", "save": "dwg", "module": "VarioFoerderer" }, { "name": "omniflo", "save": "dxf", "module": "OmniModulInsert" }, { "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" } ] diff --git a/tests/reference/kreisel_tests_sivas.csv b/tests/reference/kreisel_tests_sivas.csv index a2a0551..7f36c5b 100644 --- a/tests/reference/kreisel_tests_sivas.csv +++ b/tests/reference/kreisel_tests_sivas.csv @@ -1,10 +1,10 @@ Nr;TeileArt;SivasNummer;Dispogruppe;Anzahl;Laenge[mm];Hoehe[m];details -1;"ILS 2.0 Kreisel";"6200";20;1;;;{"Abstand (Kreiselachse A - Kreiselachse) in Meter": "3.0", "Anzahl der Separatoren": "2", "Kreiselart": "PIN", "Anzahl der Scanner": "0", "Anzahl der Rampen": "0", "Hoehe in m": "1.5", "Drehrichtung": "UZS", "Drehung": 30, "ID": "0008"} -2;"ILS 2.0 Kreisel";"6200";20;1;;;{"Abstand (Kreiselachse A - Kreiselachse) in Meter": "4.0", "Anzahl der Separatoren": "2", "Kreiselart": "PIN", "Anzahl der Scanner": "0", "Anzahl der Rampen": "0", "Hoehe in m": "1.5", "Drehrichtung": "UZS", "Drehung": 90, "ID": "0007"} -3;"ILS 2.0 Kreisel";"6200";20;1;;;{"Abstand (Kreiselachse A - Kreiselachse) in Meter": "3.0", "Anzahl der Separatoren": "2", "Kreiselart": "PIN", "Anzahl der Scanner": "0", "Anzahl der Rampen": "0", "Hoehe in m": "1.5", "Drehrichtung": "UZS", "Drehung": 0, "ID": "0006"} -4;"ILS 2.0 Kreisel";"6200";20;1;;;{"Abstand (Kreiselachse A - Kreiselachse) in Meter": "3.0", "Anzahl der Separatoren": "2", "Kreiselart": "STANDARD", "Anzahl der Scanner": "0", "Anzahl der Rampen": "0", "Hoehe in m": "3.0", "Drehrichtung": "UZS", "Drehung": 30, "ID": "0005"} -5;"ILS 2.0 Kreisel";"6200";20;1;;;{"Abstand (Kreiselachse A - Kreiselachse) in Meter": "5.0", "Anzahl der Separatoren": "2", "Kreiselart": "STANDARD", "Anzahl der Scanner": "0", "Anzahl der Rampen": "0", "Hoehe in m": "2.5", "Drehrichtung": "UZS", "Drehung": 270, "ID": "0004"} -6;"ILS 2.0 Kreisel";"6200";20;1;;;{"Abstand (Kreiselachse A - Kreiselachse) in Meter": "4.0", "Anzahl der Separatoren": "2", "Kreiselart": "STANDARD", "Anzahl der Scanner": "0", "Anzahl der Rampen": "0", "Hoehe in m": "2.5", "Drehrichtung": "UZS", "Drehung": 180, "ID": "0003"} -7;"ILS 2.0 Kreisel";"6200";20;1;;;{"Abstand (Kreiselachse A - Kreiselachse) in Meter": "3.0", "Anzahl der Separatoren": "2", "Kreiselart": "STANDARD", "Anzahl der Scanner": "0", "Anzahl der Rampen": "0", "Hoehe in m": "2.5", "Drehrichtung": "UZS", "Drehung": 90, "ID": "0002"} -8;"ILS 2.0 Kreisel";"6200";20;1;;;{"Abstand (Kreiselachse A - Kreiselachse) in Meter": "2.0", "Anzahl der Separatoren": "2", "Kreiselart": "STANDARD", "Anzahl der Scanner": "0", "Anzahl der Rampen": "0", "Hoehe in m": "2.5", "Drehrichtung": "UZS", "Drehung": 0, "ID": "0001"} +1;"ILS 2.0 Kreisel";"6200";20;1;;;{"Hoehe in m": "1.5", "Kreiselart": "PIN", "Abstand (Kreiselachse A - Kreiselachse) in Meter": "3.0", "Anzahl der Separatoren": "2", "Anzahl der Scanner": "0", "Anzahl der Rampen": "0", "Drehrichtung": "UZS", "Drehung": 30, "ID": "0008"} +2;"ILS 2.0 Kreisel";"6200";20;1;;;{"Hoehe in m": "1.5", "Kreiselart": "PIN", "Abstand (Kreiselachse A - Kreiselachse) in Meter": "4.0", "Anzahl der Separatoren": "2", "Anzahl der Scanner": "0", "Anzahl der Rampen": "0", "Drehrichtung": "UZS", "Drehung": 90, "ID": "0007"} +3;"ILS 2.0 Kreisel";"6200";20;1;;;{"Hoehe in m": "1.5", "Kreiselart": "PIN", "Abstand (Kreiselachse A - Kreiselachse) in Meter": "3.0", "Anzahl der Separatoren": "2", "Anzahl der Scanner": "0", "Anzahl der Rampen": "0", "Drehrichtung": "UZS", "Drehung": 0, "ID": "0006"} +4;"ILS 2.0 Kreisel";"6200";20;1;;;{"Hoehe in m": "3.0", "Kreiselart": "STANDARD", "Abstand (Kreiselachse A - Kreiselachse) in Meter": "3.0", "Anzahl der Separatoren": "2", "Anzahl der Scanner": "0", "Anzahl der Rampen": "0", "Drehrichtung": "UZS", "Drehung": 30, "ID": "0005"} +5;"ILS 2.0 Kreisel";"6200";20;1;;;{"Hoehe in m": "2.5", "Kreiselart": "STANDARD", "Abstand (Kreiselachse A - Kreiselachse) in Meter": "5.0", "Anzahl der Separatoren": "2", "Anzahl der Scanner": "0", "Anzahl der Rampen": "0", "Drehrichtung": "UZS", "Drehung": 270, "ID": "0004"} +6;"ILS 2.0 Kreisel";"6200";20;1;;;{"Hoehe in m": "2.5", "Kreiselart": "STANDARD", "Abstand (Kreiselachse A - Kreiselachse) in Meter": "4.0", "Anzahl der Separatoren": "2", "Anzahl der Scanner": "0", "Anzahl der Rampen": "0", "Drehrichtung": "UZS", "Drehung": 180, "ID": "0003"} +7;"ILS 2.0 Kreisel";"6200";20;1;;;{"Hoehe in m": "2.5", "Kreiselart": "STANDARD", "Abstand (Kreiselachse A - Kreiselachse) in Meter": "3.0", "Anzahl der Separatoren": "2", "Anzahl der Scanner": "0", "Anzahl der Rampen": "0", "Drehrichtung": "UZS", "Drehung": 90, "ID": "0002"} +8;"ILS 2.0 Kreisel";"6200";20;1;;;{"Hoehe in m": "2.5", "Kreiselart": "STANDARD", "Abstand (Kreiselachse A - Kreiselachse) in Meter": "2.0", "Anzahl der Separatoren": "2", "Anzahl der Scanner": "0", "Anzahl der Rampen": "0", "Drehrichtung": "UZS", "Drehung": 0, "ID": "0001"} 9;"ILS 2.0 Automation und Pneumatik - Gesamtanlage";"6269";20;1;;;{"Anzahl Kreisel": 8, "Anzahl Kreisel mit Pinband": 3, "Anzahl aller Separatoren in Kreisel mit PIN": 6, "Anzahl aller Separatoren in Kreisel ohne PIN": 10, "Anzahl Verbindungskreisel": 0, "Laengen aller Kreisel": 27.0, "Anzahl der Weichen": 0, "Anzahl der Eckraeder": 0, "Anzahl der einzelnen Gefaellestrecken": 0, "Anzahl der einzelnen Variofoerderer": 0, "Anzahl der Streckengruppen": 1, "Laengen aller Gefaellestrecken": 0.0, "Anzahl der Motoren Variofoerderer": 0, "Laengen aller Variofoerderer": 0.0, "Anzahl ETFs": 0, "Laengen aller ETFs": 0.0, "Anzahl aller Separatoren": 16, "Anzahl aller Scanner": 0, "Anzahl aller BTBeladung": 0, "Anzahl aller Abwurfstationen": 0} diff --git a/tests/test_linienzug.lsp b/tests/test_linienzug.lsp index b417ccb..85380a7 100644 --- a/tests/test_linienzug.lsp +++ b/tests/test_linienzug.lsp @@ -8,21 +8,25 @@ ;; Skript ab: die vier Eingabe-Funktionen werden NUR waehrend des Testlaufs ;; durch Mocks ersetzt (mit Save/Restore), danach wieder hergestellt. ;; +;; Die Eingabe-Sequenz (Skript) wird NICHT mehr im Code hartkodiert, sondern +;; aus tests/testdata/linienzug_tests.json geladen. Jedes JSON-Objekt mit +;; "typ" ist ein Eingabe-Schritt in exakter Aufruf-Reihenfolge: +;; {"typ":"point_abs","wert":[x,y,z]} absoluter Punkt (getpoint) +;; {"typ":"point_rel","dL":..,"hz":..} Basispunkt + dL entlang Richtung hz +;; {"typ":"real","wert":..} getreal +;; {"typ":"int","wert":..} getint +;; {"typ":"string","wert":".."} getstring +;; Das erste Objekt (ohne "typ") enthaelt Beschreibung/Erwartung (Metadaten). +;; ;; Voraussetzungen: ;; - SSG_LIB geladen (VarioFoerderer.lsp inkl. vf_linienzug.lsp mit ;; vfl-getpoint, Gefaellestrecke.lsp, ssg_core.lsp) ;; - Umgebungsvariable DXFMAKRO gesetzt ;; -;; Repliziertes Szenario (aus manuellem Test): -;; Start Z=4500, AS rechts -;; -> Neue Linie (Ab, Ziel-Z=1800, Winkel 45 Grad), GF ganz am Einlauf -;; -> Horizontal 900 mm mit Separator VOR + NACH (beide horizontal, 0 Grad) -;; -> Vario-Kurve 90 links aussen -;; -> Horizontal 250 mm (ohne Separatoren) -;; -> Vario-Kurve 90 links aussen -;; -> Option 3 (Kettenende definieren): Ziel-Z=4500, Winkel 45 Grad, ES rechts +;; Speichert (Konvention fuer test_run_all.lsp): +;; - tests/output/linienzug_results.json (via linienzug:export-results) ;; -;; Erwartetes Ergebnis (Ist-Ziel-Report): +;; Erwartetes Ergebnis (Ist-Ziel-Report, siehe Metadaten in JSON): ;; dX ~ 0, dZ ~ 0 (Ziel exakt getroffen), ;; dY ~ 637 mm = seitlicher Fussabdruck des ES_Element_90 (90-Grad-Schwenk; ;; KEIN Baufehler, sondern die Bauform des ES-Elements). @@ -63,7 +67,67 @@ (defun tlz-getint (a) (tlz-pop)) (defun tlz-getreal (a) (tlz-pop)) -(defun c:TEST_LINIENZUG ( / old-vgp old-gs old-gi old-gr err) +;; --- Parsierte JSON-Daten in die Skript-Queue uebersetzen --- +;; Erwartet die von ssg-load-json gelieferte Liste von Alists. +;; Nur Objekte mit "typ" sind Eingabe-Schritte; Objekte ohne "typ" +;; (Metadaten) werden uebersprungen. Die Reihenfolge bleibt erhalten. +;; Rueckgabe: Queue im Format, das die Mock-Funktionen erwarten. +(defun tlz-daten->queue (daten / q typ) + (setq q nil) + (foreach schritt daten + (setq typ (ssg-val schritt "typ")) + (cond + ((= typ "point_abs") + (setq q (cons (cons 'A (ssg-val schritt "wert")) q))) + ((= typ "point_rel") + (setq q (cons (list 'P (ssg-val schritt "dL") (ssg-val schritt "hz")) q))) + ((= typ "real") + (setq q (cons (float (ssg-val schritt "wert")) q))) + ((= typ "int") + (setq q (cons (ssg-val schritt "wert") q))) + ((= typ "string") + (setq q (cons (ssg-val schritt "wert") q))) + ) + ) + (reverse q) +) + +;; --- Metadaten-Objekt (Beschreibung/Erwartung) aus JSON-Daten holen --- +;; Liefert die erste Alist ohne "typ" oder nil. +(defun tlz-daten->meta (daten / meta) + (setq meta nil) + (foreach obj daten + (if (and (null meta) (null (ssg-val obj "typ"))) + (setq meta obj) + ) + ) + meta +) + +;; --- JSON-Export der Linienzug-Testergebnisse (Konvention fuer run-all) --- +(defun linienzug:export-results (tests-out-dir / out-json f) + (if (null *linienzug-test-results*) + (princ "\n Keine Linienzug-Ergebnisse vorhanden.") + (progn + (vl-mkdir tests-out-dir) + (setq out-json (strcat tests-out-dir "/linienzug_results.json")) + (setq f (open out-json "w")) + (if f + (progn + (write-line "[" f) + (write-line *linienzug-test-results* f) + (write-line "]" f) + (close f) + (princ (strcat "\n Ergebnisse: " out-json)) + ) + (princ (strcat "\n FEHLER: Kann " out-json " nicht schreiben!")) + ) + ) + ) +) + +(defun c:TEST_LINIENZUG ( / json-datei daten meta schritte-anz rest-anz status + old-vgp old-gs old-gi old-gr err) (if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek)) (if (null (car (atoms-family 1 '("VF-LINIENZUG-MODUS")))) (progn @@ -77,50 +141,28 @@ (princ "\n TEST_LINIENZUG - VF-Linienzug Replay-Test") (princ "\n================================================================") + ;; --- Eingabe-Skript aus JSON laden --- + (setq json-datei + (strcat (getenv "DXFMAKRO") "\\tests\\testdata\\linienzug_tests.json")) + (setq daten (ssg-load-json json-datei)) + (if (null daten) + (progn + (alert (strcat "Testdaten nicht gefunden:\n" json-datei)) + (exit) + ) + ) + (setq meta (tlz-daten->meta daten)) + (setq *tlz-q* (tlz-daten->queue daten)) + (setq schritte-anz (length *tlz-q*)) + (princ (strcat "\n Testdaten: " json-datei)) + (princ (strcat "\n Eingabe-Schritte: " (itoa schritte-anz))) + (if meta (princ (strcat "\n Szenario: " (ssg-val meta "beschreibung")))) + (ssg-start "TEST_LINIENZUG" '(("OSMODE") ("ATTREQ") ("ATTDIA"))) (setvar "OSMODE" 0) (setvar "ATTREQ" 0) (setvar "ATTDIA" 0) - ;; --- Skript der Eingaben in exakter Aufruf-Reihenfolge --- - (setq *tlz-q* - (list - (list 'A 0 0 0) ; getpoint Startpunkt der Kette - 4500.0 ; getreal Hoehe (Z) Startpunkt - "2" ; getstring AS-Seite (rechts) - "1" ; getstring Naechstes Element (Neue Linie) - (list 'P 5500 0) ; getpoint Endpunkt Linie (frei, +X, dL=5500) - 1800.0 ; getreal Hoehe (Z) Linienendpunkt - 1 ; getint Winkelwahl (45 Grad) - "2" ; getstring GF-Verteilung (alles am Einlauf) - "2" ; getstring Ist Endpunkt? (Nein) - "1" ; getstring Naechstes im VF (Horizontal) - (list 'P 900 0) ; getpoint Horizontal-Endpunkt (dL=900, hz=0) - "1" ; getstring Separator VOR? (Ja) - "1" ; getstring Separator NACH? (Ja) - "2" ; getstring Ist Endpunkt? (Nein) - "2" ; getstring Naechstes (Vario-Kurve) - 1 ; getint Kurve-Winkel (90) - "1" ; getstring Kurve-Seite (links) - "1" ; getstring Kurve-Variante (aussen) - "2" ; getstring Ist Endpunkt? (Nein) - "1" ; getstring Naechstes (Horizontal) - (list 'P 250 90) ; getpoint Horizontal-Endpunkt (dL=250, hz=90) - "2" ; getstring Separator VOR? (Nein) - "2" ; getstring Separator NACH? (Nein) - "2" ; getstring Ist Endpunkt? (Nein) - "2" ; getstring Naechstes (Vario-Kurve) - 1 ; getint Kurve-Winkel (90) - "1" ; getstring Kurve-Seite (links) - "1" ; getstring Kurve-Variante (aussen) - "3" ; getstring Ist Endpunkt? (Option 3 - Kettenende) - (list 'P 6215 180) ; getpoint Option-3-Endpunkt (dL=6215, hz=180) - 4500.0 ; getreal Hoehe (Z) Kettenende (ES) - 2 ; getint Option-3-Winkelwahl (45 Grad) - "2" ; getstring ES-Seite (rechts) - ) - ) - ;; --- Eingabe-Funktionen sichern und durch Mocks ersetzen --- ;; Punkte ueber den Produktions-Wrapper vfl-getpoint (feste 2-Arity), ;; getstring/getint/getreal (alle 1-argumentig) direkt. @@ -136,21 +178,40 @@ ;; --- Eingabe-Funktionen wiederherstellen (IMMER) --- (setq vfl-getpoint old-vgp getstring old-gs getint old-gi getreal old-gr) + (setq rest-anz (length *tlz-q*)) + (princ "\n================================================================") (if (vl-catch-all-error-p err) - (princ (strcat "\n TEST_LINIENZUG: FEHLER -> " - (vl-catch-all-error-message err))) (progn + (setq status "FEHLER") + (princ (strcat "\n TEST_LINIENZUG: FEHLER -> " + (vl-catch-all-error-message err)))) + (progn + (setq status (if (= rest-anz 0) "OK" "ABWEICHUNG")) (princ "\n TEST_LINIENZUG: Kette gebaut.") - (if *tlz-q* - (princ (strcat "\n WARNUNG: " (itoa (length *tlz-q*)) + (if (> rest-anz 0) + (princ (strcat "\n WARNUNG: " (itoa rest-anz) " Skript-Eintraege NICHT verbraucht (Ablauf weicht ab).")) (princ "\n Alle Skript-Eingaben verbraucht (Ablauf wie erwartet).")) - (princ "\n Erwartung Ist-Ziel: dX~0, dZ~0, dY~637 mm (ES_90-Seitenversatz).") + (if meta + (princ (strcat "\n Erwartung Ist-Ziel: dX~" + (itoa (ssg-val meta "erwartung_dx_mm")) ", dZ~" + (itoa (ssg-val meta "erwartung_dz_mm")) ", dY~" + (itoa (ssg-val meta "erwartung_dy_mm")) " mm (" + (ssg-val meta "erwartung_hinweis") ")."))) ) ) (princ "\n================================================================") + ;; --- Ergebnis fuer run-all als JSON-String ablegen --- + (setq *linienzug-test-results* + (strcat " {\n" + " \"test_id\": \"LZ_Standard\",\n" + " \"status\": \"" status "\",\n" + " \"schritte_gesamt\": " (itoa schritte-anz) ",\n" + " \"schritte_offen\": " (itoa rest-anz) "\n" + " }")) + (ssg-end) (princ) ) diff --git a/tests/testdata/linienzug_tests.json b/tests/testdata/linienzug_tests.json new file mode 100644 index 0000000..2acc209 --- /dev/null +++ b/tests/testdata/linienzug_tests.json @@ -0,0 +1,178 @@ +[ + { + "beschreibung": "Standard-Linienzug (Modus 1): Start Z4500 AS rechts -> Neue Linie Ab Z1800 45deg (GF am Einlauf) -> Horizontal 900 mit Separator VOR+NACH -> Vario-Kurve 90 links aussen -> Horizontal 250 -> Vario-Kurve 90 links aussen -> Kettenende (Option 3) Z4500 45deg ES rechts", + "erwartung_dx_mm": 0, + "erwartung_dy_mm": 637, + "erwartung_dz_mm": 0, + "erwartung_hinweis": "dY~637 = seitlicher Fussabdruck ES_Element_90 (Bauform, kein Baufehler)" + }, + { + "typ": "point_abs", + "wert": [0, 0, 0], + "kommentar": "getpoint Startpunkt der Kette" + }, + { + "typ": "real", + "wert": 4500.0, + "kommentar": "getreal Hoehe (Z) Startpunkt" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring AS-Seite (rechts)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes Element (Neue Linie)" + }, + { + "typ": "point_rel", + "dL": 5500, + "hz": 0, + "kommentar": "getpoint Endpunkt Linie (frei, +X, dL=5500)" + }, + { + "typ": "real", + "wert": 1800.0, + "kommentar": "getreal Hoehe (Z) Linienendpunkt" + }, + { + "typ": "int", + "wert": 1, + "kommentar": "getint Winkelwahl (45 Grad)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring GF-Verteilung (alles am Einlauf)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist Endpunkt? (Nein)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes im VF (Horizontal)" + }, + { + "typ": "point_rel", + "dL": 900, + "hz": 0, + "kommentar": "getpoint Horizontal-Endpunkt (dL=900, hz=0)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Separator VOR? (Ja)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Separator NACH? (Ja)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist Endpunkt? (Nein)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes (Vario-Kurve)" + }, + { + "typ": "int", + "wert": 1, + "kommentar": "getint Kurve-Winkel (90)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Kurve-Seite (links)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Kurve-Variante (aussen)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist Endpunkt? (Nein)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes (Horizontal)" + }, + { + "typ": "point_rel", + "dL": 250, + "hz": 90, + "kommentar": "getpoint Horizontal-Endpunkt (dL=250, hz=90)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR? (Nein)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH? (Nein)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist Endpunkt? (Nein)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes (Vario-Kurve)" + }, + { + "typ": "int", + "wert": 1, + "kommentar": "getint Kurve-Winkel (90)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Kurve-Seite (links)" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Kurve-Variante (aussen)" + }, + { + "typ": "string", + "wert": "3", + "kommentar": "getstring Ist Endpunkt? (Option 3 - Kettenende)" + }, + { + "typ": "point_rel", + "dL": 6215, + "hz": 180, + "kommentar": "getpoint Option-3-Endpunkt (dL=6215, hz=180)" + }, + { + "typ": "real", + "wert": 4500.0, + "kommentar": "getreal Hoehe (Z) Kettenende (ES)" + }, + { + "typ": "int", + "wert": 2, + "kommentar": "getint Option-3-Winkelwahl (45 Grad)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring ES-Seite (rechts)" + } +]