diff --git a/menu/SSG_LIB.cui b/menu/SSG_LIB.cui index 6b1d71d..1f395d4 100644 --- a/menu/SSG_LIB.cui +++ b/menu/SSG_LIB.cui @@ -687,42 +687,48 @@ + + Mubea Gesamtmodell + ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_mubea.lsp")) TEST_MUBEA + + + KS 2D/3D pruefen (CSV) ^C^C(load (strcat (getenv "DXFMAKRO") "/Lisp/ks_pruefung.lsp")) KS_PRUEFEN - + Export CSV/Sivas ALL ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_export_all.lsp")) TEST_EXPORT_ALL - + Kreisel bearbeiten (ssg-ensure "KreiselInsert") KreiselEdit - + Omniflo Element bearbeiten (ssg-ensure "OmniModulInsert") OMNI_EDIT - + Gefaellestrecke bearbeiten (ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_EDIT - + VarioFoerderer bearbeiten (ssg-ensure "VarioFoerderer") VARIOFOERDERER_EDIT - + SSG Bearbeiten (Auto) (ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT @@ -1501,50 +1507,56 @@ - KS 2D/3D pruefen (CSV) + Mubea Gesamtmodell - Export CSV/Sivas ALL + KS 2D/3D pruefen (CSV) + + Export CSV/Sivas ALL + + + + - + POP501 SSG_LIB Edit Context Kreisel bearbeiten - + Omniflo Element bearbeiten - + Gefaellestrecke bearbeiten - + VarioFoerderer bearbeiten - + SSG Bearbeiten (Auto) - + diff --git a/menu/SSG_LIB.mnu b/menu/SSG_LIB.mnu index 0948989..baadb3a 100644 --- a/menu/SSG_LIB.mnu +++ b/menu/SSG_LIB.mnu @@ -142,6 +142,7 @@ [Foerderer-Linienzug]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_linienzug.lsp")) TEST_LINIENZUG [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 +[Mubea Gesamtmodell]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_mubea.lsp")) TEST_MUBEA [KS 2D/3D pruefen (CSV)]^C^C(load (strcat (getenv "DXFMAKRO") "/Lisp/ks_pruefung.lsp")) KS_PRUEFEN [<-Export CSV/Sivas ALL]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_export_all.lsp")) TEST_EXPORT_ALL diff --git a/tests/alltests.json b/tests/alltests.json index e1838ae..422d492 100644 --- a/tests/alltests.json +++ b/tests/alltests.json @@ -4,5 +4,6 @@ { "name": "linienzug", "save": "dwg", "module": "VarioFoerderer" }, { "name": "omniflo", "save": "dxf", "module": "OmniModulInsert" }, { "name": "omniflo_strecke", "save": "dxf", "module": "OmniModulInsert" }, - { "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" } + { "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" }, + { "name": "mubea", "save": "dxf", "module": "KreiselInsert" } ] diff --git a/tests/conftest.py b/tests/conftest.py index 9112419..6eea4aa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -157,3 +157,33 @@ def gefaellestrecke_dxf(): if not os.path.exists(path): pytest.skip("gefaellestrecke_tests.dxf nicht vorhanden - TESTRUN in BricsCAD ausfuehren") return ezdxf.readfile(path) + + +# --- Mubea Fixtures --- + +@pytest.fixture +def mubea_testdata(): + """Laedt die Mubea-Testdefinitionen aus testdata/mubea.json (alle Eintraege).""" + path = os.path.join(_testdata_dir(), "mubea.json") + return _load_json(path) + + +@pytest.fixture +def mubea_results(): + """Laedt die Mubea-Ergebnisse aus output/mubea_results.json.""" + path = os.path.join(_output_dir(), "mubea_results.json") + if not os.path.exists(path): + pytest.skip("mubea_results.json nicht vorhanden - TEST_MUBEA in BricsCAD ausfuehren") + data = _load_json(path) + if not data: + pytest.skip("mubea_results.json ist leer") + return data + + +@pytest.fixture +def mubea_dxf(): + """Laedt die erzeugte Mubea-DXF aus output/mubea_tests.dxf.""" + path = os.path.join(_output_dir(), "mubea_tests.dxf") + if not os.path.exists(path): + pytest.skip("mubea_tests.dxf nicht vorhanden - TESTRUN (save:dxf) in BricsCAD ausfuehren") + return ezdxf.readfile(path) diff --git a/tests/test_mubea.lsp b/tests/test_mubea.lsp new file mode 100644 index 0000000..04e68a6 --- /dev/null +++ b/tests/test_mubea.lsp @@ -0,0 +1,339 @@ +;; ============================================================ +;; test_mubea.lsp - Integrationstest: erzeugt das Mubea-Gesamtmodell +;; +;; Liest tests/testdata/mubea.json und baut in EINER Zeichnung: +;; - Kreisel (Eintraege mit "id", Block KREISEL_*) via kreisel-insert-script +;; - Vario (test_id "VF_*") Block VF_* via variofoerderer-einfuegen +;; + vf-block-erstellen +;; (deltaL/L_VF werden fuer winkel+deltaH geloest, L_GF Split 500/4230) +;; - Gefaelle (test_id "GF_*", Modus 1) Block GF_* via gefaellestrecke-einfuegen +;; - Separator (Eintraege mit "block") Block S-LP/Separator_SP via _.INSERT +;; +;; Speichert (via SSG_RUN_ALL_TESTS bei "save":"dxf"): +;; tests/output/mubea_tests.dxf +;; tests/output/mubea_results.json (via mubea:export-results) +;; +;; Voraussetzungen: +;; - SSG_LIB geladen (KreiselInsert, VarioFoerderer, Gefaellestrecke, +;; SSG_LIB_Commands, ssg_core, vf_core) +;; - Umgebungsvariable DXFMAKRO gesetzt +;; +;; Aufruf in BricsCAD: +;; (load (strcat (getenv "DXFMAKRO") "/tests/test_mubea.lsp")) +;; TEST_MUBEA +;; ============================================================ + + +;; --- Ergebnis eines Bau-Schritts als JSON-String erzeugen --- +(defun mubea:result-json (test-id kind status block-name block-handle insert-point attribs / + json tag val first) + (setq json (strcat " {\n" + " \"test_id\": \"" test-id "\",\n" + " \"kind\": \"" kind "\",\n" + " \"status\": \"" status "\",\n" + " \"block_name\": \"" (if block-name block-name "") "\",\n" + " \"block_handle\": \"" (if block-handle block-handle "") "\",\n" + " \"insert_point\": [" + (if insert-point + (strcat (rtos (car insert-point) 2 1) ", " + (rtos (cadr insert-point) 2 1) ", " + (rtos (if (caddr insert-point) (caddr insert-point) 0.0) 2 1)) + "0.0, 0.0, 0.0") + "],\n" + " \"actual_attributes\": {")) + (setq first T) + (foreach att attribs + (setq tag (car att) val (cdr att)) + (if (not first) (setq json (strcat json ","))) + (setq json (strcat json "\n \"" tag "\": \"" (if val val "") "\"")) + (setq first nil)) + (setq json (strcat json "\n }\n }")) + json +) + + +;; --- Aus einer Block-ENAME ein Ergebnis-JSON erzeugen --- +(defun mubea:ent-json (test-id kind ent / ed) + (if ent + (progn + (setq ed (entget ent)) + (mubea:result-json test-id kind "executed" + (cdr (assoc 2 ed)) (cdr (assoc 5 ed)) (cdr (assoc 10 ed)) + (ssg-attrib-read ent))) + (mubea:result-json test-id kind "failed" nil nil nil nil)) +) + + +;; --- Letzten INSERT mit Blocknamen-Praefix (rueckwaerts) suchen --- +(defun mubea:last-insert-prefix (pref / ent found ed bn) + (setq ent (entlast)) + (while (and ent (null found)) + (setq ed (entget ent) bn (cdr (assoc 2 ed))) + (if (and (= (cdr (assoc 0 ed)) "INSERT") bn + (>= (strlen bn) (strlen pref)) + (= (substr bn 1 (strlen pref)) pref)) + (setq found ent)) + (setq ent (entprev ent))) + found +) + + +;; ============================================================ +;; Bau-Funktionen je Elementtyp +;; ============================================================ + +;; --- Kreisel (wie test_kreisel: kreisel-insert-script) --- +(defun mubea:build-kreisel (e / id x y z abstand rotation typ pt ent) + (setq id (ssg-val e "id") + x (float (ssg-val e "x")) + y (float (ssg-val e "y")) + z (float (ssg-val e "z")) + abstand (float (ssg-val e "abstand")) + rotation (float (ssg-val e "rotation")) + typ (ssg-val e "typ")) + (if (null typ) (setq typ "STANDARD")) + (setq pt (list x y z)) + (princ (strcat "\n [KREISEL] " id " -> (" (rtos x 2 0) "," (rtos y 2 0) + "," (rtos z 2 0) ") A=" (rtos abstand 2 0) " R=" (rtos rotation 2 1))) + (setq ent (kreisel-insert-script pt abstand rotation typ z)) + (mubea:ent-json id "kreisel" ent) +) + + +;; --- Vario (VF_*): deltaL/L_VF fuer winkel+deltaH loesen, dann bauen --- +(defun mubea:build-vario (e / tid richtung deltaH winkel gf1 gf2 gf-ziel seite hz + startpunkt dlA dlB it1 it2 lgfA lgfB steig dL item L_VF + hoehe-bis lastEnt ent) + (setq tid (ssg-val e "test_id") + richtung (ssg-val e "richtung") + deltaH (float (ssg-val e "deltaH")) + winkel (fix (ssg-val e "winkel")) + gf1 (float (ssg-val e "L_GF1")) + gf2 (float (ssg-val e "L_GF2")) + seite (ssg-val e "seite") + hz (ssg-val e "hz") + startpunkt (list (float (ssg-val e "x")) (float (ssg-val e "y")) (float (ssg-val e "z")))) + (if (or (null seite) (= seite "")) (setq seite "links")) + (if (null hz) (setq hz 0.0) (setq hz (float hz))) + (setq gf-ziel (+ gf1 gf2)) + (princ (strcat "\n [VARIO] " tid " -> Winkel=" (itoa winkel) + " deltaH=" (rtos deltaH 2 0) " L_GF1=" (rtos gf1 2 0) + " L_GF2=" (rtos gf2 2 0))) + + ;; L_GF ist linear in deltaL -> deltaL aus zwei Stuetzstellen interpolieren, + ;; sodass L_GF gesamt = gf-ziel am gewaehlten Winkel. + (setq dlA 5000.0 dlB 10000.0) + (setq it1 (car (vl-remove-if-not '(lambda (a) (= (car a) winkel)) + (cadddr (berechne-alle-winkel dlA deltaH richtung nil))))) + (setq it2 (car (vl-remove-if-not '(lambda (a) (= (car a) winkel)) + (cadddr (berechne-alle-winkel dlB deltaH richtung nil))))) + (if (and it1 it2 (numberp (cadr it1)) (numberp (cadr it2)) + (/= (cadr it1) (cadr it2))) + (progn + (setq lgfA (cadr it1) lgfB (cadr it2)) + (setq steig (/ (- lgfB lgfA) (- dlB dlA))) + (setq dL (+ dlA (/ (- gf-ziel lgfA) steig))) + (setq item (car (vl-remove-if-not '(lambda (a) (= (car a) winkel)) + (cadddr (berechne-alle-winkel dL deltaH richtung nil))))) + (setq L_VF (caddr item)) + (princ (strcat " -> deltaL=" (rtos dL 2 1) " L_VF=" (rtos L_VF 2 1))) + + (setq hoehe-bis (if (= richtung "Auf") deltaH (- deltaH))) + (setq lastEnt (vf-lastent-ohne-attribute)) + (variofoerderer-einfuegen dL deltaH richtung winkel gf1 gf2 L_VF startpunkt seite hz) + (setq ent (vf-block-erstellen "standard" seite 1 0 0 hoehe-bis + deltaH dL L_VF gf1 gf2 richtung winkel + startpunkt lastEnt hz "0" "Schoenenberger Geruest")) + (if (or (null ent) (/= (type ent) 'ENAME)) + (setq ent (mubea:last-insert-prefix "VF_"))) + (mubea:ent-json tid "vario" ent)) + (progn + (princ (strcat "\n FEHLER: Winkel " (itoa winkel) " nicht loesbar (kein gueltiges L_GF).")) + (mubea:result-json tid "vario" "failed" nil nil nil nil))) +) + + +;; --- Gefaellestrecke (GF_*, Modus 1) --- +;; Wie test_gefaellestrecke: Element-Winkel 3 Grad, L_stau berechnet. +;; deltaL wird NICHT mehr fest auf 10000 gesetzt, sondern aus der realen +;; 2D-Distanz zwischen start_mm und ende_mm (aus der DXF abgeleitet) berechnet - +;; sonst haetten (wie im ersten TEST_MUBEA-Lauf beobachtet) alle 20 Strecken +;; die gleiche Default-Laenge statt ihrer tatsaechlichen Laenge. +;; Aus JSON: start_mm, ende_mm, hz_grad, as_seite, es_seite, as_winkel, es_winkel. +(defun mubea:build-gf (e / tid start-mm ende-mm hz as-seite es-seite as-winkel es-winkel + test-deltaL test-winkel test-rad sep-x test-L_stau ent + dx dy) + (setq tid (ssg-val e "test_id") + start-mm (mapcar 'float (ssg-val e "start_mm")) + ende-mm (ssg-val e "ende_mm") + hz (float (ssg-val e "hz_grad")) + as-seite (ssg-val e "as_seite") + es-seite (ssg-val e "es_seite") + as-winkel (ssg-val e "as_winkel") + es-winkel (ssg-val e "es_winkel")) + (if (or (null as-seite) (= as-seite "")) (setq as-seite "links")) + (if (or (null es-seite) (= es-seite "")) (setq es-seite "links")) + (if (or (null as-winkel) (= as-winkel "")) (setq as-winkel "90")) + (if (or (null es-winkel) (= es-winkel "")) (setq es-winkel "90")) + + ;; deltaL = reale 2D-Distanz start_mm -> ende_mm; Fallback 10000 falls + ;; ende_mm in einem Eintrag fehlen sollte. + (if ende-mm + (progn + (setq ende-mm (mapcar 'float ende-mm)) + (setq dx (- (car ende-mm) (car start-mm))) + (setq dy (- (cadr ende-mm) (cadr start-mm))) + (setq test-deltaL (sqrt (+ (* dx dx) (* dy dy))))) + (setq test-deltaL 10000.0)) + + (setq test-winkel 3.0 + test-rad (* test-winkel (/ pi 180.0)) + sep-x (* 300.0 (cos test-rad))) + (setq test-L_stau + (/ (- test-deltaL (float aus-dx) (float ein-dx) sep-x) (cos test-rad))) + + (princ (strcat "\n [GF] " tid " -> Start=(" (rtos (car start-mm) 2 0) + "," (rtos (cadr start-mm) 2 0) "," (rtos (caddr start-mm) 2 0) + ") hz=" (rtos hz 2 0) " deltaL=" (rtos test-deltaL 2 1))) + (setq ent (car (gefaellestrecke-einfuegen + test-L_stau test-winkel start-mm as-seite es-seite hz + test-deltaL as-winkel es-winkel "0" "Schoenenberger Geruest"))) + (mubea:ent-json tid "gefaellestrecke" ent) +) + + +;; --- Separator (Eintraege mit "block"): _.INSERT der Blockdatei --- +;; Verwendet den in JSON genannten Block; faellt auf Separator_SP zurueck +;; (Block der "Separator einfuegen"-Routine ILS_SEPARATOR_INSERT). +(defun mubea:build-separator (e idx / blockname x y z rot pt pfad tid) + (setq blockname (ssg-val e "block") + x (float (ssg-val e "x")) + y (float (ssg-val e "y")) + z (float (ssg-val e "z")) + rot (ssg-val e "rotation")) + (if (null rot) (setq rot 0)) + (setq rot (float rot)) + (setq pt (list x y z)) + (setq tid (strcat "SEPARATOR_" (itoa idx))) + (princ (strcat "\n [SEP] " tid " '" (if blockname blockname "?") + "' -> (" (rtos x 2 0) "," (rtos y 2 0) "," (rtos z 2 0) ") R=" (rtos rot 2 0))) + (setq pfad (and blockname (ssg-ils-block-datei blockname))) + (if (null pfad) (setq pfad (ssg-ils-block-datei "Separator_SP"))) + (if pfad + (progn + (command "_.INSERT" pfad pt "" "" rot) + (mubea:ent-json tid "separator" (entlast))) + (progn + ;; Kein Block gefunden -> sichtbaren Platzhalter zeichnen + (if (not (tblsearch "LAYER" "MUBEA_SEPARATOR")) + (entmake (list '(0 . "LAYER") '(2 . "MUBEA_SEPARATOR") '(70 . 0) '(62 . 1)))) + (entmake (list '(0 . "CIRCLE") (cons 10 pt) (cons 40 150.0) '(8 . "MUBEA_SEPARATOR"))) + (princ " (Block fehlt - Platzhalter)") + (mubea:result-json tid "separator" "placeholder" + (if blockname blockname "S-LP") nil pt nil))) +) + + +;; --- JSON-Export der Mubea-Ergebnisse --- +(defun mubea:export-results (tests-out-dir / out-json f first) + (if (null *mubea-test-results*) + (princ "\n Keine Mubea-Ergebnisse vorhanden.") + (progn + (vl-mkdir tests-out-dir) + (setq out-json (strcat tests-out-dir "/mubea_results.json")) + (setq f (open out-json "w")) + (if f + (progn + (write-line "[" f) + (setq first T) + (foreach r *mubea-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_MUBEA - Hauptbefehl +;; ============================================================ +(defun c:TEST_MUBEA ( / json-datei daten eintrag tid blk res + results-list sep-idx anz-ok anz-fehler kind) + + ;; Benoetigte Feature-Module laden + (ssg-ensure "KreiselInsert") + (ssg-ensure "VarioFoerderer") + (ssg-ensure "Gefaellestrecke") + (ssg-ensure "SSG_LIB_Commands") + + (ssg-start "TEST_MUBEA" '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA"))) + (setvar "OSMODE" 0) + (setvar "ATTREQ" 0) + (setvar "ATTDIA" 0) + + ;; Block-Bibliothek (Bogen-/AS-/ES-Masse) initialisieren - fuer Vario+GF noetig + (if (or (not *lib-initialized*) (null bogen-auf)) + (init-bibliothek)) + + ;; Testdaten laden + (setq json-datei (strcat (getenv "DXFMAKRO") "/tests/testdata/mubea.json")) + (if (not (findfile json-datei)) + (progn + (princ (strcat "\n[TEST_MUBEA] FEHLER: " json-datei " nicht gefunden!")) + (ssg-end) + (exit))) + (setq daten (ssg-load-json json-datei)) + (if (null daten) + (progn + (princ "\n[TEST_MUBEA] FEHLER: Testdaten konnten nicht geladen werden.") + (ssg-end) + (exit))) + + (princ "\n\n================================================================") + (princ "\n TEST_MUBEA - Mubea-Gesamtmodell") + (princ (strcat "\n " (itoa (length daten)) " Eintraege geladen")) + (princ "\n================================================================") + + (setq results-list '() sep-idx 0 anz-ok 0 anz-fehler 0) + + (foreach eintrag daten + (setq tid (ssg-val eintrag "test_id")) + (setq res + (cond + ;; Separator: hat "block" + ((ssg-val eintrag "block") + (setq sep-idx (1+ sep-idx)) + (mubea:build-separator eintrag sep-idx)) + ;; Kreisel: hat "id" + ((ssg-val eintrag "id") + (mubea:build-kreisel eintrag)) + ;; Vario: test_id "VF_*" + ((and tid (>= (strlen tid) 3) (= (substr tid 1 3) "VF_")) + (mubea:build-vario eintrag)) + ;; Gefaellestrecke: test_id "GF_*" + ((and tid (>= (strlen tid) 3) (= (substr tid 1 3) "GF_")) + (mubea:build-gf eintrag)) + (T + (princ (strcat "\n WARNUNG: Unbekannter Eintrag uebersprungen")) + nil))) + (if res + (progn + (setq results-list (cons res results-list)) + (if (or (vl-string-search "\"executed\"" res) + (vl-string-search "\"placeholder\"" res)) + (setq anz-ok (1+ anz-ok)) + (setq anz-fehler (1+ anz-fehler)))))) + + (princ "\n================================================================") + (princ (strcat "\n Ergebnis: " (itoa anz-ok) " OK, " (itoa anz-fehler) " Fehler")) + (princ "\n================================================================") + + (setq *mubea-test-results* (reverse results-list)) + + (ssg-end) + (princ "\n TEST_MUBEA abgeschlossen.") + (princ) +) diff --git a/tests/test_mubea.py b/tests/test_mubea.py new file mode 100644 index 0000000..fd40dee --- /dev/null +++ b/tests/test_mubea.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +test_mubea.py - Validiert das Mubea-Gesamtmodell. + +Prueft die von TEST_MUBEA (test_mubea.lsp) erzeugten Ergebnisse: + 1. Alle Eintraege aus mubea.json wurden gebaut (results.json) + 2. Statuswerte (executed / placeholder fuer Separatoren) + 3. Kreisel-Attribute (HOEHE, KREISELART, Block-Praefix) + 4. Vario: VF_-Block mit VF_WINKEL=21 und L_GF-Split 500/4230 + 5. DXF-Geometrie: Block-Anzahlen je Typ (ezdxf) +""" + +import pytest + + +# ============================================================ +# Klassifikation der mubea.json-Eintraege (analog test_mubea.lsp) +# ============================================================ + +def _is_separator(item): + return "block" in item + +def _is_kreisel(item): + return "id" in item and "block" not in item + +def _is_vario(item): + tid = item.get("test_id", "") + return tid.startswith("VF_") + +def _is_gefaelle(item): + tid = item.get("test_id", "") + return tid.startswith("GF_") + + +def _expected_counts(testdata): + return { + "kreisel": sum(1 for t in testdata if _is_kreisel(t)), + "vario": sum(1 for t in testdata if _is_vario(t)), + "gefaellestrecke": sum(1 for t in testdata if _is_gefaelle(t)), + "separator": sum(1 for t in testdata if _is_separator(t)), + } + + +# ============================================================ +# Ergebnis-Pruefung (results.json vs. testdata) +# ============================================================ + +class TestMubeaResults: + + def test_alle_gebaut(self, mubea_testdata, mubea_results): + """Je Kind muss die erwartete Anzahl an Ergebnissen vorliegen.""" + expected = _expected_counts(mubea_testdata) + got = {} + for r in mubea_results: + got[r["kind"]] = got.get(r["kind"], 0) + 1 + for kind, n in expected.items(): + assert got.get(kind, 0) == n, \ + f'{kind}: erwartet {n}, gebaut {got.get(kind, 0)}' + + def test_status_ok(self, mubea_results): + """Alle Ergebnisse muessen 'executed' sein (Separatoren duerfen 'placeholder').""" + for r in mubea_results: + if r["kind"] == "separator": + assert r["status"] in ("executed", "placeholder"), \ + f'{r["test_id"]}: Status={r["status"]}' + else: + assert r["status"] == "executed", \ + f'{r["test_id"]}: Status={r["status"]}' + + def test_kreisel_attribute(self, mubea_testdata, mubea_results): + """Kreisel: HOEHE / KREISELART / Block-Praefix wie in mubea.json erwartet.""" + by_id = {r["test_id"]: r for r in mubea_results} + for t in mubea_testdata: + if not _is_kreisel(t): + continue + r = by_id.get(t["id"]) + assert r, f'Kreisel {t["id"]} fehlt im Ergebnis' + if "expect_hoehe" in t: + assert r["actual_attributes"].get("HOEHE") == t["expect_hoehe"], \ + f'{t["id"]}: HOEHE ist={r["actual_attributes"].get("HOEHE")}, erwartet={t["expect_hoehe"]}' + if "expect_kreiselart" in t: + assert r["actual_attributes"].get("KREISELART") == t["expect_kreiselart"], \ + f'{t["id"]}: KREISELART ist={r["actual_attributes"].get("KREISELART")}' + prefix = t.get("expect_block_prefix", "KREISEL_") + assert r["block_name"].startswith(prefix), \ + f'{t["id"]}: Block "{r["block_name"]}" ohne Praefix "{prefix}"' + + def test_vario_winkel_und_split(self, mubea_testdata, mubea_results): + """Vario: VF_WINKEL entspricht Vorgabe, L_GF-Split = 500/4230 (Meter).""" + by_id = {r["test_id"]: r for r in mubea_results} + for t in mubea_testdata: + if not _is_vario(t): + continue + r = by_id.get(t["test_id"]) + assert r, f'Vario {t["test_id"]} fehlt im Ergebnis' + attrs = r["actual_attributes"] + assert r["block_name"].startswith("VF_"), \ + f'{t["test_id"]}: Block "{r["block_name"]}" ist kein VF_-Block' + if "winkel" in t: + assert attrs.get("VF_WINKEL") == str(t["winkel"]), \ + f'{t["test_id"]}: VF_WINKEL ist={attrs.get("VF_WINKEL")}, erwartet={t["winkel"]}' + # L_GF_m = "vorne,hinten" in Metern -> 500mm/4230mm = "0.500,4.230" + if "L_GF1" in t and "L_GF2" in t and "L_GF_m" in attrs: + teile = attrs["L_GF_m"].split(",") + assert len(teile) == 2, f'{t["test_id"]}: L_GF_m Format "{attrs["L_GF_m"]}"' + v, h = float(teile[0]) * 1000.0, float(teile[1]) * 1000.0 + assert abs(v - t["L_GF1"]) < 1.0, \ + f'{t["test_id"]}: L_GF1 vorn ist={v}, erwartet={t["L_GF1"]}' + assert abs(h - t["L_GF2"]) < 1.0, \ + f'{t["test_id"]}: L_GF2 hinten ist={h}, erwartet={t["L_GF2"]}' + + +# ============================================================ +# DXF Geometrie-Pruefung (ezdxf) +# ============================================================ + +class TestMubeaGeometry: + + def _counts(self, dxf): + c = {} + for e in dxf.modelspace(): + if e.dxftype() == "INSERT": + c[e.dxf.name] = c.get(e.dxf.name, 0) + 1 + return c + + def test_block_anzahlen(self, mubea_dxf, mubea_testdata): + """Anzahl der KREISEL_/VF_/GF_-Bloecke >= Vorgabe aus mubea.json.""" + c = self._counts(mubea_dxf) + n_kr = sum(v for n, v in c.items() if n.startswith("KREISEL_")) + n_vf = sum(v for n, v in c.items() if n.startswith("VF_")) + n_gf = sum(v for n, v in c.items() if n.startswith("GF_")) + exp = _expected_counts(mubea_testdata) + assert n_kr >= exp["kreisel"], f"KREISEL_: {n_kr} < {exp['kreisel']}" + assert n_vf >= exp["vario"], f"VF_: {n_vf} < {exp['vario']}" + assert n_gf >= exp["gefaellestrecke"], f"GF_: {n_gf} < {exp['gefaellestrecke']}" + + def test_handles_im_modelspace(self, mubea_dxf, mubea_results): + """Jedes gebaute Block-Handle (executed) liegt im Modelspace.""" + handles = {e.dxf.handle for e in mubea_dxf.modelspace() + if e.dxftype() == "INSERT"} + for r in mubea_results: + if r["status"] == "executed" and r["block_handle"]: + assert r["block_handle"] in handles, \ + f'{r["test_id"]}: Handle {r["block_handle"]} nicht im Modelspace' diff --git a/tests/testdata/mubea.json b/tests/testdata/mubea.json new file mode 100644 index 0000000..92fa674 --- /dev/null +++ b/tests/testdata/mubea.json @@ -0,0 +1,409 @@ +[ + { + "id": "Kreisel_unten", + "function": "insert", + "x": 400, + "y": -5478, + "z": 1467, + "abstand": 5000, + "rotation": 0.0, + "typ": "STANDARD", + "expect_block_prefix": "KREISEL_", + "expect_hoehe": "1467", + "expect_kreiselart": "STANDARD", + "comment": "x,y aus DXF (500573_60_1.dxf, SP8 Insert #2 (x=-40865.2521,y=5878.2466), raw) abgeleitet; z=1467 unveraendert (Einfuegehoehe, im 2D-Plan nicht enthalten). Vorheriger Handwert: x=800, y=14137" + }, + { + "id": "Kreisel_links", + "function": "insert", + "x": 0, + "y": -5878, + "z": 1467, + "abstand": 20015, + "rotation": 90.0, + "typ": "STANDARD", + "expect_block_prefix": "KREISEL_", + "expect_hoehe": "1467", + "expect_kreiselart": "STANDARD", + "comment": "x,y aus DXF (500573_60_1.dxf, SP8 Insert #1 (x=-50775.7283,y=5878.2466), raw) abgeleitet; z=1467 unveraendert (Einfuegehoehe, im 2D-Plan nicht enthalten). Vorheriger Handwert: x=0, y=14137" + }, + { + "comment": "Separator S-LP #3 Separator bei (-51175.7283,-11702.2510) - vom Nutzer explizit Kreisel_links zugeordnet", + "function": "insert", + "block": "S-LP", + "x": -400, + "y": 11702, + "z": 1467, + "rotation": 270, + "assigned_to": "Kreisel_links" + }, + { + "comment": "Separator S-LP #4 Separator bei (-50375.7283,-12309.7617) - vom Nutzer explizit Kreisel_links zugeordnet", + "function": "insert", + "block": "S-LP", + "x": 399, + "y": 12309, + "z": 1467, + "rotation": 270, + "assigned_to": "Kreisel_links" + }, + { + "comment": "Separator S-LP #7 Separator 'am linken Kreisel oben' bei (-51175.7283,5477.7449) - vom Nutzer explizit Kreisel_links zugeordnet", + "function": "insert", + "block": "S-LP", + "x": -400, + "y": -5477, + "z": 1467, + "rotation": 270, + "assigned_to": "Kreisel_links" + }, + { + "id": "Kreisel_rechts", + "function": "insert", + "x": 9910, + "y": -5878, + "z": 1941, + "abstand": 20015, + "rotation": 90.0, + "typ": "STANDARD", + "expect_block_prefix": "KREISEL_", + "expect_hoehe": "1941", + "expect_kreiselart": "STANDARD", + "comment": "x,y aus DXF (500573_60_1.dxf, AN8 Insert (x=-40865.2521,y=-14812.3513), raw) abgeleitet; z=1941 unveraendert (Einfuegehoehe, im 2D-Plan nicht enthalten). Vorheriger Handwert: x=9910, y=14137" + }, + { + "comment": "Separator S-LP #9 Separator fuer Qualitaetskontrolle bei (-51175.7283,-9002.5827) - vom Nutzer explizit benannt, kein Kreisel/GF-Bezug", + "function": "insert", + "block": "S-LP", + "x": -400, + "y": 9002, + "z": 1467, + "rotation": 270, + "assigned_to": "Qualitaetskontrolle" + }, + { + "test_id": "VF_Schnitt_C", + "richtung": "Auf", + "deltaH": 553, + "winkel": 21, + "gf_verteilung": "manuell", + "L_GF1": 500, + "L_GF2": 4230, + "seite": "rechts", + "hz": 0.0, + "x": 410, + "y": 11526, + "z": 1475, + "comment": "Foerdererwinkel 21 (naechster verfuegbarer Bogenwinkel); Gefaellestrecke vorn L_GF1=500 / hinten L_GF2=4230 (gesamt L_GF=4730); deltaL und L_VF werden beim Bauen fuer winkel=21 und deltaH=553 geloest (L_GF ist linear in deltaL)" + }, + { + "test_id": "GF_Mubea_01", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, 11526, 1941], + "ende_mm": [9510, 12314, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=11526" + }, + { + "comment": "Separator S-LP #1 Y-Abgleich mit Gefaellestrecken (Sep-Y=14158, GF_Mubea_01-Y=11526, diff=2632mm, naechstgelegene Reihe, kein enger Treffer)", + "function": "insert", + "block": "S-LP", + "x": 10310, + "y": 14158, + "z": 1467, + "rotation": 270, + "assigned_to": "GF_Mubea_01" + }, + { + "comment": "Separator S-LP #2 Y-Abgleich mit Gefaellestrecken (Sep-Y=12506, GF_Mubea_01-Y=11526, diff=980mm, naechstgelegene Reihe, kein enger Treffer)", + "function": "insert", + "block": "S-LP", + "x": 9510, + "y": 12506, + "z": 1467, + "rotation": 270, + "assigned_to": "GF_Mubea_01" + }, + { + "comment": "Separator S-LP #5 Y-Abgleich mit Gefaellestrecken (Sep-Y=13737, GF_Mubea_01-Y=11526, diff=2211mm, naechstgelegene Reihe, kein enger Treffer)", + "function": "insert", + "block": "S-LP", + "x": 1415, + "y": 13737, + "z": 1467, + "rotation": 180, + "assigned_to": "GF_Mubea_01" + }, + { + "comment": "Separator S-LP #6 Y-Abgleich mit Gefaellestrecken (Sep-Y=14537, GF_Mubea_01-Y=11526, diff=3011mm, naechstgelegene Reihe, kein enger Treffer)", + "function": "insert", + "block": "S-LP", + "x": 5415, + "y": 14537, + "z": 1467, + "rotation": 180, + "assigned_to": "GF_Mubea_01" + }, + { + "test_id": "GF_Mubea_02", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, 10726, 1941], + "ende_mm": [9510, 11514, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=12326" + }, + { + "test_id": "GF_Mubea_03", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, 9926, 1941], + "ende_mm": [9510, 10714, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=13126" + }, + { + "test_id": "GF_Mubea_04", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, 9126, 1941], + "ende_mm": [9510, 9914, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=13926" + }, + { + "comment": "Separator S-LP #11 Y-Abgleich mit Gefaellestrecken (Sep-Y=8777, GF_Mubea_04-Y=9126, diff=349mm, enger Treffer)", + "function": "insert", + "block": "S-LP", + "x": 10310, + "y": 8777, + "z": 1467, + "rotation": 270, + "assigned_to": "GF_Mubea_04" + }, + { + "test_id": "GF_Mubea_05", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, 8326, 1941], + "ende_mm": [9510, 9114, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=14726" + }, + { + "comment": "Separator S-LP #10 Y-Abgleich mit Gefaellestrecken (Sep-Y=7938, GF_Mubea_05-Y=8326, diff=388mm, enger Treffer)", + "function": "insert", + "block": "S-LP", + "x": 10310, + "y": 7938, + "z": 1467, + "rotation": 270, + "assigned_to": "GF_Mubea_05" + }, + { + "test_id": "GF_Mubea_06", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, 7526, 1941], + "ende_mm": [9510, 8314, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=15526" + }, + { + "test_id": "GF_Mubea_07", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, 6726, 1941], + "ende_mm": [9510, 7514, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=16326" + }, + { + "test_id": "GF_Mubea_08", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, 5926, 1941], + "ende_mm": [9510, 6714, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=17126" + }, + { + "test_id": "GF_Mubea_09", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, 5126, 1941], + "ende_mm": [9510, 5914, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=17926" + }, + { + "test_id": "GF_Mubea_10", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, 4326, 1941], + "ende_mm": [9510, 5114, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=18726" + }, + { + "test_id": "GF_Mubea_11", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, 3526, 1941], + "ende_mm": [9510, 4314, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=19526" + }, + { + "test_id": "GF_Mubea_12", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, 2726, 1941], + "ende_mm": [9510, 3514, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=20326" + }, + { + "test_id": "GF_Mubea_13", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, 1926, 1941], + "ende_mm": [9510, 2714, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=21126" + }, + { + "test_id": "GF_Mubea_14", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, 1126, 1941], + "ende_mm": [9510, 1914, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=21926" + }, + { + "test_id": "GF_Mubea_15", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, 326, 1941], + "ende_mm": [9510, 1114, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=22726" + }, + { + "test_id": "GF_Mubea_16", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, -473, 1941], + "ende_mm": [9510, 314, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=23526" + }, + { + "test_id": "GF_Mubea_17", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, -1273, 1941], + "ende_mm": [9510, -485, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=24326" + }, + { + "test_id": "GF_Mubea_18", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, -2073, 1941], + "ende_mm": [9510, -1285, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=25126" + }, + { + "test_id": "GF_Mubea_19", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, -2873, 1941], + "ende_mm": [9510, -2085, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=25926" + }, + { + "test_id": "GF_Mubea_20", + "modus": 1, + "hz_grad": 0.0, + "start_mm": [399, -3673, 1941], + "ende_mm": [9510, -2885, 1467], + "as_seite": "rechts", + "es_seite": "rechts", + "as_winkel": "90", + "es_winkel": "90", + "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=26726" + }, + { + "comment": "Separator S-LP #8 Y-Abgleich mit Gefaellestrecken (Sep-Y=-4823, GF_Mubea_20-Y=-3673, diff=1150mm, naechstgelegene Reihe, kein enger Treffer)", + "function": "insert", + "block": "S-LP", + "x": 9510, + "y": -4823, + "z": 1467, + "rotation": 270, + "assigned_to": "GF_Mubea_20" + } +]