[ADD] Modul zum Erstellen von Hängewarenlagerbereichen und Stockwerken mit Attributsdefinition für den Export in ein Anforderungsdokument für Ausschreibungen

This commit is contained in:
2026-10-30 11:11:20 +01:00
parent 4fdeaf831f
commit 473aeec5bf
26 changed files with 5921 additions and 175 deletions
+1
View File
@@ -5,6 +5,7 @@
{ "name": "vfl_schema", "module": "VarioFoerderer" },
{ "name": "vf_spec", "module": "VarioFoerderer" },
{ "name": "lastent", "module": "GeruestInsert" },
{ "name": "lager", "save": "dxf", "module": "LagerInsert" },
{ "name": "omniflo", "save": "dxf", "module": "OmniModulInsert" },
{ "name": "omniflo_strecke", "save": "dxf", "module": "OmniModulInsert" },
{ "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" },
+550
View File
@@ -0,0 +1,550 @@
;; ============================================================
;; test_lager.lsp - Szenario-Test Lagerplanung (LagerInsert.lsp)
;;
;; Baut in der aktuellen (leeren) Zeichnung ohne Dialoge auf:
;; - zwei Geschosse auf derselben 2D-Kontur (EG Hallenboden, OG 3100 mm)
;; - einen Lagerbereich 10000 x 6000 im OG mit Auto-Fuellung
;; und prueft Bloecke, Attribute, Layer, Neuberechnung (keine Verdopplung),
;; den Neuaufbau einer geaenderten Reihe und die Rohdaten von LAGER_EXPORT.
;; Zum Schluss Regression der goh_rack-Erkennung (lg-racks-sammeln) gegen
;; tests/testdata/goh_storage_test.dwg (Referenzwerte aus
;; goh_storage_test_regal_report.txt: 38946.39 mm / 77892.78 mm).
;;
;; Aufruf in BricsCAD:
;; (load (strcat (getenv "DXFMAKRO") "/tests/test_lager.lsp"))
;; TEST_LAGER
;;
;; Speichert tests/output/lager_results.json (Konvention test_run_all)
;; und tests/output/lager_tests_lager_raw.json (Eingabe fuer
;; lib/lager_yaml_export.py).
;; ============================================================
(setq *tlg-pass* 0 *tlg-fail* 0 *tlg-fails* '())
(defun tlg-eq (name erwartet ist)
(if (equal erwartet ist 1e-3)
(progn (setq *tlg-pass* (1+ *tlg-pass*))
(princ (strcat "\n [PASS] " name)))
(progn (setq *tlg-fail* (1+ *tlg-fail*))
(setq *tlg-fails* (cons name *tlg-fails*))
(princ (strcat "\n [FAIL] " name
"\n erwartet: " (vl-princ-to-string erwartet)
"\n ist: " (vl-princ-to-string ist))))))
(defun tlg-rechteck (x0 y0 x1 y1 / )
(entmakex (list '(0 . "LWPOLYLINE") '(100 . "AcDbEntity") '(8 . "0")
'(100 . "AcDbPolyline") '(90 . 4) '(70 . 1)
(list 10 x0 y0) (list 10 x1 y0) (list 10 x1 y1) (list 10 x0 y1))))
(defun tlg-attr (ent tag) (cdr (assoc tag (ssg-attrib-read ent))))
(defun tlg-layer-gefroren-p (name / e)
(setq e (tblsearch "LAYER" name))
(and e (= 1 (logand 1 (cdr (assoc 70 e))))))
(defun tlg-summe (texte feld / s)
(setq s 0.0)
(foreach t1 texte (setq s (+ s (cond ((ssg-json-get (ssg-json-parse t1) feld)) (0.0)))))
s)
;; Bloecke einer Liste, deren Name auf muster passt.
(defun tlg-nur (ents muster)
(vl-remove-if-not '(lambda (e) (wcmatch (cdr (assoc 2 (entget e))) muster)) ents))
(defun tlg-anzahl-art (ents art)
(length (vl-remove-if-not '(lambda (e) (= (tlg-attr e "ART") art)) ents)))
(defun tlg-summe-attr (ents tag / s)
(setq s 0.0)
(foreach e ents (setq s (+ s (cond ((lg-zahl (tlg-attr e tag))) (0.0)))))
s)
;; Schraffurmuster der (ersten) HATCH einer Blockdefinition oder nil.
(defun tlg-schraffur (bname / h)
(if (tblsearch "BLOCK" bname)
(if (setq h (vl-some '(lambda (ed) (if (= (cdr (assoc 0 ed)) "HATCH") ed))
(lg-blockdef-objekte bname)))
(cdr (assoc 2 h)))))
;; Bloecke nach Muster, die zu Geschoss gs (Zahl) gehoeren.
(defun tlg-gs (muster gs)
(vl-remove-if-not '(lambda (e) (= (tlg-attr e "GESCHOSS") (itoa gs))) (lg-alle-bloecke muster)))
;; Kinderzahl je Bereich eines Geschosses, sortiert.
(defun tlg-kinderzahlen (gs)
(vl-sort (mapcar '(lambda (b) (length (lg-bereich-kinder b))) (tlg-gs "LG_BEREICH" gs)) '<))
(defun tlg-anzahl-attr (ents tag wert)
(length (vl-remove-if-not '(lambda (e) (= (tlg-attr e tag) wert)) ents)))
;; ATTDEF-Tags einer Blockdefinition als Alist (tag . T).
(defun tlg-attdef-tags (bname)
(mapcar '(lambda (ed) (cons (cdr (assoc 2 ed)) T))
(vl-remove-if-not '(lambda (ed) (= (cdr (assoc 0 ed)) "ATTDEF")) (lg-blockdef-objekte bname))))
(defun lager:tests ( / k0 k1 g0 g1 kb bereich reihen gaenge r0 neu raw daten pfad f
racks laengen kap tr kb2 b2 kinder mg mg2 hg alt eb
r1 gg1 n-alt g2 r2 gg2 ids b2k n-kinder bb gr rg grp rk kand zl
b3 rh3 mg3 ge3 ag3 n3 mr3 alt3)
(setvar "CMDECHO" 0)
(ssg-gui-aus)
;; --- Geschosse auf derselben Kontur (2D-Grundrisse uebereinander) ---
(setq k0 (tlg-rechteck 0.0 0.0 10000.0 6000.0))
(setq g0 (lg-geschoss-anlegen k0
(append (list (cons "HOEHE_BELAG_mm" "0") (cons "BELAG_ART" "hallenboden")
(cons "BEZEICHNUNG" "ground floor") (cons "KONSTR_HOEHE_mm" "0"))
(lg-geschoss-vorgaben 0))))
(setq g1 (lg-geschoss-anlegen k0
(append (list (cons "HOEHE_BELAG_mm" "3100") (cons "KONSTR_HOEHE_mm" "150"))
(lg-geschoss-vorgaben 1))))
(tlg-eq "geschoss 0 angelegt" "INSERT" (if g0 (cdr (assoc 0 (entget g0)))))
(tlg-eq "geschoss 1 angelegt" "INSERT" (if g1 (cdr (assoc 0 (entget g1)))))
(tlg-eq "geschosse gesamt" 2 (length (lg-geschosse)))
(tlg-eq "geschoss 1 layer" "LG_Geschoss_G1" (cdr (assoc 8 (entget g1))))
(tlg-eq "geschoss 1 hoehe" "3100" (tlg-attr g1 "HOEHE_BELAG_mm"))
;; die vergebene Kontur wurde fuer Geschoss 1 kopiert
(tlg-eq "kontur kopiert" T (not (equal (lg-xdata-wert g0 "kontur") (lg-xdata-wert g1 "kontur"))))
(tlg-eq "kontur besitzer" (lg-handle g1)
(lg-xdata-wert (lg-ent-aus-handle (lg-xdata-wert g1 "kontur")) "besitzer"))
(tlg-eq "doppelte nummer abgewiesen" nil
(lg-geschoss-anlegen (tlg-rechteck 0.0 0.0 10.0 10.0) (lg-geschoss-vorgaben 1)))
;; --- Aktives Geschoss friert die anderen ---
(lg-geschoss-aktivieren 1)
(tlg-eq "aktiv 1" 1 (lg-geschoss-aktiv))
(tlg-eq "G0 gefroren" T (tlg-layer-gefroren-p "LG_Geschoss_G0"))
(tlg-eq "G1 nicht gefroren" nil (tlg-layer-gefroren-p "LG_Geschoss_G1"))
(tlg-eq "ermitteln = aktiv" 1 (lg-geschoss-ermitteln '(5.0 5.0 0.0)))
(tlg-eq "gefrorene geschosse gefunden" 2 (length (lg-geschosse)))
;; --- Bereich mit Auto-Fuellung ---
(setq kb (tlg-rechteck 0.0 0.0 10000.0 6000.0))
(setq bereich (lg-bereich-anlegen kb (cons (cons "GESCHOSS" "1")
(lg-bereich-vorgaben (ssg-lwpoly-punkte kb)))))
(tlg-eq "bereich angelegt" "INSERT" (if bereich (cdr (assoc 0 (entget bereich)))))
(setq reihen (lg-alle-bloecke "LG_REIHE_*") gaenge (lg-alle-bloecke "LG_GANG_*"))
(tlg-eq "reihen" 4 (length reihen))
(tlg-eq "gaenge" 2 (length gaenge))
(tlg-eq "kinder des bereichs" 6 (length (lg-bereich-kinder bereich)))
(setq r0 (car reihen))
(tlg-eq "reihe laenge" "10000" (tlg-attr r0 "LAENGE"))
(tlg-eq "reihe tiefe" "700" (tlg-attr r0 "TIEFE_mm"))
(tlg-eq "reihe geschoss" "1" (tlg-attr r0 "GESCHOSS"))
(tlg-eq "reihe quelle" "auto" (tlg-attr r0 "QUELLE"))
(tlg-eq "reihe ebenen" "2300/1200;1100/1100" (tlg-attr r0 "HAENGEEBENEN"))
(tlg-eq "reihe layer" "LG_Reihe_G1" (cdr (assoc 8 (entget r0))))
(tlg-eq "reihe blockdef" "LG_REIHE_10000_700" (cdr (assoc 2 (entget r0))))
(tlg-eq "bereich rest" "1200" (tlg-attr bereich "RESTBREITE_mm"))
(tlg-eq "gang belag" "spanplatte" (tlg-attr (car gaenge) "BELAG"))
;; --- Neuberechnung ersetzt statt verdoppelt ---
(lg-bereich-fuellen bereich)
(tlg-eq "neu gefuellt: reihen" 4 (length (lg-alle-bloecke "LG_REIHE_*")))
(tlg-eq "neu gefuellt: gaenge" 2 (length (lg-alle-bloecke "LG_GANG_*")))
;; --- Bereichsparameter aendern: Gang 1200 -> Modul 2600, 2 Module ---
(lg-attribute-setzen bereich (list (cons "GANGBREITE_mm" "1200")
(cons "QUERGANG_ABSTAND_mm" "4000")))
(lg-bereich-fuellen bereich)
(tlg-eq "quergaenge: reihenstuecke" 8 (length (lg-alle-bloecke "LG_REIHE_*")))
(tlg-eq "bereich rest neu" "800" (tlg-attr bereich "RESTBREITE_mm"))
;; --- Einzelne Reihe aendern (Neuaufbau, Kennzeichnung) ---
(setq r0 (car (lg-alle-bloecke "LG_REIHE_*")))
(setq neu (lg-linie-neu-aufbauen r0 (list (cons "LAENGE" "2000") (cons "TIEFE_mm" "700")
(cons "HAENGEEBENEN" "2300/1200") (cons "SCHIENEN" "1")
(cons "GESCHOSS" "1") (cons "BEREICH" "1")
(cons "QUELLE" "auto_geaendert"))))
(tlg-eq "reihe neu aufgebaut" "LG_REIHE_2000_700" (if neu (cdr (assoc 2 (entget neu)))))
(tlg-eq "alte reihe weg" nil (entget r0))
(tlg-eq "reihenzahl gleich" 8 (length (lg-alle-bloecke "LG_REIHE_*")))
(tlg-eq "geaenderte reihe bleibt kind" T (if (member neu (lg-bereich-kinder bereich)) T nil))
;; --- Rohdaten fuer LAGER_EXPORT ---
(setq raw (lg-export-json))
(setq daten (ssg-json-parse raw))
(tlg-eq "export geschosse" 2 (length (ssg-json-get daten "geschosse")))
(tlg-eq "export bereiche" 1 (length (ssg-json-get daten "bereiche")))
(tlg-eq "export reihen" 8 (length (ssg-json-get daten "reihen")))
(tlg-eq "export land" "DE" (ssg-json-get daten "land"))
(tlg-eq "export konturflaeche" 6.0e7
(ssg-json-get (car (vl-remove-if-not '(lambda (g) (= (ssg-json-get g "nr") 1))
(ssg-json-get daten "geschosse")))
"kontur_flaeche_mm2"))
(tlg-eq "export haengeebenen"
'((("schienenhoehe_mm" . 2300) ("warenlaenge_mm" . 1200))
(("schienenhoehe_mm" . 1100) ("warenlaenge_mm" . 1100)))
(ssg-json-get (vl-some '(lambda (r) (if (= (ssg-json-get r "laenge_mm") 4000) r))
(ssg-json-get daten "reihen"))
"haengeebenen"))
;; --- Randgaenge Nord + Sued, Schraffur je Belag ---
(setq kb2 (tlg-rechteck 20000.0 0.0 30000.0 6000.0))
(setq b2 (lg-bereich-anlegen kb2 (append (list (cons "GESCHOSS" "1")
(cons "RANDGANG_N_mm" "1000")
(cons "RANDGANG_S_mm" "1000"))
(lg-bereich-vorgaben (ssg-lwpoly-punkte kb2)))))
(tlg-eq "randgang: attribut" "1000" (if b2 (tlg-attr b2 "RANDGANG_N_mm")))
(setq kinder (lg-bereich-kinder b2))
(tlg-eq "randgang: anzahl" 2 (tlg-anzahl-art kinder "randgang"))
(tlg-eq "randgang: reihen" 2 (length (tlg-nur kinder "LG_REIHE_*")))
;; Stapel 1000..5000 -> ein Modul, Rest 1600
(tlg-eq "randgang: rest" "1600" (tlg-attr b2 "RESTBREITE_mm"))
(tlg-eq "randgang: quelle auto" "auto" (tlg-attr (car (tlg-nur kinder "LG_GANG_*")) "QUELLE"))
(tlg-eq "schraffur spanplatte" "ANSI31" (tlg-schraffur "LG_GANG_10000_1000_SPANPLATTE"))
;; --- Manueller Gang quer durch den Bereich spart die Reihen aus ---
(setq mg (lg-gang-einfuegen '(25000.0 1000.0 0.0) '(25000.0 5000.0 0.0)
(list (cons "GESCHOSS" "1") (cons "BREITE_mm" "1000") (cons "ART" "gang")
(cons "BELAG" "gitterrost"))))
(tlg-eq "manuell: eingefuegt" "LG_GANG_4000_1000_GITTERROST" (if mg (cdr (assoc 2 (entget mg)))))
(tlg-eq "manuell: quelle" "manuell" (tlg-attr mg "QUELLE"))
(tlg-eq "manuell: bereich" (tlg-attr b2 "NR") (tlg-attr mg "BEREICH"))
(tlg-eq "schraffur gitterrost" "GRID" (tlg-schraffur "LG_GANG_4000_1000_GITTERROST"))
(setq kinder (lg-bereich-kinder b2))
(tlg-eq "manuell: reihen unterbrochen" 4 (length (tlg-nur kinder "LG_REIHE_*")))
(tlg-eq "manuell: reihenlaenge" 18000.0 (tlg-summe-attr (tlg-nur kinder "LG_REIHE_*") "LAENGE"))
;; Neuaufbau: manueller Gang bleibt, Reihen bleiben ausgespart
(lg-bereich-fuellen b2)
(setq kinder (lg-bereich-kinder b2))
(tlg-eq "manuell: bleibt beim neuaufbau" T (if (entget mg) T nil))
(tlg-eq "manuell: weiter ausgespart" 4 (length (tlg-nur kinder "LG_REIHE_*")))
;; Belag aendern -> neuer Blockname/neue Schraffur
(setq mg2 (lg-linie-neu-aufbauen mg (lg-wert-setzen (ssg-attrib-read mg) "BELAG" "spanplatte")))
(tlg-eq "manuell: belag geaendert" "LG_GANG_4000_1000_SPANPLATTE" (if mg2 (cdr (assoc 2 (entget mg2)))))
(tlg-eq "manuell: bleibt kind" T (if (member mg2 (lg-bereich-kinder b2)) T nil))
;; Hallenboden: keine Schraffur; ausserhalb jedes Bereichs: nicht verknuepft
(setq hg (lg-gang-einfuegen '(40000.0 0.0 0.0) '(42000.0 0.0 0.0)
(list (cons "GESCHOSS" "1") (cons "BREITE_mm" "1500") (cons "ART" "gang")
(cons "BELAG" "hallenboden"))))
(tlg-eq "hallenboden: ohne schraffur" nil (tlg-schraffur "LG_GANG_2000_1500_HALLENBODEN"))
(tlg-eq "hallenboden: kein bereich" "" (tlg-attr hg "BEREICH"))
;; --- Altbestand: Blockdefinition um ein neues ATTDEF ergaenzen ---
(lg-blockdef "TLG_ALT" (list (lg-geom-linie '(0.0 0.0 0.0) '(100.0 0.0 0.0))
(lg-geom-attdef "A" "" '(0.0 0.0 0.0) 50.0 nil)))
(setq alt (lg-block-einfuegen "TLG_ALT" '(0.0 -5000.0 0.0) 0.0 "0" '(("A" . "1"))))
(lg-blockdef "TLG_ALT" (list (lg-geom-linie '(0.0 0.0 0.0) '(100.0 0.0 0.0))
(lg-geom-attdef "A" "" '(0.0 0.0 0.0) 50.0 nil)
(lg-geom-attdef "B" "" '(0.0 -100.0 0.0) 50.0 nil)))
(tlg-eq "altbestand: neues ATTDEF" T (if (assoc "B" (tlg-attdef-tags "TLG_ALT")) T nil))
(tlg-eq "altbestand: referenz hat neuen tag" T (if (assoc "B" (ssg-attrib-read alt)) T nil))
(tlg-eq "altbestand: alter wert bleibt" "1" (tlg-attr alt "A"))
;; --- Export mit Randgaengen und manuellen Gaengen ---
(setq raw (lg-export-json) daten (ssg-json-parse raw))
(setq eb (vl-some '(lambda (b) (if (= (ssg-json-get b "nr") (atoi (tlg-attr b2 "NR"))) b))
(ssg-json-get daten "bereiche")))
(tlg-eq "export randgang_mm" '(("n" . 1000) ("o" . 0) ("s" . 1000) ("w" . 0))
(ssg-json-get eb "randgang_mm"))
(tlg-eq "export randgaenge" 2
(length (vl-remove-if-not '(lambda (g) (= (ssg-json-get g "art") "randgang"))
(ssg-json-get daten "gaenge"))))
(tlg-eq "export manuelle gaenge" 2
(length (vl-remove-if-not '(lambda (g) (= (ssg-json-get g "quelle") "manuell"))
(ssg-json-get daten "gaenge"))))
(setq pfad (strcat (getenv "DXFMAKRO") "/tests/output/lager_tests_lager_raw.json"))
(if (setq f (open pfad "w")) (progn (princ raw f) (close f)))
;; --- Geschoss kopieren: G1 (3100, Spanplatte) -> G2, G3 mit Gitterrost ---
(setq r1 (tlg-gs "LG_REIHE_*" 1) gg1 (tlg-gs "LG_GANG_*" 1)
n-alt (+ (length r1) (length gg1)))
(setq neu (lg-geschosse-kopieren
(list (cons "QUELLE" "1") (cons "ANZAHL" "2") (cons "ABSTAND_mm" "3000")
(cons "BELAG_ART" "gitterrost") (cons "VERSATZ_X_mm" "0") (cons "VERSATZ_Y_mm" "0"))))
(tlg-eq "kopie: neue geschosse" '(2 3) neu)
(tlg-eq "kopie: geschosse gesamt" 4 (length (lg-geschosse)))
(setq g2 (car (lg-geschoss-finden 2)))
(tlg-eq "kopie: G2 hoehe" "6100" (tlg-attr g2 "HOEHE_BELAG_mm"))
(tlg-eq "kopie: G3 hoehe" "9100" (tlg-attr (car (lg-geschoss-finden 3)) "HOEHE_BELAG_mm"))
(tlg-eq "kopie: G2 belag" "gitterrost" (tlg-attr g2 "BELAG_ART"))
(tlg-eq "kopie: G2 bezeichnung" "Level 2" (tlg-attr g2 "BEZEICHNUNG"))
(tlg-eq "kopie: G2 konstr. von quelle" "150" (tlg-attr g2 "KONSTR_HOEHE_mm"))
(tlg-eq "kopie: G2 layer" "LG_Geschoss_G2" (cdr (assoc 8 (entget g2))))
(tlg-eq "kopie: G2 eigene kontur" T
(and (lg-xdata-wert g2 "kontur")
(not (equal (lg-xdata-wert g2 "kontur") (lg-xdata-wert g1 "kontur")))))
(foreach nr '(2 3)
(tlg-eq (strcat "kopie: G" (itoa nr) " reihen") (length r1) (length (tlg-gs "LG_REIHE_*" nr)))
(tlg-eq (strcat "kopie: G" (itoa nr) " gaenge") (length gg1) (length (tlg-gs "LG_GANG_*" nr)))
(tlg-eq (strcat "kopie: G" (itoa nr) " reihenlaenge")
(tlg-summe-attr r1 "LAENGE") (tlg-summe-attr (tlg-gs "LG_REIHE_*" nr) "LAENGE"))
(tlg-eq (strcat "kopie: G" (itoa nr) " kinder je bereich") (tlg-kinderzahlen 1) (tlg-kinderzahlen nr)))
(tlg-eq "kopie: quelle unveraendert" n-alt
(+ (length (tlg-gs "LG_REIHE_*" 1)) (length (tlg-gs "LG_GANG_*" 1))))
(setq r2 (tlg-gs "LG_REIHE_*" 2) gg2 (tlg-gs "LG_GANG_*" 2))
(tlg-eq "kopie: reihen-layer G2" T
(vl-every '(lambda (e) (= (cdr (assoc 8 (entget e))) "LG_Reihe_G2")) r2))
(setq ids (mapcar '(lambda (e) (tlg-attr e "ID"))
(append (lg-alle-bloecke "LG_REIHE_*") (lg-alle-bloecke "LG_GANG_*"))))
(tlg-eq "kopie: IDs eindeutig" (length ids)
(length (vl-remove-if '(lambda (x) (member x (cdr (member x ids)))) ids)))
(tlg-eq "kopie: auto_geaendert bleibt" (tlg-anzahl-attr r1 "QUELLE" "auto_geaendert")
(tlg-anzahl-attr r2 "QUELLE" "auto_geaendert"))
(tlg-eq "kopie: manuelle gaenge" 2 (tlg-anzahl-attr gg2 "QUELLE" "manuell"))
;; Kinder zeigen auf den neuen Bereich (Nummer und XDATA)
(tlg-eq "kopie: kinder verknuepft" T
(vl-every '(lambda (b / nr)
(setq nr (tlg-attr b "NR"))
(vl-every '(lambda (k) (= (tlg-attr k "BEREICH") nr)) (lg-bereich-kinder b)))
(tlg-gs "LG_BEREICH" 2)))
;; Belag folgt dem Geschoss (Spanplatte -> Gitterrost), Hallenboden-Gang bleibt
(tlg-eq "kopie: belag folgt" 0 (tlg-anzahl-attr gg2 "BELAG" "spanplatte"))
(tlg-eq "kopie: manueller gang gitterrost" 1
(length (tlg-nur (vl-remove-if-not '(lambda (e) (= (tlg-attr e "QUELLE") "manuell")) gg2)
"LG_GANG_4000_1000_GITTERROST")))
(tlg-eq "kopie: hallenboden bleibt" 1 (length (tlg-nur gg2 "LG_GANG_2000_1500_HALLENBODEN")))
(tlg-eq "kopie: bereich belag" T
(vl-every '(lambda (b) (= (tlg-attr b "BELAG") "gitterrost")) (tlg-gs "LG_BEREICH" 2)))
;; Neufuellen einer Kopie laesst die Quelle in Ruhe, manueller Gang bleibt
(setq b2k (vl-some '(lambda (b) (if (= (tlg-attr b "RANDGANG_N_mm") "1000") b)) (tlg-gs "LG_BEREICH" 2)))
(setq n-kinder (length (lg-bereich-kinder b2k)))
(lg-bereich-fuellen b2k)
(tlg-eq "kopie neu gefuellt: kinder" n-kinder (length (lg-bereich-kinder b2k)))
(tlg-eq "kopie neu gefuellt: quelle unveraendert" n-alt
(+ (length (tlg-gs "LG_REIHE_*" 1)) (length (tlg-gs "LG_GANG_*" 1))))
;; Nachtraeglich erweitern: G3 -> G4
(tlg-eq "kopie: G3 -> G4" '(4)
(lg-geschosse-kopieren (list (cons "QUELLE" "3") (cons "ANZAHL" "1") (cons "ABSTAND_mm" "3000")
(cons "BELAG_ART" "gitterrost"))))
(tlg-eq "kopie: G4 hoehe" "12100" (tlg-attr (car (lg-geschoss-finden 4)) "HOEHE_BELAG_mm"))
;; Versatz in Y
(setq neu (lg-geschosse-kopieren (list (cons "QUELLE" "1") (cons "ANZAHL" "1") (cons "ABSTAND_mm" "3100")
(cons "BELAG_ART" "spanplatte") (cons "VERSATZ_Y_mm" "20000"))))
(tlg-eq "kopie versatz: G5" '(5) neu)
(tlg-eq "kopie versatz: kontur" '((0.0 20000.0) (10000.0 26000.0))
(mapcar '(lambda (p) (list (car p) (cadr p))) (ssg-poly-bbox (cadddr (lg-geschoss-finden 5)))))
(tlg-eq "kopie versatz: reihen verschoben" T
(vl-every '(lambda (e) (>= (cadr (cdr (assoc 10 (entget e)))) 20000.0)) (tlg-gs "LG_REIHE_*" 5)))
(tlg-eq "kopie: abstand 0 abgewiesen" nil
(lg-geschosse-kopieren (list (cons "QUELLE" "1") (cons "ANZAHL" "1") (cons "ABSTAND_mm" "0"))))
(setq daten (ssg-json-parse (lg-export-json)))
(tlg-eq "kopie: export geschosse" 6 (length (ssg-json-get daten "geschosse")))
;; --- Gang per Rechteck quer durch den kopierten Randgang-Bereich (G2) ---
(setq rg (lg-gang-rechteck-einfuegen
'((26500.0 0.0 0.0) (27500.0 0.0 0.0) (27500.0 6000.0 0.0) (26500.0 6000.0 0.0))
(list (cons "GESCHOSS" "2") (cons "BREITE_mm" "1000") (cons "ART" "gang")
(cons "BELAG" "gitterrost"))))
(tlg-eq "rechteck: blockname" "LG_GANG_6000_1000_GITTERROST" (if rg (cdr (assoc 2 (entget rg)))))
(tlg-eq "rechteck: mittellinie" '(27000.0 0.0) (if rg (list (car (cdr (assoc 10 (entget rg))))
(cadr (cdr (assoc 10 (entget rg)))))))
(tlg-eq "rechteck: quelle" "manuell" (tlg-attr rg "QUELLE"))
(tlg-eq "rechteck: bereich" (tlg-attr b2k "NR") (tlg-attr rg "BEREICH"))
(tlg-eq "rechteck: kind" T (if (member rg (lg-bereich-kinder b2k)) T nil))
;; Breite aus dem Rechteck, nicht aus werte
(setq rg (lg-gang-rechteck-einfuegen
'((40000.0 10000.0 0.0) (44000.0 10000.0 0.0) (44000.0 11500.0 0.0) (40000.0 11500.0 0.0))
(list (cons "GESCHOSS" "2") (cons "BREITE_mm" "1000") (cons "ART" "gang")
(cons "BELAG" "spanplatte"))))
(tlg-eq "rechteck: breite aus flaeche" "1500" (tlg-attr rg "BREITE_mm"))
(tlg-eq "rechteck: ohne bereich" "" (tlg-attr rg "BEREICH"))
(tlg-eq "rechteck: zu klein" nil
(lg-gang-rechteck-einfuegen '((0.0 0.0) (10.0 0.0) (10.0 0.0) (0.0 0.0))
(list (cons "GESCHOSS" "2") (cons "ART" "gang") (cons "BELAG" "spanplatte"))))
;; Polylinie aus dem BricsCAD-Befehl RECHTECK auswerten
(command "_.RECTANG" "_non" '(700000.0 0.0) "_non" '(704000.0 1200.0))
(setq rk (lg-rechteck-ecken-aus-poly (entlast)))
(entdel (entlast))
(tlg-eq "rechteck-befehl: 4 ecken" 4 (if (listp rk) (length rk)))
(tlg-eq "rechteck-befehl: gang" 1200.0 (caddr (lg-gang-aus-ecken rk)))
;; um 30 Grad gedrehtes Rechteck 5000 x 800 (wie RECHTECK mit Option Drehung)
(setq rk (mapcar '(lambda (p) (polar '(0.0 0.0) (+ (angle '(0.0 0.0) p) (/ pi 6.0)) (distance '(0.0 0.0) p)))
'((0.0 0.0) (5000.0 0.0) (5000.0 800.0) (0.0 800.0))))
(entmake (append (list '(0 . "LWPOLYLINE") '(100 . "AcDbEntity") '(100 . "AcDbPolyline") '(90 . 4) '(70 . 1))
(mapcar '(lambda (p) (cons 10 p)) rk)))
(setq rk (lg-rechteck-ecken-aus-poly (entlast)))
(entdel (entlast))
(tlg-eq "rechteck-befehl: gedreht breite" 800.0 (caddr (lg-gang-aus-ecken rk)))
(tlg-eq "rechteck-befehl: gedreht laenge" 5000.0
(distance (car (lg-gang-aus-ecken rk)) (cadr (lg-gang-aus-ecken rk))))
;; Fuenfeck (z.B. Fase) -> kein Rechteck
(entmake (append (list '(0 . "LWPOLYLINE") '(100 . "AcDbEntity") '(100 . "AcDbPolyline") '(90 . 5) '(70 . 1))
(mapcar '(lambda (p) (cons 10 p)) '((0.0 0.0) (900.0 0.0) (1000.0 100.0) (1000.0 500.0) (0.0 500.0)))))
(tlg-eq "rechteck-befehl: fase abgelehnt" 'kein-rechteck (lg-rechteck-ecken-aus-poly (entlast)))
(entdel (entlast))
;; --- Rack-Erkennung: direkt, verschachtelt + gedreht, MINSERT ---
;; eigener Testblock (Parameter als Attribut bzw. ATTDEF-Vorgabe), damit
;; die Suche nach goh_rack davon unberuehrt bleibt
(lg-blockdef "TLG_RACK" (list (lg-geom-linie '(0.0 0.0 0.0) '(1000.0 0.0 0.0))
(lg-geom-attdef "Laenge" "500" '(0.0 0.0 0.0) 50.0 nil)
(lg-geom-attdef "Ebenen" "3" '(0.0 0.0 0.0) 50.0 nil)))
(lg-blockdef "TLG_GRUPPE" (list '((0 . "INSERT") (8 . "0") (2 . "TLG_RACK") (10 0.0 0.0 0.0))
'((0 . "INSERT") (8 . "0") (2 . "TLG_RACK") (10 0.0 2000.0 0.0))))
(lg-block-einfuegen "TLG_RACK" '(-100000.0 0.0 0.0) 0.0 "0" '(("Laenge" . "100 cm") ("Ebenen" . "2")))
(setq grp (lg-block-einfuegen "TLG_GRUPPE" '(-200000.0 0.0 0.0) (/ pi 2.0) "0" nil))
(vla-AddMInsertBlock (lg-modelspace) (vlax-3d-point '(-300000.0 0.0 0.0)) "TLG_RACK"
1.0 1.0 1.0 0.0 1 3 1000.0 3000.0)
(setq rk (lg-racks-finden "TLG_RACK" "Laenge" "Ebenen"))
(tlg-eq "racks: anzahl" 6 (length rk))
(tlg-eq "racks: laenge (attribut mit einheit + vorgaben)" 3500.0
(apply '+ (mapcar '(lambda (r) (cond ((lg-rk-laenge r)) (0.0))) rk)))
(tlg-eq "racks: ebenen" 17.0 (apply '+ (mapcar '(lambda (r) (cond ((lg-rk-ebenen r)) (0.0))) rk)))
(tlg-eq "racks: verschachtelt top" 2
(length (vl-remove-if-not '(lambda (r) (equal (lg-rk-top r) grp)) rk)))
;; Gruppe um 90 Grad gedreht: zweites Rack (lokal y = 2000) liegt bei x = -202000
(tlg-eq "racks: verschachtelt gedreht" T
(equal (vl-sort (mapcar '(lambda (r) (car (lg-rk-mitte r)))
(vl-remove-if-not '(lambda (r) (equal (lg-rk-top r) grp)) rk))
'<)
'(-202000.0 -200000.0) 100.0))
;; MINSERT 1 Zeile x 3 Spalten, Abstand 3000: Mitten = Einfuegepunkte
(tlg-eq "racks: minsert" '(-300000.0 -297000.0 -294000.0)
(vl-sort (mapcar '(lambda (r) (car (lg-rk-mitte r)))
(vl-remove-if-not '(lambda (r) (< (car (lg-rk-mitte r)) -250000.0)) rk))
'<))
;; --- goh_rack-Regression (Referenzwerte goh_storage_test_regal_report.txt) ---
(setq pfad (strcat (getenv "DXFMAKRO") "/tests/testdata/goh_storage_test.dwg"))
(if (findfile pfad)
(progn
;; "*" = aufgeloest einfuegen: die goh_rack-Bloecke liegen danach
;; direkt im Modellbereich wie in der Originalzeichnung
(command "_.-INSERT" (strcat "*" pfad) '(50000.0 0.0 0.0) 1 0)
(setq racks (lg-export-goh-racks nil))
(tlg-eq "goh_rack anzahl" 4 (length (car racks)))
(setq laengen (tlg-summe (car racks) "laenge_mm"))
(setq kap 0.0)
(foreach t1 (car racks)
(setq tr (ssg-json-parse t1))
(setq kap (+ kap (* (ssg-json-get tr "laenge_mm") (ssg-json-get tr "ebenen")))))
(tlg-eq "goh_rack laenge gesamt" T (equal laengen 38946.39 0.1))
(tlg-eq "goh_rack laenge x ebenen" T (equal kap 77892.78 0.1))
(princ (strcat "\n goh_rack: " (rtos laengen 2 2) " mm / " (rtos kap 2 2) " mm"))
;; --- Geschoss mit goh_racks kopieren (deckungsgleich, 2D) ---
;; Geschosskontur um die Racks legen (Mitten aus lg-racks-sammeln + Rand)
(setq bb (ssg-poly-bbox (mapcar '(lambda (p) (list (car p) (cadr p)))
(mapcar 'lg-rk-mitte (lg-racks-sammeln)))))
(setq gr (lg-geschoss-naechste-nr))
(lg-geschoss-anlegen (tlg-rechteck (- (car (car bb)) 20000.0) (- (cadr (car bb)) 20000.0)
(+ (car (cadr bb)) 20000.0) (+ (cadr (cadr bb)) 20000.0))
(append (list (cons "HOEHE_BELAG_mm" "20000")) (lg-geschoss-vorgaben gr)))
(setq neu (lg-geschosse-kopieren (list (cons "QUELLE" (itoa gr)) (cons "ANZAHL" "1")
(cons "ABSTAND_mm" "3000") (cons "BELAG_ART" "spanplatte"))))
(setq racks (lg-export-goh-racks (lg-geschoss-kandidaten)))
(tlg-eq "rack-kopie: anzahl" 8 (length (car racks)))
(tlg-eq "rack-kopie: quelle" 4
(length (vl-remove-if-not '(lambda (t1) (equal (ssg-json-get (ssg-json-parse t1) "geschoss") gr))
(car racks))))
(tlg-eq "rack-kopie: kopie" 4
(length (vl-remove-if-not '(lambda (t1) (equal (ssg-json-get (ssg-json-parse t1) "geschoss") (car neu)))
(car racks))))
(tlg-eq "rack-kopie: keine warnung" nil (cadr racks))
(tlg-eq "rack-kopie: laenge kopie" T
(equal (tlg-summe (car racks) "laenge_mm") (* 2 38946.39) 0.2))
(tlg-eq "rack-kopie: layer" T (if (tblsearch "LAYER" (strcat "LG_Regal_G" (itoa (car neu)))) T nil))
;; Auswertung je Geschoss wie LG_REGALE: je 4 Racks in Quelle und Kopie
(setq kand (lg-geschoss-kandidaten))
(setq zl (lg-racks-auswerten
(mapcar '(lambda (r) (list (lg-rack-geschoss (lg-ent-layer (lg-rk-top r)) (lg-rk-mitte r) kand)
(lg-rk-laenge r) (lg-rk-ebenen r)))
(lg-racks-sammeln))))
(tlg-eq "lg_regale: geschosse" (list gr (car neu)) (mapcar 'car zl))
(tlg-eq "lg_regale: anzahl" '(4 4) (mapcar 'cadr zl))
(tlg-eq "lg_regale: laenge x ebenen" T (equal (cadddr (car zl)) 77892.78 0.2))
(tlg-eq "lg_regale: befehl laeuft" nil (vl-catch-all-error-p (vl-catch-all-apply 'c:LG_REGALE nil))))
(tlg-eq "goh_storage_test.dwg vorhanden" T nil))
;; --- Von Hand Gesetztes bleibt: Einfuegen loest keine Neuberechnung aus ---
(setq kb (tlg-rechteck 600000.0 0.0 610000.0 6000.0))
(setq b3 (lg-bereich-anlegen kb (append (list (cons "GESCHOSS" "1"))
(lg-bereich-vorgaben (ssg-lwpoly-punkte kb)))))
(setq rh3 (tlg-nur (lg-bereich-kinder b3) "LG_REIHE_*"))
(tlg-eq "von hand: auto reihen" 4 (length rh3))
;; Anwender loescht eine automatisch erzeugte Reihe ...
(entdel (car rh3))
;; ... und setzt einen Gang quer durch den Bereich
(setq mg3 (lg-gang-einfuegen '(605000.0 0.0 0.0) '(605000.0 6000.0 0.0)
(list (cons "GESCHOSS" "1") (cons "BREITE_mm" "1000") (cons "ART" "gang")
(cons "BELAG" "spanplatte"))))
(setq rh3 (tlg-nur (lg-bereich-kinder b3) "LG_REIHE_*"))
(tlg-eq "von hand: geloeschte reihe bleibt weg" 6 (length rh3))
(tlg-eq "von hand: reihen zugeschnitten" 27000.0 (tlg-summe-attr rh3 "LAENGE"))
(tlg-eq "von hand: reste bleiben auto" 6 (tlg-anzahl-attr rh3 "QUELLE" "auto"))
;; Reihe von Hand aendern (auto -> auto_geaendert)
(setq ge3 (lg-linie-neu-aufbauen-sperre (car rh3)
(lg-wert-setzen (lg-wert-setzen (ssg-attrib-read (car rh3)) "QUELLE" "auto_geaendert")
"LAENGE" "3000")))
;; Regalreihe von Hand quer in einen automatisch erzeugten Gang setzen
(setq ag3 (vl-some '(lambda (g) (if (and (= (tlg-attr g "QUELLE") "auto") (= (tlg-attr g "ART") "gang")
(< (car (cdr (assoc 10 (entget g)))) 601000.0))
g))
(tlg-nur (lg-bereich-kinder b3) "LG_GANG_*")))
(setq n3 (length (tlg-nur (lg-bereich-kinder b3) "LG_GANG_*")))
(setq mr3 (lg-manuell-einfuegen "reihe"
(list 601000.0 (cadr (cdr (assoc 10 (entget ag3)))) 0.0)
(list 603000.0 (cadr (cdr (assoc 10 (entget ag3)))) 0.0)
(list (cons "GESCHOSS" "1") (cons "TIEFE_mm" "700") (cons "BEREICH" ""))))
(tlg-eq "von hand: reihe verknuepft" (tlg-attr b3 "NR") (tlg-attr mr3 "BEREICH"))
(tlg-eq "von hand: reihe quelle" "manuell" (tlg-attr mr3 "QUELLE"))
(tlg-eq "von hand: gang zerteilt" (1+ n3) (length (tlg-nur (lg-bereich-kinder b3) "LG_GANG_*")))
(tlg-eq "von hand: manueller gang unberuehrt" T (if (entget mg3) T nil))
;; unverknuepfte manuelle Reihe (Altbestand) in der Kontur, im Streifen
;; des manuellen Gangs (dort sicher frei von automatischen Objekten)
(setq alt3 (lg-linie-einfuegen "reihe" '(605000.0 500.0 0.0) '(605000.0 2500.0 0.0)
(list (cons "GESCHOSS" "1") (cons "TIEFE_mm" "700") (cons "QUELLE" "manuell")) nil))
;; ausdrueckliche Neuberechnung (Bereich bearbeiten): Handarbeit bleibt
(lg-bereich-fuellen b3)
(tlg-eq "neuberechnung: manueller gang bleibt" T (if (entget mg3) T nil))
(tlg-eq "neuberechnung: manuelle reihe bleibt" T (if (entget mr3) T nil))
(tlg-eq "neuberechnung: geaenderte reihe bleibt" T (if (and ge3 (entget ge3)) T nil))
(tlg-eq "neuberechnung: altbestand bleibt" T (if (entget alt3) T nil))
;; nichts Automatisches ueberdeckt mehr die Handarbeit
(tlg-eq "neuberechnung: nichts zu schneiden" '(0 0 0 0)
(mapcar 'lg-sperre-anwenden (list mg3 mr3 ge3 alt3)))
;; Gang-Rechteck buendig an eine Reihe gezeichnet, krumme Breite 999.6:
;; die Rundung darf die Ansetzkante nicht verschieben, die Reihe bleibt
(setq rh3 (vl-remove-if-not '(lambda (r) (= (tlg-attr r "QUELLE") "auto"))
(tlg-nur (lg-bereich-kinder b3) "LG_REIHE_*")))
(setq bb (lg-linie-polygon (car rh3))
kb (mapcar '(lambda (q p) (/ (- q p) (distance (car bb) (cadddr bb)))) (cadddr bb) (car bb)))
(setq rg (lg-gang-rechteck-einfuegen
(list (cadddr bb) (caddr bb)
(mapcar '+ (caddr bb) (mapcar '(lambda (x) (* x 999.6)) kb))
(mapcar '+ (cadddr bb) (mapcar '(lambda (x) (* x 999.6)) kb)))
(list (cons "GESCHOSS" "1") (cons "ART" "gang") (cons "BELAG" "spanplatte"))))
(tlg-eq "rechteck buendig: gang breite" "1000" (tlg-attr rg "BREITE_mm"))
(tlg-eq "rechteck buendig: reihe bleibt" T (if (entget (car rh3)) T nil))
(ssg-gui-an)
(princ))
(defun lager:export-results (out-dir / f pfad)
(setq pfad (strcat out-dir "/lager_results.json"))
(setq f (open pfad "w"))
(if f
(progn
(write-line "{" f)
(write-line (strcat " \"pass\": " (itoa *tlg-pass*) ",") f)
(write-line (strcat " \"fail\": " (itoa *tlg-fail*) ",") f)
(write-line (strcat " \"status\": \""
(if (= *tlg-fail* 0) "OK" "FEHLER") "\"") f)
(write-line "}" f)
(close f)
(princ (strcat "\n Ergebnis: " pfad))))
(princ))
(defun c:TEST_LAGER ( / )
(setq *tlg-pass* 0 *tlg-fail* 0 *tlg-fails* '())
(princ "\n================================================================")
(princ "\n TEST_LAGER - Lagerplanung (LagerInsert.lsp)")
(princ "\n================================================================")
(lager:tests)
(princ "\n\n----------------------------------------------------------------")
(princ (strcat "\n Ergebnis: " (itoa *tlg-pass*) " PASS, "
(itoa *tlg-fail*) " FAIL"))
(if (> *tlg-fail* 0)
(progn
(princ "\n Fehlgeschlagen:")
(foreach n (reverse *tlg-fails*) (princ (strcat "\n - " n)))))
(princ "\n================================================================")
(princ))
(princ "\nTEST_LAGER geladen. Aufruf: TEST_LAGER")
(princ)
+208
View File
@@ -0,0 +1,208 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test_lager_yaml.py - lib/lager_yaml_export.py (Lagerplanung -> RFQ-Overlay-YAML).
Laeuft ohne BricsCAD: die Rohdaten sind von Hand nachgestellt, im Format von
LAGER_EXPORT (Lisp/LagerInsert.lsp, lg-export-json). Geprueft wird:
1. levels: Belagshoehen, lichte Hoehe aus dem Geschoss darueber,
Haengeebenen (Summe aller Reihen je Schienenhoehe/Warenlaenge, inkl.
goh_rack-Bloecke), Belag/Gitterrost nur auf Buehnen.
2. layout: Bereichsparameter, Ganganzahl/-laenge, Huelle.
3. Provenienz: jeder Overlay-Wert hat einen Herkunfts-Eintrag.
4. Abgleich gegen cfg/example_goh_storage.yaml: Abweichungen -> Konflikte,
null/fehlend -> gefuellt.
5. CLI schreibt YAML (wieder einlesbar) und Nachweis-HTML.
"""
import json
import os
import sys
import pytest
import yaml
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "lib"))
import lager_yaml_export as lye # noqa: E402
BEISPIEL_YAML = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"..", "cfg", "example_goh_storage.yaml")
EBENEN_EG = [{"schienenhoehe_mm": 2300, "warenlaenge_mm": 1200},
{"schienenhoehe_mm": 1100, "warenlaenge_mm": 1100}]
EBENEN_OG = [{"schienenhoehe_mm": 2800, "warenlaenge_mm": 1200},
{"schienenhoehe_mm": 1400, "warenlaenge_mm": 1200}]
def _reihe(gs, laenge, ebenen, tiefe=700):
return {"geschoss": gs, "laenge_mm": laenge, "tiefe_mm": tiefe, "haengeebenen": ebenen,
"schienen": 1, "quelle": "auto", "bereich": 1}
def _gang(gs, laenge, breite=1000, art="gang", belag="spanplatte"):
return {"geschoss": gs, "laenge_mm": laenge, "breite_mm": breite, "art": art,
"belag": belag, "bereich": 1}
@pytest.fixture
def raw():
"""Zwei Geschosse: Hallenboden (EG) + Buehne auf 3100 mm, je ein 2-Reihen-Modul."""
return {
"version": 1, "zeichnung": "test.dwg", "erzeugt": "20261021120000", "einheit": "mm",
"land": "DE",
"belag_yaml": {"spanplatte": "chipboard", "gitterrost": "grating", "hallenboden": ""},
"reihen_als_gitterrost": True,
"geschosse": [
# bewusst nicht nach Hoehe sortiert - der Export sortiert selbst
{"nr": 1, "bezeichnung": "Level 1", "hoehe_belag_mm": 3100, "konstr_hoehe_mm": 150,
"lichte_hoehe_mm": None, "belag_art": "spanplatte", "personen": 20,
"kontur_flaeche_mm2": 60e6, "kontur_bbox": [0, 0, 10000, 6000]},
{"nr": 0, "bezeichnung": "ground floor", "hoehe_belag_mm": 0, "konstr_hoehe_mm": 0,
"lichte_hoehe_mm": None, "belag_art": "hallenboden", "personen": 20,
"kontur_flaeche_mm2": 60e6, "kontur_bbox": [0, 0, 10000, 6000]},
],
"bereiche": [
{"nr": 1, "geschoss": 0, "gangbreite_mm": 1000, "regaltiefe_mm": 700,
"reihen_je_gang": 2, "schienen_je_gang": 1},
{"nr": 2, "geschoss": 1, "gangbreite_mm": 1000, "regaltiefe_mm": 700,
"reihen_je_gang": 2, "schienen_je_gang": 1},
],
"reihen": [_reihe(0, 10000, EBENEN_EG), _reihe(0, 10000, EBENEN_EG),
_reihe(1, 10000, EBENEN_OG), _reihe(1, 10000, EBENEN_OG)],
"gaenge": [_gang(0, 10000), _gang(1, 10000),
_gang(1, 700, breite=1200, art="quergang"),
_gang(1, 500, breite=1200, art="quergang", belag="gitterrost")],
"goh_racks": [{"geschoss": 1, "name": "goh_rack", "laenge_mm": 5000, "ebenen": 2,
"tiefe_mm": 700, "haengeebenen": EBENEN_OG, "haengeebenen_quelle": "vorgabe"}],
"warnungen": ["Testwarnung"],
}
def test_levels_sortiert_und_hoehen(raw):
overlay, _ = lye.aggregiere(raw)
lv = overlay["levels"]
assert [l["floor_elevation_m"] for l in lv] == [0.0, 3.1]
assert lv[0]["remark"] == "ground floor"
# lichte Hoehe EG = 3100 - 150 (Konstruktion darueber) - 0
assert lv[0]["clear_height_m"] == pytest.approx(2.95)
# oberstes Geschoss ohne LICHTE_HOEHE_mm -> nicht berechenbar
assert "clear_height_m" not in lv[1]
def test_haengeebenen_summe_inkl_goh_rack(raw):
overlay, _ = lye.aggregiere(raw)
eg, og = overlay["levels"]
assert eg["hanging_levels"] == [
{"rail_height_m": 2.3, "rack_length_m": 20.0, "goods_length_m": 1.2},
{"rail_height_m": 1.1, "rack_length_m": 20.0, "goods_length_m": 1.1},
]
# 2 Reihen a 10 m + goh_rack 5 m, oberste Schiene zuerst
assert [h["rack_length_m"] for h in og["hanging_levels"]] == [25.0, 25.0]
assert og["hanging_levels"][0]["rail_height_m"] == 2.8
def test_belag_nur_auf_buehne(raw):
overlay, _ = lye.aggregiere(raw)
eg, og = overlay["levels"]
assert "walkway" not in eg and "grating" not in eg
# Belag: Hauptgang 10 m2 + Quergang Spanplatte 0,84 m2
assert og["walkway"]["area_m2"] == pytest.approx(10.8)
assert og["walkway"]["spec"] == "chipboard"
# Gitterrost: Quergang 0,6 m2 + Reihen 2 x 7 m2 + goh_rack 3,5 m2
assert og["grating"] == pytest.approx(18.1)
def test_belag_ohne_gaenge_aus_kontur(raw):
raw["gaenge"] = []
overlay, s = lye.aggregiere(raw)
og = overlay["levels"][1]
# 60 m2 Kontur - (2 x 7 + 3,5) m2 Reihen
assert og["walkway"]["area_m2"] == pytest.approx(42.5)
assert "Konturflaeche" in s.provenance["levels[1].walkway.area_m2"]["formel"]
def test_layout(raw):
overlay, _ = lye.aggregiere(raw)
lay = overlay["layout"]
assert lay["aisle_width_m"] == 1.0
assert lay["rack_rows_per_aisle"] == 2
assert lay["rack_depth_m"] == 0.7
assert lay["trolley_rails_per_aisle"] == 1
assert lay["aisles"] == 1 # Quergaenge zaehlen nicht
assert lay["aisle_length_m"] == 10.0
assert lay["block_length_m"] == 10.0
assert lay["block_width_m"] == 6.0
def test_randgang_im_belag_nicht_in_aisles(raw):
# Randgang 10 m x 1 m (Spanplatte) im OG: Belag waechst, aisles bleibt
raw["gaenge"].append(_gang(1, 10000, art="randgang"))
overlay, s = lye.aggregiere(raw)
og = overlay["levels"][1]
assert og["walkway"]["area_m2"] == pytest.approx(20.8)
assert overlay["layout"]["aisles"] == 1
assert overlay["layout"]["aisle_length_m"] == 10.0
assert "Randgaenge G1: 10.0 m" in s.provenance["layout.aisles"]["formel"]
def test_unterschiedliche_bereiche_warnen(raw):
raw["bereiche"][1]["gangbreite_mm"] = 1200
raw["bereiche"].append(dict(raw["bereiche"][1]))
overlay, s = lye.aggregiere(raw)
assert overlay["layout"]["aisle_width_m"] == 1.2
assert any("aisle_width_m" in w for w in s.warnungen)
def test_provenienz_vollstaendig(raw):
ausgabe, _ = lye.baue_ausgabe(raw)
# hanging_levels: Provenienz je rack_length_m; Schienenhoehe/Warenlaenge
# sind der Gruppenschluessel derselben Zeile und stehen in deren Formel.
fehlend = [p for p, _ in lye._blaetter(ausgabe)
if p not in ausgabe["_provenance"]
and not (".hanging_levels[" in p and not p.endswith(".rack_length_m"))]
assert fehlend == []
assert ausgabe["_warnings"][0] == "Testwarnung"
def test_abgleich_mit_beispiel_yaml(raw):
with open(BEISPIEL_YAML, encoding="utf-8") as f:
hand = yaml.safe_load(f)
konflikte, fuellt = lye.abgleich(lye.aggregiere(raw)[0], hand)
pfade = {k["pfad"]: k for k in konflikte}
# Hand 4 Ebenen, Zeichnung 2
assert "levels" in pfade
# Hand 1,0 m Gangbreite = Zeichnung -> kein Konflikt
assert "layout.aisle_width_m" not in pfade
# Hand 4286 m2, Zeichnung 10,8 m2
assert pfade["levels[1].walkway.area_m2"]["hand"] == 4286
assert pfade["levels[1].walkway.area_m2"]["abweichung_prozent"] < -99
# Hand null -> Zeichnung fuellt
gef = {f["pfad"] for f in fuellt}
assert "layout.block_length_m" in gef
assert "levels[1].grating" in gef
def test_hole_pfad():
obj = {"levels": [{"a": 1}, {"walkway": {"area_m2": 5}}]}
assert lye._hole(obj, "levels[1].walkway.area_m2") == (True, 5)
assert lye._hole(obj, "levels[2].walkway") == (False, None)
assert lye._hole(obj, "layout.x") == (False, None)
def test_cli(raw, tmp_path):
rp = tmp_path / "x_lager_raw.json"
rp.write_text(json.dumps(raw), encoding="cp1252")
yp = tmp_path / "x_drawing.yaml"
hp = tmp_path / "x_nachweis.html"
rc = lye.main([str(rp), str(yp), "--hand", BEISPIEL_YAML, "--nachweis", str(hp)])
assert rc == 0
daten = yaml.safe_load(yp.read_text(encoding="utf-8"))
assert daten["levels"][1]["floor_elevation_m"] == 3.1
assert daten["_conflicts"]
assert "levels[0].floor_elevation_m" in daten["_provenance"]
assert "Lager-Nachweis" in hp.read_text(encoding="utf-8")
def test_cli_fehlende_datei(tmp_path):
assert lye.main([str(tmp_path / "fehlt.json"), str(tmp_path / "o.yaml")]) == 2
+436 -1
View File
@@ -2278,6 +2278,441 @@
)
)
;; ------------------------------------------------------------
;; TESTS: ssg_core.lsp - Polygon-Helfer (reine 2D-Berechnung)
;; ------------------------------------------------------------
(defun tu-tests-poly ( / _q _u _l)
(princ "\n[ssg_core Polygon]")
(setq _q '((0.0 0.0) (10.0 0.0) (10.0 5.0) (0.0 5.0)))
(tu-eqf "ssg-poly-flaeche/rechteck" 50.0 (ssg-poly-flaeche _q))
(tu-eqf "ssg-poly-flaeche/gegen-uhrzeiger" 50.0 (ssg-poly-flaeche (reverse _q)))
(tu-eqf "ssg-poly-umfang" 30.0 (ssg-poly-umfang _q))
(tu-eq "ssg-poly-kanten/anzahl" 4 (length (ssg-poly-kanten _q)))
(tu-eq "ssg-poly-kanten/doppelter-schlusspunkt" 4
(length (ssg-poly-kanten (append _q (list (car _q))))))
(tu-eqf "ssg-poly-bbox" '((0.0 0.0) (10.0 5.0)) (ssg-poly-bbox _q))
(tu-true "ssg-punkt-in-poly-p/innen" (ssg-punkt-in-poly-p '(5.0 2.0) _q))
(tu-nil "ssg-punkt-in-poly-p/aussen" (ssg-punkt-in-poly-p '(11.0 2.0) _q))
;; U-Form: Schnitt durch beide Schenkel
(setq _u '((0.0 0.0) (10.0 0.0) (10.0 10.0) (7.0 10.0) (7.0 3.0) (3.0 3.0) (3.0 10.0) (0.0 10.0)))
(tu-eqf "ssg-poly-flaeche/u-form" 72.0 (ssg-poly-flaeche _u))
(tu-nil "ssg-punkt-in-poly-p/u-luecke" (ssg-punkt-in-poly-p '(5.0 5.0) _u))
(tu-eqf "ssg-poly-schnitt-y/u-schenkel" '((0.0 3.0) (7.0 10.0)) (ssg-poly-schnitt-y _u 5.0))
(tu-eqf "ssg-poly-schnitt-y/u-boden" '((0.0 10.0)) (ssg-poly-schnitt-y _u 1.0))
(tu-eqf "ssg-poly-schnitt-y/unterkante" '((0.0 10.0)) (ssg-poly-schnitt-y _q 0.0))
(tu-nil "ssg-poly-schnitt-y/oberkante" (ssg-poly-schnitt-y _q 5.0))
(tu-eqf "ssg-intervalle-schnitt" '((2.0 3.0) (5.0 10.0))
(ssg-intervalle-schnitt '((0.0 10.0)) '((2.0 3.0) (5.0 12.0))))
(tu-nil "ssg-intervalle-schnitt/leer" (ssg-intervalle-schnitt '((0.0 1.0)) '((2.0 3.0))))
(tu-eqf "ssg-intervalle-minus" '((0.0 2.0) (3.0 5.0) (6.0 10.0))
(ssg-intervalle-minus '((0.0 10.0)) '((2.0 3.0) (5.0 6.0))))
(tu-eqf "ssg-intervalle-minus/ohne-b" '((0.0 10.0)) (ssg-intervalle-minus '((0.0 10.0)) nil))
(tu-eqf "ssg-intervalle-minus/ueberdeckt" nil (ssg-intervalle-minus '((2.0 3.0)) '((0.0 10.0))))
(tu-eqf "ssg-punkt-drehen/90" '(0.0 1.0) (ssg-punkt-drehen '(1.0 0.0) (/ pi 2.0) '(0.0 0.0)))
(tu-eqf "ssg-punkt-drehen/z-bleibt" '(1.0 2.0 7.0) (ssg-punkt-drehen '(1.0 2.0 7.0) 0.0 '(5.0 5.0)))
;; Umlaufsinn
(tu-eqf "ssg-poly-flaeche-signiert/ccw" 50.0 (ssg-poly-flaeche-signiert _q))
(tu-eqf "ssg-poly-flaeche-signiert/cw" -50.0 (ssg-poly-flaeche-signiert (reverse _q)))
;; Band-Clip: x-Ausdehnung des Polygonteils zwischen zwei Waagrechten
(tu-eqf "ssg-poly-band-x/rechteck" '(0.0 10.0) (ssg-poly-band-x _q 1.0 2.0))
(tu-eqf "ssg-poly-band-x/dreieck-unten" '(0.0 10.0) (ssg-poly-band-x '((0.0 0.0) (10.0 0.0) (0.0 10.0)) 0.0 5.0))
(tu-eqf "ssg-poly-band-x/dreieck-oben" '(0.0 5.0) (ssg-poly-band-x '((0.0 0.0) (10.0 0.0) (0.0 10.0)) 5.0 10.0))
(tu-nil "ssg-poly-band-x/ausserhalb" (ssg-poly-band-x _q 6.0 7.0))
;; nur an der Kante beruehrt -> sperrt nicht
(tu-nil "ssg-poly-band-x/kante" (ssg-poly-band-x _q 5.0 6.0))
)
;; ------------------------------------------------------------
;; TESTS: LagerInsert.lsp / LagerPruefung.lsp - reine Funktionen
;; (Geschoss-/Bereichsbloecke, Export und Dialoge brauchen eine Zeichnung:
;; tests/test_lager.lsp)
;; ------------------------------------------------------------
(defun tu-lg-anzahl (recs typ)
(length (vl-remove-if-not '(lambda (r) (= (car r) typ)) recs))
)
(defun tu-lg-summe (recs typ / s)
(setq s 0.0)
(foreach r recs
(if (= (car r) typ) (setq s (+ s (distance (cadr r) (caddr r))))))
s
)
(defun tu-tests-lager ( / _q _erg _l _kand _kand2 _sq0 _sq3 _regel _prof _rand _rs _tab _w)
(princ "\n[LagerInsert / LagerPruefung]")
(if (not (and (car (atoms-family 1 '("LG-FUELLUNG-BERECHNEN")))
(car (atoms-family 1 '("LG-REGEL-AUFLOESEN")))))
(tu-record nil "lager/geladen" "LagerInsert/LagerPruefung nicht geladen (ssg_load.lsp)")
(progn
;; --- Zahlen/Texte ---
(tu-eq "lg-mm-str/ganz" "2300" (lg-mm-str 2300.0))
(tu-eq "lg-mm-str/halb" "2.5" (lg-mm-str 2.5))
(tu-eq "lg-mm-str/negativ" "-1" (lg-mm-str -1.0))
(tu-eq "lg-mm-str/nil" "" (lg-mm-str nil))
(tu-eqf "lg-zahl/komma" 1.5 (lg-zahl "1,5"))
(tu-nil "lg-zahl/leer" (lg-zahl ""))
(tu-nil "lg-zahl/text" (lg-zahl "abc"))
(tu-eqf "lg-haengeebenen-parse" '((2300.0 1200.0) (1100.0 1100.0))
(lg-haengeebenen-parse "2300/1200;1100/1100"))
(tu-eqf "lg-haengeebenen-parse/ohne-ware" '((2300.0 0.0)) (lg-haengeebenen-parse "2300"))
(tu-nil "lg-haengeebenen-parse/ungueltig" (lg-haengeebenen-parse "x"))
(tu-eq "lg-haengeebenen-string" "2300/1200;1100/1100"
(lg-haengeebenen-string '((2300.0 1200.0) (1100.0 1100.0))))
(tu-eq "lg-haengeebenen-n/1" "2300/1200" (lg-haengeebenen-n "2300/1200;1100/1100" 1))
(tu-eq "lg-haengeebenen-n/zuviel" "2300/1200;1100/1100" (lg-haengeebenen-n "2300/1200;1100/1100" 5))
(tu-eq "lg-feld-pruefen/mm" "1000" (lg-feld-pruefen "mm" " 1000 "))
(tu-nil "lg-feld-pruefen/mm-neg" (lg-feld-pruefen "mm" "-5"))
(tu-nil "lg-feld-pruefen/mm-leer" (lg-feld-pruefen "mm" ""))
(tu-eq "lg-feld-pruefen/mm_leer" "" (lg-feld-pruefen "mm_leer" ""))
(tu-eq "lg-feld-pruefen/int" "2" (lg-feld-pruefen "int" "2"))
(tu-nil "lg-feld-pruefen/int-0" (lg-feld-pruefen "int" "0"))
(tu-eq "lg-feld-pruefen/int0-0" "0" (lg-feld-pruefen "int0" "0"))
(tu-nil "lg-feld-pruefen/int-bruch" (lg-feld-pruefen "int" "1.5"))
(tu-eq "lg-feld-pruefen/grad-neg" "-90" (lg-feld-pruefen "grad" "-90"))
(tu-eq "lg-feld-pruefen/ebenen" "2300/1200;1100/1100" (lg-feld-pruefen "ebenen" "2300/1200; 1100/1100"))
(tu-nil "lg-feld-pruefen/ebenen-leer" (lg-feld-pruefen "ebenen" ""))
(tu-eq "lg-layer-name/geschoss" "LG_Reihe_G2" (lg-layer-name "LG_Reihe" "2"))
(tu-eq "lg-layer-name/int" "LG_Reihe_G3" (lg-layer-name "LG_Reihe" 3))
(tu-eq "lg-layer-name/ohne" "LG_Reihe" (lg-layer-name "LG_Reihe" nil))
;; --- Auto-Fuellung: Rechteck 10000 x 6000, Gang 1000, Tiefe 700 ---
(setq _q '((0.0 0.0) (10000.0 0.0) (10000.0 6000.0) (0.0 6000.0)))
;; 2 Reihen je Gang: Modul 700+1000+700 = 2400 -> 2 Module, Rest 1200
(setq _erg (lg-fuellung-berechnen _q (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2))))
(tu-eq "lg-fuellung/2je-reihen" 4 (tu-lg-anzahl (car _erg) "reihe"))
(tu-eq "lg-fuellung/2je-gaenge" 2 (tu-lg-anzahl (car _erg) "gang"))
(tu-eq "lg-fuellung/2je-module" 2 (caddr _erg))
(tu-eqf "lg-fuellung/2je-rest" 1200.0 (cadr _erg))
(tu-eqf "lg-fuellung/2je-reihenlaenge" 40000.0 (tu-lg-summe (car _erg) "reihe"))
(tu-eqf "lg-fuellung/erste-reihe" '("reihe" (0.0 350.0) (10000.0 350.0) 700.0) (car (car _erg)))
(tu-eqf "lg-fuellung/erster-gang" '("gang" (0.0 1200.0) (10000.0 1200.0) 1000.0) (cadr (car _erg)))
;; Ruecken an Ruecken: 3. Reihe (Modul 1) direkt nach der 2. Reihe
(tu-eqf "lg-fuellung/ruecken-an-ruecken" '(0.0 2050.0) (cadr (nth 2 (car _erg))))
(tu-eqf "lg-fuellung/zweites-modul" '(0.0 2750.0) (cadr (nth 3 (car _erg))))
;; 1 Reihe je Gang: Modul 1700 -> 3 Module, Rest 900
(setq _erg (lg-fuellung-berechnen _q (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 1))))
(tu-eq "lg-fuellung/1je-reihen" 3 (tu-lg-anzahl (car _erg) "reihe"))
(tu-eq "lg-fuellung/1je-gaenge" 3 (tu-lg-anzahl (car _erg) "gang"))
(tu-eqf "lg-fuellung/1je-rest" 900.0 (cadr _erg))
;; Quergaenge alle 4000 mm, 1000 breit, ein Modul (Hoehe 2400)
(tu-eqf "lg-quergang-luecken" '((4000.0 5000.0) (9000.0 10000.0))
(lg-quergang-luecken 0.0 10000.0 4000.0 1000.0 0.0))
;; keine Luecke am Reihenende, wenn dahinter kein Mindeststueck passt
(tu-eqf "lg-quergang-luecken/reihenende" '((4000.0 5000.0))
(lg-quergang-luecken 0.0 10000.0 4000.0 1000.0 500.0))
(tu-nil "lg-quergang-luecken/aus" (lg-quergang-luecken 0.0 10000.0 0.0 1000.0 500.0))
(setq _erg (lg-fuellung-berechnen '((0.0 0.0) (10000.0 0.0) (10000.0 2400.0) (0.0 2400.0))
(list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2)
(cons "q_abstand" 4000.0) (cons "q_breite" 1000.0))))
;; Luecke 4000-5000; die zweite (9000-10000) entfaellt am Reihenende
(tu-eq "lg-fuellung/quer-reihenstuecke" 4 (tu-lg-anzahl (car _erg) "reihe"))
(tu-eqf "lg-fuellung/quer-reihenlaenge" 18000.0 (tu-lg-summe (car _erg) "reihe"))
(tu-eq "lg-fuellung/quer-hauptgang-durch" 1 (tu-lg-anzahl (car _erg) "gang"))
(tu-eqf "lg-fuellung/quer-hauptganglaenge" 10000.0 (tu-lg-summe (car _erg) "gang"))
;; zwei Stuecke (unter und ueber dem Hauptgang), je 700 lang
(tu-eq "lg-fuellung/quergaenge" 2 (tu-lg-anzahl (car _erg) "quergang"))
(tu-eqf "lg-fuellung/quergang-laenge" 1400.0 (tu-lg-summe (car _erg) "quergang"))
;; L-Form: oberes Modul nur halb so lang
(setq _l '((0.0 0.0) (10000.0 0.0) (10000.0 2400.0) (5000.0 2400.0) (5000.0 4800.0) (0.0 4800.0)))
(setq _erg (lg-fuellung-berechnen _l (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2))))
(tu-eqf "lg-fuellung/l-form-reihenlaenge" 30000.0 (tu-lg-summe (car _erg) "reihe"))
(tu-eqf "lg-fuellung/l-form-ganglaenge" 15000.0 (tu-lg-summe (car _erg) "gang"))
;; Kontur kleiner als ein Modul -> nichts, Rest = ganze Breite
(setq _erg (lg-fuellung-berechnen '((0.0 0.0) (5000.0 0.0) (5000.0 2000.0) (0.0 2000.0))
(list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2))))
(tu-nil "lg-fuellung/zu-schmal" (car _erg))
(tu-eqf "lg-fuellung/zu-schmal-rest" 2000.0 (cadr _erg))
(tu-eqf "lg-kontur-richtung/waagrecht" 0.0 (lg-kontur-richtung _q))
(tu-eqf "lg-kontur-richtung/senkrecht" 90.0
(lg-kontur-richtung '((0.0 0.0) (1000.0 0.0) (1000.0 8000.0) (0.0 8000.0))))
;; --- Randgaenge (Himmelsrichtung, entlang der Konturkanten) ---
(tu-eq "lg-kompass/ost" "O" (lg-kompass 0.0))
(tu-eq "lg-kompass/nord" "N" (lg-kompass (/ pi 2.0)))
(tu-eq "lg-kompass/west" "W" (lg-kompass pi))
(tu-eq "lg-kompass/sued" "S" (lg-kompass (/ pi -2.0)))
(tu-eq "lg-kompass/diagonal-ns" "N" (lg-kompass (/ pi 4.0)))
(setq _rand '(("N" . 1000.0) ("O" . 1000.0) ("S" . 1000.0) ("W" . 1000.0)))
;; Rechteck, alle vier Seiten: N/S volle Laenge, O/W um 2 x 1000 gekuerzt
(setq _rs (lg-randgang-streifen _q 0.0 _rand 100.0))
(tu-eq "lg-randgang/rechteck-anzahl" 4 (length _rs))
(tu-eq "lg-randgang/rechteck-sued" "S" (car (car _rs)))
(tu-eqf "lg-randgang/rechteck-sued-mitte" '((0.0 500.0) (10000.0 500.0))
(list (cadr (car _rs)) (caddr (car _rs))))
(tu-eqf "lg-randgang/rechteck-ost-gekuerzt" '((9500.0 1000.0) (9500.0 5000.0))
(list (cadr (cadr _rs)) (caddr (cadr _rs))))
(setq _erg (lg-fuellung-berechnen _q (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2)
(cons "rand" _rand))))
(tu-eqf "lg-fuellung/rand-laenge" 28000.0 (tu-lg-summe (car _erg) "randgang"))
;; Stapel 1000..5000 -> ein Modul, Rest 1600; Reihen/Gang 1000..9000
(tu-eq "lg-fuellung/rand-module" 1 (caddr _erg))
(tu-eqf "lg-fuellung/rand-rest" 1600.0 (cadr _erg))
(tu-eqf "lg-fuellung/rand-erste-reihe" '("reihe" (1000.0 1350.0) (9000.0 1350.0) 700.0) (car (car _erg)))
(tu-eqf "lg-fuellung/rand-reihenlaenge" 16000.0 (tu-lg-summe (car _erg) "reihe"))
(tu-eqf "lg-fuellung/rand-ganglaenge" 8000.0 (tu-lg-summe (car _erg) "gang"))
(tu-eq "lg-fuellung/rand-am-ende" "randgang" (car (last (car _erg))))
;; RICHTUNG 90 Grad: Nord liegt auf der lokalen rechten Kante (Reihenende)
(setq _rs (lg-randgang-streifen _q (/ pi 2.0) '(("N" . 1000.0)) 100.0))
(tu-eq "lg-randgang/gedreht-anzahl" 1 (length _rs))
(tu-eqf "lg-randgang/gedreht-lage" '((9500.0 0.0) (9500.0 6000.0))
(list (cadr (car _rs)) (caddr (car _rs))))
(setq _erg (lg-fuellung-berechnen _q (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2)
(cons "rand" '(("N" . 1000.0))) (cons "richtung" (/ pi 2.0)))))
(tu-eqf "lg-fuellung/gedreht-reihenlaenge" 36000.0 (tu-lg-summe (car _erg) "reihe"))
(tu-eqf "lg-fuellung/gedreht-rest" 1200.0 (cadr _erg))
;; L-Form, Nord + Ost: Stufenkante bekommt den Nord-Randgang und wird
;; an der Innenecke um die Ost-Breite verlaengert; Ost-Gaenge an den
;; konvexen Ecken gekuerzt
(setq _rs (lg-randgang-streifen _l 0.0 '(("N" . 1000.0) ("O" . 1000.0)) 100.0))
(tu-eq "lg-randgang/l-anzahl" 4 (length _rs))
(tu-eq "lg-randgang/l-nordkanten" 2
(length (vl-remove-if-not '(lambda (s) (= (car s) "N")) _rs)))
(tu-eqf "lg-randgang/l-stufe-verlaengert" '((10000.0 1900.0) (4000.0 1900.0))
(list (cadr (nth 1 _rs)) (caddr (nth 1 _rs))))
(setq _erg (lg-fuellung-berechnen _l (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2)
(cons "rand" '(("N" . 1000.0) ("O" . 1000.0))))))
(tu-eqf "lg-fuellung/l-rand-laenge" 13800.0 (tu-lg-summe (car _erg) "randgang"))
(tu-eqf "lg-fuellung/l-reihenlaenge" 13000.0 (tu-lg-summe (car _erg) "reihe"))
(tu-eqf "lg-fuellung/l-ganglaenge" 4000.0 (tu-lg-summe (car _erg) "gang"))
;; Sperrrechteck (manueller Gang) quer durch die Reihen
(setq _erg (lg-fuellung-berechnen _q (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2)
(cons "sperr" '(((4000.0 0.0) (5000.0 0.0) (5000.0 6000.0) (4000.0 6000.0)))))))
(tu-eq "lg-fuellung/sperr-reihenstuecke" 8 (tu-lg-anzahl (car _erg) "reihe"))
(tu-eqf "lg-fuellung/sperr-reihenlaenge" 36000.0 (tu-lg-summe (car _erg) "reihe"))
(tu-eqf "lg-fuellung/sperr-ganglaenge" 18000.0 (tu-lg-summe (car _erg) "gang"))
;; Reststuecke unter min_reihe fallen weg, Gangstuecke ab min_gang bleiben
(setq _erg (lg-fuellung-berechnen _q (list (cons "gang" 1000.0) (cons "tiefe" 700.0) (cons "reihen" 2)
(cons "sperr" '(((0.0 0.0) (9700.0 0.0) (9700.0 6000.0) (0.0 6000.0)))))))
(tu-eq "lg-fuellung/sperr-reststueck" 0 (tu-lg-anzahl (car _erg) "reihe"))
(tu-eq "lg-fuellung/sperr-gangstueck" 2 (tu-lg-anzahl (car _erg) "gang"))
;; --- Schraffur je Belag, Gang-Blockname ---
(setq _tab (ssg-json-parse
"{\"spanplatte\": {\"muster\": \"ANSI31\", \"massstab\": 50}, \"hallenboden\": null}"))
(tu-eqf "lg-schraffur-vorgabe/spanplatte" '("ANSI31" 50.0 0.0) (lg-schraffur-vorgabe "spanplatte" _tab))
(tu-nil "lg-schraffur-vorgabe/hallenboden" (lg-schraffur-vorgabe "hallenboden" _tab))
(tu-nil "lg-schraffur-vorgabe/unbekannt" (lg-schraffur-vorgabe "beton" _tab))
(tu-nil "lg-schraffur-vorgabe/nil" (lg-schraffur-vorgabe nil _tab))
(tu-eq "lg-gang-blockname/belag" "LG_GANG_12000_1000_SPANPLATTE"
(lg-gang-blockname 12000.0 1000.0 "spanplatte"))
(tu-eq "lg-gang-blockname/ohne" "LG_GANG_12000_1000" (lg-gang-blockname 12000.0 1000.0 ""))
;; --- Geschosszuordnung ---
(setq _sq0 '((0.0 0.0 0.0) (100.0 0.0 0.0) (100.0 100.0 0.0) (0.0 100.0 0.0))
_sq3 '((0.0 0.0 3100.0) (100.0 0.0 3100.0) (100.0 100.0 3100.0) (0.0 100.0 3100.0)))
(tu-eq "lg-geschoss-punkt/einzeln" 1
(lg-geschoss-fuer-punkt-kern '(50.0 50.0 0.0) (list (list 1 0.0 _sq0))))
(tu-nil "lg-geschoss-punkt/aussen"
(lg-geschoss-fuer-punkt-kern '(150.0 50.0 0.0) (list (list 1 0.0 _sq0))))
;; 2D: beide Konturen auf Z=0 uebereinander -> nicht eindeutig
(tu-nil "lg-geschoss-punkt/2d-mehrdeutig"
(lg-geschoss-fuer-punkt-kern '(50.0 50.0 0.0) (list (list 1 0.0 _sq0) (list 2 3100.0 _sq0))))
;; 3D: Konturen auf ihrer Belagshoehe -> Z entscheidet
(setq _kand (list (list 1 0.0 _sq0) (list 2 3100.0 _sq3)))
(tu-eq "lg-geschoss-punkt/3d-oben" 2 (lg-geschoss-fuer-punkt-kern '(50.0 50.0 3200.0) _kand))
(tu-eq "lg-geschoss-punkt/3d-unten" 1 (lg-geschoss-fuer-punkt-kern '(50.0 50.0 100.0) _kand))
;; --- Geschoss kopieren ---
(tu-eq "lg-geschoss-aus-layer/regal" 2 (lg-geschoss-aus-layer "LG_Regal_G2"))
(tu-eq "lg-geschoss-aus-layer/12" 12 (lg-geschoss-aus-layer "LG_Reihe_G12"))
(tu-nil "lg-geschoss-aus-layer/ohne" (lg-geschoss-aus-layer "LG_Reihe"))
(tu-nil "lg-geschoss-aus-layer/0" (lg-geschoss-aus-layer "0"))
(tu-nil "lg-geschoss-aus-layer/suffix" (lg-geschoss-aus-layer "LG_X_G2a"))
(tu-nil "lg-geschoss-aus-layer/nil" (lg-geschoss-aus-layer nil))
;; 2D uebereinander: Kontur mehrdeutig, Layer entscheidet
(setq _kand2 (list (list 1 0.0 _sq0) (list 2 3100.0 _sq0)))
(tu-eq "lg-rack-geschoss/layer" 2 (lg-rack-geschoss "LG_Regal_G2" '(50.0 50.0 0.0) _kand2))
(tu-nil "lg-rack-geschoss/mehrdeutig" (lg-rack-geschoss "0" '(50.0 50.0 0.0) _kand2))
(tu-eq "lg-rack-geschoss/kontur" 1 (lg-rack-geschoss "0" '(50.0 50.0 0.0) (list (list 1 0.0 _sq0))))
;; Layer eines nicht vorhandenen Geschosses zaehlt nicht
(tu-eq "lg-rack-geschoss/layer-fremd" 1 (lg-rack-geschoss "LG_Regal_G7" '(50.0 50.0 0.0) (list (list 1 0.0 _sq0))))
(tu-eq "lg-kopie-belag/folgt" "spanplatte" (lg-kopie-belag "hallenboden" "hallenboden" "spanplatte"))
(tu-eq "lg-kopie-belag/eigen" "gitterrost" (lg-kopie-belag "gitterrost" "hallenboden" "spanplatte"))
(tu-nil "lg-kopie-belag/nil" (lg-kopie-belag nil "hallenboden" "spanplatte"))
(setq _w (lg-kopie-werte '(("ID" . "2A") ("GESCHOSS" . "1") ("LAYER" . "LG_Gang_G1") ("LAENGE" . "9000")
("BREITE_mm" . "1000") ("ART" . "gang") ("BELAG" . "hallenboden")
("BEREICH" . "3") ("QUELLE" . "manuell"))
2 5 "hallenboden" "spanplatte"))
(tu-eq "lg-kopie-werte/gang"
'(("GESCHOSS" . "2") ("BEREICH" . "5") ("BREITE_mm" . "1000") ("ART" . "gang")
("BELAG" . "spanplatte") ("QUELLE" . "manuell"))
_w)
(tu-eq "lg-kopie-werte/ohne-bereich" "" (cdr (assoc "BEREICH" (lg-kopie-werte '(("BEREICH" . "3")) 2 nil nil nil))))
(tu-eqf "lg-kopie-plan/2d"
'((1 3000.0 (0.0 0.0 0.0)) (2 6000.0 (0.0 0.0 0.0)))
(lg-kopie-plan 0.0 3000.0 2 0.0 0.0 nil))
(tu-eqf "lg-kopie-plan/versatz-3d"
'((1 6100.0 (20000.0 0.0 3000.0)) (2 9100.0 (40000.0 0.0 6000.0)))
(lg-kopie-plan 3100.0 3000.0 2 20000.0 0.0 T))
(tu-eqf "lg-kopie-abstand/unten" 3100.0 (lg-kopie-abstand-vorgabe 6200.0 3100.0 nil nil))
(tu-eqf "lg-kopie-abstand/lichte" 3000.0 (lg-kopie-abstand-vorgabe 0.0 nil 2750.0 250.0))
(tu-nil "lg-kopie-abstand/nichts" (lg-kopie-abstand-vorgabe 0.0 nil nil 250.0))
(tu-nil "lg-kopie-3d-p/2d" (lg-kopie-3d-p _kand2))
(tu-eq "lg-kopie-3d-p/3d" T (lg-kopie-3d-p _kand))
(tu-eq "lg-feld-pruefen/mm_vz" "-1500" (lg-feld-pruefen "mm_vz" "-1500"))
(tu-nil "lg-feld-pruefen/mm_vz-x" (lg-feld-pruefen "mm_vz" "abc"))
(tu-nil "lg-feld-pruefen/mm_vz-leer" (lg-feld-pruefen "mm_vz" ""))
;; --- Gang aus Rechteck ---
(tu-eqf "lg-gang-aus-ecken/quer"
'((0.0 500.0 0.0) (4000.0 500.0 0.0) 1000.0)
(lg-gang-aus-ecken '((0.0 0.0 0.0) (4000.0 0.0 0.0) (4000.0 1000.0 0.0) (0.0 1000.0 0.0))))
;; hochkant, Ecken gegen den Uhrzeigersinn ab oben rechts, Z uebernommen
(tu-eqf "lg-gang-aus-ecken/hoch"
'((600.0 5000.0 3100.0) (600.0 0.0 3100.0) 1200.0)
(lg-gang-aus-ecken '((1200.0 5000.0 3100.0) (0.0 5000.0 3100.0) (0.0 0.0 3100.0) (1200.0 0.0 3100.0))))
;; Breite auf ganze mm
(tu-eqf "lg-gang-aus-ecken/runden" 900.0
(caddr (lg-gang-aus-ecken '((0.0 0.0) (3000.0 0.0) (3000.0 899.6) (0.0 899.6)))))
;; Rundung verschiebt nur die Seite gegenueber der ersten Ecke
(tu-eqf "lg-gang-aus-ecken/rundung-erste-ecke-unten"
'((0.0 450.0 0.0) (3000.0 450.0 0.0) 900.0)
(lg-gang-aus-ecken '((0.0 0.0) (3000.0 0.0) (3000.0 899.6) (0.0 899.6))))
(tu-eqf "lg-gang-aus-ecken/rundung-erste-ecke-oben"
'((0.0 449.6 0.0) (3000.0 449.6 0.0) 900.0)
(lg-gang-aus-ecken '((0.0 899.6) (3000.0 899.6) (3000.0 0.0) (0.0 0.0))))
;; erste Ecke rechts: Linie beginnt an der rechten Querseite
(tu-eqf "lg-gang-aus-ecken/start-an-erster-ecke"
'((3000.0 450.0 0.0) (0.0 450.0 0.0) 900.0)
(lg-gang-aus-ecken '((3000.0 0.0) (0.0 0.0) (0.0 899.6) (3000.0 899.6))))
;; gedreht (UCS 90 Grad): laengere Seite bestimmt die Richtung
(tu-eqf "lg-gang-aus-ecken/gedreht"
'((-500.0 0.0 0.0) (-500.0 4000.0 0.0) 1000.0)
(lg-gang-aus-ecken '((0.0 0.0 0.0) (0.0 4000.0 0.0) (-1000.0 4000.0 0.0) (-1000.0 0.0 0.0))))
;; --- Zuschnitt durch von Hand gesetzte Objekte ---
;; Reihe 10000 x 700, Gang 1000 breit quer darueber -> zwei Reste
(tu-eqf "lg-linie-zuschnitt/quer" '((0.0 4500.0) (5500.0 10000.0))
(lg-linie-zuschnitt 10000.0 700.0
'(((4500.0 -2000.0) (5500.0 -2000.0) (5500.0 2000.0) (4500.0 2000.0))) 500.0))
;; Gang liegt nur an der Laengsseite an -> nichts zu schneiden
(tu-eq "lg-linie-zuschnitt/anliegend" 'unveraendert
(lg-linie-zuschnitt 10000.0 700.0
'(((0.0 350.0) (10000.0 350.0) (10000.0 1350.0) (0.0 1350.0))) 500.0))
(tu-eq "lg-linie-zuschnitt/weit-weg" 'unveraendert
(lg-linie-zuschnitt 10000.0 700.0
'(((20000.0 -500.0) (21000.0 -500.0) (21000.0 500.0) (20000.0 500.0))) 500.0))
(tu-nil "lg-linie-zuschnitt/ganz-ueberdeckt"
(lg-linie-zuschnitt 4000.0 700.0
'(((-100.0 -500.0) (4100.0 -500.0) (4100.0 500.0) (-100.0 500.0))) 500.0))
;; Rest 300 < Mindestlaenge 500 faellt weg
(tu-nil "lg-linie-zuschnitt/rest-zu-kurz"
(lg-linie-zuschnitt 10000.0 700.0
'(((300.0 -500.0) (10000.0 -500.0) (10000.0 500.0) (300.0 500.0))) 500.0))
(tu-eq "lg-quelle-rang" '(2 1 0 0)
(mapcar 'lg-quelle-rang '("manuell" "auto_geaendert" "auto" nil)))
;; --- goh_rack-Erkennung: Parameterwerte, Namen, Transformationen ---
(tu-eqf "lg-zahl-einheit/mm" 700.0 (lg-zahl-einheit "700 mm"))
(tu-eqf "lg-zahl-einheit/cm" 718.752 (lg-zahl-einheit "71.8752 cm"))
(tu-eqf "lg-zahl-einheit/m" 12900.0 (lg-zahl-einheit "12,9 m"))
(tu-eqf "lg-zahl-einheit/ohne" 2.0 (lg-zahl-einheit "2"))
(tu-eqf "lg-zahl-einheit/zahl" 3.0 (lg-zahl-einheit 3))
(tu-nil "lg-zahl-einheit/km" (lg-zahl-einheit "5 km"))
(tu-nil "lg-zahl-einheit/text" (lg-zahl-einheit "abc"))
(tu-nil "lg-zahl-einheit/leer" (lg-zahl-einheit ""))
(tu-nil "lg-zahl-einheit/nil" (lg-zahl-einheit nil))
(tu-eqf "lg-racks-auswerten"
'((1 2 3000.0 6000.0) (2 1 500.0 1500.0) (nil 2 700.0 1400.0))
(lg-racks-auswerten '((2 500.0 3.0) (nil 700.0 2.0) (1 1000.0 2.0)
(1 2000.0 2.0) (nil nil 2.0))))
(tu-nil "lg-racks-auswerten/leer" (lg-racks-auswerten nil))
(tu-eq "lg-name-brauchbar-p/name" T (lg-name-brauchbar-p "goh_rack"))
(tu-nil "lg-name-brauchbar-p/anonym" (lg-name-brauchbar-p "*U29"))
(tu-nil "lg-name-brauchbar-p/leer" (lg-name-brauchbar-p ""))
;; Einfuegung bei (100,0), 90 Grad, x-Skalierung 2: lokales (1,0,0) -> (100,2,0)
(tu-eqf "lg-insert-tf/drehung-skalierung" '(100.0 2.0 0.0)
(lg-tf-pkt (lg-insert-tf (list '(2 . "TU_KEIN_BLOCK") '(10 100.0 0.0 0.0)
(cons 50 (/ pi 2.0)) '(41 . 2.0))
0 0)
'(1.0 0.0 0.0)))
;; MINSERT-Spalte 2 bei Spaltenabstand 3000, gedreht um 90 Grad -> entlang +Y
(tu-eqf "lg-insert-tf/minsert" '(0.0 6000.0 0.0)
(car (lg-insert-tf (list '(2 . "TU_KEIN_BLOCK") '(10 0.0 0.0 0.0) (cons 50 (/ pi 2.0))
'(70 . 3) '(44 . 3000.0))
2 0)))
;; verschachtelt: Container bei (1000,0) um 90 Grad, Kind bei lokal (0,2000)
(tu-eqf "lg-tf-verketten" '(-1000.0 0.0 0.0)
(car (lg-tf-verketten
(lg-insert-tf (list '(2 . "TU_KEIN_BLOCK") '(10 1000.0 0.0 0.0) (cons 50 (/ pi 2.0))) 0 0)
(lg-insert-tf (list '(2 . "TU_KEIN_BLOCK") '(10 0.0 2000.0 0.0)) 0 0))))
(tu-nil "lg-gang-aus-ecken/entartet"
(lg-gang-aus-ecken '((0.0 0.0) (4000.0 0.0) (4000.0 0.0) (0.0 0.0))))
;; --- JSON-Schreiber ---
(tu-eq "lg-json-wert/nil" "null" (lg-json-wert nil))
(tu-eq "lg-json-wert/t" "true" (lg-json-wert T))
(tu-eq "lg-json-wert/int" "3" (lg-json-wert 3))
(tu-eq "lg-json-wert/real" "2.5" (lg-json-wert 2.5))
(tu-eq "lg-json-wert/rund" "1000" (lg-json-wert 1000.0))
(tu-eq "lg-json-wert/str" "\"a\\\"b\"" (lg-json-wert "a\"b"))
(tu-eq "lg-json-wert/roh" "[1]" (lg-json-wert (list 'ROH "[1]")))
(tu-eq "lg-json-obj" "{\"a\": 1, \"b\": \"x\"}" (lg-json-obj '(("a" . 1) ("b" . "x"))))
(tu-eq "lg-json-ebenen" "[{\"schienenhoehe_mm\": 2300, \"warenlaenge_mm\": 1200}]"
(cadr (lg-json-ebenen "2300/1200")))
;; --- Regelprofil-Aufloesung ---
(tu-eq "lg-bedingung/leer" 'ja (lg-bedingung-pruefen nil nil))
(tu-eq "lg-bedingung/gleich" 'ja (lg-bedingung-pruefen "brandgefaehrdung = normal"
'(("brandgefaehrdung" . "normal"))))
(tu-eq "lg-bedingung/ungleich" 'nein (lg-bedingung-pruefen "brandgefaehrdung = normal"
'(("brandgefaehrdung" . "erhoeht_ohne_loeschanlage"))))
(tu-eq "lg-bedingung/fehlt" 'unbekannt (lg-bedingung-pruefen "brandgefaehrdung = normal" nil))
(tu-eq "lg-bedingung/groesser" 'ja (lg-bedingung-pruefen "absturzhoehe > 12000" '(("absturzhoehe" . 13000))))
(tu-eq "lg-bedingung/kleinergl" 'nein (lg-bedingung-pruefen "absturzhoehe <= 12000" '(("absturzhoehe" . "13000"))))
;; min: strengster = groesster, max: strengster = kleinster
(setq _regel (ssg-json-parse
"{\"id\": \"s\", \"art\": \"min\", \"werte\": [{\"wert\": 590, \"quelle\": \"A\"}, {\"wert\": 600, \"quelle\": \"B\"}]}"))
(tu-eq "lg-regel/min-strengster" 600 (car (lg-regel-aufloesen _regel nil)))
(tu-eq "lg-regel/min-gewinner" "B" (ssg-json-get (cadr (lg-regel-aufloesen _regel nil)) "quelle"))
(setq _regel (ssg-json-parse
"{\"id\": \"s\", \"art\": \"max\", \"werte\": [{\"wert\": 650, \"quelle\": \"A\"}, {\"wert\": 660, \"quelle\": \"B\"}]}"))
(tu-eq "lg-regel/max-strengster" 650 (car (lg-regel-aufloesen _regel nil)))
(setq _regel (ssg-json-parse (strcat
"{\"id\": \"f\", \"art\": \"max\", \"werte\": ["
"{\"wert\": 35000, \"bedingung\": \"brandgefaehrdung = normal\"},"
"{\"wert\": 25000, \"bedingung\": \"brandgefaehrdung = erhoeht_ohne_loeschanlage\"}]}")))
(tu-eq "lg-regel/bedingung-normal" 35000
(car (lg-regel-aufloesen _regel '(("brandgefaehrdung" . "normal")))))
(tu-eq "lg-regel/bedingung-erhoeht" 25000
(car (lg-regel-aufloesen _regel '(("brandgefaehrdung" . "erhoeht_ohne_loeschanlage")))))
(tu-eq "lg-regel/bedingung-fehlt-konservativ" 25000 (car (lg-regel-aufloesen _regel nil)))
(tu-nil "lg-regel/nichts-zutreffend"
(lg-regel-aufloesen (ssg-json-parse
"{\"id\": \"x\", \"art\": \"max\", \"werte\": [{\"wert\": 1, \"bedingung\": \"a = b\"}]}")
'(("a" . "c"))))
(setq _regel (ssg-json-parse (strcat
"{\"id\": \"b\", \"art\": \"min\", \"werte\": [{\"tabelle\": [[5, 875], [20, 1000], [200, 1200]],"
" \"tabellen_schluessel\": \"personen\"}]}")))
(tu-eq "lg-regel/tabelle-15" 1000 (car (lg-regel-aufloesen _regel '(("personen" . 15)))))
(tu-eq "lg-regel/tabelle-5" 875 (car (lg-regel-aufloesen _regel '(("personen" . 5)))))
(tu-eq "lg-regel/tabelle-ueber" 1200 (car (lg-regel-aufloesen _regel '(("personen" . 500)))))
(tu-eq "lg-regel/tabelle-fehlt" 1200 (car (lg-regel-aufloesen _regel nil)))
;; Vererbung: Kind ersetzt Regel b, ergaenzt c, Kopf vom Kind
(setq _prof (lg-profil-mischen
(ssg-json-parse "{\"land\": \"DE\", \"regeln\": [{\"id\": \"a\", \"x\": 1}, {\"id\": \"b\", \"x\": 1}]}")
(ssg-json-parse "{\"land\": \"SK\", \"regeln\": [{\"id\": \"b\", \"x\": 2}, {\"id\": \"c\", \"x\": 3}]}")))
(tu-eq "lg-profil-mischen/ids" '("a" "b" "c")
(mapcar '(lambda (r) (ssg-json-get r "id")) (ssg-json-get _prof "regeln")))
(tu-eq "lg-profil-mischen/ersetzt" 2 (ssg-json-get (lg-regel-finden _prof "b") "x"))
(tu-eq "lg-profil-mischen/kopf" "SK" (ssg-json-get _prof "land"))
;; echte Profile (wie tu-tests-json gegen die Geruest-Daten): SK erbt DE
(if (and (lg-regelwerk-pfad "SK") (findfile (lg-regelwerk-pfad "SK")))
(progn
(setq _prof (lg-profil-laden-roh "SK" nil))
(tu-eq "lg-profil/sk-erbt-de" 190
(car (lg-regel-aufloesen (lg-regel-finden _prof "treppe.steigung_max") nil)))
(tu-eq "lg-profil/laufhoehe" 3000
(car (lg-regel-aufloesen (lg-regel-finden _prof "treppe.laufhoehe_max") nil)))
(tu-eq "lg-profil/schrittmass-schnittmenge" 600
(car (lg-regel-aufloesen (lg-regel-finden _prof "treppe.schrittmass_min") nil)))
(tu-eq "lg-profil/gelaender-14122" 1100
(car (lg-regel-aufloesen (lg-regel-finden _prof "gelaender.hoehe_min")
'(("absturzhoehe" . 3100)))))
(tu-nil "lg-profil/sk-ungepflegt" (ssg-json-get _prof "gepflegt")))
(tu-record nil "lg-profil/dateien" "cfg/regelwerk/SK.json nicht gefunden (DXFM_CFG?)"))
)
)
)
(defun c:TEST_UNIT ( / out-dir report)
(tu-reset)
(princ "\n================================================================")
@@ -2289,7 +2724,7 @@
"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")
"tu-tests-werkzeuge" "tu-tests-poly" "tu-tests-lager")
(tu-run-block fn))
(princ "\n\n----------------------------------------------------------------")