[REFACTOR] Stufe 2: VF-Eingang/-Ausgang getrennt + Regressionsnetz fuer die Geometrie
Der CAD-Lauf belegt die Schritte 0-4 jetzt auch EMPIRISCH: results/test_hundm05.dxf entstand nach dem Umbau, das Ergebnis-JSON von 11:18 davor - fuer alle 5 Ketten sind ALLE 37 Attribute identisch, inklusive der reihenfolgeabhaengigen Komma-Listen L_VF_m, L_GF_m, GF_WINKEL und ANTRIEBFAHRTRICHTUNG. TEST_VF_SPEC: 28 PASS / 0 FAIL. results/test_linienzug.dxf zeigt eine vollstaendige Kette (AS+ES, Motor, Umlenkung, 2 Vario-Kurven, 22 Bausteine, HOEHE_VON = HOEHE_BIS = 4500 bei DELTA_H 0). Schritt 5a/5b - vfl-vf-eingang-bauen und vfl-vf-ausgang-bauen: beide Bloecke (GF1 + Einlauf-Separator + Umlenkstation bzw. Motorstation + GF2) enthalten keine Frage und sind zusammenhaengend, der Umzug ist wortwoertlich (Zeilenvergleich: 0 Zeilen entfernt, nur zwei Koepfe, zwei Aufrufe und zwei "frame)" neu). Die Auswahl gf2-eff (ziel-gf2 im Kettenende-Modus, sonst L_GF2-bau) bleibt BEWUSST an der Aufrufstelle - genau dort entsteht sonst still die falsche GF2 hinter dem Motor. Der Separator-Zaehler und die vfl-acc-*-Aufrufe bleiben an derselben Stelle in derselben Reihenfolge. Regressionsnetz, damit die restlichen Schritte nicht blind gemacht werden muessen: - lib/dxf_vf_abbild.py (neu): liest eine Testzeichnung streamend und bildet je Kette Attribute, Bausteinzusammensetzung und Einfuegepunkt ab. Ketten werden ueber den EINFUEGEPUNKT der Spec zugeordnet, nicht ueber den Blocknamen - die VF-Nummer beginnt in jeder neuen Zeichnung wieder bei 1. ID und Bezeichnung sind aus dem Vergleich heraus (pro Zeichnung neu). --referenz vergleicht (Exit 1 bei Abweichung), --schreibe-referenz frischt die Referenz nach einem GEPRUEFTEN Lauf auf. - tests/reference/hundm05_attribute.json (neu): die Referenz, erzeugt aus dem geprueften Lauf - also dem Stand, der attributgleich mit dem Zustand VOR dem Umbau ist. - tests/test_vf_geometrie.py (neu, 8 Tests): vergleicht automatisch, mit einem eigenen Test nur fuer die Komma-Listen (das einzige, was eine vertauschte vfl-acc-*-Reihenfolge sichtbar macht) und einer Gegenprobe, dass eine verfaelschte Referenz auch erkannt wird. Das faengt, was der Ergebnis-Record NICHT sieht: er sagt "executed, kein Prompt, Journal aufgegangen", aber nichts darueber, ob dieselbe Geometrie herauskam. Ablauf ab jetzt pro Schritt: umbauen -> TEST_HUNDM05 (save dxf) -> pytest tests/test_vf_geometrie.py. Offen bleiben 5c, 6, 8, 9, 10 (Schleifenrumpf der VF-Einheit, Daten-Executor, Einheit-Abschluss, Kettenebene je Glied, vfl-spec-ausfuehren). Die vier haengen zusammen: 6 und 10 brauchen den aufgeteilten Schleifenrumpf. Halb umgebaut waere der Code schlechter dran als vor oder nach dem Umbau, darum als ein Schritt mit CAD-Lauf davor und danach. Verifiziert: 117 pytest-Tests gruen, vf_linienzug.lsp lint-sauber. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -167,6 +167,7 @@ Abhaengigkeiten: `ezdxf`, Standard-Library. Benoetigen Umgebungsvariablen (`DXFM
|
||||
| `export_csv.py` | Erzeugt einfache Item-Liste als CSV (ohne Summierung). Aufruf: `python export_csv.py <export_raw.json> <data_dir> <output.csv>` |
|
||||
| `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) |
|
||||
| `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 |
|
||||
|
||||
|
||||
+38
-13
@@ -2704,6 +2704,37 @@
|
||||
)
|
||||
)
|
||||
|
||||
;; --- VF-Einheit: Eingang (reiner Bauteil) ---
|
||||
;; GF1 + Einlauf-Separator + Umlenkstation. Keine Frage, kein Dialog.
|
||||
;; Rueckgabe: neuer Frame (Ausgang der Umlenkstation, 3-Grad-Basis).
|
||||
;; Der Separator-Zaehler wird hier erhoeht, weil vfs-vf-entry den Separator
|
||||
;; mitbaut - die *vfl-acc-*-Aufrufe muessen an derselben Stelle in derselben
|
||||
;; Reihenfolge bleiben, sonst vertauschen sich die Komma-Listen L_VF_m/L_GF_m
|
||||
;; im Sivas-Export (in der Zeichnung unsichtbar).
|
||||
(defun vfl-vf-eingang-bauen (frame hz1 L_GF1-bau)
|
||||
(setq frame (vfl-frame-3grad (vfs-vf-entry (car frame) L_GF1-bau hz1) hz1))
|
||||
(if (> L_GF1-bau 0.1)
|
||||
(vfl-acc-gf-seg L_GF1-bau (ssg-cfg-or "vario" "gefaelle_winkel" 3)))
|
||||
(setq *vfl-acc-separator* (1+ *vfl-acc-separator*)) ; Einlauf-Separator (in vfs-vf-entry)
|
||||
frame)
|
||||
|
||||
;; --- VF-Einheit: Ausgang (reiner Bauteil) ---
|
||||
;; Motorstation [+ GF2], KEIN Separator - der sitzt erst vor dem ES-Element
|
||||
;; bzw. optional zwischen zwei Foerderern.
|
||||
;; gf2-eff kommt FERTIG herein (siehe Aufrufstelle).
|
||||
(defun vfl-vf-ausgang-bauen (frame letzt-hz gf2-eff)
|
||||
;; Motorseite erfassen: derzeit immer "rechts" (nur diese DWG vorhanden;
|
||||
;; die "links"-Einzel-DWG wird spaeter ergaenzt).
|
||||
(setq *vfl-acc-motorseite* (append *vfl-acc-motorseite* (list "rechts")))
|
||||
;; Motorstation braucht 3-Grad-Basis: falls die Kette gerade flach endet
|
||||
;; (horizontales Stueck ohne ab_3), hier den ab_3-Uebergang nachholen.
|
||||
(setq frame (vfl-nach-3grad frame))
|
||||
(setq frame (vfl-frame-3grad
|
||||
(vfs-vf-exit (car frame) gf2-eff letzt-hz nil) letzt-hz))
|
||||
(if (> gf2-eff 0.1)
|
||||
(vfl-acc-gf-seg gf2-eff (ssg-cfg-or "vario" "gefaelle_winkel" 3)))
|
||||
frame)
|
||||
|
||||
;; Rueckgabe: (frame anzahl-koerper ziel-ende)
|
||||
(defun vfl-vf-einheit (frame hz1 richtung1 winkel1 L_GF1 L_VF1 gf-am-ausgang /
|
||||
p-umlenk vf-count antwort L_GF-eff L_GF1-bau L_GF2-bau
|
||||
@@ -2721,11 +2752,10 @@
|
||||
(setq L_GF1-bau L_GF-eff L_GF2-bau 0.0)) ; alles am Einlauf
|
||||
|
||||
;; --- Eingang: GF1 + Separator + Umlenkstation ---
|
||||
;; entry-start/p-umlenk sind der Stand VOR bzw. NACH dem Eingang - die
|
||||
;; horizontale Erstkoerper-Rechnung braucht beide (gemessener Fussabdruck).
|
||||
(setq entry-start (car frame))
|
||||
(setq frame (vfl-frame-3grad (vfs-vf-entry (car frame) L_GF1-bau hz1) hz1))
|
||||
(if (> L_GF1-bau 0.1)
|
||||
(vfl-acc-gf-seg L_GF1-bau (ssg-cfg-or "vario" "gefaelle_winkel" 3)))
|
||||
(setq *vfl-acc-separator* (1+ *vfl-acc-separator*)) ; Einlauf-Separator (in vfs-vf-entry)
|
||||
(setq frame (vfl-vf-eingang-bauen frame hz1 L_GF1-bau))
|
||||
(setq p-umlenk (car frame))
|
||||
|
||||
;; --- Erstes Koerper-Sub-Segment ---
|
||||
@@ -2908,17 +2938,12 @@
|
||||
;; zwischen zwei Foerderern - nicht hier.
|
||||
;; Motorseite erfassen: derzeit immer "rechts" (nur diese DWG vorhanden;
|
||||
;; die "links"-Einzel-DWG wird spaeter ergaenzt).
|
||||
(setq *vfl-acc-motorseite* (append *vfl-acc-motorseite* (list "rechts")))
|
||||
;; Motorstation braucht 3-Grad-Basis: falls die Kette gerade flach endet
|
||||
;; (horizontales Stueck ohne ab_3), hier den ab_3-Uebergang nachholen.
|
||||
(setq frame (vfl-nach-3grad frame))
|
||||
;; Auslauf-GF2: im Kettenende-Modus (Option 3) neu berechnet (ziel-gf2),
|
||||
;; sonst aus der GF-Verteilungs-Frage (L_GF2-bau).
|
||||
;; sonst aus der GF-Verteilungs-Frage (L_GF2-bau). Die Auswahl steht
|
||||
;; BEWUSST hier und nicht im Bauteil - sie ist die Stelle, an der sonst
|
||||
;; still die falsche GF2 hinter dem Motor entsteht.
|
||||
(setq gf2-eff (if ziel-ende ziel-gf2 L_GF2-bau))
|
||||
(setq frame (vfl-frame-3grad
|
||||
(vfs-vf-exit (car frame) gf2-eff letzt-hz nil) letzt-hz))
|
||||
(if (> gf2-eff 0.1)
|
||||
(vfl-acc-gf-seg gf2-eff (ssg-cfg-or "vario" "gefaelle_winkel" 3)))
|
||||
(setq frame (vfl-vf-ausgang-bauen frame letzt-hz gf2-eff))
|
||||
(list frame vf-count ziel-ende es-gewuenscht)
|
||||
)
|
||||
|
||||
|
||||
@@ -605,7 +605,48 @@ der falschen Stelle verbraucht wird, kann beide passieren lassen und trotzdem
|
||||
andere Geometrie erzeugen. Sitzt im Daten-Pfad (`vf_spec.lsp`), nicht als
|
||||
`dbgmsg` im Bau-Pfad - dort waere er nur bei offener `.dbg`-Datei sichtbar.
|
||||
|
||||
**Offen: 5, 6, 8, 9, 10** (VF-Einheit aufteilen, Daten-Executor,
|
||||
**Nachtrag (nach dem CAD-Lauf 12:22): Schritte 0-4 sind auch EMPIRISCH
|
||||
belegt.** `results/test_hundm05.dxf` entstand nach dem Umbau, das
|
||||
Ergebnis-JSON von 11:18 davor - fuer alle 5 Ketten sind **alle 37 Attribute
|
||||
identisch**, inklusive der reihenfolgeabhaengigen Komma-Listen `L_VF_m`,
|
||||
`L_GF_m`, `GF_WINKEL`, `ANTRIEBFAHRTRICHTUNG`. Genau der Vergleich, den
|
||||
dieser Abschnitt fuer die Geometrie-Schritte verlangt.
|
||||
`results/test_linienzug.dxf` zeigt eine vollstaendige Kette (AS+ES, Motor,
|
||||
Umlenkung, 2 Vario-Kurven, 22 Bausteine, `HOEHE_VON` = `HOEHE_BIS` = 4500 bei
|
||||
`DELTA_H` 0).
|
||||
|
||||
**Zusaetzlich Schritt 5a/5b**: `vfl-vf-eingang-bauen` (GF1 + Einlauf-Separator
|
||||
+ Umlenkstation) und `vfl-vf-ausgang-bauen` (Motorstation + GF2) sind
|
||||
herausgezogen - beide Bloecke enthalten keine Frage und sind
|
||||
zusammenhaengend, der Umzug ist wortwoertlich (Zeilenvergleich: 0 Zeilen
|
||||
entfernt, nur zwei Koepfe, zwei Aufrufe und zwei `frame)` neu). Die Auswahl
|
||||
`gf2-eff` (`ziel-gf2` im Kettenende-Modus, sonst `L_GF2-bau`) bleibt
|
||||
BEWUSST an der Aufrufstelle - genau dort entsteht sonst still die falsche
|
||||
GF2 hinter dem Motor.
|
||||
|
||||
### Regressionsnetz fuer die restlichen Schritte
|
||||
|
||||
Damit 5c/6/8/9/10 nicht mehr blind gemacht werden muessen, ist der
|
||||
Attribut-Vergleich jetzt ein Werkzeug plus Test:
|
||||
|
||||
- `lib/dxf_vf_abbild.py` liest die Testzeichnung streamend und bildet je
|
||||
Kette Attribute, Bausteinzusammensetzung und Einfuegepunkt ab. Ketten
|
||||
werden ueber den **Einfuegepunkt der Spec** zugeordnet, nicht ueber den
|
||||
Blocknamen (die VF-Nummer beginnt in jeder Zeichnung neu bei 1). `ID` und
|
||||
`Bezeichnung` sind bewusst aus dem Vergleich heraus.
|
||||
- `tests/reference/hundm05_attribute.json` ist die Referenz - erzeugt aus dem
|
||||
geprueften Lauf, also dem Stand, der attributgleich mit dem Zustand VOR
|
||||
dem Umbau ist.
|
||||
- `tests/test_vf_geometrie.py` (8 Tests) vergleicht automatisch, mit einem
|
||||
eigenen Test nur fuer die Komma-Listen und einer Gegenprobe, dass eine
|
||||
verfaelschte Referenz auch erkannt wird.
|
||||
|
||||
Ablauf ab jetzt pro Schritt: umbauen -> `TEST_HUNDM05` (save dxf) ->
|
||||
`pytest tests/test_vf_geometrie.py`. Weicht ein Attribut oder ein Baustein
|
||||
ab, nennt der Test Kette und Feld. Referenz nur nach geprueftem Lauf
|
||||
auffrischen (`--schreibe-referenz`).
|
||||
|
||||
**Offen: 5c, 6, 8, 9, 10** (Schleifenrumpf der VF-Einheit, Daten-Executor,
|
||||
Einheit-Abschluss, Kettenebene je Glied-Zweig, `vfl-spec-ausfuehren`).
|
||||
Bewusst NICHT vorab erledigt: das sind die Schritte, bei denen laut Abschnitt
|
||||
"Wo Geometrie still kippen kann" die Reihenfolge selbst Geometrie ist. Der
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
dxf_vf_abbild.py - Abbild der VF_/GF_-Wrapperbloecke einer Testzeichnung:
|
||||
Attribute und Bausteinliste je Kette.
|
||||
|
||||
Zweck: Regressionsnetz fuer Umbauten am Bau-Ablauf. Nach jedem Schritt der
|
||||
Funktionstrennung (doc/TODO-plan-vf-interactive.md, Stufe 2) muss die
|
||||
gebaute Geometrie unveraendert sein - "unveraendert" heisst hier: gleiche
|
||||
Attribute (inklusive der reihenfolgeabhaengigen Komma-Listen L_VF_m/L_GF_m/
|
||||
GF_WINKEL, die in der Zeichnung unsichtbar, im Sivas-Export aber falsch
|
||||
waeren) UND gleiche Bausteinzusammensetzung des Blocks (faengt eine falsche
|
||||
Blockvariante, die kein Attribut verraet).
|
||||
|
||||
Die Ketten werden ueber ihren Einfuegepunkt der Spec zugeordnet, nicht ueber
|
||||
den Blocknamen: die laufende VF-Nummer beginnt in jeder neuen Zeichnung
|
||||
wieder bei 1, der Kettenstart ist dagegen der stabile Anker.
|
||||
|
||||
Liest ASCII-DXF streamend (Gruppencode-Paare; ein ezdxf-DOM waere fuer die
|
||||
~90 MB Testzeichnungen zu gross). Binaere DXF kann dieses Skript NICHT -
|
||||
dafuer gibt es lib/dxf_scan_components.py.
|
||||
|
||||
Aufruf:
|
||||
python dxf_vf_abbild.py <dxf> [--spec tests/testdata/hundm05.json]
|
||||
[--json abbild.json]
|
||||
[--referenz tests/reference/hundm05_attribute.json]
|
||||
[--schreibe-referenz]
|
||||
|
||||
Ohne --referenz wird das Abbild nur ausgegeben. Mit --referenz wird
|
||||
verglichen (Exit-Code 1 bei Abweichung); mit --schreibe-referenz wird die
|
||||
Referenz aus der Zeichnung neu geschrieben - nur nach geprueftem Lauf!
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
PREFIX = "[dxf_vf_abbild]"
|
||||
|
||||
# Attribute, die von Lauf zu Lauf legitim wechseln: die laufende Nummer und
|
||||
# der daraus gebildete Name. Sie sagen nichts ueber die Geometrie.
|
||||
FLUECHTIG = ("ID", "Bezeichnung")
|
||||
|
||||
RUND = 3
|
||||
|
||||
|
||||
def gruppen(pfad):
|
||||
"""Alle (Gruppencode, Wert)-Paare der Datei."""
|
||||
with io.open(pfad, "r", encoding="utf-8", errors="replace") as fh:
|
||||
while True:
|
||||
code = fh.readline()
|
||||
if not code:
|
||||
return
|
||||
wert = fh.readline()
|
||||
if not wert:
|
||||
return
|
||||
code = code.strip()
|
||||
if not (code.isdigit() or (code.startswith("-")
|
||||
and code[1:].isdigit())):
|
||||
continue
|
||||
yield int(code), wert.rstrip("\n").rstrip("\r")
|
||||
|
||||
|
||||
def lese_bloecke(pfad, praefixe):
|
||||
"""(modelspace_inserts, blockinhalte) aus einer ASCII-DXF.
|
||||
|
||||
modelspace_inserts: INSERTs der ENTITIES-Sektion mit passendem Praefix,
|
||||
je mit Einfuegepunkt, Handle und den zugehoerigen ATTRIB-Werten.
|
||||
blockinhalte: {Blockname: [(Baustein, x, y, z, rotation), ...]} aus der
|
||||
BLOCKS-Sektion.
|
||||
"""
|
||||
sektion = None
|
||||
block_name = None
|
||||
ent = None
|
||||
ms = []
|
||||
blockinhalt = collections.defaultdict(list)
|
||||
letzter_insert = None
|
||||
erwarte_sektion = False
|
||||
erwarte_block_name = False
|
||||
|
||||
for code, wert in gruppen(pfad):
|
||||
if code == 0:
|
||||
if ent is not None:
|
||||
_entity_ablegen(ent, sektion, block_name, ms, blockinhalt,
|
||||
praefixe)
|
||||
if ent["typ"] == "INSERT" and not block_name:
|
||||
letzter_insert = ent
|
||||
elif ent["typ"] == "ATTRIB" and letzter_insert is not None:
|
||||
letzter_insert.setdefault("attribs", {})[
|
||||
ent.get("tag", "?")] = ent.get("wert", "")
|
||||
ent = None
|
||||
if wert == "SECTION":
|
||||
erwarte_sektion = True
|
||||
elif wert == "ENDSEC":
|
||||
sektion = None
|
||||
elif wert == "BLOCK":
|
||||
erwarte_block_name = True
|
||||
ent = {"typ": "BLOCK"}
|
||||
elif wert == "ENDBLK":
|
||||
block_name = None
|
||||
elif wert in ("INSERT", "ATTRIB"):
|
||||
ent = {"typ": wert}
|
||||
continue
|
||||
|
||||
if erwarte_sektion and code == 2:
|
||||
sektion = wert
|
||||
erwarte_sektion = False
|
||||
continue
|
||||
if ent is None:
|
||||
continue
|
||||
if ent["typ"] == "BLOCK" and code == 2 and erwarte_block_name:
|
||||
block_name = wert
|
||||
erwarte_block_name = False
|
||||
ent = None
|
||||
continue
|
||||
|
||||
if code == 2:
|
||||
# Bei ATTRIB ist Gruppencode 2 der TAG, bei INSERT der Blockname.
|
||||
if ent["typ"] == "ATTRIB":
|
||||
ent["tag"] = wert
|
||||
else:
|
||||
ent["name"] = wert
|
||||
elif code == 1 and ent["typ"] == "ATTRIB":
|
||||
ent["wert"] = wert
|
||||
elif code == 10:
|
||||
ent["x"] = float(wert)
|
||||
elif code == 20:
|
||||
ent["y"] = float(wert)
|
||||
elif code == 30:
|
||||
ent["z"] = float(wert)
|
||||
elif code == 50:
|
||||
ent["rot"] = float(wert)
|
||||
elif code == 5:
|
||||
ent["handle"] = wert
|
||||
|
||||
if ent is not None:
|
||||
_entity_ablegen(ent, sektion, block_name, ms, blockinhalt, praefixe)
|
||||
return ms, blockinhalt
|
||||
|
||||
|
||||
def _entity_ablegen(ent, sektion, block_name, ms, blockinhalt, praefixe):
|
||||
if ent["typ"] != "INSERT":
|
||||
return
|
||||
name = ent.get("name", "")
|
||||
if block_name:
|
||||
blockinhalt[block_name].append(
|
||||
(name, round(ent.get("x", 0.0), RUND), round(ent.get("y", 0.0), RUND),
|
||||
round(ent.get("z", 0.0), RUND), round(ent.get("rot", 0.0), RUND)))
|
||||
elif sektion == "ENTITIES" and any(name.startswith(p) for p in praefixe):
|
||||
ms.append(ent)
|
||||
|
||||
|
||||
def spec_startpunkte(spec_datei):
|
||||
"""{spec_id: [x, y, z]} aus einer Spec-Datei (flaches JSON)."""
|
||||
if not spec_datei or not os.path.isfile(spec_datei):
|
||||
return {}
|
||||
with io.open(spec_datei, encoding="utf-8") as fh:
|
||||
daten = json.load(fh)
|
||||
return dict((o["spec_id"], o["start_punkt"]) for o in daten
|
||||
if "spec_id" in o)
|
||||
|
||||
|
||||
def abbild(dxf, spec_datei=None, praefixe=("VF_", "GF_")):
|
||||
ms, blocks = lese_bloecke(dxf, praefixe)
|
||||
starts = spec_startpunkte(spec_datei)
|
||||
raus = collections.OrderedDict()
|
||||
for e in sorted(ms, key=lambda x: x.get("x", 0.0)):
|
||||
name = e.get("name")
|
||||
punkt = [e.get("x"), e.get("y"), e.get("z")]
|
||||
kette = _kette_zuordnen(punkt, starts) or name
|
||||
inhalt = blocks.get(name, [])
|
||||
raus[kette] = collections.OrderedDict([
|
||||
("block", name),
|
||||
("insert", [round(v, RUND) if v is not None else None
|
||||
for v in punkt]),
|
||||
("bausteine", collections.OrderedDict(
|
||||
sorted(collections.Counter(b[0] for b in inhalt).items()))),
|
||||
("bausteine_gesamt", len(inhalt)),
|
||||
("attribs", collections.OrderedDict(
|
||||
(k, v) for k, v in sorted(e.get("attribs", {}).items())
|
||||
if k not in FLUECHTIG)),
|
||||
])
|
||||
return raus
|
||||
|
||||
|
||||
def _kette_zuordnen(punkt, starts, toleranz=1.0):
|
||||
"""Kette ueber den Einfuegepunkt der Spec zuordnen - der Blockname
|
||||
taugt nicht: die VF-Nummer beginnt in jeder Zeichnung neu bei 1."""
|
||||
for sid, p in starts.items():
|
||||
if all(abs(a - b) <= toleranz for a, b in zip(punkt[:3], p[:3])):
|
||||
return sid
|
||||
return None
|
||||
|
||||
|
||||
def vergleiche(ist, soll):
|
||||
"""Liste von Abweichungstexten (leer = gleich)."""
|
||||
fehler = []
|
||||
for kette in sorted(set(ist) | set(soll)):
|
||||
if kette not in ist:
|
||||
fehler.append("%s: in der Zeichnung nicht gebaut" % kette)
|
||||
continue
|
||||
if kette not in soll:
|
||||
fehler.append("%s: nicht in der Referenz (neue Kette? Referenz "
|
||||
"mit --schreibe-referenz auffrischen)" % kette)
|
||||
continue
|
||||
a, b = ist[kette], soll[kette]
|
||||
for feld in ("bausteine", "bausteine_gesamt"):
|
||||
if a[feld] != b[feld]:
|
||||
fehler.append("%s: %s ist %s, Referenz %s"
|
||||
% (kette, feld, a[feld], b[feld]))
|
||||
for tag in sorted(set(a["attribs"]) | set(b["attribs"])):
|
||||
wa, wb = a["attribs"].get(tag), b["attribs"].get(tag)
|
||||
if wa != wb:
|
||||
fehler.append("%s: Attribut %s ist %r, Referenz %r"
|
||||
% (kette, tag, wa, wb))
|
||||
for achse, (wa, wb) in enumerate(zip(a["insert"], b["insert"])):
|
||||
if wa is None or wb is None or abs(wa - wb) > 1.0:
|
||||
fehler.append("%s: Einfuegepunkt Achse %d ist %s, Referenz %s"
|
||||
% (kette, achse, wa, wb))
|
||||
return fehler
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description=__doc__.split("\n")[1])
|
||||
p.add_argument("dxf", help="Testzeichnung (ASCII-DXF)")
|
||||
p.add_argument("--spec", help="Spec-JSON zur Ketten-Zuordnung")
|
||||
p.add_argument("--json", help="Abbild hierhin schreiben")
|
||||
p.add_argument("--referenz", help="Referenz-Abbild zum Vergleich")
|
||||
p.add_argument("--schreibe-referenz", action="store_true",
|
||||
help="Referenz aus dieser Zeichnung neu schreiben")
|
||||
p.add_argument("--praefix", default="VF_,GF_",
|
||||
help="Blocknamen-Praefixe, komma-getrennt")
|
||||
args = p.parse_args()
|
||||
|
||||
if not os.path.isfile(args.dxf):
|
||||
print("%s FEHLER: %s nicht gefunden" % (PREFIX, args.dxf))
|
||||
return 1
|
||||
groesse = os.path.getsize(args.dxf) / (1024.0 * 1024.0)
|
||||
print("%s lese %s (%.1f MB) ..." % (PREFIX, args.dxf, groesse))
|
||||
|
||||
ist = abbild(args.dxf, args.spec, tuple(args.praefix.split(",")))
|
||||
print("%s %d Kette(n): %s" % (PREFIX, len(ist), ", ".join(ist)))
|
||||
|
||||
if args.json:
|
||||
_schreibe(args.json, ist)
|
||||
print("%s Abbild -> %s" % (PREFIX, args.json))
|
||||
|
||||
if args.schreibe_referenz:
|
||||
if not args.referenz:
|
||||
print("%s FEHLER: --schreibe-referenz braucht --referenz" % PREFIX)
|
||||
return 1
|
||||
_schreibe(args.referenz, ist)
|
||||
print("%s Referenz neu geschrieben -> %s" % (PREFIX, args.referenz))
|
||||
return 0
|
||||
|
||||
if args.referenz:
|
||||
if not os.path.isfile(args.referenz):
|
||||
print("%s FEHLER: Referenz %s fehlt" % (PREFIX, args.referenz))
|
||||
return 1
|
||||
with io.open(args.referenz, encoding="utf-8") as fh:
|
||||
soll = json.load(fh)
|
||||
fehler = vergleiche(ist, soll)
|
||||
if fehler:
|
||||
print("%s %d ABWEICHUNG(EN):" % (PREFIX, len(fehler)))
|
||||
for f in fehler:
|
||||
print(" - %s" % f)
|
||||
return 1
|
||||
print("%s Geometrie unveraendert (Attribute und Bausteine je Kette "
|
||||
"gleich der Referenz)" % PREFIX)
|
||||
return 0
|
||||
|
||||
|
||||
def _schreibe(pfad, daten):
|
||||
verz = os.path.dirname(os.path.abspath(pfad))
|
||||
if verz and not os.path.isdir(verz):
|
||||
os.makedirs(verz)
|
||||
with io.open(pfad, "w", encoding="utf-8", newline="\r\n") as fh:
|
||||
fh.write(json.dumps(daten, indent=2, ensure_ascii=False))
|
||||
fh.write("\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -170,6 +170,10 @@ tests/
|
||||
# format (in alltests.json abgeschaltet,
|
||||
# baut sonst dieselbe Geometrie zweimal)
|
||||
test_hm_recformat.py # pytest-Validierung dazu
|
||||
test_vf_geometrie.py # pytest: die GEBAUTE Geometrie gegen
|
||||
# reference/hundm05_attribute.json - Attribute,
|
||||
# Bausteine, Einfuegepunkt (faengt eine
|
||||
# vertauschte vfl-acc-*-Reihenfolge)
|
||||
test_vf_spec.lsp # LISP-Unittest: Spec-Uebersetzer (vsp-journal),
|
||||
# je Glied-Typ + die 5 echten HundM-Ketten
|
||||
# gegen ihr aufgezeichnetes Journal
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
{
|
||||
"VF_hundm05_LZ_01": {
|
||||
"block": "VF_1",
|
||||
"insert": [
|
||||
4912.239,
|
||||
1230.886,
|
||||
2154.0
|
||||
],
|
||||
"bausteine": {
|
||||
"AS_Element_90_links_3D": 1,
|
||||
"ES_Element_90_links_3D": 1,
|
||||
"Staustrecke_SP_1000_mm_3D": 7,
|
||||
"Staustrecke_Separator_SP_300_mm_3D": 3,
|
||||
"Vario_Bogen_ab_3_TEF_rechts_3D": 2,
|
||||
"Vario_Bogen_auf_3_TEF_rechts_3D": 2,
|
||||
"Vario_Kurve_links_30_TEF_innen_3D": 1,
|
||||
"Vario_Kurve_rechts_30_TEF_innen_3D": 1,
|
||||
"Vario_Kurve_rechts_90_TEF_innen_3D": 2,
|
||||
"Vario_Motorstation_500mm_rechts_3D": 2,
|
||||
"Vario_Umlenkstation_500mm_rechts_3D": 2
|
||||
},
|
||||
"bausteine_gesamt": 24,
|
||||
"attribs": {
|
||||
"ANTRIEBFAHRTRICHTUNG": "horizontal,horizontal,horizontal,horizontal",
|
||||
"ANZAHL_GF": "3",
|
||||
"ANZAHL_SCANNER": "0",
|
||||
"ANZAHL_SEPARATOR": "3",
|
||||
"ANZAHL_VF": "4",
|
||||
"ARTINR": "6220",
|
||||
"DELTA_H_mm": "463",
|
||||
"DELTA_L_mm": "1934",
|
||||
"GERUEST_EINZELMODUL": "1",
|
||||
"GERUEST_TYP": "Schoenenberger Geruest",
|
||||
"GF_Bogen_L_30": "0",
|
||||
"GF_Bogen_L_60": "0",
|
||||
"GF_Bogen_L_90": "0",
|
||||
"GF_Bogen_R_30": "0",
|
||||
"GF_Bogen_R_60": "0",
|
||||
"GF_Bogen_R_90": "0",
|
||||
"GF_WINKEL": "3,3,3",
|
||||
"HOEHE_BIS_mm": "1690",
|
||||
"HOEHE_VON_mm": "2154",
|
||||
"L_GF_m": "1.826,0.4,0.4",
|
||||
"L_VF_m": "1.859,1.434,0.1,1.83",
|
||||
"MONTAGEHOEHE_m": "1.922",
|
||||
"MOTORSEITE": "rechts,rechts",
|
||||
"SEITE_AS": "links",
|
||||
"SEITE_ES": "links",
|
||||
"TYP": "Streckengruppe",
|
||||
"VF_Bogen_A_30": "0",
|
||||
"VF_Bogen_A_60": "0",
|
||||
"VF_Bogen_A_90": "0",
|
||||
"VF_Bogen_I_30": "2",
|
||||
"VF_Bogen_I_60": "0",
|
||||
"VF_Bogen_I_90": "2",
|
||||
"VF_WINKEL": "0,0,0,0",
|
||||
"WINKEL_AS": "90",
|
||||
"WINKEL_ES": "90"
|
||||
}
|
||||
},
|
||||
"VF_hundm05_LZ_02": {
|
||||
"block": "VF_2",
|
||||
"insert": [
|
||||
8571.951,
|
||||
1276.061,
|
||||
2494.931
|
||||
],
|
||||
"bausteine": {
|
||||
"AS_Element_90_links_3D": 1,
|
||||
"ES_Element_90_links_3D": 1,
|
||||
"Staustrecke_SP_1000_mm_3D": 7,
|
||||
"Staustrecke_Separator_SP_300_mm_3D": 2,
|
||||
"Vario_Bogen_ab_21_TEF_rechts_3D": 1,
|
||||
"Vario_Bogen_ab_3_TEF_rechts_3D": 2,
|
||||
"Vario_Bogen_auf_21_TEF_rechts_3D": 1,
|
||||
"Vario_Bogen_auf_3_TEF_rechts_3D": 2,
|
||||
"Vario_Kurve_rechts_90_TEF_innen_3D": 2,
|
||||
"Vario_Motorstation_500mm_rechts_3D": 1,
|
||||
"Vario_Umlenkstation_500mm_rechts_3D": 1
|
||||
},
|
||||
"bausteine_gesamt": 21,
|
||||
"attribs": {
|
||||
"ANTRIEBFAHRTRICHTUNG": "horizontal,horizontal,Auf,horizontal",
|
||||
"ANZAHL_GF": "3",
|
||||
"ANZAHL_SCANNER": "0",
|
||||
"ANZAHL_SEPARATOR": "2",
|
||||
"ANZAHL_VF": "4",
|
||||
"ARTINR": "6220",
|
||||
"DELTA_H_mm": "107",
|
||||
"DELTA_L_mm": "1808",
|
||||
"GERUEST_EINZELMODUL": "1",
|
||||
"GERUEST_TYP": "Schoenenberger Geruest",
|
||||
"GF_Bogen_L_30": "0",
|
||||
"GF_Bogen_L_60": "0",
|
||||
"GF_Bogen_L_90": "0",
|
||||
"GF_Bogen_R_30": "0",
|
||||
"GF_Bogen_R_60": "0",
|
||||
"GF_Bogen_R_90": "0",
|
||||
"GF_WINKEL": "3,3,3",
|
||||
"HOEHE_BIS_mm": "2387",
|
||||
"HOEHE_VON_mm": "2494",
|
||||
"L_GF_m": "2.727,0.4,0.418",
|
||||
"L_VF_m": "0.177,0.339,0.548,0.548",
|
||||
"MONTAGEHOEHE_m": "2.441",
|
||||
"MOTORSEITE": "rechts",
|
||||
"SEITE_AS": "links",
|
||||
"SEITE_ES": "links",
|
||||
"TYP": "Streckengruppe",
|
||||
"VF_Bogen_A_30": "0",
|
||||
"VF_Bogen_A_60": "0",
|
||||
"VF_Bogen_A_90": "0",
|
||||
"VF_Bogen_I_30": "0",
|
||||
"VF_Bogen_I_60": "0",
|
||||
"VF_Bogen_I_90": "2",
|
||||
"VF_WINKEL": "0,0,21,0",
|
||||
"WINKEL_AS": "90",
|
||||
"WINKEL_ES": "90"
|
||||
}
|
||||
},
|
||||
"VF_hundm05_LZ_03": {
|
||||
"block": "VF_3",
|
||||
"insert": [
|
||||
12916.466,
|
||||
1276.061,
|
||||
2494.931
|
||||
],
|
||||
"bausteine": {
|
||||
"AS_Element_90_links_3D": 1,
|
||||
"ES_Element_90_links_3D": 1,
|
||||
"Staustrecke_SP_1000_mm_3D": 7,
|
||||
"Staustrecke_Separator_SP_300_mm_3D": 2,
|
||||
"Vario_Bogen_ab_21_TEF_rechts_3D": 1,
|
||||
"Vario_Bogen_ab_3_TEF_rechts_3D": 2,
|
||||
"Vario_Bogen_auf_21_TEF_rechts_3D": 1,
|
||||
"Vario_Bogen_auf_3_TEF_rechts_3D": 2,
|
||||
"Vario_Kurve_rechts_90_TEF_innen_3D": 2,
|
||||
"Vario_Motorstation_500mm_rechts_3D": 1,
|
||||
"Vario_Umlenkstation_500mm_rechts_3D": 1
|
||||
},
|
||||
"bausteine_gesamt": 21,
|
||||
"attribs": {
|
||||
"ANTRIEBFAHRTRICHTUNG": "horizontal,horizontal,Auf,horizontal",
|
||||
"ANZAHL_GF": "3",
|
||||
"ANZAHL_SCANNER": "0",
|
||||
"ANZAHL_SEPARATOR": "2",
|
||||
"ANZAHL_VF": "4",
|
||||
"ARTINR": "6220",
|
||||
"DELTA_H_mm": "107",
|
||||
"DELTA_L_mm": "1808",
|
||||
"GERUEST_EINZELMODUL": "1",
|
||||
"GERUEST_TYP": "Schoenenberger Geruest",
|
||||
"GF_Bogen_L_30": "0",
|
||||
"GF_Bogen_L_60": "0",
|
||||
"GF_Bogen_L_90": "0",
|
||||
"GF_Bogen_R_30": "0",
|
||||
"GF_Bogen_R_60": "0",
|
||||
"GF_Bogen_R_90": "0",
|
||||
"GF_WINKEL": "3,3,3",
|
||||
"HOEHE_BIS_mm": "2387",
|
||||
"HOEHE_VON_mm": "2494",
|
||||
"L_GF_m": "2.727,0.4,0.418",
|
||||
"L_VF_m": "0.177,0.339,0.548,0.548",
|
||||
"MONTAGEHOEHE_m": "2.441",
|
||||
"MOTORSEITE": "rechts",
|
||||
"SEITE_AS": "links",
|
||||
"SEITE_ES": "links",
|
||||
"TYP": "Streckengruppe",
|
||||
"VF_Bogen_A_30": "0",
|
||||
"VF_Bogen_A_60": "0",
|
||||
"VF_Bogen_A_90": "0",
|
||||
"VF_Bogen_I_30": "0",
|
||||
"VF_Bogen_I_60": "0",
|
||||
"VF_Bogen_I_90": "2",
|
||||
"VF_WINKEL": "0,0,21,0",
|
||||
"WINKEL_AS": "90",
|
||||
"WINKEL_ES": "90"
|
||||
}
|
||||
},
|
||||
"VF_hundm05_LZ_04": {
|
||||
"block": "VF_4",
|
||||
"insert": [
|
||||
17201.778,
|
||||
1205.361,
|
||||
2154.0
|
||||
],
|
||||
"bausteine": {
|
||||
"AS_Element_90_links_3D": 1,
|
||||
"ES_Element_90_links_3D": 1,
|
||||
"Staustrecke_SP_1000_mm_3D": 7,
|
||||
"Staustrecke_Separator_SP_300_mm_3D": 2,
|
||||
"Vario_Bogen_ab_39_TEF_rechts_3D": 1,
|
||||
"Vario_Bogen_ab_3_TEF_rechts_3D": 2,
|
||||
"Vario_Bogen_auf_39_TEF_rechts_3D": 1,
|
||||
"Vario_Bogen_auf_3_TEF_rechts_3D": 2,
|
||||
"Vario_Kurve_rechts_90_TEF_innen_3D": 2,
|
||||
"Vario_Motorstation_500mm_rechts_3D": 1,
|
||||
"Vario_Umlenkstation_500mm_rechts_3D": 1
|
||||
},
|
||||
"bausteine_gesamt": 21,
|
||||
"attribs": {
|
||||
"ANTRIEBFAHRTRICHTUNG": "horizontal,horizontal,Auf,horizontal",
|
||||
"ANZAHL_GF": "3",
|
||||
"ANZAHL_SCANNER": "0",
|
||||
"ANZAHL_SEPARATOR": "2",
|
||||
"ANZAHL_VF": "4",
|
||||
"ARTINR": "6220",
|
||||
"DELTA_H_mm": "220",
|
||||
"DELTA_L_mm": "1788",
|
||||
"GERUEST_EINZELMODUL": "1",
|
||||
"GERUEST_TYP": "Schoenenberger Geruest",
|
||||
"GF_Bogen_L_30": "0",
|
||||
"GF_Bogen_L_60": "0",
|
||||
"GF_Bogen_L_90": "0",
|
||||
"GF_Bogen_R_30": "0",
|
||||
"GF_Bogen_R_60": "0",
|
||||
"GF_Bogen_R_90": "0",
|
||||
"GF_WINKEL": "3,3,3",
|
||||
"HOEHE_BIS_mm": "2374",
|
||||
"HOEHE_VON_mm": "2154",
|
||||
"L_GF_m": "2.727,0.4,0.418",
|
||||
"L_VF_m": "0.177,0.339,0.356,0.548",
|
||||
"MONTAGEHOEHE_m": "2.264",
|
||||
"MOTORSEITE": "rechts",
|
||||
"SEITE_AS": "links",
|
||||
"SEITE_ES": "links",
|
||||
"TYP": "Streckengruppe",
|
||||
"VF_Bogen_A_30": "0",
|
||||
"VF_Bogen_A_60": "0",
|
||||
"VF_Bogen_A_90": "0",
|
||||
"VF_Bogen_I_30": "0",
|
||||
"VF_Bogen_I_60": "0",
|
||||
"VF_Bogen_I_90": "2",
|
||||
"VF_WINKEL": "0,0,39,0",
|
||||
"WINKEL_AS": "90",
|
||||
"WINKEL_ES": "90"
|
||||
}
|
||||
},
|
||||
"VF_hundm05_LZ_05": {
|
||||
"block": "VF_5",
|
||||
"insert": [
|
||||
21490.274,
|
||||
1195.74,
|
||||
2494.931
|
||||
],
|
||||
"bausteine": {
|
||||
"AS_Element_90_links_3D": 1,
|
||||
"ES_Element_90_links_3D": 1,
|
||||
"Staustrecke_SP_1000_mm_3D": 7,
|
||||
"Staustrecke_Separator_SP_300_mm_3D": 2,
|
||||
"Vario_Bogen_ab_21_TEF_rechts_3D": 1,
|
||||
"Vario_Bogen_ab_3_TEF_rechts_3D": 2,
|
||||
"Vario_Bogen_auf_21_TEF_rechts_3D": 1,
|
||||
"Vario_Bogen_auf_3_TEF_rechts_3D": 2,
|
||||
"Vario_Kurve_rechts_90_TEF_innen_3D": 2,
|
||||
"Vario_Motorstation_500mm_rechts_3D": 1,
|
||||
"Vario_Umlenkstation_500mm_rechts_3D": 1
|
||||
},
|
||||
"bausteine_gesamt": 21,
|
||||
"attribs": {
|
||||
"ANTRIEBFAHRTRICHTUNG": "horizontal,horizontal,Auf,horizontal",
|
||||
"ANZAHL_GF": "3",
|
||||
"ANZAHL_SCANNER": "0",
|
||||
"ANZAHL_SEPARATOR": "2",
|
||||
"ANZAHL_VF": "4",
|
||||
"ARTINR": "6220",
|
||||
"DELTA_H_mm": "107",
|
||||
"DELTA_L_mm": "1808",
|
||||
"GERUEST_EINZELMODUL": "1",
|
||||
"GERUEST_TYP": "Schoenenberger Geruest",
|
||||
"GF_Bogen_L_30": "0",
|
||||
"GF_Bogen_L_60": "0",
|
||||
"GF_Bogen_L_90": "0",
|
||||
"GF_Bogen_R_30": "0",
|
||||
"GF_Bogen_R_60": "0",
|
||||
"GF_Bogen_R_90": "0",
|
||||
"GF_WINKEL": "3,3,3",
|
||||
"HOEHE_BIS_mm": "2387",
|
||||
"HOEHE_VON_mm": "2494",
|
||||
"L_GF_m": "2.727,0.4,0.418",
|
||||
"L_VF_m": "0.177,0.339,0.548,0.548",
|
||||
"MONTAGEHOEHE_m": "2.441",
|
||||
"MOTORSEITE": "rechts",
|
||||
"SEITE_AS": "links",
|
||||
"SEITE_ES": "links",
|
||||
"TYP": "Streckengruppe",
|
||||
"VF_Bogen_A_30": "0",
|
||||
"VF_Bogen_A_60": "0",
|
||||
"VF_Bogen_A_90": "0",
|
||||
"VF_Bogen_I_30": "0",
|
||||
"VF_Bogen_I_60": "0",
|
||||
"VF_Bogen_I_90": "2",
|
||||
"VF_WINKEL": "0,0,21,0",
|
||||
"WINKEL_AS": "90",
|
||||
"WINKEL_ES": "90"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
test_vf_geometrie.py - Regressionsnetz fuer die GEBAUTE Geometrie der
|
||||
HundM-Ketten.
|
||||
|
||||
Warum zusaetzlich zu tests/test_hundm05.py: der Ergebnis-Record sagt
|
||||
"executed, kein Prompt, Journal aufgegangen" - er sagt NICHT, ob dieselbe
|
||||
Geometrie herauskam wie vorher. Genau das ist bei der Funktionstrennung in
|
||||
Stufe 2 (doc/TODO-plan-vf-interactive.md) das Risiko: die Reihenfolge der
|
||||
dL-Subtraktionen und der vfl-acc-*-Aufrufe IST Geometrie, und eine falsche
|
||||
Reihenfolge faellt in der Zeichnung nicht auf - nur im Sivas-Export.
|
||||
|
||||
Verglichen wird gegen tests/reference/hundm05_attribute.json:
|
||||
|
||||
- alle Attribute des VF_n-Blocks, insbesondere die reihenfolgeabhaengigen
|
||||
Komma-Listen L_VF_m / L_GF_m / GF_WINKEL / ANTRIEBFAHRTRICHTUNG
|
||||
- die Bausteinzusammensetzung des Blocks (faengt eine falsche
|
||||
Blockvariante, die kein Attribut verraet)
|
||||
- der Einfuegepunkt
|
||||
|
||||
Die laufende ID und der Blockname sind bewusst NICHT Teil des Vergleichs:
|
||||
die VF-Nummer beginnt in jeder neuen Zeichnung wieder bei 1.
|
||||
|
||||
Braucht eine gebaute Zeichnung (TEST_HUNDM05 mit "save": "dxf") und wird
|
||||
sonst uebersprungen. Referenz auffrischen - nur nach geprueftem Lauf:
|
||||
|
||||
python lib/dxf_vf_abbild.py <dxf> --spec tests/testdata/hundm05.json \
|
||||
--referenz tests/reference/hundm05_attribute.json \
|
||||
--schreibe-referenz
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
|
||||
|
||||
import dxf_vf_abbild as dva # noqa: E402
|
||||
|
||||
HIER = os.path.dirname(__file__)
|
||||
SPEC = os.path.join(HIER, "testdata", "hundm05.json")
|
||||
REFERENZ = os.path.join(HIER, "reference", "hundm05_attribute.json")
|
||||
|
||||
# SSG_RUN_ALL_TESTS legt die Zeichnung in tests/output ab; ein Lauf mit
|
||||
# gesetztem DXFM_RESULTS speichert sie nach results/.
|
||||
DXF_ORTE = [os.path.join(HIER, "output", "hundm05_tests.dxf"),
|
||||
os.path.join(HIER, "..", "results", "test_hundm05.dxf")]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def gebautes_abbild():
|
||||
for pfad in DXF_ORTE:
|
||||
if os.path.isfile(pfad):
|
||||
return dva.abbild(pfad, SPEC, ("VF_",))
|
||||
pytest.skip("keine gebaute HundM05-Zeichnung gefunden (%s) - "
|
||||
"TEST_HUNDM05 in BricsCAD ausfuehren"
|
||||
% " bzw. ".join(os.path.basename(p) for p in DXF_ORTE))
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def referenz():
|
||||
if not os.path.isfile(REFERENZ):
|
||||
pytest.skip("%s fehlt - mit lib/dxf_vf_abbild.py "
|
||||
"--schreibe-referenz anlegen" % REFERENZ)
|
||||
with open(REFERENZ, encoding="utf-8") as fh:
|
||||
return json.load(fh)
|
||||
|
||||
|
||||
def test_referenz_deckt_alle_specs(referenz):
|
||||
"""Jede Kette der Spec muss in der Referenz stehen - sonst laeuft der
|
||||
Vergleich unbemerkt ins Leere."""
|
||||
if not os.path.isfile(SPEC):
|
||||
pytest.skip("Spec fehlt")
|
||||
with open(SPEC, encoding="utf-8") as fh:
|
||||
specs = [o["spec_id"] for o in json.load(fh) if "spec_id" in o]
|
||||
fehlt = sorted(set(specs) - set(referenz))
|
||||
assert not fehlt, (
|
||||
"Ketten ohne Referenz: %s. Nach einem geprueften Lauf die Referenz "
|
||||
"mit lib/dxf_vf_abbild.py --schreibe-referenz auffrischen." % fehlt)
|
||||
|
||||
|
||||
def test_alle_ketten_gebaut(gebautes_abbild, referenz):
|
||||
fehlt = sorted(set(referenz) - set(gebautes_abbild))
|
||||
assert not fehlt, "in der Zeichnung nicht gebaut: %s" % fehlt
|
||||
|
||||
|
||||
@pytest.mark.parametrize("feld", ["bausteine", "bausteine_gesamt"])
|
||||
def test_bausteine_wie_referenz(gebautes_abbild, referenz, feld):
|
||||
"""Die Bausteinliste faengt, was kein Attribut verraet: eine falsche
|
||||
Blockvariante (z.B. auf_3 statt ab_3, links statt rechts)."""
|
||||
for kette, soll in sorted(referenz.items()):
|
||||
if kette not in gebautes_abbild:
|
||||
continue
|
||||
ist = gebautes_abbild[kette]
|
||||
assert ist[feld] == soll[feld], \
|
||||
f"{kette}: {feld} ist {ist[feld]}, Referenz {soll[feld]}"
|
||||
|
||||
|
||||
def test_attribute_wie_referenz(gebautes_abbild, referenz):
|
||||
for kette, soll in sorted(referenz.items()):
|
||||
if kette not in gebautes_abbild:
|
||||
continue
|
||||
ist = gebautes_abbild[kette]["attribs"]
|
||||
for tag in sorted(set(ist) | set(soll["attribs"])):
|
||||
assert ist.get(tag) == soll["attribs"].get(tag), \
|
||||
f'{kette}: Attribut {tag} ist {ist.get(tag)!r}, ' \
|
||||
f'Referenz {soll["attribs"].get(tag)!r}'
|
||||
|
||||
|
||||
def test_reihenfolge_listen_wie_referenz(gebautes_abbild, referenz):
|
||||
"""Eigener Test fuer die Komma-Listen: sie sind das einzige, was eine
|
||||
vertauschte vfl-acc-*-Reihenfolge sichtbar macht - in der Zeichnung
|
||||
unsichtbar, im Sivas-Export falsch."""
|
||||
listen = ("L_VF_m", "L_GF_m", "GF_WINKEL", "ANTRIEBFAHRTRICHTUNG")
|
||||
for kette, soll in sorted(referenz.items()):
|
||||
if kette not in gebautes_abbild:
|
||||
continue
|
||||
ist = gebautes_abbild[kette]["attribs"]
|
||||
for tag in listen:
|
||||
if tag not in soll["attribs"]:
|
||||
continue
|
||||
assert ist.get(tag) == soll["attribs"][tag], \
|
||||
f'{kette}: {tag} ist {ist.get(tag)!r}, ' \
|
||||
f'Referenz {soll["attribs"][tag]!r} - Reihenfolge der ' \
|
||||
f'vfl-acc-*-Aufrufe geaendert?'
|
||||
|
||||
|
||||
def test_einfuegepunkt_wie_referenz(gebautes_abbild, referenz):
|
||||
for kette, soll in sorted(referenz.items()):
|
||||
if kette not in gebautes_abbild:
|
||||
continue
|
||||
for achse, (a, b) in enumerate(
|
||||
zip(gebautes_abbild[kette]["insert"], soll["insert"])):
|
||||
assert abs(a - b) < 1.0, \
|
||||
f"{kette}: Einfuegepunkt Achse {achse} ist {a}, Referenz {b}"
|
||||
|
||||
|
||||
def test_vergleichsfunktion_meldet_abweichung(gebautes_abbild, referenz):
|
||||
"""Gegenprobe: der Vergleich muss eine verfaelschte Referenz erkennen -
|
||||
sonst bewiesen die Tests oben nur, dass beide Seiten gleich gelesen
|
||||
werden."""
|
||||
kette = sorted(referenz)[0]
|
||||
gift = json.loads(json.dumps(referenz))
|
||||
gift[kette]["attribs"]["DELTA_L_mm"] = "999999"
|
||||
assert dva.vergleiche(gebautes_abbild, gift), \
|
||||
"verfaelschte Referenz wurde NICHT erkannt"
|
||||
assert not dva.vergleiche(gebautes_abbild, referenz), \
|
||||
"echte Referenz wird als abweichend gemeldet"
|
||||
Reference in New Issue
Block a user