Merge remote-tracking branch 'origin/master'

Konflikt in menu/SSG_LIB.cui (generiert): Stand von origin/master
uebernommen, nur der Menuepunkt "Testanlage test_system" (MM_00210)
ergaenzt. SSG_LIB.mnu ohne Konflikt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 10:54:55 +02:00
25 changed files with 2529 additions and 574 deletions
+7 -6
View File
File diff suppressed because one or more lines are too long
+10 -1
View File
@@ -2496,6 +2496,8 @@
es-winkel (nth 3 xd)
z-start (atof (nth 4 xd))
deltaL (atof (nth 5 xd)))
;; aktuelle Z statt XDATA (Block evtl. in Z verschoben)
(if (caddr startpunkt) (setq z-start (caddr startpunkt)))
(setq as-seite (cond ((cdr (assoc "SEITE_AS" attribs))) ("links")))
(setq es-seite (cond ((cdr (assoc "SEITE_ES" attribs))) ("links")))
;; bestehende Geruest-Einstellungen erhalten
@@ -2599,6 +2601,10 @@
z-start (atof (cond ((cdr (assoc "HOEHE_VON_mm" attribs))) ("0")))
deltaL (atof (cond ((cdr (assoc "DELTA_L_mm" attribs))) ("0"))))
)
;; Einfuegehoehe = aktuelle Z des Einfuegepunkts (= Kettenstart). XDATA und
;; HOEHE_VON_mm sind veraltet, wenn der Block in Z verschoben wurde - OK im
;; Dialog baut dann direkt auf der neuen Hoehe neu auf (wie Kreisel/Eckrad).
(if (caddr startpunkt) (setq z-start (caddr startpunkt)))
(setq prefill
(list (cons "deltaL" deltaL)
@@ -2616,7 +2622,10 @@
;; AS-Seite + untere Hoehe fuer den optionalen parallelen VF-Bau
(setq as-seite (cond ((cdr (assoc "SEITE_AS" attribs))) ("links")))
(setq hoehe-bis (atof (cond ((cdr (assoc "HOEHE_BIS_mm" attribs))) ("0"))))
;; um dieselbe Z-Verschiebung korrigiert: BIS = Z - (VON_alt - BIS_alt)
(setq hoehe-bis (- z-start
(- (atof (cond ((cdr (assoc "HOEHE_VON_mm" attribs))) ("0")))
(atof (cond ((cdr (assoc "HOEHE_BIS_mm" attribs))) ("0"))))))
;; 2. Argument (hoehe-vorbelegung) explizit nil: BricsCAD verlangt die exakte
;; Argumentzahl (setzt fehlende NICHT automatisch auf nil). Im Edit kommt die
+616 -25
View File
@@ -193,10 +193,35 @@
(dbgreturn result)
)
;; Beschriftungstext (reiner Kern, in tests/test_unit.lsp getestet):
;; "KREISEL 1: Pufferkreisel PIN_4000" - mit eigenem Namen
;; "KREISEL 1: PIN_4000" - Name leer oder Vorgabe "Kreisel1"
(defun kreisel-label-text (nummer name kreiselart abstand)
(strcat "KREISEL " (itoa nummer) ": "
(if (or (null name) (= name "") (= name (strcat "Kreisel" (itoa nummer))))
""
(strcat name " "))
kreiselart "_" (rtos abstand 2 0))
)
;; Beschriftung in einer BESTEHENDEN Blockdefinition auf textStr setzen.
;; draw-module verwendet eine vorhandene Definition KREISEL_n_TYP_ABSTAND
;; wieder (Edit/Redraw) - ein geaenderter Name kaeme sonst nie in die
;; Beschriftung. Wirkt auf alle Referenzen dieser Definition.
(defun kreisel-label-aktualisieren (bname textStr / e ed)
(setq e (tblobjname "BLOCK" bname))
(while (setq e (entnext e))
(setq ed (entget e))
(if (and (= (cdr (assoc 0 ed)) "TEXT")
(/= (cdr (assoc 1 ed)) textStr))
(entmod (subst (cons 1 textStr) (assoc 1 ed) ed))
)
)
)
;; Beschriftungstext erzeugen
(defun kreisel-make-label (nummer abstand kreiselart / textStr insertPoint radius mitteX mitteY textHoehe lay)
;; Text: "KREISEL 1: PIN_4000"
(setq textStr (strcat "KREISEL " (itoa nummer) ": " kreiselart "_" (rtos abstand 2 0)))
(defun kreisel-make-label (nummer abstand kreiselart name / textStr insertPoint radius mitteX mitteY textHoehe lay)
(setq textStr (kreisel-label-text nummer name kreiselart abstand))
;; Geometrie berechnen
(setq radius (/ *kreisel-durchmesser* 2.0))
@@ -344,9 +369,11 @@
)
)
;; Bezeichnung automatisch setzen: "KreiselN"
(setq attribs (subst (cons "Bezeichnung" (strcat "Kreisel" (itoa kreisel-nummer)))
(assoc "Bezeichnung" attribs) attribs))
;; Bezeichnung: eigener Name (KreiselEdit) bleibt erhalten, sonst Vorgabe "KreiselN"
(if (member (cdr (assoc "Bezeichnung" attribs)) '(nil ""))
(setq attribs (subst (cons "Bezeichnung" (strcat "Kreisel" (itoa kreisel-nummer)))
(assoc "Bezeichnung" attribs) attribs))
)
;; --- HÖHE BESTIMMEN ---
;; Priorität 1: Aus basePoint (wenn 3D Punkt mit Z > 0)
@@ -445,7 +472,8 @@
)
;; BESCHRIFTUNG einfügen (vor Block-Erzeugung, damit sie im Block ist)
(kreisel-make-label kreisel-nummer abstand (if is-pin "PIN" "STANDARD") )
(kreisel-make-label kreisel-nummer abstand (if is-pin "PIN" "STANDARD")
(cdr (assoc "Bezeichnung" attribs)))
;; Unsichtbare Attribut-Definitionen erzeugen
(ssg-attrib-make-defs *kreisel-attrib-defs* 50.0)
@@ -472,7 +500,13 @@
(princ (ssg-textf "kreisel-neue-blockdef" (list bname)))
)
(princ (ssg-textf "kreisel-bestehende-blockdef" (list bname)))
(progn
(princ (ssg-textf "kreisel-bestehende-blockdef" (list bname)))
;; Beschriftung der wiederverwendeten Definition an den Namen anpassen
(kreisel-label-aktualisieren bname
(kreisel-label-text kreisel-nummer (cdr (assoc "Bezeichnung" attribs))
(if is-pin "PIN" "STANDARD") abstand))
)
)
;; --- Block am Zielpunkt einfuegen (ohne Attribut-Dialog) ---
@@ -1071,7 +1105,7 @@
;; 4. Hoehe aus Z-Koordinate oder manuell
;; --------------------------------------------
(defun c:ILS_Eckrad ( / ptTangent ptDir dx dy dist ux uy radius
centerPt rotation hoehe
centerPt rotation hoehe hoehe-aus-z attribs ergebnis
geruest-einzelmodul geruest-typ)
(dbgf "c:ILS_Eckrad")
@@ -1114,30 +1148,205 @@
(setq rotation (* (/ 180.0 pi) (atan dy dx)))
(dbg 'rotation)
;; 4. Hoehe bestimmen
(if (and (> (length ptTangent) 2) (caddr ptTangent) (> (caddr ptTangent) 0))
;; 4. Hoehe vorbelegen: aus Z des Beruehrpunkts, sonst Default
(setq hoehe-aus-z (and (> (length ptTangent) 2) (caddr ptTangent) (> (caddr ptTangent) 0)))
(if hoehe-aus-z
(progn
(setq hoehe (caddr ptTangent))
(princ (ssg-textf "kreisel-hoehe-aus-3dpunkt" (list (rtos hoehe 2 0))))
)
(progn
(setq hoehe (getreal (ssg-textf "kreisel-prompt-hoehe-z" (list (rtos *eckrad-default-hoehe* 2 0)))))
(if (null hoehe) (setq hoehe *eckrad-default-hoehe*))
)
(setq hoehe *eckrad-default-hoehe*)
)
(dbg 'hoehe)
;; 5. Geruest fuer Einzelmodul + Geruestoption abfragen
(setq geruest-einzelmodul (if (ssg-ques (ssg-text "kreisel-eckrad-geruest-frage") T) "1" "0"))
(setq geruest-typ (ssg-ask-geruest-typ))
(if (ssg-gui-p)
;; 5a. Dialog: Bezeichnung, Hoehe, Drehung, Drehrichtung, Geruest
(progn
(setq attribs (ssg-attrib-merge (list (cons "HOEHE" (rtos hoehe 2 0)))
*eckrad-attrib-defs*))
(setq ergebnis (eckrad-dialog attribs rotation))
(if (null ergebnis)
(progn (princ (ssg-text "kreisel-abgebrochen")) (dbgreturn nil) (exit))
)
(setq attribs (car ergebnis)
rotation (cdr ergebnis)
hoehe (atof (cdr (assoc "HOEHE" attribs))))
)
;; 5b. Ohne GUI (Tests): Konsolenabfrage wie bisher
(progn
(if (not hoehe-aus-z)
(progn
(setq hoehe (getreal (ssg-textf "kreisel-prompt-hoehe-z" (list (rtos *eckrad-default-hoehe* 2 0)))))
(if (null hoehe) (setq hoehe *eckrad-default-hoehe*))
)
)
(setq geruest-einzelmodul (if (ssg-ques (ssg-text "kreisel-eckrad-geruest-frage") T) "1" "0"))
(setq geruest-typ (ssg-ask-geruest-typ))
(setq attribs (list (cons "GERUEST_EINZELMODUL" geruest-einzelmodul)
(cons "GERUEST_TYP" geruest-typ)))
)
)
;; 6. Einfuegen (centerPt ist der Kreismittelpunkt)
(ils-eckrad-insert (list (car centerPt) (cadr centerPt) hoehe) rotation
(list (cons "GERUEST_EINZELMODUL" geruest-einzelmodul)
(cons "GERUEST_TYP" geruest-typ)))
(ils-eckrad-insert (list (car centerPt) (cadr centerPt) hoehe) rotation attribs)
(dbgreturn nil)
)
;; --------------------------------------------
;; eckrad-dialog-werte: Dialogeingaben -> (attribs . rotation)
;; Reiner Kern (ohne DCL/Zeichnung, in tests/test_unit.lsp getestet).
;; Parameter:
;; attribs - bisherige Attribut-alist ((TAG . WERT) ...)
;; name - Bezeichnung (String)
;; hoehe-str - Hoehe in mm (String aus edit_box)
;; drehung-str - Drehung in Grad (String aus edit_box)
;; drehr-idx - popup_list-Index Drehrichtung ("0"=UZS, "1"=GUZ)
;; einzelmodul - toggle-Wert "0"/"1"
;; geruest-idx - popup_list-Index in *ssg-geruest-optionen* (String)
;; Rueckgabe: (attribs-neu . rotation) oder nil bei ungueltiger Zahl.
;; Drehung wird auf [0, 360) normiert.
;; --------------------------------------------
(defun eckrad-dialog-werte (attribs name hoehe-str drehung-str drehr-idx
einzelmodul geruest-idx / hoehe drehung neu)
(setq hoehe (distof hoehe-str 2)
drehung (distof drehung-str 2))
(if (and hoehe drehung)
(progn
(setq drehung (rem (+ (rem drehung 360.0) 360.0) 360.0))
(setq neu (list (cons "Bezeichnung" name)
(cons "HOEHE" (rtos hoehe 2 0))
(cons "DREHUNG" (rtos drehung 2 1))
(cons "DREHRICHTUNG" (if (= drehr-idx "1") "GUZ" "UZS"))
(cons "GERUEST_EINZELMODUL" (if (= einzelmodul "1") "1" "0"))
(cons "GERUEST_TYP" (ssg-geruest-idx-to-typ (atoi geruest-idx)))))
;; Neue Werte ueber die bestehenden legen (attribs als Default-Schema)
(cons (ssg-attrib-merge neu (mapcar '(lambda (p) (list (car p) (cdr p))) attribs))
drehung)
)
)
)
;; --------------------------------------------
;; eckrad-dialog: DCL-Dialog fuer Einfuegen und Bearbeiten
;; Parameter:
;; attribs - Attribut-alist zur Vorbelegung (mit *eckrad-attrib-defs* gemergt)
;; rotation - aktuelle Drehung in Grad
;; Rueckgabe: (attribs-neu . rotation-neu) oder nil (Abbruch/DCL fehlt)
;; --------------------------------------------
(defun eckrad-dialog (attribs rotation / dcl-pfad dat eckrad-dlg-ergebnis)
(setq attribs (ssg-attrib-merge attribs *eckrad-attrib-defs*))
(setq dcl-pfad (strcat (getenv "DXFM_DCL") "/eckrad_edit.dcl"))
(setq dat (load_dialog dcl-pfad))
(if (or (< dat 0) (not (new_dialog "eckrad_edit" dat)))
(progn
(if (>= dat 0) (unload_dialog dat))
(alert (ssg-textf "kreisel-edit-dialog-fehlt" (list dcl-pfad)))
nil
)
(progn
(set_tile "name" (cdr (assoc "Bezeichnung" attribs)))
(set_tile "hoehe" (cdr (assoc "HOEHE" attribs)))
(set_tile "drehung" (rtos rotation 2 1))
(start_list "drehrichtung")
(add_list "UZS")
(add_list "GUZ")
(end_list)
(set_tile "drehrichtung" (if (equal (cdr (assoc "DREHRICHTUNG" attribs)) "GUZ") "1" "0"))
(set_tile "geruest_einzelmodul"
(if (equal (cdr (assoc "GERUEST_EINZELMODUL" attribs)) "1") "1" "0"))
(start_list "geruest_typ")
(foreach opt *ssg-geruest-optionen* (add_list opt))
(end_list)
(set_tile "geruest_typ" (itoa (ssg-geruest-typ-to-idx (cdr (assoc "GERUEST_TYP" attribs)))))
;; OK nur bei gueltigen Zahlen, sonst Fehlerzeile und Dialog bleibt offen
(action_tile "accept"
(strcat
"(setq eckrad-dlg-ergebnis (eckrad-dialog-werte attribs"
" (get_tile \"name\") (get_tile \"hoehe\") (get_tile \"drehung\")"
" (get_tile \"drehrichtung\") (get_tile \"geruest_einzelmodul\")"
" (get_tile \"geruest_typ\")))"
"(if eckrad-dlg-ergebnis (done_dialog 1)"
" (set_tile \"error\" (ssg-text \"kreisel-eckrad-ungueltige-zahl\")))"
)
)
(action_tile "cancel" "(setq eckrad-dlg-ergebnis nil) (done_dialog 0)")
(start_dialog)
(unload_dialog dat)
eckrad-dlg-ergebnis
)
)
)
;; -----------------------------------------------
;; EckradEdit: Bestehendes Eckrad per Dialog bearbeiten
;; (Doppelklick auf ECKRAD_n ueber SSG_BLOCKEDIT)
;;
;; Ablauf:
;; 1. Eckrad-Block auswaehlen (oder vorselektiert)
;; 2. Dialog mit aktuellen Werten oeffnen
;; 3. Bei OK: Block loeschen und mit gleicher Nummer neu einfuegen
;; -----------------------------------------------
(defun c:EckradEdit ( / ss sel ent ed attribs basePoint rotation ergebnis)
;; Implied Selection VOR ssg-start pruefen (ssg-start hebt Selektion auf)
(setq ss (ssget "I"))
(if (and ss (= (sslength ss) 1)
(= (cdr (assoc 0 (entget (ssname ss 0)))) "INSERT"))
(setq ent (ssname ss 0))
)
(ssg-start (ssg-text "kreisel-start-eckrad-edit") '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA")))
(setvar "OSMODE" 0)
(if (null ent)
(progn
(setq sel (entsel (ssg-text "kreisel-prompt-eckrad-auswaehlen")))
(if sel (setq ent (car sel)))
)
)
(if (null ent) (progn (ssg-end) (exit)))
(setq ed (entget ent))
(if (or (/= (cdr (assoc 0 ed)) "INSERT")
(not (wcmatch (strcase (cdr (assoc 2 ed))) "ECKRAD_*")))
(progn (ssg-emsg (ssg-text "kreisel-emsg-kein-eckrad-block")) (ssg-end) (exit))
)
;; Bestehende Werte auslesen, fehlende mit Defaults auffuellen
(setq attribs (ssg-attrib-merge (ssg-attrib-read ent) *eckrad-attrib-defs*))
(setq basePoint (cdr (assoc 10 ed)))
(setq rotation (* (/ 180.0 pi) (cdr (assoc 50 ed))))
;; HOEHE immer aus Z-Koordinate des Einfuegepunkts
(setq attribs (subst (cons "HOEHE" (rtos (if (caddr basePoint) (caddr basePoint) 0.0) 2 0))
(assoc "HOEHE" attribs) attribs))
(setq ergebnis (eckrad-dialog attribs rotation))
(if ergebnis
(progn
(setq attribs (car ergebnis)
rotation (cdr ergebnis))
(command "_.ERASE" ent "")
(ils-eckrad-insert (list (car basePoint) (cadr basePoint)
(atof (cdr (assoc "HOEHE" attribs))))
rotation attribs)
(princ (ssg-textf "kreisel-eckrad-edit-aktualisiert"
(list (cdr (assoc "Bezeichnung" attribs))
(cdr (assoc "HOEHE" attribs))
(rtos rotation 2 1))))
)
(princ (ssg-text "kreisel-abgebrochen"))
)
(ssg-end)
)
;; --------------------------------------------
;; ils-eckrad-insert: Erzeugt Eckrad als Compound-Block mit Attributen
;; Parameter:
@@ -1182,10 +1391,16 @@
(dbg 'z-hoehe)
(dbg 'block-hoehe)
;; Automatische Nummer vergeben
(setq eckrad-nummer (eckrad-next-number))
(setq attribs (subst (cons "NUMMER" (itoa eckrad-nummer))
(assoc "NUMMER" attribs) attribs))
;; NUMMER: Bestehende beibehalten (EckradEdit), sonst neue vergeben -
;; so behaelt ein bearbeitetes Eckrad seinen Blocknamen ECKRAD_n
(setq eckrad-nummer (atoi (cdr (assoc "NUMMER" attribs))))
(if (<= eckrad-nummer 0)
(progn
(setq eckrad-nummer (eckrad-next-number))
(setq attribs (subst (cons "NUMMER" (itoa eckrad-nummer))
(assoc "NUMMER" attribs) attribs))
)
)
(dbg 'eckrad-nummer)
;; Blockname: ECKRAD_1, ECKRAD_2, ...
@@ -1309,5 +1524,381 @@
(princ)
)
;; ============================================================
;; TEIL 5: BERUEHRPUNKTE / WEICHEN ZWISCHEN KREISELN UND ECKRAEDERN
;; ============================================================
;; Ermittelt, wo sich Kreisel (KREISEL_n) und Eckraeder (ECKRAD_n) gleicher
;; Hoehe beruehren - dort sitzt eine ILS-Weiche. Gerechnet wird NUR aus den
;; Blockparametern (Einfuegepunkt, Drehung, ABSTAND), nicht aus der
;; gezeichneten Geometrie - dasselbe Modell wie draw-module:
;; Kreisel: Rad AN8 bei P+R(r,0), Rad SP8 bei P+R(r+abstand,0), Laufprofile
;; = die beiden Tangenten bei lokal y=+-r. Frei ist je Rad nur der
;; aeussere Halbkreis (die "drei freien Seiten": vorn + beide
;; Flanken), die innere Haelfte liegt zwischen den Laufprofilen.
;; Eckrad: Vollkreis um P, ganz frei, kein Laufprofil.
;; Paar-Logik:
;; - Mindestabstand der Achsen (Kapsel-Modell) < 2r - tol -> KOLLISION
;; (Rad ragt hinein, Laufprofile kreuzen sich) - keine Weiche.
;; - Sonst je Rad: Rad/Rad mit |Abstand - 2r| <= tol bzw. Rad/Laufprofil
;; mit |Lot - r| <= tol, Beruehrpunkt auf einer freien Radseite ->
;; BERUEHRUNG ("Kreis/Kreis" bzw. "Kreis/Tangente", Namen wie im Python-
;; Export export_neighbors.compute_kreisel_touch_switches).
;; - Beruehren sich nur Laufprofile (Kreisel liegen Seite an Seite) ->
;; KOLLISION "Tangente/Tangente".
;; Die reinen Kerne (kreisel-bp-element/-paar/-berechnen) laufen ohne
;; Zeichnung und sind in tests/test_unit.lsp getestet.
(setq *kreisel-bp-app* "SSG_KR_WEICHE")
;; --- 2D-Vektorhelfer (Punkte als (x y)) ---
(defun kreisel-bp-plus (a b) (list (+ (car a) (car b)) (+ (cadr a) (cadr b))))
(defun kreisel-bp-minus (a b) (list (- (car a) (car b)) (- (cadr a) (cadr b))))
(defun kreisel-bp-mal (a s) (list (* (car a) s) (* (cadr a) s)))
(defun kreisel-bp-dot (a b) (+ (* (car a) (car b)) (* (cadr a) (cadr b))))
(defun kreisel-bp-abst (a b) (distance (list (car a) (cadr a)) (list (car b) (cadr b))))
(defun kreisel-bp-mitte (a b) (kreisel-bp-mal (kreisel-bp-plus a b) 0.5))
;; Element-Record aus Blockparametern (reiner Kern).
;; typ "KREISEL" oder "ECKRAD"
;; p Einfuegepunkt (x y [z]) - Kreisel: AN-seitige Tangente, Eckrad: Mitte
;; z Hoehe (Einfuege-Z)
;; rot Drehung in Grad
;; abstand Achsabstand AN8->SP8 (Kreisel), beim Eckrad ignoriert
;; r Radradius
;; id/handle/name nur zur Ausgabe/Zuordnung
;; Rueckgabe: alist mit "raeder" ((label mitte freie-richtung|nil) ...),
;; "profile" ((p1 p2) (p1 p2)) und "achse" (a b) fuer den Kapsel-Abstand.
(defun kreisel-bp-element (typ p z rot abstand r id handle name / u n p2 a b)
(setq u (list (cos (* pi (/ rot 180.0))) (sin (* pi (/ rot 180.0))))
n (list (- (cadr u)) (car u))
p2 (list (car p) (cadr p)))
(if (= typ "KREISEL")
(progn
(setq a (kreisel-bp-plus p2 (kreisel-bp-mal u r))
b (kreisel-bp-plus p2 (kreisel-bp-mal u (+ r abstand))))
(list (cons "typ" typ) (cons "id" id) (cons "handle" handle) (cons "name" name)
(cons "z" z) (cons "r" r)
(cons "achse" (list a b))
(cons "raeder" (list (list "AN8" a (kreisel-bp-mal u -1.0))
(list "SP8" b u)))
(cons "profile" (list (list (kreisel-bp-plus a (kreisel-bp-mal n r))
(kreisel-bp-plus b (kreisel-bp-mal n r)))
(list (kreisel-bp-minus a (kreisel-bp-mal n r))
(kreisel-bp-minus b (kreisel-bp-mal n r)))))))
(list (cons "typ" typ) (cons "id" id) (cons "handle" handle) (cons "name" name)
(cons "z" z) (cons "r" r)
(cons "achse" (list p2 p2))
(cons "raeder" (list (list "ECKRAD" p2 nil)))
(cons "profile" nil)))
)
(defun kreisel-bp-get (key elem) (cdr (assoc key elem)))
;; Naechstgelegene Punkte zweier Strecken [p1,q1] und [p2,q2] (Ericson,
;; Real-Time Collision Detection - wie export_neighbors._closest_pts_segments).
;; Rueckgabe (c1 c2).
(defun kreisel-bp-naechste-punkte (p1 q1 p2 q2 / eps d1 d2 rr a e f c b den s tt)
(setq eps 1e-9
d1 (kreisel-bp-minus q1 p1)
d2 (kreisel-bp-minus q2 p2)
rr (kreisel-bp-minus p1 p2)
a (kreisel-bp-dot d1 d1)
e (kreisel-bp-dot d2 d2)
f (kreisel-bp-dot d2 rr))
(cond
((and (<= a eps) (<= e eps)) (setq s 0.0 tt 0.0))
((<= a eps) (setq s 0.0 tt (max 0.0 (min 1.0 (/ f e)))))
(t
(setq c (kreisel-bp-dot d1 rr))
(if (<= e eps)
(setq tt 0.0 s (max 0.0 (min 1.0 (/ (- c) a))))
(progn
(setq b (kreisel-bp-dot d1 d2)
den (- (* a e) (* b b)))
(setq s (if (> den eps) (max 0.0 (min 1.0 (/ (- (* b f) (* c e)) den))) 0.0))
(setq tt (/ (+ (* b s) f) e))
(cond
((< tt 0.0) (setq tt 0.0 s (max 0.0 (min 1.0 (/ (- c) a)))))
((> tt 1.0) (setq tt 1.0 s (max 0.0 (min 1.0 (/ (- b c) a)))))
)
)
))
)
(list (kreisel-bp-plus p1 (kreisel-bp-mal d1 s))
(kreisel-bp-plus p2 (kreisel-bp-mal d2 tt)))
)
;; Liegt Punkt pt auf der freien Seite des Rades (label mitte richtung)?
;; Richtung nil = Vollkreis (Eckrad). Die Flanken (90 Grad) zaehlen mit.
(defun kreisel-bp-frei-p (rad pt tol)
(or (null (caddr rad))
(>= (kreisel-bp-dot (kreisel-bp-minus pt (cadr rad)) (caddr rad)) (- tol)))
)
;; Liegt pt genau auf einer Flanke (90 Grad zur Achse) eines Kreiselrades?
;; Dort beginnt das eigene Laufprofil - eine Beruehrung NUR an der Flanke
;; bedeutet parallel aneinanderliegende Kreisel, keine Weiche.
(defun kreisel-bp-flanke-p (rad pt tol)
(and (caddr rad)
(<= (abs (kreisel-bp-dot (kreisel-bp-minus pt (cadr rad)) (caddr rad))) tol))
)
;; Lotfusspunkt von c auf die Strecke (p1 p2). Rueckgabe (t fusspunkt), t
;; unbegrenzt (0 = p1, 1 = p2).
(defun kreisel-bp-lot (c p1 p2 / d l2 tt)
(setq d (kreisel-bp-minus p2 p1)
l2 (kreisel-bp-dot d d))
(setq tt (if (> l2 1e-12) (/ (kreisel-bp-dot (kreisel-bp-minus c p1) d) l2) 0.0))
(list tt (kreisel-bp-plus p1 (kreisel-bp-mal d tt)))
)
;; Ergebnis-Record (reiner Kern)
(defun kreisel-bp-treffer (status art ea eb pt rad-elem rad detail)
(list (cons "status" status) (cons "art" art)
(cons "a" ea) (cons "b" eb)
(cons "p" pt)
(cons "z" (/ (+ (kreisel-bp-get "z" ea) (kreisel-bp-get "z" eb)) 2.0))
(cons "achse_von" rad-elem)
;; Ausrichtung der Weiche: Radiale vom beruehrenden Rad zum Punkt
(cons "achse_rot" (if rad (* (/ 180.0 pi) (angle (cadr rad) pt)) 0.0))
(cons "detail" detail))
)
;; Alle Beruehrungen eines Rades von ew mit Raedern/Profilen von eo.
;; Rueckgabe: Liste von (pt art rad detail).
(defun kreisel-bp-rad-kontakte (ew eo tol / r res d pt lot tt l)
(setq r (kreisel-bp-get "r" ew))
(foreach rad (kreisel-bp-get "raeder" ew)
;; Rad/Rad
(foreach rad2 (kreisel-bp-get "raeder" eo)
(setq d (kreisel-bp-abst (cadr rad) (cadr rad2)))
(if (<= (abs (- d (+ r (kreisel-bp-get "r" eo)))) tol)
(progn
(setq pt (kreisel-bp-mitte (cadr rad) (cadr rad2)))
;; Beide Kreiselraeder nur an der Flanke = Kreisel Seite an Seite
(if (and (kreisel-bp-frei-p rad pt tol) (kreisel-bp-frei-p rad2 pt tol)
(not (and (kreisel-bp-flanke-p rad pt tol)
(kreisel-bp-flanke-p rad2 pt tol))))
(setq res (cons (list pt "Kreis/Kreis" rad
(strcat (car rad) "/" (car rad2)))
res))))))
;; Rad/Laufprofil
(foreach pr (kreisel-bp-get "profile" eo)
(setq lot (kreisel-bp-lot (cadr rad) (car pr) (cadr pr))
tt (car lot)
pt (cadr lot)
l (kreisel-bp-abst (car pr) (cadr pr)))
(if (and (> l 0.0)
(>= tt (- (/ tol l))) (<= tt (+ 1.0 (/ tol l)))
(<= (abs (- (kreisel-bp-abst (cadr rad) pt) r)) tol)
(kreisel-bp-frei-p rad pt tol)
;; Flanke an Laufprofil = Profile liegen parallel aufeinander
(not (kreisel-bp-flanke-p rad pt tol)))
(setq res (cons (list pt "Kreis/Tangente" rad (strcat (car rad) "/Profil")) res))))
)
(reverse res)
)
;; Ein Paar pruefen (reiner Kern). Rueckgabe: Liste von Treffer-Records
;; (leer = kein Kontakt). Unterschiedliche Hoehe wird VORHER ausgefiltert.
(defun kreisel-bp-paar (ea eb tol / ac bc cp dmin soll res kont pt doppelt)
(setq ac (kreisel-bp-get "achse" ea)
bc (kreisel-bp-get "achse" eb)
cp (kreisel-bp-naechste-punkte (car ac) (cadr ac) (car bc) (cadr bc))
dmin (kreisel-bp-abst (car cp) (cadr cp))
soll (+ (kreisel-bp-get "r" ea) (kreisel-bp-get "r" eb)))
(cond
;; Weit auseinander - nichts
((> dmin (+ soll tol)) nil)
;; Ueberschneidung - Kollision, keine Weiche
((< dmin (- soll tol))
(list (kreisel-bp-treffer "kollision" "Ueberschneidung" ea eb
(kreisel-bp-mitte (car cp) (cadr cp)) (kreisel-bp-get "id" ea) nil
(strcat "Achsabstand " (rtos dmin 2 1)))))
;; Beruehrung innerhalb Toleranz - Kontaktpunkte aufzaehlen
(t
(foreach k (append
(mapcar '(lambda (x) (cons ea x)) (kreisel-bp-rad-kontakte ea eb tol))
(mapcar '(lambda (x) (cons eb x)) (kreisel-bp-rad-kontakte eb ea tol)))
;; k = (elem pt art rad detail); Dubletten (gleicher Punkt) verwerfen
(setq pt (cadr k) doppelt nil)
(foreach t2 res
(if (<= (kreisel-bp-abst pt (kreisel-bp-get "p" t2)) (+ (* 2.0 tol) 1.0))
(setq doppelt T)))
(if (not doppelt)
(setq res (append res
(list (kreisel-bp-treffer "beruehrung" (nth 2 k) ea eb pt
(kreisel-bp-get "id" (car k)) (nth 3 k) (nth 4 k)))))))
(if res
res
;; Nur Laufprofile liegen aneinander (Seite an Seite) - keine Weiche
(list (kreisel-bp-treffer "kollision" "Tangente/Tangente" ea eb
(kreisel-bp-mitte (car cp) (cadr cp)) (kreisel-bp-get "id" ea) nil
"Laufprofile liegen aneinander"))))
)
)
;; Alle Paare auswerten (reiner Kern).
;; elemente = Liste von kreisel-bp-element-Records
;; Rueckgabe: (beruehrungen kollisionen)
(defun kreisel-bp-berechnen (elemente tol tol-z / rest ea ber kol)
(setq rest elemente)
(while rest
(setq ea (car rest) rest (cdr rest))
(foreach eb rest
(if (<= (abs (- (kreisel-bp-get "z" ea) (kreisel-bp-get "z" eb))) tol-z)
(foreach tr (kreisel-bp-paar ea eb tol)
(if (= (kreisel-bp-get "status" tr) "kollision")
(setq kol (cons tr kol))
(setq ber (cons tr ber)))))))
(list (reverse ber) (reverse kol))
)
;; ABSTAND eines Kreisels: Attribut, sonst letzter Token des Blocknamens
;; KREISEL_n_TYP_ABSTAND (reiner Kern).
(defun kreisel-bp-abstand (attr-wert bname / pos)
(cond
((and attr-wert (> (atof attr-wert) 0.0)) (atof attr-wert))
((setq pos (vl-string-position (ascii "_") bname 0 T))
(atof (substr bname (+ pos 2))))
(t 0.0))
)
;; Toleranzen aus data/json/component_defaults.json (Sektion kreisel)
(defun kreisel-bp-toleranz () (float (ssg-cfg-or "kreisel" "toleranz_beruehrung" 1.0)))
(defun kreisel-bp-toleranz-z () (float (ssg-cfg-or "kreisel" "toleranz_hoehe" 1.0)))
;; Alle KREISEL_n/ECKRAD_n im Modellbereich als Element-Records sammeln.
;; Bloecke auf gefrorenen Layern bleiben aussen vor (wie im Export).
(defun kreisel-bp-elemente-sammeln ( / ss i ent ed bname typ attribs p r res)
(setq r (/ *kreisel-durchmesser* 2.0))
(if (setq ss (ssget "_X" '((0 . "INSERT") (2 . "KREISEL_*,ECKRAD_*") (410 . "Model"))))
(progn
(setq i 0)
(while (setq ent (ssname ss i))
(setq ed (entget ent)
bname (strcase (cdr (assoc 2 ed)))
typ (if (wcmatch bname "KREISEL_*") "KREISEL" "ECKRAD"))
(if (not (and (car (atoms-family 1 '("SSG-ENT-LAYER-GEFROREN-P")))
(ssg-ent-layer-gefroren-p ent)))
(progn
(setq attribs (ssg-attrib-read ent)
p (cdr (assoc 10 ed)))
(setq res (cons (kreisel-bp-element typ p (if (caddr p) (caddr p) 0.0)
(* (/ 180.0 pi) (cdr (assoc 50 ed)))
(if (= typ "KREISEL")
(kreisel-bp-abstand (cdr (assoc "ABSTAND" attribs)) bname)
0.0)
r
(cond ((cdr (assoc "ID" attribs))) (""))
(cdr (assoc 5 ed))
(cond ((cdr (assoc "Bezeichnung" attribs))
(if (= (cdr (assoc "Bezeichnung" attribs)) "")
bname (cdr (assoc "Bezeichnung" attribs))))
(bname)))
res))))
(setq i (1+ i))
)
)
)
(reverse res)
)
;; Einstieg fuer Befehl UND Export: (beruehrungen kollisionen) der Zeichnung
(defun kreisel-bp-ermitteln ()
(kreisel-bp-berechnen (kreisel-bp-elemente-sammeln)
(kreisel-bp-toleranz) (kreisel-bp-toleranz-z))
)
;; Anzeigetext eines Elements: "Kreisel3 (ID 0007)" bzw. ohne ID mit Handle
(defun kreisel-bp-elem-text (e)
(strcat (kreisel-bp-get "name" e)
(if (/= (kreisel-bp-get "id" e) "")
(strcat " (ID " (kreisel-bp-get "id" e) ")")
(strcat " (#" (kreisel-bp-get "handle" e) ")")))
)
;; Alte Marker (Kreise mit XDATA *kreisel-bp-app*) entfernen. Rueckgabe: Anzahl
(defun kreisel-bp-marker-loeschen ( / ss i n)
(setq n 0)
(if (setq ss (ssget "_X" (list '(0 . "CIRCLE") (list -3 (list *kreisel-bp-app*)))))
(progn
(setq i 0)
(while (< i (sslength ss))
(entdel (ssname ss i))
(setq i (1+ i) n (1+ n)))))
n
)
;; Marker-Kreis fuer einen Treffer zeichnen
(defun kreisel-bp-marker (tr lay radius)
(if (not (tblsearch "APPID" *kreisel-bp-app*)) (regapp *kreisel-bp-app*))
(entmake (list '(0 . "CIRCLE") (cons 8 lay)
(list 10 (car (kreisel-bp-get "p" tr)) (cadr (kreisel-bp-get "p" tr))
(kreisel-bp-get "z" tr))
(cons 40 radius)
(list -3 (list *kreisel-bp-app*
(cons 1000 (kreisel-bp-get "status" tr))
(cons 1000 (kreisel-bp-get "art" tr))))))
)
;; Layer fuer Marker: cfg/layer.cfg [allgemein] key, sonst Rueckfall
(defun kreisel-bp-layer (key fallback farbe / lay)
(if (null (setq lay (ssg-layer-anlegen key nil)))
(progn (ssg-make-layer fallback farbe nil) (setq lay fallback)))
lay
)
;; Konsolenzeile fuer einen Treffer
(defun kreisel-bp-zeile (key nr tr)
(princ (ssg-textf key
(list nr
(kreisel-bp-elem-text (kreisel-bp-get "a" tr))
(kreisel-bp-elem-text (kreisel-bp-get "b" tr))
(strcat (kreisel-bp-get "art" tr) ", " (kreisel-bp-get "detail" tr))
(rtos (car (kreisel-bp-get "p" tr)) 2 1)
(rtos (cadr (kreisel-bp-get "p" tr)) 2 1)
(rtos (kreisel-bp-get "z" tr) 2 1))))
)
;; --------------------------------------------
;; KREISEL_WEICHEN: Beruehrpunkte (Weichen) und Kollisionen aller Kreisel/
;; Eckraeder ermitteln, in der Konsole auflisten und als Kreise markieren.
;; Ein erneuter Aufruf ersetzt die Marker des vorigen Laufs.
;; --------------------------------------------
(defun c:KREISEL_WEICHEN ( / erg ber kol nr lay-w lay-k radius)
(ssg-start (ssg-text "kreisel-bp-start") nil)
(setq erg (kreisel-bp-ermitteln)
ber (car erg)
kol (cadr erg))
(dbgmsg (strcat "KREISEL_WEICHEN: " (itoa (length ber)) " Beruehrungen, "
(itoa (length kol)) " Kollisionen"))
(kreisel-bp-marker-loeschen)
(princ (ssg-textf "kreisel-bp-summe"
(list (itoa (length ber)) (itoa (length kol)) (rtos (kreisel-bp-toleranz) 2 1))))
(setq radius (float (ssg-cfg-or "kreisel" "weiche_marker_radius" 50.0)))
(if ber
(setq lay-w (kreisel-bp-layer "kreisel_weiche" "ZZ_Kreisel_Weichen" "4")))
(if kol
(setq lay-k (kreisel-bp-layer "kreisel_kollision" "ZZ_Kreisel_Kollision" "1")))
(setq nr 0)
(foreach tr ber
(setq nr (1+ nr))
(kreisel-bp-zeile "kreisel-bp-zeile" (itoa nr) tr)
(kreisel-bp-marker tr lay-w radius))
(setq nr 0)
(foreach tr kol
(setq nr (1+ nr))
(kreisel-bp-zeile "kreisel-bp-kollision" (itoa nr) tr)
(kreisel-bp-marker tr lay-k (* 2.0 radius)))
(if (or ber kol)
(princ (ssg-textf "kreisel-bp-marker"
(list (itoa (+ (length ber) (length kol)))
(cond (lay-w) (lay-k))))))
(ssg-end)
(princ)
)
(princ "\n>>> KreiselInsert geladen")
(princ)
+4 -2
View File
@@ -684,7 +684,9 @@ AutoLISP-Implementierung fuer ILS Kreisel und Eckrad. Enthaelt alle Befehle fuer
| `c:KreiselLabelSetup` | Schriftart, Hoehe und Farbe fuer Kreisel-Beschriftung konfigurieren |
| `c:KreiselLabelPos` | Label-Offset (dx/dy) setzen |
| `c:KreiselLabelHoehe` | Label-Hoehe aendern |
| `c:ILS_Eckrad` | Eckrad an Tangentenpunkt einfuegen |
| `c:ILS_Eckrad` | Eckrad an Tangentenpunkt einfuegen, Parameter per DCL-Dialog (`eckrad_edit.dcl`; ohne GUI Konsolen-Rueckfall) |
| `c:EckradEdit` | Eckrad ueber DCL-Dialog bearbeiten (Bezeichnung, Hoehe, Drehung, Drehrichtung, Geruest; kein Abstand/Typ), Neuaufbau mit gleicher Nummer |
| `c:KREISEL_WEICHEN` | Beruehrpunkte (= ILS-Weichen) aller Kreisel/Eckraeder gleicher Hoehe ermitteln: Rad/Rad und Rad/Laufprofil auf der freien Radseite, Toleranz `toleranz_beruehrung`/`toleranz_hoehe` (component_defaults.json). Ueberschneidungen und Seite-an-Seite-Kreisel werden als Kollision gemeldet. Konsolenliste + Kreis-Marker (Layer `kreisel_weiche`/`kreisel_kollision`), ein neuer Lauf ersetzt die Marker. Derselbe Kern (`kreisel-bp-ermitteln`) liefert beim Export `kreisel_weichen.json` fuer `export_csv.py`/`export_sivas.py` |
---
@@ -784,7 +786,7 @@ Erkennt den Blocktyp des selektierten INSERT-Blocks und ruft den passenden Bearb
| Blockmuster | Aktion |
| --- | --- |
| `KREISEL_*` | `KreiselEdit` |
| `ECKRAD_*` | `BEDIT` (Block-Editor) |
| `ECKRAD_*` | `EckradEdit` |
| `GF_*` | `GEFAELLESTRECKE_EDIT` |
| `VF_*` | `VARIOFOERDERER_EDIT` |
| `GR_*` | Geruest – `BEDIT` (kein eigener Edit-Pfad; siehe `GeruestInsert.lsp`) |
+2 -2
View File
@@ -345,10 +345,10 @@
(c:KreiselEdit)
)
;; Eckrad-Bloecke -> BEDIT (kein eigener Edit-Dialog in LISP)
;; Eckrad-Bloecke -> EckradEdit
((wcmatch bname "ECKRAD_*")
(princ (ssg-textf "cmd-eckrad-bearbeiten" (list bname)))
(command "_.BEDIT" bname)
(c:EckradEdit)
)
;; Gefaellestrecke-Bloecke (GF_n) -> GEFAELLESTRECKE_EDIT (DCL-Dialog)
+9 -6
View File
@@ -139,15 +139,18 @@
;;; dieselben Muster wie csv:collect-export-blocks/export_csv.py.
;;; ------------------------------------------------------------
;; --- Ist nm ein Kreisel/Eckrad-, VarioFoerderer- oder Gefaellestrecke-
;; --- Ist nm ein Kreisel-, VarioFoerderer- oder Gefaellestrecke-
;; Compound-Block? Alle drei fuehren einheitlich die Attribute
;; ANZAHL_SCANNER/ANZAHL_SEPARATOR. ---
;; ANZAHL_SCANNER/ANZAHL_SEPARATOR. Das Eckrad faellt zwar unter
;; pattern_kreisel, hat diese Attribute aber nicht und zaehlt bewusst
;; KEINE Sensoren (Entscheidung 2026-10) - darum ausgenommen; sonst
;; "FEHLER beim Schreiben der Anzahl-Attribute an ECKRAD_n". ---
(defun cs-carrier-p (nm / up)
(setq up (strcase nm))
(or (wcmatch up (export:pattern "pattern_kreisel" "KR_*,KREISEL_*,ECKRAD_*"))
(wcmatch up (export:pattern "pattern_variofoerderer" "VF_*"))
(wcmatch up (export:pattern "pattern_gefaellestrecke" "GF_*"))
)
(and (not (wcmatch up (export:pattern "pattern_eckrad" "ECKRAD_*")))
(or (wcmatch up (export:pattern "pattern_kreisel" "KR_*,KREISEL_*,ECKRAD_*"))
(wcmatch up (export:pattern "pattern_variofoerderer" "VF_*"))
(wcmatch up (export:pattern "pattern_gefaellestrecke" "GF_*"))))
)
;;; ------------------------------------------------------------
+147
View File
@@ -149,6 +149,7 @@
(setq *export-filenames*
(list
(cons "raw_json_datei" "export_raw.json")
(cons "kreisel_weichen_datei" "kreisel_weichen.json")
(cons "sivas_script" "export_sivas.py")
(cons "sivas_csv" "sivas.csv")
(cons "csv_script" "export_csv.py")
@@ -2493,6 +2494,77 @@
;; Sammelt relevante Bloecke, schreibt JSON, ruft Python auf.
;; label = Anzeigename (z.B. "EXPORTCSV")
;; --- Export-Vorbereitung: Zeichnungszustand fuer den Export herrichten ---
;; --- Hoehen-Attribute aus der Z-Koordinate neu berechnen ---
;; Reiner Kern (in tests/test_unit.lsp getestet). Wird ein Kreisel/Eckrad/VF_n/
;; GF_n nachtraeglich in Z verschoben, sind seine Hoehen-Attribute veraltet.
;; Rechnet dieselben Formeln/Rundungen wie beim Bau:
;; KREISEL: HOEHE = (rtos z 2 0), MONTAGEHOEHE_m = (rtos z/1000 2 3) (draw-module)
;; ECKRAD : HOEHE = (rtos z 2 0) (ils-eckrad-insert)
;; GF : VON = (fix z), MONTAGEHOEHE_m = (rtos z/1000 2 3) (gf-block-erstellen)
;; VF : VON = (fix z), MONTAGEHOEHE_m = (rtos (VON+BIS)/2000 2 3) (vf_core/vf_linienzug)
;; GF/VF : BIS = VON - (VON_alt - BIS_alt). NICHT z - DELTA_H_mm: DELTA_H_mm
;; ist ohne Vorzeichen (steigende VF!) und separat abgeschnitten.
;; DELTA_H_mm nur als Rueckfall, wenn VON/BIS fehlen.
;; art = "KREISEL"/"ECKRAD"/"GF"/"VF", z = Einfuege-Z (VF/GF: Kettenstart),
;; attribs = bisherige Attribut-alist.
;; Rueckgabe: alist der neuen Werte, oder nil, wenn der Block nicht verschoben
;; wurde (Attribut passt noch zu z) - dann bleibt er bit-genau beim Bau-Wert.
(defun csv:hoehen-werte (art z attribs / alt von von-alt bis-alt diff bis)
(cond
((member art '("KREISEL" "ECKRAD"))
(setq alt (cdr (assoc "HOEHE" attribs)))
(if (not (and alt (/= alt "") (equal (atof alt) (atof (rtos z 2 0)) 1e-6)))
(if (= art "KREISEL")
(list (cons "HOEHE" (rtos z 2 0))
(cons "MONTAGEHOEHE_m" (rtos (/ z 1000.0) 2 3)))
(list (cons "HOEHE" (rtos z 2 0))))))
((member art '("GF" "VF"))
(setq von (fix z)
von-alt (cdr (assoc "HOEHE_VON_mm" attribs))
bis-alt (cdr (assoc "HOEHE_BIS_mm" attribs)))
(if (not (and von-alt (/= von-alt "") (= (atoi von-alt) von)))
(progn
(setq diff (if (and von-alt (/= von-alt "") bis-alt (/= bis-alt ""))
(- (atoi von-alt) (atoi bis-alt))
(atoi (cond ((cdr (assoc "DELTA_H_mm" attribs))) ("0")))))
(setq bis (- von diff))
(list (cons "HOEHE_VON_mm" (itoa von))
(cons "HOEHE_BIS_mm" (itoa bis))
(cons "MONTAGEHOEHE_m"
(if (= art "GF")
(rtos (/ z 1000.0) 2 3)
(rtos (/ (+ von bis) 2000.0) 2 3)))))))
)
)
;; Hoehen-Attribute aller Kreisel/Eckrad/VF_n/GF_n pruefen und bei in Z
;; verschobenen Bloecken neu setzen (csv:hoehen-werte). Laeuft als erster
;; Schritt von csv:export-vorbereiten, gilt also fuer EXPORTSIVAS und EXPORTCSV.
(defun csv:hoehen-aktualisieren ( / ss i ent ed bname art werte n)
(setq n 0)
(if (setq ss (ssget "_X" '((0 . "INSERT") (2 . "KREISEL_*,ECKRAD_*,VF_*,GF_*"))))
(progn
(setq i 0)
(while (setq ent (ssname ss i))
(setq ed (entget ent)
bname (strcase (cdr (assoc 2 ed))))
(setq art (cond ((wcmatch bname "KREISEL_*") "KREISEL")
((wcmatch bname "ECKRAD_*") "ECKRAD")
((wcmatch bname "GF_*") "GF")
((wcmatch bname "VF_*") "VF")))
(setq werte (csv:hoehen-werte art (cadddr (assoc 10 ed)) (ssg-attrib-read ent)))
(if werte
(progn
(ssg-attrib-set-on ent werte)
(setq n (1+ n))))
(setq i (1+ i))
)
)
)
(if (> n 0) (princ (ssg-textf "exp-hoehen-aktualisiert" (list n))))
n
)
;; Aus csv:run-export ausgelagert: alle Schritte, die VOR dem Sammeln/Schreiben
;; den Zeichnungszustand herrichten - IDs sicherstellen, Stellvertreter der
;; verpackten Kettenbausteine erzeugen und nummerieren, Omniflo-Attribute
@@ -2522,6 +2594,9 @@
(setq *csv-res-sammlung* nil)
(setq *csv-ketten* nil)
(setq *csv-sepliste-id-by-handle* nil)
;; 0. Hoehen-Attribute in Z verschobener Kreisel/Eckrad/VF/GF nachziehen,
;; damit Sivas- und Raw-Export die aktuelle Hoehe sehen.
(csv:hoehen-aktualisieren)
;; 1. IDs aller Bloecke sicherstellen und Duplikate korrigieren - hier nur
;; fuer die dauerhaften Bloecke, Stellvertreter gibt es noch keine.
(princ (ssg-textf "exp-check-ids" (list label)))
@@ -2671,6 +2746,74 @@
)
)
;; --- Kreisel-Beruehrpunkte (Weichen) fuer den Python-Export -------------
;; Schreibt <out-dir>/kreisel_weichen.json aus kreisel-bp-ermitteln
;; (KreiselInsert.lsp) - derselbe Kern wie der Befehl KREISEL_WEICHEN, nur
;; ohne Marker. Gelesen von export_neighbors.load_lisp_touch_switches
;; (export_csv.py) und export_sivas.py (Merkmal 4403). Partner werden ueber
;; ihr ID-Attribut (= teileid im Python-Export) UND den Handle genannt.
;; Eine alte Datei wird vorher geloescht, damit Python nie veraltete Punkte
;; liest, falls KreiselInsert nicht geladen ist (dann rechnet Python selbst).
;; Rueckgabe: Pfad der geschriebenen Datei oder nil.
(defun csv:kreisel-weichen-json-eintrag (tr / ea eb)
(setq ea (cdr (assoc "a" tr))
eb (cdr (assoc "b" tr)))
(strcat
" {\"a\":\"" (csv:json-escape (cdr (assoc "id" ea))) "\""
",\"b\":\"" (csv:json-escape (cdr (assoc "id" eb))) "\""
",\"a_handle\":\"" (csv:json-escape (cdr (assoc "handle" ea))) "\""
",\"b_handle\":\"" (csv:json-escape (cdr (assoc "handle" eb))) "\""
",\"a_typ\":\"" (cdr (assoc "typ" ea)) "\""
",\"b_typ\":\"" (cdr (assoc "typ" eb)) "\""
",\"x\":" (rtos (car (cdr (assoc "p" tr))) 2 4)
",\"y\":" (rtos (cadr (cdr (assoc "p" tr))) 2 4)
",\"z\":" (rtos (cdr (assoc "z" tr)) 2 4)
",\"art\":\"" (csv:json-escape (cdr (assoc "art" tr))) "\""
",\"detail\":\"" (csv:json-escape (cdr (assoc "detail" tr))) "\""
",\"achse_von\":\"" (csv:json-escape (cdr (assoc "achse_von" tr))) "\""
",\"achse_rot_grad\":" (rtos (cdr (assoc "achse_rot" tr)) 2 4)
"}")
)
(defun csv:kreisel-weichen-json-liste (fh treffer / first)
(setq first T)
(foreach tr treffer
(if (not first) (write-line "," fh))
(princ (csv:kreisel-weichen-json-eintrag tr) fh)
(setq first nil))
(if treffer (write-line "" fh))
)
(defun csv:kreisel-weichen-schreiben (label out-dir / pfad erg fh)
(setq pfad (strcat out-dir "/" (export:filename "kreisel_weichen_datei")))
(if (findfile pfad) (vl-file-delete pfad))
(if (car (atoms-family 1 '("KREISEL-BP-ERMITTELN")))
(progn
(setq erg (kreisel-bp-ermitteln))
(if (setq fh (open pfad "w"))
(progn
(write-line "{" fh)
(write-line (strcat " \"toleranz_mm\": " (rtos (kreisel-bp-toleranz) 2 4) ",") fh)
(write-line " \"beruehrungen\": [" fh)
(csv:kreisel-weichen-json-liste fh (car erg))
(write-line " ]," fh)
(write-line " \"kollisionen\": [" fh)
(csv:kreisel-weichen-json-liste fh (cadr erg))
(write-line " ]" fh)
(write-line "}" fh)
(close fh)
(princ (ssg-textf "exp-kreisel-weichen"
(list label (itoa (length (car erg))) (itoa (length (cadr erg))) pfad)))
(if (cadr erg)
(princ (ssg-textf "exp-kreisel-kollision" (list label (itoa (length (cadr erg)))))))
pfad
)
(progn (princ (ssg-textf "exp-file-open-error" (list label pfad))) nil)
)
)
)
)
;; py-name = Python-Skript-Name (z.B. "export_csv.py")
;; csv-name = Ergebnis-CSV-Name (z.B. "export.csv")
;; include-bbox = T -> Bounding-Box je Block ermitteln und mit exportieren
@@ -2745,6 +2888,10 @@
(list (itoa *csv-bbox-fail-count*)
(cond (*csv-bbox-fail-first*) ("?"))))))
;; Kreisel-Beruehrpunkte (Weichen) neben die Raw-Datei schreiben -
;; die Python-Skripte uebernehmen sie statt selbst zu rechnen.
(csv:kreisel-weichen-schreiben label out-dir)
;; Python-Export-Skript aufrufen (ausgelagert, siehe oben).
(csv:python-aufrufen label out-dir out-pfad py-name csv-name)
)
+61 -3
View File
@@ -682,9 +682,67 @@
(defun vf-set-as-masse (winkel seite / m)
(setq m (vf-element-masse (strcat "AS_Element_" winkel "_" seite)))
(if m (setq aus-dx (car m) aus-dy (cadr m) aus-dz (caddr m))))
(defun vf-set-es-masse (winkel seite / m)
(setq m (vf-element-masse (strcat "ES_Element_" winkel "_" seite)))
(if m (setq ein-dx (car m) ein-dy (cadr m) ein-dz (caddr m))))
;; Fussabdruck eines Elements ENTLANG der Fahrtrichtung (Laengsanteil).
;;
;; Warum das nicht (car (vf-element-masse ...)) ist: vf-element-masse liefert
;; KS_EIN->KS_AUS im BLOCK-Koordinatensystem. Ein Element wird aber so gedreht
;; eingebaut, dass sein KS_EIN auf der Fahrtrichtung liegt (insert-block-ks-to-ks
;; bzw. insert-block-by-ks mit (hz - ein-ang)). Bei einem drehenden Element -
;; ES_Element_30 hat ein-ang = 120 Grad - landet dadurch ein TEIL der
;; Block-Y-Komponente auf der Fahrtrichtung, und die Block-X-Komponente allein
;; unterschaetzt den Laengsverbrauch massiv: beim ES_Element_30_rechts 242.6 mm
;; statt tatsaechlich 576.2 mm (realer Bug, Nutzer-Befund 2026-10-09: die Kette
;; ragte 333.65 mm ueber den gepickten Endpunkt hinaus).
;;
;; Die Projektion ist hz-UNABHAENGIG (der Einbauwinkel kuerzt sich heraus), sie
;; haengt nur am blockinternen ein-ang:
;; laengs = vx*cos(ein-ang) + vy*sin(ein-ang)
;; Gleiche Rechnung wie etage-laengs in vf_etage.lsp mit d = -ein-ang, dort fuer
;; die Rand-Bilanz des Etage-Typs.
;;
;; Rueckgabe: Laenge in mm, oder nil wenn der Block/sein KS fehlt (Aufrufer
;; faellt dann auf das alte Verhalten zurueck).
(defun vf-element-laengs (blockname / i rad)
(setq i (vf-element-ks-info blockname))
(if (null i)
nil
(progn
(setq rad (* (car i) (/ pi 180.0)))
(+ (* (caddr i) (cos rad)) (* (cadddr i) (sin rad))))))
;; Gegenstueck zu vf-element-laengs: Versatz QUER zur Fahrtrichtung.
;; quer = -vx*sin(ein-ang) + vy*cos(ein-ang)
;; Ebenfalls hz-unabhaengig. Beim ES_Element_30 sind das rund -52.5 mm - der
;; seitliche Versatz, mit dem das ES-KS_AUS neben der Fahrtachse landet. Er
;; wird NICHT ausgeglichen (der Hauptstrang bleibt gerade, Nutzer-Entscheidung
;; 2026-09-15), aber er muss BEKANNT sein: die Bahn des ES-KS_AUS verlaeuft um
;; genau diesen Betrag seitlich versetzt, und nur mit ihm trifft die
;; Schnittpunkt-Rechnung gegen eine Ziellinie die richtige Stelle.
(defun vf-element-quer (blockname / i rad)
(setq i (vf-element-ks-info blockname))
(if (null i)
nil
(progn
(setq rad (* (car i) (/ pi 180.0)))
(+ (* (- (caddr i)) (sin rad)) (* (cadddr i) (cos rad))))))
;; Setzt ein-dx/ein-dy/ein-dz (BLOCK-Koordinaten, fuer Quer-/Hoehenrechnungen)
;; und zusaetzlich ein-laengs: den Fussabdruck ENTLANG der Fahrtrichtung. Wer
;; eine Laenge von deltaL reserviert, nimmt ein-laengs - NICHT (abs ein-dx),
;; siehe vf-element-laengs.
(defun vf-set-es-masse (winkel seite / m blk)
(setq blk (strcat "ES_Element_" winkel "_" seite))
(setq m (vf-element-masse blk))
(if m (setq ein-dx (car m) ein-dy (cadr m) ein-dz (caddr m)))
;; Fallback auf das alte (zu kleine) Mass, wenn das KS nicht lesbar ist -
;; lieber etwas zu kurz reservieren als gar nicht bauen koennen.
(setq ein-laengs (vf-element-laengs blk))
(if (and (null ein-laengs) m) (setq ein-laengs (abs (car m))))
;; ein-quer: seitlicher Versatz des ES-KS_AUS zur Fahrtachse (siehe
;; vf-element-quer). Wird fuer die Ziellinien-Schnittpunktrechnung gebraucht.
(setq ein-quer (vf-element-quer blk))
(if (null ein-quer) (setq ein-quer 0.0))
ein-laengs)
;; ============================================================
+466 -28
View File
@@ -116,6 +116,36 @@
(defun vfl-as-winkel () (if (boundp '*vfl-as-winkel*) *vfl-as-winkel* "90"))
(defun vfl-es-winkel () (if (boundp '*vfl-es-winkel*) *vfl-es-winkel* "90"))
;; Laengs-Fussabdruck des ES-Elements, den ein Segment VORAB von deltaL
;; reservieren muss, damit die Kette am gepickten Punkt endet statt
;; darueber hinauszuragen.
;;
;; ein-laengs setzt vf-set-es-masse (vf_core.lsp) aus dem KS des Blocks; es ist
;; der Anteil ENTLANG der Fahrtrichtung. Frueher stand hier (abs ein-dx) - die
;; Block-X-Komponente - und die unterschaetzt ein DREHENDES ES-Element massiv:
;; beim ES_Element_30_rechts 242.6 mm statt 576.2 mm, die Kette ragte dadurch
;; 333.65 mm zu weit (Nutzer-Befund 2026-10-09). Siehe vf-element-laengs.
;;
;; Der seitliche Versatz (ein-dy) wird hier BEWUSST nicht ausgeglichen - der
;; Hauptstrang bleibt gerade (Nutzer-Entscheidung 2026-09-15, bestaetigt
;; 2026-10-09; vgl. vfl-richtung-mit-drehendem-fussabdruck).
;;
;; WICHTIG - Kopplung an die Null-Konvention: an rund zwei Dutzend Stellen
;; wird ein-dx bewusst auf 0 gesetzt (und spaeter restauriert), um einen
;; DOPPELTEN Abzug zu verhindern, wenn das ES-Element an anderer Stelle schon
;; beruecksichtigt ist. ein-laengs wird dort NICHT mitgefuehrt. Darum gilt
;; ein genulltes ein-dx hier als "nichts reservieren" und gewinnt vor
;; ein-laengs - sonst wuerde dieser Fix genau den Doppelabzug einbauen, den
;; jene Stellen vermeiden.
;;
;; Rueckfallkette, damit eine fehlende Messung nie einen (- deltaL nil)-Absturz
;; ausloest: genulltes ein-dx -> ein-laengs -> (abs ein-dx) -> 0.0.
(defun vfl-es-laengs ()
(cond ((and (numberp ein-dx) (< (abs ein-dx) 0.001)) 0.0)
((numberp ein-laengs) (abs ein-laengs))
((numberp ein-dx) (abs ein-dx))
(t 0.0)))
;; ============================================================
;; TEIL 0a-2: MELDUNGEN (statt alert im Bau-Pfad)
;; ============================================================
@@ -332,6 +362,21 @@
(list (cdr e)))
nil))
;; Art des naechsten Replay-Eintrags, OHNE ihn zu verbrauchen ("DL", "STR",
;; "REAL", ...) bzw. nil bei leerer Queue. STEP-Marker werden dabei wie in
;; vfl-replay-pop uebersprungen.
;;
;; Gebraucht fuer Abwaertskompatibilitaet: wird an einer Stelle eine NEUE
;; Frage eingefuehrt, fehlt deren Antwort im Journal aelterer Ketten. Statt
;; blind zu fragen (und dadurch den naechsten Wert als Antwort zu
;; verschlucken - ein Queue-Desync), kann der Aufrufer hier nachsehen, was
;; tatsaechlich als Naechstes ansteht, und die Frage bei einem Altbestand
;; ueberspringen.
(defun vfl-replay-peek-art ( / q)
(setq q *vfl-replay-queue*)
(while (and q (= (car (car q)) "STEP")) (setq q (cdr q)))
(if q (car (car q)) nil))
;; ============================================================
;; TEIL 0b-1b: GLIED-SCHEMA (single source of truth)
;; ============================================================
@@ -671,6 +716,69 @@
(setq r (vl-catch-all-apply 'vflw-wahl-impl (list prompt optionen default-idx)))
(if (vl-catch-all-error-p r) "" r))
;; RADIO-BUTTON-Variante des generischen Wahl-Dialogs.
;;
;; vflw-wahl zeigt eine Listbox; fuer kurze Entscheidungen (2-3 Optionen) sind
;; Radio-Buttons uebersichtlicher - man sieht alle Moeglichkeiten auf einen
;; Blick statt sie in einer Liste zu suchen (Nutzer-Wunsch 2026-10-14).
;;
;; Technik wie bei den Standard-/Etage-Radio-Dialogen (vf_standard.lsp,
;; Kommentar dort): Radio-Labels lassen sich nach new_dialog NICHT per
;; set_tile aendern - darum wird die DCL bei JEDEM Aufruf frisch geschrieben
;; (ssg-dcl-temp-schreiben) und die Labels direkt mit dem schon von ssg-text
;; aufgeloesten String befuellt. Das funktioniert dadurch in jeder Sprache,
;; ohne Sprachweiche. Wiederverwendet werden vfs-dcl-radio-gruppe-zeilen und
;; vfs-dcl-radio-idx-cond (vf_standard wird vor vf_linienzug geladen).
;;
;; Rueckgabe IDENTISCH zu vflw-wahl: Index als String ("1", "2", ...) bzw. ""
;; bei Abbruch/Zurueck (dann ist *vfl-wizard-abbruch-grund* gesetzt). Dadurch
;; ist sie ein direkter Ersatz und die Journal-Semantik bleibt unberuehrt.
(defun vflw-radio-wahl-impl (prompt optionen default-idx /
zeilen dcl-pfad dat idx ergebnis n sauber)
(setq n (length optionen))
;; Die Optionstexte kommen als Konsolenzeilen ("\n 1 - Ziellinie ..."):
;; vflw-clean schneidet Umbruch, fuehrende Leerzeichen und die Nummerierung
;; weg - im Dialog nummerieren die Radio-Buttons selbst. Genau wie in
;; vflw-wahl-impl, dort beim Fuellen der Listbox.
(setq sauber (mapcar '(lambda (o) (vflw-clean o)) optionen))
(setq zeilen
(append
(list "vflw_radio_dyn : dialog {"
(strcat " label = \"" (ssg-dcl-label-escape (vflw-clean prompt)) "\";"))
(vfs-dcl-radio-gruppe-zeilen "vflwopt" "" sauber)
(list " ok_cancel;" "}")))
(setq dcl-pfad (ssg-dcl-temp-schreiben "vflwradio" zeilen))
(if (null dcl-pfad)
'FEHLER
(progn
(setq dat (load_dialog dcl-pfad))
(if (not (new_dialog "vflw_radio_dyn" dat))
(progn
(if (and dat (>= dat 0)) (unload_dialog dat))
(vl-file-delete dcl-pfad)
'FEHLER)
(progn
;; Vorauswahl setzen (default-idx ist 1-basiert, Tiles sind 0-basiert)
(set_tile (strcat "vflwopt_" (itoa (max 0 (1- (if default-idx default-idx 1))))) "1")
(action_tile "accept"
(strcat "(setq idx " (vfs-dcl-radio-idx-cond "vflwopt" n) ") (done_dialog 1)"))
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(vl-file-delete dcl-pfad)
(if (= ergebnis 1)
(itoa (1+ (if idx idx 0)))
(progn (setq *vfl-wizard-abbruch-grund* "abbrechen") "")))))))
;; Dispatcher: bevorzugt Radio, faellt bei technischem Fehler (Temp-Datei
;; nicht schreibbar/ladbar) auf die Listbox-Variante zurueck - genau wie die
;; -radio/-popup-Paare in vf_standard.lsp.
(defun vflw-radio-wahl (prompt optionen default-idx / r)
(setq r (vl-catch-all-apply 'vflw-radio-wahl-impl (list prompt optionen default-idx)))
(if (or (vl-catch-all-error-p r) (eq r 'FEHLER))
(vflw-wahl prompt optionen default-idx)
r))
;; ============================================================
;; TEIL 0b-4: GRUPPEN-DIALOGE - GEMEINSAME PUNKTWAHL-BAUSTEINE
;; ============================================================
@@ -1865,10 +1973,16 @@
(defun vfl-in-string (prompt)
(vfl-in-value-p "STR"
(function (lambda (v) (eq (type v) 'STR)))
(function (lambda ()
(function (lambda ( / txt)
(if (and (vfl-wizard-mode-p) *vflw-menu-optionen*)
(vflw-wahl (if *vflw-menu-frage* (ssg-text *vflw-menu-frage*) prompt)
*vflw-menu-optionen* *vflw-menu-default*)
(progn
(setq txt (if *vflw-menu-frage* (ssg-text *vflw-menu-frage*) prompt))
;; *vflw-menu-radio* setzt vfl-menu-radio fuer kurze Entscheidungen,
;; bei denen Radio-Buttons uebersichtlicher sind als eine Listbox.
;; Alle uebrigen Fragen bleiben unveraendert bei vflw-wahl.
(if (and (boundp '*vflw-menu-radio*) *vflw-menu-radio*)
(vflw-radio-wahl txt *vflw-menu-optionen* *vflw-menu-default*)
(vflw-wahl txt *vflw-menu-optionen* *vflw-menu-default*)))
(getstring prompt))))))
(defun vfl-in-real (prompt)
@@ -1902,6 +2016,20 @@
(setq *vflw-menu-default* default-idx)
(setq *vflw-menu-frage* frage-key)
(vfl-in-string prompt))
;; Wie vfl-menu, zeigt die Auswahl im Wizard-Modus aber als RADIO-BUTTONS
;; statt als Listbox - fuer kurze Entscheidungen (2-3 Optionen) uebersichtlicher
;; (Nutzer-Wunsch 2026-10-14). Journal-Semantik identisch zu vfl-menu: es wird
;; derselbe "STR"-Eintrag mit dem Auswahl-Index geschrieben. Das Flag ist
;; lokal gebunden und gilt daher nur fuer DIESEN einen Aufruf.
(defun vfl-menu-radio (prompt option-labels default-idx frage-key /
*vflw-menu-optionen* *vflw-menu-default* *vflw-menu-frage*
*vflw-menu-radio*)
(setq *vflw-menu-optionen* option-labels)
(setq *vflw-menu-default* default-idx)
(setq *vflw-menu-frage* frage-key)
(setq *vflw-menu-radio* T)
(vfl-in-string prompt))
(defun vfl-menu-int (prompt option-labels default-idx frage-key /
*vflw-menu-optionen* *vflw-menu-default* *vflw-menu-frage*)
(setq *vflw-menu-optionen* option-labels)
@@ -3206,23 +3334,68 @@
(make-frame-from-dir endpunkt (hz-winkel->xu hz winkel))
)
;; Ansicht auf Grundriss (Draufsicht, Welt-UCS) zoomen: nach jedem
;; Kettenglied mit Geometrieerzeugung wird die neu entstandene Strecke sonst
;; leicht aus dem sichtbaren Bereich oder in eine schraege 3D-Ansicht
;; hinauslaufen - der naechste Punkt (vfl-neue-linie-messen, siehe unten)
;; waere dann schwer/ungenau anzuwaehlen. _PLAN "_World" aendert nur die
;; ANSICHT, nicht das aktuell aktive BKS. Rein kosmetisch (per
;; vl-catch-all-apply abgesichert) - ein Fehler hier darf den eigentlichen
;; Kettenbau nie unterbrechen.
(defun vfl-view-refresh ( / )
;; Headless nur Kosten: die Ansicht sieht niemand, _PLAN/_ZOOM laufen aber
;; vor JEDER Laengeneingabe (vfl-neue-linie-messen) - im Batch reine Zeit.
;; Blickrichtung ist Draufsicht (Welt-Z)? Dann ist ein _PLAN ueberfluessig.
(defun vfl-draufsicht-p ( / vd)
(setq vd (vl-catch-all-apply 'getvar (list "VIEWDIR")))
(if (or (vl-catch-all-error-p vd) (null vd))
t ; unbekannt -> nicht eingreifen
(and (< (abs (car vd)) 1e-6)
(< (abs (cadr vd)) 1e-6)
(> (caddr vd) 0.0))))
;; Liegt pt komfortabel im sichtbaren Ausschnitt? VIEWCTR/VIEWSIZE geben Mitte
;; und HOEHE des Viewports, SCREENSIZE das Seitenverhaeltnis. Der Faktor 0.85
;; laesst einen Rand: ein Punkt direkt an der Fensterkante gilt als "fast
;; draussen", damit auch die noch zu bauende Strecke Platz hat.
;; Im Zweifel (Variable nicht lesbar) T -> lieber nichts tun als springen.
(defun vfl-punkt-sichtbar-p (pt / ctr size scr aspect)
(setq ctr (vl-catch-all-apply 'getvar (list "VIEWCTR"))
size (vl-catch-all-apply 'getvar (list "VIEWSIZE"))
scr (vl-catch-all-apply 'getvar (list "SCREENSIZE")))
(if (or (vl-catch-all-error-p ctr) (vl-catch-all-error-p size)
(vl-catch-all-error-p scr) (null ctr) (null size) (null pt)
(not (numberp size)) (<= size 0.0))
t
(progn
(setq aspect (if (and scr (numberp (car scr)) (numberp (cadr scr))
(> (cadr scr) 0))
(/ (float (car scr)) (float (cadr scr)))
1.0))
(and (< (abs (- (car pt) (car ctr))) (* 0.85 0.5 size aspect))
(< (abs (- (cadr pt) (cadr ctr))) (* 0.85 0.5 size))))))
;; Ansicht fuer die naechste Punktwahl vorbereiten - so WENIG wie moeglich.
;;
;; Frueher lief hier vor JEDER Laengeneingabe hart (command "_.PLAN" "_World")
;; + (command "_.ZOOM" "_Extents"). Bei einer grossen Anlage zoomt Extents auf
;; die GESAMTE Zeichnung: die Ansicht sprang nach jedem Klick weg, und der
;; Nutzer musste sich jedes Mal neu zurechtfinden (Nutzer-Wunsch 2026-10-09:
;; "beim Setzen eines Segments an dieser Position bleiben").
;;
;; Jetzt wird nur eingegriffen, wenn es noetig ist:
;; - _PLAN nur, wenn die Ansicht gar keine Draufsicht ist (in einer schraegen
;; 3D-Ansicht ist ein XY-Pick mehrdeutig - das war der eigentliche Zweck).
;; - verschoben wird nur, wenn pt aus dem sichtbaren Ausschnitt laeuft, und
;; dann per _ZOOM _Center: der Ausschnitt folgt dem Baupunkt, die
;; ZOOMSTUFE bleibt erhalten (anders als _Extents).
;; Ist der Punkt sichtbar und die Ansicht eine Draufsicht, passiert GAR NICHTS.
;;
;; Rein kosmetisch und per vl-catch-all-apply abgesichert - ein Fehler hier
;; darf den Kettenbau nie unterbrechen.
(defun vfl-view-refresh (pt / size)
;; Headless nur Kosten: die Ansicht sieht niemand.
(if (not (vfl-headless-p))
(vl-catch-all-apply
(function (lambda ()
(command "_.PLAN" "_World")
(command "_.ZOOM" "_Extents")))
nil))
(progn
(if (not (vfl-draufsicht-p))
(vl-catch-all-apply 'command (list "_.PLAN" "_World")))
(if (and pt (not (vfl-punkt-sichtbar-p pt)))
(progn
(setq size (vl-catch-all-apply 'getvar (list "VIEWSIZE")))
(if (or (vl-catch-all-error-p size) (not (numberp size)))
(vl-catch-all-apply 'command (list "_.ZOOM" "_Extents"))
;; Mitte auf den Baupunkt, Hoehe unveraendert = gleiche Zoomstufe.
(vl-catch-all-apply 'command
(list "_.ZOOM" "_Center" (list (car pt) (cadr pt)) size)))))))
(princ))
;; --- Hilfsfunktionen fuer die VF-Einheit ---
@@ -3257,6 +3430,65 @@
pt
)
;; Punkt-Abfrage MIT Achsenzwang: der Cursor wird auf die Anschlussachse
;; gezwungen, statt frei in jede Richtung zu laufen.
;;
;; Warum: das KS_AUS des zuletzt gebauten Teils legt die Richtung des KS_EIN
;; des naechsten Teils bereits fest - frei ist nur noch die LAENGE. Bisher
;; durfte der Nutzer irgendwohin klicken und vfl-in-abstand projizierte den
;; Punkt STILL auf die Fahrtrichtung: das Ergebnis entsprach nicht dem Klick,
;; und der Fehler fiel erst an der fertigen Kette auf (Nutzer-Befund
;; 2026-10-09, sichtbarer Knick zwischen zwei Segmenten).
;;
;; Technik: SNAPANG (Rasterwinkel) temporaer auf hz drehen und ORTHOMODE
;; einschalten - ORTHO richtet sich nach SNAPANG, der Cursor rastet dadurch
;; auf der Anschlussachse (und 90 Grad quer dazu) ein.
;;
;; NICHT ueber einen BKS-Wechsel: der erste Versuch drehte das BKS per
;; (command "_.UCS" "_3point" ...) - BricsCAD kennt diese Optionsbezeichnung
;; nicht, der Aufruf scheiterte, und weil er in vl-catch-all-apply steckte,
;; scheiterte er STILL: der Cursor blieb frei und nur die ORTHO-Rastung auf
;; die WELT-Achsen war aktiv (Nutzer-Befund 2026-10-09: "kann immer noch jede
;; Richtung waehlen"). SNAPANG braucht kein Kommando, keine Options-Syntax,
;; kein _Previous und laesst das BKS voellig unberuehrt - getpoint liefert
;; weiterhin dieselben Koordinaten wie bisher, es ist also KEINE
;; Koordinaten-Transformation noetig.
;;
;; Erzwungen wird NUR die horizontale Richtung (Nutzer-Entscheidung
;; 2026-10-09); die Hoehe bleibt frei, weil GF-/VF-Segmente ihre Neigung
;; selbst festlegen. SNAPANG wirkt ausschliesslich in der XY-Ebene - genau
;; das.
;;
;; Ohne base oder hz (erstes Kettensegment: Richtung noch frei) faellt die
;; Funktion unveraendert auf vfl-getpoint zurueck - ebenso ohne GUI bzw.
;; headless: dort gibt es keinen Cursor zu fuehren.
;; Rueckgabe: Punkt wie vfl-getpoint (gleiches Bezugssystem) oder nil.
(defun vfl-getpoint-achse (base prompt hz / alt-snapang alt-ortho pt)
(if (or (null base) (null hz) (not (ssg-gui-p)) (vfl-headless-p))
(vfl-getpoint base prompt)
(progn
(setq alt-snapang (vl-catch-all-apply 'getvar (list "SNAPANG")))
(setq alt-ortho (vl-catch-all-apply 'getvar (list "ORTHOMODE")))
(if (vl-catch-all-error-p alt-snapang) (setq alt-snapang nil))
(if (vl-catch-all-error-p alt-ortho) (setq alt-ortho nil))
;; SNAPANG erwartet RADIANT.
(vl-catch-all-apply 'setvar (list "SNAPANG" (* (float hz) (/ pi 180.0))))
(vl-catch-all-apply 'setvar (list "ORTHOMODE" 1))
;; Gecatcht, damit SNAPANG/ORTHOMODE auch bei ESC oder Fehler sicher
;; zurueckgesetzt werden - ein verdrehter Rasterwinkel wuerde sonst in
;; der Zeichnung des Nutzers stehen bleiben. Ein Fehler wird zu nil,
;; also exakt dem bisherigen Abbruch-Rueckgabewert von vfl-getpoint;
;; der Aufrufer laeuft damit in seinen vorhandenen nil-Zweig.
(setq pt (vl-catch-all-apply 'vfl-getpoint (list base prompt)))
(if alt-snapang (vl-catch-all-apply 'setvar (list "SNAPANG" alt-snapang)))
(if alt-ortho (vl-catch-all-apply 'setvar (list "ORTHOMODE" alt-ortho))
(vl-catch-all-apply 'setvar (list "ORTHOMODE" 0)))
(if (vl-catch-all-error-p pt) (setq pt nil))
pt
)
)
)
;; Vorschlagswert fuer die GF-Zielhoehe: die aktuelle Kettenhoehe (ist-hoehe)
;; UNVERAENDERT als Default anzubieten fuehrt in eine Falle - ein GF-Segment
;; MUSS fallen (0 Grad Gefaelle gibt es nicht), ein direkt uebernommener
@@ -3325,7 +3557,12 @@
;; picken (Gummiband). Der Punkt lebt nur hier lokal, er wird nie
;; journalisiert.
(setq popped (vflw-pending-pop))
(setq p2 (if popped (car popped) (vfl-getpoint p-akt prompt)))
;; Achsenzwang, sobald die Fahrtrichtung vom Vorgaenger feststeht
;; (hz-vorgabe): der Cursor rastet auf der Anschlussachse ein, statt
;; frei zu laufen und spaeter still projiziert zu werden. Beim ERSTEN
;; Segment (hz-vorgabe=nil) definiert der Klick die Richtung noch selbst
;; - dort faellt vfl-getpoint-achse von sich aus auf vfl-getpoint zurueck.
(setq p2 (if popped (car popped) (vfl-getpoint-achse p-akt prompt hz-vorgabe)))
;; *vfl-letzter-pick-z*: die Z-Hoehe DIESES Picks, fuer eine unmittelbar
;; nachfolgende Zielhoehe-Frage (siehe vfl-hoehe-endpunkt-abfragen) als
;; Vorschlag verfuegbar machen - vorher bei JEDEM Endpunkt-Pick
@@ -3360,6 +3597,101 @@
(vfl-journal-record "REAL" hz-aktuell))
(if deltaL (cons deltaL hz-aktuell) nil))
;; Laenge aus einer ZIELLINIE statt aus einem freien Klick.
;;
;; Steht die Fahrtrichtung fest (hz), ist die Laenge der letzten Strecke nicht
;; mehr frei: die Bahn des ES-KS_AUS - um quer seitlich versetzt - trifft die
;; angeklickte Linie in genau einem Punkt. Ein Klick kann diesen Punkt nur
;; annaehern (bei weit herausgezoomter Ansicht sind das schnell etliche mm),
;; die Rechnung trifft ihn exakt (Nutzer-Vorgabe 2026-10-09: "nur EIN Winkel,
;; dann den Schnittpunkt auf der Linie anklicken, Z kann ignoriert werden").
;;
;; Z wird bewusst ignoriert - rein planare X/Y-Rechnung; die Hoehe kommt
;; weiterhin aus dem Gefaellewinkel.
;;
;; JOURNAL: schreibt denselben "DL"-Eintrag wie vfl-in-abstand und liest beim
;; Replay aus derselben Queue. Dadurch ist KEIN neues Journal-Format noetig -
;; Doppelklick-Editieren, Replay und 2D/3D-Konvertierung sehen nur eine
;; (genauere) Laenge und muessen von Ziellinien nichts wissen. Die Bindung an
;; die Linie geht damit allerdings verloren: wird die Linie spaeter
;; verschoben, folgt die Kette NICHT (dafuer muesste die Linie selbst
;; journalisiert werden, wie Modus 2 es mit "OBJS" tut).
;;
;; Rueckgabe: Gesamtlaenge bis zum Schnittpunkt (der Aufrufer zieht davon wie
;; gewohnt Separator-/ES-Fussabdruck ab) oder nil bei Abbruch / wenn keine
;; brauchbare Linie getroffen wurde.
(defun vfl-in-abstand-ziellinie (p-akt hz quer / popped pts L fertig p2 weg)
(if *vfl-wizard-abbruch-grund*
(progn
(dbgmsg (strcat "ABBRUCH-TRIGGER (Ziellinie): " *vfl-wizard-abbruch-grund* " -> (exit)"))
(exit)))
;; Eigene Replay-Logik (ein Eintrag pro Aufruf) -> eigener Headless-Riegel:
;; leere Queue ist ein Desync, kein "Nutzer soll waehlen".
(if (and (null *vfl-replay-queue*) (vfl-headless-p))
(vfl-headless-abbruch "DL"))
;; --- Wahl des Eingabewegs (zwei gleichwertige Optionen) ---
;; ABWAERTSKOMPATIBILITAET: Ketten, die VOR der Einfuehrung dieser Frage
;; gebaut wurden, haben an dieser Stelle kein "STR" im Journal, sondern
;; direkt den "DL"-Wert. Wuerde hier blind gefragt, verschluckte die
;; Menue-Abfrage diesen Laengenwert als Antwort -> Queue-Desync und eine
;; falsch nachgebaute Kette. Darum beim Replay erst nachsehen, was
;; tatsaechlich ansteht: steht dort ein "DL", ist es ein Altbestand und die
;; Frage entfaellt (dessen Laenge entstand durch freie Punktwahl).
(if (and *vfl-replay-queue* (= (vfl-replay-peek-art) "DL"))
(setq weg "2")
(progn
(princ (ssg-text "vfl-endpunkt-modus-header"))
(princ (ssg-text "vfl-endpunkt-modus-opt-linie"))
(princ (ssg-text "vfl-endpunkt-modus-opt-punkt"))
(setq weg (vfl-menu-radio (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-endpunkt-modus-opt-linie")
(ssg-text "vfl-endpunkt-modus-opt-punkt")) 1
"vfl-endpunkt-modus-header"))))
(if *vfl-replay-queue*
(progn
(setq popped (vfl-replay-pop))
(setq L (if popped (car popped) nil)))
;; Live: wiederholen, bis eine brauchbare Eingabe vorliegt. Beide Wege
;; liefern EINE Laenge und schreiben daher denselben einzelnen
;; "DL"-Journaleintrag - der Replay sieht keinen Unterschied und muss von
;; Ziellinien nichts wissen. Abbruch (ESC) liefert nil; der Aufrufer
;; behandelt das wie jeden anderen Eingabe-Abbruch.
(progn
(setq fertig nil)
(while (not fertig)
(if (= weg "2")
;; --- Option 2: freie Punktwahl (wie bisher) ---
;; Punkt picken und auf die feststehende Fahrtrichtung projizieren.
;; Der Achsenzwang (vfl-getpoint-achse) gilt dabei weiter, der
;; Cursor rastet also auf der Anschlussachse ein. Noetig, wenn gar
;; keine Ziellinie existiert (Nutzer-Wunsch 2026-10-12).
(progn
(setq p2 (vfl-getpoint-achse p-akt
(ssg-text "vfl-prompt-endpunkt-fahrtrichtung") hz))
(if (null p2)
(setq L nil fertig t)
(progn
(setq L (vfl-projiziere-distanz p-akt p2 hz))
(if (< L 1.0)
(vfl-meldung (ssg-text "vfl-ziellinie-hinter-start"))
(setq fertig t)))))
;; --- Option 1: Ziellinie aus zwei Punkten, Schnittpunkt gerechnet ---
(progn
(setq pts (vfl-ziellinie-zwei-punkte))
(cond
;; ESC / Punkte identisch
((null pts) (setq L nil fertig t))
(t
(setq L (vfl-laenge-bis-ziellinie p-akt hz quer (car pts) (cadr pts)))
(cond
;; parallel zur Fahrtrichtung -> kein eindeutiger Schnittpunkt
((null L) (vfl-meldung (ssg-text "vfl-ziellinie-parallel")))
;; Schnittpunkt liegt hinter dem Start -> unbrauchbar
((< L 1.0) (vfl-meldung (ssg-text "vfl-ziellinie-hinter-start")))
(t (setq fertig t))))))))))
(vfl-journal-record (if L "DL" "NIL") L)
L)
;; Rueckgabe: (deltaL hz) oder nil bei Abbruch. Foerderer-Maximallaenge 25 m:
;; bei Ueberschreitung wird die Eingabe abgelehnt und der Endpunkt erneut
;; abgefragt.
@@ -3390,7 +3722,9 @@
(if p2 (vfl-hz-snappen-absolut (vfl-planar-hz p-akt p2)) nil)))))
(defun vfl-neue-linie-messen (p-akt hz-vorgabe / erg deltaL hz-aktuell ergebnis fertig)
(vfl-view-refresh)
;; Ansicht nur nachfuehren, wenn der Baupunkt aus dem Bild laeuft - sonst
;; bleibt der Ausschnitt stehen (siehe vfl-view-refresh).
(vfl-view-refresh p-akt)
(setq fertig nil)
(while (not fertig)
(setq erg (vfl-in-abstand p-akt hz-vorgabe
@@ -3422,6 +3756,92 @@
;; die Dreh­teller-Rotation, die insert-block-mixed-to-ks beim echten Einfuegen
;; anwendet, und liefern empirisch bestaetigt einen falschen Wert (~210mm
;; Schaetzung vs. ~420mm tatsaechlich noetiger Versatz bei AS_Element_30).
;; ============================================================
;; ZIELLINIE: X/Y-Lage des Kettenendes aus einer Linie ableiten
;; ============================================================
;; Steht die Fahrtrichtung fest (Frame des Vorgaengers) und soll die Kette mit
;; einem ES-Element auf einer vorgegebenen Linie enden, ist die Laenge NICHT
;; mehr frei: das ES-KS_AUS laeuft auf einer Geraden PARALLEL zur
;; Fahrtrichtung (um den ES-Querversatz seitlich versetzt), und die trifft die
;; Ziellinie in genau einem Punkt.
;;
;; WICHTIG - das ist bewusst eine REIN PLANARE (X/Y-)Rechnung, kein
;; 3D-Schnittpunkt (Nutzer-Praezisierung 2026-10-09): die HOEHE ergibt sich
;; weiterhin aus dem Gefaellewinkel (3 Grad), die Ziellinie bestimmt
;; ausschliesslich die X/Y-Lage. Die Z-Koordinaten der Linie werden daher
;; ignoriert.
;;
;; 2D-Kreuzprodukt (Determinante) zweier Richtungen - 0 heisst parallel.
(defun vfl-kreuz2 (ax ay bx by) (- (* ax by) (* ay bx)))
;; Horizontale Laenge L vom Startpunkt bis zu dem Punkt, an dem die um
;; quer seitlich versetzte Bahn in Richtung hz die Ziellinie (durch pa und pb)
;; schneidet.
;; p-start : Startpunkt der Bahn (X/Y zaehlen)
;; hz : Fahrtrichtung in Grad
;; quer : seitlicher Versatz der Bahn (ES-Querversatz; 0 = Achse selbst)
;; pa/pb : zwei Punkte der Ziellinie
;; Rueckgabe: L (kann negativ sein, der Aufrufer prueft das) oder nil, wenn
;; die Ziellinie parallel zur Fahrtrichtung liegt (kein eindeutiger Treffer).
(defun vfl-laenge-bis-ziellinie (p-start hz quer pa pb /
rad ux uy nx ny vx vy wx wy nenner)
(setq rad (* (float hz) (/ pi 180.0)))
(setq ux (cos rad) uy (sin rad)) ; Fahrtrichtung
(setq nx (- uy) ny ux) ; quer dazu
(setq vx (- (car pb) (car pa)) ; Richtung der Ziellinie
vy (- (cadr pb) (cadr pa)))
(setq nenner (vfl-kreuz2 ux uy vx vy))
(if (< (abs nenner) 1e-9)
nil ; parallel -> kein Schnittpunkt
(progn
;; w = pa - (p-start + quer*n) (Startpunkt der versetzten Bahn)
(setq wx (- (car pa) (+ (car p-start) (* quer nx)))
wy (- (cadr pa) (+ (cadr p-start) (* quer ny))))
(/ (vfl-kreuz2 wx wy vx vy) nenner))))
;; Ziellinie auswaehlen und als zwei Punkte zurueckgeben.
;;
;; entsel deckt BEIDE vom Nutzer gewuenschten Eingabewege in EINER Interaktion
;; ab (Nutzer-Wunsch 2026-10-09 "kann man nicht beide anbieten?"): es liefert
;; gleichzeitig das getroffene Objekt UND den Pickpunkt. Wird eine Linie
;; getroffen, wird exakt mit ihr gerechnet; wird nichts getroffen, liefert die
;; Funktion nil und der Aufrufer bleibt beim bisherigen Punkt-Verhalten.
;;
;; Statt den Objekttyp zu unterscheiden, wird die TANGENTE am Pickpunkt
;; genommen (vlax-curve-*): fuer eine LINE ist das exakt die Linie selbst, fuer
;; eine Polylinie genau das angeklickte Segment - ohne Segment-Suche von Hand.
;; Rueckgabe: (pa pb) oder nil.
;; Ziellinie durch ZWEI selbst gesetzte Punkte definieren.
;;
;; Allgemeiner als das Anklicken eines vorhandenen Objekts (Nutzer-Idee
;; 2026-10-12): es muss gar keine Linie gezeichnet sein. Existiert doch eine,
;; faengt man mit Objektfang zwei Punkte darauf und hat sie exakt erfasst -
;; dieser Weg deckt den Objekt-Fall also mit ab.
;;
;; WICHTIG - hier KEIN Achsenzwang (vfl-getpoint statt vfl-getpoint-achse):
;; die Ziellinie darf jede Richtung haben. Wuerde der Cursor auf die
;; Fahrtrichtung gezwungen, koennte man nur Linien PARALLEL dazu definieren -
;; und genau die haben keinen Schnittpunkt.
;;
;; Die beiden Punkte werden NICHT journalisiert: ins Journal geht allein die
;; daraus berechnete Laenge (ein "DL", siehe vfl-in-abstand-ziellinie). Darum
;; der rohe vfl-getpoint und nicht vfl-in-point.
;; Rueckgabe: (p1 p2) oder nil bei Abbruch.
(defun vfl-ziellinie-zwei-punkte ( / p1 p2)
(setq p1 (vfl-getpoint nil (ssg-text "vfl-prompt-ziellinie-p1")))
(if (null p1)
nil
(progn
;; Gummiband ab p1, damit die entstehende Ziellinie sichtbar ist.
(setq p2 (vfl-getpoint p1 (ssg-text "vfl-prompt-ziellinie-p2")))
(if (or (null p2)
(and (< (abs (- (car p2) (car p1))) 1e-9)
(< (abs (- (cadr p2) (cadr p1))) 1e-9)))
nil ; abgebrochen oder Punkte identisch
(list p1 p2)))))
;; Planare Projektionsdistanz von neu-basis zu ziel-punkt entlang hz - siehe
;; unten (unveraendert).
(defun vfl-projiziere-distanz (neu-basis ziel-punkt hz / rad)
(setq rad (* (float hz) (/ pi 180.0)))
(+ (* (- (car ziel-punkt) (car neu-basis)) (cos rad))
@@ -7006,9 +7426,11 @@
)
)
;; ES-Fussabdruck VORAB reservieren, siehe Kommentar bei
;; gf-kettenende-antwort oben.
;; gf-kettenende-antwort oben. ein-laengs = Anteil ENTLANG
;; der Fahrtrichtung (vf-element-laengs); (abs ein-dx) waere
;; die Block-X-Komponente und damit viel zu klein.
(if (= gf-kettenende-antwort "1")
(setq deltaL (- deltaL (abs ein-dx))))
(setq deltaL (- deltaL (vfl-es-laengs))))
(if (< deltaL 1.0)
(princ (ssg-text "vfl-fehler-linie-zu-kurz"))
(progn
@@ -7117,10 +7539,26 @@
)
(t
;; --- Zweig A: Endpunkt waehlen (heutiges Verhalten + Separator) ---
(setq linie-mess (vfl-neue-linie-messen p-aktuell
(if frame (car (frame->hz-winkel frame)) nil)))
(if (null linie-mess) (progn (princ (ssg-text "vfl-abgebrochen")) (exit)))
(setq deltaL (car linie-mess) hz-neu (cadr linie-mess))
;; Steht die Fahrtrichtung bereits fest (frame) UND endet die Kette
;; mit einem ES-Element, ist die Laenge nicht mehr frei: dann wird
;; eine ZIELLINIE angeklickt und der Schnittpunkt exakt gerechnet
;; (Nutzer-Vorgabe 2026-10-09 - ein Klick trifft den Schnittpunkt
;; nie genau). Trifft der Klick keine Linie, faellt der Ablauf auf
;; die gewohnte Punktwahl zurueck, damit kein Weg verbaut wird.
(setq hz-neu (if frame (car (frame->hz-winkel frame)) nil))
(if (and hz-neu (= gf-kettenende-antwort "1"))
;; Richtung steht fest UND die Kette endet mit ES -> Ziellinie.
;; Rueckgabe ist die Gesamtlaenge bis zum Schnittpunkt; der
;; Separator-/ES-Fussabdruck wird weiter unten wie gewohnt
;; abgezogen.
(progn
(setq deltaL (vfl-in-abstand-ziellinie p-aktuell hz-neu
(if (numberp ein-quer) ein-quer 0.0)))
(if (null deltaL) (progn (princ (ssg-text "vfl-abgebrochen")) (exit))))
(progn
(setq linie-mess (vfl-neue-linie-messen p-aktuell hz-neu))
(if (null linie-mess) (progn (princ (ssg-text "vfl-abgebrochen")) (exit)))
(setq deltaL (car linie-mess) hz-neu (cadr linie-mess))))
(if (< deltaL 1.0)
(princ (ssg-text "vfl-fehler-linie-zu-kurz"))
(progn
@@ -7188,7 +7626,7 @@
;; anders als die optionalen vor/nach-Separatoren oben
;; NICHT reserviert wurde).
(if (= gf-kettenende-antwort "1")
(setq deltaL (- deltaL (* *vfk-separator-laenge* (cos rad3)) (abs ein-dx))))
(setq deltaL (- deltaL (* *vfk-separator-laenge* (cos rad3)) (vfl-es-laengs))))
(if (< deltaL 1.0)
(progn (princ (ssg-text "vfl-fehler-linie-zu-kurz")) (setq gf-ok nil)))
(if gf-ok
+6
View File
@@ -152,6 +152,12 @@ kreisel_pinbereich = pinbereich, 3
# Beschriftungstext des Kreisels (Laenge/Bezeichnung).
kreisel_beschriftung = S_KRS_BESCHR, 7
# Marker der Kreisel-Beruehrpunkte (Weichen) und -Kollisionen, gezeichnet
# vom Befehl KREISEL_WEICHEN (Lisp/KreiselInsert.lsp, kreisel-bp-*). Reine
# Hilfsgeometrie, jeder Aufruf ersetzt die Marker des vorigen Laufs.
kreisel_weiche = ZZ_Kreisel_Weichen, 4
kreisel_kollision = ZZ_Kreisel_Kollision, 1
# Beschriftungstext der Vario-Foerderanlage (Hoehen-/Sektionslabel).
vf_beschriftung = VF_Beschriftung, 7
+18 -7
View File
@@ -64,13 +64,16 @@
# Code (bedingte Umbenennung bzw. Umrechnung mm->m).
[eckrad]
# Dieselben Sivas-Merkmalnummern wie bei Kreisel (siehe oben), aus
# Merkmalsstamm_in_Sivas.xlsx: 3992 "Geruest fuer Einzelmodul", 3993
# "Geruestoption fuer Einzelmodul". "ist_eins" liefert ein ECHTES bool
# (True/False), NICHT "Ja"/"Nein" wie ja_nein bei Kreisel/Gefaellestrecke -
# json.dumps schreibt dann true/false ohne Anfuehrungszeichen (bisheriges
# Verhalten von build_eckrad_details).
3992= GERUEST_EINZELMODUL : 0 | ist_eins
# Am Kreisel ausgerichtet, aber nur Merkmale, fuer die das Eckrad-Objekt ein
# Attribut hat (*eckrad-attrib-defs*); Merkmalnummern aus
# Merkmalsstamm_in_Sivas.xlsx. Hoehe: beim Eckrad 2950 "Anlagenhoehe in
# Meter" (NICHT 2952 "Kreiselhoehe" wie beim Kreisel), Wert aus HOEHE (mm),
# da das Eckrad kein MONTAGEHOEHE_m traegt. 3381 "Eckrad - zu
# fertigende Menge" steht wie 3511 beim Kreisel fest auf 1 (reale Stueckzahl
# in der Anzahl-Spalte). Separatoren (3382/3383) hat das Eckrad nicht.
2950= HOEHE : 0 | mm_zu_m
3381= : 1
3992= GERUEST_EINZELMODUL : 0 | ja_nein
3993= GERUEST_TYP : Schoenenberger Geruest | geruest_typ_anzeige
[geruest]
@@ -104,3 +107,11 @@ Farbe= FARBE
# ASCII-Attributwert (GERUEST_TYP) -> Sivas-Klartext mit Umlauten/Sonder-
# zeichen (Attribute selbst bleiben ASCII, siehe CLAUDE.md-Konvention).
Schoenenberger Geruest= Schönenberger-Gerüst
[weichen]
# ILS 2.0 Weiche (6216): eine Summenzeile, Werte aus den Kreisel-Beruehr-
# punkten des LISP-Exports (kreisel_weichen.json). Ein Beruehrpunkt ist eine
# Weichenkombination = 1x aktiv + 1x passiv, darum lesen beide Merkmale
# dieselbe Zahl ANZAHL_WEICHEN (= Anzahl Beruehrpunkte, siehe export_sivas.py).
3371= ANZAHL_WEICHEN : 0
3372= ANZAHL_WEICHEN : 0
+4 -1
View File
@@ -12,7 +12,10 @@
"color_pin": 3,
"toleranz_kollinear": 0.01,
"toleranz_winkel": 1.0,
"toleranz_min_distanz": 0.001
"toleranz_min_distanz": 0.001,
"toleranz_beruehrung": 1.0,
"toleranz_hoehe": 1.0,
"weiche_marker_radius": 50.0
},
"eckrad": {
"default_hoehe": 2000.0
Binary file not shown.
+54
View File
@@ -0,0 +1,54 @@
// ============================================================
// eckrad_edit.dcl - Eckrad Parameter einfuegen/bearbeiten
// Wie kreisel_edit.dcl, aber ohne Abstand und Typ (ein Eckrad
// hat keine Laenge und keine Kreiselart). Die Drehung ist ein
// freier Winkel (aus dem Richtungsklick), daher edit_box statt
// der festen Kreisel-Ausrichtungen.
// ============================================================
eckrad_edit : dialog {
label = "Eckrad";
: column {
: edit_box {
key = "name";
label = "Bezeichnung:";
edit_width = 30;
}
: row {
: edit_box {
key = "hoehe";
label = "Hoehe (mm):";
edit_width = 10;
}
: edit_box {
key = "drehung";
label = "Drehung (Grad):";
edit_width = 10;
}
}
: popup_list {
key = "drehrichtung";
label = "Motordrehrichtung:";
width = 30;
}
: toggle {
key = "geruest_einzelmodul";
label = "Geruest fuer Einzelmodul";
}
: popup_list {
key = "geruest_typ";
label = "Geruestoption:";
width = 30;
}
}
ok_cancel;
errtile;
}
+21 -1
View File
@@ -99,6 +99,13 @@
"vfl-es-setzen-frage": "\n\nES-Element setzen?",
"vfl-es-nein-zielpunkt": "\n 2 - Nein (Kette endet direkt am Zielpunkt)",
"vfl-prompt-endpunkt-fahrtrichtung": "\n\nEndpunkt entlang Fahrtrichtung waehlen (bestimmt die Laenge): ",
"vfl-endpunkt-modus-header": "\n\nWie soll der Endpunkt bestimmt werden?",
"vfl-endpunkt-modus-opt-linie": "\n 1 - Ziellinie zeichnen (2 Punkte) - der Schnittpunkt bestimmt die Laenge",
"vfl-prompt-ziellinie-p1": "\n\nZiellinie - 1. Punkt (beliebige Richtung, Z wird ignoriert): ",
"vfl-prompt-ziellinie-p2": "\n Ziellinie - 2. Punkt: ",
"vfl-endpunkt-modus-opt-punkt": "\n 2 - Punkt waehlen - entlang der festen Fahrtrichtung",
"vfl-ziellinie-parallel": "\n Die gewaehlte Linie liegt parallel zur Fahrtrichtung - es gibt keinen eindeutigen Schnittpunkt.",
"vfl-ziellinie-hinter-start": "\n Der Schnittpunkt liegt hinter dem Startpunkt der Strecke - bitte eine andere Linie waehlen.",
"vfl-prompt-endpunkt-frei": "\n\nEndpunkt der Linie (XY, beliebige Richtung): ",
"vfl-fehler-laenge-max": "\n>>> FEHLER: Laenge %1 m ueberschreitet die Foerderer-Maximallaenge von 25 m - bitte anderen Endpunkt waehlen.",
"vfl-glied-gf-bogen": "GF-Bogen (horizontale Kurve)",
@@ -469,6 +476,11 @@
"kreisel-quick-fertig": "\nSchnell Einfuegen fertig.",
"kreisel-start-connect": "Kreisel Smart Connect",
"kreisel-start-eckrad": "Eckrad einfuegen",
"kreisel-start-eckrad-edit": "Eckrad bearbeiten",
"kreisel-prompt-eckrad-auswaehlen": "\nEckrad-Block auswaehlen: ",
"kreisel-emsg-kein-eckrad-block": "Kein Eckrad-Block! (Blockname ECKRAD_* erwartet)",
"kreisel-eckrad-ungueltige-zahl": "Hoehe und Drehung muessen Zahlen sein.",
"kreisel-eckrad-edit-aktualisiert": "\nEckrad aktualisiert: Name=%1 Hoehe=%2 Rotation=%3",
"kreisel-start-edit": "Kreisel bearbeiten",
"kreisel-start-insert": "Kreisel Modul einfuegen",
"kreisel-start-quick": "Kreisel Schnell Einfuegen",
@@ -674,6 +686,7 @@
"plq-geloescht": "\n[DELETE_PLANQUADRATE] Planquadrat-Raster entfernt (%1 Einfuegung(en)).",
"plq-nichts-zu-loeschen": "\n[DELETE_PLANQUADRATE] Kein Planquadrat-Raster in der Zeichnung vorhanden.",
"exp-check-ids": "\n[%1] Pruefe und vergebe IDs...",
"exp-hoehen-aktualisiert": "\nHoehen-Attribute von %1 verschobenen Bloecken aus der Z-Koordinate neu gesetzt.",
"exp-sep-proxy-fehler": "\n[EXPORT] WARNUNG: Kopie von '%1' aus Block '%2' fehlgeschlagen (%3) - dieses Element fehlt im Export.",
"exp-sep-zuordnung": "\n[%1] verpackte Separator-Kopie(n): ZUORDNUNG aus der ID des Wrapper-Blocks gesetzt.",
"exp-sep-zuordnung-ohne-id": "\n[%1] verpackte Separator-Kopie(n) ohne Wrapper-ID - ZUORDNUNG wird geometrisch bestimmt.",
@@ -1102,6 +1115,13 @@
"sl-erk-fahrstrecke": "Omniflo Fahrstrecke",
"sl-erk-ap60": "Omniflo Gerade AP60",
"sl-erk-ap110": "Omniflo Gerade AP110",
"sl-erk-apg110": "Omniflo Gerade APG110"
"sl-erk-apg110": "Omniflo Gerade APG110",
"kreisel-bp-start": "Kreisel-Weichenpunkte",
"kreisel-bp-summe": "\n=== Kreisel-Weichenpunkte: %1 Beruehrung(en), %2 Kollision(en) (Toleranz %3 mm) ===",
"kreisel-bp-zeile": "\n Weiche %1: %2 <-> %3 [%4] bei X=%5 Y=%6 Z=%7",
"kreisel-bp-kollision": "\n KOLLISION %1: %2 <-> %3 [%4] bei X=%5 Y=%6 Z=%7",
"kreisel-bp-marker": "\n%1 Marker gezeichnet (Layer %2).",
"exp-kreisel-weichen": "\n[%1] Kreisel-Weichenpunkte: %2 Beruehrung(en), %3 Kollision(en) -> %4",
"exp-kreisel-kollision": "\n[%1] WARNUNG: %2 Kreisel-Kollision(en) - Befehl KREISEL_WEICHEN zeigt sie an."
}
}
+21 -1
View File
@@ -99,6 +99,13 @@
"vfl-es-setzen-frage": "\n\nPlace ES element?",
"vfl-es-nein-zielpunkt": "\n 2 - No (chain ends directly at the target point)",
"vfl-prompt-endpunkt-fahrtrichtung": "\n\nSelect end point along travel direction (defines the length): ",
"vfl-endpunkt-modus-header": "\n\nHow should the end point be determined?",
"vfl-endpunkt-modus-opt-linie": "\n 1 - Draw target line (2 points) - the intersection defines the length",
"vfl-prompt-ziellinie-p1": "\n\nTarget line - 1st point (any direction, Z is ignored): ",
"vfl-prompt-ziellinie-p2": "\n Target line - 2nd point: ",
"vfl-endpunkt-modus-opt-punkt": "\n 2 - Select point - along the fixed travel direction",
"vfl-ziellinie-parallel": "\n The selected line is parallel to the travel direction - there is no unique intersection.",
"vfl-ziellinie-hinter-start": "\n The intersection lies behind the start of the run - please select a different line.",
"vfl-prompt-endpunkt-frei": "\n\nEnd point of the line (XY, any direction): ",
"vfl-fehler-laenge-max": "\n>>> ERROR: Length %1 m exceeds the conveyor maximum length of 25 m - please pick another end point.",
"vfl-glied-gf-bogen": "GF arc (horizontal curve)",
@@ -469,6 +476,11 @@
"kreisel-quick-fertig": "\nQuick insert done.",
"kreisel-start-connect": "Kreisel Smart Connect",
"kreisel-start-eckrad": "Insert Eckrad",
"kreisel-start-eckrad-edit": "Edit Eckrad",
"kreisel-prompt-eckrad-auswaehlen": "\nSelect Eckrad block: ",
"kreisel-emsg-kein-eckrad-block": "Not an Eckrad block! (block name ECKRAD_* expected)",
"kreisel-eckrad-ungueltige-zahl": "Height and rotation must be numbers.",
"kreisel-eckrad-edit-aktualisiert": "\nEckrad updated: Name=%1 Height=%2 Rotation=%3",
"kreisel-start-edit": "Edit Kreisel",
"kreisel-start-insert": "Insert Kreisel module",
"kreisel-start-quick": "Kreisel quick insert",
@@ -674,6 +686,7 @@
"plq-geloescht": "\n[DELETE_PLANQUADRATE] Planquadrat grid removed (%1 insertion(s)).",
"plq-nichts-zu-loeschen": "\n[DELETE_PLANQUADRATE] No Planquadrat grid found in the drawing.",
"exp-check-ids": "\n[%1] Checking and assigning IDs...",
"exp-hoehen-aktualisiert": "\nHeight attributes of %1 moved blocks reset from their Z coordinate.",
"exp-sep-proxy-fehler": "\n[EXPORT] WARNING: copying '%1' out of block '%2' failed (%3) - this element is missing from the export.",
"exp-sep-zuordnung": "\n[%1] packed separator copy/copies: ZUORDNUNG set from the wrapper block ID.",
"exp-sep-zuordnung-ohne-id": "\n[%1] packed separator copy/copies without a wrapper ID - ZUORDNUNG determined geometrically.",
@@ -1102,6 +1115,13 @@
"sl-erk-fahrstrecke": "Omniflo track",
"sl-erk-ap60": "Omniflo straight AP60",
"sl-erk-ap110": "Omniflo straight AP110",
"sl-erk-apg110": "Omniflo straight APG110"
"sl-erk-apg110": "Omniflo straight APG110",
"kreisel-bp-start": "Kreisel switch points",
"kreisel-bp-summe": "\n=== Kreisel switch points: %1 contact(s), %2 collision(s) (tolerance %3 mm) ===",
"kreisel-bp-zeile": "\n Switch %1: %2 <-> %3 [%4] at X=%5 Y=%6 Z=%7",
"kreisel-bp-kollision": "\n COLLISION %1: %2 <-> %3 [%4] at X=%5 Y=%6 Z=%7",
"kreisel-bp-marker": "\n%1 markers drawn (layer %2).",
"exp-kreisel-weichen": "\n[%1] Kreisel switch points: %2 contact(s), %3 collision(s) -> %4",
"exp-kreisel-kollision": "\n[%1] WARNING: %2 Kreisel collision(s) - command KREISEL_WEICHEN shows them."
}
}
+21 -5
View File
@@ -40,6 +40,8 @@ from export_neighbors import (
compute_sensor_zuordnung,
compute_scanner_nearest_separator,
compute_kreisel_touch_switches,
lisp_touch_switches,
load_lisp_kreisel_weichen,
compute_sep_kette,
compute_kreisel_umlauf,
SENSOR_TEILEARTEN,
@@ -952,7 +954,9 @@ def pruefe_ases_in_beiden_containern(items, dbg=None):
return auffaellig
def process_blocks(blocks, lookup, dbg=None):
def process_blocks(blocks, lookup, dbg=None, lisp_weichen=None):
# lisp_weichen = Inhalt von kreisel_weichen.json (load_lisp_kreisel_weichen)
# oder None - dann werden die Kreisel-Beruehrpunkte hier selbst gerechnet.
# x_origin_mm/y_origin_mm einmalig fuer diesen Exportlauf aufloesen (siehe
# export_planquadrat.resolve_origins): automatisch aus den Bloecken, falls
# nicht explizit in [Planquadrate] konfiguriert.
@@ -1535,9 +1539,15 @@ def process_blocks(blocks, lookup, dbg=None):
# compute_kreisel_umlauf (damit die Weiche als Verzweigungspunkt in
# BEIDEN beruehrenden Kreisel-Umlaeufen auftaucht) als auch in
# build_kreisel_weiche_items (das die tatsaechlichen Zeilen erzeugt).
touch_switches = compute_kreisel_touch_switches(
items, NEIGHBOR_TOLERANCE_MM, durchmesser_mm=KREISEL_DURCHMESSER_MM,
element_box_mm=ELEMENT_BOX_MM, dbg=dbg)
# Quelle ist der LISP-Export (kreisel_weichen.json: Kreisel UND Eckrad,
# nur gleiche Hoehe, Kollisionen getrennt); fehlt die Datei, rechnet
# compute_kreisel_touch_switches wie bisher (nur Kreisel, reine XY-Pruefung).
if lisp_weichen is not None:
touch_switches = lisp_touch_switches(lisp_weichen, element_box_mm=ELEMENT_BOX_MM, dbg=dbg)
else:
touch_switches = compute_kreisel_touch_switches(
items, NEIGHBOR_TOLERANCE_MM, durchmesser_mm=KREISEL_DURCHMESSER_MM,
element_box_mm=ELEMENT_BOX_MM, dbg=dbg)
# Umlauf-Reihenfolge an jedem Kreisel: freie Separatoren, BTMT-Stationen
# UND die AS/ES-Enden andockender Ketten (alle zusammen KREISEL_UMLAUF_
@@ -1693,7 +1703,13 @@ def main():
dbg_file = None
dbg = None
items = process_blocks(blocks, lookup, dbg=dbg)
lisp_weichen = load_lisp_kreisel_weichen(raw_json_path)
if lisp_weichen is not None:
print(f"[export_csv] Kreisel-Weichen aus LISP: "
f"{len(lisp_weichen['beruehrungen'])} Beruehrung(en), "
f"{len(lisp_weichen['kollisionen'])} Kollision(en).")
items = process_blocks(blocks, lookup, dbg=dbg, lisp_weichen=lisp_weichen)
if dbg_file:
dbg_file.close()
+79
View File
@@ -47,6 +47,7 @@ MIN_PARTNER) - Ergebnis ist die CSV-Spalte "Fehler" in export_csv.py.
Nur von export_csv.py genutzt (EXPORTCSV), nicht von export_sivas.py.
"""
import json
import math
import os
@@ -1091,6 +1092,84 @@ def compute_kreisel_touch_switches(items, tolerance_mm, durchmesser_mm=800.0,
return switches
# Dateiname der von LISP (csv:kreisel-weichen-schreiben, export.lsp) neben
# export_raw.json geschriebenen Kreisel-Beruehrpunkte.
LISP_KREISEL_WEICHEN_DATEI = "kreisel_weichen.json"
def load_lisp_kreisel_weichen(raw_json_path):
"""Liest kreisel_weichen.json aus dem Ordner von raw_json_path.
Die Datei schreibt der LISP-Export (KreiselInsert.lsp kreisel-bp-*):
Kreisel UND Eckraeder, nur gleiche Hoehe, mehrere Punkte je Paar moeglich,
Ueberschneidungen getrennt als "kollisionen". Rueckgabe: dict mit
"beruehrungen"/"kollisionen" oder None, wenn die Datei fehlt/unlesbar ist
(dann rechnet der Aufrufer selbst - compute_kreisel_touch_switches)."""
pfad = os.path.join(os.path.dirname(os.path.abspath(raw_json_path)),
LISP_KREISEL_WEICHEN_DATEI)
if not os.path.exists(pfad):
return None
try:
with open(pfad, encoding="utf-8", errors="replace") as f:
daten = json.load(f)
except (OSError, ValueError):
return None
if not isinstance(daten, dict):
return None
daten.setdefault("beruehrungen", [])
daten.setdefault("kollisionen", [])
return daten
def lisp_touch_switches(lisp_daten, element_box_mm=None, dbg=None):
"""Baut aus den LISP-Beruehrpunkten (load_lisp_kreisel_weichen) dieselben
Switch-dicts wie compute_kreisel_touch_switches - damit laufen
compute_kreisel_umlauf und build_kreisel_weiche_items unveraendert.
Partner-ID = ID-Attribut (= teileid), ohne ID der Handle. Mehrere Punkte
eines Paares bekommen eindeutige Schluessel (WEICHE#a#b, WEICHE#a#b#2, ...).
Die Box wird an achse_rot_grad (Radiale des beruehrenden Rades) ausgerichtet.
Kollisionen werden NICHT zu Weichen, nur ins Debuglog geschrieben."""
if element_box_mm is None:
element_box_mm = load_element_box_mm()
box_l, box_b, box_h = element_box_mm
switches = []
keys = set()
if dbg:
dbg("")
dbg("=== Kreisel-Beruehrpunkte -> ILS-Weichen (aus LISP kreisel_weichen.json) ===")
for b in lisp_daten.get("beruehrungen", []):
id_a = b.get("a") or b.get("a_handle", "")
id_b = b.get("b") or b.get("b_handle", "")
key = f"WEICHE#{id_a}#{id_b}"
n = 1
while key in keys:
n += 1
key = f"WEICHE#{id_a}#{id_b}#{n}"
keys.add(key)
dx, dy, dz = _oriented_box_aabb(math.radians(float(b.get("achse_rot_grad", 0.0))),
box_l, box_b, box_h)
sw = {
"key": key,
"x": float(b.get("x", 0.0)), "y": float(b.get("y", 0.0)), "z": float(b.get("z", 0.0)),
"dx": dx, "dy": dy, "dz": dz,
"nachbarn": [id_a, id_b],
"achse_von": b.get("achse_von", "") or id_a,
"beruehrung": b.get("art", ""),
}
switches.append(sw)
if dbg:
dbg(f" {id_a} <-> {id_b}: {sw['beruehrung']} ({b.get('detail', '')}) "
f"bei ({sw['x']:.0f}, {sw['y']:.0f}, {sw['z']:.0f})")
if dbg:
for k in lisp_daten.get("kollisionen", []):
dbg(f" KOLLISION {k.get('a', '')} <-> {k.get('b', '')}: {k.get('art', '')} "
f"({k.get('detail', '')}) bei ({float(k.get('x', 0)):.0f}, "
f"{float(k.get('y', 0)):.0f})")
dbg(f"Erzeugte ILS-Weichen: {len(switches)}")
return switches
# ---------------------------------------------------------------------------
# Kreisel <-> Strecken-Enden: verfeinerte Kollisionsbox fuer compute_neighbor_
# ids (die "Nachbarn"-Spalte einer GF/VF-Strecke soll nur an ihrem echten AS-/
+55 -17
View File
@@ -63,6 +63,7 @@ from export_blockpatterns import (load_patterns, load_export_cfg, cfg_path_from_
matches_any, csv_quote, safe_float, safe_int)
from merkmalstamm import lade_merkmalstamm, bezeichnung_fuer
import sivas_mapping
from export_neighbors import load_lisp_kreisel_weichen
BLOCKPATTERNS = load_patterns()
@@ -113,17 +114,17 @@ def ja_nein(flag_1_0):
return "Ja" if flag_1_0 == "1" else "Nein"
def ist_eins(flag_1_0):
"""Wandelt ein "0"/"1"-Attribut in ein echtes bool um (True/False im
JSON, NICHT "Ja"/"Nein" wie ja_nein - siehe build_eckrad_details)."""
return flag_1_0 == "1"
def mm_zu_m(wert_mm):
"""Wandelt ein mm-Attribut (z.B. Eckrad-HOEHE) in Meter um, gerundet wie
3512 beim Kreisel ("2000" -> "2.0")."""
return str(round(safe_float(wert_mm, 0.0) / 1000.0, 3))
# --- Transform-Namen, wie sie in cfg/sivas.cfg hinter "|" stehen koennen ---
TRANSFORMS = {
"ja_nein": ja_nein,
"geruest_typ_anzeige": geruest_typ_anzeige,
"ist_eins": ist_eins,
"mm_zu_m": mm_zu_m,
}
@@ -157,13 +158,15 @@ def build_gefaellestrecke_details(block, anzahl=1):
def kreisel_signature(block):
"""
Schluessel zur Gruppierung gleichartiger Kreisel: Kreiselart, Abstand,
Anzahl der Separatoren, Anzahl der Scanner, Anzahl der Rampen sowie
Geruest fuer Einzelmodul/Geruestoption entscheiden, ob Kreisel in
derselben Sivas-Zeile summiert werden.
Schluessel zur Gruppierung gleichartiger Kreisel: Montagehoehe (Merkmal
2952), Kreiselart, Abstand, Anzahl der Separatoren, Anzahl der Scanner,
Anzahl der Rampen sowie Geruest fuer Einzelmodul/Geruestoption
entscheiden, ob Kreisel in derselben Sivas-Zeile summiert werden.
Die Hoehe wird numerisch verglichen ("4.820" == "4.82").
"""
attribs = block.get("attribs", {})
return (
round(safe_float(attribs.get("MONTAGEHOEHE_m", "0"), 0.0), 3),
attribs.get("KREISELART", "STANDARD"),
attribs.get("ABSTAND", "2300"),
attribs.get("ANZAHL_SEPARATOR", attribs.get("N_SEPARATOREN", "2")),
@@ -176,12 +179,13 @@ def kreisel_signature(block):
def eckrad_signature(block):
"""
Schluessel zur Gruppierung gleichartiger Eckraeder: Geruest fuer
Einzelmodul/Geruestoption entscheiden, ob Eckraeder in derselben
Sivas-Zeile summiert werden.
Schluessel zur Gruppierung gleichartiger Eckraeder: Hoehe (Merkmal 2950)
und Geruest fuer Einzelmodul/Geruestoption entscheiden, ob Eckraeder in
derselben Sivas-Zeile summiert werden.
"""
attribs = block.get("attribs", {})
return (
mm_zu_m(attribs.get("HOEHE", "0")),
attribs.get("GERUEST_EINZELMODUL", "0"),
attribs.get("GERUEST_TYP", "Schoenenberger Geruest"),
)
@@ -294,6 +298,7 @@ def build_variofoerderer_details(block):
SIVAS_MAPPING_KREISEL = sivas_mapping.load_section_mapping("kreisel")
SIVAS_MAPPING_ECKRAD = sivas_mapping.load_section_mapping("eckrad")
SIVAS_MAPPING_GERUEST = sivas_mapping.load_section_mapping("geruest")
SIVAS_MAPPING_WEICHEN = sivas_mapping.load_section_mapping("weichen")
def build_kreisel_details(block):
@@ -315,8 +320,8 @@ def build_kreisel_details(block):
def build_eckrad_details(block):
"""Merkmale-Dict fuer ein ILS Eckrad. Vollstaendig aus cfg/sivas.cfg
[eckrad] (keine berechneten Werte)."""
"""Merkmale-Dict fuer eine Gruppe gleichartiger ILS Eckraeder.
Vollstaendig aus cfg/sivas.cfg [eckrad] (2950 per Transform mm_zu_m)."""
attribs = block.get("attribs", {})
return sivas_mapping.anwenden(SIVAS_MAPPING_ECKRAD, attribs, TRANSFORMS)
@@ -411,7 +416,9 @@ ILS_SIVASNR = {
}
def process_blocks(blocks):
def process_blocks(blocks, anzahl_weichen_ils=0):
# anzahl_weichen_ils = Anzahl Kreisel-Beruehrpunkte aus dem LISP-Export
# (kreisel_weichen.json, siehe main) -> Merkmal 4403 der Automation-Zeile.
"""
Verarbeitet alle Bloecke.
@@ -445,7 +452,7 @@ def process_blocks(blocks):
"sep_kreisel_ohne_pin": 0,
"anzahl_verbindungskreisel": 0,
"laengen_kreisel": 0.0,
"anzahl_weichen_ils": 0,
"anzahl_weichen_ils": anzahl_weichen_ils,
"anzahl_eckraeder": 0,
"anzahl_gefaellestrecken": 0,
"anzahl_variofoerderer": 0,
@@ -823,6 +830,30 @@ def process_blocks(blocks):
})
nr += 1
# ILS 2.0 Weiche (6216): EINE Summenzeile fuer alle Kreisel-Beruehrpunkte
# (kreisel_weichen.json des LISP-Exports, Anzahl = anzahl_weichen_ils).
# Ein Beruehrpunkt = eine Weichenkombination aus 1x aktiv (3371) + 1x
# passiv (3372); die Zuordnung Merkmal -> Wert steht in sivas.cfg
# [weichen], beide lesen ANZAHL_WEICHEN. Anzahl-Spalte 1 wie bei der
# Automation-Zeile - die Gesamtanzahlen stecken in den Merkmalen.
if counters["anzahl_weichen_ils"] > 0:
weiche_sivasnr, weiche_teileart = ILS_SIVASNR["ils_weiche"]
items.append({
"nr": nr,
"teileart": weiche_teileart,
"sivasnr": weiche_sivasnr,
"sivasnr_quoted": True,
"bezeichnung": "",
"dispgruppe": 20,
"anzahl": 1,
"laenge_mm": "",
"details": sivas_mapping.anwenden(
SIVAS_MAPPING_WEICHEN,
{"ANZAHL_WEICHEN": str(counters["anzahl_weichen_ils"])},
TRANSFORMS),
})
nr += 1
# ILS Automation Summierungszeile (nur wenn ILS-Elemente vorhanden)
has_ils = (counters["anzahl_kreisel"] > 0
or counters["anzahl_eckraeder"] > 0
@@ -983,7 +1014,14 @@ def main():
blocks = load_json(raw_json_path)
print(f"[export_sivas] {len(blocks)} Bloecke geladen.")
items, _ = process_blocks(blocks)
# Kreisel-Beruehrpunkte (= ILS-Weichen) zaehlt der LISP-Export
# (kreisel_weichen.json neben der Raw-Datei); fehlt sie, bleibt 4403 = 0.
lisp_weichen = load_lisp_kreisel_weichen(raw_json_path)
anzahl_weichen = len(lisp_weichen["beruehrungen"]) if lisp_weichen else 0
if lisp_weichen is not None:
print(f"[export_sivas] Kreisel-Weichen aus LISP: {anzahl_weichen}")
items, _ = process_blocks(blocks, anzahl_weichen_ils=anzahl_weichen)
header = "Nr;TeileArt;SivasNummer;Bezeichnung;Dispogruppe;Anzahl;Laenge[mm];IDs;details"
with open(output_csv, "w", encoding="utf-8") as f:
+1 -3
View File
@@ -109,9 +109,7 @@ def anwenden(section_mapping, attribs, transforms):
Rueckgabe: dict merkmal -> wert. Ohne Transform wird der Rohwert zu str()
gewandelt (bisheriges Verhalten der build_*_details-Funktionen: alles
Merkmalnummer-Werte sind Strings). Ein Transform liefert seinen
Rueckgabewert UNVERAENDERT (z.B. build_eckrad_details erwartet fuer
"Geruest fuer Einzelmodul" ein echtes bool, kein "True"/"False"-String -
json.dumps schreibt dann true/false statt eines Strings).
Rueckgabewert UNVERAENDERT (er ist selbst fuer den Typ verantwortlich).
"""
ergebnis = {}
for merkmal, (attribut, alt_attribut, default, transform) in section_mapping.items():
+499 -462
View File
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -7,7 +7,10 @@
[Einfuegen]^C^C(ssg-ensure "KreiselInsert") KreiselInsert
[Verbinden]^C^C(ssg-ensure "KreiselInsert") KreiselConnect
[Bearbeiten]^C^C(ssg-ensure "KreiselInsert") KreiselEdit
[<-Eckrad]^C^C(ssg-ensure "KreiselInsert") ILS_Eckrad
[Eckrad]^C^C(ssg-ensure "KreiselInsert") ILS_Eckrad
[Eckrad bearbeiten]^C^C(ssg-ensure "KreiselInsert") EckradEdit
[--]
[<-Weichenpunkte ermitteln]^C^C(ssg-ensure "KreiselInsert") KREISEL_WEICHEN
[->Zusatzmodul]
[BTMT-Beladung]^C^C(ssg-ensure "SSG_LIB_Commands") ILS_BTMT_Beladung
[BTMT-Entladung]^C^C(ssg-ensure "SSG_LIB_Commands") ILS_BTMT_Entladung
@@ -197,6 +200,7 @@
***POP501
[SSG_LIB Edit Context]
[Kreisel bearbeiten](ssg-ensure "KreiselInsert") KreiselEdit
[Eckrad bearbeiten](ssg-ensure "KreiselInsert") EckradEdit
[Omniflo Element bearbeiten](ssg-ensure "OmniModulInsert") OMNI_EDIT
[Gefaellestrecke bearbeiten](ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_EDIT
[VarioFoerderer bearbeiten](ssg-ensure "VarioFoerderer") VARIOFOERDERER_EDIT
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test_export_sivas_eckrad.py - Sivas-Merkmale des ILS Eckrads (6210),
am Kreisel ausgerichtet (cfg/sivas.cfg [eckrad]).
Laeuft komplett ohne BricsCAD (reine Python-Logik).
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
import export_sivas # noqa: E402
def _eckrad(nr, hoehe="2000", einzelmodul="1", typ="Schoenenberger Geruest"):
return {"block_name": f"ECKRAD_{nr}", "x": 0.0, "y": 0.0, "z": float(hoehe),
"rotation": 0.0,
"attribs": {"ID": f"000{nr}", "Bezeichnung": f"Eckrad {nr}",
"NUMMER": str(nr), "HOEHE": hoehe,
"GERUEST_EINZELMODUL": einzelmodul, "GERUEST_TYP": typ}}
def test_eckrad_details_wie_kreisel():
details = export_sivas.build_eckrad_details(_eckrad(1))
assert details == {"2950": "2.0", "3381": "1", "3992": "Ja",
"3993": export_sivas.geruest_typ_anzeige("Schoenenberger Geruest")}
def test_eckrad_ohne_geruest_ist_nein():
details = export_sivas.build_eckrad_details(_eckrad(1, einzelmodul="0"))
assert details["3992"] == "Nein"
def test_eckrad_hoehe_in_metern():
assert export_sivas.build_eckrad_details(_eckrad(1, hoehe="4820"))["2950"] == "4.82"
def _kreisel(nr, montagehoehe):
return {"block_name": f"KREISEL_{nr}", "x": 0.0, "y": 0.0, "z": 0.0,
"rotation": 0.0,
"attribs": {"ID": f"000{nr}", "NUMMER": str(nr), "KREISELART": "STANDARD",
"ABSTAND": "2300", "MONTAGEHOEHE_m": montagehoehe}}
def test_kreisel_verschiedene_hoehen_getrennte_zeilen():
assert (export_sivas.kreisel_signature(_kreisel(1, "4.820"))
!= export_sivas.kreisel_signature(_kreisel(2, "2.000")))
# numerischer Vergleich: gleiche Hoehe, andere Schreibweise -> eine Zeile
assert (export_sivas.kreisel_signature(_kreisel(1, "4.820"))
== export_sivas.kreisel_signature(_kreisel(2, "4.82")))
def test_eckrad_verschiedene_hoehen_getrennte_zeilen():
assert (export_sivas.eckrad_signature(_eckrad(1, hoehe="2000"))
!= export_sivas.eckrad_signature(_eckrad(2, hoehe="2500")))
assert (export_sivas.eckrad_signature(_eckrad(1, hoehe="2000"))
== export_sivas.eckrad_signature(_eckrad(2, hoehe="2000")))
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test_kreisel_weichen_json.py - Uebernahme der LISP-Kreisel-Beruehrpunkte.
Der LISP-Export (export.lsp csv:kreisel-weichen-schreiben, Kern
KreiselInsert.lsp kreisel-bp-*) schreibt kreisel_weichen.json neben
export_raw.json. export_csv.py/export_sivas.py lesen sie statt selbst zu
rechnen; fehlt sie, bleibt es beim alten Python-Rueckfall.
Laeuft ohne BricsCAD.
"""
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
from export_neighbors import ( # noqa: E402
lisp_touch_switches,
load_lisp_kreisel_weichen,
)
import export_sivas # noqa: E402
BOX = (250.0, 100.0, 30.0)
BEISPIEL = {
"toleranz_mm": 1.0,
"beruehrungen": [
# Rad an Laufprofil, Radiale senkrecht (90 Grad)
{"a": "0001", "b": "0002", "a_handle": "2A", "b_handle": "2B",
"a_typ": "KREISEL", "b_typ": "KREISEL", "x": 22503.49, "y": -2821.76,
"z": 5000.0, "art": "Kreis/Tangente", "detail": "AN8/Profil",
"achse_von": "0002", "achse_rot_grad": 270.0},
# Zweiter Punkt desselben Paares -> eigener Schluessel
{"a": "0001", "b": "0002", "a_handle": "2A", "b_handle": "2B",
"a_typ": "KREISEL", "b_typ": "KREISEL", "x": 23000.0, "y": -2821.76,
"z": 5000.0, "art": "Kreis/Kreis", "detail": "SP8/AN8",
"achse_von": "0001", "achse_rot_grad": 0.0},
# Eckrad ohne ID -> Handle
{"a": "", "b": "0003", "a_handle": "3F", "b_handle": "3C",
"a_typ": "ECKRAD", "b_typ": "KREISEL", "x": 1.0, "y": 2.0, "z": 0.0,
"art": "Kreis/Kreis", "detail": "ECKRAD/AN8",
"achse_von": "", "achse_rot_grad": 0.0},
],
"kollisionen": [
{"a": "0004", "b": "0005", "x": 0.0, "y": 0.0, "z": 0.0,
"art": "Ueberschneidung", "detail": "Achsabstand 500.0",
"achse_von": "0004", "achse_rot_grad": 0.0},
],
}
def _schreibe(tmp_path, daten):
raw = tmp_path / "export_raw.json"
raw.write_text("[]", encoding="utf-8")
(tmp_path / "kreisel_weichen.json").write_text(json.dumps(daten), encoding="utf-8")
return str(raw)
def test_datei_fehlt_liefert_none(tmp_path):
raw = tmp_path / "export_raw.json"
raw.write_text("[]", encoding="utf-8")
assert load_lisp_kreisel_weichen(str(raw)) is None
def test_kaputte_datei_liefert_none(tmp_path):
raw = tmp_path / "export_raw.json"
raw.write_text("[]", encoding="utf-8")
(tmp_path / "kreisel_weichen.json").write_text("{kaputt", encoding="utf-8")
assert load_lisp_kreisel_weichen(str(raw)) is None
def test_switches_aus_lisp(tmp_path):
daten = load_lisp_kreisel_weichen(_schreibe(tmp_path, BEISPIEL))
sw = lisp_touch_switches(daten, element_box_mm=BOX)
# Kollisionen werden keine Weichen
assert len(sw) == 3
assert [s["key"] for s in sw] == ["WEICHE#0001#0002", "WEICHE#0001#0002#2",
"WEICHE#3F#0003"]
assert sw[0]["nachbarn"] == ["0001", "0002"]
assert sw[0]["beruehrung"] == "Kreis/Tangente"
assert sw[0]["achse_von"] == "0002"
assert (sw[0]["x"], sw[0]["y"], sw[0]["z"]) == (22503.49, -2821.76, 5000.0)
# Radiale 270 Grad: Laengsseite (250) quer zur Radialen -> entlang X
assert abs(sw[0]["dx"] - 250.0) < 1e-6 and abs(sw[0]["dy"] - 100.0) < 1e-6
# Radiale 0 Grad: Laengsseite entlang Y
assert abs(sw[1]["dx"] - 100.0) < 1e-6 and abs(sw[1]["dy"] - 250.0) < 1e-6
# Ohne achse_von -> erster Partner
assert sw[2]["achse_von"] == "3F"
def test_sivas_4403_zaehlt_beruehrungen():
_, counters = export_sivas.process_blocks([], anzahl_weichen_ils=10)
assert counters["anzahl_weichen_ils"] == 10
_, counters = export_sivas.process_blocks([])
assert counters["anzahl_weichen_ils"] == 0
def test_sivas_6216_summenzeile():
"""Eine Zeile 6216, je Beruehrpunkt 1x aktiv (3371) + 1x passiv (3372)."""
items, _ = export_sivas.process_blocks([], anzahl_weichen_ils=10)
weichen = [i for i in items if i["sivasnr"] == "6216"]
assert len(weichen) == 1
w = weichen[0]
assert w["teileart"] == "ILS 2.0 Weiche"
assert w["anzahl"] == 1
assert w["details"] == {"3371": "10", "3372": "10"}
# CSV-Zeile laesst sich formatieren
assert ';"6216";' in export_sivas.format_csv_line(w)
def test_sivas_6216_ohne_weichen_keine_zeile():
items, _ = export_sivas.process_blocks([])
assert not [i for i in items if i["sivasnr"] == "6216"]
+249 -3
View File
@@ -360,6 +360,95 @@
)
;; ------------------------------------------------------------
;; TESTS: vf_linienzug.lsp - ES-Fussabdruck entlang der Fahrtrichtung
;; ------------------------------------------------------------
;; vfl-es-laengs liefert die Laenge, die ein Segment fuer das ES-Element
;; reservieren muss. Getestet wird die Rueckfallkette: ohne sie wuerde ein
;; fehlender Messwert zu (- deltaL nil) und damit zum Absturz fuehren.
(defun tu-tests-vfl-es ( / _s-laengs _s-dx)
(princ "\n[vf_linienzug ES]")
;; globale Masse sichern - sie gehoeren dem laufenden Bau-Kontext
(setq _s-laengs ein-laengs _s-dx ein-dx)
;; Normalfall: ein-laengs ist gesetzt und wird genommen.
;; 576.2 = ES_Element_30_rechts entlang der Fahrtrichtung; (abs ein-dx)
;; waere 242.6 - genau die 333.65 mm, um die die Kette frueher zu weit ragte.
(setq ein-laengs 576.2 ein-dx -242.6)
(tu-eqf "vfl-es-laengs/nimmt-laengs-nicht-dx" 576.2 (vfl-es-laengs))
;; Vorzeichen: reserviert wird immer ein Betrag
(setq ein-laengs -576.2)
(tu-eqf "vfl-es-laengs/betrag" 576.2 (vfl-es-laengs))
;; Genulltes ein-dx gewinnt: die Null-Konvention der Bau-Pfade bedeutet
;; "ES anderswo schon beruecksichtigt - hier nichts reservieren". Ohne diese
;; Kopplung wuerde der Fix einen doppelten Abzug einbauen.
(setq ein-laengs 576.2 ein-dx 0.0)
(tu-eqf "vfl-es-laengs/genulltes-dx-gewinnt" 0.0 (vfl-es-laengs))
;; KS nicht lesbar -> Rueckfall auf das alte (zu kleine) Blockmass
(setq ein-laengs nil ein-dx -242.6)
(tu-eqf "vfl-es-laengs/fallback-auf-dx" 242.6 (vfl-es-laengs))
;; gar nichts gemessen -> 0.0, niemals nil (Absturzschutz)
(setq ein-dx nil)
(tu-eqf "vfl-es-laengs/fallback-null" 0.0 (vfl-es-laengs))
(setq ein-laengs _s-laengs ein-dx _s-dx)
)
;; ------------------------------------------------------------
;; TESTS: vf_linienzug.lsp - Laenge aus einer Ziellinie (X/Y)
;; ------------------------------------------------------------
;; Steht die Fahrtrichtung fest, ist die Laenge nicht mehr frei: die (ggf.
;; seitlich versetzte) Bahn trifft die Ziellinie in genau einem Punkt.
;; REIN PLANAR - die Hoehe kommt weiterhin aus dem Gefaellewinkel.
(defun tu-tests-vfl-ziellinie ( / _p0 _l _ks)
(princ "\n[vf_linienzug Ziellinie]")
(tu-eqf "vfl-kreuz2/einheitsachsen" 1.0 (vfl-kreuz2 1.0 0.0 0.0 1.0))
(tu-eqf "vfl-kreuz2/parallel" 0.0 (vfl-kreuz2 2.0 4.0 1.0 2.0))
(setq _p0 '(0.0 0.0 0.0))
;; Fahrtrichtung 0 Grad, senkrechte Ziellinie bei x=1000 -> L=1000
(tu-eqf "vfl-laenge-bis-ziellinie/senkrecht"
1000.0 (vfl-laenge-bis-ziellinie _p0 0.0 0.0 '(1000.0 -500.0 0.0) '(1000.0 500.0 0.0)))
;; Querversatz verschiebt die BAHN seitlich, nicht laengs: bei einer zur
;; Fahrtrichtung senkrechten Ziellinie bleibt L deshalb gleich.
(tu-eqf "vfl-laenge-bis-ziellinie/querversatz-ohne-wirkung"
1000.0 (vfl-laenge-bis-ziellinie _p0 0.0 52.5 '(1000.0 -500.0 0.0) '(1000.0 500.0 0.0)))
;; 45-Grad-Ziellinie durch (1000,0): die um +100 versetzte Bahn trifft sie
;; 100 frueher (y=100 wird bei x=1100 erreicht -> ohne Versatz 1000+100).
(tu-eqf "vfl-laenge-bis-ziellinie/schraege-linie"
1100.0 (vfl-laenge-bis-ziellinie _p0 0.0 100.0 '(1000.0 0.0 0.0) '(2000.0 1000.0 0.0)))
;; Ziellinie PARALLEL zur Fahrtrichtung -> kein eindeutiger Treffer -> nil
(tu-nil "vfl-laenge-bis-ziellinie/parallel"
(vfl-laenge-bis-ziellinie _p0 0.0 0.0 '(0.0 500.0 0.0) '(1000.0 500.0 0.0)))
;; Z der Ziellinie ist irrelevant (rein planare Rechnung, Nutzer-Vorgabe):
;; dieselbe Linie auf anderer Hoehe liefert dasselbe L.
(tu-eqf "vfl-laenge-bis-ziellinie/z-ignoriert"
1000.0 (vfl-laenge-bis-ziellinie _p0 0.0 0.0 '(1000.0 -500.0 7777.0) '(1000.0 500.0 -99.0)))
;; Realfall aus dem Nutzer-Log: nach dem 30-Grad-Bogen steht die Kette bei
;; (901.21,-2728.97) mit hz=-120; das ES-KS_AUS laeuft 52.51 mm versetzt und
;; landete bei (231.42,-3994.11). Eine Ziellinie durch diesen Punkt quer zur
;; Fahrtrichtung muss genau die damals gebaute Gesamtlaenge liefern.
(setq _ks '(231.42 -3994.11 0.0))
(setq _l (vfl-laenge-bis-ziellinie '(901.21 -2728.97 0.0) -120.0 52.51
_ks (list (+ (car _ks) (cos (/ pi 6.0))) ; 30 Grad = quer zu -120
(+ (cadr _ks) (sin (/ pi 6.0))) 0.0)))
(tu-true "vfl-laenge-bis-ziellinie/realfall-plausibel"
(and _l (> _l 1430.0) (< _l 1431.0)))
)
;; ------------------------------------------------------------
;; TESTS: export.lsp - CSV/KOS-Kodierung
;; ------------------------------------------------------------
@@ -617,6 +706,46 @@
)
;; ------------------------------------------------------------
;; TESTS: export.lsp - Hoehen-Attribute aus Z neu berechnen
;; ------------------------------------------------------------
(defun tu-tests-hoehen ( / _w)
(princ "\n[export/hoehen]")
;; Nicht verschoben -> nil (Block bleibt beim Bau-Wert)
(tu-nil "csv:hoehen-werte/kreisel-unveraendert"
(csv:hoehen-werte "KREISEL" 2000.0 '(("HOEHE" . "2000") ("MONTAGEHOEHE_m" . "1.800"))))
(tu-nil "csv:hoehen-werte/vf-unveraendert"
(csv:hoehen-werte "VF" 2000.0 '(("HOEHE_VON_mm" . "2000") ("HOEHE_BIS_mm" . "1876"))))
;; Kreisel / Eckrad verschoben
(setq _w (csv:hoehen-werte "KREISEL" 2500.0 '(("HOEHE" . "2000"))))
(tu-eq "csv:hoehen-werte/kreisel-hoehe" "2500" (cdr (assoc "HOEHE" _w)))
(tu-eqf "csv:hoehen-werte/kreisel-montage" 2.5 (atof (cdr (assoc "MONTAGEHOEHE_m" _w))))
(setq _w (csv:hoehen-werte "ECKRAD" 1500.0 '(("HOEHE" . "2000"))))
(tu-eq "csv:hoehen-werte/eckrad-hoehe" "1500" (cdr (assoc "HOEHE" _w)))
(tu-nil "csv:hoehen-werte/eckrad-ohne-montage" (assoc "MONTAGEHOEHE_m" _w))
;; GF abwaerts: Differenz 124 bleibt, MONTAGE = VON/1000
(setq _w (csv:hoehen-werte "GF" 2500.0
'(("HOEHE_VON_mm" . "2000") ("HOEHE_BIS_mm" . "1876") ("DELTA_H_mm" . "123"))))
(tu-eq "csv:hoehen-werte/gf-von" "2500" (cdr (assoc "HOEHE_VON_mm" _w)))
(tu-eq "csv:hoehen-werte/gf-bis" "2376" (cdr (assoc "HOEHE_BIS_mm" _w)))
(tu-eqf "csv:hoehen-werte/gf-montage" 2.5 (atof (cdr (assoc "MONTAGEHOEHE_m" _w))))
;; VF steigend ("Auf"): BIS liegt ueber VON, DELTA_H_mm ohne Vorzeichen
(setq _w (csv:hoehen-werte "VF" 1000.0
'(("HOEHE_VON_mm" . "2000") ("HOEHE_BIS_mm" . "2400") ("DELTA_H_mm" . "400"))))
(tu-eq "csv:hoehen-werte/vf-auf-von" "1000" (cdr (assoc "HOEHE_VON_mm" _w)))
(tu-eq "csv:hoehen-werte/vf-auf-bis" "1400" (cdr (assoc "HOEHE_BIS_mm" _w)))
(tu-eqf "csv:hoehen-werte/vf-auf-montage" 1.2 (atof (cdr (assoc "MONTAGEHOEHE_m" _w))))
;; VF ohne VON/BIS -> Rueckfall z - DELTA_H_mm
(setq _w (csv:hoehen-werte "VF" 3000.0 '(("DELTA_H_mm" . "500"))))
(tu-eq "csv:hoehen-werte/vf-rueckfall-bis" "2500" (cdr (assoc "HOEHE_BIS_mm" _w)))
)
;; ------------------------------------------------------------
;; TESTS: Gefaellestrecke.lsp - Geometrie-/Listen-Helfer
;; ------------------------------------------------------------
@@ -671,7 +800,7 @@
;; ------------------------------------------------------------
;; TESTS: KreiselInsert.lsp - Geometrie-Helfer
;; ------------------------------------------------------------
(defun tu-tests-kreisel ()
(defun tu-tests-kreisel ( / _e)
(princ "\n[KreiselInsert]")
;; Projektion eines Punktes auf eine Gerade
@@ -689,6 +818,123 @@
(tu-eqf "kreisel-idx-to-rotation/2" 90.0 (kreisel-idx-to-rotation 2))
(tu-eq "kreisel-rotation-to-idx/90" 2 (kreisel-rotation-to-idx 90.0))
(tu-eq "kreisel-rotation-to-idx/180" 1 (kreisel-rotation-to-idx 180.0))
;; Beschriftung mit/ohne eigenen Namen
(tu-eq "kreisel-label-text/name"
"KREISEL 3: Puffer STANDARD_2300" (kreisel-label-text 3 "Puffer" "STANDARD" 2300.0))
(tu-eq "kreisel-label-text/vorgabe-name"
"KREISEL 3: PIN_4000" (kreisel-label-text 3 "Kreisel3" "PIN" 4000.0))
(tu-eq "kreisel-label-text/leer"
"KREISEL 3: PIN_4000" (kreisel-label-text 3 "" "PIN" 4000.0))
;; Eckrad-Dialog: Eingaben -> (attribs . rotation)
(setq _e (eckrad-dialog-werte '(("NUMMER" . "3") ("HOEHE" . "0") ("DREHRICHTUNG" . "UZS"))
"Eck3" "2500" "-90" "1" "0" "1"))
(tu-eqf "eckrad-dialog-werte/rotation-normiert" 270.0 (cdr _e))
(tu-eq "eckrad-dialog-werte/hoehe" "2500" (cdr (assoc "HOEHE" (car _e))))
(tu-eq "eckrad-dialog-werte/nummer-bleibt" "3" (cdr (assoc "NUMMER" (car _e))))
(tu-eq "eckrad-dialog-werte/name" "Eck3" (cdr (assoc "Bezeichnung" (car _e))))
(tu-eq "eckrad-dialog-werte/guz" "GUZ" (cdr (assoc "DREHRICHTUNG" (car _e))))
(tu-eq "eckrad-dialog-werte/einzelmodul" "0" (cdr (assoc "GERUEST_EINZELMODUL" (car _e))))
(tu-eq "eckrad-dialog-werte/geruest-typ" (nth 1 *ssg-geruest-optionen*)
(cdr (assoc "GERUEST_TYP" (car _e))))
(tu-nil "eckrad-dialog-werte/hoehe-ungueltig"
(eckrad-dialog-werte nil "" "abc" "0" "0" "1" "2"))
(tu-nil "eckrad-dialog-werte/drehung-leer"
(eckrad-dialog-werte nil "" "2000" "" "0" "1" "2"))
)
;; ------------------------------------------------------------
;; TESTS: KreiselInsert.lsp - Beruehrpunkte/Weichen (kreisel-bp-*)
;; ------------------------------------------------------------
;; Element-Record kurz: (typ x y z rot abstand id)
(defun tu-bp-el (typ x y z rot abstand id)
(kreisel-bp-element typ (list x y) z rot abstand 400.0 id id id)
)
(defun tu-bp-arten (treffer)
(mapcar '(lambda (tr) (cdr (assoc "art" tr))) treffer)
)
(defun tu-tests-kreisel-bp ( / _els _erg _ber _kol)
(princ "\n[KreiselInsert/Beruehrpunkte]")
;; Hilfs-Kerne
(tu-eqf "kreisel-bp-abstand/attribut" 7715.0 (kreisel-bp-abstand "7715" "KREISEL_1_STANDARD_7715"))
(tu-eqf "kreisel-bp-abstand/blockname" 3469.0 (kreisel-bp-abstand nil "KREISEL_3_STANDARD_3469"))
(tu-eqf "kreisel-bp-naechste-punkte/kreuz"
'((5.0 0.0) (5.0 0.0))
(kreisel-bp-naechste-punkte '(0.0 0.0) '(10.0 0.0) '(5.0 -5.0) '(5.0 5.0)))
;; Die 11 Einfuegungen aus tests/testdata/schnittspunkte_kreisel.dxf
(setq _els
(list
(tu-bp-el "ECKRAD" 22405.42028816571 -4021.762548616969 5000.0 270.0 0.0 "E1")
(tu-bp-el "KREISEL" 20971.14247926632 -3221.762548616969 5000.0 0.0 7715.0 "K1")
(tu-bp-el "KREISEL" 22503.49142858727 -2821.762548616969 5000.0 90.0 9020.0 "K2")
(tu-bp-el "KREISEL" 27253.08134883916 -3621.762548616969 5000.0 270.0 3469.0 "K3")
(tu-bp-el "KREISEL" 22503.49142858727 6998.075803812235 5000.0 90.0 4746.0 "K4")
(tu-bp-el "KREISEL" 22405.42028816571 -4421.762548616969 5000.0 270.0 6147.0 "K5")
(tu-bp-el "KREISEL" 26853.08134883916 -6582.442351476258 5000.0 180.0 3248.0 "K6")
(tu-bp-el "KREISEL" 32541.84792939903 -5752.219206460449 2000.0 270.0 9200.0 "K7")
(tu-bp-el "KREISEL" 32941.84792939903 -7662.266486416901 2000.0 0.0 3637.0 "K8a")
(tu-bp-el "KREISEL" 32941.84792939903 -11185.64734962363 2000.0 0.0 3637.0 "K8b")
(tu-bp-el "ECKRAD" 20571.14247926632 -3221.762548616969 5000.0 270.0 0.0 "E1b")))
(setq _erg (kreisel-bp-berechnen _els 1.0 1.0)
_ber (car _erg)
_kol (cadr _erg))
(tu-eq "kreisel-bp/dxf-beruehrungen" 10 (length _ber))
(tu-eq "kreisel-bp/dxf-kollisionen" 0 (length _kol))
(tu-eq "kreisel-bp/dxf-kreis-kreis" 3
(length (vl-remove-if-not '(lambda (a) (= a "Kreis/Kreis")) (tu-bp-arten _ber))))
(tu-eq "kreisel-bp/dxf-paare"
'(("E1" "K1") ("E1" "K5") ("K1" "K2") ("K1" "K3") ("K1" "E1b") ("K2" "K4")
("K3" "K6") ("K5" "K6") ("K7" "K8a") ("K7" "K8b"))
(mapcar '(lambda (tr) (list (cdr (assoc "id" (cdr (assoc "a" tr))))
(cdr (assoc "id" (cdr (assoc "b" tr))))))
_ber))
;; K2 (AN8) steht auf dem oberen Laufprofil von K1
(tu-eqf "kreisel-bp/dxf-punkt-k1-k2"
(list 22503.49142858727 -2821.762548616969)
(cdr (assoc "p" (nth 2 _ber))))
(tu-eq "kreisel-bp/dxf-achse-von-k1-k2" "K2" (cdr (assoc "achse_von" (nth 2 _ber))))
;; Unterschiedliche Hoehe -> kein Kontakt
(tu-eq "kreisel-bp/hoehe"
'(nil nil)
(kreisel-bp-berechnen (list (tu-bp-el "ECKRAD" 0.0 0.0 0.0 0.0 0.0 "A")
(tu-bp-el "ECKRAD" 800.0 0.0 500.0 0.0 0.0 "B"))
1.0 1.0))
;; Zwei Eckraeder Rad an Rad -> eine Weiche in der Mitte
(setq _ber (car (kreisel-bp-berechnen (list (tu-bp-el "ECKRAD" 0.0 0.0 0.0 0.0 0.0 "A")
(tu-bp-el "ECKRAD" 800.0 0.0 0.0 0.0 0.0 "B"))
1.0 1.0)))
(tu-eq "kreisel-bp/eckraeder-art" '("Kreis/Kreis") (tu-bp-arten _ber))
(tu-eqf "kreisel-bp/eckraeder-punkt" '(400.0 0.0) (cdr (assoc "p" (car _ber))))
;; Spalt groesser als Toleranz -> nichts
(tu-eq "kreisel-bp/spalt"
'(nil nil)
(kreisel-bp-berechnen (list (tu-bp-el "ECKRAD" 0.0 0.0 0.0 0.0 0.0 "A")
(tu-bp-el "ECKRAD" 805.0 0.0 0.0 0.0 0.0 "B"))
1.0 1.0))
;; Laufprofile kreuzen sich -> Kollision, keine Weiche
(setq _erg (kreisel-bp-berechnen (list (tu-bp-el "KREISEL" 0.0 0.0 0.0 0.0 2000.0 "A")
(tu-bp-el "KREISEL" 1000.0 -1500.0 0.0 90.0 2000.0 "B"))
1.0 1.0))
(tu-nil "kreisel-bp/kreuzung-keine-weiche" (car _erg))
(tu-eq "kreisel-bp/kreuzung-kollision" '("Ueberschneidung") (tu-bp-arten (cadr _erg)))
;; Kreisel Seite an Seite (Profile aufeinander) -> Kollision, keine Weiche
(setq _erg (kreisel-bp-berechnen (list (tu-bp-el "KREISEL" 0.0 0.0 0.0 0.0 2000.0 "A")
(tu-bp-el "KREISEL" 500.0 800.0 0.0 0.0 2000.0 "B"))
1.0 1.0))
(tu-nil "kreisel-bp/parallel-keine-weiche" (car _erg))
(tu-eq "kreisel-bp/parallel-kollision" '("Tangente/Tangente") (tu-bp-arten (cadr _erg)))
;; Eckrad an der Flanke eines Kreiselrades -> Weiche (freie Seite)
(setq _ber (car (kreisel-bp-berechnen (list (tu-bp-el "KREISEL" 0.0 0.0 0.0 0.0 2000.0 "A")
(tu-bp-el "ECKRAD" 400.0 800.0 0.0 0.0 0.0 "B"))
1.0 1.0)))
(tu-eq "kreisel-bp/flanke-eckrad" 1 (length _ber))
)
@@ -1981,8 +2227,8 @@
(princ "\n================================================================")
(foreach fn '("tu-tests-core" "tu-tests-lang" "tu-tests-id" "tu-tests-vfcore"
"tu-tests-etage"
"tu-tests-export" "tu-tests-gf" "tu-tests-omni" "tu-tests-kreisel"
"tu-tests-etage" "tu-tests-vfl-es" "tu-tests-vfl-ziellinie"
"tu-tests-export" "tu-tests-hoehen" "tu-tests-gf" "tu-tests-omni" "tu-tests-kreisel" "tu-tests-kreisel-bp"
"tu-tests-sepliste" "tu-tests-json" "tu-tests-geruest"
"tu-tests-sepscan" "tu-tests-vfl-segs" "tu-tests-vfl-abbruch"
"tu-tests-werkzeuge")