#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ test_export_graph.py - Materialfluss-Graph (lib/export_graph.py). Laeuft komplett ohne BricsCAD (reine Python-Logik). Regressionstest fuer einen realen Befund: BTMT Beladung/Entladung sind die Ein-/Ausgaenge des gesamten Materialfluss-Graphen (Beladung speist neu ein, Entladung nimmt endgueltig raus), wurden aber bisher wie ein generisches Durchlaufelement mit ZWEI Enden behandelt (_enden nahm die laengere Boundingbox-Seite als Achse) - dadurch bekamen sie mehrere, teils widerspruechliche Kanten und die Rolle "Element" statt "Eingang"/"Ausgang". """ import csv import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib")) from export_graph import load_elements, build_graph # noqa: E402 def _write_csv(path, rows): header = ["Elementnummer", "TeileArt", "TeileId", "Bezeichnung", "Planquadrat", "Anzahl", "Position", "Boundingbox", "Insertpoint", "K1", "K2", "K3", "K4", "Nachbarn", "TrackIds", "Fehler", "Warnungen", "Merkmale"] with open(path, "w", encoding="utf-8", newline="") as f: writer = csv.DictWriter(f, fieldnames=header, delimiter=";") writer.writeheader() for row in rows: full = {k: "" for k in header} full.update(row) writer.writerow(full) def _zeile(teileart, teileid, x, y, z, dx, dy, dz, bezeichnung=""): return { "TeileArt": teileart, "TeileId": teileid, "Bezeichnung": bezeichnung or teileart, "Position": f"{x:.2f}, {y:.2f}, {z:.2f}", "Boundingbox": f"{dx:.2f}, {dy:.2f}, {dz:.2f}", "Merkmale": "{}", } class TestBtmtEinAusgangDesGraphen: """Kreisel (5000x800mm, Achse entlang X) mit einer BTMT-Beladung UND einer BTMT-Entladung, beide mittig auf der Kreiselflaeche platziert (wie physisch: sie sitzen AUF der Kreiselbahn, keine eigenstaendige Durchlaufstrecke).""" def _csv_pfad(self, tmp_path): rows = [ _zeile("ILS 2.0 Kreisel", "0001", 2500.0, 0.0, 1500.0, 5000.0, 800.0, 1.0), _zeile("ILS 2.0 BTMT Beladung", "0002", 1000.0, 0.0, 1500.0, 400.0, 650.0, 300.0), _zeile("ILS 2.0 BTMT Entladung", "0003", 4000.0, 0.0, 1500.0, 900.0, 1450.0, 450.0), ] pfad = os.path.join(tmp_path, "graph_test.csv") _write_csv(pfad, rows) return pfad def test_beladung_hat_nur_einen_anschluss(self, tmp_path): elemente = load_elements(self._csv_pfad(tmp_path)) beladung = next(e for e in elemente if e["teileart"] == "ILS 2.0 BTMT Beladung") assert list(beladung["enden"].keys()) == ["a"], ( "BTMT Beladung darf nur EINEN Anschlusspunkt haben (Elementmitte), " f"hat aber {list(beladung['enden'].keys())}") def test_entladung_hat_nur_einen_anschluss(self, tmp_path): elemente = load_elements(self._csv_pfad(tmp_path)) entladung = next(e for e in elemente if e["teileart"] == "ILS 2.0 BTMT Entladung") assert list(entladung["enden"].keys()) == ["a"] def test_beladung_ist_reiner_eingang(self, tmp_path): """Beladung speist Material EIN - kein Vorgaenger im Fluss (grad_ein=0), genau ein Nachfolger (grad_aus=1), Rolle "Eingang".""" elemente = load_elements(self._csv_pfad(tmp_path)) graph = build_graph(elemente) knoten = next(n for n in graph["knoten"] if n["teileart"] == "ILS 2.0 BTMT Beladung") assert knoten["rolle"] == "Eingang" assert knoten["grad_ein"] == 0, "Beladung darf keinen Vorgaenger im Fluss haben" assert knoten["grad_aus"] == 1, "Beladung muss genau einen Nachfolger haben" def test_entladung_ist_reiner_ausgang(self, tmp_path): """Entladung nimmt Material AUS - kein Nachfolger im Fluss (grad_aus=0), genau ein Vorgaenger (grad_ein=1), Rolle "Ausgang".""" elemente = load_elements(self._csv_pfad(tmp_path)) graph = build_graph(elemente) knoten = next(n for n in graph["knoten"] if n["teileart"] == "ILS 2.0 BTMT Entladung") assert knoten["rolle"] == "Ausgang" assert knoten["grad_ein"] == 1, "Entladung muss genau einen Vorgaenger haben" assert knoten["grad_aus"] == 0, "Entladung darf keinen Nachfolger im Fluss haben" def test_kante_beladung_zeigt_zum_kreisel(self, tmp_path): elemente = load_elements(self._csv_pfad(tmp_path)) graph = build_graph(elemente) beladung_idx = next(i for i, e in enumerate(elemente) if e["teileart"] == "ILS 2.0 BTMT Beladung") kreisel_idx = next(i for i, e in enumerate(elemente) if e["teileart"] == "ILS 2.0 Kreisel") kante = next(k for k in graph["kanten"] if beladung_idx in (k["von"], k["nach"])) assert kante["von"] == beladung_idx and kante["nach"] == kreisel_idx assert kante["gerichtet"] is True assert kante["richtung_quelle"] == "btmt" def test_kante_entladung_kommt_vom_kreisel(self, tmp_path): elemente = load_elements(self._csv_pfad(tmp_path)) graph = build_graph(elemente) entladung_idx = next(i for i, e in enumerate(elemente) if e["teileart"] == "ILS 2.0 BTMT Entladung") kreisel_idx = next(i for i, e in enumerate(elemente) if e["teileart"] == "ILS 2.0 Kreisel") kante = next(k for k in graph["kanten"] if entladung_idx in (k["von"], k["nach"])) assert kante["von"] == kreisel_idx and kante["nach"] == entladung_idx assert kante["gerichtet"] is True assert kante["richtung_quelle"] == "btmt"