Merge: Konflikt im VF/GF-Edit-Prefill aufgeloest
Eingehender Branch setzt geruest-einzelmodul-Default auf "1"; lokal kamen dim- und motorseite-Vorbelegung hinzu. Aufloesung: beide lokalen Zusaetze behalten und den neuen Default "1" uebernommen (Gefaellestrecke.lsp, vf_standard.lsp). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+4
-2
@@ -185,5 +185,7 @@ cython_debug/
|
|||||||
# BricsCAD kompilierte Menuedateien
|
# BricsCAD kompilierte Menuedateien
|
||||||
#CUI soll nun auch verwaltet werden
|
#CUI soll nun auch verwaltet werden
|
||||||
#*.cui
|
#*.cui
|
||||||
data/block_libraries/ils_library.dxf >> .gitignore
|
data/block_libraries/ils_library.dxf
|
||||||
echo data/block_libraries/ils_library.dxf
|
|
||||||
|
# Tool-Caches
|
||||||
|
.skylos/
|
||||||
|
|||||||
@@ -1430,7 +1430,7 @@
|
|||||||
es-seite geruest-einzelmodul geruest-typ /
|
es-seite geruest-einzelmodul geruest-typ /
|
||||||
gf-bname gf-ss ent gf-insert typ-str anzahl-gf
|
gf-bname gf-ss ent gf-insert typ-str anzahl-gf
|
||||||
gf-winkel-str i gf-g10z gf-korr-dz gf-osmode-alt)
|
gf-winkel-str i gf-g10z gf-korr-dz gf-osmode-alt)
|
||||||
(if (null geruest-einzelmodul) (setq geruest-einzelmodul "0"))
|
(if (null geruest-einzelmodul) (setq geruest-einzelmodul "1"))
|
||||||
(if (null geruest-typ) (setq geruest-typ (ssg-geruest-idx-to-typ (ssg-geruest-typ-to-idx nil))))
|
(if (null geruest-typ) (setq geruest-typ (ssg-geruest-idx-to-typ (ssg-geruest-typ-to-idx nil))))
|
||||||
;; Beschriftung: Mitte der ersten Geraden, 400mm senkrecht versetzt
|
;; Beschriftung: Mitte der ersten Geraden, 400mm senkrecht versetzt
|
||||||
(gf-make-label gf-nummer hoehe-von hoehe-bis deltaH deltaL
|
(gf-make-label gf-nummer hoehe-von hoehe-bis deltaH deltaL
|
||||||
@@ -1753,7 +1753,7 @@
|
|||||||
(if (and prefill (equal (cdr (assoc "es-seite" prefill)) "rechts")) "1" "0"))
|
(if (and prefill (equal (cdr (assoc "es-seite" prefill)) "rechts")) "1" "0"))
|
||||||
|
|
||||||
(set_tile "geruest_einzelmodul"
|
(set_tile "geruest_einzelmodul"
|
||||||
(if (and prefill (equal (cdr (assoc "geruest-einzelmodul" prefill)) "1")) "1" "0"))
|
(if (or (null prefill) (equal (cdr (assoc "geruest-einzelmodul" prefill)) "1")) "1" "0"))
|
||||||
(start_list "geruest_typ")
|
(start_list "geruest_typ")
|
||||||
(foreach opt *ssg-geruest-optionen* (add_list opt))
|
(foreach opt *ssg-geruest-optionen* (add_list opt))
|
||||||
(end_list)
|
(end_list)
|
||||||
@@ -2270,7 +2270,7 @@
|
|||||||
(cons "es-winkel" es-winkel)
|
(cons "es-winkel" es-winkel)
|
||||||
;; Dimension aus XDATA erkennen (Default 3D, falls Alt-Block ohne Eintrag)
|
;; Dimension aus XDATA erkennen (Default 3D, falls Alt-Block ohne Eintrag)
|
||||||
(cons "dim" (cond ((ssg-dim-xdata-lesen ent)) ("3D")))
|
(cons "dim" (cond ((ssg-dim-xdata-lesen ent)) ("3D")))
|
||||||
(cons "geruest-einzelmodul" (cond ((cdr (assoc "GERUEST_EINZELMODUL" attribs))) ("0")))
|
(cons "geruest-einzelmodul" (cond ((cdr (assoc "GERUEST_EINZELMODUL" attribs))) ("1")))
|
||||||
(cons "geruest-typ" (cdr (assoc "GERUEST_TYP" attribs)))))
|
(cons "geruest-typ" (cdr (assoc "GERUEST_TYP" attribs)))))
|
||||||
|
|
||||||
;; AS-Seite + untere Hoehe fuer den optionalen parallelen VF-Bau
|
;; AS-Seite + untere Hoehe fuer den optionalen parallelen VF-Bau
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
("ANZAHL_SEPARATOR" "2")
|
("ANZAHL_SEPARATOR" "2")
|
||||||
("ANZAHL_SCANNER" "0")
|
("ANZAHL_SCANNER" "0")
|
||||||
("ANZAHL_RAMPEN" "0")
|
("ANZAHL_RAMPEN" "0")
|
||||||
("GERUEST_EINZELMODUL" "0")
|
("GERUEST_EINZELMODUL" "1")
|
||||||
("GERUEST_TYP" "Schoenenberger Geruest")
|
("GERUEST_TYP" "Schoenenberger Geruest")
|
||||||
("DREHRICHTUNG" "UZS")
|
("DREHRICHTUNG" "UZS")
|
||||||
("DREHUNG" "0")
|
("DREHUNG" "0")
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
("DREHUNG" "0")
|
("DREHUNG" "0")
|
||||||
("NUMMER" "0")
|
("NUMMER" "0")
|
||||||
("HOEHE" "0")
|
("HOEHE" "0")
|
||||||
("GERUEST_EINZELMODUL" "0")
|
("GERUEST_EINZELMODUL" "1")
|
||||||
("GERUEST_TYP" "Schoenenberger Geruest")
|
("GERUEST_TYP" "Schoenenberger Geruest")
|
||||||
("ID" ""))
|
("ID" ""))
|
||||||
)
|
)
|
||||||
@@ -1120,7 +1120,7 @@
|
|||||||
(dbg 'hoehe)
|
(dbg 'hoehe)
|
||||||
|
|
||||||
;; 5. Geruest fuer Einzelmodul + Geruestoption abfragen
|
;; 5. Geruest fuer Einzelmodul + Geruestoption abfragen
|
||||||
(setq geruest-einzelmodul (if (ssg-ques (ssg-text "kreisel-eckrad-geruest-frage") nil) "0" "1"))
|
(setq geruest-einzelmodul (if (ssg-ques (ssg-text "kreisel-eckrad-geruest-frage") T) "1" "0"))
|
||||||
(setq geruest-typ (ssg-ask-geruest-typ))
|
(setq geruest-typ (ssg-ask-geruest-typ))
|
||||||
|
|
||||||
;; 6. Einfuegen (centerPt ist der Kreismittelpunkt)
|
;; 6. Einfuegen (centerPt ist der Kreismittelpunkt)
|
||||||
|
|||||||
+135
-92
@@ -497,24 +497,142 @@
|
|||||||
;;
|
;;
|
||||||
;; blockname = Profiltyp (z.B. "AP60", "AP110", "APG110")
|
;; blockname = Profiltyp (z.B. "AP60", "AP110", "APG110")
|
||||||
;; laengemax = Maximale Fahrstreckenlaenge in mm
|
;; laengemax = Maximale Fahrstreckenlaenge in mm
|
||||||
|
;; --- Quell-Attribute einer Aluprofil-.dwg lesen (mit ID-Feld) ---
|
||||||
|
;; blockname = Profiltyp (z.B. "AP60", "AP110", "APG110")
|
||||||
|
;; Rueckgabe: Attribut-Alist (mit angehaengtem leeren "ID"-Feld) oder nil,
|
||||||
|
;; wenn *block-path* fehlt oder die Quell-.dwg keine Attribute hat.
|
||||||
|
(defun omni:read-src-attribs (blockname / srcAttribs)
|
||||||
|
(if (and (boundp '*block-path*) *block-path*)
|
||||||
|
(progn
|
||||||
|
(setq srcAttribs (ssg-attrib-read-dwg (strcat *block-path* blockname ".dwg")))
|
||||||
|
(if (and srcAttribs (not (assoc "ID" srcAttribs)))
|
||||||
|
(setq srcAttribs (append srcAttribs (list (cons "ID" ""))))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
srcAttribs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Aluprofil-Gerade non-interaktiv aus zwei Punkten erzeugen ---
|
||||||
|
;;
|
||||||
|
;; Skriptfaehiger Kern von omni:insert-block: baut aus zwei bekannten
|
||||||
|
;; Punkten (Anfang pt, Ende pt2) den Compound-Block (Linie + ATTDEFs) und
|
||||||
|
;; fuegt ihn ein. Fuehrt KEINE Nutzerabfrage und KEIN ssg-start/ssg-end aus -
|
||||||
|
;; der Aufrufer sichert die Umgebung und setzt OSMODE/ATTREQ/ATTDIA=0.
|
||||||
|
;;
|
||||||
|
;; blockname = Profiltyp (z.B. "AP110")
|
||||||
|
;; pt / pt2 = Anfangs-/Endpunkt (Liste x y [z])
|
||||||
|
;; srcAttribs = Quell-Attribute (Alist) oder nil (dann Block ohne ATTDEFs)
|
||||||
|
;; textHeight / textGap = Textparameter
|
||||||
|
;; textEnt = optionaler, bereits erzeugter Vorschau-Text (Tag 1 = blockname);
|
||||||
|
;; nil -> wird hier erzeugt
|
||||||
|
;; Rueckgabe: Entity-Name des eingefuegten INSERT-Blocks oder nil.
|
||||||
|
(defun omni:make-gerade (blockname pt pt2 srcAttribs textHeight textGap textEnt /
|
||||||
|
laenge laengeStr dx dy angle angleDeg
|
||||||
|
perpX perpY textPt ed lastEnt ss e bname blockEnt
|
||||||
|
attribs attribDefs)
|
||||||
|
(setq laenge (distance (list (car pt) (cadr pt))
|
||||||
|
(list (car pt2) (cadr pt2))))
|
||||||
|
(setq laengeStr (rtos laenge 2 0))
|
||||||
|
(setq dx (- (car pt2) (car pt))
|
||||||
|
dy (- (cadr pt2) (cadr pt)))
|
||||||
|
(setq angle (atan dy dx))
|
||||||
|
(setq angleDeg (* (/ 180.0 pi) angle))
|
||||||
|
|
||||||
|
;; 1. Vorschau-Text erzeugen, falls nicht vom Aufrufer uebergeben
|
||||||
|
(if (null textEnt)
|
||||||
|
(progn
|
||||||
|
(entmake (list '(0 . "TEXT")
|
||||||
|
(cons 8 (getvar "CLAYER"))
|
||||||
|
(cons 10 (list (car pt) (cadr pt)
|
||||||
|
(if (caddr pt) (caddr pt) 0.0)))
|
||||||
|
(cons 40 textHeight)
|
||||||
|
(cons 1 blockname)
|
||||||
|
'(50 . 0.0)))
|
||||||
|
(setq textEnt (entlast))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(setq lastEnt textEnt)
|
||||||
|
|
||||||
|
;; 2. Compound-Block: Linie bei (0,0)..(laenge,0) + ATTDEFs aus Quell-Attributen
|
||||||
|
(command "_.LINE" (list 0.0 0.0) (list laenge 0.0) "")
|
||||||
|
(setq attribDefs (ssg-attrib-alist-to-defs srcAttribs))
|
||||||
|
(ssg-attrib-make-defs attribDefs 50.0)
|
||||||
|
|
||||||
|
;; Neue Entities einsammeln (alles nach textEnt)
|
||||||
|
(setq ss (ssadd))
|
||||||
|
(setq e (entnext lastEnt))
|
||||||
|
(while e (ssadd e ss) (setq e (entnext e)))
|
||||||
|
|
||||||
|
;; Block mit eindeutigem Zeitstempel-Namen erzeugen
|
||||||
|
(setq bname (strcat blockname "_" (ssg-timestamp)))
|
||||||
|
;; Falls Name schon existiert, Suffix anhaengen
|
||||||
|
(if (tblsearch "BLOCK" bname)
|
||||||
|
(setq bname (strcat bname "_" (itoa (fix (* (getvar "CDATE") 1000000)))))
|
||||||
|
)
|
||||||
|
;; Objektfang aus: sonst kann _.INSERT den Einfuegepunkt pt auf ein Objekt
|
||||||
|
;; umfangen (z.B. den Text bei pt) und die Hoehe verfaelschen.
|
||||||
|
(setvar "OSMODE" 0)
|
||||||
|
(command "_.-BLOCK" bname (list 0.0 0.0 0.0) ss "")
|
||||||
|
|
||||||
|
;; Block einfuegen (ATTREQ/ATTDIA bereits auf 0)
|
||||||
|
(command "_.INSERT" bname pt 1 1 angleDeg)
|
||||||
|
(setq blockEnt (entlast))
|
||||||
|
|
||||||
|
;; 3. Attribute setzen: Quell-Werte + LAENGE/A aktualisieren
|
||||||
|
(setq attribs srcAttribs)
|
||||||
|
(if (assoc "LAENGE" attribs)
|
||||||
|
(setq attribs (subst (cons "LAENGE" laengeStr)
|
||||||
|
(assoc "LAENGE" attribs) attribs))
|
||||||
|
(setq attribs (cons (cons "LAENGE" laengeStr) attribs))
|
||||||
|
)
|
||||||
|
(if (assoc "A" attribs)
|
||||||
|
(setq attribs (subst (cons "A" laengeStr)
|
||||||
|
(assoc "A" attribs) attribs))
|
||||||
|
(setq attribs (cons (cons "A" laengeStr) attribs))
|
||||||
|
)
|
||||||
|
(ssg-attrib-set-on blockEnt attribs)
|
||||||
|
|
||||||
|
;; 4. Text oberhalb der Linie positionieren, um Linienwinkel drehen,
|
||||||
|
;; Inhalt mit Laenge ergaenzen
|
||||||
|
(setq perpX (* (- (sin angle)) textGap)
|
||||||
|
perpY (* (cos angle) textGap))
|
||||||
|
(setq textPt (list (+ (car pt) perpX)
|
||||||
|
(+ (cadr pt) perpY)
|
||||||
|
(if (caddr pt) (caddr pt) 0.0)))
|
||||||
|
(setq ed (entget textEnt))
|
||||||
|
(setq ed (subst (cons 10 textPt) (assoc 10 ed) ed))
|
||||||
|
(if (assoc 50 ed)
|
||||||
|
(setq ed (subst (cons 50 angle) (assoc 50 ed) ed))
|
||||||
|
(setq ed (append ed (list (cons 50 angle))))
|
||||||
|
)
|
||||||
|
(setq ed (subst (cons 1 (strcat blockname " L=" laengeStr))
|
||||||
|
(assoc 1 ed) ed))
|
||||||
|
(entmod ed)
|
||||||
|
(entupd textEnt)
|
||||||
|
|
||||||
|
;; 5. Eindeutige ID zuweisen
|
||||||
|
(if (and blockEnt (atoms-family 1 '("ssg-id-generate")))
|
||||||
|
(ssg-id-generate blockEnt)
|
||||||
|
)
|
||||||
|
|
||||||
|
(princ (ssg-textf "omni-insert-block-eingefuegt" (list bname laengeStr)))
|
||||||
|
blockEnt
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; Aluprofil-Fahrstrecke interaktiv einfuegen (siehe Ablaufbeschreibung oben).
|
||||||
|
;; Fragt Einfuege- und Endpunkt ab und delegiert die Blockerzeugung an
|
||||||
|
;; omni:make-gerade.
|
||||||
(defun omni:insert-block (blockname laengemax /
|
(defun omni:insert-block (blockname laengemax /
|
||||||
pt pt2 laenge laengeStr
|
pt pt2 laenge textEnt textHeight textGap srcAttribs ok)
|
||||||
dx dy angle angleDeg
|
|
||||||
textEnt textHeight textGap
|
|
||||||
perpX perpY textPt ed
|
|
||||||
lastEnt ss e bname blockEnt
|
|
||||||
srcAttribs attribDefs attribs
|
|
||||||
ok)
|
|
||||||
|
|
||||||
(setq textHeight (ssg-cfg-or "omniflo" "text_height" 100.0))
|
(setq textHeight (ssg-cfg-or "omniflo" "text_height" 100.0))
|
||||||
(setq textGap (ssg-cfg-or "omniflo" "text_gap" 20.0))
|
(setq textGap (ssg-cfg-or "omniflo" "text_gap" 20.0))
|
||||||
|
|
||||||
;; 0. Attribute aus der Quell-.dwg lesen (vor ssg-start, da eigene INSERT/ERASE)
|
;; 0. Attribute aus der Quell-.dwg lesen (vor ssg-start, da eigene INSERT/ERASE)
|
||||||
(setq srcAttribs (ssg-attrib-read-dwg (strcat *block-path* blockname ".dwg")))
|
(setq srcAttribs (omni:read-src-attribs blockname))
|
||||||
;; ID-Attribut hinzufuegen falls noch nicht vorhanden
|
|
||||||
(if (and srcAttribs (not (assoc "ID" srcAttribs)))
|
|
||||||
(setq srcAttribs (append srcAttribs (list (cons "ID" ""))))
|
|
||||||
)
|
|
||||||
(if (null srcAttribs)
|
(if (null srcAttribs)
|
||||||
(progn (princ (ssg-text "omni-insert-keine-attribute")) nil)
|
(progn (princ (ssg-text "omni-insert-keine-attribute")) nil)
|
||||||
(progn
|
(progn
|
||||||
@@ -533,7 +651,7 @@
|
|||||||
(setvar "ATTREQ" 0)
|
(setvar "ATTREQ" 0)
|
||||||
(setvar "ATTDIA" 0)
|
(setvar "ATTDIA" 0)
|
||||||
|
|
||||||
;; 1. Text horizontal am Einfuegepunkt erzeugen
|
;; Vorschau-Text horizontal am Einfuegepunkt (waehrend Endpunktwahl)
|
||||||
(entmake (list '(0 . "TEXT")
|
(entmake (list '(0 . "TEXT")
|
||||||
(cons 8 (getvar "CLAYER"))
|
(cons 8 (getvar "CLAYER"))
|
||||||
(cons 10 (list (car pt) (cadr pt)
|
(cons 10 (list (car pt) (cadr pt)
|
||||||
@@ -543,13 +661,13 @@
|
|||||||
'(50 . 0.0)))
|
'(50 . 0.0)))
|
||||||
(setq textEnt (entlast))
|
(setq textEnt (entlast))
|
||||||
|
|
||||||
;; 2. Endpunkt fuer Fahrstrecke abfragen (Schleife bei Ueberschreitung)
|
;; Endpunkt fuer Fahrstrecke abfragen (Schleife bei Ueberschreitung)
|
||||||
(setq ok nil)
|
(setq ok nil)
|
||||||
(while (not ok)
|
(while (not ok)
|
||||||
(setq pt2 (getpoint pt (ssg-text "omni-prompt-endpunkt-fahrstrecke")))
|
(setq pt2 (getpoint pt (ssg-text "omni-prompt-endpunkt-fahrstrecke")))
|
||||||
(if (null pt2)
|
(if (null pt2)
|
||||||
(progn
|
(progn
|
||||||
;; Abbruch: Text loeschen
|
;; Abbruch: Vorschau-Text loeschen
|
||||||
(command "_.ERASE" textEnt "")
|
(command "_.ERASE" textEnt "")
|
||||||
(princ (ssg-text "omni-abbruch-omni"))
|
(princ (ssg-text "omni-abbruch-omni"))
|
||||||
(setq ok T pt nil)
|
(setq ok T pt nil)
|
||||||
@@ -561,83 +679,8 @@
|
|||||||
(alert (ssg-textf "omni-laenge-ueberschritten"
|
(alert (ssg-textf "omni-laenge-ueberschritten"
|
||||||
(list (rtos laenge 2 0) (rtos laengemax 2 0))))
|
(list (rtos laenge 2 0) (rtos laengemax 2 0))))
|
||||||
(progn
|
(progn
|
||||||
(setq laengeStr (rtos laenge 2 0))
|
(omni:make-gerade blockname pt pt2 srcAttribs
|
||||||
(setq dx (- (car pt2) (car pt))
|
textHeight textGap textEnt)
|
||||||
dy (- (cadr pt2) (cadr pt)))
|
|
||||||
(setq angle (atan dy dx))
|
|
||||||
(setq angleDeg (* (/ 180.0 pi) angle))
|
|
||||||
|
|
||||||
;; 3. Compound-Block: Linie bei (0,0) + ATTDEFs aus Quell-Attributen
|
|
||||||
(setq lastEnt textEnt)
|
|
||||||
|
|
||||||
;; Linie von Ursprung bis (laenge, 0)
|
|
||||||
(command "_.LINE" (list 0.0 0.0) (list laenge 0.0) "")
|
|
||||||
|
|
||||||
;; ATTDEFs aus Quell-Attributen erzeugen
|
|
||||||
(setq attribDefs (ssg-attrib-alist-to-defs srcAttribs))
|
|
||||||
(ssg-attrib-make-defs attribDefs 50.0)
|
|
||||||
|
|
||||||
;; Neue Entities einsammeln (alles nach textEnt)
|
|
||||||
(setq ss (ssadd))
|
|
||||||
(setq e (entnext lastEnt))
|
|
||||||
(while e (ssadd e ss) (setq e (entnext e)))
|
|
||||||
|
|
||||||
;; Block mit eindeutigem Zeitstempel-Namen erzeugen
|
|
||||||
(setq bname (strcat blockname "_" (ssg-timestamp)))
|
|
||||||
;; Falls Name schon existiert, Suffix anhaengen
|
|
||||||
(if (tblsearch "BLOCK" bname)
|
|
||||||
(setq bname (strcat bname "_" (itoa (fix (* (getvar "CDATE") 1000000)))))
|
|
||||||
)
|
|
||||||
;; Objektfang aus: sonst kann _.INSERT den Einfuegepunkt pt
|
|
||||||
;; auf ein Objekt umfangen (z.B. den Text bei pt) und die
|
|
||||||
;; Hoehe verfaelschen. OSMODE via ssg-start gesichert.
|
|
||||||
(setvar "OSMODE" 0)
|
|
||||||
(command "_.-BLOCK" bname (list 0.0 0.0 0.0) ss "")
|
|
||||||
|
|
||||||
;; Block einfuegen (ATTREQ/ATTDIA bereits auf 0)
|
|
||||||
(command "_.INSERT" bname pt 1 1 angleDeg)
|
|
||||||
|
|
||||||
(setq blockEnt (entlast))
|
|
||||||
|
|
||||||
;; 4. Attribute setzen: Quell-Werte + LAENGE/A aktualisieren
|
|
||||||
(setq attribs srcAttribs)
|
|
||||||
(if (assoc "LAENGE" attribs)
|
|
||||||
(setq attribs (subst (cons "LAENGE" laengeStr)
|
|
||||||
(assoc "LAENGE" attribs) attribs))
|
|
||||||
(setq attribs (cons (cons "LAENGE" laengeStr) attribs))
|
|
||||||
)
|
|
||||||
(if (assoc "A" attribs)
|
|
||||||
(setq attribs (subst (cons "A" laengeStr)
|
|
||||||
(assoc "A" attribs) attribs))
|
|
||||||
(setq attribs (cons (cons "A" laengeStr) attribs))
|
|
||||||
)
|
|
||||||
(ssg-attrib-set-on blockEnt attribs)
|
|
||||||
|
|
||||||
;; 5. Text aktualisieren: oberhalb der Linie positionieren,
|
|
||||||
;; um Linienwinkel drehen, Inhalt mit Laenge ergaenzen
|
|
||||||
(setq perpX (* (- (sin angle)) textGap)
|
|
||||||
perpY (* (cos angle) textGap))
|
|
||||||
(setq textPt (list (+ (car pt) perpX)
|
|
||||||
(+ (cadr pt) perpY)
|
|
||||||
(if (caddr pt) (caddr pt) 0.0)))
|
|
||||||
|
|
||||||
(setq ed (entget textEnt))
|
|
||||||
(setq ed (subst (cons 10 textPt) (assoc 10 ed) ed))
|
|
||||||
(if (assoc 50 ed)
|
|
||||||
(setq ed (subst (cons 50 angle) (assoc 50 ed) ed))
|
|
||||||
(setq ed (append ed (list (cons 50 angle))))
|
|
||||||
)
|
|
||||||
(setq ed (subst (cons 1 (strcat blockname " L=" laengeStr))
|
|
||||||
(assoc 1 ed) ed))
|
|
||||||
(entmod ed)
|
|
||||||
(entupd textEnt)
|
|
||||||
|
|
||||||
;; Eindeutige ID zuweisen
|
|
||||||
(if (and blockEnt (atoms-family 1 '("ssg-id-generate")))
|
|
||||||
(ssg-id-generate blockEnt)
|
|
||||||
)
|
|
||||||
|
|
||||||
(princ (ssg-textf "omni-insert-block-eingefuegt" (list bname laengeStr)))
|
|
||||||
(setq ok T)
|
(setq ok T)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -358,6 +358,77 @@ Testbefehle für die Python-Integration. Export-Funktionen wurden in `export.lsp
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Zwei Test-Ebenen, beide gesteuert über `bin\run_tests.bat` (Details und
|
||||||
|
Verzeichnisstruktur: [`../tests/README.md`](../tests/README.md)):
|
||||||
|
|
||||||
|
1. **LISP-Unit-Tests** (`tests/test_unit.lsp`) – prüfen reine
|
||||||
|
Standardfunktionen direkt in AutoLISP, ohne Zeichnung.
|
||||||
|
2. **Zeichnungsbasierte Integrationstests** (`tests/test_*.lsp` +
|
||||||
|
`tests/test_*.py`) – erzeugen Blöcke in BricsCAD und validieren das
|
||||||
|
Ergebnis anschließend mit pytest/ezdxf.
|
||||||
|
|
||||||
|
### LISP-Unit-Tests (`test_unit.lsp`)
|
||||||
|
|
||||||
|
Testen **reine Hilfsfunktionen** – solche, die nur aus ihren Argumenten ein
|
||||||
|
Ergebnis berechnen und keine Zeichnungsdatenbank, Auswahlsätze, Dialoge oder
|
||||||
|
Blockdateien brauchen (String-, Zahl-, Vektor-, Listen- und Alist-Helfer).
|
||||||
|
Abgedeckt werden Funktionen aus `ssg_core`, `ssg_lang`, `ssg_id`, `vf_core`,
|
||||||
|
`export`, `Gefaellestrecke`, `OmniModulInsert` und `KreiselInsert`.
|
||||||
|
|
||||||
|
**Aufruf über die Kommandozeile** (startet BricsCAD headless):
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
bin\run_tests.bat --lisp
|
||||||
|
```
|
||||||
|
|
||||||
|
`run_tests.bat --lisp` löscht das alte Ergebnis, startet BricsCAD mit
|
||||||
|
`tests/test_unit.scr` (lädt `ssg_load.lsp` + `test_unit.lsp`, ruft `TEST_UNIT`
|
||||||
|
auf und beendet BricsCAD), gibt danach `tests/output/unit_results.txt` aus und
|
||||||
|
liefert Exit-Code 1, wenn ein Test fehlschlägt oder kein Report entsteht.
|
||||||
|
|
||||||
|
**Aufruf direkt in BricsCAD** (SSG_LIB geladen):
|
||||||
|
|
||||||
|
```lisp
|
||||||
|
(load (strcat (getenv "DXFMAKRO") "/tests/test_unit.lsp"))
|
||||||
|
TEST_UNIT
|
||||||
|
```
|
||||||
|
|
||||||
|
**Aufbau eines Testfalls.** `test_unit.lsp` enthält ein Mini-Framework: jeder
|
||||||
|
Testfall ruft eine Funktion auf und vergleicht das Ergebnis gegen einen
|
||||||
|
erwarteten Wert. Die Assertion-Helfer zählen Treffer/Fehler mit und schreiben
|
||||||
|
je eine `PASS`/`FAIL`-Zeile:
|
||||||
|
|
||||||
|
| Helfer | Vergleich |
|
||||||
|
| --- | --- |
|
||||||
|
| `tu-eq name erwartet ist` | exakt (`equal`) – Strings, Ganzzahlen, Listen davon |
|
||||||
|
| `tu-eqf name erwartet ist` | numerisch mit Toleranz `1e-6` – Fliesskomma, auch verschachtelte Listen |
|
||||||
|
| `tu-true name ist` | Ergebnis ist nicht `nil` |
|
||||||
|
| `tu-nil name ist` | Ergebnis ist `nil` |
|
||||||
|
|
||||||
|
Beispiel – Formatierung einer ID und ein Vektor-Kreuzprodukt:
|
||||||
|
|
||||||
|
```lisp
|
||||||
|
(tu-eq "ssg-id-format/1" "0001" (ssg-id-format 1))
|
||||||
|
(tu-eqf "vec3-cross/x-cross-y" '(0.0 0.0 1.0) (vec3-cross '(1.0 0.0 0.0) '(0.0 1.0 0.0)))
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Testfälle sind in `tu-tests-<modul>`-Funktionen gruppiert; `c:TEST_UNIT`
|
||||||
|
ruft alle nacheinander auf, gibt eine Zusammenfassung
|
||||||
|
(`Gesamt / PASS / FAIL`) aus und schreibt den Report `unit_results.txt` mit
|
||||||
|
einer abschließenden Zeile `RESULT: OK` bzw. `RESULT: FAIL` (die
|
||||||
|
`run_tests.bat` auswertet).
|
||||||
|
|
||||||
|
**Neuen Testfall ergänzen:** in der passenden `tu-tests-<modul>`-Funktion eine
|
||||||
|
`tu-eq`/`tu-eqf`/`tu-true`/`tu-nil`-Zeile hinzufügen. Für ein neues Modul eine
|
||||||
|
eigene `tu-tests-<modul>`-Funktion anlegen und in `c:TEST_UNIT` aufrufen.
|
||||||
|
Getestet werden sollten nur Funktionen ohne Zeichnungs-/Dialog-Umfeld – alles
|
||||||
|
mit `ssget`/`entget`/`entmake`/`command`/`vla-*`/`entsel`/DCL gehört in die
|
||||||
|
zeichnungsbasierten Integrationstests.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### `KreiselInsert.lsp` – ILS Kreisel und Eckrad
|
### `KreiselInsert.lsp` – ILS Kreisel und Eckrad
|
||||||
|
|
||||||
AutoLISP-Implementierung fuer ILS Kreisel und Eckrad. Enthaelt alle Befehle fuer Einfuegen, Verbinden, Neuzeichnen und Bearbeiten.
|
AutoLISP-Implementierung fuer ILS Kreisel und Eckrad. Enthaelt alle Befehle fuer Einfuegen, Verbinden, Neuzeichnen und Bearbeiten.
|
||||||
|
|||||||
+181
-3
@@ -90,11 +90,171 @@
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
;; ============================================================
|
||||||
|
;; KOS-KOMPRIMIERUNG (Position + Rotation als 24-Zeichen Base64-String)
|
||||||
|
;; ============================================================
|
||||||
|
;; Kodiert Position (x,y,z in mm) und Rotation (als Quaternion qx,qy,qz,qw)
|
||||||
|
;; verlustarm (24 Bit Fixed-Point je Wert) in einen 24-Zeichen Base64-String.
|
||||||
|
;; Ported aus einer interaktiven Referenz-Routine (c:ExportKOS), hier als
|
||||||
|
;; reine Funktion fuer den automatischen Export nutzbar. Alle Elemente in
|
||||||
|
;; diesem Projekt werden ausschliesslich um die Z-Achse gedreht (siehe
|
||||||
|
;; DREHUNG-Konvention weiter unten), daher genuegt eine Halbwinkel-Quaternion
|
||||||
|
;; fuer eine reine Z-Rotation (csv:z-angle-to-quat) -- keine vollstaendige
|
||||||
|
;; Rotationsmatrix/Quaternion-Extraktion noetig.
|
||||||
|
(setq *csv-b64-chars* "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
|
||||||
|
|
||||||
|
;; --- Wert auf [min-val, max-val] begrenzen ---
|
||||||
|
(defun csv:clamp (val min-val max-val)
|
||||||
|
(max min-val (min val max-val))
|
||||||
|
)
|
||||||
|
|
||||||
|
;; --- Rotationswinkel (Grad, reine Z-Drehung) -> Quaternion (qx qy qz qw) ---
|
||||||
|
(defun csv:z-angle-to-quat (deg / rad)
|
||||||
|
(setq rad (/ (* deg pi) 180.0))
|
||||||
|
(list 0.0 0.0 (sin (/ rad 2.0)) (cos (/ rad 2.0)))
|
||||||
|
)
|
||||||
|
|
||||||
|
;; --- Liste vorzeichenbehafteter 24-Bit-Integer -> Base64 (je 4 Zeichen) ---
|
||||||
|
;; Gemeinsamer Kodierer fuer csv:kos-encode und csv:trans-encode.
|
||||||
|
(defun csv:b64-encode-ints (int-list / b64-str val)
|
||||||
|
(setq b64-str "")
|
||||||
|
(foreach val int-list
|
||||||
|
(if (< val 0) (setq val (+ val 16777216))) ; Zweierkomplement (24 Bit)
|
||||||
|
(setq b64-str (strcat b64-str
|
||||||
|
(substr *csv-b64-chars* (1+ (lsh val -18)) 1)
|
||||||
|
(substr *csv-b64-chars* (1+ (logand (lsh val -12) 63)) 1)
|
||||||
|
(substr *csv-b64-chars* (1+ (logand (lsh val -6) 63)) 1)
|
||||||
|
(substr *csv-b64-chars* (1+ (logand val 63)) 1)
|
||||||
|
))
|
||||||
|
)
|
||||||
|
b64-str
|
||||||
|
)
|
||||||
|
|
||||||
|
;; --- Position (mm) + Quaternion -> 24-Zeichen Base64-String ---
|
||||||
|
;; Fixed-Point: x,y,z (Faktor 1000 = mm) und qx,qy,qz werden je auf 24 Bit
|
||||||
|
;; gerundet (6 Werte * 4 Zeichen = 24 Zeichen). qw wird nicht mitkodiert,
|
||||||
|
;; sondern nur zur Vorzeichen-Normierung verwendet (q und -q sind dieselbe
|
||||||
|
;; Rotation). Fuer den Insertpoint (volle Lage), siehe csv:trans-encode fuer
|
||||||
|
;; reine Positionen (K1-K4).
|
||||||
|
(defun csv:kos-encode (x y z qx qy qz qw)
|
||||||
|
(if (< qw 0.0)
|
||||||
|
(setq qx (- qx) qy (- qy) qz (- qz))
|
||||||
|
)
|
||||||
|
(csv:b64-encode-ints (list
|
||||||
|
(fix (csv:clamp (* x 1000.0) -8388607 8388607))
|
||||||
|
(fix (csv:clamp (* y 1000.0) -8388607 8388607))
|
||||||
|
(fix (csv:clamp (* z 1000.0) -8388607 8388607))
|
||||||
|
(fix (csv:clamp (* qx 8388607.0) -8388607 8388607))
|
||||||
|
(fix (csv:clamp (* qy 8388607.0) -8388607 8388607))
|
||||||
|
(fix (csv:clamp (* qz 8388607.0) -8388607 8388607))
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
;; --- Position (nur x,y,z) -> 12-Zeichen Base64-String ---
|
||||||
|
;; Fixed-Point Faktor 10.0 (0.1 mm Genauigkeit, Bereich +-838 m), 3 Werte *
|
||||||
|
;; 4 Zeichen = 12 Zeichen. Fuer die Koordinatensysteme K1-K4 (nur Position,
|
||||||
|
;; keine Rotation). Ported aus der Referenz-Routine c:ExportTrans.
|
||||||
|
(defun csv:trans-encode (x y z)
|
||||||
|
(csv:b64-encode-ints (list
|
||||||
|
(fix (csv:clamp (* x 10.0) -8388607 8388607))
|
||||||
|
(fix (csv:clamp (* y 10.0) -8388607 8388607))
|
||||||
|
(fix (csv:clamp (* z 10.0) -8388607 8388607))
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
;; --- Lokalen Punkt (relativ zum Block-Ursprung) unter Beruecksichtigung der
|
||||||
|
;; Eltern-Rotation (reine Z-Drehung um den Einfuegepunkt) in Weltkoordinaten
|
||||||
|
;; umrechnen. Rueckgabe: (x y z) ---
|
||||||
|
(defun csv:local-to-world (parent-pt parent-rot-rad local-pt / c s lx ly lz pz)
|
||||||
|
(setq c (cos parent-rot-rad) s (sin parent-rot-rad))
|
||||||
|
(setq lx (car local-pt) ly (cadr local-pt))
|
||||||
|
(setq lz (if (caddr local-pt) (caddr local-pt) 0.0))
|
||||||
|
(setq pz (if (caddr parent-pt) (caddr parent-pt) 0.0))
|
||||||
|
(list
|
||||||
|
(+ (car parent-pt) (- (* lx c) (* ly s)))
|
||||||
|
(+ (cadr parent-pt) (+ (* lx s) (* ly c)))
|
||||||
|
(+ pz lz)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
;; --- K1-K4-Koordinatensysteme eines Blocks als Positions-Strings lesen ---
|
||||||
|
;; ename = Entity-Name des platzierten INSERT (Omniflo Bogen/Weiche/Gerade).
|
||||||
|
;; pt/rot-rad = bereits ermittelte Welt-Position/-Rotation (radiant) des
|
||||||
|
;; Eltern-INSERT (siehe csv:block-to-json), keine erneute Abfrage.
|
||||||
|
;; Sucht in der Block-DEFINITION (wie omni:get-block-hoehe) nach direkten
|
||||||
|
;; Sub-INSERTs "K1".."K4" und rechnet deren lokale Position ueber die
|
||||||
|
;; Eltern-Rotation (reine Z-Drehung) in Weltkoordinaten um. Es wird nur die
|
||||||
|
;; POSITION kodiert (12-Zeichen-String, csv:trans-encode) - keine Rotation.
|
||||||
|
;; Zusaetzlich werden Strecken-Koordinatensysteme KS_EIN->K1 und KS_AUS->K2
|
||||||
|
;; (inkl. der KSYS_-Varianten, siehe ks-normalize-name in vf_core.lsp) gemappt,
|
||||||
|
;; sofern kein echter K1/K2 am Block vorhanden ist (echte K-Bloecke haben
|
||||||
|
;; Vorrang: sie werden vorne angehaengt, csv:k-kos liest per assoc den ersten).
|
||||||
|
;; Rueckgabe: Assoc-Liste (("K1" . "<pos-string>") ("K2" . ...) ...), nur
|
||||||
|
;; fuer tatsaechlich vorhandene K-Bloecke (nicht jedes Element hat alle 4).
|
||||||
|
(defun csv:get-k-kos-strings (ename pt rot-rad / ed bname blk-tbl sub-ent sub-ed
|
||||||
|
sub-bname sub-pt world-pt target is-real result)
|
||||||
|
(setq ed (entget ename))
|
||||||
|
(setq bname (cdr (assoc 2 ed)))
|
||||||
|
(setq blk-tbl (tblsearch "BLOCK" bname))
|
||||||
|
(setq result nil)
|
||||||
|
(if blk-tbl
|
||||||
|
(progn
|
||||||
|
(setq sub-ent (entnext (cdr (assoc -2 blk-tbl))))
|
||||||
|
(while sub-ent
|
||||||
|
(setq sub-ed (entget sub-ent))
|
||||||
|
(if (= (cdr (assoc 0 sub-ed)) "INSERT")
|
||||||
|
(progn
|
||||||
|
(setq sub-bname (strcase (cdr (assoc 2 sub-ed))))
|
||||||
|
;; Ziel-Slot bestimmen: echte K1-K4 direkt, KS_EIN/KS_AUS gemappt.
|
||||||
|
(setq is-real (wcmatch sub-bname "K1,K2,K3,K4"))
|
||||||
|
(setq target
|
||||||
|
(cond
|
||||||
|
(is-real sub-bname)
|
||||||
|
((or (= sub-bname "KS_EIN") (= sub-bname "KSYS_EIN")) "K1")
|
||||||
|
((or (= sub-bname "KS_AUS") (= sub-bname "KSYS_AUS")) "K2")
|
||||||
|
(t nil)))
|
||||||
|
;; Echte K-Bloecke immer aufnehmen (vorne = Vorrang bei assoc);
|
||||||
|
;; KS-abgeleitete nur, wenn der Slot noch nicht belegt ist.
|
||||||
|
(if (and target (or is-real (not (assoc target result))))
|
||||||
|
(progn
|
||||||
|
(setq sub-pt (cdr (assoc 10 sub-ed)))
|
||||||
|
(setq world-pt (csv:local-to-world pt rot-rad sub-pt))
|
||||||
|
(setq result (cons
|
||||||
|
(cons target
|
||||||
|
(csv:trans-encode
|
||||||
|
(car world-pt) (cadr world-pt) (caddr world-pt)))
|
||||||
|
result))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(setq sub-ent (entnext sub-ent))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result
|
||||||
|
)
|
||||||
|
|
||||||
|
;; --- Wert aus csv:get-k-kos-strings nach Name lesen, "" falls nicht vorhanden ---
|
||||||
|
(defun csv:k-kos (k-list name / found)
|
||||||
|
(setq found (assoc name k-list))
|
||||||
|
(if found (cdr found) "")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Mindesthoehe der Bounding-Box (Z-Ausdehnung), aus cfg/export.cfg ---
|
||||||
|
;; [Boundingbox] bb_minimum_height_mm, Default 1 (mm) falls nicht gesetzt.
|
||||||
|
;; Verhindert dz=0 bei flachen 2D-Bloecken (Draufsicht ohne Z-Ausdehnung).
|
||||||
|
(defun csv:bb-minimum-height-mm ()
|
||||||
|
(atof (export:cfg "Boundingbox" "bb_minimum_height_mm" "1"))
|
||||||
|
)
|
||||||
|
|
||||||
;; --- Bounding-Box (WCS, achsparallel) eines INSERT-Blocks ermitteln ---
|
;; --- Bounding-Box (WCS, achsparallel) eines INSERT-Blocks ermitteln ---
|
||||||
;; Erzwingt vorher ein vla-update (Regen), da vla-getboundingbox sonst auf
|
;; Erzwingt vorher ein vla-update (Regen), da vla-getboundingbox sonst auf
|
||||||
;; nicht-regenerierten oder auf eingefrorenen Layern liegenden Bloecken scheitert.
|
;; nicht-regenerierten oder auf eingefrorenen Layern liegenden Bloecken scheitert.
|
||||||
;; Rueckgabe: Liste (cx cy cz dx dy dz) -- Mittelpunkt + Ausdehnung, oder nil bei Fehler.
|
;; Rueckgabe: Liste (cx cy cz dx dy dz) -- Mittelpunkt + Ausdehnung, oder nil bei Fehler.
|
||||||
(defun csv:get-bbox (ename / obj res minpt maxpt)
|
;; dz wird auf csv:bb-minimum-height-mm angehoben, falls kleiner (siehe dort).
|
||||||
|
(defun csv:get-bbox (ename / obj res minpt maxpt dz min-hoehe)
|
||||||
(setq obj (vlax-ename->vla-object ename))
|
(setq obj (vlax-ename->vla-object ename))
|
||||||
(vl-catch-all-apply 'vla-update (list obj))
|
(vl-catch-all-apply 'vla-update (list obj))
|
||||||
(setq res
|
(setq res
|
||||||
@@ -107,13 +267,16 @@
|
|||||||
(progn
|
(progn
|
||||||
(setq minpt (car res))
|
(setq minpt (car res))
|
||||||
(setq maxpt (cadr res))
|
(setq maxpt (cadr res))
|
||||||
|
(setq dz (- (caddr maxpt) (caddr minpt)))
|
||||||
|
(setq min-hoehe (csv:bb-minimum-height-mm))
|
||||||
|
(if (< dz min-hoehe) (setq dz min-hoehe))
|
||||||
(list
|
(list
|
||||||
(/ (+ (car minpt) (car maxpt)) 2.0)
|
(/ (+ (car minpt) (car maxpt)) 2.0)
|
||||||
(/ (+ (cadr minpt) (cadr maxpt)) 2.0)
|
(/ (+ (cadr minpt) (cadr maxpt)) 2.0)
|
||||||
(/ (+ (caddr minpt) (caddr maxpt)) 2.0)
|
(/ (+ (caddr minpt) (caddr maxpt)) 2.0)
|
||||||
(- (car maxpt) (car minpt))
|
(- (car maxpt) (car minpt))
|
||||||
(- (cadr maxpt) (cadr minpt))
|
(- (cadr maxpt) (cadr minpt))
|
||||||
(- (caddr maxpt) (caddr minpt))
|
dz
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -155,7 +318,12 @@
|
|||||||
;; --- Einen INSERT-Block als JSON-Objekt schreiben ---
|
;; --- Einen INSERT-Block als JSON-Objekt schreiben ---
|
||||||
;; include-bbox = T -> zusaetzlich Bounding-Box (Mitte + Ausdehnung) ermitteln
|
;; include-bbox = T -> zusaetzlich Bounding-Box (Mitte + Ausdehnung) ermitteln
|
||||||
;; und als "bbox"-Objekt anhaengen (nur fuer EXPORTCSV, nicht fuer EXPORTSIVAS).
|
;; und als "bbox"-Objekt anhaengen (nur fuer EXPORTCSV, nicht fuer EXPORTSIVAS).
|
||||||
(defun csv:block-to-json (ename include-bbox / ed blk-name layer pt rotation handle attribs bbox bbox-json)
|
;; "insertpoint" (KOS-String von Position+Rotation des Blocks selbst) und
|
||||||
|
;; "k1".."k4" (KOS-Strings der Koordinatensysteme im Block, siehe
|
||||||
|
;; csv:get-k-kos-strings) werden immer geschrieben, unabhaengig von
|
||||||
|
;; include-bbox -- Kodierung ueber csv:kos-encode (siehe dort).
|
||||||
|
(defun csv:block-to-json (ename include-bbox / ed blk-name layer pt rotation handle attribs bbox bbox-json
|
||||||
|
insert-quat insert-kos k-list)
|
||||||
(setq ed (entget ename))
|
(setq ed (entget ename))
|
||||||
(setq blk-name (cdr (assoc 2 ed)))
|
(setq blk-name (cdr (assoc 2 ed)))
|
||||||
(setq layer (cdr (assoc 8 ed)))
|
(setq layer (cdr (assoc 8 ed)))
|
||||||
@@ -164,6 +332,11 @@
|
|||||||
(setq handle (cdr (assoc 5 ed)))
|
(setq handle (cdr (assoc 5 ed)))
|
||||||
(if (null rotation) (setq rotation 0.0))
|
(if (null rotation) (setq rotation 0.0))
|
||||||
(setq attribs (csv:read-attribs ename))
|
(setq attribs (csv:read-attribs ename))
|
||||||
|
(setq insert-quat (csv:z-angle-to-quat (* (/ rotation pi) 180.0)))
|
||||||
|
(setq insert-kos (csv:kos-encode
|
||||||
|
(car pt) (cadr pt) (if (caddr pt) (caddr pt) 0.0)
|
||||||
|
(nth 0 insert-quat) (nth 1 insert-quat) (nth 2 insert-quat) (nth 3 insert-quat)))
|
||||||
|
(setq k-list (csv:get-k-kos-strings ename pt rotation))
|
||||||
(setq bbox-json "")
|
(setq bbox-json "")
|
||||||
(if include-bbox
|
(if include-bbox
|
||||||
(progn
|
(progn
|
||||||
@@ -193,6 +366,11 @@
|
|||||||
",\"z\":" (rtos (if (caddr pt) (caddr pt) 0.0) 2 4)
|
",\"z\":" (rtos (if (caddr pt) (caddr pt) 0.0) 2 4)
|
||||||
",\"rotation\":" (rtos (* (/ rotation pi) 180.0) 2 4)
|
",\"rotation\":" (rtos (* (/ rotation pi) 180.0) 2 4)
|
||||||
",\"attribs\":" attribs
|
",\"attribs\":" attribs
|
||||||
|
",\"insertpoint\":\"" insert-kos "\""
|
||||||
|
",\"k1\":\"" (csv:k-kos k-list "K1") "\""
|
||||||
|
",\"k2\":\"" (csv:k-kos k-list "K2") "\""
|
||||||
|
",\"k3\":\"" (csv:k-kos k-list "K3") "\""
|
||||||
|
",\"k4\":\"" (csv:k-kos k-list "K4") "\""
|
||||||
bbox-json
|
bbox-json
|
||||||
"}"
|
"}"
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -621,7 +621,7 @@
|
|||||||
(setq *strecke-attr-hinten*
|
(setq *strecke-attr-hinten*
|
||||||
'(("ANZAHL_SEPARATOR" "1")
|
'(("ANZAHL_SEPARATOR" "1")
|
||||||
("ANZAHL_SCANNER" "0")
|
("ANZAHL_SCANNER" "0")
|
||||||
("GERUEST_EINZELMODUL" "0")
|
("GERUEST_EINZELMODUL" "1")
|
||||||
("GERUEST_TYP" "Schoenenberger Geruest")))
|
("GERUEST_TYP" "Schoenenberger Geruest")))
|
||||||
|
|
||||||
;; ============================================================
|
;; ============================================================
|
||||||
|
|||||||
+1
-1
@@ -1076,7 +1076,7 @@
|
|||||||
geruest-einzelmodul geruest-typ
|
geruest-einzelmodul geruest-typ
|
||||||
/ vf-bname vf-ss vf-e vf-insert montagehoehe-m attdef-ypos L_GF_m-str)
|
/ vf-bname vf-ss vf-e vf-insert montagehoehe-m attdef-ypos L_GF_m-str)
|
||||||
(if (null hz) (setq hz 0.0))
|
(if (null hz) (setq hz 0.0))
|
||||||
(if (null geruest-einzelmodul) (setq geruest-einzelmodul "0"))
|
(if (null geruest-einzelmodul) (setq geruest-einzelmodul "1"))
|
||||||
(if (null geruest-typ) (setq geruest-typ (ssg-geruest-idx-to-typ (ssg-geruest-typ-to-idx nil))))
|
(if (null geruest-typ) (setq geruest-typ (ssg-geruest-idx-to-typ (ssg-geruest-typ-to-idx nil))))
|
||||||
;; Beschriftungstext erzeugen
|
;; Beschriftungstext erzeugen
|
||||||
(vf-make-label vf-nummer hoehe-von hoehe-bis deltaH deltaL L_VF L_GF1 L_GF2
|
(vf-make-label vf-nummer hoehe-von hoehe-bis deltaH deltaL L_VF L_GF1 L_GF2
|
||||||
|
|||||||
@@ -784,7 +784,7 @@
|
|||||||
(set_tile "dimension"
|
(set_tile "dimension"
|
||||||
(if (equal (cond ((cdr (assoc "dim" prefill))) ((ssg-ils-dim-aktuell))) "2D") "1" "0"))
|
(if (equal (cond ((cdr (assoc "dim" prefill))) ((ssg-ils-dim-aktuell))) "2D") "1" "0"))
|
||||||
(set_tile "geruest_einzelmodul"
|
(set_tile "geruest_einzelmodul"
|
||||||
(if (and prefill (equal (cdr (assoc "geruest-einzelmodul" prefill)) "1")) "1" "0"))
|
(if (or (null prefill) (equal (cdr (assoc "geruest-einzelmodul" prefill)) "1")) "1" "0"))
|
||||||
(start_list "geruest_typ")
|
(start_list "geruest_typ")
|
||||||
(foreach opt *ssg-geruest-optionen* (add_list opt))
|
(foreach opt *ssg-geruest-optionen* (add_list opt))
|
||||||
(end_list)
|
(end_list)
|
||||||
@@ -1192,7 +1192,7 @@
|
|||||||
(cons "einfuegehoehe" z-start) (cons "hz" hz) (cons "seite" seite)
|
(cons "einfuegehoehe" z-start) (cons "hz" hz) (cons "seite" seite)
|
||||||
;; Dimension aus XDATA erkennen (Default 3D, falls Alt-Block ohne Eintrag)
|
;; Dimension aus XDATA erkennen (Default 3D, falls Alt-Block ohne Eintrag)
|
||||||
(cons "dim" (cond ((ssg-dim-xdata-lesen ent)) ("3D")))
|
(cons "dim" (cond ((ssg-dim-xdata-lesen ent)) ("3D")))
|
||||||
(cons "geruest-einzelmodul" (cond ((cdr (assoc "GERUEST_EINZELMODUL" attribs))) ("0")))
|
(cons "geruest-einzelmodul" (cond ((cdr (assoc "GERUEST_EINZELMODUL" attribs))) ("1")))
|
||||||
(cons "geruest-typ" (cdr (assoc "GERUEST_TYP" attribs)))
|
(cons "geruest-typ" (cdr (assoc "GERUEST_TYP" attribs)))
|
||||||
;; Motorseite aus Attribut (Default rechts, falls Alt-Block ohne Eintrag)
|
;; Motorseite aus Attribut (Default rechts, falls Alt-Block ohne Eintrag)
|
||||||
(cons "motorseite" (cond ((cdr (assoc "MOTORSEITE" attribs))) ("rechts")))))
|
(cons "motorseite" (cond ((cdr (assoc "MOTORSEITE" attribs))) ("rechts")))))
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ echo ================================================================
|
|||||||
echo BricsCAD wird gestartet mit dem Testskript...
|
echo BricsCAD wird gestartet mit dem Testskript...
|
||||||
echo Nach Abschluss des Tests BricsCAD schliessen.
|
echo Nach Abschluss des Tests BricsCAD schliessen.
|
||||||
echo.
|
echo.
|
||||||
start /wait "" %BRICSCAD% /s "%DXFM_TESTS%\ILS_test.scr"
|
REM /b = Skript ausfuehren (NICHT /s - das ist der Support-Ordner-Schalter).
|
||||||
|
start /wait "" "%BRICSCAD%" /b "%DXFM_TESTS%\ILS_test.scr"
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ================================================================
|
echo ================================================================
|
||||||
|
|||||||
+68
-2
@@ -4,12 +4,15 @@ REM ================================================================
|
|||||||
REM run_tests.bat - Testausfuehrung fuer SSG_LIB
|
REM run_tests.bat - Testausfuehrung fuer SSG_LIB
|
||||||
REM
|
REM
|
||||||
REM Schalter:
|
REM Schalter:
|
||||||
|
REM --lisp Startet BricsCAD headless, fuehrt die LISP-Unit-Tests
|
||||||
|
REM (TEST_UNIT) aus und wertet tests\output\unit_results.txt aus
|
||||||
REM --check Fuehrt pytest-Validierung aus
|
REM --check Fuehrt pytest-Validierung aus
|
||||||
REM --diff_references Vergleicht CSVs in tests\reference und tests\output,
|
REM --diff_references Vergleicht CSVs in tests\reference und tests\output,
|
||||||
REM ruft bei Unterschieden das Diff-Tool (meld) auf
|
REM ruft bei Unterschieden das Diff-Tool (meld) auf
|
||||||
REM (ohne) Zeigt Hilfe an
|
REM (ohne) Zeigt Hilfe an
|
||||||
REM
|
REM
|
||||||
REM Beispiele:
|
REM Beispiele:
|
||||||
|
REM bin\run_tests.bat --lisp
|
||||||
REM bin\run_tests.bat --check
|
REM bin\run_tests.bat --check
|
||||||
REM bin\run_tests.bat --check test_kreisel.py
|
REM bin\run_tests.bat --check test_kreisel.py
|
||||||
REM bin\run_tests.bat --diff_references
|
REM bin\run_tests.bat --diff_references
|
||||||
@@ -30,10 +33,16 @@ set DO_CHECK=0
|
|||||||
set CHECK_ARG=
|
set CHECK_ARG=
|
||||||
set DO_DIFF=0
|
set DO_DIFF=0
|
||||||
set DIFF_ARG=
|
set DIFF_ARG=
|
||||||
|
set DO_LISP=0
|
||||||
|
|
||||||
REM Argumente parsen
|
REM Argumente parsen
|
||||||
:parse_args
|
:parse_args
|
||||||
if "%~1"=="" goto :after_args
|
if "%~1"=="" goto :after_args
|
||||||
|
if "%~1"=="--lisp" (
|
||||||
|
set DO_LISP=1
|
||||||
|
shift
|
||||||
|
goto :parse_args
|
||||||
|
)
|
||||||
if "%~1"=="--check" (
|
if "%~1"=="--check" (
|
||||||
set DO_CHECK=1
|
set DO_CHECK=1
|
||||||
if not "%~2"=="" (
|
if not "%~2"=="" (
|
||||||
@@ -56,7 +65,60 @@ echo Unbekannter Schalter: %~1
|
|||||||
goto :show_help
|
goto :show_help
|
||||||
|
|
||||||
:after_args
|
:after_args
|
||||||
if %DO_CHECK%==0 if %DO_DIFF%==0 goto :show_help
|
if %DO_LISP%==0 if %DO_CHECK%==0 if %DO_DIFF%==0 goto :show_help
|
||||||
|
|
||||||
|
REM ================================================================
|
||||||
|
REM --lisp: LISP-Unit-Tests in BricsCAD ausfuehren
|
||||||
|
REM ================================================================
|
||||||
|
if %DO_LISP%==1 (
|
||||||
|
echo.
|
||||||
|
echo ================================================================
|
||||||
|
echo LISP UNIT-TESTS ^(TEST_UNIT^)
|
||||||
|
echo ================================================================
|
||||||
|
|
||||||
|
if not defined BRICSCAD (
|
||||||
|
echo FEHLER: BricsCAD nicht gefunden - siehe setenv.bat.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
if not exist "%DXFM_TESTS%\test_unit.scr" (
|
||||||
|
echo FEHLER: %DXFM_TESTS%\test_unit.scr nicht gefunden.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Altes Ergebnis loeschen - fehlender Report = fehlgeschlagener Lauf
|
||||||
|
if exist "%DXFM_TESTOUT%\unit_results.txt" del "%DXFM_TESTOUT%\unit_results.txt"
|
||||||
|
|
||||||
|
echo BricsCAD wird im Batch-Modus gestartet ^(Skript: test_unit.scr^)...
|
||||||
|
REM /b = Skript im Batch-Modus ausfuehren (NICHT /s - das ist der
|
||||||
|
REM Support-Ordner-Schalter und ignoriert die .scr komplett).
|
||||||
|
REM /nologo unterdrueckt den Startbildschirm. Das Skript endet mit
|
||||||
|
REM _.QUIT, daher schliesst sich BricsCAD nach dem Lauf selbst.
|
||||||
|
start /wait "" "%BRICSCAD%" /nologo /b "%DXFM_TESTS%\test_unit.scr"
|
||||||
|
|
||||||
|
if not exist "%DXFM_TESTOUT%\unit_results.txt" (
|
||||||
|
echo.
|
||||||
|
echo FEHLER: Kein Report erzeugt ^(%DXFM_TESTOUT%\unit_results.txt^).
|
||||||
|
echo Lief TEST_UNIT in BricsCAD durch?
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo.
|
||||||
|
type "%DXFM_TESTOUT%\unit_results.txt"
|
||||||
|
|
||||||
|
findstr /b /c:"RESULT: OK" "%DXFM_TESTOUT%\unit_results.txt" >nul
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo.
|
||||||
|
echo ================================================================
|
||||||
|
echo ERGEBNIS: LISP-TESTS FEHLGESCHLAGEN
|
||||||
|
echo ================================================================
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
echo.
|
||||||
|
echo ================================================================
|
||||||
|
echo ERGEBNIS: ALLE LISP-TESTS BESTANDEN
|
||||||
|
echo ================================================================
|
||||||
|
)
|
||||||
|
|
||||||
REM ================================================================
|
REM ================================================================
|
||||||
REM --check: pytest-Validierung
|
REM --check: pytest-Validierung
|
||||||
@@ -159,10 +221,13 @@ goto :eof
|
|||||||
|
|
||||||
:show_help
|
:show_help
|
||||||
echo.
|
echo.
|
||||||
echo Aufruf: bin\run_tests.bat --check [testdatei]
|
echo Aufruf: bin\run_tests.bat --lisp
|
||||||
|
echo bin\run_tests.bat --check [testdatei]
|
||||||
echo bin\run_tests.bat --diff_references [csv-datei]
|
echo bin\run_tests.bat --diff_references [csv-datei]
|
||||||
echo.
|
echo.
|
||||||
echo Schalter:
|
echo Schalter:
|
||||||
|
echo --lisp Startet BricsCAD headless, fuehrt TEST_UNIT aus
|
||||||
|
echo und wertet tests\output\unit_results.txt aus
|
||||||
echo --check Fuehrt pytest-Validierung fuer alle Module aus
|
echo --check Fuehrt pytest-Validierung fuer alle Module aus
|
||||||
echo --check test_kreisel.py Validiert nur Kreisel-Tests
|
echo --check test_kreisel.py Validiert nur Kreisel-Tests
|
||||||
echo --diff_references Vergleicht alle CSVs in tests\reference/output,
|
echo --diff_references Vergleicht alle CSVs in tests\reference/output,
|
||||||
@@ -170,6 +235,7 @@ echo ruft bei Unterschieden meld auf
|
|||||||
echo --diff_references datei.csv Vergleicht nur die angegebene CSV-Datei
|
echo --diff_references datei.csv Vergleicht nur die angegebene CSV-Datei
|
||||||
echo.
|
echo.
|
||||||
echo Workflow:
|
echo Workflow:
|
||||||
|
echo 0. bin\run_tests.bat --lisp ^(reine LISP-Funktionen, ohne Zeichnung^)
|
||||||
echo 1. In BricsCAD: SSG_RUN_ALL_TESTS ausfuehren
|
echo 1. In BricsCAD: SSG_RUN_ALL_TESTS ausfuehren
|
||||||
echo 2. bin\run_tests.bat --check
|
echo 2. bin\run_tests.bat --check
|
||||||
echo 3. bin\run_tests.bat --diff_references
|
echo 3. bin\run_tests.bat --diff_references
|
||||||
|
|||||||
Binary file not shown.
+38
-5
@@ -11,12 +11,18 @@ Aufruf:
|
|||||||
python export_csv.py <export_raw.json> <data_dir> <output.csv>
|
python export_csv.py <export_raw.json> <data_dir> <output.csv>
|
||||||
|
|
||||||
CSV-Format:
|
CSV-Format:
|
||||||
Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox;Nachbarn;Fehler;Merkmale
|
Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox;
|
||||||
|
Insertpoint;K1;K2;K3;K4;Nachbarn;Fehler;Merkmale
|
||||||
|
|
||||||
Position und Boundingbox stammen aus der von export.lsp (csv:get-bbox, per
|
Position und Boundingbox stammen aus der von export.lsp (csv:get-bbox, per
|
||||||
vla-getboundingbox) ermittelten Bounding-Box je Block:
|
vla-getboundingbox) ermittelten Bounding-Box je Block:
|
||||||
Position = Mittenkoordinate x, y, z
|
Position = Mittenkoordinate x, y, z
|
||||||
Boundingbox = Ausdehnung (Laenge, Breite, Hoehe) in x, y, z
|
Boundingbox = Ausdehnung (Laenge, Breite, Hoehe) in x, y, z
|
||||||
|
Insertpoint = KOS-String (24-Zeichen Base64, Position+Rotation als Quaternion,
|
||||||
|
siehe csv:kos-encode in export.lsp) des Block-Einfuegepunkts selbst.
|
||||||
|
K1-K4 = Positions-Strings der im Block enthaltenen Koordinatensysteme K1..K4
|
||||||
|
(12-Zeichen Base64, nur Position ohne Rotation, siehe csv:trans-encode;
|
||||||
|
nur bei Omniflo Bogen/Weiche/Gerade vorhanden, sonst leer).
|
||||||
Planquadrat wird aus der x/y-Koordinate des Blocks berechnet, siehe
|
Planquadrat wird aus der x/y-Koordinate des Blocks berechnet, siehe
|
||||||
export_planquadrat.py ([Planquadrate] in cfg/export.cfg).
|
export_planquadrat.py ([Planquadrate] in cfg/export.cfg).
|
||||||
Nachbarn = kommaseparierte TeileId-Liste ueberschneidender Elemente (Bounding-
|
Nachbarn = kommaseparierte TeileId-Liste ueberschneidender Elemente (Bounding-
|
||||||
@@ -34,7 +40,10 @@ Omniflo-Elemente brauchen mindestens einen Partner, Gefaellestrecke/
|
|||||||
Foerderer/Strecke-Modul mindestens zwei (siehe export_neighbors.py
|
Foerderer/Strecke-Modul mindestens zwei (siehe export_neighbors.py
|
||||||
MIN_PARTNER).
|
MIN_PARTNER).
|
||||||
Position, Boundingbox, Planquadrat, Nachbarn und Fehler sind nur fuer
|
Position, Boundingbox, Planquadrat, Nachbarn und Fehler sind nur fuer
|
||||||
EXPORTCSV vorhanden (nicht EXPORTSIVAS) - siehe csv:run-export.
|
EXPORTCSV vorhanden (nicht EXPORTSIVAS) - siehe csv:run-export. Insertpoint/
|
||||||
|
K1-K4 stehen zwar in export_raw.json fuer jeden Export zur Verfuegung (siehe
|
||||||
|
csv:block-to-json), werden aber ebenfalls nur von diesem Skript (EXPORTCSV)
|
||||||
|
als CSV-Spalten ausgegeben.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
@@ -243,21 +252,34 @@ def build_kreisel_merkmale(block):
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def bbox_columns(block):
|
def bbox_columns(block):
|
||||||
"""Liest die von export.lsp (csv:get-bbox) ermittelte Bounding-Box.
|
"""Liest die von export.lsp (csv:get-bbox) ermittelte Bounding-Box, sowie
|
||||||
|
die KOS-Strings von Insertpoint und K1-K4 (csv:kos-encode/csv:block-to-json).
|
||||||
|
|
||||||
Rueckgabe: dict mit "position" (Mittenkoordinate x,y,z) und "boundingbox"
|
Rueckgabe: dict mit "position" (Mittenkoordinate x,y,z) und "boundingbox"
|
||||||
(Ausdehnung x,y,z) als Strings, oder leere Strings falls kein bbox-Feld
|
(Ausdehnung x,y,z) als Strings, oder leere Strings falls kein bbox-Feld
|
||||||
vorhanden ist (z.B. EXPORTSIVAS oder die synthetische Omniflo-Sum-Zeile).
|
vorhanden ist (z.B. EXPORTSIVAS oder die synthetische Omniflo-Sum-Zeile).
|
||||||
|
"insertpoint" ist ein 24-Zeichen Base64-KOS-String (Position + Rotation
|
||||||
|
als Quaternion, csv:kos-encode); "k1".."k4" sind 12-Zeichen Positions-
|
||||||
|
Strings (nur Position, csv:trans-encode) - leer falls das jeweilige
|
||||||
|
Koordinatensystem am Block nicht existiert.
|
||||||
"_bbox" enthaelt zusaetzlich die rohen Zahlenwerte fuer die Nachbarschafts-
|
"_bbox" enthaelt zusaetzlich die rohen Zahlenwerte fuer die Nachbarschafts-
|
||||||
erkennung (siehe export_neighbors.py), wird nicht in die CSV geschrieben.
|
erkennung (siehe export_neighbors.py), wird nicht in die CSV geschrieben.
|
||||||
"""
|
"""
|
||||||
|
kos_cols = {
|
||||||
|
"insertpoint": block.get("insertpoint", ""),
|
||||||
|
"k1": block.get("k1", ""),
|
||||||
|
"k2": block.get("k2", ""),
|
||||||
|
"k3": block.get("k3", ""),
|
||||||
|
"k4": block.get("k4", ""),
|
||||||
|
}
|
||||||
bbox = block.get("bbox")
|
bbox = block.get("bbox")
|
||||||
if not bbox:
|
if not bbox:
|
||||||
return {"position": "", "boundingbox": "", "_bbox": None}
|
return {"position": "", "boundingbox": "", "_bbox": None, **kos_cols}
|
||||||
return {
|
return {
|
||||||
"position": f'{bbox.get("cx", 0):.2f}, {bbox.get("cy", 0):.2f}, {bbox.get("cz", 0):.2f}',
|
"position": f'{bbox.get("cx", 0):.2f}, {bbox.get("cy", 0):.2f}, {bbox.get("cz", 0):.2f}',
|
||||||
"boundingbox": f'{bbox.get("dx", 0):.2f}, {bbox.get("dy", 0):.2f}, {bbox.get("dz", 0):.2f}',
|
"boundingbox": f'{bbox.get("dx", 0):.2f}, {bbox.get("dy", 0):.2f}, {bbox.get("dz", 0):.2f}',
|
||||||
"_bbox": bbox,
|
"_bbox": bbox,
|
||||||
|
**kos_cols,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -468,6 +490,11 @@ def process_blocks(blocks, lookup):
|
|||||||
len_ap110_mm, len_ap60_mm),
|
len_ap110_mm, len_ap60_mm),
|
||||||
"position": "",
|
"position": "",
|
||||||
"boundingbox": "",
|
"boundingbox": "",
|
||||||
|
"insertpoint": "",
|
||||||
|
"k1": "",
|
||||||
|
"k2": "",
|
||||||
|
"k3": "",
|
||||||
|
"k4": "",
|
||||||
})
|
})
|
||||||
|
|
||||||
neighbor_ids = compute_neighbor_ids(
|
neighbor_ids = compute_neighbor_ids(
|
||||||
@@ -495,6 +522,11 @@ def format_csv_line(item):
|
|||||||
f';1'
|
f';1'
|
||||||
f';"{csv_quote(item["position"])}"'
|
f';"{csv_quote(item["position"])}"'
|
||||||
f';"{csv_quote(item["boundingbox"])}"'
|
f';"{csv_quote(item["boundingbox"])}"'
|
||||||
|
f';"{csv_quote(item["insertpoint"])}"'
|
||||||
|
f';"{csv_quote(item["k1"])}"'
|
||||||
|
f';"{csv_quote(item["k2"])}"'
|
||||||
|
f';"{csv_quote(item["k3"])}"'
|
||||||
|
f';"{csv_quote(item["k4"])}"'
|
||||||
f';"{csv_quote(item["nachbarn"])}"'
|
f';"{csv_quote(item["nachbarn"])}"'
|
||||||
f';"{csv_quote(item["fehler"])}"'
|
f';"{csv_quote(item["fehler"])}"'
|
||||||
f';{merkmale_json}'
|
f';{merkmale_json}'
|
||||||
@@ -528,7 +560,8 @@ def main():
|
|||||||
|
|
||||||
items = process_blocks(blocks, lookup)
|
items = process_blocks(blocks, lookup)
|
||||||
|
|
||||||
header = "Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox;Nachbarn;Fehler;Merkmale"
|
header = ("Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox;"
|
||||||
|
"Insertpoint;K1;K2;K3;K4;Nachbarn;Fehler;Merkmale")
|
||||||
with open(output_csv, "w", encoding="utf-8") as f:
|
with open(output_csv, "w", encoding="utf-8") as f:
|
||||||
f.write(header + "\n")
|
f.write(header + "\n")
|
||||||
for item in items:
|
for item in items:
|
||||||
|
|||||||
+50
-20
@@ -98,17 +98,13 @@ def build_gefaellestrecke_details(block):
|
|||||||
"""Merkmale-Dict fuer eine einzelne ILS Gefaellestrecke (GF_N-Block)."""
|
"""Merkmale-Dict fuer eine einzelne ILS Gefaellestrecke (GF_N-Block)."""
|
||||||
attribs = block.get("attribs", {})
|
attribs = block.get("attribs", {})
|
||||||
|
|
||||||
def mm_to_m(key):
|
|
||||||
return str(round(safe_float(attribs.get(key, "0")) / 1000.0, 2))
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"Hoehe oben": mm_to_m("HOEHE_VON_mm"),
|
"Hoehe in m": attribs.get("MONTAGEHOEHE_m", "0"),
|
||||||
"Hoehe unten": mm_to_m("HOEHE_BIS_mm"),
|
"Typ": attribs.get("TYP", "Gefaellestrecke"),
|
||||||
"Laenge in Meter": attribs.get("L_GF_m", ""),
|
"Laenge in Meter": attribs.get("L_GF_m", ""),
|
||||||
"Winkel": attribs.get("GF_WINKEL", ""),
|
"Winkel": attribs.get("GF_WINKEL", ""),
|
||||||
"Anzahl der Separatoren": attribs.get("ANZAHL_SEPARATOR", "0"),
|
"Anzahl der Separatoren": attribs.get("ANZAHL_SEPARATOR", "0"),
|
||||||
"Anzahl der Scanner": attribs.get("ANZAHL_SCANNER", "0"),
|
"Anzahl der Scanner": attribs.get("ANZAHL_SCANNER", "0"),
|
||||||
"Typ": attribs.get("TYP", "Gefaellestrecke"),
|
|
||||||
"Geruest fuer Einzelmodul": attribs.get("GERUEST_EINZELMODUL", "0") == "1",
|
"Geruest fuer Einzelmodul": attribs.get("GERUEST_EINZELMODUL", "0") == "1",
|
||||||
"Geruestoption fuer Einzelmodul": attribs.get("GERUEST_TYP", "Schoenenberger Geruest"),
|
"Geruestoption fuer Einzelmodul": attribs.get("GERUEST_TYP", "Schoenenberger Geruest"),
|
||||||
}
|
}
|
||||||
@@ -197,9 +193,9 @@ def build_variofoerderer_details(block):
|
|||||||
return safe_int(attribs.get(key, "0") or "0")
|
return safe_int(attribs.get(key, "0") or "0")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
"Hoehe in m": attribs.get("MONTAGEHOEHE_m", "0"),
|
||||||
"Anzahl Foerdergruppen": to_int("ANZAHL_VF"),
|
"Anzahl Foerdergruppen": to_int("ANZAHL_VF"),
|
||||||
"Anzahl Gefaellegruppen": to_int("ANZAHL_GF"),
|
"Anzahl Gefaellegruppen": to_int("ANZAHL_GF"),
|
||||||
"Hoehe in m": attribs.get("MONTAGEHOEHE_m", "0"),
|
|
||||||
"GruppenteileGefaelle": {
|
"GruppenteileGefaelle": {
|
||||||
"AnzahlGeraden": to_int("ANZAHL_GF"),
|
"AnzahlGeraden": to_int("ANZAHL_GF"),
|
||||||
"Laenge in Meter": parse_float_list(attribs.get("L_GF_m", "")),
|
"Laenge in Meter": parse_float_list(attribs.get("L_GF_m", "")),
|
||||||
@@ -235,9 +231,8 @@ def build_kreisel_details(block):
|
|||||||
"""Merkmale-Dict fuer einen ILS Kreisel."""
|
"""Merkmale-Dict fuer einen ILS Kreisel."""
|
||||||
attribs = block.get("attribs", {})
|
attribs = block.get("attribs", {})
|
||||||
abstand_m = str(round(safe_float(attribs.get("ABSTAND", "2300"), 2300.0) / 1000.0, 2))
|
abstand_m = str(round(safe_float(attribs.get("ABSTAND", "2300"), 2300.0) / 1000.0, 2))
|
||||||
hoehe_m = str(round(safe_float(attribs.get("HOEHE", "0"), 0.0) / 1000.0, 2))
|
|
||||||
return {
|
return {
|
||||||
"Hoehe in m": hoehe_m,
|
"Hoehe in m": attribs.get("MONTAGEHOEHE_m", "0"),
|
||||||
"Kreiselart": attribs.get("KREISELART", "STANDARD"),
|
"Kreiselart": attribs.get("KREISELART", "STANDARD"),
|
||||||
"Abstand (Kreiselachse A - Kreiselachse) in Meter": abstand_m,
|
"Abstand (Kreiselachse A - Kreiselachse) in Meter": abstand_m,
|
||||||
"Anzahl der Separatoren": attribs.get("ANZAHL_SEPARATOR", attribs.get("N_SEPARATOREN", "2")),
|
"Anzahl der Separatoren": attribs.get("ANZAHL_SEPARATOR", attribs.get("N_SEPARATOREN", "2")),
|
||||||
@@ -338,21 +333,54 @@ def process_blocks(blocks, lookup):
|
|||||||
# --- Omniflo Bogen oder Weiche (erkannt via Katalog) ---
|
# --- Omniflo Bogen oder Weiche (erkannt via Katalog) ---
|
||||||
if bname in lookup:
|
if bname in lookup:
|
||||||
typ, eintrag = lookup[bname]
|
typ, eintrag = lookup[bname]
|
||||||
sivasnr = format_sivasnr(eintrag.get("Sivasnr", bname))
|
sivasnr_raw = eintrag.get("Sivasnr", bname)
|
||||||
|
sivasnr_parts = [p.strip() for p in str(sivasnr_raw).split("+")]
|
||||||
|
has_tef = any(p.startswith("0_B") for p in sivasnr_parts)
|
||||||
|
block_id = get_id(block)
|
||||||
|
|
||||||
if typ == "bogen":
|
if typ == "bogen":
|
||||||
if sivasnr not in bogen_groups:
|
if has_tef and len(sivasnr_parts) > 1:
|
||||||
bogen_groups[sivasnr] = {"anzahl": 0, "ids": []}
|
# Multi-Komponenten: jede Komponente separat gruppieren
|
||||||
bogen_groups[sivasnr]["anzahl"] += 1
|
for i, comp_sivasnr in enumerate(sivasnr_parts):
|
||||||
bogen_groups[sivasnr]["ids"].append(get_id(block))
|
comp_id = f"{block_id}.t{i+1}"
|
||||||
|
if comp_sivasnr not in bogen_groups:
|
||||||
|
bogen_groups[comp_sivasnr] = {"anzahl": 0, "ids": []}
|
||||||
|
bogen_groups[comp_sivasnr]["anzahl"] += 1
|
||||||
|
bogen_groups[comp_sivasnr]["ids"].append(comp_id)
|
||||||
|
else:
|
||||||
|
# Normal: alle als eine Komponente
|
||||||
|
sivasnr = format_sivasnr(sivasnr_raw)
|
||||||
|
if sivasnr not in bogen_groups:
|
||||||
|
bogen_groups[sivasnr] = {"anzahl": 0, "ids": []}
|
||||||
|
bogen_groups[sivasnr]["anzahl"] += 1
|
||||||
|
bogen_groups[sivasnr]["ids"].append(block_id)
|
||||||
counters["anzahl_boegen"] += 1
|
counters["anzahl_boegen"] += 1
|
||||||
|
|
||||||
elif typ == "weiche":
|
elif typ == "weiche":
|
||||||
if sivasnr not in weiche_groups:
|
|
||||||
weiche_groups[sivasnr] = {"anzahl": 0, "ids": []}
|
|
||||||
weiche_groups[sivasnr]["anzahl"] += 1
|
|
||||||
weiche_groups[sivasnr]["ids"].append(get_id(block))
|
|
||||||
subtyp = classify_weiche(eintrag)
|
subtyp = classify_weiche(eintrag)
|
||||||
|
if has_tef and len(sivasnr_parts) > 1:
|
||||||
|
# Multi-Komponenten: jede Komponente separat gruppieren
|
||||||
|
for i, comp_sivasnr in enumerate(sivasnr_parts):
|
||||||
|
comp_id = f"{block_id}.t{i+1}"
|
||||||
|
if comp_sivasnr.startswith("0_B"):
|
||||||
|
# TEF-Komponente (Bogen) in bogen_groups
|
||||||
|
if comp_sivasnr not in bogen_groups:
|
||||||
|
bogen_groups[comp_sivasnr] = {"anzahl": 0, "ids": []}
|
||||||
|
bogen_groups[comp_sivasnr]["anzahl"] += 1
|
||||||
|
bogen_groups[comp_sivasnr]["ids"].append(comp_id)
|
||||||
|
else:
|
||||||
|
# Weichen-Komponente in weiche_groups
|
||||||
|
if comp_sivasnr not in weiche_groups:
|
||||||
|
weiche_groups[comp_sivasnr] = {"anzahl": 0, "ids": []}
|
||||||
|
weiche_groups[comp_sivasnr]["anzahl"] += 1
|
||||||
|
weiche_groups[comp_sivasnr]["ids"].append(comp_id)
|
||||||
|
else:
|
||||||
|
# Normal: alle als eine Komponente
|
||||||
|
sivasnr = format_sivasnr(sivasnr_raw)
|
||||||
|
if sivasnr not in weiche_groups:
|
||||||
|
weiche_groups[sivasnr] = {"anzahl": 0, "ids": []}
|
||||||
|
weiche_groups[sivasnr]["anzahl"] += 1
|
||||||
|
weiche_groups[sivasnr]["ids"].append(block_id)
|
||||||
if subtyp == "einzelweiche":
|
if subtyp == "einzelweiche":
|
||||||
counters["anzahl_einzelweichen"] += 1
|
counters["anzahl_einzelweichen"] += 1
|
||||||
elif subtyp in ("doppelweiche", "dreiwegeweiche"):
|
elif subtyp in ("doppelweiche", "dreiwegeweiche"):
|
||||||
@@ -453,9 +481,10 @@ def process_blocks(blocks, lookup):
|
|||||||
nr = 1
|
nr = 1
|
||||||
|
|
||||||
for sivasnr, g in bogen_groups.items():
|
for sivasnr, g in bogen_groups.items():
|
||||||
|
teileart = "TEF Bogen" if str(sivasnr).startswith("0_B") else "Omniflo Bogen"
|
||||||
items.append({
|
items.append({
|
||||||
"nr": nr,
|
"nr": nr,
|
||||||
"teileart": "Omniflo Bogen",
|
"teileart": teileart,
|
||||||
"sivasnr": sivasnr,
|
"sivasnr": sivasnr,
|
||||||
"sivasnr_quoted": False,
|
"sivasnr_quoted": False,
|
||||||
"bezeichnung": "",
|
"bezeichnung": "",
|
||||||
@@ -468,9 +497,10 @@ def process_blocks(blocks, lookup):
|
|||||||
nr += 1
|
nr += 1
|
||||||
|
|
||||||
for sivasnr, g in weiche_groups.items():
|
for sivasnr, g in weiche_groups.items():
|
||||||
|
teileart = "TEF Weiche" if str(sivasnr).startswith("0_B") else "Omniflo Weiche"
|
||||||
items.append({
|
items.append({
|
||||||
"nr": nr,
|
"nr": nr,
|
||||||
"teileart": "Omniflo Weiche",
|
"teileart": teileart,
|
||||||
"sivasnr": sivasnr,
|
"sivasnr": sivasnr,
|
||||||
"sivasnr_quoted": False,
|
"sivasnr_quoted": False,
|
||||||
"bezeichnung": "",
|
"bezeichnung": "",
|
||||||
|
|||||||
@@ -39,6 +39,43 @@ tests/output/ |
|
|||||||
test_foerderer.py
|
test_foerderer.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## LISP-Unit-Tests fuer reine Standardfunktionen (`--lisp`)
|
||||||
|
|
||||||
|
Neben den zeichnungsbasierten Integrationstests gibt es **Unit-Tests fuer einfache
|
||||||
|
LISP-Standardfunktionen** — reine Funktionen ohne Zeichnungsdatenbank, Auswahlsaetze,
|
||||||
|
Dialoge oder Blockdateien (String-/Zahl-/Vektor-/Alist-Helfer). Sie funktionieren
|
||||||
|
analog zu den Python-Unittests: jeder Testfall vergleicht einen Funktionsaufruf gegen
|
||||||
|
ein erwartetes Ergebnis.
|
||||||
|
|
||||||
|
- **Testrunner**: `tests/test_unit.lsp` — Befehl `TEST_UNIT`, Mini-Framework mit
|
||||||
|
`tu-eq` (exakt), `tu-eqf` (Fliesskomma-Toleranz), `tu-true`, `tu-nil`.
|
||||||
|
- **Startskript**: `tests/test_unit.scr` — laedt SSG_LIB + die Tests, ruft `TEST_UNIT`
|
||||||
|
auf und beendet BricsCAD (pfadunabhaengig ueber `DXFMAKRO`).
|
||||||
|
- **Ergebnis**: Konsole (PASS/FAIL je Test) und `tests/output/unit_results.txt`
|
||||||
|
(letzte Zeile `RESULT: OK` / `RESULT: FAIL`).
|
||||||
|
|
||||||
|
Ausfuehrung ueber die Kommandozeile (startet BricsCAD headless):
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
bin\run_tests.bat --lisp
|
||||||
|
```
|
||||||
|
|
||||||
|
`run_tests.bat --lisp` loescht das alte Ergebnis, startet BricsCAD mit `test_unit.scr`,
|
||||||
|
gibt danach `unit_results.txt` aus und liefert Exit-Code 1, wenn ein Test fehlschlaegt
|
||||||
|
oder kein Report erzeugt wurde.
|
||||||
|
|
||||||
|
Direkt in BricsCAD (SSG_LIB geladen):
|
||||||
|
|
||||||
|
```lisp
|
||||||
|
(load (strcat (getenv "DXFMAKRO") "/tests/test_unit.lsp"))
|
||||||
|
TEST_UNIT
|
||||||
|
```
|
||||||
|
|
||||||
|
Getestete Module: `ssg_core`, `ssg_lang`, `ssg_id`, `vf_core`, `export`,
|
||||||
|
`Gefaellestrecke`, `OmniModulInsert`, `KreiselInsert`.
|
||||||
|
|
||||||
|
## Zeichnungsbasierte Integrationstests
|
||||||
|
|
||||||
**Jedes Testmodul laeuft in einer eigenen, neuen Zeichnung** (statt die aktuelle zu
|
**Jedes Testmodul laeuft in einer eigenen, neuen Zeichnung** (statt die aktuelle zu
|
||||||
leeren/wiederzuverwenden). `SSG_RUN_ALL_TESTS` fragt beim Start interaktiv nach
|
leeren/wiederzuverwenden). `SSG_RUN_ALL_TESTS` fragt beim Start interaktiv nach
|
||||||
dem gewuenschten Modus:
|
dem gewuenschten Modus:
|
||||||
@@ -69,6 +106,7 @@ Die Datei `tests/alltests.json` ist die zentrale Registry aller Test-Module.
|
|||||||
{ "name": "kreisel", "save": "dxf" },
|
{ "name": "kreisel", "save": "dxf" },
|
||||||
{ "name": "foerderer", "save": "dwg" },
|
{ "name": "foerderer", "save": "dwg" },
|
||||||
{ "name": "omniflo", "save": "dxf" },
|
{ "name": "omniflo", "save": "dxf" },
|
||||||
|
{ "name": "omniflo_strecke", "save": "dxf" },
|
||||||
{ "name": "gefaellestrecke", "save": "dwg" }
|
{ "name": "gefaellestrecke", "save": "dwg" }
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
@@ -112,10 +150,13 @@ tests/
|
|||||||
test_foerderer.lsp # LISP-Testrunner: C:TEST_FOERDERER
|
test_foerderer.lsp # LISP-Testrunner: C:TEST_FOERDERER
|
||||||
test_omniflo.lsp # LISP-Testrunner: C:TEST_OMNIFLO
|
test_omniflo.lsp # LISP-Testrunner: C:TEST_OMNIFLO
|
||||||
test_omniflo.py # pytest-Validierung der Omniflo-Ergebnisse
|
test_omniflo.py # pytest-Validierung der Omniflo-Ergebnisse
|
||||||
|
test_omniflo_strecke.lsp # LISP-Testrunner: C:TEST_OMNIFLO_STRECKE (Streckenzug)
|
||||||
|
test_omniflo_strecke.py # pytest-Validierung des Omniflo-Streckenzugs
|
||||||
test_export_all.lsp # LISP-Testrunner: C:TEST_EXPORT_ALL (manuell, nicht in alltests.json)
|
test_export_all.lsp # LISP-Testrunner: C:TEST_EXPORT_ALL (manuell, nicht in alltests.json)
|
||||||
testdata/
|
testdata/
|
||||||
kreisel_tests.json # Testfall-Definitionen fuer Kreisel
|
kreisel_tests.json # Testfall-Definitionen fuer Kreisel
|
||||||
omniflo_tests.json # Testfall-Definitionen fuer Omniflo
|
omniflo_tests.json # Testfall-Definitionen fuer Omniflo
|
||||||
|
omniflo_strecke_tests.json # Streckenzug: 4 Geraden -> 180-Grad-Kurve -> 4 Geraden
|
||||||
output/ # Ergebnisse aus BricsCAD (nicht in Git)
|
output/ # Ergebnisse aus BricsCAD (nicht in Git)
|
||||||
kreisel_results.json # JSON-Ergebnisse pro Testmodul
|
kreisel_results.json # JSON-Ergebnisse pro Testmodul
|
||||||
kreisel_tests.dxf # DXF-Zeichnung pro Testmodul
|
kreisel_tests.dxf # DXF-Zeichnung pro Testmodul
|
||||||
@@ -433,6 +474,18 @@ def <modul>_ref_dxf():
|
|||||||
| `TestOmnifloResults` | omniflo_results.json aus BricsCAD: Testfaelle vollstaendig, Status OK, Hoehe |
|
| `TestOmnifloResults` | omniflo_results.json aus BricsCAD: Testfaelle vollstaendig, Status OK, Hoehe |
|
||||||
| `TestOmnifloReferenceCSV` | CSV-Export aus `lib/export_csv.py` gegen abgenommene Referenz-CSV |
|
| `TestOmnifloReferenceCSV` | CSV-Export aus `lib/export_csv.py` gegen abgenommene Referenz-CSV |
|
||||||
|
|
||||||
|
### test_omniflo_strecke.py
|
||||||
|
|
||||||
|
Streckenzug **4 Geraden -> 180-Grad-Kurve (4x 45-Grad-Bogen) -> 4 Geraden**.
|
||||||
|
Die Geometrie wird ohne BricsCAD direkt aus `omniflo_strecke_tests.json` geprueft.
|
||||||
|
|
||||||
|
| Klasse | Prueft |
|
||||||
|
|---|---|
|
||||||
|
| `TestStreckeStruktur` | 4+4+4 = 12 Elemente, Reihenfolge der Gruppen, Typen |
|
||||||
|
| `TestStreckeKatalog` | Bogen-SivasNummern existieren in `omniflo_boegen.json` (Radius/Winkel) |
|
||||||
|
| `TestStreckeGeometrie` | Lueckenlose Verkettung, Kurvensumme 180 Grad, gemeinsamer Bogen-Mittelpunkt |
|
||||||
|
| `TestStreckeResults` | omniflo_strecke_results.json aus BricsCAD: Elemente vollstaendig, Status OK |
|
||||||
|
|
||||||
## Umgebungsvariablen
|
## Umgebungsvariablen
|
||||||
|
|
||||||
Werden von `bin\setenv.bat` gesetzt. Fuer Tests relevant:
|
Werden von `bin\setenv.bat` gesetzt. Fuer Tests relevant:
|
||||||
|
|||||||
@@ -3,5 +3,6 @@
|
|||||||
{ "name": "foerderer", "save": "dwg", "module": "VarioFoerderer" },
|
{ "name": "foerderer", "save": "dwg", "module": "VarioFoerderer" },
|
||||||
{ "name": "linienzug", "save": "dwg", "module": "VarioFoerderer" },
|
{ "name": "linienzug", "save": "dwg", "module": "VarioFoerderer" },
|
||||||
{ "name": "omniflo", "save": "dxf", "module": "OmniModulInsert" },
|
{ "name": "omniflo", "save": "dxf", "module": "OmniModulInsert" },
|
||||||
|
{ "name": "omniflo_strecke", "save": "dxf", "module": "OmniModulInsert" },
|
||||||
{ "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" }
|
{ "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" }
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,280 @@
|
|||||||
|
;; ============================================================
|
||||||
|
;; test_omniflo_strecke.lsp - Integrationstest Omniflo-Streckenzug
|
||||||
|
;;
|
||||||
|
;; Baut aus tests/testdata/omniflo_strecke_tests.json einen verketteten
|
||||||
|
;; Streckenzug auf:
|
||||||
|
;; 4 Geraden -> 180-Grad-Kurve (4x 45-Grad-Bogen) -> 4 Geraden
|
||||||
|
;;
|
||||||
|
;; Jedes Element traegt bereits seine absolute Platzierung (x, y, drehung)
|
||||||
|
;; sowie Endpunkt (x_ende, y_ende). Die Elemente sind lueckenlos verkettet
|
||||||
|
;; (Ende[i] = Anfang[i+1]), sodass eine durchgehende U-foermige Strecke
|
||||||
|
;; entsteht.
|
||||||
|
;;
|
||||||
|
;; - Boegen (type "bogen"): DXF aus DXFM_OMNIFLO an (x,y) mit Drehung
|
||||||
|
;; einfuegen (wie test_omniflo.lsp).
|
||||||
|
;; - Geraden (type "gerade"): echter Aluprofil-Block ueber den skript-
|
||||||
|
;; faehigen Kern omni:make-gerade (aus OmniModulInsert.lsp). Fehlt die
|
||||||
|
;; Quell-.dwg (kein AP110.dwg im Blockpfad), werden Ersatz-Attribute
|
||||||
|
;; verwendet, sodass dennoch ein Compound-Block (Linie + ATTDEFs) statt
|
||||||
|
;; einer nackten Linie entsteht.
|
||||||
|
;;
|
||||||
|
;; Ergebnisse -> tests/output/omniflo_strecke_results.json
|
||||||
|
;; (via omniflo_strecke:export-results, aufgerufen von SSG_RUN_ALL_TESTS
|
||||||
|
;; oder manuell).
|
||||||
|
;;
|
||||||
|
;; Voraussetzungen:
|
||||||
|
;; - SSG_LIB geladen (ssg_core.lsp, OmniModulInsert.lsp)
|
||||||
|
;; - Umgebungsvariablen DXFMAKRO, DXFM_OMNIFLO gesetzt
|
||||||
|
;;
|
||||||
|
;; Aufruf in BricsCAD:
|
||||||
|
;; (load "tests/test_omniflo_strecke.lsp")
|
||||||
|
;; TEST_OMNIFLO_STRECKE
|
||||||
|
;; ============================================================
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Bogen-DXF non-interaktiv an gegebenem Punkt einfuegen ---
|
||||||
|
(defun omnistr:insert-bogen (sivasnr-str insert-pt hoehe-str drehung-str /
|
||||||
|
dxf-pfad omniflo-pfad blockEnt angleDeg)
|
||||||
|
(setq omniflo-pfad (getenv "DXFM_OMNIFLO"))
|
||||||
|
(if (null omniflo-pfad)
|
||||||
|
(progn
|
||||||
|
(princ "\n[TEST_OMNIFLO_STRECKE] FEHLER: DXFM_OMNIFLO nicht gesetzt!")
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
(progn
|
||||||
|
(setq dxf-pfad (strcat omniflo-pfad "/" sivasnr-str ".dxf"))
|
||||||
|
(if (not (findfile dxf-pfad))
|
||||||
|
(progn
|
||||||
|
(princ (strcat "\n[TEST_OMNIFLO_STRECKE] WARNUNG: DXF nicht gefunden: " dxf-pfad))
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
(progn
|
||||||
|
(setq angleDeg (if drehung-str (atof drehung-str) 0.0))
|
||||||
|
(setvar "ATTREQ" 0)
|
||||||
|
(setvar "ATTDIA" 0)
|
||||||
|
(command "_.INSERT" dxf-pfad insert-pt "" "" angleDeg)
|
||||||
|
(setq blockEnt (entlast))
|
||||||
|
(if (and blockEnt
|
||||||
|
(= (cdr (assoc 0 (entget blockEnt))) "INSERT"))
|
||||||
|
(ssg-attrib-set-on blockEnt
|
||||||
|
(list (cons "HOEHE" (if hoehe-str hoehe-str "2000"))
|
||||||
|
(cons "DREHUNG" (if drehung-str drehung-str "0"))))
|
||||||
|
)
|
||||||
|
blockEnt
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Gerade non-interaktiv als echten Aluprofil-Block erzeugen ---
|
||||||
|
;; Nutzt den skriptfaehigen Kern omni:make-gerade aus OmniModulInsert.lsp.
|
||||||
|
;; profil = Profiltyp (z.B. "AP110")
|
||||||
|
;; pt-anf = Anfangspunkt, pt-end = Endpunkt (bestimmen Laenge + Drehung)
|
||||||
|
;; hoehe-str / drehung-str = Attributwerte HOEHE/DREHUNG
|
||||||
|
;; Rueckgabe: Block-Entity oder nil.
|
||||||
|
(defun omnistr:build-gerade (profil pt-anf pt-end hoehe-str drehung-str /
|
||||||
|
srcAttribs textHeight textGap blockEnt)
|
||||||
|
(if (null (car (atoms-family 1 '("OMNI:MAKE-GERADE"))))
|
||||||
|
(progn
|
||||||
|
(princ "\n[TEST_OMNIFLO_STRECKE] FEHLER: omni:make-gerade nicht geladen (OmniModulInsert.lsp)!")
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
(progn
|
||||||
|
(setq textHeight (ssg-cfg-or "omniflo" "text_height" 100.0))
|
||||||
|
(setq textGap (ssg-cfg-or "omniflo" "text_gap" 20.0))
|
||||||
|
;; Quell-Attribute lesen; falls keine .dwg vorhanden, Ersatz erzeugen
|
||||||
|
(setq srcAttribs (omni:read-src-attribs profil))
|
||||||
|
(if (null srcAttribs)
|
||||||
|
(setq srcAttribs (list (cons "PROFILTYP" profil)
|
||||||
|
(cons "HOEHE" (if hoehe-str hoehe-str "2000"))
|
||||||
|
(cons "ID" "")))
|
||||||
|
)
|
||||||
|
(setvar "ATTREQ" 0)
|
||||||
|
(setvar "ATTDIA" 0)
|
||||||
|
(setq blockEnt (omni:make-gerade profil pt-anf pt-end
|
||||||
|
srcAttribs textHeight textGap nil))
|
||||||
|
(if (and blockEnt (= (cdr (assoc 0 (entget blockEnt))) "INSERT"))
|
||||||
|
(ssg-attrib-set-on blockEnt
|
||||||
|
(list (cons "HOEHE" (if hoehe-str hoehe-str "2000"))
|
||||||
|
(cons "DREHUNG" (if drehung-str drehung-str "0"))))
|
||||||
|
)
|
||||||
|
blockEnt
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Ergebnis eines Streckenelements als JSON-String erzeugen ---
|
||||||
|
(defun omnistr:test-result-json (test-id seq typ description status / )
|
||||||
|
(strcat " {\n"
|
||||||
|
" \"test_id\": \"" test-id "\",\n"
|
||||||
|
" \"seq\": " (itoa seq) ",\n"
|
||||||
|
" \"type\": \"" (if typ typ "") "\",\n"
|
||||||
|
" \"description\": \"" (if description description "") "\",\n"
|
||||||
|
" \"status\": \"" status "\"\n"
|
||||||
|
" }")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; --- JSON-Export der Streckenzug-Testergebnisse ---
|
||||||
|
(defun omniflo_strecke:export-results (tests-out-dir / out-json f first)
|
||||||
|
(if (null *omniflo-strecke-test-results*)
|
||||||
|
(princ "\n Keine Streckenzug-Ergebnisse vorhanden.")
|
||||||
|
(progn
|
||||||
|
(vl-mkdir tests-out-dir)
|
||||||
|
(setq out-json (strcat tests-out-dir "/omniflo_strecke_results.json"))
|
||||||
|
(setq f (open out-json "w"))
|
||||||
|
(if f
|
||||||
|
(progn
|
||||||
|
(write-line "[" f)
|
||||||
|
(setq first T)
|
||||||
|
(foreach r *omniflo-strecke-test-results*
|
||||||
|
(if (not first) (write-line "," f))
|
||||||
|
(write-line r f)
|
||||||
|
(setq first nil)
|
||||||
|
)
|
||||||
|
(write-line "]" f)
|
||||||
|
(close f)
|
||||||
|
(princ (strcat "\n Ergebnisse: " out-json))
|
||||||
|
)
|
||||||
|
(princ (strcat "\n FEHLER: Kann " out-json " nicht schreiben!"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; ============================================================
|
||||||
|
;; C:TEST_OMNIFLO_STRECKE
|
||||||
|
;;
|
||||||
|
;; Liest omniflo_strecke_tests.json und baut den Streckenzug auf.
|
||||||
|
;; ============================================================
|
||||||
|
(defun c:TEST_OMNIFLO_STRECKE (/ tests-json-pfad elemente
|
||||||
|
idx eintrag typ sivasnr profil hoehe-str drehung-str
|
||||||
|
description x-val y-val xe-val ye-val seq
|
||||||
|
insert-pt pt-end anzahl-ok anzahl-fehler
|
||||||
|
results-list ergebnis)
|
||||||
|
|
||||||
|
(ssg-start "TEST_OMNIFLO_STRECKE"
|
||||||
|
'(("OSMODE") ("ATTREQ") ("ATTDIA") ("CLAYER")))
|
||||||
|
(setvar "OSMODE" 0)
|
||||||
|
(setvar "ATTREQ" 0)
|
||||||
|
(setvar "ATTDIA" 0)
|
||||||
|
|
||||||
|
(setq tests-json-pfad
|
||||||
|
(strcat (getenv "DXFMAKRO") "/tests/testdata/omniflo_strecke_tests.json"))
|
||||||
|
|
||||||
|
(if (not (findfile tests-json-pfad))
|
||||||
|
(progn
|
||||||
|
(princ (strcat "\n[TEST_OMNIFLO_STRECKE] FEHLER: Testdaten nicht gefunden: "
|
||||||
|
tests-json-pfad))
|
||||||
|
(ssg-end)
|
||||||
|
(princ)
|
||||||
|
)
|
||||||
|
(progn
|
||||||
|
(setq elemente (ssg-load-json tests-json-pfad))
|
||||||
|
(if (null elemente)
|
||||||
|
(progn
|
||||||
|
(princ "\n[TEST_OMNIFLO_STRECKE] FEHLER: Testdaten konnten nicht geladen werden.")
|
||||||
|
(ssg-end)
|
||||||
|
(princ)
|
||||||
|
)
|
||||||
|
(progn
|
||||||
|
(princ "\n================================================================")
|
||||||
|
(princ "\n TEST_OMNIFLO_STRECKE")
|
||||||
|
(princ (strcat "\n " (itoa (length elemente)) " Streckenelemente geladen"))
|
||||||
|
(princ "\n================================================================")
|
||||||
|
|
||||||
|
(setq anzahl-ok 0 anzahl-fehler 0 results-list nil idx 0)
|
||||||
|
|
||||||
|
(foreach eintrag elemente
|
||||||
|
(setq idx (1+ idx))
|
||||||
|
(setq seq (ssg-val eintrag "seq"))
|
||||||
|
(if (null seq) (setq seq idx))
|
||||||
|
(if (not (numberp seq)) (setq seq (atoi (vl-princ-to-string seq))))
|
||||||
|
(setq typ (ssg-val eintrag "type"))
|
||||||
|
(setq description (ssg-val eintrag "description"))
|
||||||
|
|
||||||
|
(setq hoehe-str (ssg-val eintrag "hoehe"))
|
||||||
|
(if (null hoehe-str) (setq hoehe-str "2000"))
|
||||||
|
(if (numberp hoehe-str) (setq hoehe-str (rtos hoehe-str 2 0)))
|
||||||
|
|
||||||
|
(setq drehung-str (ssg-val eintrag "drehung"))
|
||||||
|
(if (null drehung-str) (setq drehung-str "0"))
|
||||||
|
(if (numberp drehung-str) (setq drehung-str (rtos drehung-str 2 4)))
|
||||||
|
|
||||||
|
;; Anfangs- und Endpunkt lesen
|
||||||
|
(setq x-val (ssg-val eintrag "x")
|
||||||
|
y-val (ssg-val eintrag "y")
|
||||||
|
xe-val (ssg-val eintrag "x_ende")
|
||||||
|
ye-val (ssg-val eintrag "y_ende"))
|
||||||
|
(if (null x-val) (setq x-val 0.0))
|
||||||
|
(if (null y-val) (setq y-val 0.0))
|
||||||
|
(if (null xe-val) (setq xe-val x-val))
|
||||||
|
(if (null ye-val) (setq ye-val y-val))
|
||||||
|
(if (not (numberp x-val)) (setq x-val (atof x-val)))
|
||||||
|
(if (not (numberp y-val)) (setq y-val (atof y-val)))
|
||||||
|
(if (not (numberp xe-val)) (setq xe-val (atof xe-val)))
|
||||||
|
(if (not (numberp ye-val)) (setq ye-val (atof ye-val)))
|
||||||
|
|
||||||
|
(setq insert-pt (list x-val y-val (atof hoehe-str)))
|
||||||
|
(setq pt-end (list xe-val ye-val (atof hoehe-str)))
|
||||||
|
|
||||||
|
(princ (strcat "\n " (itoa seq) ". " (if typ typ "?") ": "
|
||||||
|
(if description description "")
|
||||||
|
" -> (" (rtos x-val 2 0) ", " (rtos y-val 2 0)
|
||||||
|
") @ " drehung-str))
|
||||||
|
|
||||||
|
;; Element aufbauen je nach Typ
|
||||||
|
(setq ergebnis
|
||||||
|
(cond
|
||||||
|
((= typ "bogen")
|
||||||
|
(setq sivasnr (ssg-val eintrag "sivasnr"))
|
||||||
|
(omnistr:insert-bogen sivasnr insert-pt hoehe-str drehung-str))
|
||||||
|
((= typ "gerade")
|
||||||
|
(setq profil (ssg-val eintrag "profil"))
|
||||||
|
(if (null profil) (setq profil "AP110"))
|
||||||
|
(omnistr:build-gerade profil insert-pt pt-end hoehe-str drehung-str))
|
||||||
|
(T
|
||||||
|
(princ (strcat "\n WARNUNG: unbekannter Typ '"
|
||||||
|
(if typ typ "nil") "'"))
|
||||||
|
nil)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(if ergebnis
|
||||||
|
(progn
|
||||||
|
(setq anzahl-ok (1+ anzahl-ok))
|
||||||
|
(setq results-list (cons
|
||||||
|
(omnistr:test-result-json (strcat "STRECKE_" (itoa seq))
|
||||||
|
seq typ description "OK")
|
||||||
|
results-list))
|
||||||
|
)
|
||||||
|
(progn
|
||||||
|
(setq anzahl-fehler (1+ anzahl-fehler))
|
||||||
|
(setq results-list (cons
|
||||||
|
(omnistr:test-result-json (strcat "STRECKE_" (itoa seq))
|
||||||
|
seq typ description "FEHLER")
|
||||||
|
results-list))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(princ "\n================================================================")
|
||||||
|
(princ (strcat "\n Aufgebaut: " (itoa anzahl-ok)
|
||||||
|
" OK, " (itoa anzahl-fehler) " Fehler"))
|
||||||
|
(princ "\n================================================================")
|
||||||
|
|
||||||
|
(setq *omniflo-strecke-test-results* (reverse results-list))
|
||||||
|
|
||||||
|
(ssg-end)
|
||||||
|
|
||||||
|
(princ "\n TEST_OMNIFLO_STRECKE abgeschlossen.")
|
||||||
|
(princ)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
test_omniflo_strecke.py - Validiert den Omniflo-Streckenzug.
|
||||||
|
|
||||||
|
Testfall (tests/testdata/omniflo_strecke_tests.json):
|
||||||
|
4 Geraden -> 180-Grad-Kurve aus 4x 45-Grad-Boegen -> 4 Geraden
|
||||||
|
|
||||||
|
Prueft (ohne BricsCAD):
|
||||||
|
1. Struktur: 4 + 4 + 4 = 12 Elemente in der richtigen Reihenfolge
|
||||||
|
2. Katalog: die verwendeten Bogen-SivasNummern existieren in
|
||||||
|
data/json/omniflo_boegen.json mit passendem Radius/Kurvenwinkel
|
||||||
|
3. Geometrie: die Elemente sind lueckenlos verkettet (Ende[i] == Anfang[i+1]),
|
||||||
|
die Kurve summiert auf genau 180 Grad, die Boegen liegen auf einem
|
||||||
|
gemeinsamen Kreismittelpunkt (echte Halbkreis-Kurve)
|
||||||
|
|
||||||
|
Optional (nach BricsCAD-Lauf c:TEST_OMNIFLO_STRECKE):
|
||||||
|
4. Integrationstest gegen tests/output/omniflo_strecke_results.json
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
_TOL = 0.05 # mm / Grad Toleranz (JSON ist auf 2 Nachkommastellen gerundet)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Hilfsfunktionen
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _data_dir():
|
||||||
|
base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
return os.path.join(base, "data")
|
||||||
|
|
||||||
|
|
||||||
|
def _testdata_dir():
|
||||||
|
return os.getenv("DXFM_TESTDATA",
|
||||||
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), "testdata"))
|
||||||
|
|
||||||
|
|
||||||
|
def _output_dir():
|
||||||
|
return os.getenv("DXFM_TESTOUT",
|
||||||
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), "output"))
|
||||||
|
|
||||||
|
|
||||||
|
def _load_json(path):
|
||||||
|
try:
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
with open(path, "r", encoding="cp1252") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_strecke():
|
||||||
|
return _load_json(os.path.join(_testdata_dir(), "omniflo_strecke_tests.json"))
|
||||||
|
|
||||||
|
|
||||||
|
def _load_boegen_lookup():
|
||||||
|
boegen = _load_json(os.path.join(_data_dir(), "json", "omniflo_boegen.json"))
|
||||||
|
return {str(b["Sivasnr"]): b for b in boegen}
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Fixtures
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def strecke():
|
||||||
|
return _load_strecke()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def boegen_lookup():
|
||||||
|
return _load_boegen_lookup()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 1. Struktur des Streckenzugs
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class TestStreckeStruktur:
|
||||||
|
|
||||||
|
def test_gesamtzahl(self, strecke):
|
||||||
|
"""4 Geraden + 4 Boegen + 4 Geraden = 12 Elemente."""
|
||||||
|
assert len(strecke) == 12, f"Erwartet 12 Elemente, erhalten {len(strecke)}"
|
||||||
|
|
||||||
|
def test_seq_lueckenlos(self, strecke):
|
||||||
|
"""seq muss lueckenlos bei 1 beginnend aufsteigen."""
|
||||||
|
for i, el in enumerate(strecke):
|
||||||
|
assert el["seq"] == i + 1, f"Element {i}: seq={el['seq']}, erwartet {i + 1}"
|
||||||
|
|
||||||
|
def test_gruppen_reihenfolge(self, strecke):
|
||||||
|
"""Reihenfolge: 4x gerade_start, 4x kurve_180, 4x gerade_ende."""
|
||||||
|
erwartet = (["gerade_start"] * 4 + ["kurve_180"] * 4 + ["gerade_ende"] * 4)
|
||||||
|
ist = [el["gruppe"] for el in strecke]
|
||||||
|
assert ist == erwartet, f"Gruppenfolge abweichend:\n Ist: {ist}\n Soll: {erwartet}"
|
||||||
|
|
||||||
|
def test_typen(self, strecke):
|
||||||
|
"""gerade_* -> type 'gerade', kurve_180 -> type 'bogen'."""
|
||||||
|
for el in strecke:
|
||||||
|
erwartet = "bogen" if el["gruppe"] == "kurve_180" else "gerade"
|
||||||
|
assert el["type"] == erwartet, (
|
||||||
|
f"seq {el['seq']}: type={el['type']}, erwartet {erwartet}")
|
||||||
|
|
||||||
|
def test_vier_geraden_je_seite(self, strecke):
|
||||||
|
assert sum(1 for el in strecke if el["gruppe"] == "gerade_start") == 4
|
||||||
|
assert sum(1 for el in strecke if el["gruppe"] == "gerade_ende") == 4
|
||||||
|
|
||||||
|
def test_kurve_aus_mehreren_boegen(self, strecke):
|
||||||
|
"""Die 180-Grad-Kurve muss aus mehr als einem Bogen bestehen."""
|
||||||
|
boegen = [el for el in strecke if el["gruppe"] == "kurve_180"]
|
||||||
|
assert len(boegen) > 1, "Kurve muss aus mehreren Boegen aufgebaut sein"
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 2. Katalog-Abgleich der Boegen
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class TestStreckeKatalog:
|
||||||
|
|
||||||
|
def test_bogen_sivasnr_im_katalog(self, strecke, boegen_lookup):
|
||||||
|
for el in strecke:
|
||||||
|
if el["type"] != "bogen":
|
||||||
|
continue
|
||||||
|
assert el["sivasnr"] in boegen_lookup, (
|
||||||
|
f"SivasNr {el['sivasnr']} nicht in omniflo_boegen.json")
|
||||||
|
|
||||||
|
def test_bogen_radius_und_winkel(self, strecke, boegen_lookup):
|
||||||
|
for el in strecke:
|
||||||
|
if el["type"] != "bogen":
|
||||||
|
continue
|
||||||
|
eintrag = boegen_lookup[el["sivasnr"]]
|
||||||
|
assert float(eintrag["Radius"]) == pytest.approx(el["radius"], abs=_TOL), (
|
||||||
|
f"{el['sivasnr']}: Radius Katalog={eintrag['Radius']}, Test={el['radius']}")
|
||||||
|
assert float(eintrag["KurvenWinkel"]) == pytest.approx(el["winkel"], abs=_TOL), (
|
||||||
|
f"{el['sivasnr']}: Winkel Katalog={eintrag['KurvenWinkel']}, Test={el['winkel']}")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 3. Geometrie der Verkettung
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class TestStreckeGeometrie:
|
||||||
|
|
||||||
|
def test_verkettung_lueckenlos(self, strecke):
|
||||||
|
"""Endpunkt jedes Elements == Anfangspunkt des naechsten (verkettet)."""
|
||||||
|
for a, b in zip(strecke, strecke[1:]):
|
||||||
|
assert a["x_ende"] == pytest.approx(b["x"], abs=_TOL), (
|
||||||
|
f"Luecke x zwischen seq {a['seq']} und {b['seq']}: "
|
||||||
|
f"{a['x_ende']} != {b['x']}")
|
||||||
|
assert a["y_ende"] == pytest.approx(b["y"], abs=_TOL), (
|
||||||
|
f"Luecke y zwischen seq {a['seq']} und {b['seq']}: "
|
||||||
|
f"{a['y_ende']} != {b['y']}")
|
||||||
|
assert a["drehung_ende"] == pytest.approx(b["drehung"], abs=_TOL), (
|
||||||
|
f"Richtungssprung zwischen seq {a['seq']} und {b['seq']}: "
|
||||||
|
f"{a['drehung_ende']} != {b['drehung']}")
|
||||||
|
|
||||||
|
def test_geraden_behalten_richtung(self, strecke):
|
||||||
|
"""Eine Gerade aendert die Fahrtrichtung nicht."""
|
||||||
|
for el in strecke:
|
||||||
|
if el["type"] != "gerade":
|
||||||
|
continue
|
||||||
|
assert el["drehung"] == pytest.approx(el["drehung_ende"], abs=_TOL), (
|
||||||
|
f"seq {el['seq']}: Gerade darf Richtung nicht aendern")
|
||||||
|
|
||||||
|
def test_geraden_laenge(self, strecke):
|
||||||
|
"""Die euklidische Distanz Anfang->Ende entspricht der Laenge."""
|
||||||
|
for el in strecke:
|
||||||
|
if el["type"] != "gerade":
|
||||||
|
continue
|
||||||
|
dist = math.hypot(el["x_ende"] - el["x"], el["y_ende"] - el["y"])
|
||||||
|
assert dist == pytest.approx(el["laenge"], abs=0.5), (
|
||||||
|
f"seq {el['seq']}: Distanz {dist:.2f} != Laenge {el['laenge']}")
|
||||||
|
|
||||||
|
def test_kurve_summiert_180_grad(self, strecke):
|
||||||
|
"""Die Boegen der Kurve summieren auf exakt 180 Grad."""
|
||||||
|
summe = sum(el["winkel"] for el in strecke if el["gruppe"] == "kurve_180")
|
||||||
|
assert summe == pytest.approx(180.0, abs=_TOL), (
|
||||||
|
f"Kurvensumme {summe} Grad, erwartet 180")
|
||||||
|
|
||||||
|
def test_kurve_dreht_fahrtrichtung_um_180(self, strecke):
|
||||||
|
"""Fahrtrichtung vor der Kurve + 180 == Fahrtrichtung danach."""
|
||||||
|
boegen = [el for el in strecke if el["gruppe"] == "kurve_180"]
|
||||||
|
vor = boegen[0]["drehung"]
|
||||||
|
nach = boegen[-1]["drehung_ende"]
|
||||||
|
assert (nach - vor) == pytest.approx(180.0, abs=_TOL), (
|
||||||
|
f"Kurve dreht um {nach - vor} Grad, erwartet 180")
|
||||||
|
|
||||||
|
def test_boegen_gemeinsamer_mittelpunkt(self, strecke):
|
||||||
|
"""Gleichradige 45-Grad-Boegen einer 180-Grad-Kurve teilen einen Mittelpunkt."""
|
||||||
|
mitten = [(el["x_mitte"], el["y_mitte"])
|
||||||
|
for el in strecke if el["gruppe"] == "kurve_180"]
|
||||||
|
mx0, my0 = mitten[0]
|
||||||
|
for mx, my in mitten[1:]:
|
||||||
|
assert mx == pytest.approx(mx0, abs=_TOL) and my == pytest.approx(my0, abs=_TOL), (
|
||||||
|
f"Bogen-Mittelpunkt ({mx}, {my}) weicht von ({mx0}, {my0}) ab")
|
||||||
|
|
||||||
|
def test_bogenpunkte_liegen_auf_kreis(self, strecke):
|
||||||
|
"""Anfangs-/Endpunkte der Boegen liegen im Radius-Abstand vom Mittelpunkt."""
|
||||||
|
for el in strecke:
|
||||||
|
if el["gruppe"] != "kurve_180":
|
||||||
|
continue
|
||||||
|
for px, py in ((el["x"], el["y"]), (el["x_ende"], el["y_ende"])):
|
||||||
|
d = math.hypot(px - el["x_mitte"], py - el["y_mitte"])
|
||||||
|
assert d == pytest.approx(el["radius"], abs=0.5), (
|
||||||
|
f"seq {el['seq']}: Punkt ({px}, {py}) Abstand {d:.2f} != Radius {el['radius']}")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 4. Integrationstest gegen BricsCAD-Ergebnis (optional)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class TestStreckeResults:
|
||||||
|
"""Validiert omniflo_strecke_results.json aus c:TEST_OMNIFLO_STRECKE."""
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def results(self):
|
||||||
|
path = os.path.join(_output_dir(), "omniflo_strecke_results.json")
|
||||||
|
if not os.path.exists(path):
|
||||||
|
pytest.skip("omniflo_strecke_results.json nicht vorhanden - "
|
||||||
|
"TEST_OMNIFLO_STRECKE in BricsCAD ausfuehren")
|
||||||
|
data = _load_json(path)
|
||||||
|
if not data:
|
||||||
|
pytest.skip("omniflo_strecke_results.json ist leer")
|
||||||
|
return data
|
||||||
|
|
||||||
|
def test_alle_elemente_ausgefuehrt(self, strecke, results):
|
||||||
|
assert len(results) == len(strecke), (
|
||||||
|
f"Erwartet {len(strecke)} Ergebnisse, erhalten {len(results)}")
|
||||||
|
|
||||||
|
def test_alle_ok(self, results):
|
||||||
|
for r in results:
|
||||||
|
assert r["status"] == "OK", (
|
||||||
|
f'{r.get("test_id")} (seq {r.get("seq")}): Status={r["status"]}')
|
||||||
@@ -0,0 +1,383 @@
|
|||||||
|
;; ============================================================
|
||||||
|
;; test_unit.lsp - Unit-Tests fuer einfache LISP-Standardfunktionen
|
||||||
|
;;
|
||||||
|
;; Testet reine Hilfsfunktionen (String, Zahl, Vektor, Liste,
|
||||||
|
;; Alist), die OHNE Zeichnungsdatenbank, Auswahlsaetze, Dialoge
|
||||||
|
;; oder Blockdateien auskommen - analog zu den Python-Unittests,
|
||||||
|
;; nur direkt in AutoLISP formuliert. Jeder Testfall vergleicht
|
||||||
|
;; einen Funktionsaufruf gegen ein erwartetes Ergebnis.
|
||||||
|
;;
|
||||||
|
;; Aufruf in BricsCAD (SSG_LIB muss geladen sein):
|
||||||
|
;; (load (strcat (getenv "DXFMAKRO") "/tests/test_unit.lsp"))
|
||||||
|
;; TEST_UNIT
|
||||||
|
;;
|
||||||
|
;; Ueber die Kommandozeile:
|
||||||
|
;; bin\run_tests.bat --lisp
|
||||||
|
;; (startet BricsCAD mit tests/output/_unit_run.scr, laedt SSG_LIB,
|
||||||
|
;; fuehrt TEST_UNIT aus und schreibt tests/output/unit_results.txt)
|
||||||
|
;;
|
||||||
|
;; Ergebnis:
|
||||||
|
;; Konsole: PASS/FAIL je Test + Zusammenfassung
|
||||||
|
;; Datei: tests/output/unit_results.txt (von run_tests.bat gelesen)
|
||||||
|
;; ============================================================
|
||||||
|
|
||||||
|
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
;; MINI-TEST-FRAMEWORK
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
|
||||||
|
(setq *tu-results* nil) ;; Liste von (pass-p name detail)
|
||||||
|
(setq *tu-pass* 0)
|
||||||
|
(setq *tu-fail* 0)
|
||||||
|
|
||||||
|
(defun tu-reset ()
|
||||||
|
(setq *tu-results* nil *tu-pass* 0 *tu-fail* 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
(defun tu-str (v) (vl-princ-to-string v))
|
||||||
|
|
||||||
|
;; Ergebnis eines Testfalls erfassen
|
||||||
|
(defun tu-record (pass-p name detail)
|
||||||
|
(if pass-p
|
||||||
|
(setq *tu-pass* (1+ *tu-pass*))
|
||||||
|
(setq *tu-fail* (1+ *tu-fail*))
|
||||||
|
)
|
||||||
|
(setq *tu-results* (cons (list pass-p name detail) *tu-results*))
|
||||||
|
(princ (strcat "\n " (if pass-p "PASS" "FAIL") ": " name))
|
||||||
|
(if (not pass-p) (princ (strcat " | " detail)))
|
||||||
|
pass-p
|
||||||
|
)
|
||||||
|
|
||||||
|
;; Exakter Vergleich (Strings, Ganzzahlen, Listen von Strings/Ints)
|
||||||
|
(defun tu-eq (name expected actual)
|
||||||
|
(tu-record (equal expected actual) name
|
||||||
|
(strcat "erwartet=" (tu-str expected) " tatsaechlich=" (tu-str actual)))
|
||||||
|
)
|
||||||
|
|
||||||
|
;; Numerischer Vergleich mit Toleranz (Fliesskomma, auch verschachtelte Listen)
|
||||||
|
(defun tu-eqf (name expected actual)
|
||||||
|
(tu-record (equal expected actual 1e-6) name
|
||||||
|
(strcat "erwartet=" (tu-str expected) " tatsaechlich=" (tu-str actual)))
|
||||||
|
)
|
||||||
|
|
||||||
|
(defun tu-true (name actual)
|
||||||
|
(tu-record (not (null actual)) name (strcat "tatsaechlich=" (tu-str actual)))
|
||||||
|
)
|
||||||
|
|
||||||
|
(defun tu-nil (name actual)
|
||||||
|
(tu-record (null actual) name (strcat "tatsaechlich=" (tu-str actual)))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
;; TESTS: ssg_core.lsp - String-/JSON-/Alist-Helfer
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
(defun tu-tests-core ()
|
||||||
|
(princ "\n[ssg_core]")
|
||||||
|
|
||||||
|
;; ssg-cfg-trim: Whitespace + abschliessendes Komma entfernen
|
||||||
|
(tu-eq "ssg-cfg-trim/spaces+komma" "hello" (ssg-cfg-trim " hello, "))
|
||||||
|
(tu-eq "ssg-cfg-trim/nur-spaces" "abc" (ssg-cfg-trim "\tabc\r\n"))
|
||||||
|
|
||||||
|
;; ssg-cfg-parse-value: JSON-Skalar erkennen
|
||||||
|
(tu-eq "ssg-cfg-parse-value/string" "text" (ssg-cfg-parse-value "\"text\""))
|
||||||
|
(tu-eq "ssg-cfg-parse-value/int" 42 (ssg-cfg-parse-value "42"))
|
||||||
|
(tu-eqf "ssg-cfg-parse-value/real" 3.5 (ssg-cfg-parse-value "3.5"))
|
||||||
|
(tu-true "ssg-cfg-parse-value/true" (ssg-cfg-parse-value "true"))
|
||||||
|
(tu-nil "ssg-cfg-parse-value/false" (ssg-cfg-parse-value "false"))
|
||||||
|
(tu-nil "ssg-cfg-parse-value/null" (ssg-cfg-parse-value "null"))
|
||||||
|
|
||||||
|
;; ssg-cfg-split-comma: an Kommas aufteilen
|
||||||
|
(tu-eq "ssg-cfg-split-comma" '("a" "b" "c") (ssg-cfg-split-comma "a,b,c"))
|
||||||
|
|
||||||
|
;; ssg-cfg-parse-array: JSON-Array parsen
|
||||||
|
(tu-eq "ssg-cfg-parse-array" '(1 2 3) (ssg-cfg-parse-array "[1, 2, 3]"))
|
||||||
|
|
||||||
|
;; ssg-cfg-parse-kv: "key": wert -> ("key" . wert)
|
||||||
|
(tu-eq "ssg-cfg-parse-kv" '("key" . 42) (ssg-cfg-parse-kv "\"key\": 42"))
|
||||||
|
|
||||||
|
;; ssg-val: Wert aus Alist lesen
|
||||||
|
(tu-eq "ssg-val/vorhanden" 2 (ssg-val '(("a" . 1) ("b" . 2)) "b"))
|
||||||
|
(tu-nil "ssg-val/fehlt" (ssg-val '(("a" . 1)) "x"))
|
||||||
|
|
||||||
|
;; ssg-attrib-defaults: (TAG DEFAULT) -> (TAG . DEFAULT)
|
||||||
|
(tu-eq "ssg-attrib-defaults"
|
||||||
|
'(("TYP" . "A") ("NR" . "0"))
|
||||||
|
(ssg-attrib-defaults '(("TYP" "A") ("NR" "0"))))
|
||||||
|
|
||||||
|
;; ssg-attrib-merge: gegebene Werte ueberschreiben Defaults
|
||||||
|
(tu-eq "ssg-attrib-merge/override"
|
||||||
|
'(("TYP" . "A") ("NR" . "5"))
|
||||||
|
(ssg-attrib-merge '(("NR" . "5")) '(("TYP" "A") ("NR" "0"))))
|
||||||
|
(tu-eq "ssg-attrib-merge/nil-given"
|
||||||
|
'(("TYP" . "A") ("NR" . "0"))
|
||||||
|
(ssg-attrib-merge nil '(("TYP" "A") ("NR" "0"))))
|
||||||
|
|
||||||
|
;; ssg-attrib-alist-to-defs: (TAG . Wert) -> (TAG Wert)
|
||||||
|
(tu-eq "ssg-attrib-alist-to-defs"
|
||||||
|
'(("TYP" "A") ("NR" "0"))
|
||||||
|
(ssg-attrib-alist-to-defs '(("TYP" . "A") ("NR" . "0"))))
|
||||||
|
|
||||||
|
;; ssg-geruest-typ-to-idx / idx-to-typ (Combobox-Zuordnung)
|
||||||
|
(tu-eq "ssg-geruest-typ-to-idx/bekannt" 2 (ssg-geruest-typ-to-idx "Schoenenberger Geruest"))
|
||||||
|
(tu-eq "ssg-geruest-typ-to-idx/erst" 0 (ssg-geruest-typ-to-idx "IPE-Geruest abgestuft"))
|
||||||
|
(tu-eq "ssg-geruest-typ-to-idx/unbekannt" 2 (ssg-geruest-typ-to-idx "gibtsnicht"))
|
||||||
|
(tu-eq "ssg-geruest-idx-to-typ" "Schoenenberger Geruest" (ssg-geruest-idx-to-typ 2))
|
||||||
|
|
||||||
|
;; ssg-ini-* : INI-Parser (Eingabe = Zeilenliste, kein Datei-IO)
|
||||||
|
(setq _ini (ssg-ini-parse-lines
|
||||||
|
'("# Kommentar" "[sek]" "key = wert" "liste = a, b ,c")))
|
||||||
|
(tu-eq "ssg-ini-get/vorhanden" "wert" (ssg-ini-get _ini "sek" "key" "def"))
|
||||||
|
(tu-eq "ssg-ini-get/default" "def" (ssg-ini-get _ini "sek" "fehlt" "def"))
|
||||||
|
(tu-eq "ssg-ini-list/normalisiert" "a,b,c" (ssg-ini-list _ini "sek" "liste" "x"))
|
||||||
|
|
||||||
|
;; ssg-parse-json-array: flaches JSON-Array -> Liste von Alists
|
||||||
|
(tu-eq "ssg-parse-json-array"
|
||||||
|
'((("a" . 1) ("b" . 2)))
|
||||||
|
(ssg-parse-json-array '("[" "{" "\"a\": 1," "\"b\": 2" "}" "]")))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
;; TESTS: ssg_lang.lsp - Textersetzung
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
(defun tu-tests-lang ()
|
||||||
|
(princ "\n[ssg_lang]")
|
||||||
|
|
||||||
|
;; ssg-str-replace-all: (neu alt str)
|
||||||
|
(tu-eq "ssg-str-replace-all/alle" "fXX" (ssg-str-replace-all "X" "o" "foo"))
|
||||||
|
(tu-eq "ssg-str-replace-all/mehrfach" "a-b-c" (ssg-str-replace-all "-" "." "a.b.c"))
|
||||||
|
(tu-eq "ssg-str-replace-all/keins" "abc" (ssg-str-replace-all "X" "z" "abc"))
|
||||||
|
|
||||||
|
;; ssg-lang-unescape: literales \n (Backslash+n) -> echter Zeilenumbruch
|
||||||
|
(setq _u (ssg-lang-unescape (strcat "a" "\\n" "b")))
|
||||||
|
(tu-eq "ssg-lang-unescape/laenge" 3 (strlen _u))
|
||||||
|
(tu-eq "ssg-lang-unescape/newline" (chr 10) (substr _u 2 1))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
;; TESTS: ssg_id.lsp - ID-Formatierung
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
(defun tu-tests-id ()
|
||||||
|
(princ "\n[ssg_id]")
|
||||||
|
(tu-eq "ssg-id-format/1" "0001" (ssg-id-format 1))
|
||||||
|
(tu-eq "ssg-id-format/42" "0042" (ssg-id-format 42))
|
||||||
|
(tu-eq "ssg-id-format/1000" "1000" (ssg-id-format 1000))
|
||||||
|
(tu-eq "ssg-id-format/12345" "12345" (ssg-id-format 12345))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
;; TESTS: vf_core.lsp - Vektor-/Frame-Mathematik
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
(defun tu-tests-vfcore ()
|
||||||
|
(princ "\n[vf_core]")
|
||||||
|
|
||||||
|
(tu-eqf "vec-length/3-4-0" 5.0 (vec-length '(3.0 4.0 0.0)))
|
||||||
|
|
||||||
|
(tu-eqf "vec3-cross/x-cross-y" '(0.0 0.0 1.0) (vec3-cross '(1.0 0.0 0.0) '(0.0 1.0 0.0)))
|
||||||
|
(tu-eqf "vec3-cross/z-cross-x" '(0.0 1.0 0.0) (vec3-cross '(0.0 0.0 1.0) '(1.0 0.0 0.0)))
|
||||||
|
|
||||||
|
(tu-eqf "vec3-normalize/z5" '(0.0 0.0 1.0) (vec3-normalize '(0.0 0.0 5.0)))
|
||||||
|
|
||||||
|
(tu-eqf "mat3-mul-vec3/identitaet"
|
||||||
|
'(2.0 3.0 4.0)
|
||||||
|
(mat3-mul-vec3 '((1.0 0.0 0.0) (0.0 1.0 0.0) (0.0 0.0 1.0)) '(2.0 3.0 4.0)))
|
||||||
|
|
||||||
|
(tu-eqf "punkt-differenz" '(3.0 4.0 5.0) (punkt-differenz '(1.0 1.0 1.0) '(4.0 5.0 6.0)))
|
||||||
|
|
||||||
|
(tu-eq "ks-normalize-name/ksys-ein" "KS_EIN" (ks-normalize-name "KSYS_EIN"))
|
||||||
|
(tu-eq "ks-normalize-name/ks-aus" "KS_AUS" (ks-normalize-name "KS_AUS"))
|
||||||
|
(tu-nil "ks-normalize-name/kein-ks" (ks-normalize-name "IRGENDWAS"))
|
||||||
|
|
||||||
|
(tu-eq "ks-line-axis/X-1" "X" (ks-line-axis 1.0))
|
||||||
|
(tu-eq "ks-line-axis/X-100" "X" (ks-line-axis 100.0))
|
||||||
|
(tu-eq "ks-line-axis/Y" "Y" (ks-line-axis 2.0))
|
||||||
|
(tu-eq "ks-line-axis/Z" "Z" (ks-line-axis 3.0))
|
||||||
|
|
||||||
|
(tu-eq "fmt/zahl" "3.14" (fmt 3.14159))
|
||||||
|
(tu-eq "fmt/nil" "---" (fmt nil))
|
||||||
|
|
||||||
|
;; hz-winkel->xu: Richtungsvektor aus Horizontal-/Vertikalwinkel
|
||||||
|
(tu-eqf "hz-winkel->xu/0-0" '(1.0 0.0 0.0) (hz-winkel->xu 0 0))
|
||||||
|
(tu-eqf "hz-winkel->xu/90-0" '(0.0 1.0 0.0) (hz-winkel->xu 90 0))
|
||||||
|
|
||||||
|
;; Roundtrip frame -> (hz winkel)
|
||||||
|
(tu-eqf "frame->hz-winkel/roundtrip"
|
||||||
|
'(30.0 10.0)
|
||||||
|
(frame->hz-winkel (punkt-hz-winkel->frame '(0.0 0.0 0.0) 30 10)))
|
||||||
|
|
||||||
|
;; ks-relativize / ks-absolutize sind zueinander invers
|
||||||
|
(setq _ks '(("KS_EIN" ((100.0 200.0 300.0) (101.0 200.0 300.0)))))
|
||||||
|
(tu-eqf "ks-relativize+absolutize/invers"
|
||||||
|
_ks
|
||||||
|
(ks-absolutize (ks-relativize _ks '(10.0 20.0 30.0)) '(10.0 20.0 30.0)))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
;; TESTS: export.lsp - CSV/KOS-Kodierung
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
(defun tu-tests-export ()
|
||||||
|
(princ "\n[export]")
|
||||||
|
|
||||||
|
(tu-eq "csv:clamp/innen" 5 (csv:clamp 5 0 10))
|
||||||
|
(tu-eq "csv:clamp/unten" 0 (csv:clamp -3 0 10))
|
||||||
|
(tu-eq "csv:clamp/oben" 10 (csv:clamp 20 0 10))
|
||||||
|
|
||||||
|
(tu-eqf "csv:z-angle-to-quat/0" '(0.0 0.0 0.0 1.0) (csv:z-angle-to-quat 0))
|
||||||
|
(tu-eqf "csv:z-angle-to-quat/180" '(0.0 0.0 1.0 0.0) (csv:z-angle-to-quat 180))
|
||||||
|
|
||||||
|
(tu-eq "csv:b64-encode-ints/0" "AAAA" (csv:b64-encode-ints '(0)))
|
||||||
|
(tu-eq "csv:trans-encode/0-0-0" "AAAAAAAAAAAA" (csv:trans-encode 0.0 0.0 0.0))
|
||||||
|
|
||||||
|
;; local-to-world: reine Z-Drehung um den Elternpunkt
|
||||||
|
(tu-eqf "csv:local-to-world/keine-drehung"
|
||||||
|
'(1.0 0.0 0.0) (csv:local-to-world '(0.0 0.0 0.0) 0.0 '(1.0 0.0 0.0)))
|
||||||
|
(tu-eqf "csv:local-to-world/90-grad"
|
||||||
|
'(0.0 1.0 0.0) (csv:local-to-world '(0.0 0.0 0.0) (/ pi 2.0) '(1.0 0.0 0.0)))
|
||||||
|
|
||||||
|
;; json-escape: Anfuehrungszeichen maskieren
|
||||||
|
(tu-eq "csv:json-escape/quote" "a\\\"b" (csv:json-escape "a\"b"))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
;; TESTS: Gefaellestrecke.lsp - Geometrie-/Listen-Helfer
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
(defun tu-tests-gf ()
|
||||||
|
(princ "\n[Gefaellestrecke]")
|
||||||
|
|
||||||
|
(tu-eq "gf-snap-bogenwinkel/30" 30 (gf-snap-bogenwinkel 30))
|
||||||
|
(tu-eq "gf-snap-bogenwinkel/44" 30 (gf-snap-bogenwinkel 44))
|
||||||
|
(tu-eq "gf-snap-bogenwinkel/60" 60 (gf-snap-bogenwinkel 60))
|
||||||
|
(tu-eq "gf-snap-bogenwinkel/90" 90 (gf-snap-bogenwinkel 90))
|
||||||
|
|
||||||
|
(tu-eq "gf-bogen-blockname"
|
||||||
|
"Gefaellebogen_links_90_R500" (gf-bogen-blockname 90 "links"))
|
||||||
|
|
||||||
|
(tu-eqf "gf-abstand-2d/3-4" 5.0 (gf-abstand-2d '(0.0 0.0) '(3.0 4.0)))
|
||||||
|
(tu-eqf "gf-hz-winkel/rechts" 0.0 (gf-hz-winkel '(0.0 0.0) '(1.0 0.0)))
|
||||||
|
(tu-eqf "gf-hz-winkel/oben" 90.0 (gf-hz-winkel '(0.0 0.0) '(0.0 1.0)))
|
||||||
|
|
||||||
|
(tu-eq "gf-replace-nth" '(a x c) (gf-replace-nth '(a b c) 1 'x))
|
||||||
|
|
||||||
|
;; gf-exit-hz: Ausgangsrichtung nach letztem Segment
|
||||||
|
(tu-eqf "gf-exit-hz/linie" 45.0 (gf-exit-hz '(("Linie" 45.0))))
|
||||||
|
(tu-eqf "gf-exit-hz/bogen-links"
|
||||||
|
90.0 (gf-exit-hz '(("Linie" 0.0) ("Bogen" 0.0 nil 90 "links"))))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
;; TESTS: OmniModulInsert.lsp - Datensatz-Helfer
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
(defun tu-tests-omni ()
|
||||||
|
(princ "\n[OmniModulInsert]")
|
||||||
|
|
||||||
|
(tu-eq "omni:sivasid-to-str/int" "42" (omni:sivasid-to-str 42))
|
||||||
|
(tu-eq "omni:sivasid-to-str/str" "abc" (omni:sivasid-to-str "abc"))
|
||||||
|
(tu-eq "omni:sivasid-to-str/real" "3" (omni:sivasid-to-str 3.0))
|
||||||
|
|
||||||
|
;; Mock-Datensatz (Struktur wie ssg-load-json liefern wuerde)
|
||||||
|
(setq _lst
|
||||||
|
'((("Sivasnr" . 100) ("KurvenWinkel" . 45) ("Radius" . 550))
|
||||||
|
(("Sivasnr" . 200) ("KurvenWinkel" . 90) ("Radius" . 400))
|
||||||
|
(("Sivasnr" . 300) ("KurvenWinkel" . 45) ("Radius" . 750))))
|
||||||
|
|
||||||
|
(tu-eq "omni:val" 400 (omni:val (omni:get-by-sivasid _lst 200) "Radius"))
|
||||||
|
(tu-nil "omni:get-by-sivasid/fehlt" (omni:get-by-sivasid _lst 999))
|
||||||
|
(tu-eq "omni:filter/anzahl" 2 (length (omni:filter _lst "KurvenWinkel" 45)))
|
||||||
|
(tu-eq "omni:filter/wert"
|
||||||
|
100 (cdr (assoc "Sivasnr" (car (omni:filter _lst "KurvenWinkel" 45)))))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
;; TESTS: KreiselInsert.lsp - Geometrie-Helfer
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
(defun tu-tests-kreisel ()
|
||||||
|
(princ "\n[KreiselInsert]")
|
||||||
|
|
||||||
|
;; Projektion eines Punktes auf eine Gerade
|
||||||
|
(tu-eqf "kreisel-project-point/lot"
|
||||||
|
'(0.0 0.0) (kreisel-project-point '(0.0 5.0) '(0.0 0.0) '(10.0 0.0)))
|
||||||
|
(tu-eqf "kreisel-project-point/mitte"
|
||||||
|
'(5.0 0.0) (kreisel-project-point '(5.0 5.0) '(0.0 0.0) '(10.0 0.0)))
|
||||||
|
|
||||||
|
;; Parallelitaet zweier Richtungsvektoren
|
||||||
|
(tu-true "kreisel-parallel-p/parallel" (kreisel-parallel-p '(1.0 0.0) '(2.0 0.0)))
|
||||||
|
(tu-nil "kreisel-parallel-p/senkrecht" (kreisel-parallel-p '(1.0 0.0) '(0.0 1.0)))
|
||||||
|
|
||||||
|
;; Ausrichtung <-> Index
|
||||||
|
(tu-eqf "kreisel-idx-to-rotation/0" 0.0 (kreisel-idx-to-rotation 0))
|
||||||
|
(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))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
;; REPORT SCHREIBEN
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
(defun tu-write-report (pfad / f res)
|
||||||
|
(setq f (open pfad "w"))
|
||||||
|
(if f
|
||||||
|
(progn
|
||||||
|
(write-line "# SSG_LIB LISP Unit-Test Report" f)
|
||||||
|
(foreach res (reverse *tu-results*)
|
||||||
|
(write-line
|
||||||
|
(strcat (if (car res) "PASS: " "FAIL: ") (cadr res)
|
||||||
|
(if (car res) "" (strcat " | " (caddr res))))
|
||||||
|
f))
|
||||||
|
(write-line (strcat "SUMMARY: total=" (itoa (+ *tu-pass* *tu-fail*))
|
||||||
|
" passed=" (itoa *tu-pass*)
|
||||||
|
" failed=" (itoa *tu-fail*)) f)
|
||||||
|
(write-line (if (= *tu-fail* 0) "RESULT: OK" "RESULT: FAIL") f)
|
||||||
|
(close f)
|
||||||
|
T
|
||||||
|
)
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
;; BEFEHL: TEST_UNIT
|
||||||
|
;; ------------------------------------------------------------
|
||||||
|
(defun c:TEST_UNIT ( / out-dir report)
|
||||||
|
(tu-reset)
|
||||||
|
(princ "\n================================================================")
|
||||||
|
(princ "\n TEST_UNIT - LISP-Standardfunktionen")
|
||||||
|
(princ "\n================================================================")
|
||||||
|
|
||||||
|
(tu-tests-core)
|
||||||
|
(tu-tests-lang)
|
||||||
|
(tu-tests-id)
|
||||||
|
(tu-tests-vfcore)
|
||||||
|
(tu-tests-export)
|
||||||
|
(tu-tests-gf)
|
||||||
|
(tu-tests-omni)
|
||||||
|
(tu-tests-kreisel)
|
||||||
|
|
||||||
|
(princ "\n\n----------------------------------------------------------------")
|
||||||
|
(princ (strcat "\n Gesamt: " (itoa (+ *tu-pass* *tu-fail*))
|
||||||
|
" | PASS: " (itoa *tu-pass*)
|
||||||
|
" | FAIL: " (itoa *tu-fail*)))
|
||||||
|
(princ (if (= *tu-fail* 0) "\n ERGEBNIS: ALLE TESTS BESTANDEN" "\n ERGEBNIS: TESTS FEHLGESCHLAGEN"))
|
||||||
|
(princ "\n----------------------------------------------------------------")
|
||||||
|
|
||||||
|
;; Report-Datei schreiben (fuer run_tests.bat --lisp)
|
||||||
|
(setq out-dir (strcat (getenv "DXFMAKRO") "/tests/output"))
|
||||||
|
(vl-mkdir out-dir)
|
||||||
|
(setq report (strcat out-dir "/unit_results.txt"))
|
||||||
|
(if (tu-write-report report)
|
||||||
|
(princ (strcat "\n Report: " report))
|
||||||
|
(princ "\n WARNUNG: Report konnte nicht geschrieben werden.")
|
||||||
|
)
|
||||||
|
(princ)
|
||||||
|
)
|
||||||
|
|
||||||
|
(princ "\ntest_unit.lsp geladen. Befehl: TEST_UNIT")
|
||||||
|
(princ)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
; test_unit.scr - Headless-Skript fuer bin\run_tests.bat --lisp
|
||||||
|
; Laedt SSG_LIB + die Unit-Tests, fuehrt TEST_UNIT aus und beendet BricsCAD.
|
||||||
|
; Pfadunabhaengig ueber DXFMAKRO (von setenv.bat gesetzt).
|
||||||
|
(load (strcat (getenv "DXFMAKRO") "/Lisp/ssg_load.lsp"))
|
||||||
|
(load (strcat (getenv "DXFMAKRO") "/tests/test_unit.lsp"))
|
||||||
|
(c:TEST_UNIT)
|
||||||
|
(command "_.QUIT")
|
||||||
+194
@@ -0,0 +1,194 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"seq": 1,
|
||||||
|
"gruppe": "gerade_start",
|
||||||
|
"type": "gerade",
|
||||||
|
"profil": "AP110",
|
||||||
|
"laenge": 1000.0,
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"drehung": 0.0,
|
||||||
|
"x_ende": 1000.0,
|
||||||
|
"y_ende": 0.0,
|
||||||
|
"drehung_ende": 0.0,
|
||||||
|
"hoehe": 2000,
|
||||||
|
"description": "Gerade AP110 L=1000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seq": 2,
|
||||||
|
"gruppe": "gerade_start",
|
||||||
|
"type": "gerade",
|
||||||
|
"profil": "AP110",
|
||||||
|
"laenge": 1000.0,
|
||||||
|
"x": 1000.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"drehung": 0.0,
|
||||||
|
"x_ende": 2000.0,
|
||||||
|
"y_ende": 0.0,
|
||||||
|
"drehung_ende": 0.0,
|
||||||
|
"hoehe": 2000,
|
||||||
|
"description": "Gerade AP110 L=1000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seq": 3,
|
||||||
|
"gruppe": "gerade_start",
|
||||||
|
"type": "gerade",
|
||||||
|
"profil": "AP110",
|
||||||
|
"laenge": 1000.0,
|
||||||
|
"x": 2000.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"drehung": 0.0,
|
||||||
|
"x_ende": 3000.0,
|
||||||
|
"y_ende": 0.0,
|
||||||
|
"drehung_ende": 0.0,
|
||||||
|
"hoehe": 2000,
|
||||||
|
"description": "Gerade AP110 L=1000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seq": 4,
|
||||||
|
"gruppe": "gerade_start",
|
||||||
|
"type": "gerade",
|
||||||
|
"profil": "AP110",
|
||||||
|
"laenge": 1000.0,
|
||||||
|
"x": 3000.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"drehung": 0.0,
|
||||||
|
"x_ende": 4000.0,
|
||||||
|
"y_ende": 0.0,
|
||||||
|
"drehung_ende": 0.0,
|
||||||
|
"hoehe": 2000,
|
||||||
|
"description": "Gerade AP110 L=1000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seq": 5,
|
||||||
|
"gruppe": "kurve_180",
|
||||||
|
"type": "bogen",
|
||||||
|
"sivasnr": "821104021",
|
||||||
|
"radius": 400.0,
|
||||||
|
"winkel": 45.0,
|
||||||
|
"x": 4000.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"drehung": 0.0,
|
||||||
|
"x_ende": 4282.84,
|
||||||
|
"y_ende": 117.16,
|
||||||
|
"drehung_ende": 45.0,
|
||||||
|
"x_mitte": 4000.0,
|
||||||
|
"y_mitte": 400.0,
|
||||||
|
"hoehe": 2000,
|
||||||
|
"description": "APB 110 R 400/45 - 400/670 (1/4)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seq": 6,
|
||||||
|
"gruppe": "kurve_180",
|
||||||
|
"type": "bogen",
|
||||||
|
"sivasnr": "821104021",
|
||||||
|
"radius": 400.0,
|
||||||
|
"winkel": 45.0,
|
||||||
|
"x": 4282.84,
|
||||||
|
"y": 117.16,
|
||||||
|
"drehung": 45.0,
|
||||||
|
"x_ende": 4400.0,
|
||||||
|
"y_ende": 400.0,
|
||||||
|
"drehung_ende": 90.0,
|
||||||
|
"x_mitte": 4000.0,
|
||||||
|
"y_mitte": 400.0,
|
||||||
|
"hoehe": 2000,
|
||||||
|
"description": "APB 110 R 400/45 - 400/670 (2/4)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seq": 7,
|
||||||
|
"gruppe": "kurve_180",
|
||||||
|
"type": "bogen",
|
||||||
|
"sivasnr": "821104021",
|
||||||
|
"radius": 400.0,
|
||||||
|
"winkel": 45.0,
|
||||||
|
"x": 4400.0,
|
||||||
|
"y": 400.0,
|
||||||
|
"drehung": 90.0,
|
||||||
|
"x_ende": 4282.84,
|
||||||
|
"y_ende": 682.84,
|
||||||
|
"drehung_ende": 135.0,
|
||||||
|
"x_mitte": 4000.0,
|
||||||
|
"y_mitte": 400.0,
|
||||||
|
"hoehe": 2000,
|
||||||
|
"description": "APB 110 R 400/45 - 400/670 (3/4)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seq": 8,
|
||||||
|
"gruppe": "kurve_180",
|
||||||
|
"type": "bogen",
|
||||||
|
"sivasnr": "821104021",
|
||||||
|
"radius": 400.0,
|
||||||
|
"winkel": 45.0,
|
||||||
|
"x": 4282.84,
|
||||||
|
"y": 682.84,
|
||||||
|
"drehung": 135.0,
|
||||||
|
"x_ende": 4000.0,
|
||||||
|
"y_ende": 800.0,
|
||||||
|
"drehung_ende": 180.0,
|
||||||
|
"x_mitte": 4000.0,
|
||||||
|
"y_mitte": 400.0,
|
||||||
|
"hoehe": 2000,
|
||||||
|
"description": "APB 110 R 400/45 - 400/670 (4/4)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seq": 9,
|
||||||
|
"gruppe": "gerade_ende",
|
||||||
|
"type": "gerade",
|
||||||
|
"profil": "AP110",
|
||||||
|
"laenge": 1000.0,
|
||||||
|
"x": 4000.0,
|
||||||
|
"y": 800.0,
|
||||||
|
"drehung": 180.0,
|
||||||
|
"x_ende": 3000.0,
|
||||||
|
"y_ende": 800.0,
|
||||||
|
"drehung_ende": 180.0,
|
||||||
|
"hoehe": 2000,
|
||||||
|
"description": "Gerade AP110 L=1000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seq": 10,
|
||||||
|
"gruppe": "gerade_ende",
|
||||||
|
"type": "gerade",
|
||||||
|
"profil": "AP110",
|
||||||
|
"laenge": 1000.0,
|
||||||
|
"x": 3000.0,
|
||||||
|
"y": 800.0,
|
||||||
|
"drehung": 180.0,
|
||||||
|
"x_ende": 2000.0,
|
||||||
|
"y_ende": 800.0,
|
||||||
|
"drehung_ende": 180.0,
|
||||||
|
"hoehe": 2000,
|
||||||
|
"description": "Gerade AP110 L=1000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seq": 11,
|
||||||
|
"gruppe": "gerade_ende",
|
||||||
|
"type": "gerade",
|
||||||
|
"profil": "AP110",
|
||||||
|
"laenge": 1000.0,
|
||||||
|
"x": 2000.0,
|
||||||
|
"y": 800.0,
|
||||||
|
"drehung": 180.0,
|
||||||
|
"x_ende": 1000.0,
|
||||||
|
"y_ende": 800.0,
|
||||||
|
"drehung_ende": 180.0,
|
||||||
|
"hoehe": 2000,
|
||||||
|
"description": "Gerade AP110 L=1000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"seq": 12,
|
||||||
|
"gruppe": "gerade_ende",
|
||||||
|
"type": "gerade",
|
||||||
|
"profil": "AP110",
|
||||||
|
"laenge": 1000.0,
|
||||||
|
"x": 1000.0,
|
||||||
|
"y": 800.0,
|
||||||
|
"drehung": 180.0,
|
||||||
|
"x_ende": 0.0,
|
||||||
|
"y_ende": 800.0,
|
||||||
|
"drehung_ende": 180.0,
|
||||||
|
"hoehe": 2000,
|
||||||
|
"description": "Gerade AP110 L=1000"
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user