[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) <noreply@anthropic.com>
This commit is contained in:
2026-09-03 14:49:46 +02:00
parent e91f1d00d0
commit 2dd5382060
8 changed files with 296 additions and 12 deletions
+1 -1
View File
@@ -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_ | | `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 <sivas_export.csv>` fuer Strecken-ID und Erwartungswerte je Kette. Aufruf: `python vf_journal_export.py <dxf> <output.json> [--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) | | `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 <sivas_export.csv>` fuer Strecken-ID und Erwartungswerte je Kette. Aufruf: `python vf_journal_export.py <dxf> <output.json> [--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` | | `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 <protokoll.json> <spec.json>` — 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 <protokoll.json> <spec.json>` — 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 <dxf> <data_dir> <output.json> [--fenster X0 Y0 X1 Y1]` — ohne `--fenster` liefert eine Anlagenzeichnung leicht ueber 1300 Eintraege, zuviel fuer einen Testlauf | | `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 <dxf> <data_dir> <output.json> [--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 ### Entwickler-Tools (`lib/`) - kein ezdxf, reine Text-/AutoLISP-Verarbeitung
+5 -1
View File
@@ -520,6 +520,10 @@
(defun vsp-rec-neu (sid) (defun vsp-rec-neu (sid)
(list (cons "spec_id" 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 "status" "?")
(cons "dimension" "?") (cons "dimension" "?")
(cons "block_name" nil) (cons "block_name" nil)
@@ -586,7 +590,7 @@
(defun vsp-result-json (rec / json) (defun vsp-result-json (rec / json)
(setq json (strcat " {\n" (setq json (strcat " {\n"
" \"spec_id\": " (vsp-json-str (ssg-val rec "spec_id")) ",\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" " \"status\": " (vsp-json-str (ssg-val rec "status")) ",\n"
" \"dimension\": " (vsp-json-str (ssg-val rec "dimension")) ",\n" " \"dimension\": " (vsp-json-str (ssg-val rec "dimension")) ",\n"
" \"block_name\": " (vsp-json-str (ssg-val rec "block_name")) ",\n" " \"block_name\": " (vsp-json-str (ssg-val rec "block_name")) ",\n"
+33 -1
View File
@@ -624,6 +624,26 @@ def flache_objekte(spec_id, spec, kopf_extra=None):
return objekte 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): def spec_aus_flachen_objekten(objekte):
"""Umkehrung von flache_objekte - dieselbe Gruppierregel wie """Umkehrung von flache_objekte - dieselbe Gruppierregel wie
hmrec:gruppiere in LISP (Kopf, dann glied/sub in Reihenfolge).""" 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.setdefault("vf", collections.OrderedDict())
sek["vf"].setdefault("subs", []).append(sub) sek["vf"].setdefault("subs", []).append(sub)
else: 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 return specs
@@ -733,6 +757,14 @@ def main():
% (PREFIX, fehler)) % (PREFIX, fehler))
return 1 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) text = json.dumps(alle, indent=2, ensure_ascii=False)
with open(args.ziel, "w", encoding="utf-8", newline="\r\n") as fh: with open(args.ziel, "w", encoding="utf-8", newline="\r\n") as fh:
fh.write(arrays_einzeilig(text)) fh.write(arrays_einzeilig(text))
+116 -2
View File
@@ -46,6 +46,117 @@
(strcat (getenv "DXFMAKRO") "/tests/testdata/hundm05.json")) (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 ;; Testlauf
;; ============================================================ ;; ============================================================
@@ -53,7 +164,7 @@
(defun c:TEST_HUNDM05 ( / datei) (defun c:TEST_HUNDM05 ( / datei)
(setq datei (hundm05:spec-datei)) (setq datei (hundm05:spec-datei))
(princ "\n\n================================================================") (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 (strcat "\n " datei))
(princ "\n================================================================") (princ "\n================================================================")
(cond (cond
@@ -65,7 +176,10 @@
(princ (strcat "\n[TEST_HUNDM05] FEHLER: " datei " nicht gefunden!")) (princ (strcat "\n[TEST_HUNDM05] FEHLER: " datei " nicht gefunden!"))
(setq *hundm05-records* nil)) (setq *hundm05-records* nil))
(T (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)) (princ))
+107 -1
View File
@@ -38,7 +38,7 @@ def spec_objekte():
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def spec_ergebnisse(): def alle_ergebnisse():
for pfad in ERGEBNIS_ORTE: for pfad in ERGEBNIS_ORTE:
if os.path.isfile(pfad): if os.path.isfile(pfad):
with open(pfad, encoding="utf-8") as fh: with open(pfad, encoding="utf-8") as fh:
@@ -47,6 +47,37 @@ def spec_ergebnisse():
"ausfuehren") "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: class TestSpecBau:
def test_je_spec_ein_ergebnis(self, spec_objekte, spec_ergebnisse): 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["spec_id"]}: Attribut {tag} ist ' \
f'{spec_r["actual_attributes"].get(tag)!r}, ' \ f'{spec_r["actual_attributes"].get(tag)!r}, ' \
f'Journal-Pfad hatte {wert!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}'
+18 -5
View File
@@ -164,11 +164,23 @@ class TestSpecDaten:
f'Token {idx} - {was}' f'Token {idx} - {was}'
def test_objekte_zuordenbar(self, spec_objekte): def test_objekte_zuordenbar(self, spec_objekte):
"""Jedes Objekt ist Kopf, Sektion oder Sub - die Gruppierregel von """Jedes Objekt ist Kopf, Sektion, Sub - oder ein von Hand
vsp-gruppieren kennt nichts anderes.""" 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): for i, obj in enumerate(spec_objekte):
assert ("spec_id" in obj) or ("glied" in obj) or ("sub" in obj), \ assert ("spec_id" in obj) or ("glied" in obj) or ("sub" in obj) \
f"Objekt {i} ist weder Kopf, Sektion noch Sub: {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): def test_kopf_felder(self, spec_objekte):
for obj in spec_objekte: for obj in spec_objekte:
@@ -221,7 +233,8 @@ class TestSpecDaten:
def test_flach_fuer_lisp_parser(self, spec_objekte): def test_flach_fuer_lisp_parser(self, spec_objekte):
"""ssg-parse-json-array liest zeilenweise: ein "{" beginnt ein """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 obj in spec_objekte:
for k, v in obj.items(): for k, v in obj.items():
assert not isinstance(v, dict), \ assert not isinstance(v, dict), \
+13
View File
@@ -414,5 +414,18 @@
"nr": 4, "nr": 4,
"winkel": "90", "winkel": "90",
"seite": "links" "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"
} }
] ]
+3 -1
View File
@@ -647,7 +647,9 @@ Zwei Dateien beschreiben dieselben 5 Ketten, in zwei Formaten:
| Datei | Inhalt | Test | | 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/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. 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.