Merge branch 'master' of https://gitea.schoenenberger.de/Schoenenberger_Systeme_GmbH/dxfmakros
This commit is contained in:
+41
-18
@@ -16,8 +16,8 @@ BricsCAD Kommandozeile
|
||||
| SSG_RUN_ALL_TESTS |
|
||||
| (TEST_KREISEL, |
|
||||
| TEST_FOERDERER, |
|
||||
| TEST_KSEINAUS, |
|
||||
| TEST_OMNIFLO) |
|
||||
| TEST_OMNIFLO, |
|
||||
| TEST_GEFAELLESTRECKE) |
|
||||
| | |
|
||||
v v |
|
||||
tests/output/ |
|
||||
@@ -25,11 +25,10 @@ tests/output/ |
|
||||
kreisel_tests.dxf (save: dxf) |
|
||||
foerderer_results.json (immer) |
|
||||
foerderer_tests.dwg (save: dwg) |
|
||||
kseinaus_results.json (immer) |
|
||||
(keine Zeichnung) (save: null) |
|
||||
omniflo_results.json (immer) |
|
||||
omniflo_tests.dxf (save: dxf) |
|
||||
omniflo_export.csv |
|
||||
gefaellestrecke_results.json |
|
||||
gefaellestrecke_tests.dwg |
|
||||
|
|
||||
bin\run_tests.bat --runall
|
||||
|
|
||||
@@ -37,8 +36,29 @@ tests/output/ |
|
||||
|
|
||||
test_kreisel.py
|
||||
test_omniflo.py
|
||||
test_foerderer.py
|
||||
```
|
||||
|
||||
**Jedes Testmodul laeuft in einer eigenen, neuen Zeichnung** (statt die aktuelle zu
|
||||
leeren/wiederzuverwenden). `SSG_RUN_ALL_TESTS` fragt beim Start interaktiv nach
|
||||
dem gewuenschten Modus:
|
||||
|
||||
- **Kontroll-Modus (Default, Enter/Nein)**: Zeichnung wird gespeichert, per
|
||||
`_.ZOOM _Extents` auf den Inhalt gezoomt und bleibt als eigener Tab offen —
|
||||
der naechste Test startet in einer weiteren neuen Zeichnung. Am Ende des Laufs
|
||||
sind alle Testzeichnungen gleichzeitig offen und direkt sichtbar. Sinnvoll,
|
||||
wenn neue/geaenderte Testfaelle visuell geprueft werden sollen.
|
||||
`c:EXPORTCSV`/`c:EXPORTSIVAS` laufen **nicht** automatisch mit — CSV/Sivas-
|
||||
Export danach bei Bedarf manuell ueber `TEST_EXPORT_ALL`
|
||||
(`tests/test_export_all.lsp`, Menuepunkt "Export CSV/Sivas ALL"), das alle
|
||||
vorhandenen DXF/DWG-Dateien aus `tests/output/` einliest und je Datei
|
||||
`<basis>_export.csv`/`<basis>_sivas.csv` erzeugt.
|
||||
- **Automatik-Modus (Ja)**: Zeichnung wird gespeichert, direkt per
|
||||
`c:EXPORTCSV`/`c:EXPORTSIVAS` exportiert (`<name>_tests_export.csv`/
|
||||
`<name>_tests_sivas.csv`) und danach sofort wieder geschlossen. Kompletter
|
||||
Lauf ohne manuellen Zusatzschritt, aber ohne gleichzeitige visuelle Kontrolle
|
||||
aller Zeichnungen am Ende. Sinnvoll fuer reine CI-artige Validierung.
|
||||
|
||||
## alltests.json — Testregistry
|
||||
|
||||
Die Datei `tests/alltests.json` ist die zentrale Registry aller Test-Module.
|
||||
@@ -46,10 +66,10 @@ Die Datei `tests/alltests.json` ist die zentrale Registry aller Test-Module.
|
||||
|
||||
```json
|
||||
[
|
||||
{ "name": "kreisel", "save": "dxf" },
|
||||
{ "name": "foerderer", "save": "dwg" },
|
||||
{ "name": "kseinaus", "save": null },
|
||||
{ "name": "omniflo", "save": "dxf" }
|
||||
{ "name": "kreisel", "save": "dxf" },
|
||||
{ "name": "foerderer", "save": "dwg" },
|
||||
{ "name": "omniflo", "save": "dxf" },
|
||||
{ "name": "gefaellestrecke", "save": "dwg" }
|
||||
]
|
||||
```
|
||||
|
||||
@@ -73,7 +93,7 @@ wird in `tests/output/` geschrieben, sobald der Test abgeschlossen ist.
|
||||
|
||||
1. **BricsCAD starten** (ueber `bin\start_briscad.bat`) und die LISP-Testrunner ausfuehren (siehe Abschnitt "LISP-Tests in BricsCAD ausfuehren"). Die Testrunner erzeugen Bloecke in der Zeichnung und speichern Ergebnisse nach `tests/output/`.
|
||||
|
||||
2. **Optische Kontrolle** in BricsCAD — pruefen ob die erzeugten Bloecke korrekt aussehen (Positionen, Drehungen, Beschriftungen). Die Zeichnung bleibt offen und kann visuell inspiziert werden.
|
||||
2. **Optische Kontrolle** in BricsCAD — pruefen ob die erzeugten Bloecke korrekt aussehen (Positionen, Drehungen, Beschriftungen). Jede Testzeichnung bleibt als eigener Tab offen (auf den Inhalt gezoomt) und kann direkt visuell inspiziert werden.
|
||||
|
||||
3. **Kommandozeile**: Ergebnisse validieren oder als neue Referenz speichern:
|
||||
- `bin\run_tests.bat --runall` — pytest prueft die Ergebnisse aus `output/` gegen Testdefinitionen und Referenzdaten
|
||||
@@ -90,9 +110,9 @@ tests/
|
||||
test_kreisel.lsp # LISP-Testrunner: C:TEST_KREISEL
|
||||
test_kreisel.py # pytest-Validierung der Kreisel-Ergebnisse
|
||||
test_foerderer.lsp # LISP-Testrunner: C:TEST_FOERDERER
|
||||
test_kseinaus.lsp # LISP-Testrunner: C:TEST_KSEINAUS
|
||||
test_omniflo.lsp # LISP-Testrunner: C:TEST_OMNIFLO
|
||||
test_omniflo.py # pytest-Validierung der Omniflo-Ergebnisse
|
||||
test_export_all.lsp # LISP-Testrunner: C:TEST_EXPORT_ALL (manuell, nicht in alltests.json)
|
||||
testdata/
|
||||
kreisel_tests.json # Testfall-Definitionen fuer Kreisel
|
||||
omniflo_tests.json # Testfall-Definitionen fuer Omniflo
|
||||
@@ -100,12 +120,14 @@ tests/
|
||||
kreisel_results.json # JSON-Ergebnisse pro Testmodul
|
||||
kreisel_tests.dxf # DXF-Zeichnung pro Testmodul
|
||||
foerderer_results.json
|
||||
foerderer_tests.dxf
|
||||
kseinaus_results.json
|
||||
kseinaus_tests.dxf
|
||||
foerderer_tests.dwg
|
||||
omniflo_results.json
|
||||
omniflo_tests.dxf
|
||||
omniflo_export.csv # CSV-Export (Omniflo-spezifisch)
|
||||
gefaellestrecke_results.json
|
||||
gefaellestrecke_tests.dwg
|
||||
export_all_results.json # nur nach manuellem TEST_EXPORT_ALL-Lauf
|
||||
<basis>_export.csv # EXPORTCSV je Quelldatei, nur nach TEST_EXPORT_ALL
|
||||
<basis>_sivas.csv # EXPORTSIVAS je Quelldatei, nur nach TEST_EXPORT_ALL
|
||||
reference/ # Abgenommene Referenzdaten (in Git)
|
||||
kreisel_ref.dxf
|
||||
...
|
||||
@@ -173,7 +195,7 @@ Die Python-Tests pruefen:
|
||||
- **Attribut-Werte** gegen die Testdefinitionen (JSON)
|
||||
- **DXF-Geometrie** mit ezdxf (Bloecke, Positionen, Radien)
|
||||
- **Referenz-Vergleich** gegen abgenommene Referenz-DXF
|
||||
- **CSV-Export** (Omniflo: Spalten, Merkmale, Datentypen)
|
||||
- **CSV-Export** ueber `lib/export_csv.py` (Omniflo: Spalten, Merkmale, Sum-Zeile, Datentypen)
|
||||
|
||||
Tests, deren Eingabedaten fehlen (z.B. kein BricsCAD-Lauf), werden automatisch uebersprungen (`pytest.skip`).
|
||||
|
||||
@@ -381,8 +403,9 @@ def <modul>_ref_dxf():
|
||||
|
||||
| Klasse | Prueft |
|
||||
|---|---|
|
||||
| `TestOmnifloExportUnit` | Mock-Export: Merkmale, Datentypen, Vollstaendigkeit (ohne BricsCAD) |
|
||||
| `TestOmnifloExportCSV` | CSV aus BricsCAD: Header, Spalten, JSON-Merkmale |
|
||||
| `TestOmnifloExportUnit` | Mock-Export ueber `lib/export_csv.py`: Merkmale, Datentypen, Sum-Zeile, Vollstaendigkeit (ohne BricsCAD) |
|
||||
| `TestOmnifloResults` | omniflo_results.json aus BricsCAD: Testfaelle vollstaendig, Status OK, Hoehe |
|
||||
| `TestOmnifloReferenceCSV` | CSV-Export aus `lib/export_csv.py` gegen abgenommene Referenz-CSV |
|
||||
|
||||
## Umgebungsvariablen
|
||||
|
||||
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
[
|
||||
{ "name": "kreisel", "save": "dxf", "module": "KreiselInsert" },
|
||||
{ "name": "foerderer", "save": "dwg", "module": "VarioFoerderer" },
|
||||
{ "name": "kseinaus", "save": null, "module": "VarioFoerderer" },
|
||||
{ "name": "omniflo", "save": "dxf", "module": "OmniModulInsert" },
|
||||
{ "name": "gefaellestrecke", "save": "dxf", "module": "Gefaellestrecke" }
|
||||
{ "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" }
|
||||
]
|
||||
|
||||
+51
-17
@@ -32,8 +32,18 @@ def _reference_dir():
|
||||
|
||||
|
||||
def _load_json(path):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
"""Laedt JSON, tolerant gegenueber BricsCAD-Ausgaben.
|
||||
|
||||
AutoLISP schreibt Dateien (write-line) im ANSI-Codepage des Systems,
|
||||
nicht in UTF-8 - Sonderzeichen (z.B. Grad-Zeichen in Beschreibungen)
|
||||
koennen daher nicht als UTF-8 dekodierbar sein. Fallback auf cp1252.
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
# --- Foerderer Fixtures ---
|
||||
@@ -68,22 +78,15 @@ def omniflo_testdata():
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def omniflo_csv():
|
||||
"""Laedt den Inhalt der omniflo_export.csv aus output/."""
|
||||
import csv as _csv
|
||||
path = os.path.join(_output_dir(), "omniflo_export.csv")
|
||||
def omniflo_results():
|
||||
"""Laedt die Omniflo-Ergebnisse aus output/omniflo_results.json."""
|
||||
path = os.path.join(_output_dir(), "omniflo_results.json")
|
||||
if not os.path.exists(path):
|
||||
pytest.skip(
|
||||
"omniflo_export.csv nicht vorhanden - "
|
||||
"TEST_OMNIFLO_EXPORT in BricsCAD ausfuehren"
|
||||
)
|
||||
rows = []
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
reader = _csv.reader(f, delimiter=";")
|
||||
header = next(reader)
|
||||
for row in reader:
|
||||
rows.append(row)
|
||||
return {"header": header, "rows": rows}
|
||||
pytest.skip("omniflo_results.json nicht vorhanden - TEST_OMNIFLO in BricsCAD ausfuehren")
|
||||
data = _load_json(path)
|
||||
if not data:
|
||||
pytest.skip("omniflo_results.json ist leer")
|
||||
return data
|
||||
|
||||
|
||||
# --- Kreisel Fixtures ---
|
||||
@@ -123,3 +126,34 @@ def kreisel_ref_dxf():
|
||||
if not os.path.exists(path):
|
||||
pytest.skip("kreisel_ref.dxf nicht vorhanden - Referenz noch nicht erstellt")
|
||||
return ezdxf.readfile(path)
|
||||
|
||||
|
||||
# --- Gefaellestrecke Fixtures ---
|
||||
|
||||
@pytest.fixture
|
||||
def gefaellestrecke_testdata():
|
||||
"""Laedt die Gefaellestrecke-Testdefinitionen aus testdata/gefaellestrecke_tests.json."""
|
||||
path = os.path.join(_testdata_dir(), "gefaellestrecke_tests.json")
|
||||
data = _load_json(path)
|
||||
return [t for t in data if "test_id" in t]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gefaellestrecke_results():
|
||||
"""Laedt die Gefaellestrecke-Ergebnisse aus output/gefaellestrecke_results.json."""
|
||||
path = os.path.join(_output_dir(), "gefaellestrecke_results.json")
|
||||
if not os.path.exists(path):
|
||||
pytest.skip("gefaellestrecke_results.json nicht vorhanden - TEST_GEFAELLESTRECKE in BricsCAD ausfuehren")
|
||||
data = _load_json(path)
|
||||
if not data:
|
||||
pytest.skip("gefaellestrecke_results.json ist leer")
|
||||
return data
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gefaellestrecke_dxf():
|
||||
"""Laedt die erzeugte Gefaellestrecke-DXF aus output/gefaellestrecke_tests.dxf."""
|
||||
path = os.path.join(_output_dir(), "gefaellestrecke_tests.dxf")
|
||||
if not os.path.exists(path):
|
||||
pytest.skip("gefaellestrecke_tests.dxf nicht vorhanden - TESTRUN in BricsCAD ausfuehren")
|
||||
return ezdxf.readfile(path)
|
||||
|
||||
@@ -106,3 +106,4 @@ Elementnummer;TeileArt;TeileId;Bezeichnung;Anzahl;Merkmale
|
||||
105;"Omniflo Weiche";"0000";"OFWeiche :6";1;{"Weichentyp": "Dreifachweiche", "Richtung": "3", "Weichenwinkel": 90.0, "Höhe": "2000", "Drehung": 0.0, "Antrieb Kurve": true, "SivasNummer": "834372404+0_BG071090"}
|
||||
106;"Omniflo Weiche";"0000";"OFWeiche :1";1;{"Weichentyp": "Deltaweiche", "Richtung": "7", "Weichenwinkel": 90.0, "Höhe": "2000", "Drehung": 0.0, "Antrieb Kurve": false, "SivasNummer": "834372401"}
|
||||
107;"Omniflo Weiche";"0000";"OFWeiche :1";1;{"Weichentyp": "Sternweiche", "Richtung": "3", "Weichenwinkel": 0.0, "Höhe": "2000", "Drehung": 0.0, "Antrieb Kurve": false, "SivasNummer": "834372420"}
|
||||
108;"Omniflo Sum";"autogenerated_of_json";"Omniflo sum";1;{"Anzahl Bögen": 27, "Anzahl Weichengrundkörper": 0, "Anzahl Einwegweichen": 30, "Anzahl Deltaweichen": 7, "Anzahl Doppelweichen und Dreiwegeweichen": 42, "Anzahl Sternweichen": 1, "Gesamtlänge AP110": 0.0, "Gesamtlänge AP60": 0.0}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 42.
|
@@ -0,0 +1,197 @@
|
||||
;; ============================================================
|
||||
;; test_export_all.lsp - Batch-Export aller Testzeichnungen
|
||||
;;
|
||||
;; Oeffnet jede vorhandene DXF/DWG-Datei aus tests/output/ und fuehrt
|
||||
;; darauf c:EXPORTCSV und c:EXPORTSIVAS aus (Lisp/export.lsp).
|
||||
;;
|
||||
;; Muss als LETZTES Modul in tests/alltests.json stehen: Es liest
|
||||
;; die Ausgabedateien aller anderen Testmodule, die zu diesem
|
||||
;; Zeitpunkt bereits geschrieben sein muessen.
|
||||
;;
|
||||
;; Ausgabe je Quelldatei <basis>.dxf/.dwg:
|
||||
;; tests/output/<basis>_export.csv
|
||||
;; tests/output/<basis>_sivas.csv
|
||||
;; tests/output/export_all_results.json (Zusammenfassung, immer)
|
||||
;;
|
||||
;; Voraussetzungen:
|
||||
;; - SSG_LIB geladen (export.lsp, ssg_core.lsp)
|
||||
;; - Umgebungsvariable DXFMAKRO gesetzt
|
||||
;;
|
||||
;; Aufruf in BricsCAD:
|
||||
;; (load (strcat (getenv "DXFMAKRO") "/tests/test_export_all.lsp"))
|
||||
;; TEST_EXPORT_ALL
|
||||
;; ============================================================
|
||||
|
||||
(vl-load-com)
|
||||
|
||||
|
||||
;; --- Auf Fertigstellung einer asynchron geschriebenen Datei warten ---
|
||||
;; csv:run-export startet Python per startapp (fire-and-forget). Pollt
|
||||
;; bis die Datei existiert und ihre Groesse ueber ein Intervall stabil
|
||||
;; bleibt, oder bis timeout-sec erreicht ist. Rueckgabe: T bei Erfolg.
|
||||
(defun export_all:wait-for-file (path timeout-sec / elapsed size1 size2 done)
|
||||
(setq elapsed 0.0 done nil)
|
||||
(while (and (not done) (< elapsed timeout-sec))
|
||||
(if (findfile path)
|
||||
(progn
|
||||
(setq size1 (vl-file-size path))
|
||||
(command "_.DELAY" 400)
|
||||
(setq size2 (vl-file-size path))
|
||||
(if (and size1 size2 (= size1 size2) (> size1 0))
|
||||
(setq done T)
|
||||
)
|
||||
)
|
||||
(command "_.DELAY" 400)
|
||||
)
|
||||
(setq elapsed (+ elapsed 0.4))
|
||||
)
|
||||
done
|
||||
)
|
||||
|
||||
|
||||
;; --- Zeichnung zusaetzlich oeffnen, ohne die aktuelle zu schliessen ---
|
||||
;; Nutzt reines ActiveX Documents.Open statt (command "_.OPEN" ...):
|
||||
;; Damit entfaellt sowohl die interaktive "Speichern?"-Abfrage als auch
|
||||
;; das vorherige Schliessen des aktiven Dokuments. Letzteres fuehrte dazu,
|
||||
;; dass BricsCAD (weil kein Dokument mehr offen war) automatisch ein neues
|
||||
;; Blank-Dokument erzeugt und dabei den kompletten Modul-/Menue-Ladezyklus
|
||||
;; (inkl. Fremd-Addons) erneut durchlaeuft - pro verarbeiteter Datei.
|
||||
(defun export_all:open-drawing (filepath / app doc)
|
||||
(setq app (vlax-get-acad-object))
|
||||
(setq doc (vla-open (vla-get-documents app) filepath))
|
||||
(vla-put-activedocument app doc)
|
||||
doc
|
||||
)
|
||||
|
||||
|
||||
;; --- Basisname (ohne Dateiendung) aus einem Dateinamen extrahieren ---
|
||||
(defun export_all:basisname (dateiname / i punkt-pos)
|
||||
(setq i 0 punkt-pos nil)
|
||||
(while (setq i (vl-string-search "." dateiname i))
|
||||
(setq punkt-pos i)
|
||||
(setq i (1+ i))
|
||||
)
|
||||
(if punkt-pos (substr dateiname 1 punkt-pos) dateiname)
|
||||
)
|
||||
|
||||
|
||||
;; --- Ergebnis eines Datei-Exports als JSON-String erzeugen ---
|
||||
(defun export_all:result-json (basis csv-ok sivas-ok / json)
|
||||
(strcat " {\n"
|
||||
" \"quelldatei\": \"" basis "\",\n"
|
||||
" \"csv_ok\": " (if csv-ok "true" "false") ",\n"
|
||||
" \"sivas_ok\": " (if sivas-ok "true" "false") "\n"
|
||||
" }")
|
||||
)
|
||||
|
||||
|
||||
;; --- JSON-Export der Ergebnisse ---
|
||||
(defun export_all:export-results (tests-out-dir / out-json f first)
|
||||
(if (null *export-all-results*)
|
||||
(princ "\n Keine Export-All-Ergebnisse vorhanden.")
|
||||
(progn
|
||||
(vl-mkdir tests-out-dir)
|
||||
(setq out-json (strcat tests-out-dir "/export_all_results.json"))
|
||||
(setq f (open out-json "w"))
|
||||
(if f
|
||||
(progn
|
||||
(write-line "[" f)
|
||||
(setq first T)
|
||||
(foreach r *export-all-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_EXPORT_ALL
|
||||
;;
|
||||
;; Oeffnet jede DXF/DWG-Datei aus tests/output/ und exportiert sie
|
||||
;; per EXPORTCSV/EXPORTSIVAS in eigene, nach der Quelldatei benannte
|
||||
;; CSV-Dateien.
|
||||
;; ============================================================
|
||||
(defun c:TEST_EXPORT_ALL (/ tests-out-dir dateien datei basis doc
|
||||
csv-name sivas-name csv-pfad sivas-pfad
|
||||
csv-ok sivas-ok results-list old-filenames)
|
||||
|
||||
(setq tests-out-dir (strcat (getenv "DXFMAKRO") "/tests/output"))
|
||||
(vl-mkdir tests-out-dir)
|
||||
(setenv "DXFM_RESULTS" tests-out-dir)
|
||||
|
||||
(setq dateien (append
|
||||
(vl-directory-files tests-out-dir "*.dxf" 1)
|
||||
(vl-directory-files tests-out-dir "*.dwg" 1)
|
||||
))
|
||||
(setq old-filenames *export-filenames*)
|
||||
(setq results-list nil)
|
||||
|
||||
(princ "\n================================================================")
|
||||
(princ "\n TEST_EXPORT_ALL")
|
||||
(princ (strcat "\n " (itoa (length dateien)) " Zeichnung(en) gefunden"))
|
||||
(princ "\n================================================================")
|
||||
|
||||
(foreach datei dateien
|
||||
(setq basis (export_all:basisname datei))
|
||||
(princ (strcat "\n\n>>> " datei " <<<"))
|
||||
|
||||
(setq doc (export_all:open-drawing (strcat tests-out-dir "/" datei)))
|
||||
|
||||
;; Datei-/Skriptnamen fuer diesen Durchlauf ueberschreiben, damit
|
||||
;; jede Quelldatei ihre eigenen CSV-Ergebnisse bekommt (statt der
|
||||
;; fixen Standardnamen "export.csv"/"export_sivas.csv").
|
||||
(setq csv-name (strcat basis "_export.csv"))
|
||||
(setq sivas-name (strcat basis "_sivas.csv"))
|
||||
(setq csv-pfad (strcat tests-out-dir "/" csv-name))
|
||||
(setq sivas-pfad (strcat tests-out-dir "/" sivas-name))
|
||||
(if (findfile csv-pfad) (vl-file-delete csv-pfad))
|
||||
(if (findfile sivas-pfad) (vl-file-delete sivas-pfad))
|
||||
|
||||
(setq *export-filenames*
|
||||
(list
|
||||
(cons "raw_json_datei" (cdr (assoc "raw_json_datei" old-filenames)))
|
||||
(cons "sivas_script" (cdr (assoc "sivas_script" old-filenames)))
|
||||
(cons "sivas_csv" sivas-name)
|
||||
(cons "csv_script" (cdr (assoc "csv_script" old-filenames)))
|
||||
(cons "csv_csv" csv-name)
|
||||
)
|
||||
)
|
||||
|
||||
(princ (strcat "\n[TEST_EXPORT_ALL] CSV-Export -> " csv-name))
|
||||
(c:EXPORTCSV)
|
||||
(setq csv-ok (export_all:wait-for-file csv-pfad 30.0))
|
||||
(princ (if csv-ok "\n[TEST_EXPORT_ALL] CSV OK" "\n[TEST_EXPORT_ALL] CSV TIMEOUT/FEHLER"))
|
||||
|
||||
(princ (strcat "\n[TEST_EXPORT_ALL] Sivas-Export -> " sivas-name))
|
||||
(c:EXPORTSIVAS)
|
||||
(setq sivas-ok (export_all:wait-for-file sivas-pfad 30.0))
|
||||
(princ (if sivas-ok "\n[TEST_EXPORT_ALL] Sivas OK" "\n[TEST_EXPORT_ALL] Sivas TIMEOUT/FEHLER"))
|
||||
|
||||
(setq results-list (cons
|
||||
(export_all:result-json basis csv-ok sivas-ok)
|
||||
results-list))
|
||||
|
||||
;; Diese Zeichnung wieder schliessen (verwirft ID/Hoehe/Drehung-Updates
|
||||
;; aus dem Export). Das urspruengliche Dokument (vor dieser Schleife
|
||||
;; aktiv) bleibt dabei durchgehend offen, BricsCAD erzeugt also kein
|
||||
;; neues Blank-Dokument und der teure Modul-/Menue-Reload entfaellt.
|
||||
(vla-close doc :vlax-false)
|
||||
)
|
||||
|
||||
(setq *export-filenames* old-filenames)
|
||||
(setq *export-all-results* (reverse results-list))
|
||||
|
||||
(princ "\n\n================================================================")
|
||||
(princ "\n TEST_EXPORT_ALL abgeschlossen.")
|
||||
(princ "\n================================================================")
|
||||
(princ)
|
||||
)
|
||||
+15
-17
@@ -75,7 +75,7 @@
|
||||
y-offset y-abstand idx
|
||||
anz-gebaut anz-nicht-gebaut results-list
|
||||
startpunkt vf-nummer hoehe-von hoehe-bis lastEnt
|
||||
tests-out-dir old-dxfm-results)
|
||||
sub-ent sub-typ)
|
||||
|
||||
;; Bibliothek initialisieren (laedt Block-Library und extrahiert Masse)
|
||||
(if (not *lib-initialized*)
|
||||
@@ -103,7 +103,7 @@
|
||||
;; Konfiguration aus erstem Eintrag (ohne test_id)
|
||||
(setq cfg (car testdaten))
|
||||
(setq y-abstand (ssg-val cfg "y_abstand"))
|
||||
(if (or (null y-abstand) (= y-abstand 0)) (setq y-abstand 400))
|
||||
(if (or (null y-abstand) (= y-abstand 0)) (setq y-abstand 800))
|
||||
|
||||
;; Nur Eintraege mit test_id sind Testfaelle
|
||||
(setq tests
|
||||
@@ -175,6 +175,19 @@
|
||||
(/ L_GF 2.0) (/ L_GF 2.0)
|
||||
richtung best-winkel startpunkt lastEnt 0.0)
|
||||
|
||||
;; lastEnt hinter den SEQEND des VF-Blocks schieben.
|
||||
;; Ohne diesen Schritt wuerde entnext vom INSERT-Entity
|
||||
;; in die ATTRIB-Kette des gerade erstellten VF-Blocks
|
||||
;; springen und diese beim naechsten _.-BLOCK-Aufruf
|
||||
;; in den neuen Block verschieben (Datenverlust).
|
||||
(setq lastEnt (entlast))
|
||||
(setq sub-ent (entnext lastEnt))
|
||||
(while (and sub-ent
|
||||
(not (equal (cdr (assoc 0 (entget sub-ent))) "SEQEND")))
|
||||
(setq lastEnt sub-ent)
|
||||
(setq sub-ent (entnext lastEnt)))
|
||||
(if sub-ent (setq lastEnt sub-ent))
|
||||
|
||||
(setq results-list (cons
|
||||
(foerderer:test-result-json test-id richtung deltaL deltaH
|
||||
"GEBAUT" best-winkel L_GF L_VF)
|
||||
@@ -205,21 +218,6 @@
|
||||
;; Ergebnisse in globaler Variable speichern
|
||||
(setq *foerderer-test-results* (reverse results-list))
|
||||
|
||||
;; CSV- und Sivas-Export nach tests/output/
|
||||
(setq tests-out-dir (strcat (getenv "DXFMAKRO") "/tests/output"))
|
||||
(vl-mkdir tests-out-dir)
|
||||
(setq old-dxfm-results (getenv "DXFM_RESULTS"))
|
||||
(setenv "DXFM_RESULTS" tests-out-dir)
|
||||
|
||||
(princ "\n================================================================")
|
||||
(princ "\n[TEST_FOERDERER] CSV-Export -> foerderer_export.csv")
|
||||
(csv:run-export "FOERDERER_CSV" "export_csv.py" "foerderer_export.csv")
|
||||
|
||||
(princ "\n[TEST_FOERDERER] Sivas-Export -> foerderer_sivas.csv")
|
||||
(csv:run-export "FOERDERER_SIVAS" "export_sivas.py" "foerderer_sivas.csv")
|
||||
|
||||
(setenv "DXFM_RESULTS" (if old-dxfm-results old-dxfm-results ""))
|
||||
|
||||
(ssg-end)
|
||||
(princ "\n================================================================")
|
||||
(princ "\n TEST_FOERDERER abgeschlossen.")
|
||||
|
||||
+14
-14
@@ -7,8 +7,8 @@ Prueft:
|
||||
1. Status-Werte und Vollstaendigkeit (foerderer_results.json)
|
||||
2. Mathematische Invarianten (Laengen, Winkel, Richtung)
|
||||
3. Geometrisch unmoegliche Faelle
|
||||
4. CSV-Export-Struktur (foerderer_export.csv, falls vorhanden)
|
||||
5. Sivas-Export-Struktur (foerderer_sivas.csv, falls vorhanden)
|
||||
4. CSV-Export-Struktur (foerderer_tests_export.csv, aus TEST_EXPORT_ALL)
|
||||
5. Sivas-Export-Struktur (foerderer_tests_sivas.csv, aus TEST_EXPORT_ALL)
|
||||
"""
|
||||
|
||||
import csv
|
||||
@@ -179,19 +179,19 @@ class TestFoerdererMathematik:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Klasse 3: CSV-Export-Pruefung (foerderer_export.csv)
|
||||
# Klasse 3: CSV-Export-Pruefung (foerderer_tests_export.csv)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestFoerdererExportCSV:
|
||||
"""Prueft den CSV-Export aus BricsCAD (foerderer_export.csv)."""
|
||||
"""Prueft den CSV-Export aus BricsCAD (foerderer_tests_export.csv, aus TEST_EXPORT_ALL)."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
csv_path = os.path.join(_output_dir(), "foerderer_export.csv")
|
||||
csv_path = os.path.join(_output_dir(), "foerderer_tests_export.csv")
|
||||
if not os.path.exists(csv_path):
|
||||
pytest.skip(
|
||||
"foerderer_export.csv nicht vorhanden - "
|
||||
"TEST_FOERDERER in BricsCAD ausfuehren (exportiert automatisch)"
|
||||
"foerderer_tests_export.csv nicht vorhanden - "
|
||||
"TEST_FOERDERER und anschliessend TEST_EXPORT_ALL in BricsCAD ausfuehren"
|
||||
)
|
||||
self.rows = []
|
||||
with open(csv_path, "r", encoding="utf-8") as f:
|
||||
@@ -211,7 +211,7 @@ class TestFoerdererExportCSV:
|
||||
|
||||
def test_hat_eintraege(self):
|
||||
"""CSV muss mindestens einen Dateneintrag enthalten."""
|
||||
assert len(self.rows) > 0, "foerderer_export.csv enthaelt keine Daten"
|
||||
assert len(self.rows) > 0, "foerderer_tests_export.csv enthaelt keine Daten"
|
||||
|
||||
def test_vario_teileart(self):
|
||||
"""Alle Eintraege muessen TeileArt 'VarioFoerderer' haben."""
|
||||
@@ -265,19 +265,19 @@ class TestFoerdererExportCSV:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Klasse 4: Sivas-Export-Pruefung (foerderer_sivas.csv)
|
||||
# Klasse 4: Sivas-Export-Pruefung (foerderer_tests_sivas.csv)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestFoerdererExportSivas:
|
||||
"""Prueft den Sivas-Export aus BricsCAD (foerderer_sivas.csv)."""
|
||||
"""Prueft den Sivas-Export aus BricsCAD (foerderer_tests_sivas.csv, aus TEST_EXPORT_ALL)."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
csv_path = os.path.join(_output_dir(), "foerderer_sivas.csv")
|
||||
csv_path = os.path.join(_output_dir(), "foerderer_tests_sivas.csv")
|
||||
if not os.path.exists(csv_path):
|
||||
pytest.skip(
|
||||
"foerderer_sivas.csv nicht vorhanden - "
|
||||
"TEST_FOERDERER in BricsCAD ausfuehren (exportiert automatisch)"
|
||||
"foerderer_tests_sivas.csv nicht vorhanden - "
|
||||
"TEST_FOERDERER und anschliessend TEST_EXPORT_ALL in BricsCAD ausfuehren"
|
||||
)
|
||||
self.rows = []
|
||||
with open(csv_path, "r", encoding="utf-8") as f:
|
||||
@@ -297,7 +297,7 @@ class TestFoerdererExportSivas:
|
||||
|
||||
def test_hat_eintraege(self):
|
||||
"""Sivas-CSV muss mindestens einen Dateneintrag enthalten."""
|
||||
assert len(self.rows) > 0, "foerderer_sivas.csv enthaelt keine Daten"
|
||||
assert len(self.rows) > 0, "foerderer_tests_sivas.csv enthaelt keine Daten"
|
||||
|
||||
def test_strecke_zeile_vorhanden(self):
|
||||
"""Eine Zeile mit TeileArt 'ILS 2.0 Strecke - Module' muss vorhanden sein."""
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
test_gefaellestrecke.py - Validiert GEFAELLESTRECKE Testergebnisse.
|
||||
|
||||
Prueft:
|
||||
1. Status-Werte und Vollstaendigkeit (gefaellestrecke_results.json)
|
||||
2. Mathematische Invarianten (Hoehen, Winkel, deltaH)
|
||||
3. Konsistenz zwischen den Modi (Modus 1 Richtungsunabhaengigkeit,
|
||||
Modus 3 uebernimmt deltaH/deltaL aus Modus 2)
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Hilfsfunktionen
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
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):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def _load_gefaellestrecke_results():
|
||||
path = os.path.join(_output_dir(), "gefaellestrecke_results.json")
|
||||
if not os.path.exists(path):
|
||||
pytest.skip(
|
||||
"gefaellestrecke_results.json nicht vorhanden - "
|
||||
"TEST_GEFAELLESTRECKE in BricsCAD ausfuehren"
|
||||
)
|
||||
data = _load_json(path)
|
||||
if not data:
|
||||
pytest.skip("gefaellestrecke_results.json ist leer")
|
||||
return data
|
||||
|
||||
|
||||
def _load_gefaellestrecke_testdata():
|
||||
path = os.path.join(_testdata_dir(), "gefaellestrecke_tests.json")
|
||||
return _load_json(path)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Klasse 1: Status und Vollstaendigkeit
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestGefaellestreckeStatus:
|
||||
"""Prueft Status-Werte und Vollstaendigkeit der Ergebnisse."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
self.results = _load_gefaellestrecke_results()
|
||||
testdata = _load_gefaellestrecke_testdata()
|
||||
self.tests = [t for t in testdata if "test_id" in t]
|
||||
self.results_by_id = {r["test_id"]: r for r in self.results}
|
||||
|
||||
def test_alle_testfaelle_vorhanden(self):
|
||||
"""Alle Testfaelle muessen in den Ergebnissen vorhanden sein."""
|
||||
expected_ids = {t["test_id"] for t in self.tests}
|
||||
result_ids = {r["test_id"] for r in self.results}
|
||||
missing = expected_ids - result_ids
|
||||
assert not missing, f"Fehlende Testfaelle in Ergebnissen: {missing}"
|
||||
|
||||
def test_status_werte_gueltig(self):
|
||||
"""Status muss GEBAUT oder FEHLER (bzw. FEHLER_LINIE) sein."""
|
||||
gueltige_status = {"GEBAUT", "FEHLER", "FEHLER_LINIE"}
|
||||
for r in self.results:
|
||||
assert r["status"] in gueltige_status, (
|
||||
f'{r["test_id"]}: Unbekannter Status "{r["status"]}"'
|
||||
)
|
||||
|
||||
def test_alle_modus1_gebaut(self):
|
||||
"""Alle vier Modus-1-Richtungen (hz=0/90/180/270) muessen GEBAUT sein."""
|
||||
modus1 = [r for r in self.results if r["modus"] == 1]
|
||||
assert len(modus1) == 4, f"Erwartet 4 Modus-1-Testfaelle, gefunden {len(modus1)}"
|
||||
for r in modus1:
|
||||
assert r["status"] == "GEBAUT", (
|
||||
f'{r["test_id"]}: erwartet GEBAUT, ist "{r["status"]}"'
|
||||
)
|
||||
|
||||
def test_modus2_und_modus3_gebaut(self):
|
||||
"""Modus 2 und Modus 3 muessen GEBAUT sein."""
|
||||
for test_id in ("M2_hz000", "M3_hz270"):
|
||||
r = self.results_by_id.get(test_id)
|
||||
assert r is not None, f"{test_id} fehlt in den Ergebnissen"
|
||||
assert r["status"] == "GEBAUT", (
|
||||
f'{test_id}: erwartet GEBAUT, ist "{r["status"]}"'
|
||||
)
|
||||
|
||||
def test_mindestens_ein_gebaut(self):
|
||||
"""Mindestens ein Testfall muss GEBAUT sein."""
|
||||
gebaut = [r for r in self.results if r["status"] == "GEBAUT"]
|
||||
assert len(gebaut) > 0, "Keine einzige Gefaellestrecke konnte gebaut werden"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Klasse 2: Mathematische Invarianten
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestGefaellestreckeMathematik:
|
||||
"""Prueft mathematische Korrektheit der berechneten Werte."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
self.results = _load_gefaellestrecke_results()
|
||||
self.gebaut = [r for r in self.results if r["status"] == "GEBAUT"]
|
||||
self.results_by_id = {r["test_id"]: r for r in self.results}
|
||||
|
||||
def test_gebaut_hat_pflichtfelder(self):
|
||||
"""GEBAUT-Ergebnisse muessen alle Kernfelder enthalten."""
|
||||
pflichtfelder = {
|
||||
"modus", "hz_grad", "deltaL_mm", "winkel_grad",
|
||||
"hoehe_von_mm", "hoehe_bis_mm", "delta_h_mm",
|
||||
}
|
||||
for r in self.gebaut:
|
||||
missing = pflichtfelder - set(r.keys())
|
||||
assert not missing, f'{r["test_id"]}: fehlende Felder {missing}'
|
||||
|
||||
def test_deltaL_positiv(self):
|
||||
"""deltaL_mm muss fuer alle GEBAUT-Faelle groesser als 0 sein."""
|
||||
for r in self.gebaut:
|
||||
assert r["deltaL_mm"] > 0, (
|
||||
f'{r["test_id"]}: deltaL_mm={r["deltaL_mm"]} muss > 0 sein'
|
||||
)
|
||||
|
||||
def test_winkel_positiv(self):
|
||||
"""Der berechnete Gefaellewinkel muss groesser als 0 Grad sein."""
|
||||
for r in self.gebaut:
|
||||
assert r["winkel_grad"] > 0, (
|
||||
f'{r["test_id"]}: winkel_grad={r["winkel_grad"]} muss > 0 sein'
|
||||
)
|
||||
|
||||
def test_delta_h_konsistent_mit_hoehen(self):
|
||||
"""delta_h_mm muss der absoluten Differenz von hoehe_von/hoehe_bis entsprechen."""
|
||||
for r in self.gebaut:
|
||||
erwartet = abs(r["hoehe_von_mm"] - r["hoehe_bis_mm"])
|
||||
assert erwartet == r["delta_h_mm"], (
|
||||
f'{r["test_id"]}: delta_h_mm={r["delta_h_mm"]}, '
|
||||
f'erwartet |{r["hoehe_von_mm"]}-{r["hoehe_bis_mm"]}|={erwartet}'
|
||||
)
|
||||
|
||||
def test_hoehe_bis_kleiner_hoehe_von(self):
|
||||
"""Bei einer Gefaellestrecke muss die Endhoehe unter der Starthoehe liegen."""
|
||||
for r in self.gebaut:
|
||||
assert r["hoehe_bis_mm"] < r["hoehe_von_mm"], (
|
||||
f'{r["test_id"]}: hoehe_bis_mm={r["hoehe_bis_mm"]} '
|
||||
f'nicht < hoehe_von_mm={r["hoehe_von_mm"]}'
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Klasse 3: Konsistenz zwischen den Modi
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestGefaellestreckeModusKonsistenz:
|
||||
"""Prueft Zusammenhaenge zwischen Modus 1/2/3 (Richtungsunabhaengigkeit,
|
||||
Uebernahme von deltaH/deltaL aus Modus 2 in Modus 3)."""
|
||||
|
||||
# Toleranz fuer die 2-Pass-Winkelkorrektur in Modus 3 (mm)
|
||||
DELTA_H_TOLERANZ_MM = 5
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
self.results = _load_gefaellestrecke_results()
|
||||
self.results_by_id = {r["test_id"]: r for r in self.results}
|
||||
self.testdata = _load_gefaellestrecke_testdata()
|
||||
self.tests_by_id = {t["test_id"]: t for t in self.testdata if "test_id" in t}
|
||||
|
||||
def test_modus1_richtungsunabhaengig(self):
|
||||
"""Alle vier Modus-1-Richtungen muessen dieselbe Hoehe/Winkel liefern,
|
||||
da nur die Fahrtrichtung (hz), nicht die Geometrie variiert."""
|
||||
modus1 = [r for r in self.results
|
||||
if r["modus"] == 1 and r["status"] == "GEBAUT"]
|
||||
assert len(modus1) >= 2, "Zu wenige Modus-1-Ergebnisse fuer Vergleich"
|
||||
referenz = modus1[0]
|
||||
for r in modus1[1:]:
|
||||
assert r["delta_h_mm"] == referenz["delta_h_mm"], (
|
||||
f'{r["test_id"]}: delta_h_mm={r["delta_h_mm"]} weicht von '
|
||||
f'{referenz["test_id"]}={referenz["delta_h_mm"]} ab (Richtungsabhaengigkeit!)'
|
||||
)
|
||||
assert r["winkel_grad"] == referenz["winkel_grad"], (
|
||||
f'{r["test_id"]}: winkel_grad={r["winkel_grad"]} weicht von '
|
||||
f'{referenz["test_id"]}={referenz["winkel_grad"]} ab'
|
||||
)
|
||||
|
||||
def test_modus3_uebernimmt_deltaH_aus_modus2(self):
|
||||
"""Modus 3 (Linienzug mit 2-Pass-Korrektur) muss innerhalb einer kleinen
|
||||
Toleranz dieselbe Hoehendifferenz wie die Quelle aus Modus 2 erreichen."""
|
||||
m3_test = self.tests_by_id.get("M3_hz270")
|
||||
if m3_test is None:
|
||||
pytest.skip("M3_hz270 nicht in Testdaten definiert")
|
||||
quelle_id = m3_test.get("deltaH_quelle")
|
||||
m2 = self.results_by_id.get(quelle_id)
|
||||
m3 = self.results_by_id.get("M3_hz270")
|
||||
if m2 is None or m3 is None:
|
||||
pytest.skip("M2- oder M3-Ergebnis fehlt")
|
||||
if m2["status"] != "GEBAUT" or m3["status"] != "GEBAUT":
|
||||
pytest.skip("M2 oder M3 nicht GEBAUT")
|
||||
abweichung = abs(m3["delta_h_mm"] - m2["delta_h_mm"])
|
||||
assert abweichung <= self.DELTA_H_TOLERANZ_MM, (
|
||||
f'M3_hz270: delta_h_mm={m3["delta_h_mm"]} weicht um {abweichung} mm '
|
||||
f'von {quelle_id}={m2["delta_h_mm"]} ab '
|
||||
f'(Toleranz {self.DELTA_H_TOLERANZ_MM} mm)'
|
||||
)
|
||||
@@ -1,261 +0,0 @@
|
||||
;; ============================================================
|
||||
;; test_kseinaus.lsp - Automatischer Integrationstest fuer KS_EIN/KS_AUS
|
||||
;;
|
||||
;; Fuegt alle Vario-Bloecke in 3 Gruppen ein, jeweils 2 Reihen:
|
||||
;; Gruppe 1: Stationaere Elemente (AUS, EIN, Separator, Umlenk, Motor)
|
||||
;; Gruppe 2: Alle Boegen aufwaerts (in Z verschoben)
|
||||
;; Gruppe 3: Alle Boegen abwaerts (in Z verschoben)
|
||||
;;
|
||||
;; Pro Gruppe:
|
||||
;; Reihe A: nach KS_EIN ausgerichtet
|
||||
;; Reihe B: nach KS_AUS ausgerichtet
|
||||
;; Luecke von 400mm zwischen den Reihen
|
||||
;;
|
||||
;; Speichert:
|
||||
;; - tests/output/kseinaus_results.json (Ergebnisse)
|
||||
;; - tests/output/kseinaus_tests.dxf (Zeichnung)
|
||||
;;
|
||||
;; Voraussetzungen:
|
||||
;; - SSG_LIB geladen (VarioFoerderer.lsp, ssg_core.lsp)
|
||||
;; - Umgebungsvariable DXFMAKRO gesetzt
|
||||
;;
|
||||
;; Aufruf in BricsCAD:
|
||||
;; (load (strcat (getenv "DXFMAKRO") "/tests/test_kseinaus.lsp"))
|
||||
;; TEST_KSEINAUS
|
||||
;; ============================================================
|
||||
|
||||
|
||||
;; --- Ergebnis eines Testfalls als JSON-String erzeugen ---
|
||||
(defun kseinaus:test-result-json (test-id block-name gruppe ks-type status /
|
||||
json)
|
||||
(setq json (strcat " {\n"
|
||||
" \"test_id\": \"" test-id "\",\n"
|
||||
" \"block_name\": \"" (if block-name block-name "") "\",\n"
|
||||
" \"gruppe\": \"" gruppe "\",\n"
|
||||
" \"ks_type\": \"" ks-type "\",\n"
|
||||
" \"status\": \"" status "\"\n"
|
||||
" }"))
|
||||
json
|
||||
)
|
||||
|
||||
|
||||
;; --- Eine Reihe Bloecke einfuegen und Ergebnisse sammeln ---
|
||||
;; Rueckgabe: (naechste-y-pos . results-list)
|
||||
(defun kseinaus:test-reihe (namen x-start y-start z-offset y-abstand y-luecke
|
||||
gruppe results-in /
|
||||
bname block-obj ks-data ks-ein ks-aus
|
||||
insert-pt offset-vec y-pos idx
|
||||
test-id results-out)
|
||||
|
||||
(setq results-out results-in)
|
||||
|
||||
;; --- Reihe A: nach KS_EIN ---
|
||||
(princ "\n Reihe KS_EIN:")
|
||||
(setq y-pos y-start)
|
||||
(setq idx 0)
|
||||
|
||||
(foreach bname namen
|
||||
(setq idx (1+ idx))
|
||||
(setq test-id (strcat gruppe "_KS_EIN_" (itoa idx)))
|
||||
(setq insert-pt (list x-start y-pos z-offset))
|
||||
(setq block-obj (vla-InsertBlock modelspace
|
||||
(vlax-3D-point insert-pt)
|
||||
bname 1.0 1.0 1.0 0))
|
||||
(setq ks-data (extract-ks-from-block block-obj))
|
||||
(setq ks-ein (cadr (assoc "KS_EIN" ks-data)))
|
||||
|
||||
(if ks-ein
|
||||
(progn
|
||||
(setq offset-vec (list (- x-start (car (car ks-ein)))
|
||||
(- y-pos (cadr (car ks-ein)))
|
||||
(- z-offset (caddr (car ks-ein)))))
|
||||
(vla-Move block-obj
|
||||
(vlax-3D-point '(0 0 0))
|
||||
(vlax-3D-point offset-vec))
|
||||
(princ (strcat "\n " (itoa idx) ". " bname " OK"))
|
||||
(setq results-out (cons
|
||||
(kseinaus:test-result-json test-id bname gruppe "KS_EIN" "OK")
|
||||
results-out))
|
||||
)
|
||||
(progn
|
||||
(princ (strcat "\n " (itoa idx) ". " bname " WARNUNG: KS_EIN fehlt!"))
|
||||
(setq results-out (cons
|
||||
(kseinaus:test-result-json test-id bname gruppe "KS_EIN" "KS_FEHLT")
|
||||
results-out))
|
||||
)
|
||||
)
|
||||
(setq y-pos (- y-pos y-abstand))
|
||||
)
|
||||
|
||||
;; --- Luecke ---
|
||||
(setq y-pos (- y-pos y-luecke))
|
||||
|
||||
;; --- Reihe B: nach KS_AUS ---
|
||||
(princ "\n Reihe KS_AUS:")
|
||||
(setq idx 0)
|
||||
|
||||
(foreach bname namen
|
||||
(setq idx (1+ idx))
|
||||
(setq test-id (strcat gruppe "_KS_AUS_" (itoa idx)))
|
||||
(setq insert-pt (list x-start y-pos z-offset))
|
||||
(setq block-obj (vla-InsertBlock modelspace
|
||||
(vlax-3D-point insert-pt)
|
||||
bname 1.0 1.0 1.0 0))
|
||||
(setq ks-data (extract-ks-from-block block-obj))
|
||||
(setq ks-aus (cadr (assoc "KS_AUS" ks-data)))
|
||||
|
||||
(if ks-aus
|
||||
(progn
|
||||
(setq offset-vec (list (- x-start (car (car ks-aus)))
|
||||
(- y-pos (cadr (car ks-aus)))
|
||||
(- z-offset (caddr (car ks-aus)))))
|
||||
(vla-Move block-obj
|
||||
(vlax-3D-point '(0 0 0))
|
||||
(vlax-3D-point offset-vec))
|
||||
(princ (strcat "\n " (itoa idx) ". " bname " OK"))
|
||||
(setq results-out (cons
|
||||
(kseinaus:test-result-json test-id bname gruppe "KS_AUS" "OK")
|
||||
results-out))
|
||||
)
|
||||
(progn
|
||||
(princ (strcat "\n " (itoa idx) ". " bname " WARNUNG: KS_AUS fehlt!"))
|
||||
(setq results-out (cons
|
||||
(kseinaus:test-result-json test-id bname gruppe "KS_AUS" "KS_FEHLT")
|
||||
results-out))
|
||||
)
|
||||
)
|
||||
(setq y-pos (- y-pos y-abstand))
|
||||
)
|
||||
|
||||
;; Rueckgabe: (naechste-y-pos . results-list)
|
||||
(cons y-pos results-out)
|
||||
)
|
||||
|
||||
|
||||
;; --- JSON-Export der KS-Testergebnisse ---
|
||||
(defun kseinaus:export-results (tests-out-dir / out-json f first)
|
||||
(if (null *kseinaus-test-results*)
|
||||
(princ "\n Keine KS-Ergebnisse vorhanden.")
|
||||
(progn
|
||||
(vl-mkdir tests-out-dir)
|
||||
(setq out-json (strcat tests-out-dir "/kseinaus_results.json"))
|
||||
(setq f (open out-json "w"))
|
||||
(if f
|
||||
(progn
|
||||
(write-line "[" f)
|
||||
(setq first T)
|
||||
(foreach r *kseinaus-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_KSEINAUS - Hauptbefehl
|
||||
;; ============================================================
|
||||
(defun c:TEST_KSEINAUS ( / stationen boegen-auf-liste boegen-ab-liste
|
||||
bname y-pos y-abstand y-luecke z-bogen
|
||||
results-list reihe-result
|
||||
anz-ok anz-fehlt)
|
||||
|
||||
;; Bibliothek initialisieren
|
||||
(if (not *lib-initialized*)
|
||||
(init-bibliothek)
|
||||
)
|
||||
|
||||
(ssg-start "TEST_KSEINAUS" '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA")))
|
||||
(setvar "OSMODE" 0)
|
||||
(setvar "ATTREQ" 0)
|
||||
(setvar "ATTDIA" 0)
|
||||
|
||||
(setq y-abstand 200)
|
||||
(setq y-luecke 400)
|
||||
(setq z-bogen 2500)
|
||||
(setq results-list '())
|
||||
|
||||
;; Gruppe 1: Stationaere Elemente
|
||||
(setq stationen (list
|
||||
"_3D_AS_90_links"
|
||||
"Staustrecke_Separator_SP_300_mm"
|
||||
"Vario_Umlenkstation_500mm"
|
||||
"Vario_Motorstation_500mm"
|
||||
"_3D_ES_90_links"
|
||||
))
|
||||
|
||||
;; Gruppe 2+3: Boegen sammeln
|
||||
(setq boegen-auf-liste '())
|
||||
(setq boegen-ab-liste '())
|
||||
(foreach w '(3 6 9 12 15 18 21 27 33 39 45 51)
|
||||
(setq bname (strcat "Vario_Bogen_auf_" (itoa w) grad-zeichen))
|
||||
(if (tblsearch "BLOCK" bname)
|
||||
(setq boegen-auf-liste (append boegen-auf-liste (list bname)))
|
||||
)
|
||||
(setq bname (strcat "Vario_Bogen_ab_" (itoa w) grad-zeichen))
|
||||
(if (tblsearch "BLOCK" bname)
|
||||
(setq boegen-ab-liste (append boegen-ab-liste (list bname)))
|
||||
)
|
||||
)
|
||||
|
||||
(princ "\n================================================================")
|
||||
(princ "\n TEST_KSEINAUS - KS-Ausrichtungstest")
|
||||
(princ (strcat "\n Stationen: " (itoa (length stationen))))
|
||||
(princ (strcat "\n Boegen auf: " (itoa (length boegen-auf-liste))))
|
||||
(princ (strcat "\n Boegen ab: " (itoa (length boegen-ab-liste))))
|
||||
(princ (strcat "\n Z-Offset Boegen: " (rtos z-bogen 2 0) " mm"))
|
||||
(princ "\n================================================================")
|
||||
|
||||
;; Gruppe 1: Stationaere Elemente bei X=0, Z=0
|
||||
(princ "\n\n GRUPPE 1: Stationaere Elemente (Z=0)")
|
||||
(setq reihe-result (kseinaus:test-reihe stationen 0 0 0 y-abstand y-luecke
|
||||
"Stationen" results-list))
|
||||
(setq y-pos (car reihe-result))
|
||||
(setq results-list (cdr reihe-result))
|
||||
|
||||
;; Gruppe 2: Boegen aufwaerts bei X=3000, Z=z-bogen
|
||||
(princ (strcat "\n\n GRUPPE 2: Boegen aufwaerts (Z=" (rtos z-bogen 2 0) ")"))
|
||||
(setq reihe-result (kseinaus:test-reihe boegen-auf-liste 3000 0 z-bogen y-abstand y-luecke
|
||||
"Boegen_Auf" results-list))
|
||||
(setq y-pos (car reihe-result))
|
||||
(setq results-list (cdr reihe-result))
|
||||
|
||||
;; Gruppe 3: Boegen abwaerts bei X=6000, Z=z-bogen
|
||||
(princ (strcat "\n\n GRUPPE 3: Boegen abwaerts (Z=" (rtos z-bogen 2 0) ")"))
|
||||
(setq reihe-result (kseinaus:test-reihe boegen-ab-liste 6000 0 z-bogen y-abstand y-luecke
|
||||
"Boegen_Ab" results-list))
|
||||
(setq y-pos (car reihe-result))
|
||||
(setq results-list (cdr reihe-result))
|
||||
|
||||
;; Zusammenfassung
|
||||
(setq anz-ok 0)
|
||||
(setq anz-fehlt 0)
|
||||
(foreach r results-list
|
||||
(if (vl-string-search "\"OK\"" r)
|
||||
(setq anz-ok (1+ anz-ok))
|
||||
(setq anz-fehlt (1+ anz-fehlt))
|
||||
)
|
||||
)
|
||||
|
||||
(princ "\n================================================================")
|
||||
(princ (strcat "\n Ergebnis: " (itoa anz-ok) " OK, "
|
||||
(itoa anz-fehlt) " KS fehlt"))
|
||||
(princ "\n================================================================")
|
||||
|
||||
;; Ergebnisse in globaler Variable speichern
|
||||
(setq *kseinaus-test-results* (reverse results-list))
|
||||
|
||||
(ssg-end)
|
||||
(princ "\n================================================================")
|
||||
(princ "\n TEST_KSEINAUS abgeschlossen.")
|
||||
(princ "\n================================================================")
|
||||
(princ)
|
||||
)
|
||||
+6
-20
@@ -1,10 +1,11 @@
|
||||
;; ============================================================
|
||||
;; test_omniflo.lsp - Automatischer Integrationstest fuer Omniflo
|
||||
;;
|
||||
;; Laedt Testfaelle aus tests/testdata/omniflo_tests.json,
|
||||
;; Laedt Testfaelle aus tests/testdata/omniflo_tests.json und
|
||||
;; fuegt je einen Bogen und Weiche jeden Typs in verschiedenen
|
||||
;; Hoehen ein (Versatz in x-Richtung je 200mm) und exportiert
|
||||
;; die CSV-Datei nach tests/output/omniflo_export.csv.
|
||||
;; Hoehen ein (Versatz in x-Richtung je 200mm). Ergebnisse werden
|
||||
;; ueber omniflo:export-results nach tests/output/omniflo_results.json
|
||||
;; geschrieben (Aufruf durch SSG_RUN_ALL_TESTS oder manuell).
|
||||
;;
|
||||
;; Voraussetzungen:
|
||||
;; - SSG_LIB geladen (OmniModulInsert.lsp, export.lsp)
|
||||
@@ -104,14 +105,13 @@
|
||||
;; ============================================================
|
||||
;; C:TEST_OMNIFLO
|
||||
;;
|
||||
;; Liest omniflo_tests.json, fuegt alle Testbloecke ein,
|
||||
;; exportiert CSV nach tests/output/.
|
||||
;; Liest omniflo_tests.json und fuegt alle Testbloecke ein.
|
||||
;; ============================================================
|
||||
(defun c:TEST_OMNIFLO (/ tests-json-pfad testfaelle
|
||||
x-val y-val
|
||||
idx eintrag sivasnr hoehe-str drehung-str
|
||||
description insert-pt anzahl-ok anzahl-fehler
|
||||
results-list tests-out-dir old-results)
|
||||
results-list)
|
||||
|
||||
(ssg-start "TEST_OMNIFLO"
|
||||
'(("OSMODE") ("ATTREQ") ("ATTDIA")))
|
||||
@@ -217,24 +217,10 @@
|
||||
;; Ergebnisse in globaler Variable speichern
|
||||
(setq *omniflo-test-results* (reverse results-list))
|
||||
|
||||
;; CSV-Export nach tests/output/
|
||||
(setq tests-out-dir
|
||||
(strcat (getenv "DXFMAKRO") "/tests/output"))
|
||||
(vl-mkdir tests-out-dir)
|
||||
|
||||
(setq old-results (getenv "DXFM_RESULTS"))
|
||||
(setenv "DXFM_RESULTS" tests-out-dir)
|
||||
|
||||
(princ "\n[TEST_OMNIFLO] Starte OMNI_EXPORT -> tests/output/omniflo_export.csv")
|
||||
(c:OMNI_EXPORT)
|
||||
|
||||
(setenv "DXFM_RESULTS" (if old-results old-results ""))
|
||||
|
||||
(ssg-end)
|
||||
|
||||
(princ "\n================================================================")
|
||||
(princ "\n TEST_OMNIFLO abgeschlossen.")
|
||||
(princ (strcat "\n CSV: " tests-out-dir "/omniflo_export.csv"))
|
||||
(princ "\n================================================================")
|
||||
(princ)
|
||||
)
|
||||
|
||||
+58
-95
@@ -6,11 +6,11 @@ test_omniflo.py - Validiert Omniflo Bogen/Weiche Export.
|
||||
Prueft:
|
||||
1. CSV-Inhalt gegen Testdefinitionen (Unit-Test, ohne BricsCAD)
|
||||
2. Merkmale-Werte gegen JSON-Katalog
|
||||
3. Korrekte Anzahl und Reihenfolge der Elemente
|
||||
4. Integrationstest: liest CSV aus BricsCAD-Lauf (falls vorhanden)
|
||||
3. Korrekte Anzahl und Reihenfolge der Elemente inkl. Omniflo-Sum-Zeile
|
||||
4. Integrationstest: liest omniflo_results.json aus BricsCAD-Lauf (falls vorhanden)
|
||||
5. Referenz-Vergleich: CSV-Export aus export_csv.py gegen abgenommene Referenz-CSV
|
||||
"""
|
||||
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
@@ -99,6 +99,9 @@ class TestOmnifloExportUnit:
|
||||
drehung=float(tc["drehung"]),
|
||||
))
|
||||
self.items = process_blocks(self.blocks, self.lookup)
|
||||
# Item-Zeilen ohne die abschliessende "Omniflo Sum"-Zeile
|
||||
self.element_items = [it for it in self.items if it["teileart"] != "Omniflo Sum"]
|
||||
self.sum_item = next((it for it in self.items if it["teileart"] == "Omniflo Sum"), None)
|
||||
|
||||
# CSV in output speichern
|
||||
out_dir = _output_dir()
|
||||
@@ -112,27 +115,40 @@ class TestOmnifloExportUnit:
|
||||
|
||||
def test_item_count_matches_testcases(self):
|
||||
"""Anzahl exportierter Elemente muss Anzahl Testfaelle entsprechen."""
|
||||
assert len(self.items) == len(self.tests), (
|
||||
f"Erwartet {len(self.tests)} Elemente, erhalten {len(self.items)}"
|
||||
assert len(self.element_items) == len(self.tests), (
|
||||
f"Erwartet {len(self.tests)} Elemente, erhalten {len(self.element_items)}"
|
||||
)
|
||||
|
||||
def test_numbering_starts_at_one(self):
|
||||
"""Element-Nummerierung muss bei 1 beginnen."""
|
||||
assert self.items[0]["nr"] == 1, (
|
||||
f"Erstes Element: nr={self.items[0]['nr']}, erwartet 1"
|
||||
assert self.element_items[0]["nr"] == 1, (
|
||||
f"Erstes Element: nr={self.element_items[0]['nr']}, erwartet 1"
|
||||
)
|
||||
|
||||
def test_numbering_is_consecutive(self):
|
||||
"""Element-Nummern muessen lueckenlos aufsteigen."""
|
||||
"""Element-Nummern muessen lueckenlos aufsteigen (inkl. abschliessender Sum-Zeile)."""
|
||||
for i, item in enumerate(self.items):
|
||||
assert item["nr"] == i + 1, (
|
||||
f"Element {i}: nr={item['nr']}, erwartet {i + 1}"
|
||||
)
|
||||
|
||||
def test_sum_zeile_angehaengt(self):
|
||||
"""Nach allen Omniflo-Elementen muss eine 'Omniflo Sum'-Zeile folgen."""
|
||||
assert self.sum_item is not None, "Keine 'Omniflo Sum'-Zeile erzeugt"
|
||||
assert self.sum_item["nr"] == len(self.items)
|
||||
assert self.sum_item["teileid"] == "autogenerated_of_json"
|
||||
m = self.sum_item["merkmale"]
|
||||
bogen_tests = [tc for tc in self.tests if tc["type"] == "bogen"]
|
||||
weiche_tests = [tc for tc in self.tests if tc["type"] == "weiche"]
|
||||
assert m["Anzahl Bögen"] == len(bogen_tests)
|
||||
assert (m["Anzahl Weichengrundkörper"] + m["Anzahl Einwegweichen"]
|
||||
+ m["Anzahl Deltaweichen"] + m["Anzahl Doppelweichen und Dreiwegeweichen"]
|
||||
+ m["Anzahl Sternweichen"]) == len(weiche_tests)
|
||||
|
||||
def test_bogen_teileart(self):
|
||||
"""Boegen muessen TeileArt 'Omniflo Kurve' haben."""
|
||||
bogen_tests = [tc for tc in self.tests if tc["type"] == "bogen"]
|
||||
bogen_items = [it for it in self.items if it["teileart"] == "Omniflo Kurve"]
|
||||
bogen_items = [it for it in self.element_items if it["teileart"] == "Omniflo Kurve"]
|
||||
assert len(bogen_items) == len(bogen_tests), (
|
||||
f"Erwartet {len(bogen_tests)} Boegen, erhalten {len(bogen_items)}"
|
||||
)
|
||||
@@ -140,7 +156,7 @@ class TestOmnifloExportUnit:
|
||||
def test_weiche_teileart(self):
|
||||
"""Weichen muessen TeileArt 'Omniflo Weiche' haben."""
|
||||
weiche_tests = [tc for tc in self.tests if tc["type"] == "weiche"]
|
||||
weiche_items = [it for it in self.items if it["teileart"] == "Omniflo Weiche"]
|
||||
weiche_items = [it for it in self.element_items if it["teileart"] == "Omniflo Weiche"]
|
||||
assert len(weiche_items) == len(weiche_tests), (
|
||||
f"Erwartet {len(weiche_tests)} Weichen, erhalten {len(weiche_items)}"
|
||||
)
|
||||
@@ -148,7 +164,7 @@ class TestOmnifloExportUnit:
|
||||
def test_bogen_merkmale_felder(self):
|
||||
"""Boegen-Merkmale muessen alle Pflichtfelder enthalten."""
|
||||
required = {"Kurvenwinkel", "Radius", "Höhe", "Drehung", "SivasNummer"}
|
||||
for item in self.items:
|
||||
for item in self.element_items:
|
||||
if item["teileart"] != "Omniflo Kurve":
|
||||
continue
|
||||
merkmale = item["merkmale"]
|
||||
@@ -161,7 +177,7 @@ class TestOmnifloExportUnit:
|
||||
"""Weichen-Merkmale muessen alle Pflichtfelder enthalten."""
|
||||
required = {"Weichentyp", "Richtung", "Weichenwinkel",
|
||||
"Höhe", "Drehung", "Antrieb Kurve", "SivasNummer"}
|
||||
for item in self.items:
|
||||
for item in self.element_items:
|
||||
if item["teileart"] != "Omniflo Weiche":
|
||||
continue
|
||||
merkmale = item["merkmale"]
|
||||
@@ -174,7 +190,7 @@ class TestOmnifloExportUnit:
|
||||
"""Bogen-Merkmale muessen mit JSON-Katalog uebereinstimmen."""
|
||||
for tc, item in zip(
|
||||
[t for t in self.tests if t["type"] == "bogen"],
|
||||
[it for it in self.items if it["teileart"] == "Omniflo Kurve"]
|
||||
[it for it in self.element_items if it["teileart"] == "Omniflo Kurve"]
|
||||
):
|
||||
sivasnr = tc["sivasnr"]
|
||||
assert sivasnr in self.lookup, f"SivasNr {sivasnr} nicht im Katalog"
|
||||
@@ -200,7 +216,7 @@ class TestOmnifloExportUnit:
|
||||
"""Weichen-Merkmale muessen mit JSON-Katalog uebereinstimmen."""
|
||||
for tc, item in zip(
|
||||
[t for t in self.tests if t["type"] == "weiche"],
|
||||
[it for it in self.items if it["teileart"] == "Omniflo Weiche"]
|
||||
[it for it in self.element_items if it["teileart"] == "Omniflo Weiche"]
|
||||
):
|
||||
sivasnr = tc["sivasnr"]
|
||||
assert sivasnr in self.lookup, f"SivasNr {sivasnr} nicht im Katalog"
|
||||
@@ -229,7 +245,7 @@ class TestOmnifloExportUnit:
|
||||
|
||||
def test_bogen_hoehe_als_string(self):
|
||||
"""Bogen-Hoehe muss als String vorliegen (z.B. '2000', nicht 2000)."""
|
||||
for item in self.items:
|
||||
for item in self.element_items:
|
||||
if item["teileart"] != "Omniflo Kurve":
|
||||
continue
|
||||
assert isinstance(item["merkmale"]["Höhe"], str), (
|
||||
@@ -239,7 +255,7 @@ class TestOmnifloExportUnit:
|
||||
|
||||
def test_weiche_hoehe_als_string(self):
|
||||
"""Weichen-Hoehe muss als String vorliegen."""
|
||||
for item in self.items:
|
||||
for item in self.element_items:
|
||||
if item["teileart"] != "Omniflo Weiche":
|
||||
continue
|
||||
assert isinstance(item["merkmale"]["Höhe"], str), (
|
||||
@@ -248,7 +264,7 @@ class TestOmnifloExportUnit:
|
||||
|
||||
def test_kurvenwinkel_als_float(self):
|
||||
"""Kurvenwinkel muss als Float vorliegen (90.0, nicht 90)."""
|
||||
for item in self.items:
|
||||
for item in self.element_items:
|
||||
if item["teileart"] == "Omniflo Kurve":
|
||||
assert isinstance(item["merkmale"]["Kurvenwinkel"], float), (
|
||||
f"{item['bezeichnung']}: Kurvenwinkel muss float sein"
|
||||
@@ -273,90 +289,37 @@ class TestOmnifloExportUnit:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Integrations-Tests: CSV aus BricsCAD-Lauf validieren
|
||||
# Integrations-Tests: Omniflo-Ergebnisse aus BricsCAD-Lauf validieren
|
||||
# (tests/output/omniflo_results.json, geschrieben von omniflo:export-results)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestOmnifloExportCSV:
|
||||
"""Validiert CSV-Datei aus dem BricsCAD-Lauf (c:TEST_OMNIFLO_EXPORT)."""
|
||||
class TestOmnifloResults:
|
||||
"""Validiert omniflo_results.json aus dem BricsCAD-Lauf (c:TEST_OMNIFLO)."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
csv_path = os.path.join(_output_dir(), "omniflo_export.csv")
|
||||
if not os.path.exists(csv_path):
|
||||
pytest.skip(
|
||||
"omniflo_export.csv nicht vorhanden - "
|
||||
"TEST_OMNIFLO_EXPORT in BricsCAD ausfuehren"
|
||||
)
|
||||
self.csv_path = csv_path
|
||||
self.rows = []
|
||||
with open(csv_path, "r", encoding="utf-8") as f:
|
||||
reader = csv.reader(f, delimiter=";")
|
||||
self.header = next(reader)
|
||||
for row in reader:
|
||||
self.rows.append(row)
|
||||
def test_all_tests_executed(self, omniflo_testdata, omniflo_results):
|
||||
"""Alle definierten Testfaelle muessen ausgefuehrt worden sein."""
|
||||
expected_sivas = {t["sivasnr"] for t in omniflo_testdata}
|
||||
result_sivas = {r["sivasnr"] for r in omniflo_results}
|
||||
missing = expected_sivas - result_sivas
|
||||
assert not missing, f"Nicht ausgefuehrte Tests: {missing}"
|
||||
|
||||
def test_header_felder(self):
|
||||
"""CSV-Header muss korrekte Spaltenbezeichnungen haben."""
|
||||
assert self.header[0] == "Elementnummer"
|
||||
assert self.header[1] == "TeileArt"
|
||||
assert self.header[2] == "TeileId"
|
||||
assert self.header[3] == "Bezeichnung"
|
||||
assert self.header[4] == "Anzahl"
|
||||
assert self.header[5] == "Merkmale"
|
||||
|
||||
def test_mindest_anzahl_zeilen(self):
|
||||
"""CSV muss mindestens so viele Zeilen haben wie Testfaelle."""
|
||||
tests = _load_omniflo_tests()
|
||||
assert len(self.rows) >= len(tests), (
|
||||
f"CSV hat {len(self.rows)} Zeilen, "
|
||||
f"erwartet mind. {len(tests)}"
|
||||
)
|
||||
|
||||
def test_anzahl_spalte_ist_eins(self):
|
||||
"""Anzahl-Spalte muss fuer alle Zeilen '1' sein."""
|
||||
for i, row in enumerate(self.rows):
|
||||
if len(row) < 5:
|
||||
continue
|
||||
assert row[4] == "1", (
|
||||
f"Zeile {i + 2}: Anzahl={row[4]}, erwartet 1"
|
||||
def test_all_executed_successfully(self, omniflo_results):
|
||||
"""Alle Ergebnisse muessen Status 'OK' haben."""
|
||||
for result in omniflo_results:
|
||||
assert result["status"] == "OK", (
|
||||
f'{result["test_id"]} ({result["sivasnr"]}): Status={result["status"]}'
|
||||
)
|
||||
|
||||
def test_merkmale_sind_valides_json(self):
|
||||
"""Merkmale-Spalte muss valides JSON enthalten."""
|
||||
for i, row in enumerate(self.rows):
|
||||
if len(row) < 6:
|
||||
def test_hoehe_matches(self, omniflo_testdata, omniflo_results):
|
||||
"""Eingefuegte Hoehe muss dem Testfall entsprechen."""
|
||||
results_by_sivas = {r["sivasnr"]: r for r in omniflo_results}
|
||||
for test in omniflo_testdata:
|
||||
result = results_by_sivas.get(test["sivasnr"])
|
||||
if not result:
|
||||
continue
|
||||
try:
|
||||
json.loads(row[5])
|
||||
except json.JSONDecodeError as e:
|
||||
pytest.fail(
|
||||
f"Zeile {i + 2}: Merkmale ist kein valides JSON: {e}\n"
|
||||
f" Inhalt: {row[5][:100]}"
|
||||
)
|
||||
|
||||
def test_bogen_merkmale_felder_in_csv(self):
|
||||
"""Bogen-Zeilen muessen Pflichtfelder in Merkmale haben."""
|
||||
required = {"Kurvenwinkel", "Radius", "Höhe", "Drehung", "SivasNummer"}
|
||||
for i, row in enumerate(self.rows):
|
||||
if len(row) < 6 or row[1].strip('"') != "Omniflo Kurve":
|
||||
continue
|
||||
merkmale = json.loads(row[5])
|
||||
missing = required - set(merkmale.keys())
|
||||
assert not missing, (
|
||||
f"Zeile {i + 2}: Fehlende Bogen-Felder: {missing}"
|
||||
)
|
||||
|
||||
def test_weiche_merkmale_felder_in_csv(self):
|
||||
"""Weichen-Zeilen muessen Pflichtfelder in Merkmale haben."""
|
||||
required = {"Weichentyp", "Richtung", "Weichenwinkel",
|
||||
"Höhe", "Drehung", "Antrieb Kurve", "SivasNummer"}
|
||||
for i, row in enumerate(self.rows):
|
||||
if len(row) < 6 or row[1].strip('"') != "Omniflo Weiche":
|
||||
continue
|
||||
merkmale = json.loads(row[5])
|
||||
missing = required - set(merkmale.keys())
|
||||
assert not missing, (
|
||||
f"Zeile {i + 2}: Fehlende Weichen-Felder: {missing}"
|
||||
assert result["hoehe"] == str(int(test["hoehe"])), (
|
||||
f'{test["sivasnr"]}: Hoehe erwartet={test["hoehe"]}, '
|
||||
f'ist={result["hoehe"]}'
|
||||
)
|
||||
|
||||
|
||||
@@ -377,7 +340,7 @@ def _normalize_row(line):
|
||||
|
||||
|
||||
class TestOmnifloReferenceCSV:
|
||||
"""Vergleicht den erzeugten Unit-Test-CSV-Export mit einer Referenzdatei."""
|
||||
"""Vergleicht den von export_csv.py erzeugten CSV-Export mit einer Referenzdatei."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
|
||||
+188
-29
@@ -20,8 +20,50 @@
|
||||
;; Aufruf in BricsCAD:
|
||||
;; (load (strcat (getenv "DXFMAKRO") "/tests/test_run_all.lsp"))
|
||||
;; SSG_RUN_ALL_TESTS
|
||||
;;
|
||||
;; Jedes Testmodul laeuft in einer eigenen, neuen Zeichnung (statt die
|
||||
;; aktuelle zu leeren/wiederzuverwenden). SSG_RUN_ALL_TESTS fragt zu
|
||||
;; Beginn interaktiv nach dem gewuenschten Modus:
|
||||
;; Kontroll-Modus (Default): Zeichnung wird gespeichert, auf den
|
||||
;; Inhalt gezoomt und bleibt als Tab offen - kein CSV/Sivas-Export.
|
||||
;; Am Ende sind alle Testzeichnungen gleichzeitig offen und koennen
|
||||
;; direkt begutachtet werden. CSV/Sivas-Export danach bei Bedarf
|
||||
;; manuell ueber TEST_EXPORT_ALL (tests/test_export_all.lsp).
|
||||
;; Automatik-Modus: Zeichnung wird gespeichert, per EXPORTCSV/
|
||||
;; EXPORTSIVAS exportiert und danach sofort wieder geschlossen -
|
||||
;; kompletter Lauf ohne manuellen Zusatzschritt, aber ohne
|
||||
;; gleichzeitige visuelle Kontrolle aller Zeichnungen am Ende.
|
||||
;; ============================================================
|
||||
|
||||
(vl-load-com)
|
||||
|
||||
|
||||
;; --- Auf Fertigstellung einer asynchron geschriebenen Datei warten ---
|
||||
;; csv:run-export startet Python per startapp (fire-and-forget). Pollt
|
||||
;; bis die Datei existiert und ihre Groesse ueber ein Intervall stabil
|
||||
;; bleibt, oder bis timeout-sec erreicht ist. Rueckgabe: T bei Erfolg.
|
||||
;; (Analog zu export_all:wait-for-file aus tests/test_export_all.lsp,
|
||||
;; hier lokal, da hier direkt auf der offenen Zeichnung exportiert wird
|
||||
;; statt auf einer bereits gespeicherten Datei.)
|
||||
(defun testrun:wait-for-file (path timeout-sec / elapsed size1 size2 done)
|
||||
(setq elapsed 0.0 done nil)
|
||||
(while (and (not done) (< elapsed timeout-sec))
|
||||
(if (findfile path)
|
||||
(progn
|
||||
(setq size1 (vl-file-size path))
|
||||
(command "_.DELAY" 400)
|
||||
(setq size2 (vl-file-size path))
|
||||
(if (and size1 size2 (= size1 size2) (> size1 0))
|
||||
(setq done T)
|
||||
)
|
||||
)
|
||||
(command "_.DELAY" 400)
|
||||
)
|
||||
(setq elapsed (+ elapsed 0.4))
|
||||
)
|
||||
done
|
||||
)
|
||||
|
||||
|
||||
;; --- Hilfsfunktion: String-Wert aus JSON-Objekt-Zeile lesen ---
|
||||
;; Sucht "key": "value" in obj-str und gibt value zurueck.
|
||||
@@ -99,14 +141,17 @@
|
||||
)
|
||||
|
||||
|
||||
(defun c:SSG_RUN_ALL_TESTS ( / tests-pfad tests-out-dir
|
||||
(defun c:SSG_RUN_ALL_TESTS ( / tests-pfad tests-out-dir app auto-export
|
||||
idx anz entry name save-fmt module
|
||||
namen befehl datei export-fn
|
||||
out-path old-json test-result)
|
||||
namen befehl datei export-fn new-doc
|
||||
out-path dwg-pfad dxf-pfad old-json test-result
|
||||
old-filenames csv-name sivas-name csv-pfad
|
||||
sivas-pfad csv-ok sivas-ok)
|
||||
|
||||
(setq tests-pfad (strcat (getenv "DXFMAKRO") "/tests"))
|
||||
(setq tests-out-dir (strcat tests-pfad "/output"))
|
||||
(vl-mkdir tests-out-dir)
|
||||
(setq app (vlax-get-acad-object))
|
||||
|
||||
;; Testliste aus alltests.json laden
|
||||
(setq *test-module* (alltests:load tests-pfad))
|
||||
@@ -120,15 +165,29 @@
|
||||
(setq anz (length *test-module*))
|
||||
(setq idx 0)
|
||||
|
||||
;; Modus abfragen: Kontrolle (Zeichnungen bleiben offen, kein Export)
|
||||
;; oder Automatik (Speichern + CSV/Sivas-Export + Schliessen je Test).
|
||||
;; ssg-ques gibt T zurueck wenn "No" gewaehlt wurde - deshalb "not".
|
||||
;; Default = Nein -> Kontroll-Modus (nicht-destruktiver Default).
|
||||
(setq auto-export
|
||||
(not (ssg-ques
|
||||
"Zeichnungen automatisch speichern, exportieren (CSV/Sivas) und schliessen"
|
||||
nil))
|
||||
)
|
||||
|
||||
(princ "\n")
|
||||
(princ "\n================================================================")
|
||||
(princ "\n SSG_RUN_ALL_TESTS")
|
||||
(princ (strcat "\n " (itoa anz) " Testmodule (aus alltests.json)"))
|
||||
(princ (if auto-export
|
||||
"\n Modus: Automatik (speichern + CSV/Sivas-Export + schliessen)"
|
||||
"\n Modus: Kontrolle (Zeichnungen bleiben offen, kein Export)"
|
||||
))
|
||||
(princ "\n================================================================")
|
||||
|
||||
;; Tests laden und ausfuehren
|
||||
;; Vor jedem Test werden alle Entities geloescht, damit jeder
|
||||
;; Testlauf eine eigene saubere Zeichnung erzeugt.
|
||||
;; Jeder Test bekommt eine eigene neue Zeichnung (statt die aktuelle
|
||||
;; zu leeren/wiederzuverwenden) und bleibt danach als Tab offen.
|
||||
(foreach entry *test-module*
|
||||
(setq idx (1+ idx))
|
||||
(setq name (cdr (assoc "name" entry)))
|
||||
@@ -158,19 +217,18 @@
|
||||
(princ " [kein Speichern]")
|
||||
)
|
||||
|
||||
;; Zeichnung leeren fuer sauberen Testlauf
|
||||
;; Frozen Layer werden von _.ERASE _ALL ignoriert - deshalb
|
||||
;; erst alle Layer auftauen, entsperren und einschalten.
|
||||
(command "_.LAYER" "_Thaw" "*" "_Unlock" "*" "_On" "*" "")
|
||||
(if (ssget "_X")
|
||||
(command "_.ERASE" "_ALL" "")
|
||||
)
|
||||
;; PURGE mehrfach, da verschachtelte Block-Referenzen
|
||||
;; erst nach mehreren Durchlaeufen entfernt werden
|
||||
(repeat 5
|
||||
(command "_.PURGE" "_ALL" "" "_N")
|
||||
)
|
||||
;; Block-Library-Flag zuruecksetzen
|
||||
;; Neue, leere Zeichnung fuer diesen Test anlegen und aktivieren
|
||||
(setq new-doc (vla-add (vla-get-documents app)))
|
||||
(vla-put-activedocument app new-doc)
|
||||
;; vf_core.lsp/Gefaellestrecke.lsp setzen die globalen Variablen "doc"
|
||||
;; und "modelspace" nur EINMAL, beim ersten Laden des Moduls (Top-Level-
|
||||
;; Code, kein erneuter Aufruf durch ssg-ensure). Ohne diese Aktualisierung
|
||||
;; wuerden VarioFoerderer/Gefaellestrecke weiterhin in das allererste
|
||||
;; Dokument der Sitzung zeichnen statt in die hier neu angelegte
|
||||
;; Zeichnung - die neuen Tabs blieben leer.
|
||||
(setq doc new-doc)
|
||||
(setq modelspace (vla-get-modelspace new-doc))
|
||||
;; Block-Library-Flag zuruecksetzen (Bloecke sind pro Zeichnung neu zu laden)
|
||||
(setq *lib-initialized* nil)
|
||||
|
||||
(if (findfile datei)
|
||||
@@ -193,19 +251,117 @@
|
||||
;; JSON-Export (immer)
|
||||
(apply export-fn (list tests-out-dir))
|
||||
|
||||
;; Datei speichern je nach save-fmt aus alltests.json
|
||||
(setq out-path (strcat tests-out-dir "/" name "_tests"))
|
||||
(cond
|
||||
((and save-fmt (= (strcase save-fmt) "DXF"))
|
||||
(command "_.DXFOUT" out-path 6)
|
||||
(princ (strcat "\n DXF gespeichert: " out-path ".dxf"))
|
||||
|
||||
(if auto-export
|
||||
|
||||
;; ==========================================================
|
||||
;; AUTOMATIK-MODUS: speichern (vereinfacht, kein Tab-Naming-
|
||||
;; Trick noetig) + CSV/Sivas-Export + Zeichnung schliessen.
|
||||
;; ==========================================================
|
||||
(progn
|
||||
(cond
|
||||
((and save-fmt (= (strcase save-fmt) "DWG"))
|
||||
(setq dwg-pfad (strcat out-path ".dwg"))
|
||||
(vla-saveas new-doc dwg-pfad)
|
||||
(princ (strcat "\n DWG gespeichert: " dwg-pfad))
|
||||
)
|
||||
((and save-fmt (= (strcase save-fmt) "DXF"))
|
||||
(command "_.DXFOUT" out-path 6)
|
||||
(princ (strcat "\n DXF gespeichert: " out-path ".dxf"))
|
||||
)
|
||||
(T
|
||||
(princ "\n (kein Speichern der Zeichnung)")
|
||||
)
|
||||
)
|
||||
|
||||
;; CSV/Sivas-Export direkt auf der offenen Zeichnung.
|
||||
;; *export-filenames* temporaer umbiegen, damit jeder Test
|
||||
;; seine eigenen CSV-Ergebnisse bekommt (statt der fixen
|
||||
;; Standardnamen "export.csv"/"export_sivas.csv").
|
||||
(setq csv-name (strcat name "_tests_export.csv"))
|
||||
(setq sivas-name (strcat name "_tests_sivas.csv"))
|
||||
(setq csv-pfad (strcat tests-out-dir "/" csv-name))
|
||||
(setq sivas-pfad (strcat tests-out-dir "/" sivas-name))
|
||||
(if (findfile csv-pfad) (vl-file-delete csv-pfad))
|
||||
(if (findfile sivas-pfad) (vl-file-delete sivas-pfad))
|
||||
|
||||
(setq old-filenames *export-filenames*)
|
||||
(setq *export-filenames*
|
||||
(list
|
||||
(cons "raw_json_datei" (cdr (assoc "raw_json_datei" old-filenames)))
|
||||
(cons "sivas_script" (cdr (assoc "sivas_script" old-filenames)))
|
||||
(cons "sivas_csv" sivas-name)
|
||||
(cons "csv_script" (cdr (assoc "csv_script" old-filenames)))
|
||||
(cons "csv_csv" csv-name)
|
||||
)
|
||||
)
|
||||
|
||||
(princ (strcat "\n CSV-Export -> " csv-name))
|
||||
(c:EXPORTCSV)
|
||||
(setq csv-ok (testrun:wait-for-file csv-pfad 30.0))
|
||||
(princ (if csv-ok "\n CSV OK" "\n CSV TIMEOUT/FEHLER"))
|
||||
|
||||
(princ (strcat "\n Sivas-Export -> " sivas-name))
|
||||
(c:EXPORTSIVAS)
|
||||
(setq sivas-ok (testrun:wait-for-file sivas-pfad 30.0))
|
||||
(princ (if sivas-ok "\n Sivas OK" "\n Sivas TIMEOUT/FEHLER"))
|
||||
|
||||
(setq *export-filenames* old-filenames)
|
||||
|
||||
;; Zeichnung schliessen: der urspruenglich schon offene Tab
|
||||
;; bleibt unangetastet, BricsCAD erzeugt daher kein neues
|
||||
;; Blank-Dokument samt Modul-/Menue-Reload.
|
||||
(vla-close new-doc :vlax-false)
|
||||
)
|
||||
((and save-fmt (= (strcase save-fmt) "DWG"))
|
||||
(command "_.-SAVEAS" "R2013" (strcat out-path ".dwg"))
|
||||
(princ (strcat "\n DWG gespeichert: " out-path ".dwg"))
|
||||
)
|
||||
(T
|
||||
(princ "\n (kein Speichern der Zeichnung)")
|
||||
|
||||
;; ==========================================================
|
||||
;; KONTROLL-MODUS: speichern, auf Inhalt zoomen, Tab offen
|
||||
;; lassen (bisheriges Verhalten).
|
||||
;; ==========================================================
|
||||
(progn
|
||||
(cond
|
||||
((and save-fmt (= (strcase save-fmt) "DWG"))
|
||||
;; Direkt als DWG speichern - Tab bleibt offen und benannt.
|
||||
;; Ueber ActiveX statt "_.-SAVEAS" "R2013" ...: Das Versions-
|
||||
;; Keyword "R2013" wird von BricsCADs Kommandozeilen-SAVEAS
|
||||
;; nicht zuverlaessig erkannt, wodurch der Befehl auf eine
|
||||
;; ungueltige Eingabe wartet und nie eine Datei schreibt
|
||||
;; (nachfolgende LISP-Eingaben landen dann im haengenden
|
||||
;; Prompt). vla-saveas ist deterministisch.
|
||||
(setq dwg-pfad (strcat out-path ".dwg"))
|
||||
(vla-saveas new-doc dwg-pfad)
|
||||
(princ (strcat "\n DWG gespeichert: " dwg-pfad))
|
||||
)
|
||||
((and save-fmt (= (strcase save-fmt) "DXF"))
|
||||
;; Erst als DWG speichern (nur fuer den Tab-Namen) und DXF
|
||||
;; exportieren. Danach die Zeichnung schliessen (entsperrt die
|
||||
;; DWG-Datei), die temporaere DWG loeschen und stattdessen die
|
||||
;; eigentlich angeforderte DXF-Datei erneut oeffnen - der Tab
|
||||
;; ist dann korrekt an die DXF gebunden, keine ungewollte DWG
|
||||
;; bleibt liegen.
|
||||
(setq dwg-pfad (strcat out-path ".dwg"))
|
||||
(setq dxf-pfad (strcat out-path ".dxf"))
|
||||
(vla-saveas new-doc dwg-pfad)
|
||||
(command "_.DXFOUT" out-path 6)
|
||||
(vla-close new-doc :vlax-false)
|
||||
(if (findfile dwg-pfad) (vl-file-delete dwg-pfad))
|
||||
(setq new-doc (vla-open (vla-get-documents app) dxf-pfad))
|
||||
(vla-put-activedocument app new-doc)
|
||||
(setq doc new-doc)
|
||||
(setq modelspace (vla-get-modelspace new-doc))
|
||||
(princ (strcat "\n DXF gespeichert: " dxf-pfad))
|
||||
)
|
||||
(T
|
||||
;; Kein Speichern angefordert: keine Datei erzeugen, Tab
|
||||
;; bleibt beim generischen Namen ("ZeichnungN").
|
||||
(princ "\n (kein Speichern der Zeichnung)")
|
||||
)
|
||||
)
|
||||
|
||||
;; Ansicht auf den gesamten Inhalt zoomen, Tab bleibt offen
|
||||
;; (naechster Test startet in einer neuen Zeichnung).
|
||||
(command "_.ZOOM" "_Extents")
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -216,6 +372,9 @@
|
||||
(princ "\n\n================================================================")
|
||||
(princ "\n SSG_RUN_ALL_TESTS abgeschlossen.")
|
||||
(princ (strcat "\n Ergebnisse in: " (getenv "DXFMAKRO") "/tests/output/"))
|
||||
(if (not auto-export)
|
||||
(princ "\n CSV/Sivas-Export: TEST_EXPORT_ALL manuell aufrufen (Menue: Tests -> Export CSV/Sivas ALL)")
|
||||
)
|
||||
(princ "\n================================================================")
|
||||
(princ)
|
||||
)
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"y_abstand": 400
|
||||
"y_abstand": 600
|
||||
},
|
||||
{
|
||||
"test_id": "VF_Auf_0",
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
[
|
||||
{
|
||||
"deltaL_mm": 10000,
|
||||
"winkel_grad": 3.0,
|
||||
"z_start_mm": 5000
|
||||
},
|
||||
{
|
||||
"test_id": "M1_hz000",
|
||||
"modus": 1,
|
||||
"hz_grad": 0.0,
|
||||
"start_mm": [0, 0, 5000],
|
||||
"as_seite": "links",
|
||||
"es_seite": "links"
|
||||
},
|
||||
{
|
||||
"test_id": "M1_hz090",
|
||||
"modus": 1,
|
||||
"hz_grad": 90.0,
|
||||
"start_mm": [15000, 0, 5000],
|
||||
"as_seite": "links",
|
||||
"es_seite": "links"
|
||||
},
|
||||
{
|
||||
"test_id": "M1_hz180",
|
||||
"modus": 1,
|
||||
"hz_grad": 180.0,
|
||||
"start_mm": [0, -15000, 5000],
|
||||
"as_seite": "links",
|
||||
"es_seite": "links"
|
||||
},
|
||||
{
|
||||
"test_id": "M1_hz270",
|
||||
"modus": 1,
|
||||
"hz_grad": 270.0,
|
||||
"start_mm": [15000, -15000, 5000],
|
||||
"as_seite": "links",
|
||||
"es_seite": "links"
|
||||
},
|
||||
{
|
||||
"test_id": "M2_hz000",
|
||||
"modus": 2,
|
||||
"hz_grad": 0.0,
|
||||
"start_mm": [0, -35000, 5000],
|
||||
"linie_start_mm": [0, -35000, 0],
|
||||
"linie_ende_mm": [10000, -35000, 0],
|
||||
"as_seite": "links",
|
||||
"es_seite": "links"
|
||||
},
|
||||
{
|
||||
"test_id": "M3_hz270",
|
||||
"modus": 3,
|
||||
"richtung": "Sued",
|
||||
"start_mm": [0, -50000, 5000],
|
||||
"deltaH_quelle": "M2_hz000",
|
||||
"deltaL_quelle": "M2_hz000",
|
||||
"as_seite": "links",
|
||||
"es_seite": "links"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user