feat: Omniflo-Fahrstrecke (OMNI_FAHRSTRECKE) + Geruest-Dialoge ueberarbeitet

Fahrstrecke:
- Neuer Befehl OMNI_FAHRSTRECKE (+ _BESCHRIFTEN) im Omniflo-Menue,
  Daten aus data/json/omniflo_fahrstrecke.json, Vorlage dcl/fahrstrecke.dcl
- Nutzt die Linien-Logik des Geruest-Traegers (geruest-linien-starten,
  Dialog/Einfuegen/Beschriftung je Baugruppe parametriert)
- Muffen-Dummy wie beim Traeger, farbig nur wo in der JSON gesetzt
- Stueckliste: eigene Klasse "Omniflo Fahrstrecke" (GR_Fahrstrecke_*)

Geruest-Dialoge:
- DCL als statische Vorlagen mit //@NAME@-Platzhaltern
  (ssg-dcl-vorlage-fuellen in ssg_core), dcl/geruest.dcl neu
- Ein Dialog je Bereich (keine Bereichsumschaltung mehr)
- Varianten als Radiobuttons mit Artikelnummern im Label,
  "disabled": true graut Varianten aus
- Eine Befestigungsspalte (Gesamtanzahl) + Zeile "Sonstiges",
  Art.-Nr. unter jedem Anbauteil
- Winkel Platte (0/90): Anbauteile gedreht eingefuegt
- Kennzeichnung geaenderter Konfiguration, Ueberspeichern per Enter
- Traeger: Querstreben nur bei "mit_querstrebe": true

