[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_ |
| `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` |
| `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 |
### Entwickler-Tools (`lib/`) - kein ezdxf, reine Text-/AutoLISP-Verarbeitung
+5 -1
View File
@@ -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"
+33 -1
View File
@@ -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))
+116 -2
View File
@@ -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))
+107 -1
View File
@@ -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}'
+18 -5
View File
@@ -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), \
+13
View File
@@ -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"
}
]
+3 -1
View File
@@ -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.