[FIX] Export: Schnittpunkte zwischen Kreiseln werden erkannt und Weichen entsprechend gezählt und als Teil mit Merkmalen im Export befüllt - nur Kreisel mit gleicher Höhe können einen Schnittpunkt für Weiche bilden - Funktion der Weichenzählung manuell aufrufbar; [FIX] Eckrad nun korrekt angelegt und exportiert

This commit is contained in:
2026-10-14 07:42:08 +02:00
parent 6044c35957
commit 8039251bb8
18 changed files with 1452 additions and 481 deletions
+3 -3
View File
File diff suppressed because one or more lines are too long
+376
View File
@@ -1524,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)
+1
View File
@@ -686,6 +686,7 @@ AutoLISP-Implementierung fuer ILS Kreisel und Eckrad. Enthaelt alle Befehle fuer
| `c:KreiselLabelHoehe` | Label-Hoehe aendern |
| `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` |
---
+73
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")
@@ -2745,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
@@ -2819,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)
)
+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
+8
View File
@@ -107,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
+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;
}
+8 -1
View File
@@ -1108,6 +1108,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."
}
}
+8 -1
View File
@@ -1108,6 +1108,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-/
+38 -3
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()
@@ -297,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):
@@ -414,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.
@@ -448,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,
@@ -826,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
@@ -986,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:
+502 -465
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -8,7 +8,9 @@
[Verbinden]^C^C(ssg-ensure "KreiselInsert") KreiselConnect
[Bearbeiten]^C^C(ssg-ensure "KreiselInsert") KreiselEdit
[Eckrad]^C^C(ssg-ensure "KreiselInsert") ILS_Eckrad
[<-Eckrad bearbeiten]^C^C(ssg-ensure "KreiselInsert") EckradEdit
[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
+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"]
+94 -1
View File
@@ -756,6 +756,99 @@
)
;; ------------------------------------------------------------
;; 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))
)
;; ------------------------------------------------------------
;; TESTS: ssg_ks_insert.lsp - Sepliste-Serialisierung
;; ------------------------------------------------------------
@@ -2046,7 +2139,7 @@
(foreach fn '("tu-tests-core" "tu-tests-lang" "tu-tests-id" "tu-tests-vfcore"
"tu-tests-etage"
"tu-tests-export" "tu-tests-hoehen" "tu-tests-gf" "tu-tests-omni" "tu-tests-kreisel"
"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")