#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ test_dxf_scan_gf.py - Gefaellestrecken aus Staustrecken-Linien (gf_eintraege_aus_linien in lib/dxf_scan_components.py). Laeuft ohne BricsCAD und ohne DXF: die Linien und Ausschleuselemente sind die gemessenen Werte aus Dematic_arvato_reduced.dxf, von Hand nachgestellt. 1. Fallende Linie mit AS am oberen Ende -> GF, AS wird verbraucht. 2. Der Nachbar-AS der Linie daneben (800 mm Raster, auf der anderen Seite naeher dran) wird NICHT gegriffen - die Seite aus dem Blocknamen entscheidet, nicht der kleinste Abstand. 3. Rueckwaerts gezeichnete GF (tiefes Ende zuerst) -> Start bleibt oben. 4. AS am UNTEREN Ende -> Steigfoerderer, keine GF, AS bleibt unverbraucht. 5. Waagrechte Linie -> keine GF. 6. Anschluss an die Kreisel: Start/Ende werden auf die Ketten geschoben, deltaL reicht von Kette zu Kette; Hoehen kommen von den Kreiseln. 7. Separator auf der Achse wird verbraucht, einer daneben nicht. 8. hoehen_entfernen laesst nur Anwender-Hoehen stehen. """ import os import sys import pytest sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "lib")) import dxf_scan_components as dsc # noqa: E402 AS_LINKS = "Ausschleuselement links 200000217 Draufsicht" def _as(x, y, z=3191.63, block=AS_LINKS): return {"familie": "schleuselement", "block": block, "x": x, "y": y, "z": z, "rot": 270.0} def _linie(a, b, layer="6-SP"): return {"layer": layer, "a": list(a), "b": list(b)} @pytest.fixture(autouse=True) def _projekt(monkeypatch): monkeypatch.setattr(dsc, "PROJEKT", "Test") # Die beiden Kreisel am GF-Feld (Radmitten AN8 -> SP8, r = 400), wie # baue_eintraege sie als kreisel_geo anlegt. Ketten: oben y = 12919.72, # unten y = 24478.8. def _kreisel_oben(z=2437.0): return {"id": "K_oben", "a": (35460.37, 12519.72), "u": (-1.0, 0.0), "laenge": 33044.28, "r": 400.0, "z_anwender": z} def _kreisel_unten(z=1826.0): return {"id": "K_unten", "a": (36261.24, 24878.8), "u": (-1.0, 0.0), "laenge": 33470.18, "r": 400.0, "z_anwender": z} def test_gf_mit_as_am_oberen_ende(): linie = _linie((5833.56, 13348.38, 3191.63), (5833.44, 24002.54, 2821.63)) as_eigen = _as(5367.10, 14505.61) eintraege, belegt, hinweise = dsc.gf_eintraege_aus_linien([linie], [as_eigen]) assert len(eintraege) == 1 gf = eintraege[0] assert gf["test_id"] == "GF_Test_01" assert gf["modus"] == 1 # ohne Kreisel: Linienende bleibt stehen, Hoehe unbekannt -> Platzhalter assert gf["start_mm"] == [5833.56, 13348.38, 0.0] assert gf["deltaL_mm"] == pytest.approx(10654.16, abs=0.01) assert gf["hz_grad"] == 90.0 assert gf["winkel_grad"] == 3.0 assert gf["hoehe_unbekannt"] is True assert (gf["as_seite"], gf["as_winkel"]) == ("links", "90") assert belegt == {id(as_eigen)} assert len(hinweise) == 2 and all("kein Kreisel" in h for h in hinweise) def test_anschluss_an_beide_kreisel_mit_hoehen(): linie = _linie((5833.56, 13348.38, 3191.63), (5833.44, 24002.54, 2821.63)) eintraege, _, hinweise = dsc.gf_eintraege_aus_linien( [linie], [_as(5367.10, 14505.61)], [_kreisel_oben(), _kreisel_unten()]) gf = eintraege[0] assert gf["start_mm"][1] == pytest.approx(12919.72, abs=0.01) assert gf["start_mm"][2] == 2437.0 assert gf["hoehe_ende_mm"] == 1826.0 assert gf["hoehe_quelle"] == "Anwender" assert "winkel_grad" not in gf # rechnet der LISP-Treiber assert gf["deltaL_mm"] == pytest.approx(24478.8 - 12919.72, abs=0.05) assert (gf["kreisel_start"], gf["kreisel_ende"]) == ("K_oben", "K_unten") assert hinweise == [] def test_kreisel_ohne_hoehe_gibt_platzhalter(): linie = _linie((5833.56, 13348.38, 3191.63), (5833.44, 24002.54, 2821.63)) eintraege, _, _ = dsc.gf_eintraege_aus_linien( [linie], [], [_kreisel_oben(z=None), _kreisel_unten()]) assert eintraege[0]["kreisel_start"] == "K_oben" assert "hoehe_ende_mm" not in eintraege[0] assert eintraege[0]["winkel_grad"] == 3.0 def test_separator_auf_achse_wird_verbraucht(): linie = _linie((5833.56, 13348.38, 3191.63), (5833.44, 24002.54, 2821.63)) sep_achse = {"familie": "sensor", "block": "S-SP", "x": 5834.0, "y": 23810.0} sep_daneben = {"familie": "sensor", "block": "S-SP", "x": 6734.0, "y": 23810.0} scanner = {"familie": "sensor", "block": "Scanner", "x": 5834.0, "y": 20000.0} _, belegt, _ = dsc.gf_eintraege_aus_linien( [linie], [], [_kreisel_oben(), _kreisel_unten()], [sep_achse, sep_daneben, scanner]) assert belegt == {id(sep_achse)} def test_hoehen_entfernen_laesst_anwender_hoehen_stehen(): eintraege = [ {"id": "K1", "x": 0, "y": 0, "z": 2437.0, "hoehe_quelle": "Anwender"}, {"block": "S-SP", "x": 0, "y": 0, "z": 2821.63, "comment": ""}, {"test_id": "G", "typ": "gerade", "startpunkt": [0, 0, 5.0], "endpunkt": [1, 0, 5.0]}, ] assert dsc.hoehen_entfernen(eintraege) == 2 assert eintraege[0]["z"] == 2437.0 assert eintraege[1]["z"] == 0.0 and eintraege[1]["hoehe_unbekannt"] is True assert eintraege[2]["startpunkt"][2] == 0.0 and eintraege[2]["endpunkt"][2] == 0.0 def test_nachbar_as_auf_falscher_seite_wird_nicht_gegriffen(): # Linie bei x=5834; ihr AS 467 mm links, der AS der Nachbarlinie # (x=6734) 433 mm RECHTS - also naeher, aber auf der falschen Seite. linie = _linie((5833.56, 13348.38, 3191.63), (5833.44, 24002.54, 2821.63)) as_eigen = _as(5367.10, 14505.61) as_nachbar = _as(6267.10, 14505.61) _, belegt, _ = dsc.gf_eintraege_aus_linien([linie], [as_nachbar, as_eigen]) assert belegt == {id(as_eigen)} def test_rueckwaerts_gezeichnete_gf_startet_oben(): linie = _linie((5833.44, 24002.54, 2821.63), (5833.56, 13348.38, 3191.63)) eintraege, _, _ = dsc.gf_eintraege_aus_linien([linie], [_as(5367.10, 14505.61)]) assert eintraege[0]["start_mm"][:2] == [5833.56, 13348.38] assert eintraege[0]["hz_grad"] == 90.0 def test_as_am_unteren_ende_ist_steigfoerderer(): # Gemessen: Linie x=2801 von 2822 hinauf auf 3192, AS am tiefen Ende. linie = _linie((2801.42, 24043.0, 2821.63), (2801.42, 13389.0, 3191.63)) as_unten = _as(3267.48, 22892.92, z=2821.63) eintraege, belegt, hinweise = dsc.gf_eintraege_aus_linien([linie], [as_unten]) assert eintraege == [] assert belegt == set() assert len(hinweise) == 1 and "Steigfoerderer" in hinweise[0] def test_waagrechte_linie_ist_keine_gf(): linie = _linie((36661.0, 10279.0, 2821.63), (36661.0, 24879.0, 2821.63)) eintraege, belegt, hinweise = dsc.gf_eintraege_aus_linien([linie], []) assert eintraege == [] assert belegt == set() assert any("waagrechte" in h for h in hinweise) def test_ohne_as_defaults_und_hinweis(): linie = _linie((0.0, 0.0, 1000.0), (5000.0, 0.0, 800.0)) eintraege, _, hinweise = dsc.gf_eintraege_aus_linien([linie], []) assert eintraege[0]["hz_grad"] == 0.0 assert eintraege[0]["as_seite"] == dsc.DEFAULT_SEITE assert any("ohne Ausschleuselement" in h for h in hinweise) # --- Showroom_ILS_3_0.dxf: flacher 2D-Plan, Werte dort gemessen ------------- def _station(x, y): return {"x": x, "y": y} def test_kreisel_paare_zwei_kreisel_auf_einer_achse(): # x=26998: AN8 y=3466 / SP8 y=12499 und AN8 y=13299 / SP8 y=16299. Die # Luecke SP8(12499)-AN8(13299) = 800 ist das kuerzeste Paar - ohne # Tausch-Nachbesserung kamen 800 + 12833 statt 9033 + 3000 heraus. an = [_station(26998.0, 3466.4), _station(26998.0, 13298.9)] sp = [_station(26998.0, 16298.9), _station(26998.0, 12498.9)] paare, an_rest, sp_rest = dsc.kreisel_paare_bilden(an, sp) assert an_rest == [] and sp_rest == [] abstaende = sorted(round(p[2], 1) for p in paare) assert abstaende == [3000.0, 9032.5] def test_zerstueckelte_linie_wird_vereint(): # GF bei x=34006 in vier Stuecken, das Separatorstueck 0,3 mm daneben stuecke = [ _linie((34005.93, 13893.90, 0.0), (34005.93, 14193.90, 0.0)), _linie((34005.93, 15043.90, 0.0), (34005.93, 15593.90, 0.0)), _linie((34006.24, 14744.31, 0.0), (34006.24, 15044.31, 0.0)), _linie((34005.93, 14193.90, 0.0), (34005.93, 14743.90, 0.0)), ] vereint = dsc.linien_zusammenfassen(stuecke) assert len(vereint) == 1 ys = sorted([vereint[0]["a"][1], vereint[0]["b"][1]]) assert ys == pytest.approx([13893.90, 15593.90], abs=0.01) def test_parallele_nachbarlinie_wird_nicht_vereint(): linien = [_linie((0.0, 0.0, 0.0), (0.0, 1000.0, 0.0)), _linie((800.0, 1000.0, 0.0), (800.0, 2000.0, 0.0))] assert len(dsc.linien_zusammenfassen(linien)) == 2 def _kreisel_d(): # Schleife oben (Kette unten y=15898.9), Radmitten per --kreisel-achse return {"id": "K_D", "a": (27797.93, 16298.9), "u": (1.0, 0.0), "laenge": 8908.0, "r": 400.0, "z_anwender": None} def _kreisel_e(): # Schleife Mitte (Kette oben y=13493.9) return {"id": "K_E", "a": (28772.93, 13093.9), "u": (1.0, 0.0), "laenge": 6533.0, "r": 400.0, "z_anwender": None} def test_flacher_plan_richtung_und_seiten_aus_as_es(): # Waagrechte Linie; AE-D sitzt 305 mm VOR dem oberen Ende auf der # Kreisel-Kette, 180 mm links der Fahrtrichtung (Fahrt -y, links = +x). # EE-R am unteren Ende 212 mm rechts davon. linie = _linie((30455.93, 13893.9, 0.0), (30455.93, 15593.9, 0.0)) ae = {"familie": "schleuselement", "block": "AE-D", "x": 30636.27, "y": 15898.9, "z": 0.0, "rot": 270.0} ee = {"familie": "schleuselement", "block": "EE-R", "x": 30243.76, "y": 13493.9, "z": 0.0, "rot": 90.0} eintraege, belegt, _ = dsc.gf_eintraege_aus_linien( [linie], [ae], [_kreisel_d(), _kreisel_e()], es_liste=[ee], flach=True) assert len(eintraege) == 1 gf = eintraege[0] assert gf["hz_grad"] == 270.0 assert gf["start_mm"][:2] == [30455.93, 15898.9] assert gf["deltaL_mm"] == pytest.approx(2405.0, abs=0.01) assert (gf["as_seite"], gf["es_seite"]) == ("links", "rechts") assert (gf["kreisel_start"], gf["kreisel_ende"]) == ("K_D", "K_E") assert belegt == {id(ae), id(ee)} def test_flacher_plan_ohne_flach_schalter_keine_gf(): linie = _linie((30455.93, 13893.9, 0.0), (30455.93, 15593.9, 0.0)) ae = {"familie": "schleuselement", "block": "AE-D", "x": 30636.27, "y": 15898.9, "z": 0.0, "rot": 270.0} eintraege, _, _ = dsc.gf_eintraege_aus_linien([linie], [ae]) assert eintraege == [] def test_flacher_plan_ohne_as_keine_gf(): # Staulinie am Kreisel B (x=36348) - kein AS an einem Ende linie = _linie((36347.6, 11313.9, 0.0), (36347.6, 14913.9, 0.0)) eintraege, _, hinweise = dsc.gf_eintraege_aus_linien([linie], [], flach=True) assert eintraege == [] assert any("waagrechte" in h for h in hinweise) def test_kurznamen_ae_ee(): assert dsc.familie_von("AE-D") == "schleuselement" assert dsc.familie_von("EE-R") == "schleuselement" assert dsc.mappe_block("AE-D")[0].startswith("AS_Element_90_") assert dsc.mappe_block("EE-R")[0].startswith("ES_Element_90_") def test_sensor_bekommt_hoehe_des_kreisels(): # Kreisel_Showroom_03: Einfuegepunkt (37105.93, 9888.9), rotation 180, # Ketten y=9488.9/10288.9. Separator auf der Kette, Scanner 280 mm # innerhalb, ein Teil 1000 mm daneben bleibt unberuehrt. kreisel = {"id": "K3", "x": 37105.93, "y": 9888.9, "z": 1700.0, "abstand": 7200.0, "rotation": 180.0, "hoehe_quelle": "Anwender"} sep = {"function": "insert", "block": "S-LP", "x": 30343.39, "y": 9488.9, "z": 0.0, "hoehe_unbekannt": True, "comment": ""} scan = {"function": "insert", "block": "Scanner", "x": 35905.93, "y": 10008.9, "z": 0.0, "comment": ""} weit = {"function": "insert", "block": "S-LP", "x": 33000.0, "y": 11300.0, "z": 0.0, "comment": ""} assert dsc.sensor_hoehen_von_kreiseln([kreisel, sep, scan, weit]) == 2 assert sep["z"] == 1700.0 and "hoehe_unbekannt" not in sep assert scan["z"] == 1700.0 assert weit["z"] == 0.0 def test_sensor_ohne_anwender_hoehe_bleibt(): kreisel = {"id": "K", "x": 0.0, "y": 0.0, "z": 0.0, "abstand": 5000.0, "rotation": 0.0} sep = {"function": "insert", "block": "S-LP", "x": 1000.0, "y": 400.0, "z": 0.0, "comment": ""} assert dsc.sensor_hoehen_von_kreiseln([kreisel, sep]) == 0