Omniflo-Streckenzug-Test + skriptfaehige Aluprofil-Geraden
Neuer Test: 4 Geraden -> 180-Grad-Kurve (4x 45-Grad-Bogen) -> 4 Geraden als lueckenlos verketteter Streckenzug (U-Form). - testdata/omniflo_strecke_tests.json: 12 Elemente mit berechneter, verketteter Platzierung (x, y, drehung, x_ende, y_ende, Bogen-Mittelpunkt) - test_omniflo_strecke.py: Geometrie-Validierung ohne BricsCAD (Struktur, Katalog-Abgleich, Verkettung, Kurvensumme 180 Grad, gemeinsamer Mittelpunkt) - test_omniflo_strecke.lsp: C:TEST_OMNIFLO_STRECKE baut die Strecke auf - alltests.json/README.md: Test registriert und dokumentiert OmniModulInsert.lsp: omni:insert-block in skriptfaehigen Kern omni:make-gerade + omni:read-src-attribs aufgeteilt. Der Kern erzeugt eine Aluprofil-Gerade non-interaktiv aus zwei Punkten (kein getpoint/ssg-start), sodass der Test echte Gerade-Bloecke statt nackter Linien baut. Interaktives Verhalten von omni:insert-block bleibt unveraendert (Vorschau-Text, Laengenpruefung). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
test_omniflo_strecke.py - Validiert den Omniflo-Streckenzug.
|
||||
|
||||
Testfall (tests/testdata/omniflo_strecke_tests.json):
|
||||
4 Geraden -> 180-Grad-Kurve aus 4x 45-Grad-Boegen -> 4 Geraden
|
||||
|
||||
Prueft (ohne BricsCAD):
|
||||
1. Struktur: 4 + 4 + 4 = 12 Elemente in der richtigen Reihenfolge
|
||||
2. Katalog: die verwendeten Bogen-SivasNummern existieren in
|
||||
data/json/omniflo_boegen.json mit passendem Radius/Kurvenwinkel
|
||||
3. Geometrie: die Elemente sind lueckenlos verkettet (Ende[i] == Anfang[i+1]),
|
||||
die Kurve summiert auf genau 180 Grad, die Boegen liegen auf einem
|
||||
gemeinsamen Kreismittelpunkt (echte Halbkreis-Kurve)
|
||||
|
||||
Optional (nach BricsCAD-Lauf c:TEST_OMNIFLO_STRECKE):
|
||||
4. Integrationstest gegen tests/output/omniflo_strecke_results.json
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
_TOL = 0.05 # mm / Grad Toleranz (JSON ist auf 2 Nachkommastellen gerundet)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Hilfsfunktionen
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _data_dir():
|
||||
base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
return os.path.join(base, "data")
|
||||
|
||||
|
||||
def _testdata_dir():
|
||||
return os.getenv("DXFM_TESTDATA",
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "testdata"))
|
||||
|
||||
|
||||
def _output_dir():
|
||||
return os.getenv("DXFM_TESTOUT",
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "output"))
|
||||
|
||||
|
||||
def _load_json(path):
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except UnicodeDecodeError:
|
||||
with open(path, "r", encoding="cp1252") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def _load_strecke():
|
||||
return _load_json(os.path.join(_testdata_dir(), "omniflo_strecke_tests.json"))
|
||||
|
||||
|
||||
def _load_boegen_lookup():
|
||||
boegen = _load_json(os.path.join(_data_dir(), "json", "omniflo_boegen.json"))
|
||||
return {str(b["Sivasnr"]): b for b in boegen}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fixtures
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@pytest.fixture
|
||||
def strecke():
|
||||
return _load_strecke()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def boegen_lookup():
|
||||
return _load_boegen_lookup()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. Struktur des Streckenzugs
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestStreckeStruktur:
|
||||
|
||||
def test_gesamtzahl(self, strecke):
|
||||
"""4 Geraden + 4 Boegen + 4 Geraden = 12 Elemente."""
|
||||
assert len(strecke) == 12, f"Erwartet 12 Elemente, erhalten {len(strecke)}"
|
||||
|
||||
def test_seq_lueckenlos(self, strecke):
|
||||
"""seq muss lueckenlos bei 1 beginnend aufsteigen."""
|
||||
for i, el in enumerate(strecke):
|
||||
assert el["seq"] == i + 1, f"Element {i}: seq={el['seq']}, erwartet {i + 1}"
|
||||
|
||||
def test_gruppen_reihenfolge(self, strecke):
|
||||
"""Reihenfolge: 4x gerade_start, 4x kurve_180, 4x gerade_ende."""
|
||||
erwartet = (["gerade_start"] * 4 + ["kurve_180"] * 4 + ["gerade_ende"] * 4)
|
||||
ist = [el["gruppe"] for el in strecke]
|
||||
assert ist == erwartet, f"Gruppenfolge abweichend:\n Ist: {ist}\n Soll: {erwartet}"
|
||||
|
||||
def test_typen(self, strecke):
|
||||
"""gerade_* -> type 'gerade', kurve_180 -> type 'bogen'."""
|
||||
for el in strecke:
|
||||
erwartet = "bogen" if el["gruppe"] == "kurve_180" else "gerade"
|
||||
assert el["type"] == erwartet, (
|
||||
f"seq {el['seq']}: type={el['type']}, erwartet {erwartet}")
|
||||
|
||||
def test_vier_geraden_je_seite(self, strecke):
|
||||
assert sum(1 for el in strecke if el["gruppe"] == "gerade_start") == 4
|
||||
assert sum(1 for el in strecke if el["gruppe"] == "gerade_ende") == 4
|
||||
|
||||
def test_kurve_aus_mehreren_boegen(self, strecke):
|
||||
"""Die 180-Grad-Kurve muss aus mehr als einem Bogen bestehen."""
|
||||
boegen = [el for el in strecke if el["gruppe"] == "kurve_180"]
|
||||
assert len(boegen) > 1, "Kurve muss aus mehreren Boegen aufgebaut sein"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. Katalog-Abgleich der Boegen
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestStreckeKatalog:
|
||||
|
||||
def test_bogen_sivasnr_im_katalog(self, strecke, boegen_lookup):
|
||||
for el in strecke:
|
||||
if el["type"] != "bogen":
|
||||
continue
|
||||
assert el["sivasnr"] in boegen_lookup, (
|
||||
f"SivasNr {el['sivasnr']} nicht in omniflo_boegen.json")
|
||||
|
||||
def test_bogen_radius_und_winkel(self, strecke, boegen_lookup):
|
||||
for el in strecke:
|
||||
if el["type"] != "bogen":
|
||||
continue
|
||||
eintrag = boegen_lookup[el["sivasnr"]]
|
||||
assert float(eintrag["Radius"]) == pytest.approx(el["radius"], abs=_TOL), (
|
||||
f"{el['sivasnr']}: Radius Katalog={eintrag['Radius']}, Test={el['radius']}")
|
||||
assert float(eintrag["KurvenWinkel"]) == pytest.approx(el["winkel"], abs=_TOL), (
|
||||
f"{el['sivasnr']}: Winkel Katalog={eintrag['KurvenWinkel']}, Test={el['winkel']}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. Geometrie der Verkettung
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestStreckeGeometrie:
|
||||
|
||||
def test_verkettung_lueckenlos(self, strecke):
|
||||
"""Endpunkt jedes Elements == Anfangspunkt des naechsten (verkettet)."""
|
||||
for a, b in zip(strecke, strecke[1:]):
|
||||
assert a["x_ende"] == pytest.approx(b["x"], abs=_TOL), (
|
||||
f"Luecke x zwischen seq {a['seq']} und {b['seq']}: "
|
||||
f"{a['x_ende']} != {b['x']}")
|
||||
assert a["y_ende"] == pytest.approx(b["y"], abs=_TOL), (
|
||||
f"Luecke y zwischen seq {a['seq']} und {b['seq']}: "
|
||||
f"{a['y_ende']} != {b['y']}")
|
||||
assert a["drehung_ende"] == pytest.approx(b["drehung"], abs=_TOL), (
|
||||
f"Richtungssprung zwischen seq {a['seq']} und {b['seq']}: "
|
||||
f"{a['drehung_ende']} != {b['drehung']}")
|
||||
|
||||
def test_geraden_behalten_richtung(self, strecke):
|
||||
"""Eine Gerade aendert die Fahrtrichtung nicht."""
|
||||
for el in strecke:
|
||||
if el["type"] != "gerade":
|
||||
continue
|
||||
assert el["drehung"] == pytest.approx(el["drehung_ende"], abs=_TOL), (
|
||||
f"seq {el['seq']}: Gerade darf Richtung nicht aendern")
|
||||
|
||||
def test_geraden_laenge(self, strecke):
|
||||
"""Die euklidische Distanz Anfang->Ende entspricht der Laenge."""
|
||||
for el in strecke:
|
||||
if el["type"] != "gerade":
|
||||
continue
|
||||
dist = math.hypot(el["x_ende"] - el["x"], el["y_ende"] - el["y"])
|
||||
assert dist == pytest.approx(el["laenge"], abs=0.5), (
|
||||
f"seq {el['seq']}: Distanz {dist:.2f} != Laenge {el['laenge']}")
|
||||
|
||||
def test_kurve_summiert_180_grad(self, strecke):
|
||||
"""Die Boegen der Kurve summieren auf exakt 180 Grad."""
|
||||
summe = sum(el["winkel"] for el in strecke if el["gruppe"] == "kurve_180")
|
||||
assert summe == pytest.approx(180.0, abs=_TOL), (
|
||||
f"Kurvensumme {summe} Grad, erwartet 180")
|
||||
|
||||
def test_kurve_dreht_fahrtrichtung_um_180(self, strecke):
|
||||
"""Fahrtrichtung vor der Kurve + 180 == Fahrtrichtung danach."""
|
||||
boegen = [el for el in strecke if el["gruppe"] == "kurve_180"]
|
||||
vor = boegen[0]["drehung"]
|
||||
nach = boegen[-1]["drehung_ende"]
|
||||
assert (nach - vor) == pytest.approx(180.0, abs=_TOL), (
|
||||
f"Kurve dreht um {nach - vor} Grad, erwartet 180")
|
||||
|
||||
def test_boegen_gemeinsamer_mittelpunkt(self, strecke):
|
||||
"""Gleichradige 45-Grad-Boegen einer 180-Grad-Kurve teilen einen Mittelpunkt."""
|
||||
mitten = [(el["x_mitte"], el["y_mitte"])
|
||||
for el in strecke if el["gruppe"] == "kurve_180"]
|
||||
mx0, my0 = mitten[0]
|
||||
for mx, my in mitten[1:]:
|
||||
assert mx == pytest.approx(mx0, abs=_TOL) and my == pytest.approx(my0, abs=_TOL), (
|
||||
f"Bogen-Mittelpunkt ({mx}, {my}) weicht von ({mx0}, {my0}) ab")
|
||||
|
||||
def test_bogenpunkte_liegen_auf_kreis(self, strecke):
|
||||
"""Anfangs-/Endpunkte der Boegen liegen im Radius-Abstand vom Mittelpunkt."""
|
||||
for el in strecke:
|
||||
if el["gruppe"] != "kurve_180":
|
||||
continue
|
||||
for px, py in ((el["x"], el["y"]), (el["x_ende"], el["y_ende"])):
|
||||
d = math.hypot(px - el["x_mitte"], py - el["y_mitte"])
|
||||
assert d == pytest.approx(el["radius"], abs=0.5), (
|
||||
f"seq {el['seq']}: Punkt ({px}, {py}) Abstand {d:.2f} != Radius {el['radius']}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. Integrationstest gegen BricsCAD-Ergebnis (optional)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestStreckeResults:
|
||||
"""Validiert omniflo_strecke_results.json aus c:TEST_OMNIFLO_STRECKE."""
|
||||
|
||||
@pytest.fixture
|
||||
def results(self):
|
||||
path = os.path.join(_output_dir(), "omniflo_strecke_results.json")
|
||||
if not os.path.exists(path):
|
||||
pytest.skip("omniflo_strecke_results.json nicht vorhanden - "
|
||||
"TEST_OMNIFLO_STRECKE in BricsCAD ausfuehren")
|
||||
data = _load_json(path)
|
||||
if not data:
|
||||
pytest.skip("omniflo_strecke_results.json ist leer")
|
||||
return data
|
||||
|
||||
def test_alle_elemente_ausgefuehrt(self, strecke, results):
|
||||
assert len(results) == len(strecke), (
|
||||
f"Erwartet {len(strecke)} Ergebnisse, erhalten {len(results)}")
|
||||
|
||||
def test_alle_ok(self, results):
|
||||
for r in results:
|
||||
assert r["status"] == "OK", (
|
||||
f'{r.get("test_id")} (seq {r.get("seq")}): Status={r["status"]}')
|
||||
Reference in New Issue
Block a user