feat: Kettenfoerderer (Schraegfoerderer Stahl) als parametrische 3D-Baugruppe

Neues Modul KettenFoerderer.lsp: baut aus KF_UM_<winkel>, KF_LAENGE und
KF_AN_<winkel> (je KS_AUS auf KS_EIN) plus der Draufsicht KF_2D einen Block
KF_<n>. Die Bausteine werden aufgerichtet eingebaut (Rz*Rx(90)*Rz), die Hoehe
laeuft in Z; KF_2D liegt deckungsgleich mit der Kette. Der Einbauwinkel jedes
Folgebausteins wird aus der Richtung des KS_AUS seines Vorgaengers gemessen,
KF_LAENGE ueber die Einbauhoehe skaliert.

- Dialog (DCL-Vorlage, Typ als Radiobuttons), Hoehe per Zahl oder aus zwei
  Punkten, Doppelklick auf KF_n oeffnet KF_EDIT mit Neuaufbau
- eigenstaendiges Attribut-Schema, Auswahllisten mit Sivas-Optionstexten in
  data/json/kettenfoerderer.json
- Sivas-Export: Merkmale 350, 353-357, 359-361, 391, baugleiche Foerderer
  werden zu einer Zeile zusammengefasst (Artikelnummer 6120)
- ssg_ks_insert.lsp: neue Primitiven ks-xy-winkel, ks-block-messen,
  insert-block-ks-skaliert und insert-block-ks-matrix (freie 3x3-Rotation);
  ensure-block-loaded nimmt Bloecke ohne Dimensionssuffix unveraendert
- Menueeintrag KettenFoerderer aktiviert, toter Dummy OMNI_KettenFoerderer
  entfernt; KF_* im Export-Pattern, in der ID-Vergabe und im Dispatcher
  SSG_BLOCKEDIT (vor dem Omniflo-Zweig)
