[FIX] Export: Schnittpunkte zwischen Kreiseln werden erkannt und Weichen entsprechend gezählt und als Teil mit Merkmalen im Export befüllt - nur Kreisel mit gleicher Höhe können einen Schnittpunkt für Weiche bilden - Funktion der Weichenzählung manuell aufrufbar; [FIX] Eckrad nun korrekt angelegt und exportiert

This commit is contained in:
2026-10-14 07:42:08 +02:00
parent 6044c35957
commit 8039251bb8
18 changed files with 1452 additions and 481 deletions
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test_export_sivas_eckrad.py - Sivas-Merkmale des ILS Eckrads (6210),
am Kreisel ausgerichtet (cfg/sivas.cfg [eckrad]).
Laeuft komplett ohne BricsCAD (reine Python-Logik).
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
import export_sivas # noqa: E402
def _eckrad(nr, hoehe="2000", einzelmodul="1", typ="Schoenenberger Geruest"):
return {"block_name": f"ECKRAD_{nr}", "x": 0.0, "y": 0.0, "z": float(hoehe),
"rotation": 0.0,
"attribs": {"ID": f"000{nr}", "Bezeichnung": f"Eckrad {nr}",
"NUMMER": str(nr), "HOEHE": hoehe,
"GERUEST_EINZELMODUL": einzelmodul, "GERUEST_TYP": typ}}
def test_eckrad_details_wie_kreisel():
details = export_sivas.build_eckrad_details(_eckrad(1))
assert details == {"2950": "2.0", "3381": "1", "3992": "Ja",
"3993": export_sivas.geruest_typ_anzeige("Schoenenberger Geruest")}
def test_eckrad_ohne_geruest_ist_nein():
details = export_sivas.build_eckrad_details(_eckrad(1, einzelmodul="0"))
assert details["3992"] == "Nein"
def test_eckrad_hoehe_in_metern():
assert export_sivas.build_eckrad_details(_eckrad(1, hoehe="4820"))["2950"] == "4.82"
def _kreisel(nr, montagehoehe):
return {"block_name": f"KREISEL_{nr}", "x": 0.0, "y": 0.0, "z": 0.0,
"rotation": 0.0,
"attribs": {"ID": f"000{nr}", "NUMMER": str(nr), "KREISELART": "STANDARD",
"ABSTAND": "2300", "MONTAGEHOEHE_m": montagehoehe}}
def test_kreisel_verschiedene_hoehen_getrennte_zeilen():
assert (export_sivas.kreisel_signature(_kreisel(1, "4.820"))
!= export_sivas.kreisel_signature(_kreisel(2, "2.000")))
# numerischer Vergleich: gleiche Hoehe, andere Schreibweise -> eine Zeile
assert (export_sivas.kreisel_signature(_kreisel(1, "4.820"))
== export_sivas.kreisel_signature(_kreisel(2, "4.82")))
def test_eckrad_verschiedene_hoehen_getrennte_zeilen():
assert (export_sivas.eckrad_signature(_eckrad(1, hoehe="2000"))
!= export_sivas.eckrad_signature(_eckrad(2, hoehe="2500")))
assert (export_sivas.eckrad_signature(_eckrad(1, hoehe="2000"))
== export_sivas.eckrad_signature(_eckrad(2, hoehe="2000")))
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test_kreisel_weichen_json.py - Uebernahme der LISP-Kreisel-Beruehrpunkte.
Der LISP-Export (export.lsp csv:kreisel-weichen-schreiben, Kern
KreiselInsert.lsp kreisel-bp-*) schreibt kreisel_weichen.json neben
export_raw.json. export_csv.py/export_sivas.py lesen sie statt selbst zu
rechnen; fehlt sie, bleibt es beim alten Python-Rueckfall.
Laeuft ohne BricsCAD.
"""
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
from export_neighbors import ( # noqa: E402
lisp_touch_switches,
load_lisp_kreisel_weichen,
)
import export_sivas # noqa: E402
BOX = (250.0, 100.0, 30.0)
BEISPIEL = {
"toleranz_mm": 1.0,
"beruehrungen": [
# Rad an Laufprofil, Radiale senkrecht (90 Grad)
{"a": "0001", "b": "0002", "a_handle": "2A", "b_handle": "2B",
"a_typ": "KREISEL", "b_typ": "KREISEL", "x": 22503.49, "y": -2821.76,
"z": 5000.0, "art": "Kreis/Tangente", "detail": "AN8/Profil",
"achse_von": "0002", "achse_rot_grad": 270.0},
# Zweiter Punkt desselben Paares -> eigener Schluessel
{"a": "0001", "b": "0002", "a_handle": "2A", "b_handle": "2B",
"a_typ": "KREISEL", "b_typ": "KREISEL", "x": 23000.0, "y": -2821.76,
"z": 5000.0, "art": "Kreis/Kreis", "detail": "SP8/AN8",
"achse_von": "0001", "achse_rot_grad": 0.0},
# Eckrad ohne ID -> Handle
{"a": "", "b": "0003", "a_handle": "3F", "b_handle": "3C",
"a_typ": "ECKRAD", "b_typ": "KREISEL", "x": 1.0, "y": 2.0, "z": 0.0,
"art": "Kreis/Kreis", "detail": "ECKRAD/AN8",
"achse_von": "", "achse_rot_grad": 0.0},
],
"kollisionen": [
{"a": "0004", "b": "0005", "x": 0.0, "y": 0.0, "z": 0.0,
"art": "Ueberschneidung", "detail": "Achsabstand 500.0",
"achse_von": "0004", "achse_rot_grad": 0.0},
],
}
def _schreibe(tmp_path, daten):
raw = tmp_path / "export_raw.json"
raw.write_text("[]", encoding="utf-8")
(tmp_path / "kreisel_weichen.json").write_text(json.dumps(daten), encoding="utf-8")
return str(raw)
def test_datei_fehlt_liefert_none(tmp_path):
raw = tmp_path / "export_raw.json"
raw.write_text("[]", encoding="utf-8")
assert load_lisp_kreisel_weichen(str(raw)) is None
def test_kaputte_datei_liefert_none(tmp_path):
raw = tmp_path / "export_raw.json"
raw.write_text("[]", encoding="utf-8")
(tmp_path / "kreisel_weichen.json").write_text("{kaputt", encoding="utf-8")
assert load_lisp_kreisel_weichen(str(raw)) is None
def test_switches_aus_lisp(tmp_path):
daten = load_lisp_kreisel_weichen(_schreibe(tmp_path, BEISPIEL))
sw = lisp_touch_switches(daten, element_box_mm=BOX)
# Kollisionen werden keine Weichen
assert len(sw) == 3
assert [s["key"] for s in sw] == ["WEICHE#0001#0002", "WEICHE#0001#0002#2",
"WEICHE#3F#0003"]
assert sw[0]["nachbarn"] == ["0001", "0002"]
assert sw[0]["beruehrung"] == "Kreis/Tangente"
assert sw[0]["achse_von"] == "0002"
assert (sw[0]["x"], sw[0]["y"], sw[0]["z"]) == (22503.49, -2821.76, 5000.0)
# Radiale 270 Grad: Laengsseite (250) quer zur Radialen -> entlang X
assert abs(sw[0]["dx"] - 250.0) < 1e-6 and abs(sw[0]["dy"] - 100.0) < 1e-6
# Radiale 0 Grad: Laengsseite entlang Y
assert abs(sw[1]["dx"] - 100.0) < 1e-6 and abs(sw[1]["dy"] - 250.0) < 1e-6
# Ohne achse_von -> erster Partner
assert sw[2]["achse_von"] == "3F"
def test_sivas_4403_zaehlt_beruehrungen():
_, counters = export_sivas.process_blocks([], anzahl_weichen_ils=10)
assert counters["anzahl_weichen_ils"] == 10
_, counters = export_sivas.process_blocks([])
assert counters["anzahl_weichen_ils"] == 0
def test_sivas_6216_summenzeile():
"""Eine Zeile 6216, je Beruehrpunkt 1x aktiv (3371) + 1x passiv (3372)."""
items, _ = export_sivas.process_blocks([], anzahl_weichen_ils=10)
weichen = [i for i in items if i["sivasnr"] == "6216"]
assert len(weichen) == 1
w = weichen[0]
assert w["teileart"] == "ILS 2.0 Weiche"
assert w["anzahl"] == 1
assert w["details"] == {"3371": "10", "3372": "10"}
# CSV-Zeile laesst sich formatieren
assert ';"6216";' in export_sivas.format_csv_line(w)
def test_sivas_6216_ohne_weichen_keine_zeile():
items, _ = export_sivas.process_blocks([])
assert not [i for i in items if i["sivasnr"] == "6216"]
+94 -1
View File
@@ -756,6 +756,99 @@
)
;; ------------------------------------------------------------
;; TESTS: KreiselInsert.lsp - Beruehrpunkte/Weichen (kreisel-bp-*)
;; ------------------------------------------------------------
;; Element-Record kurz: (typ x y z rot abstand id)
(defun tu-bp-el (typ x y z rot abstand id)
(kreisel-bp-element typ (list x y) z rot abstand 400.0 id id id)
)
(defun tu-bp-arten (treffer)
(mapcar '(lambda (tr) (cdr (assoc "art" tr))) treffer)
)
(defun tu-tests-kreisel-bp ( / _els _erg _ber _kol)
(princ "\n[KreiselInsert/Beruehrpunkte]")
;; Hilfs-Kerne
(tu-eqf "kreisel-bp-abstand/attribut" 7715.0 (kreisel-bp-abstand "7715" "KREISEL_1_STANDARD_7715"))
(tu-eqf "kreisel-bp-abstand/blockname" 3469.0 (kreisel-bp-abstand nil "KREISEL_3_STANDARD_3469"))
(tu-eqf "kreisel-bp-naechste-punkte/kreuz"
'((5.0 0.0) (5.0 0.0))
(kreisel-bp-naechste-punkte '(0.0 0.0) '(10.0 0.0) '(5.0 -5.0) '(5.0 5.0)))
;; Die 11 Einfuegungen aus tests/testdata/schnittspunkte_kreisel.dxf
(setq _els
(list
(tu-bp-el "ECKRAD" 22405.42028816571 -4021.762548616969 5000.0 270.0 0.0 "E1")
(tu-bp-el "KREISEL" 20971.14247926632 -3221.762548616969 5000.0 0.0 7715.0 "K1")
(tu-bp-el "KREISEL" 22503.49142858727 -2821.762548616969 5000.0 90.0 9020.0 "K2")
(tu-bp-el "KREISEL" 27253.08134883916 -3621.762548616969 5000.0 270.0 3469.0 "K3")
(tu-bp-el "KREISEL" 22503.49142858727 6998.075803812235 5000.0 90.0 4746.0 "K4")
(tu-bp-el "KREISEL" 22405.42028816571 -4421.762548616969 5000.0 270.0 6147.0 "K5")
(tu-bp-el "KREISEL" 26853.08134883916 -6582.442351476258 5000.0 180.0 3248.0 "K6")
(tu-bp-el "KREISEL" 32541.84792939903 -5752.219206460449 2000.0 270.0 9200.0 "K7")
(tu-bp-el "KREISEL" 32941.84792939903 -7662.266486416901 2000.0 0.0 3637.0 "K8a")
(tu-bp-el "KREISEL" 32941.84792939903 -11185.64734962363 2000.0 0.0 3637.0 "K8b")
(tu-bp-el "ECKRAD" 20571.14247926632 -3221.762548616969 5000.0 270.0 0.0 "E1b")))
(setq _erg (kreisel-bp-berechnen _els 1.0 1.0)
_ber (car _erg)
_kol (cadr _erg))
(tu-eq "kreisel-bp/dxf-beruehrungen" 10 (length _ber))
(tu-eq "kreisel-bp/dxf-kollisionen" 0 (length _kol))
(tu-eq "kreisel-bp/dxf-kreis-kreis" 3
(length (vl-remove-if-not '(lambda (a) (= a "Kreis/Kreis")) (tu-bp-arten _ber))))
(tu-eq "kreisel-bp/dxf-paare"
'(("E1" "K1") ("E1" "K5") ("K1" "K2") ("K1" "K3") ("K1" "E1b") ("K2" "K4")
("K3" "K6") ("K5" "K6") ("K7" "K8a") ("K7" "K8b"))
(mapcar '(lambda (tr) (list (cdr (assoc "id" (cdr (assoc "a" tr))))
(cdr (assoc "id" (cdr (assoc "b" tr))))))
_ber))
;; K2 (AN8) steht auf dem oberen Laufprofil von K1
(tu-eqf "kreisel-bp/dxf-punkt-k1-k2"
(list 22503.49142858727 -2821.762548616969)
(cdr (assoc "p" (nth 2 _ber))))
(tu-eq "kreisel-bp/dxf-achse-von-k1-k2" "K2" (cdr (assoc "achse_von" (nth 2 _ber))))
;; Unterschiedliche Hoehe -> kein Kontakt
(tu-eq "kreisel-bp/hoehe"
'(nil nil)
(kreisel-bp-berechnen (list (tu-bp-el "ECKRAD" 0.0 0.0 0.0 0.0 0.0 "A")
(tu-bp-el "ECKRAD" 800.0 0.0 500.0 0.0 0.0 "B"))
1.0 1.0))
;; Zwei Eckraeder Rad an Rad -> eine Weiche in der Mitte
(setq _ber (car (kreisel-bp-berechnen (list (tu-bp-el "ECKRAD" 0.0 0.0 0.0 0.0 0.0 "A")
(tu-bp-el "ECKRAD" 800.0 0.0 0.0 0.0 0.0 "B"))
1.0 1.0)))
(tu-eq "kreisel-bp/eckraeder-art" '("Kreis/Kreis") (tu-bp-arten _ber))
(tu-eqf "kreisel-bp/eckraeder-punkt" '(400.0 0.0) (cdr (assoc "p" (car _ber))))
;; Spalt groesser als Toleranz -> nichts
(tu-eq "kreisel-bp/spalt"
'(nil nil)
(kreisel-bp-berechnen (list (tu-bp-el "ECKRAD" 0.0 0.0 0.0 0.0 0.0 "A")
(tu-bp-el "ECKRAD" 805.0 0.0 0.0 0.0 0.0 "B"))
1.0 1.0))
;; Laufprofile kreuzen sich -> Kollision, keine Weiche
(setq _erg (kreisel-bp-berechnen (list (tu-bp-el "KREISEL" 0.0 0.0 0.0 0.0 2000.0 "A")
(tu-bp-el "KREISEL" 1000.0 -1500.0 0.0 90.0 2000.0 "B"))
1.0 1.0))
(tu-nil "kreisel-bp/kreuzung-keine-weiche" (car _erg))
(tu-eq "kreisel-bp/kreuzung-kollision" '("Ueberschneidung") (tu-bp-arten (cadr _erg)))
;; Kreisel Seite an Seite (Profile aufeinander) -> Kollision, keine Weiche
(setq _erg (kreisel-bp-berechnen (list (tu-bp-el "KREISEL" 0.0 0.0 0.0 0.0 2000.0 "A")
(tu-bp-el "KREISEL" 500.0 800.0 0.0 0.0 2000.0 "B"))
1.0 1.0))
(tu-nil "kreisel-bp/parallel-keine-weiche" (car _erg))
(tu-eq "kreisel-bp/parallel-kollision" '("Tangente/Tangente") (tu-bp-arten (cadr _erg)))
;; Eckrad an der Flanke eines Kreiselrades -> Weiche (freie Seite)
(setq _ber (car (kreisel-bp-berechnen (list (tu-bp-el "KREISEL" 0.0 0.0 0.0 0.0 2000.0 "A")
(tu-bp-el "ECKRAD" 400.0 800.0 0.0 0.0 0.0 "B"))
1.0 1.0)))
(tu-eq "kreisel-bp/flanke-eckrad" 1 (length _ber))
)
;; ------------------------------------------------------------
;; TESTS: ssg_ks_insert.lsp - Sepliste-Serialisierung
;; ------------------------------------------------------------
@@ -2046,7 +2139,7 @@
(foreach fn '("tu-tests-core" "tu-tests-lang" "tu-tests-id" "tu-tests-vfcore"
"tu-tests-etage"
"tu-tests-export" "tu-tests-hoehen" "tu-tests-gf" "tu-tests-omni" "tu-tests-kreisel"
"tu-tests-export" "tu-tests-hoehen" "tu-tests-gf" "tu-tests-omni" "tu-tests-kreisel" "tu-tests-kreisel-bp"
"tu-tests-sepliste" "tu-tests-json" "tu-tests-geruest"
"tu-tests-sepscan" "tu-tests-vfl-segs" "tu-tests-vfl-abbruch"
"tu-tests-werkzeuge")