diff --git a/Lisp/export.lsp b/Lisp/export.lsp index d87de21..13f5b32 100644 --- a/Lisp/export.lsp +++ b/Lisp/export.lsp @@ -5,10 +5,10 @@ ;; - JSON-Sammler (csv:collect-export-blocks, csv:run-export) ;; - Python-basierter Export: EXPORTSIVAS, EXPORTCSV ;; -;; Alle Blockname-Muster und Datei-/Skriptnamen dieses Moduls sind -;; zentral in cfg/export.cfg (INI-Style, Abschnitte [blockpattern] -;; und [filenames]) deklariert und werden beim Laden des Moduls -;; einmalig eingelesen (export:load-cfg). +;; Alle Blockname-Muster dieses Moduls sind zentral in cfg/export.cfg +;; (INI-Style, Abschnitt [blockpattern]) deklariert und werden beim +;; Laden des Moduls einmalig eingelesen (export:load-cfg). +;; Datei-/Skriptnamen sind fest im Code hinterlegt (*export-filenames*). ;; ============================================================ @@ -25,7 +25,7 @@ ) ;; --- Einzelwert aus *export-cfg* lesen, mit Fallback --- -;; section = "blockpattern" oder "filenames" +;; section = "blockpattern" (defun export:cfg (section key default) (ssg-ini-get *export-cfg* section key default) ) @@ -42,6 +42,23 @@ (export:load-cfg) +;; --- Feste Datei-/Skriptnamen dieses Moduls (keine Config, direkt im Code) --- +(setq *export-filenames* + (list + (cons "raw_json_datei" "export_raw.json") + (cons "sivas_script" "export_sivas.py") + (cons "sivas_csv" "export_sivas.csv") + (cons "csv_script" "export_csv.py") + (cons "csv_csv" "export.csv") + ) +) + +;; --- Dateinamen aus *export-filenames* lesen --- +(defun export:filename (key) + (cdr (assoc key *export-filenames*)) +) + + ;; --- String fuer JSON escapen (Anfuehrungszeichen/Backslash) --- ;; Konvertiert nicht-String-Werte (z.B. T, nil, Zahlen) defensiv in String. (defun csv:json-escape (s) @@ -94,10 +111,7 @@ (setq rotation (cdr (assoc 50 ed))) (setq handle (cdr (assoc 5 ed))) (if (null rotation) (setq rotation 0.0)) - (if (cdr (assoc 66 ed)) - (setq attribs (csv:read-attribs ename)) - (setq attribs "{}") - ) + (setq attribs (csv:read-attribs ename)) (strcat " {\"block_name\":\"" (csv:json-escape blk-name) "\"" ",\"layer\":\"" (csv:json-escape layer) "\"" @@ -135,7 +149,10 @@ (wcmatch bname (export:pattern "pattern_strecke" "Vario*,Staustrecke*,AUS_Element*,EIN_Element*,VF_*,GF_*")) ;; Rein numerische Namen = Omniflo SivasNr (Boegen/Weichen) - (wcmatch bname (export:pattern "pattern_numerisch" "#*")) + ;; Rein LISP-intern (Python klassifiziert Omniflo stattdessen + ;; ueber exakten Katalog-Match), daher fest im Code statt in + ;; cfg/export.cfg. + (wcmatch bname "#*") ) (ssadd ename ss-out) ) @@ -200,7 +217,7 @@ ;; JSON-Datei zusammenbauen (setq out-pfad (strcat (getenv "DXFM_RESULTS") "/" - (export:cfg "filenames" "raw_json_datei" "export_raw.json"))) + (export:filename "raw_json_datei"))) (setq fh (open out-pfad "w")) (if (null fh) (progn @@ -228,7 +245,7 @@ (if (findfile py-skript) (progn (setq ergebnis-pfad (strcat (getenv "DXFM_RESULTS") "/" csv-name)) - (setq cmd (strcat (export:cfg "filenames" "python_exe" "python") " \"" py-skript "\"" + (setq cmd (strcat "python \"" py-skript "\"" " \"" out-pfad "\"" " \"" (getenv "DXFM_DATA") "\"" " \"" ergebnis-pfad "\"")) @@ -249,15 +266,15 @@ ;; --- EXPORTSIVAS: Sivas-spezifischer CSV-Export mit Summierungszeilen --- (defun c:EXPORTSIVAS () (csv:run-export "EXPORTSIVAS" - (export:cfg "filenames" "sivas_script" "export_sivas.py") - (export:cfg "filenames" "sivas_csv" "export_sivas.csv")) + (export:filename "sivas_script") + (export:filename "sivas_csv")) ) ;; --- EXPORTCSV: Einfache Item-Liste aller Bloecke (ohne Summierung) --- (defun c:EXPORTCSV () (csv:run-export "EXPORTCSV" - (export:cfg "filenames" "csv_script" "export_csv.py") - (export:cfg "filenames" "csv_csv" "export.csv")) + (export:filename "csv_script") + (export:filename "csv_csv")) ) diff --git a/cfg/export.cfg b/cfg/export.cfg index 7758098..c36b582 100644 --- a/cfg/export.cfg +++ b/cfg/export.cfg @@ -1,26 +1,29 @@ # ============================================================ -# export.cfg - Konfiguration fuer Lisp/export.lsp +# export.cfg - Konfiguration fuer Lisp/export.lsp UND lib/export_csv.py, +# lib/export_sivas.py (gemeinsame Blockname-Muster, siehe +# lib/export_blockpatterns.py) # # Format: INI-Style # [ABSCHNITT] # Key = Wert (einzelner Wert) -# Key = Wert1, Wert2, Wert3 (Musterliste fuer wcmatch, kommagetrennt) +# Key = Wert1, Wert2, Wert3 (Musterliste fuer wcmatch/fnmatch, kommagetrennt) # # Zeilen mit # sind Kommentare. Leerzeilen werden ignoriert. +# +# pattern_kreisel/pattern_strecke sind fuer Lisp's Block-SAMMLUNG grob +# gehalten (nur "gehoert das ueberhaupt zum Export"). Die uebrigen +# pattern_eckrad/pattern_variofoerderer/pattern_gefaellestrecke/ +# pattern_strecke_module-Keys sind Teilmengen davon fuer die feinere +# TeileArt-KLASSIFIKATION in Python (eigene Zeile je Teileart) - +# Aenderungen an Blocknamen bitte in beiden Stellen nachziehen. # ============================================================ [blockpattern] -pattern_kreisel= KR_*, KREISEL_*, ECKRAD_* -pattern_gerade= AP110*, AP_110* -pattern_strecke= Vario*, Staustrecke*, AUS_Element*, EIN_Element*, VF_*, GF_* -pattern_numerisch= #* -pattern_ks_subblocks= KS_EIN, KS_AUS, K1, K2, K3, K4 - - -[filenames] -raw_json_datei= export_raw.json -python_exe= python -sivas_script= export_sivas.py -sivas_csv= export_sivas.csv -csv_script= export_csv.py -csv_csv= export.csv +pattern_kreisel= KR_*, KREISEL_*, ECKRAD_* +pattern_eckrad= ECKRAD_* +pattern_gerade= AP110*, AP_110* +pattern_strecke= Vario*, Staustrecke*, AUS_Element*, EIN_Element*, VF_*, GF_* +pattern_variofoerderer= VF_* +pattern_gefaellestrecke= GF_* +pattern_strecke_module= Vario*, Staustrecke*, AUS_Element*, EIN_Element* +pattern_ks_subblocks= KS_EIN, KS_AUS, K1, K2, K3, K4 diff --git a/doc/LispTests.md b/doc/LispTests.md index 5f5b4d2..c610364 100644 --- a/doc/LispTests.md +++ b/doc/LispTests.md @@ -5,7 +5,12 @@ Die SSG_LIB AutoLISP-Makros werden ueber einen zweistufigen Hybrid-Ansatz getestet: 1. **AutoLISP** (in BricsCAD): Liest JSON-Testfaelle, fuehrt die Funktionen aus, - schreibt Ergebnisse als JSON und DXF/DWG. + schreibt Ergebnisse als JSON und DXF/DWG. Jedes Testmodul laeuft dabei in einer + eigenen, neuen Zeichnung (kein Wiederverwenden/Leeren der aktuellen). + `SSG_RUN_ALL_TESTS` fragt beim Start interaktiv, ob die Zeichnung nach dem + Speichern (Zoom Extents) als Tab offen bleibt (Kontroll-Modus, Default) oder + ob sie zusaetzlich per `c:EXPORTCSV`/`c:EXPORTSIVAS` exportiert und danach + sofort geschlossen wird (Automatik-Modus). 2. **Python** (pytest + ezdxf): Validiert die Ergebnisse automatisch gegen erwartete Werte und optionale Referenzdaten. @@ -26,9 +31,10 @@ tests/ ├── test_run_all.lsp # C:SSG_RUN_ALL_TESTS - fuehrt alle Tests aus ├── test_kreisel.lsp # C:TEST_KREISEL - Kreisel-Testrunner ├── test_foerderer.lsp # C:TEST_FOERDERER - Variofoerderer-Testrunner -├── test_kseinaus.lsp # C:TEST_KSEINAUS - KreiselEinAus-Testrunner ├── test_omniflo.lsp # C:TEST_OMNIFLO - Omniflo-Testrunner -├── conftest.py # pytest Fixtures +├── test_gefaellestrecke.lsp # C:TEST_GEFAELLESTRECKE - Gefaellestrecke-Testrunner +├── test_export_all.lsp # C:TEST_EXPORT_ALL - manuell: oeffnet alle DXF/DWG aus output/ und exportiert sie (nicht in alltests.json) +├── conftest.py # pytest Fixtures ├── test_kreisel.py # pytest Kreisel-Validierung ├── test_omniflo.py # pytest Omniflo-Validierung ├── create_testbase.py # Hilfsskript (optional, i.d.R. nicht benoetigt) @@ -44,9 +50,14 @@ tests/ ├── kreisel_tests.dxf ├── foerderer_results.json ├── foerderer_tests.dwg - ├── kseinaus_results.json ├── omniflo_results.json - └── omniflo_tests.dxf + ├── omniflo_tests.dxf + ├── gefaellestrecke_results.json + ├── gefaellestrecke_tests.dwg + ├── export_all_results.json # nur nach manuellem TEST_EXPORT_ALL-Lauf + ├── kreisel_tests_export.csv # EXPORTCSV/EXPORTSIVAS je Testmodul - manuell via + ├── kreisel_tests_sivas.csv # TEST_EXPORT_ALL, oder automatisch im Automatik-Modus + └── ... # von SSG_RUN_ALL_TESTS ``` ## Testregistry (alltests.json) @@ -56,13 +67,22 @@ Die Datei `tests/alltests.json` ist die zentrale Registry aller Testmodule. ```json [ - { "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": "kreisel", "save": "dxf", "module": "KreiselInsert" }, + { "name": "foerderer", "save": "dwg", "module": "VarioFoerderer" }, + { "name": "omniflo", "save": "dxf", "module": "OmniModulInsert" }, + { "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" } ] ``` +`TEST_EXPORT_ALL` (`tests/test_export_all.lsp`) ist **nicht** in `alltests.json` +registriert und laeuft daher nicht automatisch mit `SSG_RUN_ALL_TESTS`. Es wird +manuell aufgerufen (Menuepunkt "Export CSV/Sivas ALL"), oeffnet dann jede zu diesem +Zeitpunkt in `tests/output/` vorhandene DXF/DWG-Datei und exportiert sie per +`c:EXPORTCSV`/`c:EXPORTSIVAS` nach `_export.csv`/`_sivas.csv`. +Alternativ: `SSG_RUN_ALL_TESTS` im Automatik-Modus (interaktive Abfrage beim +Start) erledigt Speichern, Export und Schliessen direkt je Testmodul, ohne dass +`TEST_EXPORT_ALL` danach noch manuell aufgerufen werden muss. + | Feld | Typ | Bedeutung | | --- | --- | --- | | `name` | String | Basisname des Testmoduls (Pflicht) | diff --git a/lib/export_blockpatterns.py b/lib/export_blockpatterns.py new file mode 100644 index 0000000..fc99762 --- /dev/null +++ b/lib/export_blockpatterns.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +export_blockpatterns.py - Liest Blockname-Muster aus cfg/export.cfg. + +Einzige Quelle fuer die Blockname-Klassifikation, die sowohl von +Lisp/export.lsp (csv:collect-export-blocks, ueber export:pattern) als auch +von den Python-Export-Skripten (export_csv.py, export_sivas.py) genutzt +wird. Vermeidet, dass Blockname-Praefixe/-Wildcards in mehreren Dateien +unabhaengig voneinander gepflegt werden und dabei auseinanderlaufen. + +cfg/export.cfg deklariert fuer Lisp kombinierte Kategorien (z.B. +pattern_kreisel deckt Kreisel UND Eckrad ab, weil Lisp beim Sammeln der +Bloecke nur "gehoert das ueberhaupt zum Export" wissen muss). Python +braucht dagegen feinere Kategorien (Kreisel und Eckrad sind unterschied- +liche TeileArt-Zeilen) - dafuer gibt es zusaetzliche, feinere Keys +(pattern_eckrad, pattern_strecke_compound, pattern_strecke_module), die +inhaltlich Teilmengen der groben Lisp-Kategorien sind. +""" + +import configparser +import fnmatch +import os + + +def cfg_path_from_env(default_dir=None): + """Pfad zu export.cfg ermitteln: DXFM_CFG env var, sonst /cfg.""" + dxfm_cfg = os.environ.get("DXFM_CFG") + if dxfm_cfg: + return os.path.join(dxfm_cfg, "export.cfg") + base = default_dir or os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + return os.path.join(base, "cfg", "export.cfg") + + +def load_patterns(cfg_path=None): + """Liest [blockpattern] aus export.cfg. + + Rueckgabe: dict key -> list[str] (Wildcard-Muster, wcmatch-kompatibel: + "*" = beliebig viele Zeichen). Bei fehlender Datei/Sektion: leeres dict. + """ + if cfg_path is None: + cfg_path = cfg_path_from_env() + parser = configparser.ConfigParser() + parser.read(cfg_path, encoding="utf-8") + patterns = {} + if parser.has_section("blockpattern"): + for key, value in parser.items("blockpattern"): + patterns[key] = [p.strip() for p in value.split(",") if p.strip()] + return patterns + + +def matches_any(bname, patterns): + """True, wenn bname (case-insensitiv) zu mindestens einem Muster passt.""" + upper = bname.upper() + return any(fnmatch.fnmatchcase(upper, p.upper()) for p in patterns) diff --git a/lib/export_csv.py b/lib/export_csv.py index f682310..6271ff1 100644 --- a/lib/export_csv.py +++ b/lib/export_csv.py @@ -18,14 +18,28 @@ import json import sys import os +from export_blockpatterns import load_patterns, matches_any + +BLOCKPATTERNS = load_patterns() + # --------------------------------------------------------------------------- # Gemeinsame Hilfsfunktionen (auch in export_sivas.py verwendet) # --------------------------------------------------------------------------- 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 export_raw.json (write-line) im ANSI-Codepage des + Systems, nicht in UTF-8 - Sonderzeichen (z.B. Gradzeichen) 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) def build_lookup(boegen, weichen): @@ -199,7 +213,7 @@ def build_kreisel_merkmale(block): # Bekannte Blocknamen # --------------------------------------------------------------------------- -SKIP_BLOCKS = {"K1", "K2", "K3", "K4", "KS_EIN", "KS_AUS"} +SKIP_BLOCKS = set(BLOCKPATTERNS.get("pattern_ks_subblocks", ["K1", "K2", "K3", "K4", "KS_EIN", "KS_AUS"])) # --------------------------------------------------------------------------- @@ -213,7 +227,10 @@ def process_blocks(blocks, lookup): weiche_count = {} gerade_count = 0 kreisel_count = 0 + eckrad_count = 0 vf_count = 0 + gf_count = 0 + strecke_modul_count = 0 # Zaehlung fuer die Omniflo-Sum-Zeile (nur Bogen/Weiche/Gerade) cnt_wk = cnt_einzel = cnt_delta = cnt_doppel = cnt_stern = 0 @@ -268,7 +285,7 @@ def process_blocks(blocks, lookup): continue # Omniflo Gerade (AP110) - if "AP110" in bname.upper() or "AP_110" in bname.upper(): + if matches_any(bname, BLOCKPATTERNS.get("pattern_gerade", [])): elem_nr += 1 gerade_count += 1 has_omniflo = True @@ -283,7 +300,7 @@ def process_blocks(blocks, lookup): continue # VarioFoerderer Compound-Block (VF_N) - if bname.startswith("VF_"): + if matches_any(bname, BLOCKPATTERNS.get("pattern_variofoerderer", [])): elem_nr += 1 vf_count += 1 items.append({ @@ -295,8 +312,35 @@ def process_blocks(blocks, lookup): }) continue - # ILS Kreisel (Compound-Bloecke mit Praefix KR_) - if bname.startswith("KR_"): + # Gefaellestrecke Compound-Block (GF_N) + if matches_any(bname, BLOCKPATTERNS.get("pattern_gefaellestrecke", [])): + elem_nr += 1 + gf_count += 1 + items.append({ + "nr": elem_nr, + "teileart": "Gefaellestrecke", + "teileid": block.get("attribs", {}).get("ID", "0000"), + "bezeichnung": f"Gefaellestrecke :{gf_count}", + "merkmale": build_variofoerderer_merkmale(block), + }) + continue + + # ILS Eckrad (vor Kreisel pruefen: pattern_kreisel schliesst + # ECKRAD_* mit ein, pattern_eckrad ist die praezisere Teilmenge) + if matches_any(bname, BLOCKPATTERNS.get("pattern_eckrad", [])): + elem_nr += 1 + eckrad_count += 1 + items.append({ + "nr": elem_nr, + "teileart": "ILS 2.0 Eckrad", + "teileid": block.get("attribs", {}).get("ID", "0000"), + "bezeichnung": f"Eckrad :{eckrad_count}", + "merkmale": build_kreisel_merkmale(block), + }) + continue + + # ILS Kreisel (Compound-Bloecke mit Praefix KR_ oder KREISEL_) + if matches_any(bname, BLOCKPATTERNS.get("pattern_kreisel", [])): elem_nr += 1 kreisel_count += 1 items.append({ @@ -308,6 +352,19 @@ def process_blocks(blocks, lookup): }) continue + # ILS Strecke-Module (Vario/Staustrecke-Einzelkomponenten) + if matches_any(bname, BLOCKPATTERNS.get("pattern_strecke_module", [])): + elem_nr += 1 + strecke_modul_count += 1 + items.append({ + "nr": elem_nr, + "teileart": "ILS 2.0 Strecke - Modul", + "teileid": block.get("attribs", {}).get("ID", "0000"), + "bezeichnung": f"Streckenmodul :{strecke_modul_count}", + "merkmale": {"Blockname": bname}, + }) + continue + if has_omniflo: elem_nr += 1 items.append({ diff --git a/lib/export_sivas.py b/lib/export_sivas.py index c85b0c3..2157b96 100644 --- a/lib/export_sivas.py +++ b/lib/export_sivas.py @@ -24,10 +24,24 @@ import json import sys import os +from export_blockpatterns import load_patterns, matches_any + +BLOCKPATTERNS = load_patterns() + 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 export_raw.json (write-line) im ANSI-Codepage des + Systems, nicht in UTF-8 - Sonderzeichen (z.B. Gradzeichen) 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) def build_lookup(boegen, weichen): @@ -106,7 +120,7 @@ def build_kreisel_details(block): # --- Bekannte Bloecke ignorieren --- -SKIP_BLOCKS = {"K1", "K2", "K3", "K4", "KS_EIN", "KS_AUS"} +SKIP_BLOCKS = set(BLOCKPATTERNS.get("pattern_ks_subblocks", ["K1", "K2", "K3", "K4", "KS_EIN", "KS_AUS"])) # Sivas-Nummern und Bezeichnungen je ILS-Elementtyp ILS_SIVASNR = { @@ -118,13 +132,6 @@ ILS_SIVASNR = { "automation": ("6269", "ILS 2.0 Automation und Pneumatik - Gesamtanlage"), } -# Strecken-Module: Blocknamen-Fragmente fuer Vario/Staustrecke-Elemente -STRECKE_BLOCKS = { - "AUS_Element_links", "EIN_Element_links", - "Staustrecke_SP_1000_mm", "Staustrecke_Separator_SP_300_mm", - "Vario_Spannstation_SP_500mm", "Vario_Motorstation_SP_500mm", -} - def process_blocks(blocks, lookup): """ @@ -212,7 +219,7 @@ def process_blocks(blocks, lookup): continue # --- Omniflo Gerade (AP110) --- - if "AP110" in bname.upper() or "AP_110" in bname.upper(): + if matches_any(bname, BLOCKPATTERNS.get("pattern_gerade", [])): attribs = block.get("attribs", {}) artinr = attribs.get("ARTINR", "") key = artinr if artinr else "AP110" @@ -235,8 +242,15 @@ def process_blocks(blocks, lookup): counters["gesamtlaenge_ap110"] += laenge continue + # --- ILS Eckrad (vor Kreisel pruefen: pattern_kreisel schliesst + # ECKRAD_* mit ein, pattern_eckrad ist die praezisere Teilmenge) --- + if matches_any(bname, BLOCKPATTERNS.get("pattern_eckrad", [])): + counters["anzahl_eckraeder"] += 1 + eckrad_ids.append(get_id(block)) + continue + # --- ILS Kreisel --- - if bname.startswith("KR_") or bname.startswith("KREISEL_"): + if matches_any(bname, BLOCKPATTERNS.get("pattern_kreisel", [])): kreisel_list.append(block) attribs = block.get("attribs", {}) counters["anzahl_kreisel"] += 1 @@ -255,14 +269,8 @@ def process_blocks(blocks, lookup): counters["anzahl_streckengruppen"] = 1 continue - # --- ILS Eckrad --- - if bname.startswith("ECKRAD_"): - counters["anzahl_eckraeder"] += 1 - eckrad_ids.append(get_id(block)) - continue - # --- VarioFoerderer Compound-Block (VF_N) --- - if bname.startswith("VF_"): + if matches_any(bname, BLOCKPATTERNS.get("pattern_variofoerderer", [])): attribs = block.get("attribs", {}) counters["anzahl_variofoerderer"] += 1 strecke_ids.append(get_id(block)) @@ -283,7 +291,7 @@ def process_blocks(blocks, lookup): continue # --- Gefaellestrecke Compound-Block (GF_N) --- - if bname.startswith("GF_"): + if matches_any(bname, BLOCKPATTERNS.get("pattern_gefaellestrecke", [])): attribs = block.get("attribs", {}) counters["anzahl_variofoerderer"] += 1 counters["anzahl_gefaellestrecken"] += 1 @@ -305,11 +313,9 @@ def process_blocks(blocks, lookup): continue # --- ILS Strecke-Module (Vario/Staustrecke, einzelne Komponenten in Modelspace) --- - for sb in STRECKE_BLOCKS: - if sb in bname: - counters["anzahl_variofoerderer"] += 1 - strecke_ids.append(get_id(block)) - break + if matches_any(bname, BLOCKPATTERNS.get("pattern_strecke_module", [])): + counters["anzahl_variofoerderer"] += 1 + strecke_ids.append(get_id(block)) # --- Zeilen aufbauen --- items = [] diff --git a/menu/SSG_LIB.mnu b/menu/SSG_LIB.mnu index 99f4ba2..afeb8f3 100644 --- a/menu/SSG_LIB.mnu +++ b/menu/SSG_LIB.mnu @@ -123,9 +123,9 @@ [--] [Kreisel]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_kreisel.lsp")) TEST_KREISEL [Foerderer]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_foerderer.lsp")) TEST_FOERDERER -[KS EIN/AUS]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_ks.lsp")) TEST_KSEINAUS [Omniflo]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_omniflo.lsp")) TEST_OMNIFLO -[<-Gefaellestrecke]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_gefaellestrecke.lsp")) TEST_GEFAELLESTRECKE +[Gefaellestrecke]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_gefaellestrecke.lsp")) TEST_GEFAELLESTRECKE +[<-Export CSV/Sivas ALL]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_export_all.lsp")) TEST_EXPORT_ALL ***DOUBLECLICK [INSERT]^C^C(ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT diff --git a/tests/README.md b/tests/README.md index 8e3658e..cc2f7a0 100644 --- a/tests/README.md +++ b/tests/README.md @@ -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,10 +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) | + gefaellestrecke_results.json | + gefaellestrecke_tests.dwg | | bin\run_tests.bat --runall | @@ -36,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 + `_export.csv`/`_sivas.csv` erzeugt. +- **Automatik-Modus (Ja)**: Zeichnung wird gespeichert, direkt per + `c:EXPORTCSV`/`c:EXPORTSIVAS` exportiert (`_tests_export.csv`/ + `_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. @@ -45,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" } ] ``` @@ -72,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 @@ -89,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 @@ -99,11 +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 + gefaellestrecke_results.json + gefaellestrecke_tests.dwg + export_all_results.json # nur nach manuellem TEST_EXPORT_ALL-Lauf + _export.csv # EXPORTCSV je Quelldatei, nur nach TEST_EXPORT_ALL + _sivas.csv # EXPORTSIVAS je Quelldatei, nur nach TEST_EXPORT_ALL reference/ # Abgenommene Referenzdaten (in Git) kreisel_ref.dxf ... diff --git a/tests/alltests.json b/tests/alltests.json index 64c3435..a98254e 100644 --- a/tests/alltests.json +++ b/tests/alltests.json @@ -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" } ] diff --git a/tests/test_export_all.lsp b/tests/test_export_all.lsp new file mode 100644 index 0000000..a3f2227 --- /dev/null +++ b/tests/test_export_all.lsp @@ -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 .dxf/.dwg: +;; tests/output/_export.csv +;; tests/output/_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) +) diff --git a/tests/test_foerderer.lsp b/tests/test_foerderer.lsp index 8d740aa..a6f5db6 100644 --- a/tests/test_foerderer.lsp +++ b/tests/test_foerderer.lsp @@ -75,8 +75,7 @@ y-offset y-abstand idx anz-gebaut anz-nicht-gebaut results-list startpunkt vf-nummer hoehe-von hoehe-bis lastEnt - sub-ent sub-typ - tests-out-dir old-dxfm-results) + sub-ent sub-typ) ;; Bibliothek initialisieren (laedt Block-Library und extrahiert Masse) (if (not *lib-initialized*) @@ -218,21 +217,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.") diff --git a/tests/test_foerderer.py b/tests/test_foerderer.py index 0cff335..ae33f48 100644 --- a/tests/test_foerderer.py +++ b/tests/test_foerderer.py @@ -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.""" diff --git a/tests/test_kseinaus.lsp b/tests/test_kseinaus.lsp deleted file mode 100644 index aa0fecf..0000000 --- a/tests/test_kseinaus.lsp +++ /dev/null @@ -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) -) diff --git a/tests/test_run_all.lsp b/tests/test_run_all.lsp index 9ad25cf..4e20b03 100644 --- a/tests/test_run_all.lsp +++ b/tests/test_run_all.lsp @@ -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) )