Konfigurationen im alten Format (oben/unten) werden beim Laden
uebernommen. Unit-Tests ergaenzt (599 gruen).
This commit is contained in:
2026-10-13 11:02:23 +02:00
parent 3116e639eb
commit ea0e46a441
25 changed files with 2597 additions and 1469 deletions
+39
View File
@@ -0,0 +1,39 @@
"""Merkmale der GR_-Geruest-Bloecke im CSV-Export (build_geruest_merkmale).
Prueft die Teilefarbe (Attribut TEILEFARBE - das frueher gelesene FARBE gibt
es im Schema nicht) und die Laengenabzuege, die nur der Rohr-Geruest-Traeger
traegt und die bei den anderen GR_-Bloecken nicht auftauchen duerfen.
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib"))
from export_csv import build_geruest_merkmale # noqa: E402
def test_teilefarbe_aus_teilefarbe_attribut():
block = {"attribs": {"TEILEART": "Geruest Saeule 1 Zoll",
"TEILEFARBE": ",RAL7030", "LAENGE": "2000",
"LAYER": "D-6"}}
m = build_geruest_merkmale(block)
assert m["Farbe"] == ",RAL7030"
assert m["Laenge"] == "2000"
assert m["Layer"] == "D-6"
def test_ohne_abzug_keine_abzugsmerkmale():
m = build_geruest_merkmale({"attribs": {"TEILEART": "Geruest Rohrabhaenger"}})
assert "LaengenabzugAnfang" not in m
assert "LaengenabzugEnde" not in m
def test_traeger_abzuege():
block = {"attribs": {"TEILEART": "Geruest Traeger 1 Zoll", "LAENGE": "965",
"LAENGENABZUGANFANG": "0",
"LAENGENABZUGENDE": "35"}}
m = build_geruest_merkmale(block)
assert m["Laenge"] == "965"
assert m["LaengenabzugAnfang"] == "0"
assert m["LaengenabzugEnde"] == "35"
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test_export_sivas_zusammenfassen.py - zusammengefasste Sivas-Kopie
(<output>_zusammengefasst.csv, export_sivas.items_zusammenfassen).
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 _geruest(teileid, laenge="2500"):
return {"block_name": f"GR_{teileid}", "x": 0.0, "y": 0.0, "z": 0.0,
"rotation": 0.0,
"attribs": {"ID": teileid, "TEILEART": "Geruest Saeule 2 Zoll",
"ARTINR": "821006051,825002022,825002022",
"TEILEFARBE": "RAL7035,,",
"BEZEICHNUNG": "Saeule", "LAENGE": laenge}}
def test_gleiche_zeilen_werden_summiert():
items, _ = export_sivas.process_blocks(
[_geruest("0001"), _geruest("0002"), _geruest("0003", laenge="2800")])
assert len(items) == 6
zus = export_sivas.items_zusammenfassen(items)
# Laenge gehoert zum Schluessel: 2500er und 2800er bleiben getrennt
assert len(zus) == 4
assert [z["nr"] for z in zus] == [1, 2, 3, 4]
z = {(z["sivasnr"], z["laenge_mm"]): z for z in zus}
assert z[("825002022", "2500")]["anzahl"] == 4
assert z[("825002022", "2500")]["ids"] == ["0001", "0002"]
assert z[("821006051", "2500")]["anzahl"] == 2
assert z[("821006051", "2500")]["details"] == {export_sivas.MERKMAL_TEILEFARBE: "RAL7035"}
assert z[("825002022", "2800")]["anzahl"] == 2
assert z[("825002022", "2800")]["ids"] == ["0003"]
def test_eingabe_bleibt_unveraendert():
items, _ = export_sivas.process_blocks([_geruest("0001"), _geruest("0002")])
vorher = [export_sivas.format_csv_line(it) for it in items]
export_sivas.items_zusammenfassen(items)
assert [export_sivas.format_csv_line(it) for it in items] == vorher
def test_dateiname():
assert export_sivas.zusammengefasster_dateiname("x/500651_sivas.csv") == \
"x/500651_sivas_zusammengefasst.csv"
+129
View File
@@ -0,0 +1,129 @@
;; ============================================================
;; test_lastent.lsp - Szenario-Test fuer ssg-lastent-ohne-attribute (ssg_core)
;;
;; Stellt den realen Bug nach: ein Block MIT Attributen (z.B. eine per
;; Strg+C/V eingefuegte GR_-Kopie) ist das letzte Objekt der Zeichnung.
;; Ein Makro merkt sich die Startmarke, zeichnet neue Objekte, sammelt
;; "alles ab (entnext marke)" ein und wickelt es per _.-BLOCK zu einem Block.
;; Mit nacktem (entlast) als Marke landeten dabei die ATTRIBs des alten
;; Blocks im Auswahlsatz und wurden entfernt.
;;
;; Aufruf in BricsCAD:
;; (load (strcat (getenv "DXFMAKRO") "/tests/test_lastent.lsp"))
;; TEST_LASTENT
;;
;; Speichert tests/output/lastent_results.json (Konvention test_run_all).
;; ============================================================
(setq *tle-pass* 0 *tle-fail* 0 *tle-fails* '())
;; Ein Assert: erwartet-Wert gleich ist-Wert (equal). Loggt Pass/Fail.
(defun tle-eq (name erwartet ist)
(if (equal erwartet ist)
(progn (setq *tle-pass* (1+ *tle-pass*))
(princ (strcat "\n [PASS] " name)))
(progn (setq *tle-fail* (1+ *tle-fail*))
(setq *tle-fails* (cons name *tle-fails*))
(princ (strcat "\n [FAIL] " name
"\n erwartet: " (vl-princ-to-string erwartet)
"\n ist: " (vl-princ-to-string ist))))))
;; Blockdefinition mit zwei ATTDEFs anlegen (falls noch nicht vorhanden).
(defun tle-blockdef (name)
(if (not (tblsearch "BLOCK" name))
(progn
(entmake (list '(0 . "BLOCK") (cons 2 name) '(70 . 2) '(10 0.0 0.0 0.0)))
(entmake '((0 . "LINE") (8 . "0") (10 0.0 0.0 0.0) (11 100.0 0.0 0.0)))
(foreach tag '("ID" "ARTINR")
(entmake (list '(0 . "ATTDEF") '(8 . "0") '(10 0.0 0.0 0.0)
'(40 . 10.0) '(1 . "") (cons 2 tag) (cons 3 tag)
'(70 . 0))))
(entmake '((0 . "ENDBLK")))))
name)
;; INSERT mit Attributen per entmake (INSERT 66=1, ATTRIBs, SEQEND).
;; Rueckgabe: ename des INSERT.
(defun tle-insert-mit-attributen (name pt werte / ins)
(entmake (list '(0 . "INSERT") (cons 2 name) (cons 10 pt) '(66 . 1)))
(setq ins (entlast))
(foreach paar werte
(entmake (list '(0 . "ATTRIB") '(8 . "0") (cons 10 pt) '(40 . 10.0)
(cons 1 (cdr paar)) (cons 2 (car paar)) '(70 . 0))))
(entmake '((0 . "SEQEND")))
ins)
(defun lastent:tests ( / bname werte ins marke line ss e neu anz)
(setvar "CMDECHO" 0)
(setq bname (tle-blockdef "TLE_ATT_BLOCK"))
(setq werte (list (cons "ID" "4711") (cons "ARTINR" "A1,A2")))
(setq ins (tle-insert-mit-attributen bname '(0.0 0.0 0.0) werte))
;; --- Vorbedingung: Attribute lesbar, INSERT ist (entlast) ---
(tle-eq "vorher: attribute lesbar" werte (ssg-attrib-read ins))
(tle-eq "vorher: entlast = insert" ins (entlast))
;; --- Marke steht auf dem SEQEND des Attribut-Blocks ---
(setq marke (ssg-lastent-ohne-attribute))
(tle-eq "marke ist SEQEND" "SEQEND" (cdr (assoc 0 (entget marke))))
;; --- Neues Objekt zeichnen: entnext der Marke ist genau dieses Objekt ---
(entmake '((0 . "LINE") (8 . "0") (10 0.0 50.0 0.0) (11 100.0 50.0 0.0)))
(setq line (entlast))
(tle-eq "entnext marke = neues objekt" line (entnext marke))
;; --- Einsammeln + _.-BLOCK wie geruest-block-bauen ---
(setq ss (ssadd) anz 0 e (entnext marke))
(while e (ssadd e ss) (setq anz (1+ anz)) (setq e (entnext e)))
(tle-eq "genau 1 objekt eingesammelt" 1 anz)
(setq neu (strcat "TLE_NEU_" (ssg-timestamp)))
(command "_.-BLOCK" neu '(0.0 0.0 0.0) ss "")
;; --- Kern des Bugs: der alte Block behaelt seine Attribute ---
(tle-eq "nachher: attribute erhalten" werte (ssg-attrib-read ins))
(tle-eq "nachher: neuer block existiert" T (if (tblsearch "BLOCK" neu) T nil))
;; --- Leere Zeichnung wird hier nicht getestet (Testzeichnung ist nie leer);
;; ohne Attribute liefert der Helfer (entlast) unveraendert ---
(entmake '((0 . "LINE") (8 . "0") (10 0.0 90.0 0.0) (11 10.0 90.0 0.0)))
(tle-eq "ohne attribute: marke = entlast" (entlast) (ssg-lastent-ohne-attribute))
;; Aufraeumen
(entdel ins)
(entdel (entlast))
(princ))
(defun lastent:export-results (out-dir / f pfad)
(setq pfad (strcat out-dir "/lastent_results.json"))
(setq f (open pfad "w"))
(if f
(progn
(write-line "{" f)
(write-line (strcat " \"pass\": " (itoa *tle-pass*) ",") f)
(write-line (strcat " \"fail\": " (itoa *tle-fail*) ",") f)
(write-line (strcat " \"status\": \""
(if (= *tle-fail* 0) "OK" "FEHLER") "\"") f)
(write-line "}" f)
(close f)
(princ (strcat "\n Ergebnis: " pfad))))
(princ))
(defun c:TEST_LASTENT ( / )
(setq *tle-pass* 0 *tle-fail* 0 *tle-fails* '())
(princ "\n================================================================")
(princ "\n TEST_LASTENT - ssg-lastent-ohne-attribute (ssg_core.lsp)")
(princ "\n================================================================")
(lastent:tests)
(princ "\n\n----------------------------------------------------------------")
(princ (strcat "\n Ergebnis: " (itoa *tle-pass*) " PASS, "
(itoa *tle-fail*) " FAIL"))
(if (> *tle-fail* 0)
(progn
(princ "\n Fehlgeschlagen:")
(foreach n (reverse *tle-fails*) (princ (strcat "\n - " n)))))
(princ "\n================================================================")
(princ))
(princ "\nTEST_LASTENT geladen. Aufruf: TEST_LASTENT")
(princ)
+268 -31
View File
@@ -217,6 +217,21 @@
(setq *ssg-ils-dim* _lay-dim-alt
*ssg-layer-cfg* _lay-cfg-alt
*ssg-layer-cfg-versucht* _lay-vers-alt)
;; --- DCL-Vorlagen: Platzhalterzeilen //@NAME@ ersetzen ---
(tu-eq "ssg-dcl-zeilen-ersetzen/ersetzt"
'("a {" " x;" " y;" "}")
(ssg-dcl-zeilen-ersetzen '("a {" " //@L@" "}") '(("L" " x;" " y;"))))
(tu-eq "ssg-dcl-zeilen-ersetzen/leer-entfernt"
'("a {" "}")
(ssg-dcl-zeilen-ersetzen '("a {" "\t//@L@ " "}") '(("L"))))
(tu-eq "ssg-dcl-zeilen-ersetzen/mehrere"
'("1" "2")
(ssg-dcl-zeilen-ersetzen '("//@A@" "//@B@") '(("A" "1") ("B" "2"))))
;; unbekannter Marker und normaler Kommentar bleiben stehen
(tu-eq "ssg-dcl-zeilen-ersetzen/unbekannt"
'("//@X@" "// Kommentar")
(ssg-dcl-zeilen-ersetzen '("//@X@" "// Kommentar") '(("A" "1"))))
)
@@ -849,7 +864,7 @@
)
(defun tu-tests-geruest ( / _eintrag _gruppe _teile _konfig _rec _pfad _f
_zurueck _bgtest _saeulen)
_zurueck _bgtest _saeulen _vmq _alt _fs)
(princ "\n[GeruestInsert]")
;; GeruestInsert ist ein lazy Feature-Modul (nicht in der Core-Ladeliste).
;; Geladen wird es zentral in test_unit.scr auf .scr-Top-Level; hier nur
@@ -917,10 +932,10 @@
"{\"artikel\": \"X\", \"sivas_id\": 7, \"stueckzahl\": 2, \"anzahl_vorgabe\": 5}")
2))
;; Dummy, der zugleich Teile-DWG ist, faellt aus der Teileliste
(tu-eq "geruest-ohne-pfad/entfernt" '("b/2.dwg")
(geruest-ohne-pfad "A/1.DWG" '("a/1.dwg" "b/2.dwg")))
(tu-eq "geruest-ohne-pfad/nil" '("a/1.dwg")
(geruest-ohne-pfad nil '("a/1.dwg")))
(tu-eq "geruest-ohne-pfad/entfernt" '(("b/2.dwg" . 90.0))
(geruest-ohne-pfad "A/1.DWG" '(("a/1.dwg" . 0.0) ("b/2.dwg" . 90.0))))
(tu-eq "geruest-ohne-pfad/nil" '(("a/1.dwg" . 0.0))
(geruest-ohne-pfad nil '(("a/1.dwg" . 0.0))))
;; Dummy-Wahl: Variante gewinnt, fehlende Varianten-DWG -> Baugruppe
(setq _pfad (geruest-dummy-pfad "821006036.dwg"))
(if (and _pfad (findfile _pfad))
@@ -1015,7 +1030,148 @@
"{\"artikel\": \"Schraubenset\", \"anzahl\": 3},"
"{\"artikel\": \"unbekannt\", \"anzahl\": 1}]}")))
(tu-eq "geruest-konfig-position" '(nil 3)
(geruest-konfig-position _konfig "oben" _teile))
(geruest-konfig-position _konfig "oben" _teile nil))
;; --- anbaugruppen: befestigung / sonstiges ---
(setq _bgtest (ssg-json-parse
(strcat "{\"anbaugruppen\": {\"befestigung\": [{\"artikel\": \"K\"}],"
" \"sonstiges\": [{\"artikel\": \"H1\"}, {\"artikel\": \"H2\"}]}}")))
(tu-eq "geruest-anbau-liste/befestigung" 1
(length (geruest-anbau-liste _bgtest "befestigung")))
(tu-eq "geruest-anbau-liste/sonstiges" '("H1" "H2")
(mapcar 'geruest-eintrag-name (geruest-anbau-liste _bgtest "sonstiges")))
;; altes flaches Array gilt als befestigung
(setq _rec (ssg-json-parse "{\"anbaugruppen\": [{\"artikel\": \"A\"}, {\"artikel\": \"B\"}]}"))
(tu-eq "geruest-anbau-liste/flach" 2 (length (geruest-anbau-liste _rec "befestigung")))
(tu-nil "geruest-anbau-liste/flach-sonstiges" (geruest-anbau-liste _rec "sonstiges"))
(tu-nil "geruest-anbau-liste/leer"
(geruest-anbau-liste
(ssg-json-parse "{\"anbaugruppen\": {\"befestigung\": [], \"sonstiges\": []}}")
"sonstiges"))
(tu-nil "geruest-anbau-liste/fehlt"
(geruest-anbau-liste (ssg-json-parse "{\"id\": \"X\"}") "befestigung"))
(tu-eq "geruest-dlg-standard/teile" '(("0" . "1") ("0" . "1"))
(geruest-dlg-standard (geruest-anbau-liste _bgtest "sonstiges")))
(tu-eq "geruest-mengen-addieren" '(nil 3 2 5)
(geruest-mengen-addieren '(nil 1 nil 2) '(nil 2 2 3)))
;; Alte Konfiguration mit Haltearm unter "oben": landet in sonstiges
;; (Mengen addiert), oben meldet ihn nicht als unbekannt
(setq _konfig (ssg-json-parse
(strcat "{\"name\": \"Alt\","
" \"oben\": [{\"artikel\": \"K\", \"anzahl\": 2}, {\"artikel\": \"H2\", \"anzahl\": 1}],"
" \"unten\": [{\"artikel\": \"H2\", \"anzahl\": 2}],"
" \"sonstiges\": [{\"artikel\": \"H1\", \"anzahl\": 4}]}")))
(tu-eq "geruest-konfig-position/ausweich" '(2)
(geruest-konfig-position _konfig "oben"
(geruest-anbau-liste _bgtest "befestigung")
(geruest-anbau-liste _bgtest "sonstiges")))
;; --- Winkel Platte: Basis ungedreht, Anbauteile gedreht, kein Doppel ---
(tu-eq "geruest-teile-dwgs-rot/90" '(("b.dwg" . 0.0) ("a.dwg" . 90.0))
(geruest-teile-dwgs-rot '("b.dwg") '("b.dwg" "a.dwg") 90))
(tu-eq "geruest-teile-dwgs-rot/0" '(("a.dwg" . 0.0))
(geruest-teile-dwgs-rot nil '("a.dwg") 0))
(tu-eq "geruest-rest-liste" '(3 4) (geruest-rest-liste '(1 2 3 4) 2))
(tu-eq "geruest-winkel-aus-index/90" 90 (geruest-winkel-aus-index "1"))
(tu-eq "geruest-winkel-aus-index/leer" 0 (geruest-winkel-aus-index ""))
(tu-eq "geruest-winkel-index/90" "1" (geruest-winkel-index 90))
(tu-eq "geruest-winkel-index/fehlt" "0" (geruest-winkel-index nil))
;; --- Radiobuttons der Varianten: Key "v<i>" <-> Index ---
(tu-eq "geruest-radio-key" "v3" (geruest-radio-key 3))
(tu-eq "geruest-radio-index" 3 (geruest-radio-index "v3"))
(tu-eq "geruest-radio-index/leer" 0 (geruest-radio-index ""))
(tu-eq "geruest-radio-index/nil" 0 (geruest-radio-index nil))
(tu-eq "geruest-indizes" '(0 1 2) (geruest-indizes 3))
(tu-nil "geruest-indizes/0" (geruest-indizes 0))
(tu-eq "geruest-dcl-varianten-zeilen"
'(" : radio_button { key = \"v1\"; label = \"B 1''\"; }")
(geruest-dcl-varianten-zeilen
(list '(("name" . "A")) '(("name" . "B 1\""))) '(1)))
;; Variante mit Teilen: Artikelnummern in Klammern, jede nur einmal
(tu-eq "geruest-varianten-label/nummern" "R 2\" (111, 222)"
(geruest-varianten-label
(ssg-json-parse
(strcat "{\"name\": \"R 2\\\"\", \"teile\": ["
"{\"artikel\": \"A\", \"sivas_id\": 111},"
"{\"artikel\": \"B\", \"sivas_id\": 222, \"stueckzahl\": 2}]}"))))
(tu-eq "geruest-varianten-label/ohne" "A"
(geruest-varianten-label '(("name" . "A"))))
;; --- Gesperrte Varianten ("disabled": true) ---
(setq _rec (list '(("name" . "A") ("disabled" . T))
'(("name" . "B"))
'(("name" . "C") ("disabled" . T))))
(tu-true "geruest-variante-gesperrt-p/ja" (geruest-variante-gesperrt-p (nth 0 _rec)))
(tu-nil "geruest-variante-gesperrt-p/nein" (geruest-variante-gesperrt-p (nth 1 _rec)))
(tu-eq "geruest-erste-freie-variante" 1 (geruest-erste-freie-variante _rec))
(tu-eq "geruest-erste-freie-variante/alle-gesperrt" 0
(geruest-erste-freie-variante (list '(("disabled" . T)))))
(tu-eq "geruest-variante-waehlbar/frei" 1 (geruest-variante-waehlbar _rec 1))
(tu-eq "geruest-variante-waehlbar/gesperrt" 1 (geruest-variante-waehlbar _rec 2))
(tu-eq "geruest-variante-waehlbar/zu-gross" 1 (geruest-variante-waehlbar _rec 7))
;; Ersetzungen: ohne Teile entfallen die Bloecke (leerer Cluster)
(setq _rec (geruest-dcl-ersetzungen "Saeulen" nil nil
(list '(("name" . "A")))))
(tu-nil "geruest-dcl-ersetzungen/ohne-befestigung"
(cdr (assoc "BEFESTIGUNG" _rec)))
(tu-nil "geruest-dcl-ersetzungen/ohne-sonstiges"
(cdr (assoc "SONSTIGES" _rec)))
(tu-eq "geruest-dcl-ersetzungen/varianten" 1
(length (cdr (assoc "VARIANTEN" _rec))))
;; --- Geaenderte Konfiguration: Vergleich ohne Namen ---
(tu-true "geruest-record-gleich-p/name"
(geruest-record-gleich-p '(("name" . "A") ("laenge" . "100"))
'(("name" . "B") ("laenge" . "100"))))
(tu-nil "geruest-record-gleich-p/geaendert"
(geruest-record-gleich-p '(("name" . "A") ("laenge" . "100"))
'(("name" . "A") ("laenge" . "200"))))
;; Eigene Konfiguration gewinnt vor gleichnamiger Bibliothek; nur eine
;; eigene liefert eine Namensvorgabe (Ueberspeichern)
(setq _alt *geruest-konfig-liste*)
(setq *geruest-konfig-liste*
(list (cons nil '(("name" . "K"))) (cons T '(("name" . "K")))
(cons nil '(("name" . "L")))))
(tu-eq "geruest-konfig-index/eigene-zuerst" 2 (geruest-konfig-index "K"))
(tu-eq "geruest-konfig-index/bibliothek" 3 (geruest-konfig-index "L"))
(tu-eq "geruest-konfig-name-vorgabe/eigene" "K" (geruest-konfig-name-vorgabe 2))
(tu-nil "geruest-konfig-name-vorgabe/bibliothek" (geruest-konfig-name-vorgabe 1))
(tu-nil "geruest-konfig-name-vorgabe/keine" (geruest-konfig-name-vorgabe 0))
(setq *geruest-konfig-liste* _alt)
(tu-eq "geruest-konfig-sonstiges/umhaengen" '(4 3)
(geruest-konfig-sonstiges _konfig
(geruest-anbau-liste _bgtest "sonstiges")
(geruest-anbau-liste _bgtest "befestigung")))
;; Befestigung = Gesamtanzahl: neues Feld "befestigung", altes oben +
;; unten summiert; der Haltearm unter "oben" wird hier still uebergangen
(tu-eq "geruest-konfig-befestigung/alt" '(2)
(geruest-konfig-befestigung _konfig
(geruest-anbau-liste _bgtest "befestigung")
(geruest-anbau-liste _bgtest "sonstiges")))
(tu-eq "geruest-konfig-befestigung/oben-unten" '(5)
(geruest-konfig-befestigung
(ssg-json-parse
(strcat "{\"name\": \"A\", \"oben\": [{\"artikel\": \"K\", \"anzahl\": 2}],"
" \"unten\": [{\"artikel\": \"K\", \"anzahl\": 3}]}"))
(geruest-anbau-liste _bgtest "befestigung")
(geruest-anbau-liste _bgtest "sonstiges")))
(tu-eq "geruest-konfig-befestigung/neu" '(4)
(geruest-konfig-befestigung
(ssg-json-parse
"{\"name\": \"N\", \"befestigung\": [{\"artikel\": \"K\", \"anzahl\": 4}]}")
(geruest-anbau-liste _bgtest "befestigung")
(geruest-anbau-liste _bgtest "sonstiges")))
;; Artikelnummer(n) fuer die Anzeige: Einzelteil bzw. Gruppe eindeutig
(tu-eq "geruest-artnr-anzeige/einzelteil" "825002019"
(geruest-artnr-anzeige _eintrag))
(tu-eq "geruest-artnr-anzeige/gruppe" "111, 222"
(geruest-artnr-anzeige _gruppe))
;; Eine Befestigungsspalte (Prefix b) mit Art.-Nr.-Zeile je Teil
(setq _rec (geruest-dcl-ersetzungen "Saeulen" (list _eintrag) nil
(list '(("name" . "A")))))
(tu-true "geruest-dcl-ersetzungen/befestigung-key"
(vl-some '(lambda (z) (vl-string-search "key = \"bm0\"" z))
(cdr (assoc "BEFESTIGUNG" _rec))))
(tu-true "geruest-dcl-ersetzungen/artnr-zeile"
(vl-some '(lambda (z) (vl-string-search "Art.-Nr. 825002019" z))
(cdr (assoc "BEFESTIGUNG" _rec))))
;; --- Variante gewinnt vor Baugruppe (TEILEART/dummy) ---
;; Die zusammengefuehrte Saeulen-Baugruppe unterscheidet 1 Zoll und
@@ -1032,9 +1188,7 @@
(geruest-variante-wert nil _bgtest "dummy"))
;; --- Zusammengefuehrte Saeulen-Baugruppe ---
(setq _saeulen nil)
(foreach _e (geruest-baugruppen-liste)
(if (equal (car _e) "Saeulen") (setq _saeulen (cdr _e))))
(setq _saeulen (geruest-baugruppe "Saeulen" (geruest-daten)))
(tu-eq "geruest/saeulen-vorhanden" T (if _saeulen T nil))
(if _saeulen
(progn
@@ -1046,10 +1200,27 @@
(tu-geruest-artikel-unvollstaendig
(ssg-json-get _saeulen "geometrie_varianten")))
(tu-eq "geruest/saeulen-anbauteile" T
(if (ssg-json-get _saeulen "anbaugruppen") T nil))
(if (geruest-anbau-liste _saeulen "befestigung") T nil))
(tu-eq "geruest/saeulen-anbauteile-artikel" 0
(tu-geruest-artikel-unvollstaendig
(ssg-json-get _saeulen "anbaugruppen")))
(geruest-anbau-liste _saeulen "befestigung")))
;; Haltearme passen weder oben noch unten -> eigene Sonstiges-Zeile
(tu-eq "geruest/saeulen-sonstiges" 4
(length (geruest-anbau-liste _saeulen "sonstiges")))
(tu-eq "geruest/saeulen-sonstiges-artikel" 0
(tu-geruest-artikel-unvollstaendig
(geruest-anbau-liste _saeulen "sonstiges")))
(tu-nil "geruest/saeulen-haltearm-nicht-befestigung"
(vl-some '(lambda (a)
(wcmatch (geruest-eintrag-name a) "HALTEARM*"))
(geruest-anbau-liste _saeulen "befestigung")))
;; Rohrabhaenger/Wandbefestigung haben keine Sonstiges-Teile
(foreach _e '("Rohrabhaenger" "Wandbefestigung")
(setq _rec (geruest-baugruppe _e (geruest-daten)))
(tu-true (strcat "geruest/" _e "-befestigung")
(geruest-anbau-liste _rec "befestigung"))
(tu-nil (strcat "geruest/" _e "-ohne-sonstiges")
(geruest-anbau-liste _rec "sonstiges")))
(tu-eq "geruest/saeulen-titel" "Saeulen" (geruest-titel _saeulen))
;; Variante R 1" traegt die eigene TEILEART - per Name gesucht,
;; die Reihenfolge in geruest.json ist nicht fest.
@@ -1136,20 +1307,33 @@
(tu-eqf "geruest-traeger-streben/6000-letzte" 5750.0 (last _rec))
;; Konfiguration: fehlende Felder -> Standard, ohne querstrebe -> nil
;; Ergebnis (dummy laenge querlinie abstand rand-min rand-max)
;; Streben nur bei Varianten mit "mit_querstrebe": true
(setq _vmq (ssg-json-parse "{\"name\": \"A\", \"mit_querstrebe\": true}"))
(tu-eqf "geruest-traeger-strebe-cfg/standard" '(nil 30.0 25.0 500.0 100.0 250.0)
(geruest-traeger-strebe-cfg nil
(geruest-traeger-strebe-cfg _vmq
(ssg-json-parse "{\"querstrebe\": {\"rand_min\": 100}}")))
(tu-eqf "geruest-traeger-strebe-cfg/dummy" '("s.dwg" 30.0 25.0 500.0 100.0 250.0)
(geruest-traeger-strebe-cfg nil
(geruest-traeger-strebe-cfg _vmq
(ssg-json-parse "{\"querstrebe\": {\"dummy\": \"s.dwg\"}}")))
(tu-eqf "geruest-traeger-strebe-cfg/wert" '(nil 30.0 25.0 400.0 100.0 250.0)
(geruest-traeger-strebe-cfg _vmq
(ssg-json-parse "{\"querstrebe\": {\"abstand\": 400}}")))
(tu-eqf "geruest-traeger-strebe-cfg/variante" '(nil 30.0 25.0 300.0 100.0 250.0)
(geruest-traeger-strebe-cfg
(ssg-json-parse
"{\"mit_querstrebe\": true, \"querstrebe\": {\"abstand\": 300}}")
(ssg-json-parse "{\"querstrebe\": {\"abstand\": 400}}")))
(tu-nil "geruest-traeger-strebe-cfg/ohne-flag"
(geruest-traeger-strebe-cfg (ssg-json-parse "{\"name\": \"Rohr 1\"}")
(ssg-json-parse "{\"querstrebe\": {\"abstand\": 400}}")))
(tu-nil "geruest-traeger-strebe-cfg/ohne-variante"
(geruest-traeger-strebe-cfg nil
(ssg-json-parse "{\"querstrebe\": {\"abstand\": 400}}")))
;; Querlinien an Anfang und Ende der 30 mm langen Strebe
(tu-eqf "geruest-traeger-strebe-enden" '(235.0 265.0)
(geruest-traeger-strebe-enden 250.0 30.0))
(tu-nil "geruest-traeger-strebe-cfg/ohne"
(geruest-traeger-strebe-cfg nil (ssg-json-parse "{\"id\": \"X\"}")))
(geruest-traeger-strebe-cfg _vmq (ssg-json-parse "{\"id\": \"X\"}")))
;; Muffe nur bei "mit_muffe": true; Variante darf eigene Muffe mitbringen
(setq _bgtest (ssg-json-parse "{\"muffe\": {\"dummy\": \"m.dwg\"}}"))
(tu-eq "geruest-traeger-muffe-dummy/flag" "m.dwg"
@@ -1271,13 +1455,8 @@
_bgtest "LAENGENABZUGENDE" "aktiv"))
;; --- Rohr-Geruest-Traeger in der echten Datendatei ---
;; Eigene Datei data/json/geruest_traeger.json: im Bereichs-Dropdown von
;; GERUEST darf er nicht auftauchen, ueber geruest-traeger-daten muss er
;; auffindbar sein.
(setq _rec nil)
(foreach _e (geruest-baugruppen-liste)
(if (equal (car _e) "Traeger") (setq _rec T)))
(tu-nil "geruest/traeger-nicht-im-dropdown" _rec)
;; Eigene Datei data/json/geruest_traeger.json: in geruest.json darf er
;; nicht stehen, ueber geruest-traeger-daten muss er auffindbar sein.
(tu-true "geruest/traeger-datei"
(findfile (geruest-json-pfad "geruest_traeger.json")))
(setq _traeger (geruest-baugruppe "Traeger" (geruest-traeger-daten)))
@@ -1297,12 +1476,26 @@
(geruest-attr-feld nil _traeger "LAENGE" "max"))
(tu-eqf "geruest/traeger-querstrebe"
'("dummy_Strebe_30x4.dwg" 30.0 25.0 500.0 100.0 250.0)
(geruest-traeger-strebe-cfg nil _traeger))
(geruest-traeger-strebe-cfg _vmq _traeger))
;; Querstreben-Flag je Variante: alle ausser den beiden Rohr-1"-Varianten
(setq _rec 0)
(foreach _v (ssg-json-get _traeger "geometrie_varianten")
(if (geruest-traeger-strebe-cfg _v _traeger) (setq _rec (1+ _rec))))
(tu-eq "geruest/traeger-querstreben-varianten" 6 _rec)
(tu-nil "geruest/traeger-rohr-ohne-querstrebe"
(geruest-traeger-strebe-cfg
(car (ssg-json-get _traeger "geometrie_varianten")) _traeger))
;; Muffen-Flag ausdruecklich je Variante: genau die "mit (2) Muffe(n)"
(setq _rec 0)
(foreach _v (ssg-json-get _traeger "geometrie_varianten")
(if (geruest-traeger-muffe-dummy _v _traeger) (setq _rec (1+ _rec))))
(tu-eq "geruest/traeger-muffen-varianten" 4 _rec)
;; Dialog: ein Radiobutton je Variante, auch fuer die mit Muffe
(tu-eq "geruest/traeger-radiobuttons" 8
(length (geruest-dcl-varianten-zeilen
(ssg-json-get _traeger "geometrie_varianten")
(geruest-indizes (length (ssg-json-get _traeger
"geometrie_varianten"))))))
;; Jede Variante definiert Beschriftungstext UND -layer selbst - der
;; Layer ist je Traegervariante verschieden, eine Vorgabe der
;; Baugruppe gibt es dafuer bewusst nicht.
@@ -1327,6 +1520,44 @@
)
)
;; --- Omniflo-Fahrstrecke (data/json/omniflo_fahrstrecke.json) ---
;; Gleiche Linien-Logik wie der Traeger: jede Variante ein Radiobutton,
;; nur ST/STL gibt es mit Muffen, nur ST/STL sind farbig.
(setq _fs (geruest-baugruppe "Fahrstrecke" (geruest-fahrstrecke-daten)))
(tu-true "fahrstrecke/vorhanden" _fs)
(if _fs
(progn
(setq _rec (ssg-json-get _fs "geometrie_varianten"))
(tu-eq "fahrstrecke/titel" "Fahrstrecke" (geruest-titel _fs))
(tu-eq "fahrstrecke/varianten"
'("AP 110" "APG 110" "AP 60" "ST" "ST mit Muffen" "STL" "STL mit Muffen")
(mapcar 'geruest-eintrag-name _rec))
(tu-eq "fahrstrecke/mit-muffe" '("ST mit Muffen" "STL mit Muffen")
(mapcar 'geruest-eintrag-name
(vl-remove-if-not '(lambda (v) (ssg-json-get v "mit_muffe")) _rec)))
;; AP 110 ohne "farbig" -> leerer TEILEFARBE-Eintrag, ST farbig
(tu-eq "fahrstrecke/ap110-nicht-farbig" '("")
(geruest-farbliste (geruest-teile-paare (nth 0 _rec) nil) "RAL7035"))
(tu-eq "fahrstrecke/st-farbig" '("RAL7035")
(geruest-farbliste
(geruest-teile-paare
(nth (geruest-name-index "ST" _rec) _rec) nil)
"RAL7035"))
;; Keine Querstreben; Muffen-Dummy wie beim Traeger nur bei den
;; Varianten mit "mit_muffe": true
(tu-nil "fahrstrecke/ohne-querstrebe"
(geruest-traeger-strebe-cfg (nth (geruest-name-index "ST" _rec) _rec) _fs))
(tu-eq "fahrstrecke/muffe-dummy" "dummy_Muffe_1zoll.dwg"
(geruest-traeger-muffe-dummy
(nth (geruest-name-index "ST mit Muffen" _rec) _rec) _fs))
(tu-nil "fahrstrecke/ohne-muffe-dummy"
(geruest-traeger-muffe-dummy
(nth (geruest-name-index "ST" _rec) _rec) _fs))
)
)
(tu-true "fahrstrecke/dcl-vorlage"
(findfile (geruest-dcl-vorlage-pfad "fahrstrecke.dcl")))
;; --- Rundlauf Schreiber -> Parser ---
;; Prueft den Konfigurations-Schreiber ohne BricsCAD-Dialog. Bewusst in
;; eine temporaere Datei: ein Test darf cfg/geruest_user.json nicht
@@ -1337,9 +1568,10 @@
(cons "variante" "ROHR R 1\" 33,7 x 2,6")
(cons "laenge" "2000")
(cons "teilefarbe" "RAL 7035 Lichtgrau")
(cons "oben" (list (list (cons "artikel" "ROHRKLAMMER 1\" KPL. VERTIKAL")
(cons "anzahl" 2))))
(cons "unten" nil)))
(cons "befestigung" (list (list (cons "artikel" "ROHRKLAMMER 1\" KPL. VERTIKAL")
(cons "anzahl" 2))))
(cons "sonstiges" nil)
(cons "winkel_platte" 90)))
(setq _pfad (vl-filename-mktemp "grtest" nil ".json"))
(setq _f (open _pfad "w"))
(write-line "{ \"Test\": [" _f)
@@ -1356,12 +1588,16 @@
(ssg-json-get _zurueck "laenge"))
(tu-eq "geruest-schreiber/farbe" "RAL 7035 Lichtgrau"
(ssg-json-get _zurueck "teilefarbe"))
(tu-eq "geruest-schreiber/oben-artikel" "ROHRKLAMMER 1\" KPL. VERTIKAL"
(ssg-json-get (car (ssg-json-get _zurueck "oben")) "artikel"))
(tu-eq "geruest-schreiber/oben-anzahl" 2
(ssg-json-get (car (ssg-json-get _zurueck "oben")) "anzahl"))
(tu-eq "geruest-schreiber/unten-leer" nil
(ssg-json-get _zurueck "unten"))
(tu-eq "geruest-schreiber/befestigung-artikel" "ROHRKLAMMER 1\" KPL. VERTIKAL"
(ssg-json-get (car (ssg-json-get _zurueck "befestigung")) "artikel"))
(tu-eq "geruest-schreiber/befestigung-anzahl" 2
(ssg-json-get (car (ssg-json-get _zurueck "befestigung")) "anzahl"))
(tu-eq "geruest-schreiber/sonstiges-leer" nil
(ssg-json-get _zurueck "sonstiges"))
(tu-eq "geruest-schreiber/winkel" 90
(ssg-json-get _zurueck "winkel_platte"))
;; altes Format wird nicht mehr geschrieben
(tu-nil "geruest-schreiber/ohne-oben" (assoc "oben" _zurueck))
)
)
)
@@ -1713,6 +1949,7 @@
(tu-eq "ssg-sl-klasse/vf" "ils_strecke" (car (ssg-sl-klasse "VF_2")))
(tu-eq "ssg-sl-klasse/gf" "ils_strecke" (car (ssg-sl-klasse "GF_1")))
(tu-eq "ssg-sl-klasse/geruest" "geruest" (car (ssg-sl-klasse "GR_Saeulen_20260101120000")))
(tu-eq "ssg-sl-klasse/fahrstrecke" "fahrstrecke" (car (ssg-sl-klasse "GR_Fahrstrecke_20260101120000")))
(tu-eq "ssg-sl-klasse/ap60" "ap60" (car (ssg-sl-klasse "AP60_20260101120000")))
(tu-eq "ssg-sl-klasse/apg110" "apg110" (car (ssg-sl-klasse "APG110_20260101120000")))
(tu-eq "ssg-sl-klasse/ap110" "ap110" (car (ssg-sl-klasse "AP110_20260101120000")))