- Unit-Tests tu-tests-kf fuer die reinen Rechenkerne; Modul wird in der MNL
  vorgeladen, dbg-Schalter kf-zeit zur Laufzeitmessung

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 15:34:27 +02:00
parent fb567949be
commit b8e8d0b0a0
28 changed files with 2207 additions and 21 deletions
+283 -4
View File
@@ -72,11 +72,31 @@
;; Block unter seinem effektiven, dim-suffigierten Namen und GIBT DIESEN ZURUECK,
;; damit Aufrufer ihn zum Einfuegen (vla-InsertBlock/tblsearch) verwenden.
;; Meldung nur bei Fehlschlag.
(defun ensure-block-loaded (blockname / )
(if (not (ssg-ils-block-laden blockname))
(princ (ssg-textf "vfc-fehler-block-datei-fehlt" (list blockname)))
;; Sonderfall Bloecke OHNE Dimensionssuffix (data/omniflo/<Dim>/, z.B. die
;; Kettenfoerderer-Bausteine): ssg-ils-blockname wuerde "KF_UM_26" zu
;; "KF_UM_26_2D" machen - das ist die Konvention der FLACHEN Ablage data/ils,
;; die Omniflo-Ordner arbeiten stattdessen mit getrennten Verzeichnissen und
;; unsuffigierten Namen. Liegt die Definition unter dem Rohnamen bereits in
;; der Zeichnung und gibt es KEINE suffigierte Variante, wird der Rohname
;; unveraendert verwendet. Die Bedingung ist bewusst eng gefasst (BEIDE Teile
;; muessen zutreffen), damit die Dim-Aufloesung fuer alle data/ils-Bloecke
;; unberuehrt bleibt: dort existiert die suffigierte Variante, sobald der
;; Block geladen ist, und der erste Zweig greift nie.
;; Realer Fehler, den das behebt: die KS-Messung eines KF-Bausteins lief ins
;; Leere und meldete "KS_EIN/KS_AUS fehlt", obwohl beide KS im Block vorhanden
;; waren - gesucht wurde nur ein Blockname, den es nicht gibt.
(defun ensure-block-loaded (blockname / eff)
(setq eff (ssg-ils-blockname blockname))
(if (and (not (tblsearch "BLOCK" eff))
(tblsearch "BLOCK" blockname))
blockname
(progn
(if (not (ssg-ils-block-laden blockname))
(princ (ssg-textf "vfc-fehler-block-datei-fehlt" (list blockname)))
)
eff
)
)
(ssg-ils-blockname blockname)
)
;; ------------------------------------------------------------
@@ -379,6 +399,265 @@
)
)
;; ------------------------------------------------------------
;; KS-ORIENTIERUNG + SKALIERTE KS-EINFUEGUNG
;; ------------------------------------------------------------
;; Ergaenzt fuer den Kettenfoerderer (KettenFoerderer.lsp): dort ist die
;; Neigung KEIN freier Parameter, sondern steckt fest in der Geometrie der
;; Blockvarianten (KF_UM_26 gegen KF_UM_30). Der Einbauwinkel eines
;; Folgebausteins ergibt sich darum aus der RICHTUNG des KS_AUS seines
;; Vorgaengers. Dafuer reicht der bisher allein ausgewertete KS-URSPRUNG
;; nicht - die X-Achse des KS muss mit gelesen werden. extract-ks-from-block
;; liefert sie als (origin x-end y-end z-end) bereits mit, sie wurde bislang
;; nur von keinem Aufrufer genutzt.
;; Winkel der X-Achse eines KS-Eintrags in der XY-Ebene, in Grad (-180..180).
;; ks-eintrag = (origin x-end y-end z-end), also genau der cadr-Teil eines
;; extract-ks-from-block-Ergebnisses. Rueckgabe nil, wenn die X-Achse keine
;; XY-Laenge hat (senkrecht stehendes KS) oder der Eintrag unbrauchbar ist.
(defun ks-xy-winkel (ks-eintrag / org xend dx dy)
(if (and ks-eintrag (listp ks-eintrag) (>= (length ks-eintrag) 2)
(listp (car ks-eintrag)) (listp (cadr ks-eintrag)))
(progn
(setq org (car ks-eintrag) xend (cadr ks-eintrag))
(setq dx (- (car xend) (car org))
dy (- (cadr xend) (cadr org)))
(if (< (+ (* dx dx) (* dy dy)) 1e-12)
nil
(/ (* 180.0 (atan dy dx)) pi)))
nil))
;; Winkel auf -180..180 Grad normalisieren. Reiner Rechenkern.
(defun ks-winkel-normalisieren (w)
(if (null w)
nil
(progn
(setq w (float w))
(while (> w 180.0) (setq w (- w 360.0)))
(while (<= w -180.0) (setq w (+ w 360.0)))
w)))
;; Lokalen KS_AUS-KS_EIN-Versatz eines Blocks als (dx dy dz) liefern.
;; ks-data = Ergebnis von extract-ks-from-block/ks-block-messen.
;; Rueckgabe nil, wenn eines der beiden KS fehlt.
(defun ks-versatz (ks-data / ein aus)
(setq ein (cadr (assoc "KS_EIN" ks-data))
aus (cadr (assoc "KS_AUS" ks-data)))
(if (and ein aus)
(list (- (car (car aus)) (car (car ein)))
(- (cadr (car aus)) (cadr (car ein)))
(- (caddr (car aus)) (caddr (car ein))))
nil))
;; Misst KS_EIN/KS_AUS eines Blocks, ohne eine bleibende Einfuegung zu
;; hinterlassen: der Block wird bei Massstab 1 am Ursprung eingefuegt,
;; vermessen und wieder geloescht. extract-ks-from-block cached das Ergebnis
;; je Blockname, der zweite Aufruf kostet also nichts mehr.
;; Rueckgabe wie extract-ks-from-block (Blockkoordinaten), sonst nil.
(defun ks-block-messen (blockname / eff temp-obj daten)
(setq eff (ensure-block-loaded blockname))
(if (tblsearch "BLOCK" eff)
(progn
(setq temp-obj (vla-InsertBlock modelspace
(vlax-3D-point '(0 0 0)) eff 1.0 1.0 1.0 0))
(setq daten (extract-ks-from-block temp-obj))
(if (not (vlax-erased-p temp-obj)) (vla-Delete temp-obj))
daten)
nil))
;; --- Block per KS_EIN/KS_AUS einfuegen, in X SKALIERT (nur Rz-Rotation) ---
;; Wie insert-block-by-ks, zusaetzlich mit X-Massstab skala - fuer laengen-
;; variable Zwischenstuecke (Kettenfoerderer KF_LAENGE, dieselbe Rolle wie
;; die Staustrecke beim VarioFoerderer). Die lokalen KS-Punkte werden VOR der
;; Rotation in X mitskaliert; nur so liegt KS_EIN exakt auf einfuegepunkt und
;; der zurueckgegebene KS_AUS traegt den skalierten Versatz.
;; Rueckgabe: Weltpunkt von KS_AUS (fuer die Verkettung), sonst einfuegepunkt.
(defun insert-block-ks-skaliert (blockname einfuegepunkt hz skala /
block-obj ks-data ks-ein ks-aus rad-h chv shv
ein-x ein-y ein-z aus-x dx-loc dy-loc dz-loc
offset ausgang)
(if (or (null einfuegepunkt) (not (listp einfuegepunkt)))
(progn
(princ (ssg-textf "vfc-fehler-ungueltiger-einfuegepunkt" (list blockname)))
(exit))
)
(if (or (null skala) (<= skala 0.0)) (setq skala 1.0))
(setq blockname (ensure-block-loaded blockname))
(if (not (tblsearch "BLOCK" blockname))
(progn
(princ (ssg-textf "vfc-fehler-block-nicht-in-bibliothek-abgebrochen" (list blockname)))
(exit))
)
(princ (ssg-textf "vfc-fuege-block-ein-hz" (list blockname (rtos (float hz) 2 1) (chr 176))))
(setq rad-h (* (float hz) (/ pi 180.0)))
(setq chv (cos rad-h) shv (sin rad-h))
;; KS am UNSKALIERTEN Block messen (der Cache arbeitet je Blockname), die
;; lokalen Punkte danach selbst in X skalieren.
(setq ks-data (ks-block-messen blockname))
(setq ks-ein (cadr (assoc "KS_EIN" ks-data)))
(setq ks-aus (cadr (assoc "KS_AUS" ks-data)))
(setq block-obj (vla-InsertBlock modelspace
(vlax-3D-point '(0 0 0))
blockname skala 1.0 1.0 0))
(ssg-ils-block-auf-ebene block-obj blockname)
(if (> (abs hz) 0.0001)
(vla-TransformBy block-obj (vlax-tmatrix
(list (list chv (- shv) 0 0)
(list shv chv 0 0)
(list 0 0 1 0)
(list 0 0 0 1))))
)
(if (and ks-ein ks-aus)
(progn
(setq ein-x (* skala (car (car ks-ein)))
ein-y (cadr (car ks-ein))
ein-z (caddr (car ks-ein)))
(setq aus-x (* skala (car (car ks-aus))))
(setq offset (list
(- (car einfuegepunkt) (- (* chv ein-x) (* shv ein-y)))
(- (cadr einfuegepunkt) (+ (* shv ein-x) (* chv ein-y)))
(- (caddr einfuegepunkt) ein-z)))
(vla-Move block-obj
(vlax-3D-point '(0 0 0))
(vlax-3D-point offset))
(setq dx-loc (- aus-x ein-x))
(setq dy-loc (- (cadr (car ks-aus)) ein-y))
(setq dz-loc (- (caddr (car ks-aus)) ein-z))
(setq ausgang (list
(+ (car einfuegepunkt) (- (* chv dx-loc) (* shv dy-loc)))
(+ (cadr einfuegepunkt) (+ (* shv dx-loc) (* chv dy-loc)))
(+ (caddr einfuegepunkt) dz-loc)))
(ssg-sepliste-dbg
(strcat "insert-block-ks-skaliert " blockname
" hz=" (rtos (float hz) 2 2) " skala=" (rtos (float skala) 2 4)
" einfuegepunkt=" (ssg-sepliste-dbg-pt einfuegepunkt)
" offset(=Gruppencode 10)=" (ssg-sepliste-dbg-pt offset)
" ausgang=" (ssg-sepliste-dbg-pt ausgang)))
(princ (ssg-textf "vfc-ks-aus-xyz"
(list (rtos (car ausgang) 2 2) (rtos (cadr ausgang) 2 2) (rtos (caddr ausgang) 2 2))))
ausgang
)
(progn
(princ (ssg-text "vfc-warnung-ks-nicht-gefunden"))
einfuegepunkt
)
)
)
;; ------------------------------------------------------------
;; FREI ROTIERTE KS-EINFUEGUNG (beliebige 3x3-Matrix)
;; ------------------------------------------------------------
;; Verallgemeinerung von insert-block-by-ks/insert-block-ks-skaliert: statt
;; einer festen Rz- bzw. Rz*Ry-Drehung nimmt sie eine FERTIGE 3x3-Rotations-
;; matrix entgegen. Noetig geworden fuer AUFRECHT eingebaute Baugruppen: der
;; Kettenfoerderer ist in der XY-Ebene gezeichnet (X = Fahrtrichtung,
;; Y = Hoehe), soll aber im Modell stehen - seine Hoehe muss also nach Z. Das
;; ist eine Drehung um X, die weder insert-block-by-ks (nur Rz) noch
;; ssg-rot-matrix-zy (Rz*Ry) erzeugen kann.
;;
;; Bewusst matrixbasiert statt mit einem weiteren Winkelparameter: die
;; Aufrichtung ist nur EIN Anwendungsfall, und jeder weitere haette eine
;; weitere Spezialfunktion gebraucht. Den Aufrufern bleibt freigestellt,
;; welche Drehungen sie kombinieren (beim Kettenfoerderer
;; Rz(basis) * Rx(90) * Rz(eigen), siehe kf-matrix-aufrecht).
;; Elementare Rotationsmatrizen (3x3, Winkel in GRAD).
(defun ks-rot-x (grad / r c s)
(setq r (* (float grad) (/ pi 180.0)) c (cos r) s (sin r))
(list (list 1.0 0.0 0.0)
(list 0.0 c (- s))
(list 0.0 s c )))
(defun ks-rot-y (grad / r c s)
(setq r (* (float grad) (/ pi 180.0)) c (cos r) s (sin r))
(list (list c 0.0 s )
(list 0.0 1.0 0.0)
(list (- s) 0.0 c )))
(defun ks-rot-z (grad / r c s)
(setq r (* (float grad) (/ pi 180.0)) c (cos r) s (sin r))
(list (list c (- s) 0.0)
(list s c 0.0)
(list 0.0 0.0 1.0)))
;; 3x3-Rotation in die 4x4-Form bringen, die vla-TransformBy erwartet.
(defun ks-mat3->mat4 (R)
(list (append (car R) '(0.0))
(append (cadr R) '(0.0))
(append (caddr R) '(0.0))
'(0.0 0.0 0.0 1.0)))
;; --- Block per KS_EIN/KS_AUS einfuegen, frei rotiert und in X skaliert ---
;; blockname Rohname (wird ueber ensure-block-loaded aufgeloest)
;; einfuegepunkt Weltpunkt, auf den KS_EIN zu liegen kommt
;; R 3x3-Rotationsmatrix (Blockkoordinaten -> Welt)
;; skala X-Massstab (1.0 = unskaliert)
;; Rueckgabe: Weltpunkt von KS_AUS (fuer die Verkettung), sonst einfuegepunkt.
;;
;; Reihenfolge wie bei den uebrigen Primitiven: am Ursprung einfuegen,
;; skalieren, drehen, dann so verschieben, dass das GEDREHTE KS_EIN exakt auf
;; einfuegepunkt liegt. Die lokalen KS-Punkte werden dabei zuerst in X
;; mitskaliert - sonst zeigt der zurueckgegebene KS_AUS am skalierten Block
;; vorbei.
(defun insert-block-ks-matrix (blockname einfuegepunkt R skala /
block-obj ks-data ks-ein ks-aus
ein-lok aus-lok ein-welt aus-welt offset ausgang)
(if (or (null einfuegepunkt) (not (listp einfuegepunkt)))
(progn
(princ (ssg-textf "vfc-fehler-ungueltiger-einfuegepunkt" (list blockname)))
(exit))
)
(if (or (null skala) (<= skala 0.0)) (setq skala 1.0))
(setq blockname (ensure-block-loaded blockname))
(if (not (tblsearch "BLOCK" blockname))
(progn
(princ (ssg-textf "vfc-fehler-block-nicht-in-bibliothek-abgebrochen" (list blockname)))
(exit))
)
(setq ks-data (ks-block-messen blockname))
(setq ks-ein (cadr (assoc "KS_EIN" ks-data)))
(setq ks-aus (cadr (assoc "KS_AUS" ks-data)))
(setq block-obj (vla-InsertBlock modelspace
(vlax-3D-point '(0 0 0))
blockname skala 1.0 1.0 0))
(ssg-ils-block-auf-ebene block-obj blockname)
(vla-TransformBy block-obj (vlax-tmatrix (ks-mat3->mat4 R)))
(if (and ks-ein ks-aus)
(progn
;; lokale KS-Punkte, in X mitskaliert
(setq ein-lok (list (* skala (car (car ks-ein)))
(cadr (car ks-ein))
(caddr (car ks-ein))))
(setq aus-lok (list (* skala (car (car ks-aus)))
(cadr (car ks-aus))
(caddr (car ks-aus))))
(setq ein-welt (mat3-mul-vec3 R ein-lok))
(setq aus-welt (mat3-mul-vec3 R aus-lok))
(setq offset (list (- (car einfuegepunkt) (car ein-welt))
(- (cadr einfuegepunkt) (cadr ein-welt))
(- (caddr einfuegepunkt) (caddr ein-welt))))
(vla-Move block-obj
(vlax-3D-point '(0 0 0))
(vlax-3D-point offset))
(setq ausgang (list (+ (car einfuegepunkt) (- (car aus-welt) (car ein-welt)))
(+ (cadr einfuegepunkt) (- (cadr aus-welt) (cadr ein-welt)))
(+ (caddr einfuegepunkt) (- (caddr aus-welt) (caddr ein-welt)))))
(ssg-sepliste-dbg
(strcat "insert-block-ks-matrix " blockname
" skala=" (rtos (float skala) 2 4)
" einfuegepunkt=" (ssg-sepliste-dbg-pt einfuegepunkt)
" ks_ein_welt=" (ssg-sepliste-dbg-pt ein-welt)
" ks_aus_welt=" (ssg-sepliste-dbg-pt aus-welt)
" ausgang=" (ssg-sepliste-dbg-pt ausgang)))
ausgang
)
(progn
(princ (ssg-text "vfc-warnung-ks-nicht-gefunden"))
einfuegepunkt
)
)
)
;; ------------------------------------------------------------
;; SEPLISTE: Separator-/AS-/ES-Reihenfolge als XDATA am fertigen Wrapper
;; ------------------------------------------------------------