Omniflo-Streckenzug-Test + skriptfaehige Aluprofil-Geraden
Neuer Test: 4 Geraden -> 180-Grad-Kurve (4x 45-Grad-Bogen) -> 4 Geraden als lueckenlos verketteter Streckenzug (U-Form). - testdata/omniflo_strecke_tests.json: 12 Elemente mit berechneter, verketteter Platzierung (x, y, drehung, x_ende, y_ende, Bogen-Mittelpunkt) - test_omniflo_strecke.py: Geometrie-Validierung ohne BricsCAD (Struktur, Katalog-Abgleich, Verkettung, Kurvensumme 180 Grad, gemeinsamer Mittelpunkt) - test_omniflo_strecke.lsp: C:TEST_OMNIFLO_STRECKE baut die Strecke auf - alltests.json/README.md: Test registriert und dokumentiert OmniModulInsert.lsp: omni:insert-block in skriptfaehigen Kern omni:make-gerade + omni:read-src-attribs aufgeteilt. Der Kern erzeugt eine Aluprofil-Gerade non-interaktiv aus zwei Punkten (kein getpoint/ssg-start), sodass der Test echte Gerade-Bloecke statt nackter Linien baut. Interaktives Verhalten von omni:insert-block bleibt unveraendert (Vorschau-Text, Laengenpruefung). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+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)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -106,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" }
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
@@ -149,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
|
||||||
@@ -470,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"]}')
|
||||||
+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