From 2dd53820603a396bc0656a0e9dcc47e9ac978a17 Mon Sep 17 00:00:00 2001 From: Samer Ayadi Date: Thu, 3 Sep 2026 14:49:46 +0200 Subject: [PATCH] [FEAT] Weitere Objekte der Anlage in hundm05.json bauen (Kreisel) tests/testdata/hundm05.json enthaelt jetzt neben den 5 Spec-Ketten auch andere Objekte der Anlage - erkennbar am Feld "function", im Format der jeweiligen Einzel-Testdaten (ein Kreisel also wie in kreisel_tests.json). Damit beschreibt EINE Datei die ganze Anlage. - tests/test_hundm05.lsp: hundm05:bau-zusatzobjekte sammelt alle Objekte mit "function" und baut sie; hundm05:bau-kreisel ruft kreisel-insert-script (wie tests/test_kreisel.lsp) und liefert einen Ergebnis-Record in derselben Form wie die Ketten. Eigener ssg-start-Rahmen mit ATTREQ/ATTDIA 0, weil vsp-bau-datei seinen schon geschlossen hat und (command "_.INSERT" ...) sonst nach Attributwerten fragt. Je Objekt gefangen, damit ein Fehler die restlichen nicht mitnimmt. - Lisp/vf_spec.lsp: "kind" ist jetzt ein Record-Feld (Default "linienzug") statt eines Literals in vsp-result-json - so schreibt vsp-results-schreiben beide Objektarten in EINE Ergebnisdatei. - lib/vf_spec_export.py: Objekte der Ziel-Datei, die keine Kette beschreiben, werden beim Neuschreiben unveraendert ans Dateiende uebernommen. Ohne das waere jeder von Hand ergaenzte Eintrag beim naechsten Generatorlauf weg - die Datei ist erzeugt UND handgepflegt. spec_aus_flachen_objekten ueberspringt sie jetzt statt zu werfen, genau wie vsp-gruppieren in LISP. - tests/test_hundm05.py: die Ketten-Tests filtern auf kind "linienzug" (Records ohne das Feld stammen aus aelteren Laeufen und waren nur Ketten); neue Klasse TestZusatzobjekte prueft Status, expect_block_prefix, expect_hoehe (Attribut HOEHE), expect_kreiselart (KREISELART) und den Einfuegepunkt - dieselben Erwartungsfelder wie tests/test_kreisel.py. - tests/test_vf_spec.py: neuer Test, dass die Zusatzobjekte die Ketten-Zerlegung nicht beeinflussen - auch nicht, wenn sie mitten zwischen den Sektionen stehen (der eingetragene Kreisel stand zunaechst inmitten der Sub-Knoten von Kette 5; nach dem Regenerieren steht er am Dateiende). Verifiziert: 118 pytest-Tests gruen, Spec-Regenerierung uebernimmt das Zusatzobjekt (58 Objekte), beide .lsp lint-sauber. Die Kreisel-Tests warten auf den naechsten TEST_HUNDM05-Lauf. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 2 +- Lisp/vf_spec.lsp | 6 +- lib/vf_spec_export.py | 34 +++++++++- tests/test_hundm05.lsp | 118 +++++++++++++++++++++++++++++++++- tests/test_hundm05.py | 108 ++++++++++++++++++++++++++++++- tests/test_vf_spec.py | 23 +++++-- tests/testdata/hundm05.json | 13 ++++ tests/testdata/object_data.md | 4 +- 8 files changed, 296 insertions(+), 12 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5570f8d..c5691bc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -168,7 +168,7 @@ Abhaengigkeiten: `ezdxf`, Standard-Library. Benoetigen Umgebungsvariablen (`DXFM | `split_block_library.py` | Teilt `ils_library.dxf` in einzelne DXF-Dateien pro Block auf (mit Abhaengigkeiten). Export-Prefixe: Vario_, Staustrecke_, _3D_AS_, _3D_ES_ | | `vf_journal_export.py` | Liest die Eingabe-Journale der `VF_n`-Bloecke (XDATA-App `SSG_VF_EDIT`, Marker `"linienzug"`) aus einer Zeichnung und schreibt sie als Frage-Antwort-Protokoll im Schema von `tests/testdata/linienzug_tests.json` (Gegenstueck zu `vfl-entry->string`; kein ezdxf, reine Gruppencode-Lesung). Optional `--csv ` fuer Strecken-ID und Erwartungswerte je Kette. Aufruf: `python vf_journal_export.py [--csv ...] [--kein-startpunkt-fix]` — erzeugt `tests/testdata/hm_recformat.json` aus `data/polylines.dxf` (die Aufzeichnung, aus der `tests/testdata/hundm05.json` als Spec abgeleitet wird) | | `dxf_vf_abbild.py` | Abbild der `VF_`/`GF_`-Wrapperbloecke einer Testzeichnung: Attribute UND Bausteinzusammensetzung je Kette, Ketten ueber den Einfuegepunkt der Spec zugeordnet (der Blockname taugt nicht - die VF-Nummer beginnt in jeder Zeichnung neu bei 1). Regressionsnetz fuer Umbauten am Bau-Ablauf: `--referenz` vergleicht gegen `tests/reference/hundm05_attribute.json` (Exit 1 bei Abweichung), `--schreibe-referenz` frischt sie nach einem GEPRUEFTEN Lauf auf. Faengt genau das, was der Ergebnis-Record nicht sieht - eine vertauschte `vfl-acc-*`-Reihenfolge (Komma-Listen `L_VF_m`/`L_GF_m`/`GF_WINKEL`, in der Zeichnung unsichtbar, im Sivas-Export falsch) und eine falsche Blockvariante. Liest ASCII-DXF streamend; binaere DXF kann `dxf_scan_components.py`. Genutzt von `tests/test_vf_geometrie.py` | -| `vf_spec_export.py` | Uebersetzt ein Eingabe-Journal in eine **Spec** und zurueck (`spec_aus_journal` / `journal_aus_spec`) - Gegenstueck zu `vsp-journal` in `Lisp/vf_spec.lsp`, beide muessen dieselbe Tokenfolge liefern. Der Rundlauf `journal_aus_spec(spec_aus_journal(tok)) == tok` laeuft in `tests/test_vf_spec.py` gegen die 5 echten HundM-Ketten und beweist, dass die Spec das Journal verlustfrei abbildet. Schreibt das flache Spec-JSON (vom LISP-Parser lesbar). Aufruf: `python vf_spec_export.py ` — erzeugt `tests/testdata/hundm05.json` (Spec, Eingabe fuer `TEST_HUNDM05`/`VF_SPEC_BAU`) aus `tests/testdata/hm_recformat.json` (Aufzeichnung) | +| `vf_spec_export.py` | Uebersetzt ein Eingabe-Journal in eine **Spec** und zurueck (`spec_aus_journal` / `journal_aus_spec`) - Gegenstueck zu `vsp-journal` in `Lisp/vf_spec.lsp`, beide muessen dieselbe Tokenfolge liefern. Der Rundlauf `journal_aus_spec(spec_aus_journal(tok)) == tok` laeuft in `tests/test_vf_spec.py` gegen die 5 echten HundM-Ketten und beweist, dass die Spec das Journal verlustfrei abbildet. Schreibt das flache Spec-JSON (vom LISP-Parser lesbar). Objekte der Ziel-Datei, die keine Kette beschreiben (von Hand ergaenzte Anlagenteile mit `function`, z.B. Kreisel), werden beim Neuschreiben unveraendert ans Dateiende uebernommen - sonst waeren sie beim naechsten Lauf weg. Aufruf: `python vf_spec_export.py ` — erzeugt `tests/testdata/hundm05.json` (Spec, Eingabe fuer `TEST_HUNDM05`/`VF_SPEC_BAU`) aus `tests/testdata/hm_recformat.json` (Aufzeichnung) | | `dxf_scan_components.py` | Erkennt ILS-/Omniflo-Komponenten in einer **fremden** Projektzeichnung (ohne unsere Wrapper-Bloecke) und schreibt sie im JSON-Schema aus `tests/testdata/object_data.md` — Grundlage fuer Nachbau-Testfaelle wie `tests/testdata/mubea.json`. Liest binaere DXF im GB-Bereich streamend per mmap (ezdxf-DOM waere zu gross), loest INSERTs inkl. OCS/Extrusionsrichtung rekursiv in Weltkoordinaten auf, paart AN8/SP8 zu Kreiseln und Antrieb/Umlenkung zu Foerderern. Aufruf: `python dxf_scan_components.py [--fenster X0 Y0 X1 Y1]` — ohne `--fenster` liefert eine Anlagenzeichnung leicht ueber 1300 Eintraege, zuviel fuer einen Testlauf | ### Entwickler-Tools (`lib/`) - kein ezdxf, reine Text-/AutoLISP-Verarbeitung diff --git a/Lisp/vf_spec.lsp b/Lisp/vf_spec.lsp index d56c968..d9fe841 100644 --- a/Lisp/vf_spec.lsp +++ b/Lisp/vf_spec.lsp @@ -520,6 +520,10 @@ (defun vsp-rec-neu (sid) (list (cons "spec_id" sid) + ;; kind unterscheidet die Objektarten in EINER Ergebnisdatei: die + ;; Testdaten enthalten neben den Ketten auch andere Objekte der + ;; Anlage (Kreisel u.a., siehe tests/test_hundm05.lsp). + (cons "kind" "linienzug") (cons "status" "?") (cons "dimension" "?") (cons "block_name" nil) @@ -586,7 +590,7 @@ (defun vsp-result-json (rec / json) (setq json (strcat " {\n" " \"spec_id\": " (vsp-json-str (ssg-val rec "spec_id")) ",\n" - " \"kind\": \"linienzug\",\n" + " \"kind\": " (vsp-json-str (ssg-val rec "kind")) ",\n" " \"status\": " (vsp-json-str (ssg-val rec "status")) ",\n" " \"dimension\": " (vsp-json-str (ssg-val rec "dimension")) ",\n" " \"block_name\": " (vsp-json-str (ssg-val rec "block_name")) ",\n" diff --git a/lib/vf_spec_export.py b/lib/vf_spec_export.py index 9801f83..62d2e2b 100644 --- a/lib/vf_spec_export.py +++ b/lib/vf_spec_export.py @@ -624,6 +624,26 @@ def flache_objekte(spec_id, spec, kopf_extra=None): return objekte +def fremde_objekte(pfad): + """Objekte einer bestehenden Ziel-Datei, die KEINE Spec beschreiben. + + Die Spec-Testdaten werden erzeugt, die Datei ist aber zugleich die + Stelle, an der weitere Objekte der Anlage von Hand nachgetragen werden + (Kreisel, Foerderer, ...). Alles ohne spec_id/glied/sub gehoert nicht + zum Generator und wird beim Neuschreiben uebernommen - sonst waere ein + handgeschriebener Eintrag beim naechsten Lauf weg. + """ + if not pfad or not os.path.isfile(pfad): + return [] + try: + with open(pfad, encoding="utf-8") as fh: + daten = json.load(fh) + except ValueError: + return [] + return [o for o in daten + if not ("spec_id" in o or "glied" in o or "sub" in o)] + + def spec_aus_flachen_objekten(objekte): """Umkehrung von flache_objekte - dieselbe Gruppierregel wie hmrec:gruppiere in LISP (Kopf, dann glied/sub in Reihenfolge).""" @@ -670,7 +690,11 @@ def spec_aus_flachen_objekten(objekte): sek.setdefault("vf", collections.OrderedDict()) sek["vf"].setdefault("subs", []).append(sub) else: - raise ValueError("Objekt ohne spec_id/glied/sub: %r" % obj) + # Von Hand ergaenzte Objekte der Anlage (Kreisel u.a.) gehoeren + # nicht zur Spec und werden uebersprungen - genauso wie + # vsp-gruppieren in Lisp/vf_spec.lsp es tut. Sie stehen in + # derselben Datei, damit eine Anlage EINE Beschreibung hat. + continue return specs @@ -733,6 +757,14 @@ def main(): % (PREFIX, fehler)) return 1 + # Von Hand ergaenzte Objekte der Ziel-Datei ans Ende uebernehmen. + fremde = fremde_objekte(args.ziel) + if fremde: + print("%s %d von Hand ergaenzte(s) Objekt(e) uebernommen: %s" + % (PREFIX, len(fremde), + ", ".join(str(o.get("id", "?")) for o in fremde))) + alle.extend(fremde) + text = json.dumps(alle, indent=2, ensure_ascii=False) with open(args.ziel, "w", encoding="utf-8", newline="\r\n") as fh: fh.write(arrays_einzeilig(text)) diff --git a/tests/test_hundm05.lsp b/tests/test_hundm05.lsp index ab613cd..37fd11f 100644 --- a/tests/test_hundm05.lsp +++ b/tests/test_hundm05.lsp @@ -46,6 +46,117 @@ (strcat (getenv "DXFMAKRO") "/tests/testdata/hundm05.json")) +;; ============================================================ +;; Weitere Objekte der Anlage (nicht-Ketten) +;; ============================================================ +;; Neben den Spec-Ketten (Kopf "spec_id" + "glied"/"sub") duerfen in +;; hundm05.json auch andere Objekte der Anlage stehen - erkennbar an +;; "function". Format wie in tests/testdata/kreisel_tests.json, damit die +;; Felder nicht zweimal beschrieben werden muessen (Schema: siehe +;; tests/testdata/object_data.md Abschnitt 1). +;; vsp-gruppieren ignoriert diese Objekte, sie stoeren die Ketten also nicht. +;; lib/vf_spec_export.py uebernimmt sie beim Neuerzeugen der Spec-Daten +;; unveraendert ans Dateiende (sonst waeren sie beim naechsten Lauf weg). +;; +;; Bisher unterstuetzt: Kreisel (function "insert", wie test_kreisel.lsp). +;; Weitere Arten kommen als eigener cond-Zweig dazu. + +(defun hundm05:zusatz-objekt-p (obj) + (and (ssg-val obj "function") + (null (ssg-val obj "spec_id")) + (null (ssg-val obj "glied")) + (null (ssg-val obj "sub")))) + +;; Kreisel bauen (kreisel-insert-script wie in tests/test_kreisel.lsp). +;; Rueckgabe: Ergebnis-Record in derselben Form wie die Ketten, damit +;; vsp-results-schreiben beide in EINE Datei schreibt (unterschieden ueber +;; das Feld "kind"). +(defun hundm05:bau-kreisel (obj / id x y z abstand rotation typ pt ent ed rec) + (setq id (ssg-val obj "id")) + (setq x (ssg-val obj "x") + y (ssg-val obj "y") + z (ssg-val obj "z") + abstand (ssg-val obj "abstand") + rotation (ssg-val obj "rotation") + typ (ssg-val obj "typ")) + (if (numberp x) (setq x (float x))) + (if (numberp y) (setq y (float y))) + (if (numberp z) (setq z (float z))) + (if (numberp abstand) (setq abstand (float abstand))) + (if (numberp rotation) (setq rotation (float rotation))) + (if (null typ) (setq typ "STANDARD")) + (setq rec (vsp-rec-neu (if id id "(ohne id)"))) + (setq rec (vsp-set rec "kind" "kreisel")) + (setq rec (vsp-set rec "dimension" (vsp-dim-text))) + (cond + ((not (and (numberp x) (numberp y) (numberp abstand))) + (princ (strcat "\n [KREISEL] " (if id id "?") + " - FEHLER: x, y und abstand muessen Zahlen sein")) + (vsp-set (vsp-set rec "status" "spec-fehler") "fehler_text" + "x, y oder abstand fehlt bzw. ist keine Zahl")) + (T + (setq pt (list x y z)) + (princ (strcat "\n [KREISEL] " id " -> (" (rtos x 2 0) ", " + (rtos y 2 0) ", " (rtos (if z z 0.0) 2 0) ") A=" + (rtos abstand 2 0) " R=" (rtos (if rotation rotation 0.0) 2 1) + " " typ)) + (setq ent (kreisel-insert-script pt abstand + (if rotation rotation 0.0) typ z)) + (if ent + (progn + (setq ed (entget ent)) + (princ (strcat " -> Block " (cdr (assoc 2 ed)))) + (setq rec (vsp-set rec "block_name" (cdr (assoc 2 ed)))) + (setq rec (vsp-set rec "block_handle" (cdr (assoc 5 ed)))) + (setq rec (vsp-set rec "insert_point" (cdr (assoc 10 ed)))) + (setq rec (vsp-set rec "attribs" (ssg-attrib-read ent))) + (vsp-set rec "status" "executed")) + (progn + (princ " FEHLER: kein Kreisel-Block entstanden") + (vsp-set (vsp-set rec "status" "abbruch") "fehler_text" + "kreisel-insert-script lieferte nil")))))) + +;; Alle Zusatzobjekte der Datei bauen. Eigener ssg-start-Rahmen, weil +;; vsp-bau-datei seinen schon geschlossen hat; ATTREQ/ATTDIA 0 ist Pflicht - +;; sonst fragt (command "_.INSERT" ...) nach Attributwerten. +(defun hundm05:bau-zusatzobjekte (datei / daten objekte out rec) + (setq daten (ssg-load-json datei)) + (setq objekte '()) + (foreach obj daten + (if (hundm05:zusatz-objekt-p obj) (setq objekte (cons obj objekte)))) + (setq objekte (reverse objekte)) + (cond + ((null objekte) nil) + ((null (car (atoms-family 1 '("KREISEL-INSERT-SCRIPT")))) + (princ (strcat "\n[TEST_HUNDM05] WARNUNG: " (itoa (length objekte)) + " Zusatzobjekt(e) uebersprungen - KreiselInsert nicht" + " geladen.")) + nil) + (T + (princ (strcat "\n " (itoa (length objekte)) + " weitere(s) Objekt(e) der Anlage")) + (ssg-start "TEST_HUNDM05_ZUSATZ" + '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA"))) + (setvar "OSMODE" 0) + (setvar "ATTREQ" 0) + (setvar "ATTDIA" 0) + (setq out '()) + (foreach obj objekte + (setq rec (vl-catch-all-apply 'hundm05:bau-kreisel (list obj))) + (if (vl-catch-all-error-p rec) + (progn + (princ (strcat "\n [KREISEL] Ausnahme: " + (vl-catch-all-error-message rec))) + (setq rec (vsp-set (vsp-set (vsp-set + (vsp-rec-neu (ssg-val obj "id")) + "kind" "kreisel") "status" "abbruch") + "fehler_text" + (vl-catch-all-error-message rec))))) + (setq out (cons rec out))) + (ssg-end) + (reverse out)))) + + ;; ============================================================ ;; Testlauf ;; ============================================================ @@ -53,7 +164,7 @@ (defun c:TEST_HUNDM05 ( / datei) (setq datei (hundm05:spec-datei)) (princ "\n\n================================================================") - (princ "\n TEST_HUNDM05 - 5 Linienzuege der Anlage HundM (aus der Spec)") + (princ "\n TEST_HUNDM05 - Anlage HundM aus der Spec (5 Linienzuege)") (princ (strcat "\n " datei)) (princ "\n================================================================") (cond @@ -65,7 +176,10 @@ (princ (strcat "\n[TEST_HUNDM05] FEHLER: " datei " nicht gefunden!")) (setq *hundm05-records* nil)) (T - (setq *hundm05-records* (vsp-bau-datei datei)))) + (vsp-modul-sichern "KreiselInsert") + (setq *hundm05-records* (vsp-bau-datei datei)) + (setq *hundm05-records* + (append *hundm05-records* (hundm05:bau-zusatzobjekte datei))))) (princ)) diff --git a/tests/test_hundm05.py b/tests/test_hundm05.py index 4c02881..403808e 100644 --- a/tests/test_hundm05.py +++ b/tests/test_hundm05.py @@ -38,7 +38,7 @@ def spec_objekte(): @pytest.fixture(scope="module") -def spec_ergebnisse(): +def alle_ergebnisse(): for pfad in ERGEBNIS_ORTE: if os.path.isfile(pfad): with open(pfad, encoding="utf-8") as fh: @@ -47,6 +47,37 @@ def spec_ergebnisse(): "ausfuehren") +@pytest.fixture(scope="module") +def spec_ergebnisse(alle_ergebnisse): + """Nur die Ketten. Die Ergebnisdatei enthaelt seit dem Nachtragen + weiterer Anlagenteile mehrere Objektarten, unterschieden ueber "kind" + (Records ohne das Feld stammen aus einem aelteren Lauf und waren + ausschliesslich Ketten).""" + return [r for r in alle_ergebnisse + if r.get("kind", "linienzug") == "linienzug"] + + +@pytest.fixture(scope="module") +def kreisel_ergebnisse(alle_ergebnisse): + raus = [r for r in alle_ergebnisse if r.get("kind") == "kreisel"] + if not raus: + pytest.skip("keine Kreisel-Ergebnisse (Lauf vor dem Nachtragen " + "weiterer Anlagenteile?)") + return raus + + +@pytest.fixture(scope="module") +def kreisel_objekte(spec_objekte): + """Die nachgetragenen Anlagenteile aus den Testdaten - alles mit + "function", das keine Kette beschreibt.""" + raus = [o for o in spec_objekte + if o.get("function") and not ("spec_id" in o or "glied" in o + or "sub" in o)] + if not raus: + pytest.skip("keine Zusatzobjekte in tests/testdata/hundm05.json") + return raus + + class TestSpecBau: def test_je_spec_ein_ergebnis(self, spec_objekte, spec_ergebnisse): @@ -142,3 +173,78 @@ class TestSpecBau: f'{spec_r["spec_id"]}: Attribut {tag} ist ' \ f'{spec_r["actual_attributes"].get(tag)!r}, ' \ f'Journal-Pfad hatte {wert!r}' + + +# ============================================================ +# Weitere Objekte der Anlage (Kreisel) +# ============================================================ +# tests/testdata/hundm05.json enthaelt neben den Ketten auch andere Objekte +# der Anlage - Format wie tests/testdata/kreisel_tests.json (Schema in +# tests/testdata/object_data.md Abschnitt 1). Gebaut werden sie von +# hundm05:bau-zusatzobjekte in tests/test_hundm05.lsp. + +class TestZusatzobjekte: + + def test_je_objekt_ein_ergebnis(self, kreisel_objekte, + kreisel_ergebnisse): + soll = [o["id"] for o in kreisel_objekte] + ist = [r["spec_id"] for r in kreisel_ergebnisse] + assert ist == soll, f"Ergebnisse {ist}, erwartet {soll}" + + def test_status_executed(self, kreisel_ergebnisse): + for r in kreisel_ergebnisse: + assert r["status"] == "executed", \ + f'{r["spec_id"]}: Status={r["status"]}, {r.get("fehler_text")}' + + def test_block_prefix_wie_erwartet(self, kreisel_objekte, + kreisel_ergebnisse): + """expect_block_prefix aus den Testdaten - bei Kreiseln KREISEL_, + weil die Haelften zu einem Wrapper-Block zusammengefasst werden.""" + erwartet = dict((o["id"], o.get("expect_block_prefix")) + for o in kreisel_objekte) + for r in kreisel_ergebnisse: + pref = erwartet.get(r["spec_id"]) + if not pref: + continue + assert r["block_name"].startswith(pref), \ + f'{r["spec_id"]}: Block {r["block_name"]!r} beginnt nicht ' \ + f'mit {pref!r}' + + def test_hoehe_wie_erwartet(self, kreisel_objekte, kreisel_ergebnisse): + erwartet = dict((o["id"], o.get("expect_hoehe")) + for o in kreisel_objekte) + for r in kreisel_ergebnisse: + soll = erwartet.get(r["spec_id"]) + if soll is None: + continue + ist = r["actual_attributes"].get("HOEHE") + assert ist == str(soll), \ + f'{r["spec_id"]}: HOEHE ist {ist!r}, erwartet {soll!r}' + + def test_kreiselart_wie_erwartet(self, kreisel_objekte, + kreisel_ergebnisse): + erwartet = dict((o["id"], o.get("expect_kreiselart")) + for o in kreisel_objekte) + for r in kreisel_ergebnisse: + soll = erwartet.get(r["spec_id"]) + if soll is None: + continue + ist = r["actual_attributes"].get("KREISELART") + assert ist == str(soll), \ + f'{r["spec_id"]}: KREISELART ist {ist!r}, erwartet {soll!r}' + + def test_einfuegepunkt_getroffen(self, kreisel_objekte, + kreisel_ergebnisse): + """Der Einfuegepunkt des Wrapper-Blocks ist der in den Testdaten + angegebene Punkt.""" + soll = dict((o["id"], [o.get("x"), o.get("y"), o.get("z")]) + for o in kreisel_objekte) + for r in kreisel_ergebnisse: + erwartet = soll.get(r["spec_id"]) + if not erwartet or erwartet[0] is None: + continue + for achse, (e, i) in enumerate(zip(erwartet, r["insert_point"])): + if e is None: + continue + assert abs(float(e) - float(i)) < 1.0, \ + f'{r["spec_id"]}: Achse {achse} ist {i}, erwartet {e}' diff --git a/tests/test_vf_spec.py b/tests/test_vf_spec.py index a957d37..4ddef7e 100644 --- a/tests/test_vf_spec.py +++ b/tests/test_vf_spec.py @@ -164,11 +164,23 @@ class TestSpecDaten: f'Token {idx} - {was}' def test_objekte_zuordenbar(self, spec_objekte): - """Jedes Objekt ist Kopf, Sektion oder Sub - die Gruppierregel von - vsp-gruppieren kennt nichts anderes.""" + """Jedes Objekt ist Kopf, Sektion, Sub - oder ein von Hand + ergaenztes Anlagenteil (erkennbar an "function", z.B. ein Kreisel). + Etwas anderes kennt weder vsp-gruppieren noch der Generator.""" for i, obj in enumerate(spec_objekte): - assert ("spec_id" in obj) or ("glied" in obj) or ("sub" in obj), \ - f"Objekt {i} ist weder Kopf, Sektion noch Sub: {obj}" + assert ("spec_id" in obj) or ("glied" in obj) or ("sub" in obj) \ + or ("function" in obj), \ + f"Objekt {i} ist weder Kette noch Anlagenteil: {obj}" + + def test_fremde_objekte_stoeren_die_ketten_nicht(self, spec_objekte): + """Die von Hand ergaenzten Anlagenteile duerfen die Ketten-Zerlegung + nicht beeinflussen - auch nicht, wenn sie mitten zwischen den + Sektionen stehen.""" + fremde = [o for o in spec_objekte if "function" in o] + ohne = [o for o in spec_objekte if "function" not in o] + assert (len(vse.spec_aus_flachen_objekten(spec_objekte)) + == len(vse.spec_aus_flachen_objekten(ohne))), \ + f"{len(fremde)} Zusatzobjekt(e) veraendern die Kettenzahl" def test_kopf_felder(self, spec_objekte): for obj in spec_objekte: @@ -221,7 +233,8 @@ class TestSpecDaten: def test_flach_fuer_lisp_parser(self, spec_objekte): """ssg-parse-json-array liest zeilenweise: ein "{" beginnt ein - Objekt, Verschachtelung ist unmoeglich.""" + Objekt, Verschachtelung ist unmoeglich. Gilt auch fuer die von Hand + ergaenzten Anlagenteile.""" for obj in spec_objekte: for k, v in obj.items(): assert not isinstance(v, dict), \ diff --git a/tests/testdata/hundm05.json b/tests/testdata/hundm05.json index 2240fe3..37a3269 100644 --- a/tests/testdata/hundm05.json +++ b/tests/testdata/hundm05.json @@ -414,5 +414,18 @@ "nr": 4, "winkel": "90", "seite": "links" + }, + { + "id": "KR_Kreuz_Q2_90Grad", + "function": "insert", + "x": 1544, + "y": 767, + "z": 2154, + "abstand": 23300, + "rotation": 0, + "typ": "STANDARD", + "expect_block_prefix": "KREISEL_", + "expect_hoehe": "2154", + "expect_kreiselart": "STANDARD" } ] diff --git a/tests/testdata/object_data.md b/tests/testdata/object_data.md index 4edf031..7135125 100644 --- a/tests/testdata/object_data.md +++ b/tests/testdata/object_data.md @@ -647,7 +647,9 @@ Zwei Dateien beschreiben dieselben 5 Ketten, in zwei Formaten: | Datei | Inhalt | Test | |---|---|---| | `tests/testdata/hm_recformat.json` | die **Aufzeichnung**: rohes Eingabe-Journal aus der XDATA der Kundenzeichnung, als Frage-Antwort-Protokoll | `TEST_HM_RECFORMAT` (in `alltests.json` abgeschaltet) | -| `tests/testdata/hundm05.json` | die daraus abgeleitete **Spec** (Domaenenwerte statt Menue-Codes, 52 statt 281 Objekte) - hier werden weitere Ketten nachgetragen | `TEST_HUNDM05` | +| `tests/testdata/hundm05.json` | die daraus abgeleitete **Spec** (Domaenenwerte statt Menue-Codes, 52 statt 281 Objekte) - hier werden weitere Teile der Anlage nachgetragen | `TEST_HUNDM05` | + +In `hundm05.json` stehen neben den Ketten auch **andere Objekte der Anlage**, erkennbar am Feld `function` - Format wie in den jeweiligen Einzel-Testdaten (ein Kreisel also wie in `kreisel_tests.json`, Schema in Abschnitt 1). Damit beschreibt EINE Datei die ganze Anlage. Der Ketten-Uebersetzer ueberspringt diese Objekte (`vsp-gruppieren`), `hundm05:bau-zusatzobjekte` in `tests/test_hundm05.lsp` baut sie, und `lib/vf_spec_export.py` uebernimmt sie beim Neuerzeugen der Spec-Daten. Im Ergebnis-JSON unterscheidet das Feld `kind` die Objektarten (`linienzug` / `kreisel`). Die Spec ist das Eingabeformat, die Aufzeichnung die Vorlage, an der sie gemessen wird (Rundlauf-Beweis in `tests/test_vf_spec.py`). Warum beide bleiben: die Aufzeichnung ist die einzige eingecheckte Kopie der Originaldaten (`data/polylines.dxf` ist zu gross fuer das Repo) UND der einzige Test dafuer, dass ein BESTEHENDER `VF_n`-Block weiter abspielbar ist - also dass Doppelklick-Edit und 2D/3D-Konvertierung an Altbestand funktionieren. Gebaut wird routinemaessig nur noch aus der Spec, damit nicht zweimal dieselbe Geometrie entsteht.