Files
dxfmakros/Lisp/GeruestInsert.lsp
s.hensch ea0e46a441 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).
2026-10-13 11:02:23 +02:00

2604 lines
102 KiB
Common Lisp

;; ============================================================
;; GeruestInsert.lsp - Geruest-Baugruppen konfigurieren und einfuegen
;;
;; Liest data/json/geruest.json (rekursiv per ssg-json-laden), laesst den
;; Nutzer eine Basis-Variante, Anbauteile fuer die Befestigung (oben und
;; unten zusammen, Gesamtanzahl je Teil) und Sonstiges, Laenge und Farbe
;; waehlen, und fuegt das Ergebnis als Block GR_<id>_<Zeitstempel> ein.
;; Rohr-Geruest-Traeger (GERUEST_TRAEGER) lesen data/json/geruest_traeger.json.
;;
;; "anbaugruppen" hat zwei Listen: "befestigung" (eine Spalte "Befestigung
;; oben und unten", die Anzahl ist die Gesamtanzahl) und "sonstiges" (z.B.
;; Haltearme, eigene Zeile darunter; geruest-anbau-liste). Unter jedem Teil
;; zeigt der Dialog seine Artikelnummer(n).
;;
;; Das Dialog-Layout steht als Vorlage in dcl/geruest.dcl bzw.
;; dcl/geruest_traeger.dcl; nur die datenabhaengigen Bloecke (Radiobutton je
;; Variante, Mengenfeld + Ankreuzfeld je Anbauteil) werden zur Laufzeit an
;; //@NAME@-Platzhaltern eingesetzt (geruest-dcl-schreiben,
;; ssg-dcl-vorlage-fuellen) - DCL kann Tiles weder zur Laufzeit erzeugen noch
;; ausblenden.
;;
;; Konfigurationen kommen aus zwei getrennten Quellen: der gepflegten
;; Bibliothek in geruest.json und den eigenen in cfg/geruest_user.json.
;;
;; Der Block wird - wie KREISEL_n/VF_n - selbst zusammengebaut: die Dummy-DWG
;; aus data/geruest liefert nur die Geometrie, das Attribut-Schema kommt aus
;; *geruest-attrib-defs*. Damit kann kein Attribut still verlorengehen, wenn
;; die Dummy-DWG einen Tag nicht kennt (ssg-attrib-set-on schreibt nur Tags,
;; die als ATTDEF existieren).
;;
;; Zusaetzlich wird jede ausgewaehlte Artikelnummer (Basis, oben, unten,
;; sonstiges, inkl. aufgeloester Gruppen), fuer die data/geruest/<artnr>.dwg
;; existiert, einmal bei 0,0,0 mit in den Block eingefuegt (geruest-teile-dwgs,
;; Anbauteile im "Winkel Platte" gedreht) - reine
;; Geometrie als visuelle Rueckmeldung, die Attribute bleiben davon unberuehrt.
;; Teile ohne DWG werden still uebergangen.
;;
;; TEILEFARBE ist eine kommagetrennte Liste parallel zu ARTINR: jedes Teil mit
;; "farbig": true in geruest.json bekommt die im Dialog gewaehlte Farbe, alle
;; anderen einen leeren Eintrag (geruest-teile-paare/geruest-farbliste).
;;
;; Das GR_-Praefix ist nicht kosmetisch: SSG_BLOCKEDIT haelt jeden Block mit
;; TEILEART-Attribut fuer ein Omniflo-Element (SSG_LIB_Commands.lsp). Der
;; Doppelklick-Dispatcher erkennt Geruest-Bloecke an diesem Praefix.
;;
;; Jeder Bereich (Rohrabhaenger, Saeulen, Wandbefestigung) hat seinen eigenen
;; Menuebefehl und Dialog; eine Umschaltung im Dialog gibt es nicht. GERUEST
;; ohne Bereich zeigt nur einen Hinweis.
;;
;; Befehle: GERUEST_SAEULEN, GERUEST_ROHRABHAENGER, GERUEST_WANDBEFESTIGUNG,
;; GERUEST_TRAEGER, GERUEST_TRAEGER_BESCHRIFTEN
;; ============================================================
(vl-load-com)
;; Attribut-Schema des Wrapper-Blocks. Die Dummy-DWG darf eigene Tags
;; mitbringen - die bleiben erhalten (ssg-attrib-merge).
;; "ID" (leer vorbelegt, wie bei Kreisel/VF_n/GF_n) ist Voraussetzung dafuer,
;; dass ssg-id-check-all dem Block ueberhaupt eine ID zuweisen kann -
;; ssg-attrib-set-on schreibt nur auf ein bereits vorhandenes ATTDEF, siehe
;; oben. Ohne diesen Eintrag bliebe ID fuer immer leer (csv:read-attribs
;; exportiert dann eben kein "ID"-Feld statt eines Werts).
(if (null *geruest-attrib-defs*)
(setq *geruest-attrib-defs*
'(("ID" "")
("ARTINR" "")
("LAENGE" "")
("BEZEICHNUNG" "")
("TEILEART" "")
("TEILEFARBE" "")
("LAYER" "")
)
)
)
;; Rohr-Geruest-Traeger: dasselbe Schema plus die beiden Laengenabzuege und
;; VARIANTE (Name der Geometrie-Variante). Bewusst ein eigenes Schema - die
;; anderen GR_-Bloecke behalten ihres. LAENGE ist beim Traeger die
;; NETTO-Laenge (Strecke minus Abzuege). VARIANTE macht den Block
;; selbstbeschreibend: GERUEST_TRAEGER_BESCHRIFTEN findet darueber die
;; Formatierung der Beschriftung, ohne den Dialog zu kennen.
;; Ohne null-Guard: ein Neuladen der Datei soll ein erweitertes Schema sofort
;; wirksam machen.
(setq *geruest-traeger-attrib-defs*
(append *geruest-attrib-defs*
'(("LAENGENABZUGANFANG" "")
("LAENGENABZUGENDE" "")
("VARIANTE" "")))
)
;; XDATA-App der Traeger-Beschriftung: Gruppe 1005 = Handle des Traeger-Blocks.
(setq *geruest-beschr-app* "SSG_GR_BESCHR")
(if (null *geruest-block-counter*) (setq *geruest-block-counter* 0))
;; ============================================================
;; DATEN
;; ============================================================
(defun geruest-data-verzeichnis ( / )
(cond
((getenv "DXFM_DATA") (vl-string-translate "\\" "/" (getenv "DXFM_DATA")))
((getenv "DXFMAKRO")
(strcat (vl-string-translate "\\" "/" (getenv "DXFMAKRO")) "/data"))
)
)
;; Pfad einer Datendatei unter data/json, z.B. "geruest.json".
(defun geruest-json-pfad (datei / basis)
(if (setq basis (geruest-data-verzeichnis))
(strcat basis "/json/" datei)
)
)
(defun geruest-dummy-pfad (datei / basis)
(if (and datei (setq basis (geruest-data-verzeichnis)))
(strcat basis "/geruest/" datei)
)
)
;; Datendatei unter data/json einmal je Zeichnungssitzung laden und im
;; globalen Symbol cache-sym ablegen. Rueckgabe: die geparsten Daten oder nil
;; (mit Meldung).
(defun geruest-json-laden-cache (datei cache-sym / pfad daten)
(if (null (eval cache-sym))
(progn
(setq pfad (geruest-json-pfad datei))
(cond
((null pfad) (princ (ssg-text "gr-fehler-datenpfad")))
((null (findfile pfad)) (princ (ssg-textf "gr-fehler-json-fehlt" (list pfad))))
(T
(setq daten (ssg-json-laden pfad))
(set cache-sym daten)
(if (null daten)
(princ (ssg-textf "gr-fehler-json-lesbar" (list pfad)))
)
)
)
)
)
(eval cache-sym)
)
;; Baugruppen des GERUEST-Dialogs (Rohrabhaenger, Saeulen, Wandbefestigung).
(defun geruest-daten ( / )
(geruest-json-laden-cache "geruest.json" '*geruest-daten*)
)
;; Traeger-Baugruppen mit Linien-Logik (Rohr-Geruest-Traeger, spaeter z.B.
;; Gattertraeger) - bewusst eine eigene Datei: eigener Dialog, Laenge aus der
;; Zeichnung, Streben/Muffe/Beschriftung statt Anbaugruppen/Konfigurationen.
(defun geruest-traeger-daten ( / )
(geruest-json-laden-cache "geruest_traeger.json" '*geruest-traeger-daten*)
)
;; Omniflo-Fahrstrecken (AP 110, APG 110, AP 60, ST, STL) - dieselbe
;; Linien-Logik wie der Traeger (Dialog, Laenge aus der Zeichnung,
;; Muffen-Checkbox, Beschriftung), aber eigene Datei und eigener Dialog.
(defun geruest-fahrstrecke-daten ( / )
(geruest-json-laden-cache "omniflo_fahrstrecke.json" '*geruest-fahrstrecke-daten*)
)
;; Anzeigename einer Baugruppe fuer den Dialogtitel. Bewusst das
;; ASCII-Feld "titel" und nicht der aeussere JSON-Schluessel - der traegt
;; Umlaute ("Rohrabhänger", "Säulen").
(defun geruest-titel (bg / t1)
(setq t1 (ssg-json-get bg "titel"))
(if (and t1 (= (type t1) 'STR) (/= t1 ""))
t1
(ssg-json-get bg "teileart")
)
)
;; Wert einer Variante mit Rueckfall auf die Baugruppe. Genutzt fuer
;; "teileart" (die Saeulen-Varianten unterscheiden 1 Zoll von 2 Zoll, was im
;; Export sichtbar bleiben muss) und "dummy" (eine Variante darf eine eigene
;; DWG mitbringen, sobald es sie gibt).
(defun geruest-variante-wert (variante bg schluessel / w)
(setq w (if variante (ssg-json-get variante schluessel)))
(if (and w (/= w "")) w (ssg-json-get bg schluessel))
)
;; Anbauteil-Liste einer Baugruppe. "anbaugruppen" ist ein Objekt mit zwei
;; Listen: "befestigung" (erscheint in den Spalten oben UND unten) und
;; "sonstiges" (eigene Zeile, z.B. Haltearme - weder oben noch unten).
;; schluessel = "befestigung" oder "sonstiges". Ein altes flaches Array gilt
;; als "befestigung". Unterscheidung: ein Objekt wird zur Alist mit
;; String-Schluesseln, ein Array von Objekten zur Liste von Alists.
(defun geruest-anbau-liste (bg schluessel / anb)
(setq anb (ssg-json-get bg "anbaugruppen"))
(cond
((null anb) nil)
((and (listp (car anb)) (= (type (car (car anb))) 'STR))
(ssg-json-get anb schluessel))
((= schluessel "befestigung") anb)
)
)
;; Pfad der Dummy-DWG fuer den Block. Ein "dummy" in der Geometrie-Variante
;; wird bevorzugt; fehlt die Datei unter data/geruest, wird gemeldet und auf
;; den "dummy" der Baugruppe zurueckgefallen. Rueckgabe: (datei . pfad) -
;; pfad ist nil, wenn kein Datenverzeichnis bekannt ist; ob die
;; Rueckfall-Datei existiert, prueft der Aufrufer.
(defun geruest-dummy-wahl (variante bg / v pfad)
(setq v (if variante (ssg-json-get variante "dummy")))
(if (and v (= (type v) 'STR) (/= v "")
(setq pfad (geruest-dummy-pfad v))
(findfile pfad))
(cons v pfad)
(progn
(if (and v (= (type v) 'STR) (/= v ""))
(princ (ssg-textf "gr-fehler-dummy-variante"
(list (if pfad pfad v)
(if (ssg-json-get bg "dummy")
(ssg-json-get bg "dummy")
"-"))))
)
(setq v (ssg-json-get bg "dummy"))
(cons v (geruest-dummy-pfad v))
)
)
)
;; Teile-Liste ((pfad . rot) ...) ohne die Eintraege, deren Pfad (ohne
;; Gross/Klein) gleich pfad ist. Damit landet eine Teile-DWG, die zugleich der
;; Dummy ist, nicht doppelt im Block.
(defun geruest-ohne-pfad (pfad liste)
(if pfad
(vl-remove-if '(lambda (e) (= (strcase (car e)) (strcase pfad))) liste)
liste
)
)
;; ============================================================
;; TEILE AUSWERTEN
;; ============================================================
;; Anzeigename eines Eintrags: benannte Gruppe ("name") oder Einzelteil
;; ("artikel").
(defun geruest-eintrag-name (eintrag / n)
(setq n (ssg-json-get eintrag "name"))
(if (and n (= (type n) 'STR) (/= n ""))
n
(ssg-json-get eintrag "artikel")
)
)
;; Zwei Mengenfelder im JSON, bewusst verschieden benannt:
;; "stueckzahl" = wie oft das Teil TATSAECHLICH geschrieben wird - je
;; Einbau (Variante) bzw. je Satz/Anbauteil. Fehlt = 1.
;; "anzahl_vorgabe" = NUR die Vorbelegung des Anzahl-Feldes im Dialog
;; (Anbaugruppen). Fehlt = 1.
;; Ein Anbauteil wird Anzahl (Dialog) x stueckzahl mal geschrieben; bei einer
;; Gruppe ("teile") ist die Anzahl die Zahl der Saetze.
(defun geruest-positiv-int (w / )
(if (and w (numberp w) (> w 0)) (fix w) 1)
)
(defun geruest-stueckzahl (eintrag)
(geruest-positiv-int (ssg-json-get eintrag "stueckzahl"))
)
(defun geruest-anzahl-vorgabe (eintrag)
(geruest-positiv-int (ssg-json-get eintrag "anzahl_vorgabe"))
)
;; Sivas-ID als String. Im JSON stehen rein numerische IDs als Zahl
;; (Projektkonvention), zusammengesetzte Codes waeren Strings.
(defun geruest-id-string (wert)
(cond
((null wert) nil)
((= (type wert) 'STR) wert)
((= (type wert) 'INT) (itoa wert))
((= (type wert) 'REAL) (rtos wert 2 0))
)
)
(defun geruest-wiederholen (wert n / erg i)
(setq erg nil i 0)
(if wert
(while (< i n)
(setq erg (cons wert erg))
(setq i (1+ i))
)
)
erg
)
;; Alle Teile eines Eintrags als ((artnr . farbig) ...), jedes gemaess
;; "stueckzahl" wiederholt. Benannte Gruppen ("teile") werden rekursiv aufgeloest.
;; farbig = darf das Teil die Baugruppenfarbe tragen ("farbig" im JSON). Eine
;; Gruppe mit "farbig": true vererbt das an ihre Teile (erbe); ein Teil kann
;; die Vererbung nicht per false aufheben - false und "fehlt" sind beide nil.
;; ARTINR und TEILEFARBE entstehen aus DIESEM einen Durchlauf, damit beide
;; Listen garantiert gleich lang und gleich sortiert sind.
(defun geruest-teile-paare (eintrag erbe / erg teile farbig)
(setq erg nil)
(setq farbig (if (or erbe (ssg-json-get eintrag "farbig")) T nil))
(setq teile (ssg-json-get eintrag "teile"))
(if teile
(foreach teil teile
(setq erg (append erg (geruest-teile-paare teil farbig)))
)
(setq erg (mapcar '(lambda (nr) (cons nr farbig))
(geruest-wiederholen
(geruest-id-string (ssg-json-get eintrag "sivas_id"))
(geruest-stueckzahl eintrag))))
)
erg
)
;; Alle Artikelnummern eines Eintrags, jede gemaess "stueckzahl" wiederholt.
(defun geruest-artikelnummern (eintrag)
(mapcar 'car (geruest-teile-paare eintrag nil))
)
;; TEILEFARBE-Liste zu Teile-Paaren: die Baugruppenfarbe fuer jedes farbige
;; Teil, "" fuer alle anderen.
(defun geruest-farbliste (paare farbe)
(mapcar '(lambda (p) (if (cdr p) farbe "")) paare)
)
;; Liste ohne Duplikate, Reihenfolge des ersten Auftretens bleibt erhalten.
(defun geruest-eindeutig (lst / erg)
(setq erg nil)
(foreach e lst
(if (not (member e erg)) (setq erg (cons e erg)))
)
(reverse erg)
)
;; Liste ohne die ersten n Elemente.
(defun geruest-rest-liste (lst n)
(repeat n (setq lst (cdr lst)))
lst
)
;; Teile-DWGs zu einer Liste von Artikelnummern: data/geruest/<artnr>.dwg,
;; soweit vorhanden. Jede Nummer nur einmal - alle Teile sitzen bei 0,0,0,
;; eine Mehrfach-Stueckzahl waere visuell nicht unterscheidbar. Fehlende
;; DWGs werden still uebergangen.
(defun geruest-teile-dwgs (artnr-liste / erg pfad)
(setq erg nil)
(foreach nr (geruest-eindeutig artnr-liste)
(if (and nr
(setq pfad (geruest-dummy-pfad (strcat nr ".dwg")))
(findfile pfad))
(setq erg (cons pfad erg))
)
)
(reverse erg)
)
;; Teile-DWGs mit Drehwinkel: ((pfad . rot) ...). Die Basis-Teile bleiben
;; ungedreht, die Anbauteile (oben/unten/sonstiges) bekommen den "Winkel
;; Platte" (Grad, Drehung um Z am Blockursprung). Ein Teil, das schon in der
;; Basis steckt, wird nicht ein zweites Mal als Anbauteil eingefuegt.
(defun geruest-teile-dwgs-rot (basis-pfade anbau-pfade winkel / erg)
(setq erg (mapcar '(lambda (p) (cons p 0.0)) basis-pfade))
(foreach p anbau-pfade
(if (not (assoc p erg))
(setq erg (append erg (list (cons p (float winkel)))))
)
)
erg
)
;; "Winkel Platte" im Dialog: Tile-Index ("0"/"1") <-> Grad (0/90).
;; Konfigurationen speichern den echten Winkel, nicht den Listenindex.
(setq *geruest-winkel-werte* '(0 90))
(defun geruest-winkel-aus-index (idx-str / w)
(setq w (nth (atoi idx-str) *geruest-winkel-werte*))
(if w w 0)
)
(defun geruest-winkel-index (winkel / i idx)
(setq i 0 idx 0)
(foreach w *geruest-winkel-werte*
(if (and (numberp winkel) (equal (float w) (float winkel) 1e-6)) (setq idx i))
(setq i (1+ i))
)
(itoa idx)
)
;; ============================================================
;; DIALOG
;; ============================================================
;; Reine Ziffernfolge? atoi allein taugt als Pruefung nicht - "abc" liefert
;; dort still 0.
(defun geruest-zahl-p (s / i c ok)
(setq ok (> (strlen s) 0) i 1)
(while (and ok (<= i (strlen s)))
(setq c (ascii (substr s i 1)))
(if (or (< c 48) (> c 57)) (setq ok nil))
(setq i (1+ i))
)
ok
)
;; Eingabe aus einem Mengenfeld auswerten.
;; Leer -> Vorgabewert aus dem JSON, gueltig -> Zahl >= 1, sonst nil.
(defun geruest-anzahl-wert (s vorgabe)
(setq s (ssg-cfg-trim (if s s "")))
(cond
((= s "") vorgabe)
((not (geruest-zahl-p s)) nil)
((< (atoi s) 1) nil)
(T (atoi s))
)
)
;; Zahl aus dem JSON als Anzeigetext (fehlender Wert -> "").
(defun geruest-zahl-string (w)
(cond
((null w) "")
((= (type w) 'STR) w)
((= (type w) 'INT) (itoa w))
((= (type w) 'REAL) (rtos w 2 0))
(T "")
)
)
;; Teile eines Anbauteils bei der im Dialog gewaehlten Anzahl: der Eintrag
;; (Einzelteil mit seiner "stueckzahl" bzw. benannte Gruppe als kompletter
;; Satz) anzahl-mal. Die Stueckzahlen INNERHALB eines Satzes bleiben erhalten
;; (ein Schraubenset bleibt 1 Scheibe + 2 Muttern + 1 Schraube je Satz).
(defun geruest-teile-paare-n (eintrag anzahl / einmal erg i)
(setq einmal (geruest-teile-paare eintrag nil))
(setq erg nil i 0)
(while (< i anzahl)
(setq erg (append erg einmal))
(setq i (1+ i))
)
erg
)
(defun geruest-artikelnummern-n (eintrag anzahl)
(mapcar 'car (geruest-teile-paare-n eintrag anzahl))
)
;; Index eines Eintrags (Variante oder Anbauteil) ueber seinen Anzeigenamen.
(defun geruest-name-index (name liste / i idx)
(setq i 0 idx nil)
(foreach e liste
(if (and (null idx) (equal (geruest-eintrag-name e) name)) (setq idx i))
(setq i (1+ i))
)
idx
)
;; Schluessel in einer Alist setzen oder anhaengen.
(defun geruest-alist-setzen (alist key wert)
(if (assoc key alist)
(subst (cons key wert) (assoc key alist) alist)
(append alist (list (cons key wert)))
)
)
;; n-tes Element ersetzen - AutoLISP kann nth nicht schreibend nutzen.
(defun geruest-liste-setzen (lst idx wert / erg i)
(setq erg nil i 0)
(foreach e lst
(setq erg (cons (if (= i idx) wert e) erg))
(setq i (1+ i))
)
(reverse erg)
)
(defun geruest-nil-liste (n / erg i)
(setq erg nil i 0)
(while (< i n) (setq erg (cons nil erg)) (setq i (1+ i)))
erg
)
;; ============================================================
;; KONFIGURATIONEN
;; ============================================================
;; Zwei Quellen: die gepflegte Bibliothek in data/json/geruest.json
;; (schreibgeschuetzt) und die eigenen Zusammenstellungen in
;; cfg/geruest_user.json. Beide werden NICHT vermischt - Benutzerdaten in der
;; versionierten Datendatei wuerden bei jedem Git-Update kollidieren.
(defun geruest-user-pfad ( / )
(cond
((getenv "DXFM_CFG")
(strcat (vl-string-translate "\\" "/" (getenv "DXFM_CFG"))
"/geruest_user.json"))
((getenv "DXFMAKRO")
(strcat (vl-string-translate "\\" "/" (getenv "DXFMAKRO"))
"/cfg/geruest_user.json"))
)
)
;; Benutzerdatei laden (Baugruppen-id -> Liste von Konfigurationen).
;; Eine fehlende Datei ist kein Fehler - sie entsteht beim ersten Speichern.
(defun geruest-user-daten ( / pfad)
(if (null *geruest-user-daten*)
(progn
(setq pfad (geruest-user-pfad))
(if (and pfad (findfile pfad))
(setq *geruest-user-daten* (ssg-json-laden pfad))
)
)
)
*geruest-user-daten*
)
;; Alle Konfigurationen einer Baugruppe, Bibliothek zuerst.
;; Eintrag: (benutzer-p . json-objekt)
(defun geruest-konfigs (bg / erg)
(setq erg nil)
(foreach k (ssg-json-get bg "konfigurationen")
(setq erg (append erg (list (cons nil k))))
)
(foreach k (ssg-json-get (geruest-user-daten) (ssg-json-get bg "id"))
(setq erg (append erg (list (cons T k))))
)
erg
)
;; Anzeigename; eigene Eintraege mit * markiert, damit erkennbar ist, was
;; geloescht werden kann.
(defun geruest-konfig-name (k)
(strcat (if (car k) "* " "") (ssg-json-get (cdr k) "name"))
)
;; Listenindex einer Konfiguration im popup_list (0 = "<keine>", die
;; Konfigurationen beginnen deshalb bei 1). 0, wenn nicht gefunden.
;; Eine eigene Konfiguration gewinnt vor einer gleichnamigen aus der
;; Bibliothek - gesucht wird nach dem Speichern, und gespeichert wird immer
;; eine eigene.
(defun geruest-konfig-index (name / i idx bib)
(setq i 1 idx 0 bib 0)
(foreach k *geruest-konfig-liste*
(if (equal (ssg-json-get (cdr k) "name") name)
(if (car k)
(if (= idx 0) (setq idx i))
(if (= bib 0) (setq bib i))))
(setq i (1+ i))
)
(if (> idx 0) idx bib)
)
;; Namensvorgabe beim Speichern: der Name der gewaehlten EIGENEN
;; Konfiguration (Enter ueberschreibt sie), sonst nil - die Bibliothek ist
;; schreibgeschuetzt.
(defun geruest-konfig-name-vorgabe (konfig-idx / k)
(setq k (if (> konfig-idx 0) (nth (1- konfig-idx) *geruest-konfig-liste*)))
(if (and k (car k)) (ssg-json-get (cdr k) "name"))
)
;; Teileliste ("befestigung"/"sonstiges", in alten Konfigurationen auch
;; "oben"/"unten") einer Konfiguration auf die Anbauteile
;; abbilden. Rueckgabe: ein Eintrag je Anbauteil - nil (nicht gewaehlt) oder
;; die Anzahl (Feld "anzahl"). Ein unbekannter Artikelname wird gemeldet und
;; uebersprungen, statt die ganze Konfiguration scheitern zu lassen.
;; ausweich = Teileliste, deren Namen still uebersprungen werden (sie werden
;; von einem anderen Aufruf eingesammelt), oder T = nie melden.
(defun geruest-konfig-position (konfig schluessel anbauteile ausweich
/ mengen idx n name)
(setq mengen (geruest-nil-liste (length anbauteile)))
(foreach e (ssg-json-get konfig schluessel)
(setq name (ssg-json-get e "artikel"))
(setq idx (geruest-name-index name anbauteile))
(cond
(idx
(setq n (ssg-json-get e "anzahl"))
(setq mengen (geruest-liste-setzen mengen idx
(if (and n (numberp n) (> n 0)) (fix n) 1)))
)
((or (= ausweich T) (geruest-name-index name ausweich)) nil)
(T
(princ (ssg-textf "gr-konfig-teil-unbekannt"
(list name (ssg-json-get konfig "name")))))
)
)
mengen
)
;; Zwei Mengenlisten (nil oder Zahl je Teil) elementweise addieren.
(defun geruest-mengen-addieren (a b)
(mapcar '(lambda (x y)
(cond ((and x y) (+ x y)) (x x) (y y)))
a b)
)
;; Befestigungs-Mengen einer Konfiguration (Gesamtanzahl je Teil). Aeltere
;; Konfigurationen fuehren die Befestigung getrennt unter "oben"/"unten" -
;; deren Mengen werden addiert; beim naechsten Speichern steht alles unter
;; "befestigung". Sonstiges-Teile werden hier still uebergangen (die sammelt
;; geruest-konfig-sonstiges ein).
(defun geruest-konfig-befestigung (konfig befestigung sonstiges)
(geruest-mengen-addieren
(geruest-konfig-position konfig "befestigung" befestigung sonstiges)
(geruest-mengen-addieren
(geruest-konfig-position konfig "oben" befestigung sonstiges)
(geruest-konfig-position konfig "unten" befestigung sonstiges)))
)
;; Sonstiges-Mengen einer Konfiguration. Aeltere Konfigurationen kennen keine
;; Sonstiges-Liste und fuehren solche Teile (z.B. einen Haltearm) unter
;; "oben"/"unten"/"befestigung" - die werden hier still mit eingesammelt;
;; beim naechsten Speichern stehen sie dann unter "sonstiges".
(defun geruest-konfig-sonstiges (konfig sonstiges befestigung)
(geruest-mengen-addieren
(geruest-konfig-position konfig "sonstiges" sonstiges befestigung)
(geruest-mengen-addieren
(geruest-konfig-position konfig "befestigung" sonstiges T)
(geruest-mengen-addieren
(geruest-konfig-position konfig "oben" sonstiges T)
(geruest-konfig-position konfig "unten" sonstiges T))))
)
;; ============================================================
;; DIALOGDEFINITION ERZEUGEN
;; ============================================================
;; Das Layout steht als Vorlage in dcl/geruest.dcl bzw. dcl/geruest_traeger.dcl.
;; Hier entstehen nur die datenabhaengigen Bloecke, die ssg-dcl-vorlage-fuellen
;; (ssg_core.lsp) an den //@NAME@-Platzhaltern einsetzt: ein Radiobutton je
;; Variante und Mengenfeld + Ankreuzfeld je Anbauteil. DCL kann weder Tiles
;; zur Laufzeit erzeugen noch ausblenden, Radiobutton-Labels lassen sich per
;; set_tile nicht aendern, und eine list_box kann keine Eingabefelder
;; enthalten.
;; Ein Anfuehrungszeichen im Artikelnamen wuerde das DCL-Label vorzeitig
;; beenden und die erzeugte Datei unbrauchbar machen (fuenf Bezeichnungen
;; enthalten eines, z.B. ROHRKLAMMER 1" KPL. VERTIKAL). Betrifft nur die
;; Anzeige - ARTINR und BEZEICHNUNG nutzen weiter den Originalnamen.
(defun geruest-dcl-label (s / i c erg)
(if (null s) (setq s ""))
(setq erg "" i 1)
(while (<= i (strlen s))
(setq c (substr s i 1))
(setq erg (strcat erg (if (= c "\"") "''" c)))
(setq i (1+ i))
)
erg
)
;; Pfad einer DCL-Vorlage unter DXFM_DCL bzw. DXFMAKRO/dcl, oder nil.
(defun geruest-dcl-vorlage-pfad (datei / basis)
(setq basis (cond ((getenv "DXFM_DCL"))
((getenv "DXFMAKRO") (strcat (getenv "DXFMAKRO") "/dcl"))))
(if basis
(strcat (vl-string-translate "\\" "/" basis) "/" datei)
)
)
;; Radiobutton-Keys der Varianten: "v<i>", i = Index in geometrie_varianten.
;; get_tile auf der radio_column liefert den Key des gewaehlten Buttons.
(defun geruest-radio-key (i) (strcat "v" (itoa i)))
(defun geruest-radio-index (key)
(if (and key (> (strlen key) 1)) (atoi (substr key 2)) 0)
)
;; (0 1 ... n-1)
(defun geruest-indizes (n / i erg)
(setq i (1- n) erg nil)
(while (>= i 0) (setq erg (cons i erg) i (1- i)))
erg
)
;; Beschriftung einer Variante im Dialog: Name und dahinter die enthaltenen
;; Artikelnummern in Klammern, z.B. "R 2\" (821006051, 821004201)". Ohne
;; Nummern nur der Name.
(defun geruest-varianten-label (variante / nr)
(setq nr (geruest-artnr-anzeige variante))
(if (/= nr "")
(strcat (geruest-eintrag-name variante) " (" nr ")")
(geruest-eintrag-name variante)
)
)
;; Gesperrte Varianten: "disabled": true in der JSON. Sie erscheinen im
;; Dialog ausgegraut (mode_tile) und sind nicht waehlbar - so laesst sich eine
;; Variante aus dem Angebot nehmen, ohne sie zu loeschen (Konfigurationen und
;; GERUEST_TRAEGER_BESCHRIFTEN finden sie weiter ueber den Namen).
(defun geruest-variante-gesperrt-p (variante)
(if (ssg-json-get variante "disabled") T nil)
)
;; Index der ersten nicht gesperrten Variante; 0, wenn alle gesperrt sind.
(defun geruest-erste-freie-variante (varianten / i idx)
(setq i 0 idx nil)
(foreach v varianten
(if (and (null idx) (not (geruest-variante-gesperrt-p v))) (setq idx i))
(setq i (1+ i))
)
(if idx idx 0)
)
;; Gueltiger Varianten-Index fuer die Vorauswahl: idx selbst, wenn er
;; existiert und nicht gesperrt ist, sonst die erste freie Variante.
(defun geruest-variante-waehlbar (varianten idx)
(if (and idx (>= idx 0) (< idx (length varianten))
(not (geruest-variante-gesperrt-p (nth idx varianten))))
idx
(geruest-erste-freie-variante varianten)
)
)
;; Radiobuttons gesperrter Varianten ausgrauen (im laufenden Dialog).
(defun geruest-varianten-sperren (varianten / i)
(setq i 0)
(foreach v varianten
(if (geruest-variante-gesperrt-p v) (mode_tile (geruest-radio-key i) 1))
(setq i (1+ i))
)
)
;; Ein Radiobutton je Variante aus idx-liste (Indizes in varianten).
(defun geruest-dcl-varianten-zeilen (varianten idx-liste / erg)
(setq erg nil)
(foreach i idx-liste
(setq erg (append erg
(list (strcat " : radio_button { key = \"" (geruest-radio-key i)
"\"; label = \""
(geruest-dcl-label (geruest-varianten-label (nth i varianten)))
"\"; }"))))
)
erg
)
;; Artikelnummer(n) eines Anbauteils fuer die Anzeige im Dialog: Einzelteil
;; -> seine sivas_id, Gruppe -> die Nummern ihrer Teile, jede nur einmal
;; (die Stueckzahl steckt in ARTINR, nicht in der Anzeige).
(defun geruest-artnr-anzeige (eintrag)
(ssg-join (geruest-eindeutig (geruest-artikelnummern eintrag)) ", ")
)
;; Eine Anbauteil-Spalte als DCL-Zeilen. Je Teil: Mengenfeld LINKS, rechts
;; daneben Ankreuzfeld mit dem Teilenamen und darunter eine Textzeile mit der
;; Artikelnummer (DCL kennt keine Schriftgroesse - die Nummer steht darum in
;; normaler Groesse in einer eigenen Zeile). Das Mengenfeld links haelt die
;; Eingabefelder gleich breit und laesst die Teilenamen auf gleicher Hoehe
;; beginnen - DCL richtet Zeileninhalte nicht von sich aus aus.
;;
;; Zwei DCL-Eigenheiten muessen dafuer gesetzt werden:
;; - alignment = left, weil DCL Tiles in einer Spalte sonst ZENTRIERT: bei
;; unterschiedlich langen Teilenamen wandern die Zeilen sonst verschieden
;; weit nach rechts und die Liste franst aus.
;; - fixed_width = true auf der Zeile, weil eine gestreckte row ihre Tiles
;; ueber die volle Breite VERTEILT - zwischen Mengenfeld und Ankreuzfeld
;; klaffte dadurch eine wechselnd grosse Luecke. Mit fixed_width wird die
;; Zeile nur so breit wie ihr Inhalt, das Ankreuzfeld sitzt also direkt am
;; Mengenfeld.
(defun geruest-dcl-spalte (anbauteile prefix titel / i erg)
(setq erg (list " : boxed_column {"
(strcat " label = \"" titel "\";")
" alignment = left;"))
(setq i 0)
(foreach a anbauteile
(setq erg (append erg
(list " : row {"
" alignment = left;"
" fixed_width = true;"
(strcat " : edit_box { key = \"" prefix "m" (itoa i)
"\"; label = \"\"; edit_width = 3; fixed_width = true; }")
" : column {"
" alignment = left;"
(strcat " : toggle { key = \"" prefix (itoa i)
"\"; label = \""
(geruest-dcl-label (geruest-eintrag-name a))
"\"; fixed_width = true; }")
(strcat " : text { label = \"Art.-Nr. "
(geruest-dcl-label (geruest-artnr-anzeige a))
"\"; }")
" }"
" }")))
(setq i (1+ i))
)
(append erg (list " }"))
)
;; Ersetzungen fuer dcl/geruest.dcl. Ohne Teile entfallen die Blocks ganz -
;; ein leerer Cluster ist in DCL nicht zulaessig (z.B. Wandbefestigung ohne
;; Sonstiges).
(defun geruest-dcl-ersetzungen (titel anbauteile sonstiges varianten)
(list
(cons "TITEL"
(list (strcat " label = \"" (geruest-dcl-label (ssg-textf "gr-dlg-kopf" (list titel)))
"\";")))
(cons "VARIANTEN"
(geruest-dcl-varianten-zeilen varianten (geruest-indizes (length varianten))))
(cons "BEFESTIGUNG"
(if anbauteile
(geruest-dcl-spalte anbauteile "b"
"Befestigung oben und unten (Anzahl / Teil)")))
(cons "SONSTIGES"
(if sonstiges
(geruest-dcl-spalte sonstiges "s" "Sonstiges (Anzahl / Teil)")))
)
)
;; Rueckgabe: Pfad der gefuellten temporaeren DCL-Datei oder nil.
(defun geruest-dcl-schreiben (titel anbauteile sonstiges varianten)
(ssg-dcl-vorlage-fuellen (geruest-dcl-vorlage-pfad "geruest.dcl") "geruest"
(geruest-dcl-ersetzungen titel anbauteile sonstiges varianten))
)
;; ============================================================
;; DIALOGZUSTAND
;; ============================================================
;; Die Tiles werden in Schleifen gelesen und geschrieben, ihre Anzahl steht
;; erst zur Laufzeit fest. Der Zustand liegt darum in Globals: action_tile
;; wertet seinen Rueckruf als String AUSSERHALB des lexikalischen Scopes der
;; Dialogfunktion aus, auf deren Locals kaeme er nicht.
;; Ausgangszustand einer Spalte: nichts angehakt, Mengenfeld mit der
;; JSON-Vorgabe vorbelegt (beim Klemmring also 2). teile = Anbauteile der
;; Spalte (befestigung oder sonstiges).
(defun geruest-dlg-standard (teile / erg)
(setq erg nil)
(foreach a teile
(setq erg (append erg (list (cons "0" (itoa (geruest-anzahl-vorgabe a))))))
)
erg
)
;; Mengenliste (nil oder Zahl je Teil) in den Tile-Zustand umsetzen.
(defun geruest-anzahlen->tiles (mengen teile / erg i a)
(setq erg nil i 0)
(foreach m mengen
(setq a (nth i teile))
(setq erg (append erg
(list (if m
(cons "1" (itoa m))
(cons "0" (itoa (geruest-anzahl-vorgabe a)))))))
(setq i (1+ i))
)
erg
)
;; Tile-Zustand einer Spalte lesen: ((haken . menge) ...) je Teil.
;; prefix = "b"/"s" (Tile-Keys <prefix>i und <prefix>mi).
(defun geruest-dlg-spalte-lesen (prefix teile / i n erg)
(setq n (length teile) i 0 erg nil)
(while (< i n)
(setq erg (append erg
(list (cons (get_tile (strcat prefix (itoa i)))
(get_tile (strcat prefix "m" (itoa i)))))))
(setq i (1+ i))
)
erg
)
(defun geruest-dlg-spalte-schreiben (prefix zustand / i)
(setq i 0)
(foreach paar zustand
(set_tile (strcat prefix (itoa i)) (car paar))
(set_tile (strcat prefix "m" (itoa i)) (cdr paar))
(setq i (1+ i))
)
)
;; Tile-Keys einer Spalte (Haken und Mengenfeld je Teil) fuer action_tile.
(defun geruest-dlg-spalten-keys (prefix teile / i erg)
(setq i 0 erg nil)
(foreach a teile
(setq erg (append erg (list (strcat prefix (itoa i))
(strcat prefix "m" (itoa i)))))
(setq i (1+ i))
)
erg
)
(defun geruest-dlg-lesen ( / )
;; radio_column liefert den Key des gewaehlten Buttons ("v<i>"); der
;; Zustand fuehrt weiter den Index als String.
(setq *geruest-dlg-variante*
(itoa (geruest-radio-index (get_tile "variante"))))
(setq *geruest-dlg-winkel* (get_tile "winkel"))
(if (= *geruest-dlg-winkel* "") (setq *geruest-dlg-winkel* "0"))
(setq *geruest-dlg-laenge* (get_tile "laenge"))
(setq *geruest-dlg-farbe* (get_tile "farbe"))
(setq *geruest-dlg-frei* (get_tile "farbe_frei"))
(setq *geruest-dlg-konfig* (get_tile "konfig"))
(setq *geruest-dlg-bef* (geruest-dlg-spalte-lesen "b" *geruest-anbauteile*))
(setq *geruest-dlg-sonst* (geruest-dlg-spalte-lesen "s" *geruest-sonstiges*))
(princ)
)
(defun geruest-dlg-schreiben ( / )
(set_tile "variante" (geruest-radio-key (atoi *geruest-dlg-variante*)))
(set_tile "laenge" *geruest-dlg-laenge*)
;; Nur setzen, wenn die Liste ueberhaupt gefuellt ist - ein Index auf eine
;; leere popup_list ist nicht definiert (Baugruppen ohne attribute-Block).
(if *geruest-farb-liste* (set_tile "farbe" *geruest-dlg-farbe*))
(set_tile "farbe_frei" *geruest-dlg-frei*)
(set_tile "winkel" *geruest-dlg-winkel*)
(geruest-dlg-spalte-schreiben "b" *geruest-dlg-bef*)
(geruest-dlg-spalte-schreiben "s" *geruest-dlg-sonst*)
(princ)
)
;; Farbliste des Dialogs aus der TEILEFARBE-Definition aufbauen: Optionen +
;; freie Option ans Listenende; deren Index merkt sich der Callback, um das
;; Freitextfeld freizuschalten. Genutzt vom GERUEST- und vom Traeger-Dialog.
(defun geruest-farb-liste-setzen (farb-opts / frei-opt)
(setq *geruest-farb-liste* nil *geruest-frei-idx* -1)
(if farb-opts
(progn
(setq *geruest-farb-liste* (ssg-json-get farb-opts "optionen"))
(if (ssg-json-get farb-opts "frei_erlaubt")
(progn
(setq frei-opt (ssg-json-get farb-opts "frei_option"))
(if (null frei-opt) (setq frei-opt "Individuell"))
(setq *geruest-frei-idx* (length *geruest-farb-liste*))
(setq *geruest-farb-liste*
(append *geruest-farb-liste* (list frei-opt)))
)
)
)
)
)
;; Gewaehlte Farbe als Text. idx-str = Tile-Wert der Farbliste,
;; frei = Inhalt des Freitextfelds.
(defun geruest-farbwert (idx-str frei / idx)
(setq idx (atoi idx-str))
(cond
((null *geruest-farb-liste*) "")
((= idx *geruest-frei-idx*) frei)
((nth idx *geruest-farb-liste*) (nth idx *geruest-farb-liste*))
(T "")
)
)
;; Aktuell gewaehlte Farbe des GERUEST-Dialogs als Text.
(defun geruest-dlg-farbwert ( / )
(geruest-farbwert *geruest-dlg-farbe* *geruest-dlg-frei*)
)
;; Konfiguration anwenden und die Tiles nachziehen. Wird direkt aus dem
;; popup_list-Rueckruf aufgerufen - set_tile aendert die Anzeige im laufenden
;; Dialog, ein Neuaufbau ist nicht noetig. Index 0 = "(keine)".
;; Danach wird der Stand als Vergleichsbasis gemerkt
;; (*geruest-dlg-konfig-basis*), damit eine spaetere Aenderung erkennbar ist.
(defun geruest-dlg-konfig-waehlen (idx / k vidx farbe fidx)
(setq *geruest-dlg-konfig* (itoa idx))
(if (<= idx 0)
(progn
(setq *geruest-dlg-winkel* "0")
(setq *geruest-dlg-bef* (geruest-dlg-standard *geruest-anbauteile*))
(setq *geruest-dlg-sonst* (geruest-dlg-standard *geruest-sonstiges*))
)
(progn
(setq k (nth (1- idx) *geruest-konfig-liste*))
(if k
(progn
(setq k (cdr k))
(setq vidx (geruest-name-index (ssg-json-get k "variante")
*geruest-varianten*))
(cond
((null vidx)
(princ (ssg-textf "gr-konfig-variante-unbekannt"
(list (ssg-json-get k "variante")
(ssg-json-get k "name")))))
;; gesperrte Variante: Auswahl bleibt, wie sie ist
((geruest-variante-gesperrt-p (nth vidx *geruest-varianten*))
(princ (ssg-textf "gr-konfig-variante-gesperrt"
(list (ssg-json-get k "variante")
(ssg-json-get k "name")))))
(T (setq *geruest-dlg-variante* (itoa vidx)))
)
(setq *geruest-dlg-laenge*
(geruest-zahl-string (ssg-json-get k "laenge")))
;; Farbe: bekannter Wert -> Listeneintrag, sonst Freitext (sofern
;; die Baugruppe das zulaesst).
(setq farbe (ssg-json-get k "teilefarbe"))
(setq fidx (geruest-name-farbindex farbe))
(cond
(fidx (setq *geruest-dlg-farbe* (itoa fidx)
*geruest-dlg-frei* ""))
((and farbe (/= farbe "") (>= *geruest-frei-idx* 0))
(setq *geruest-dlg-farbe* (itoa *geruest-frei-idx*)
*geruest-dlg-frei* farbe))
)
(setq *geruest-dlg-bef*
(geruest-anzahlen->tiles
(geruest-konfig-befestigung k *geruest-anbauteile* *geruest-sonstiges*)
*geruest-anbauteile*))
(setq *geruest-dlg-sonst*
(geruest-anzahlen->tiles
(geruest-konfig-sonstiges k *geruest-sonstiges* *geruest-anbauteile*)
*geruest-sonstiges*))
;; Fehlt in aelteren Konfigurationen -> 0 Grad
(setq *geruest-dlg-winkel*
(geruest-winkel-index (ssg-json-get k "winkel_platte")))
)
)
)
)
(geruest-dlg-schreiben)
(mode_tile "farbe_frei"
(if (= (atoi *geruest-dlg-farbe*) *geruest-frei-idx*) 0 1))
;; Erst NACH dem Schreiben lesen: der Vergleichsstand soll genau das sein,
;; was die Tiles zeigen (z.B. ein vom Dialog normalisiertes Mengenfeld).
(geruest-dlg-lesen)
(setq *geruest-dlg-konfig-basis*
(if (> idx 0) (geruest-dlg-record "")))
(geruest-dlg-konfig-anzeigen)
(princ)
)
;; Zwei Konfigurations-Records ohne Beachtung des Namens vergleichen.
(defun geruest-record-ohne-name (r)
(vl-remove-if '(lambda (e) (equal (car e) "name")) r)
)
(defun geruest-record-gleich-p (a b)
(equal (geruest-record-ohne-name a) (geruest-record-ohne-name b))
)
;; T, wenn eine Konfiguration gewaehlt ist und der aktuelle Stand von ihr
;; abweicht. Bewusst ein Vergleich und kein "geaendert"-Flag: wer eine
;; Aenderung zuruecknimmt, hat wieder den Stand der Konfiguration.
(defun geruest-dlg-geaendert-p ( / )
(and *geruest-dlg-konfig-basis*
(> (atoi *geruest-dlg-konfig*) 0)
(not (geruest-record-gleich-p *geruest-dlg-konfig-basis*
(geruest-dlg-record ""))))
)
;; Konfig-Liste und Statuszeile nachziehen: der gewaehlte Eintrag bekommt bei
;; Abweichung den Zusatz "(geaendert)", die Statuszeile sagt, was Speichern
;; tut (eigene Konfiguration -> ueberschreiben, Bibliothek -> neuer Name).
(defun geruest-dlg-konfig-anzeigen ( / idx i k geaendert)
(setq idx (atoi *geruest-dlg-konfig*))
(setq geaendert (geruest-dlg-geaendert-p))
(start_list "konfig")
(add_list (ssg-text "gr-konfig-keine"))
(setq i 1)
(foreach k *geruest-konfig-liste*
(add_list (if (and geaendert (= i idx))
(ssg-textf "gr-konfig-geaendert" (list (geruest-konfig-name k)))
(geruest-konfig-name k)))
(setq i (1+ i))
)
(end_list)
(set_tile "konfig" *geruest-dlg-konfig*)
(setq k (if (> idx 0) (nth (1- idx) *geruest-konfig-liste*)))
(set_tile "kstatus"
(cond
((not geaendert) "")
((car k) (ssg-textf "gr-konfig-status-geaendert"
(list (ssg-json-get (cdr k) "name"))))
(T (ssg-textf "gr-konfig-status-bibliothek"
(list (ssg-json-get (cdr k) "name"))))
)
)
(princ)
)
;; Rueckruf aller Eingabe-Tiles: Stand lesen und Kennzeichnung nachziehen.
(defun geruest-dlg-aenderung-pruefen ( / )
(geruest-dlg-lesen)
(geruest-dlg-konfig-anzeigen)
)
(defun geruest-name-farbindex (wert / i idx)
(setq i 0 idx nil)
(foreach f *geruest-farb-liste*
(if (and (null idx) (equal f wert)) (setq idx i))
(setq i (1+ i))
)
idx
)
;; Angehakte Teile einer Spalte mit gueltiger Menge einsammeln.
;; Rueckgabe: (T . ((index . anzahl) ...)) oder nil bei ungueltigem Mengenfeld.
;; Das fuehrende T ist noetig, weil "nichts ausgewaehlt" ein gueltiges
;; Ergebnis ist und sich sonst nicht von einem Fehler unterscheiden liesse.
(defun geruest-anzahlen-lesen (zustand teile / erg i a m ok)
(setq erg nil i 0 ok T)
(foreach paar zustand
(setq a (nth i teile))
(if (and ok (= (car paar) "1"))
(progn
(setq m (geruest-anzahl-wert (cdr paar) (geruest-anzahl-vorgabe a)))
(if m
(setq erg (append erg (list (cons i m))))
(setq ok nil)
)
)
)
(setq i (1+ i))
)
(if ok (cons T erg))
)
;; Aktuellen Dialogzustand als Konfigurations-Datensatz aufbauen - in
;; derselben Form, die der Parser aus der Datei liefert, damit der Rest des
;; Moduls nicht zwischen Bibliothek und eigener Konfiguration unterscheiden
;; muss.
(defun geruest-dlg-record (name)
(list (cons "name" name)
(cons "variante"
(geruest-eintrag-name
(nth (atoi *geruest-dlg-variante*) *geruest-varianten*)))
(cons "laenge" *geruest-dlg-laenge*)
(cons "teilefarbe" (geruest-dlg-farbwert))
(cons "befestigung" (geruest-dlg-teile *geruest-dlg-bef* *geruest-anbauteile*))
(cons "sonstiges" (geruest-dlg-teile *geruest-dlg-sonst* *geruest-sonstiges*))
(cons "winkel_platte" (geruest-winkel-aus-index *geruest-dlg-winkel*))
)
)
(defun geruest-dlg-teile (zustand teile / erg i a m)
(setq erg nil i 0)
(foreach paar zustand
(setq a (nth i teile))
(if (= (car paar) "1")
(progn
(setq m (geruest-anzahl-wert (cdr paar) (geruest-anzahl-vorgabe a)))
(if m
(setq erg (append erg
(list (list (cons "artikel" (geruest-eintrag-name a))
(cons "anzahl" m)))))
)
)
)
(setq i (1+ i))
)
erg
)
;; ============================================================
;; BENUTZERKONFIGURATIONEN SCHREIBEN
;; ============================================================
;; Bewusst ein auf dieses Schema zugeschnittener Schreiber und kein
;; generischer JSON-Writer: beim Schreiben ist die Abbildung des Parsers
;; mehrdeutig - eine leere Liste kann aus [], {}, false oder null entstanden
;; sein. Ein generischer Writer wuerde ein Versprechen geben, das er nicht
;; halten kann.
(defun geruest-teile-schreiben (f teile / n i)
(setq n (length teile) i 0)
(foreach e teile
(setq i (1+ i))
(write-line
(strcat " { \"artikel\": \""
(ssg-json-escape (ssg-json-get e "artikel"))
"\", \"anzahl\": " (itoa (ssg-json-get e "anzahl")) " }"
(if (< i n) "," ""))
f)
)
)
(defun geruest-konfig-liste-schreiben (f konfigs / n i)
(setq n (length konfigs) i 0)
(foreach k konfigs
(setq i (1+ i))
(write-line " {" f)
(write-line (strcat " \"name\": \""
(ssg-json-escape (ssg-json-get k "name")) "\",") f)
(write-line (strcat " \"variante\": \""
(ssg-json-escape (ssg-json-get k "variante")) "\",") f)
;; Laenge als String: sie darf leer sein (Baugruppen ohne Laengenangabe),
;; und "laenge": ohne Wert waere kein gueltiges JSON. Der Leser
;; (geruest-zahl-string) nimmt String wie Zahl entgegen.
(write-line (strcat " \"laenge\": \""
(ssg-json-escape (geruest-zahl-string
(ssg-json-get k "laenge"))) "\",") f)
(write-line (strcat " \"teilefarbe\": \""
(ssg-json-escape (ssg-json-get k "teilefarbe")) "\",") f)
(write-line (strcat " \"winkel_platte\": "
(itoa (fix (if (numberp (ssg-json-get k "winkel_platte"))
(ssg-json-get k "winkel_platte") 0))) ",") f)
(write-line " \"befestigung\": [" f)
(geruest-teile-schreiben f (ssg-json-get k "befestigung"))
(write-line " ]," f)
(write-line " \"sonstiges\": [" f)
(geruest-teile-schreiben f (ssg-json-get k "sonstiges"))
(write-line " ]" f)
(write-line (if (< i n) " }," " }") f)
)
)
;; cfg/geruest_user.json komplett neu schreiben.
(defun geruest-user-schreiben ( / pfad f n i)
(setq pfad (geruest-user-pfad))
(if (null pfad)
(progn (princ (ssg-text "gr-fehler-datenpfad")) nil)
(progn
(setq f (open pfad "w"))
(if (null f)
(progn (princ (ssg-textf "gr-fehler-schreiben" (list pfad))) nil)
(progn
(write-line "{" f)
(setq n (length *geruest-user-daten*) i 0)
(foreach bgruppe *geruest-user-daten*
(setq i (1+ i))
(write-line (strcat " \"" (ssg-json-escape (car bgruppe))
"\": [") f)
(geruest-konfig-liste-schreiben f (cdr bgruppe))
(write-line (if (< i n) " ]," " ]") f)
)
(write-line "}" f)
(close f)
T
)
)
)
)
)
;; Eigene Konfiguration ablegen; ein gleichnamiger eigener Eintrag wird
;; ersetzt. Die Bibliothek aus geruest.json bleibt unberuehrt.
(defun geruest-konfig-sichern (bg name / id liste neu ersetzt)
(setq id (ssg-json-get bg "id"))
(setq neu (geruest-dlg-record name))
(setq ersetzt nil)
(setq liste
(mapcar '(lambda (k)
(if (equal (ssg-json-get k "name") name)
(progn (setq ersetzt T) neu)
k))
(ssg-json-get (geruest-user-daten) id)))
(if (not ersetzt) (setq liste (append liste (list neu))))
(setq *geruest-user-daten*
(geruest-alist-setzen (geruest-user-daten) id liste))
(if (geruest-user-schreiben)
(princ (ssg-textf "gr-konfig-gespeichert" (list name)))
)
)
;; Gewaehlte eigene Konfiguration nach Rueckfrage entfernen.
(defun geruest-konfig-entfernen (bg / idx k id name liste)
(setq idx (atoi *geruest-dlg-konfig*))
(if (<= idx 0)
(princ (ssg-text "gr-konfig-nichts-gewaehlt"))
(progn
(setq k (nth (1- idx) *geruest-konfig-liste*))
(cond
((null k) nil)
((null (car k)) (princ (ssg-text "gr-konfig-bibliothek-fest")))
(T
(setq name (ssg-json-get (cdr k) "name"))
(initget "Ja Nein")
(if (equal (getkword (ssg-textf "gr-konfig-loeschen-frage"
(list name))) "Ja")
(progn
(setq id (ssg-json-get bg "id"))
(setq liste (vl-remove-if
'(lambda (e)
(equal (ssg-json-get e "name") name))
(ssg-json-get (geruest-user-daten) id)))
(setq *geruest-user-daten*
(geruest-alist-setzen (geruest-user-daten) id liste))
(if (geruest-user-schreiben)
(princ (ssg-textf "gr-konfig-geloescht" (list name))))
(setq *geruest-dlg-konfig* "0")
)
)
)
)
)
)
)
;; Dialog fuer einen Bereich zeigen. Rueckgabe:
;; ("OK" variante-index befestigung laenge farbe sonstiges winkel)
;; - befestigung/sonstiges je ((idx . anzahl) ...) in
;; anbaugruppen.befestigung bzw. anbaugruppen.sonstiges; die Anzahl der
;; Befestigung ist die GESAMTanzahl (oben und unten zusammen).
;; - winkel = "Winkel Platte" in Grad (0/90) fuer die Anbauteile.
;; nil - abgebrochen
;; Jeder Bereich hat seinen eigenen Menuebefehl; eine Umschaltung im Dialog
;; gibt es nicht (DCL kennt keine Reiter). Der Dialogstand beginnt je Aufruf frisch.
(defun geruest-dialog (bg / dcl-pfad dat attr laenge-def
farb-opts min-l max-l fertig rueckgabe
ergebnis wert-l bef-m sonst-m name
vorgabe tile)
(setq *geruest-varianten* (ssg-json-get bg "geometrie_varianten"))
(setq *geruest-anbauteile* (geruest-anbau-liste bg "befestigung"))
(setq *geruest-sonstiges* (geruest-anbau-liste bg "sonstiges"))
(setq attr (ssg-json-get bg "attribute"))
(setq laenge-def (if attr (ssg-json-get attr "LAENGE")))
(setq farb-opts (if attr (ssg-json-get attr "TEILEFARBE")))
(setq min-l (if laenge-def (ssg-json-get laenge-def "min")))
(setq max-l (if laenge-def (ssg-json-get laenge-def "max")))
(geruest-farb-liste-setzen farb-opts)
(setq *geruest-konfig-liste* (geruest-konfigs bg))
;; Ausgangszustand
(setq *geruest-dlg-variante* (itoa (geruest-erste-freie-variante *geruest-varianten*))
*geruest-dlg-laenge* ""
*geruest-dlg-farbe* "0" *geruest-dlg-frei* ""
*geruest-dlg-konfig* "0" *geruest-dlg-winkel* "0"
*geruest-dlg-konfig-basis* nil)
(setq *geruest-dlg-bef* (geruest-dlg-standard *geruest-anbauteile*))
(setq *geruest-dlg-sonst* (geruest-dlg-standard *geruest-sonstiges*))
(setq fertig nil rueckgabe nil)
;; Die Dialogdefinition haengt nur an Varianten und Teilelisten, nicht am
;; Zustand - einmal aus der Vorlage fuellen und erst nach der Schleife
;; loeschen (unload_dialog muss vorher gelaufen sein).
(setq dcl-pfad (geruest-dcl-schreiben (geruest-titel bg) *geruest-anbauteile*
*geruest-sonstiges* *geruest-varianten*))
;; Schleife: ungueltige Eingaben, Speichern und Loeschen zeigen den Dialog
;; mit dem bisherigen Stand erneut (Projektmuster, vgl. vf_linienzug).
(while (not fertig)
(setq dat (if dcl-pfad (load_dialog dcl-pfad) -1))
(if (or (null dcl-pfad) (< dat 0) (not (new_dialog "geruest" dat)))
(progn
(alert (ssg-text "gr-fehler-dcl"))
(setq fertig T)
)
(progn
(if *geruest-farb-liste*
(progn
(start_list "farbe")
(foreach f *geruest-farb-liste* (add_list f))
(end_list)
)
)
(start_list "winkel")
(foreach w *geruest-winkel-werte*
(add_list (ssg-textf "gr-winkel-grad" (list (itoa w) (chr 176)))))
(end_list)
;; Alle Tiles aus dem Zustand fuellen (auch Haken und Mengenfelder)
(geruest-dlg-schreiben)
(geruest-varianten-sperren *geruest-varianten*)
;; Konfig-Liste inkl. "(geaendert)"-Kennzeichnung und Statuszeile -
;; auch nach Speichern/Loeschen/Fehler-Neuaufbau aktuell
(geruest-dlg-konfig-anzeigen)
;; Der Winkel wirkt nur auf Anbauteile
(if (and (null *geruest-anbauteile*) (null *geruest-sonstiges*))
(mode_tile "winkel" 1))
;; Laenge/Farbe nur bei Baugruppen mit "attribute"-Block
(if (null laenge-def) (mode_tile "laenge" 1))
(if *geruest-farb-liste*
(progn
(mode_tile "farbe_frei"
(if (= (atoi *geruest-dlg-farbe*) *geruest-frei-idx*) 0 1))
(action_tile "farbe"
(strcat "(mode_tile \"farbe_frei\" (if (= (atoi $value) *geruest-frei-idx*) 0 1))"
" (geruest-dlg-aenderung-pruefen)"))
)
(progn
(mode_tile "farbe" 1)
(mode_tile "farbe_frei" 1)
)
)
(action_tile "konfig" "(geruest-dlg-konfig-waehlen (atoi $value))")
;; Jede Eingabe prueft, ob der Stand noch der gewaehlten
;; Konfiguration entspricht (Edit-Boxen melden sich beim Verlassen).
(foreach tile (append '("variante" "laenge" "farbe_frei" "winkel")
(geruest-dlg-spalten-keys "b" *geruest-anbauteile*)
(geruest-dlg-spalten-keys "s" *geruest-sonstiges*))
(action_tile tile "(geruest-dlg-aenderung-pruefen)"))
(action_tile "accept" "(geruest-dlg-lesen) (done_dialog 1)")
(action_tile "kspeichern" "(geruest-dlg-lesen) (done_dialog 2)")
(action_tile "kloeschen" "(geruest-dlg-lesen) (done_dialog 3)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 0) (setq fertig T))
;; Speichern: der Name wird NACH dem Schliessen abgefragt -
;; getstring funktioniert aus einem action_tile-Rueckruf heraus
;; nicht (dasselbe Muster wie die Punktwahl im VF-Assistenten).
((= ergebnis 2)
;; Enter uebernimmt den Namen der gewaehlten eigenen
;; Konfiguration und ueberschreibt sie damit.
(setq vorgabe (geruest-konfig-name-vorgabe (atoi *geruest-dlg-konfig*)))
(setq name (getstring T (if vorgabe
(ssg-textf "gr-konfig-name-vorgabe" (list vorgabe))
(ssg-text "gr-konfig-name"))))
(if (and vorgabe (or (null name) (= (ssg-cfg-trim name) "")))
(setq name vorgabe))
(if (and name (/= (ssg-cfg-trim name) ""))
(progn
(setq name (ssg-cfg-trim name))
(geruest-konfig-sichern bg name)
(setq *geruest-konfig-liste* (geruest-konfigs bg))
;; Gerade gespeicherten Eintrag auswaehlen, damit Liste und
;; angezeigter Stand zusammenpassen (Index 0 = "<keine>").
(setq *geruest-dlg-konfig*
(itoa (geruest-konfig-index name)))
;; Gespeicherter Stand = neue Vergleichsbasis
(setq *geruest-dlg-konfig-basis* (geruest-dlg-record name))
)
(progn
(princ (ssg-text "gr-abgebrochen"))
(setq *geruest-konfig-liste* (geruest-konfigs bg))
)
)
)
((= ergebnis 3)
(geruest-konfig-entfernen bg)
(setq *geruest-konfig-liste* (geruest-konfigs bg))
(if (= *geruest-dlg-konfig* "0") (setq *geruest-dlg-konfig-basis* nil))
)
;; Laenge pruefen - Bereichspruefung erfolgt im Projekt immer nach
;; done_dialog, DCL kann das nicht.
((and laenge-def
(progn (setq wert-l (atof *geruest-dlg-laenge*))
(or (= (ssg-cfg-trim *geruest-dlg-laenge*) "")
(and min-l (< wert-l min-l))
(and max-l (> wert-l max-l)))))
(alert (ssg-textf "gr-alert-laenge"
(list *geruest-dlg-laenge*
(if min-l (rtos min-l 2 0) "0")
(if max-l (rtos max-l 2 0) "-"))))
)
(T
(setq bef-m (geruest-anzahlen-lesen *geruest-dlg-bef* *geruest-anbauteile*))
(setq sonst-m (geruest-anzahlen-lesen *geruest-dlg-sonst* *geruest-sonstiges*))
(if (or (null bef-m) (null sonst-m))
(alert (ssg-text "gr-alert-menge"))
(progn
(setq rueckgabe
(list "OK"
(atoi *geruest-dlg-variante*)
(cdr bef-m)
(if laenge-def *geruest-dlg-laenge* "")
(geruest-dlg-farbwert)
(cdr sonst-m)
(geruest-winkel-aus-index *geruest-dlg-winkel*)))
(setq fertig T)
)
)
)
)
)
)
)
(if dcl-pfad (vl-file-delete dcl-pfad))
rueckgabe
)
;; ============================================================
;; BLOCK BAUEN UND EINFUEGEN
;; ============================================================
;; Eindeutiger Blockname. ssg-timestamp hat Sekundenaufloesung - bei zwei
;; Aufrufen in derselben Sekunde wird ein Zaehler angehaengt (Muster aus
;; OmniModulInsert.lsp).
(defun geruest-blockname (id / bname)
(setq bname (strcat "GR_" id "_" (ssg-timestamp)))
(while (tblsearch "BLOCK" bname)
(setq *geruest-block-counter* (1+ *geruest-block-counter*))
(setq bname (strcat "GR_" id "_" (ssg-timestamp) "_"
(itoa *geruest-block-counter*)))
)
bname
)
;; Dummy-Geometrie + eigene ATTDEFs zu einem Block zusammenfassen und am
;; gewaehlten Punkt einfuegen. werte = alist der berechneten Attributwerte,
;; variante = gewaehlte Geometrie-Variante (liefert ggf. eine eigene DWG).
;; teile-dwgs = ((pfad . rot) ...) der Teile-DWGs (geruest-teile-dwgs-rot),
;; die zusaetzlich als reine Geometrie bei 0,0,0 mit in den Block wandern,
;; um rot Grad um Z gedreht ("Winkel Platte" der Anbauteile).
;; Rueckgabe: (blockname . entity) oder nil.
(defun geruest-block-bauen (bg variante werte pt teile-dwgs / datei dummy lastEnt dummyEnt
dummy-attribs attribs winkel-vars)
;; Eine Variante darf eine eigene Dummy-DWG mitbringen; sonst (oder wenn
;; deren Datei fehlt) gilt die der Baugruppe.
(setq datei (geruest-dummy-wahl variante bg))
(setq dummy (cdr datei)
datei (car datei))
;; Ist der Dummy zugleich eine Teile-DWG (z.B. 821006036.dwg beim
;; T-Abhaenger), nicht ein zweites Mal einfuegen.
(setq teile-dwgs (geruest-ohne-pfad dummy teile-dwgs))
(if (or (null dummy) (null (findfile dummy)))
(progn
(princ (ssg-textf "gr-fehler-dummy" (list (if dummy dummy datei))))
nil
)
(progn
(setvar "ATTREQ" 0)
(setvar "ATTDIA" 0)
(setvar "OSMODE" 0)
;; Nicht (entlast): ist das letzte Objekt ein Block mit Attributen (z.B.
;; eine per Strg+C/V eingefuegte GR_-Kopie), wuerden dessen ATTRIBs mit
;; eingesammelt und von _.-BLOCK entfernt.
(setq lastEnt (ssg-lastent-ohne-attribute))
;; Dummy bei (0,0,0) einfuegen - seine Attribute liefern u.a. die
;; LAYER-Vorgabe.
(command "_.INSERT" dummy (list 0.0 0.0 0.0) 1 1 0)
(setq dummyEnt (entlast))
(setq dummy-attribs (ssg-attrib-read dummyEnt))
;; Ausgewaehlte Teile mit eigener DWG als sichtbare Rueckmeldung
;; einfuegen - erst NACH dem Lesen des Dummys, damit dummyEnt auf den
;; Dummy zeigt. Nur Geometrie: die Sammelschleife unten nimmt die
;; INSERTs mit in den Block, das Attribut-Schema bleibt unberuehrt.
;; Drehwinkel in Grad, gegen den Uhrzeigersinn ab +X - dafuer die
;; Winkel-Systemvariablen nur fuer diese Einfuegungen auf 0 stellen.
;; Die interaktive Drehung des fertigen Blocks danach soll wieder in den
;; Einheiten des Benutzers laufen.
(setq winkel-vars (mapcar '(lambda (v) (cons v (getvar v)))
'("ANGBASE" "ANGDIR" "AUNITS")))
(setvar "ANGBASE" 0.0)
(setvar "ANGDIR" 0)
(setvar "AUNITS" 0)
(foreach teil teile-dwgs
(command "_.INSERT" (car teil) (list 0.0 0.0 0.0) 1 1 (cdr teil))
)
(foreach v winkel-vars (setvar (car v) (cdr v)))
;; Unser Schema gewinnt an Struktur, die Dummy-Werte an Inhalt:
;; ssg-attrib-merge legt alle Tags aus *geruest-attrib-defs* an und
;; uebernimmt zusaetzlich alle Tags der Dummy-DWG.
(setq attribs (ssg-attrib-merge dummy-attribs *geruest-attrib-defs*))
;; nil = Drehwinkel interaktiv am Cursor
(geruest-block-abschliessen bg attribs werte lastEnt pt nil)
)
)
)
;; Gemeinsamer Schluss aller Geruest-Bloecke: Werte ins Schema eintragen,
;; ATTDEFs erzeugen, alles Neue seit lastEnt zu GR_<id>_<ts> zusammenfassen
;; und bei pt einfuegen.
;; attribs = Schema-Alist (Tags + Vorgabewerte), werte = berechnete Werte
;; rot = Drehwinkel in Grad (Zahl) oder nil fuer interaktive Drehung.
;; Bei einer Zahl muss der Aufrufer ANGBASE/ANGDIR/AUNITS auf 0
;; gesetzt haben, sonst deutet INSERT den Wert anders.
;; Rueckgabe: (blockname . entity) oder nil.
(defun geruest-block-abschliessen (bg attribs werte lastEnt pt rot
/ e ss bname blockEnt)
(foreach paar werte
(if (assoc (car paar) attribs)
(setq attribs (subst paar (assoc (car paar) attribs) attribs))
(setq attribs (append attribs (list paar)))
)
)
(ssg-attrib-make-defs (ssg-attrib-alist-to-defs attribs) 50.0)
;; Alles Neue seit lastEnt einsammeln (Geometrie + ATTDEFs)
(setq ss (ssadd))
(setq e (if lastEnt (entnext lastEnt) (entnext)))
(while e
(ssadd e ss)
(setq e (entnext e))
)
(setq bname (geruest-blockname (ssg-json-get bg "id")))
(command "_.-BLOCK" bname (list 0.0 0.0 0.0) ss "")
;; Stand VOR dem Einfuegen merken: bricht der Nutzer die Drehung mit ESC
;; ab, entsteht kein neues INSERT und (entlast) zeigt auf ein voellig
;; fremdes Objekt - ohne diesen Vergleich wuerden dessen Attribute
;; ueberschrieben.
(setq lastEnt (entlast))
(command "_.INSERT" bname pt 1 1 (if rot rot pause))
;; Laufende Einfuegung sicher beenden (z.B. ESC waehrend der Drehung)
(if (> (getvar "CMDACTIVE") 0) (command))
(if (> (getvar "CMDACTIVE") 0) (command))
(setq blockEnt (entlast))
(if (and blockEnt (not (eq blockEnt lastEnt))
(= (cdr (assoc 0 (entget blockEnt))) "INSERT"))
(progn
(ssg-attrib-set-on blockEnt attribs)
(ssg-block-layer-vorgabe blockEnt bname attribs)
(cons bname blockEnt)
)
)
)
;; ============================================================
;; ABLAUF
;; ============================================================
;; Teile-Paare ((artnr . farbig) ...) einer Befestigungsauswahl
;; ((index . anzahl) ...).
(defun geruest-auswahl-paare (auswahl anbauteile / erg)
(setq erg nil)
(foreach paar auswahl
(setq erg (append erg
(geruest-teile-paare-n (nth (car paar) anbauteile) (cdr paar))))
)
erg
)
;; Namensteile einer Befestigungsauswahl fuer die BEZEICHNUNG,
;; z.B. ("oben 2x ROHRKLAMMER 1\" KPL. VERTIKAL").
(defun geruest-auswahl-namen (auswahl anbauteile position / erg)
(setq erg nil)
(foreach paar auswahl
(setq erg (append erg
(list (strcat position " " (itoa (cdr paar)) "x "
(geruest-eintrag-name (nth (car paar) anbauteile))))))
)
erg
)
;; Ausgewaehlte Konfiguration einfuegen.
;; wahl = (variante-index befestigung laenge farbe sonstiges winkel) - die
;; Dialog-Rueckgabe ohne das fuehrende "OK".
(defun geruest-einfuegen (bg wahl / varianten anbauteile sonstiges variante teileart
paare artnr basis-nr bezeichnung teile-namen werte pt
ergebnis winkel)
(setq varianten (ssg-json-get bg "geometrie_varianten"))
(setq anbauteile (geruest-anbau-liste bg "befestigung"))
(setq sonstiges (geruest-anbau-liste bg "sonstiges"))
(setq variante (nth (car wahl) varianten))
;; TEILEART kann die Variante vorgeben (Saeule 1 Zoll vs. 2 Zoll) -
;; sonst gilt der Wert der Baugruppe.
(setq teileart (geruest-variante-wert variante bg "teileart"))
;; ARTINR: jede Sivas-ID so oft wie die gewaehlte Stueckzahl, erst die
;; Basis, dann Befestigung (Gesamtanzahl oben + unten), dann Sonstiges.
;; Die Paare tragen zusaetzlich das farbig-Flag je Teil fuer die
;; TEILEFARBE-Liste (gleiche Reihenfolge).
(setq paare (geruest-teile-paare variante nil))
(setq basis-nr (mapcar 'car paare))
(setq paare (append paare (geruest-auswahl-paare (cadr wahl) anbauteile)))
(setq paare (append paare (geruest-auswahl-paare (nth 4 wahl) sonstiges)))
(setq artnr (mapcar 'car paare))
;; BEZEICHNUNG: mit " + " verketten - die Artikelbezeichnungen enthalten
;; selbst Kommas ("ROHR R 1\" 33,7 x 2,6").
(setq teile-namen (list teileart (geruest-eintrag-name variante)))
(setq teile-namen (append teile-namen
(geruest-auswahl-namen (cadr wahl) anbauteile
(ssg-text "gr-position-befestigung"))))
(setq teile-namen (append teile-namen
(geruest-auswahl-namen (nth 4 wahl) sonstiges
(ssg-text "gr-position-sonstiges"))))
(setq bezeichnung (ssg-join teile-namen " + "))
(setq werte (list (cons "ARTINR" (ssg-join artnr ","))
(cons "BEZEICHNUNG" bezeichnung)
(cons "TEILEART" teileart)))
(if (/= (nth 2 wahl) "")
(setq werte (append werte (list (cons "LAENGE" (nth 2 wahl))))))
;; TEILEFARBE: je ARTINR-Eintrag die gewaehlte Farbe (farbiges Teil) oder
;; "" (z.B. ",,RAL7030,,RAL7030") - fuer den Sivas-Export je Artikelnummer.
(if (/= (nth 3 wahl) "")
(setq werte (append werte (list (cons "TEILEFARBE"
(ssg-join (geruest-farbliste paare (nth 3 wahl)) ","))))))
(setq pt (getpoint (ssg-text "gr-prompt-einfuegepunkt")))
(if (null pt)
(princ (ssg-text "gr-abgebrochen"))
(progn
(ssg-start "Geruest" '(("OSMODE") ("ATTREQ") ("ATTDIA")))
;; Basis-Teile ungedreht, Anbauteile im "Winkel Platte"
(setq winkel (nth 5 wahl))
(if (not (numberp winkel)) (setq winkel 0))
(setq ergebnis (geruest-block-bauen bg variante werte pt
(geruest-teile-dwgs-rot
(geruest-teile-dwgs basis-nr)
(geruest-teile-dwgs (geruest-rest-liste artnr (length basis-nr)))
winkel)))
(ssg-end)
(if ergebnis
(princ (ssg-textf "gr-fertig" (list teileart (car ergebnis))))
)
)
)
(princ)
)
;; Dialog fuer genau einen Bereich (id = ASCII-Feld "id" der Baugruppe)
;; zeigen und das Ergebnis einfuegen. Jeder Bereich hat seinen eigenen
;; Menuebefehl - eine Umschaltung im Dialog gibt es nicht.
(defun geruest-starten (id / bg erg)
(setq bg (if (geruest-daten) (geruest-baugruppe id (geruest-daten))))
(cond
((null (geruest-daten)) (princ (ssg-text "gr-fehler-keine-baugruppen")))
((null bg) (princ (ssg-textf "gr-fehler-baugruppe" (list id))))
((null (ssg-json-get bg "geometrie_varianten"))
(princ (ssg-textf "gr-fehler-keine-varianten" (list id))))
((null (setq erg (geruest-dialog bg)))
(princ (ssg-text "gr-abgebrochen")))
(T (geruest-einfuegen bg (cdr erg)))
)
(princ)
)
;; Ohne Bereich kein Dialog - die Bereiche oeffnen nur ihre eigenen Befehle.
(defun c:GERUEST ( / ) (princ (ssg-text "gr-bereich-waehlen")) (princ))
;; Menue-Einstiege je Bereich
(defun c:GERUEST_SAEULEN ( / ) (geruest-starten "Saeulen"))
(defun c:GERUEST_ROHRABHAENGER ( / ) (geruest-starten "Rohrabhaenger"))
(defun c:GERUEST_WANDBEFESTIGUNG ( / ) (geruest-starten "Wandbefestigung"))
;; ============================================================
;; ROHR-GERUEST-TRAEGER (Daten: data/json/geruest_traeger.json)
;; ============================================================
;; Eigener Dialog (dcl/geruest_traeger.dcl): Variante, Farbe und zwei
;; Laengenabzuege. Die Laenge selbst wird DANACH in der Zeichnung bestimmt -
;; Startpunkt, dann Endpunkt oder getippte Laenge (+ Richtung), wie beim
;; Kreisel. Aktive Abzuege ruecken die Linie beidseitig ein: Strecke 1000,
;; Abzug Anfang 20, Ende 35 -> Linie beginnt 20 mm hinter dem Startklick und
;; ist 945 mm lang.
;;
;; Keine Dummy-DWG: die Geometrie ist eine einzige LINE, die je Block eine
;; andere Laenge hat. Sie entsteht per entmake von (0,0,0) nach (L,0,0) auf
;; dem Layer der Variante; der Block wird am eingerueckten Startpunkt mit dem
;; Richtungswinkel eingefuegt. Es wird wiederholt eingefuegt, bis der Nutzer
;; ESC/Enter drueckt - jeder Traeger ist ein eigener GR_Traeger_<ts>-Block.
;; Baugruppe ueber das ASCII-Feld "id" in daten (z.B. (geruest-traeger-daten)).
(defun geruest-baugruppe (id daten / erg)
(setq erg nil)
(foreach eintrag daten
(if (and (null erg) (listp (cdr eintrag))
(equal (ssg-json-get (cdr eintrag) "id") id))
(setq erg (cdr eintrag))
)
)
erg
)
;; Feld einer Attribut-Definition (attribute -> TAG -> feld). Eine Variante
;; darf einen eigenen "attribute"-Block mitbringen, der vor dem der Baugruppe
;; gilt (z.B. ein anderer Laengenabzug bei einer Muffen-Variante). assoc
;; statt ssg-json-get, damit ein in der Variante gesetztes false (= nil) den
;; Wert der Baugruppe ueberschreibt, statt als "fehlt" durchzufallen.
(defun geruest-attr-feld (variante bg tag feld)
(geruest-obj-feld variante bg (list "attribute" tag) feld)
)
;; Objekt unter einem Schluesselpfad, z.B. ("attribute" "LAENGE"); nil, wenn
;; ein Schluessel fehlt.
(defun geruest-json-pfad-get (obj pfad)
(foreach k pfad (setq obj (if (and obj (listp obj)) (ssg-json-get obj k))))
obj
)
;; Allgemeine Form von geruest-attr-feld: Feld unter pfad, Variante vor
;; Baugruppe, je FELD aufgeloest (eine Variante nennt nur, was abweicht).
;; Genutzt fuer "attribute" und "beschriftung".
(defun geruest-obj-feld (variante bg pfad feld / def paar)
(setq def (if variante (geruest-json-pfad-get variante pfad)))
(setq paar (if (and def (listp def)) (assoc feld def)))
(if paar
(cdr paar)
(ssg-json-get (geruest-json-pfad-get bg pfad) feld)
)
)
;; Millimeter-Eingabe als Zahl >= 0 (Komma wie Punkt), sonst nil.
;; Leere Eingabe -> nil.
(defun geruest-mm-zahl (s / w)
(setq s (vl-string-translate "," "." (ssg-cfg-trim (if s s ""))))
(if (and (/= s "") (setq w (distof s 2)) (>= w 0.0)) w)
)
;; Abzug-Feld: leer -> 0, gueltige Zahl -> Zahl, sonst nil.
(defun geruest-abzug-wert (s)
(if (= (ssg-cfg-trim (if s s "")) "")
0.0
(geruest-mm-zahl s)
)
)
;; Millimeterwert fuer Attribute/Meldungen: ganze Zahl ohne Nachkommastelle,
;; sonst eine Stelle.
(defun geruest-mm-string (w / r)
(setq r (fix (+ w 0.5)))
(if (equal w r 1e-6) (itoa r) (rtos w 2 1))
)
;; Netto-Laenge der Linie: Strecke minus beide Abzuege, nil wenn <= 0.
(defun geruest-traeger-laenge (strecke abzug-a abzug-e / netto)
(setq netto (- strecke (if abzug-a abzug-a 0.0) (if abzug-e abzug-e 0.0)))
(if (> netto 1e-6) netto)
)
;; Eingerueckter Startpunkt: abzug-a entlang winkel ab p1, Z von p1.
(defun geruest-traeger-start (p1 winkel abzug-a / p)
(setq p (polar p1 winkel (if abzug-a abzug-a 0.0)))
(list (car p) (cadr p) (if (caddr p1) (caddr p1) 0.0))
)
;; Abweichung eines Randes vom erlaubten Bereich [rand-min, rand-max]
;; (0 = innerhalb).
(defun geruest-rand-abweichung (r rand-min rand-max)
(cond ((< r rand-min) (- rand-min r))
((> r rand-max) (- r rand-max))
(T 0.0))
)
;; Querstreben-Plan: (rand positionen). Regel: der Abstand zwischen zwei
;; Streben ist IMMER genau abstand, beide Raender sind gleich:
;; netto = k * abstand + 2 * rand (k = Felder, k+1 Streben)
;; Gewaehlt wird das k, dessen Rand am naechsten am Bereich [rand-min,
;; rand-max] liegt (bei Gleichstand das mit weniger Streben). Weil der
;; Bereich schmaler ist als abstand, geht nicht jede Laenge auf: mit 500/
;; 100..250 liegt der Rand genau dann im Bereich, wenn netto mod 500 = 0 oder
;; in [200, 500) ist - bei 1001..1199 mm oder unter 200 mm z.B. nicht. Der Plan
;; wird dann trotzdem geliefert, die Pruefung (geruest-rand-abweichung) und
;; die Meldung uebernimmt der Aufrufer. netto <= 0 -> nil.
(defun geruest-traeger-streben-plan (netto abstand rand-min rand-max
/ k kmax r dev best-k best-r best-dev i erg)
(if (and netto (> netto 1e-6) abstand (> abstand 1e-6))
(progn
(setq kmax (fix (+ (/ netto abstand) 1e-9)) k 0 best-k nil)
(while (<= k kmax)
(setq r (/ (- netto (* k abstand)) 2.0))
(if (>= r -1e-9)
(progn
(setq dev (geruest-rand-abweichung r rand-min rand-max))
;; strikt kleiner: bei Gleichstand bleibt das kleinere k
(if (or (null best-k) (< dev (- best-dev 1e-6)))
(setq best-k k best-r (max r 0.0) best-dev dev))
)
)
(setq k (1+ k))
)
(setq erg nil i best-k)
(while (>= i 0)
(setq erg (cons (+ best-r (* i abstand)) erg))
(setq i (1- i))
)
(list best-r erg)
)
)
)
;; Rand des Streben-Plans gegen [rand-min, rand-max] pruefen und bei
;; Abweichung den Benutzer informieren (Messagebox, ohne GUI auf die
;; Konsole). strebe = Ergebnis von geruest-traeger-strebe-cfg.
;; Rueckgabe: T = Rand im Bereich.
(defun geruest-traeger-rand-pruefen (netto strebe / plan txt)
(setq plan (geruest-traeger-streben-plan netto (nth 3 strebe) (nth 4 strebe) (nth 5 strebe)))
(if (and plan
(> (geruest-rand-abweichung (car plan) (nth 4 strebe) (nth 5 strebe)) 1e-6))
(progn
(setq txt (ssg-textf "gr-tr-strebe-rand"
(list (geruest-mm-string netto)
(geruest-mm-string (car plan))
(geruest-mm-string (nth 4 strebe))
(geruest-mm-string (nth 5 strebe))
(geruest-mm-string (nth 3 strebe))
(itoa (length (cadr plan))))))
(if (ssg-gui-p) (alert txt) (princ (strcat "\n" txt)))
nil
)
T
)
)
;; Nur die Positionen der Querstreben (Abstand ab Linienanfang).
(defun geruest-traeger-streben (netto abstand rand-min rand-max)
(cadr (geruest-traeger-streben-plan netto abstand rand-min rand-max))
)
;; Querstreben-Konfiguration (Variante vor Baugruppe) als
;; (dummy laenge querlinie abstand rand-min rand-max) oder nil, wenn keine
;; "querstrebe" definiert ist oder die Variante kein "mit_querstrebe": true
;; traegt. Das Flag steht bewusst ausdruecklich je Variante (wie "mit_muffe"),
;; ein 1"-Rohr hat z.B. keine Querstreben.
;; dummy = DWG unter data/geruest (Basispunkt Strebenmitte, 30 mm laengs
;; X) oder nil
;; laenge/querlinie = nur fuer den Rueckfall ohne Dummy-DWG: zwei Querlinien
;; im Abstand laenge, je querlinie lang
;; Fehlende Zahlenwerte -> Standard 30/25/500/100/250.
(defun geruest-traeger-strebe-cfg (variante bg / q w d)
(setq q (geruest-variante-wert variante bg "querstrebe"))
(if (and variante (ssg-json-get variante "mit_querstrebe") q (listp q))
(progn
(setq d (ssg-json-get q "dummy"))
(cons (if (and d (= (type d) 'STR) (/= d "")) d)
(mapcar '(lambda (feld std)
(setq w (ssg-json-get q feld))
(if (numberp w) (float w) std))
'("laenge" "querlinie" "abstand" "rand_min" "rand_max")
'(30.0 25.0 500.0 100.0 250.0)))
)
)
)
;; Muffen-Dummy einer Variante: nur bei "mit_muffe": true die "dummy"-Datei
;; aus "muffe" (Variante vor Baugruppe), sonst nil. Das Flag steht bewusst
;; ausdruecklich je Variante, statt aus dem Namen ("mit Muffe") geraten zu
;; werden. Bei "mit 2 Muffen" wird trotzdem nur eine Muffe gezeichnet.
(defun geruest-traeger-muffe-dummy (variante bg / m d)
(if (and variante (ssg-json-get variante "mit_muffe"))
(progn
(setq m (geruest-variante-wert variante bg "muffe"))
(setq d (if (and m (listp m)) (ssg-json-get m "dummy")))
(if (and d (= (type d) 'STR) (/= d "")) d)
)
)
)
;; Liegt netto in [min-l, max-l]? Fehlende Grenzen gelten nicht.
(defun geruest-traeger-pruefen (netto min-l max-l)
(and netto
(or (null min-l) (>= netto min-l))
(or (null max-l) (<= netto max-l)))
)
;; BEZEICHNUNG: Teileart + Variante + aktive Abzuege, mit " + " verkettet
;; (Artikelnamen enthalten Kommas, siehe geruest-einfuegen).
(defun geruest-traeger-bezeichnung (teileart variante-name abzug-a abzug-e / teile)
(setq teile (list teileart variante-name))
(if (> abzug-a 0.0)
(setq teile (append teile (list (ssg-textf "gr-tr-bez-abzug-anfang"
(list (geruest-mm-string abzug-a)))))))
(if (> abzug-e 0.0)
(setq teile (append teile (list (ssg-textf "gr-tr-bez-abzug-ende"
(list (geruest-mm-string abzug-e)))))))
(ssg-join teile " + ")
)
;; --- Dialogzustand (Globals, siehe Abschnitt DIALOGZUSTAND oben) ---
;; Bleibt zwischen zwei Aufrufen derselben Baugruppe erhalten.
;; *geruest-tr-variante* = Index der Variante in geometrie_varianten (String).
;; Jede Variante - auch die mit Muffe - hat ihren eigenen Radiobutton, damit
;; ihre Artikelnummer im Dialog sichtbar ist.
;; Abzug-Vorgaben (aktiv + Wert) der Variante idx in den Zustand laden.
(defun geruest-tr-vorgaben-laden (idx / v)
(setq v (nth idx (ssg-json-get *geruest-tr-bg* "geometrie_varianten")))
(setq *geruest-tr-abzug-a*
(if (geruest-attr-feld v *geruest-tr-bg* "LAENGENABZUGANFANG" "aktiv") "1" "0"))
(setq *geruest-tr-abzug-a-w*
(geruest-zahl-string (geruest-attr-feld v *geruest-tr-bg* "LAENGENABZUGANFANG" "vorgabe")))
(setq *geruest-tr-abzug-e*
(if (geruest-attr-feld v *geruest-tr-bg* "LAENGENABZUGENDE" "aktiv") "1" "0"))
(setq *geruest-tr-abzug-e-w*
(geruest-zahl-string (geruest-attr-feld v *geruest-tr-bg* "LAENGENABZUGENDE" "vorgabe")))
)
(defun geruest-tr-abzug-schreiben ( / )
(set_tile "abzug_a" *geruest-tr-abzug-a*)
(set_tile "abzug_a_w" *geruest-tr-abzug-a-w*)
(set_tile "abzug_e" *geruest-tr-abzug-e*)
(set_tile "abzug_e_w" *geruest-tr-abzug-e-w*)
(mode_tile "abzug_a_w" (if (= *geruest-tr-abzug-a* "1") 0 1))
(mode_tile "abzug_e_w" (if (= *geruest-tr-abzug-e* "1") 0 1))
)
;; Variantenwechsel: Abzug-Vorgaben der neuen Variante uebernehmen.
(defun geruest-tr-variante-gewaehlt (key / )
(setq *geruest-tr-variante* (itoa (geruest-radio-index key)))
(geruest-tr-vorgaben-laden (atoi *geruest-tr-variante*))
(geruest-tr-abzug-schreiben)
)
(defun geruest-tr-dlg-lesen ( / )
(setq *geruest-tr-variante* (itoa (geruest-radio-index (get_tile "variante"))))
(setq *geruest-tr-farbe* (get_tile "farbe"))
(setq *geruest-tr-frei* (get_tile "farbe_frei"))
(setq *geruest-tr-abzug-a* (get_tile "abzug_a"))
(setq *geruest-tr-abzug-a-w* (get_tile "abzug_a_w"))
(setq *geruest-tr-abzug-e* (get_tile "abzug_e"))
(setq *geruest-tr-abzug-e-w* (get_tile "abzug_e_w"))
(setq *geruest-tr-beschriftung* (get_tile "beschriftung"))
)
;; Wert eines Abzugs aus dem Zustand: inaktiv -> 0, aktiv -> Zahl oder nil.
(defun geruest-tr-abzug (aktiv wert)
(if (= aktiv "1") (geruest-abzug-wert wert) 0.0)
)
;; Dialog fuer Linien-Baugruppen (Rohr-Geruest-Traeger, Omniflo-Fahrstrecke).
;; dcl-datei = Vorlage unter dcl/ (z.B. "geruest_traeger.dcl"), dlg-name =
;; Dialogname darin. Rueckgabe:
;; (variante-index farbe abzug-anfang abzug-ende beschriften-p) oder nil bei
;; Abbruch. variante-index = Index in geometrie_varianten.
(defun geruest-traeger-dialog (bg dcl-datei dlg-name / varianten dcl-pfad dat
fertig rueckgabe ergebnis aw ew)
(setq varianten (ssg-json-get bg "geometrie_varianten"))
(setq *geruest-tr-bg* bg)
;; Der Dialogstand bleibt zwischen zwei Aufrufen erhalten - aber nur fuer
;; dieselbe Baugruppe: ein Varianten-Index des Traegers passt nicht zur
;; Fahrstrecke.
(if (not (equal (ssg-json-get bg "id") *geruest-tr-id*))
(setq *geruest-tr-id* (ssg-json-get bg "id")
*geruest-tr-variante* nil))
(geruest-farb-liste-setzen
(ssg-json-get (ssg-json-get bg "attribute") "TEILEFARBE"))
;; Ausgangszustand beim ersten Aufruf bzw. wenn der gemerkte Index nach
;; einer JSON-Aenderung nicht mehr passt oder die Variante gesperrt wurde.
(if (or (null *geruest-tr-variante*)
(/= (atoi *geruest-tr-variante*)
(geruest-variante-waehlbar varianten (atoi *geruest-tr-variante*))))
(progn
(setq *geruest-tr-variante* (itoa (geruest-erste-freie-variante varianten))
*geruest-tr-farbe* "0" *geruest-tr-frei* "")
(geruest-tr-vorgaben-laden (atoi *geruest-tr-variante*))
)
)
;; Beschriftung standardmaessig an; der Haken bleibt zwischen Aufrufen
(if (null *geruest-tr-beschriftung*) (setq *geruest-tr-beschriftung* "1"))
;; Vorlage einmal fuellen (ein Radiobutton je Variante), nach der
;; Schleife loeschen.
(setq dcl-pfad
(ssg-dcl-vorlage-fuellen (geruest-dcl-vorlage-pfad dcl-datei)
"grlinie"
(list (cons "VARIANTEN"
(geruest-dcl-varianten-zeilen varianten
(geruest-indizes (length varianten)))))))
(setq fertig nil rueckgabe nil)
;; Schleife: ungueltige Eingaben zeigen den Dialog mit dem bisherigen
;; Stand erneut - DCL kann Zahlen nicht selbst pruefen.
(while (not fertig)
(setq dat (if dcl-pfad (load_dialog dcl-pfad) -1))
(if (or (< dat 0) (not (new_dialog dlg-name dat)))
(progn
(alert (ssg-textf "gr-tr-fehler-dcl"
(list (cond ((geruest-dcl-vorlage-pfad dcl-datei))
(dcl-datei)))))
(setq fertig T)
)
(progn
(set_tile "variante" (geruest-radio-key (atoi *geruest-tr-variante*)))
(geruest-varianten-sperren varianten)
(if *geruest-farb-liste*
(progn
(start_list "farbe")
(foreach f *geruest-farb-liste* (add_list f))
(end_list)
(set_tile "farbe" *geruest-tr-farbe*)
(mode_tile "farbe_frei"
(if (= (atoi *geruest-tr-farbe*) *geruest-frei-idx*) 0 1))
(action_tile "farbe"
"(mode_tile \"farbe_frei\" (if (= (atoi $value) *geruest-frei-idx*) 0 1))")
)
(progn
(mode_tile "farbe" 1)
(mode_tile "farbe_frei" 1)
)
)
(set_tile "farbe_frei" *geruest-tr-frei*)
(geruest-tr-abzug-schreiben)
(set_tile "beschriftung" *geruest-tr-beschriftung*)
(set_tile "hinweis" (ssg-text "gr-tr-hinweis"))
(action_tile "variante" "(geruest-tr-variante-gewaehlt $value)")
(action_tile "abzug_a"
"(mode_tile \"abzug_a_w\" (if (= $value \"1\") 0 1))")
(action_tile "abzug_e"
"(mode_tile \"abzug_e_w\" (if (= $value \"1\") 0 1))")
(action_tile "accept" "(geruest-tr-dlg-lesen) (done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 0) (setq fertig T))
((or (null (setq aw (geruest-tr-abzug *geruest-tr-abzug-a* *geruest-tr-abzug-a-w*)))
(null (setq ew (geruest-tr-abzug *geruest-tr-abzug-e* *geruest-tr-abzug-e-w*))))
(alert (ssg-text "gr-tr-alert-abzug"))
)
(T
(setq rueckgabe
(list (atoi *geruest-tr-variante*)
(geruest-farbwert *geruest-tr-farbe* *geruest-tr-frei*)
aw ew
(= *geruest-tr-beschriftung* "1")))
(setq fertig T)
)
)
)
)
)
(if dcl-pfad (vl-file-delete dcl-pfad))
rueckgabe
)
;; X-Positionen der beiden Querlinien einer Strebe: Anfang und Ende des
;; Flacheisens, laenge mittig um die Strebenposition x.
(defun geruest-traeger-strebe-enden (x laenge)
(list (- x (/ laenge 2.0)) (+ x (/ laenge 2.0)))
)
;; Querstrebe darstellen: an Anfang und Ende je eine Linie quer zum Traeger
;; (Laenge querlinie, mittig auf der Traegerlinie y = 0).
(defun geruest-traeger-strebe-zeichnen (x laenge querlinie lay / hq)
(setq hq (/ querlinie 2.0))
(foreach xe (geruest-traeger-strebe-enden x laenge)
(entmake (list '(0 . "LINE") (cons 8 lay)
(list 10 xe (- hq) 0.0) (list 11 xe hq 0.0)))
)
)
;; Vollstaendiger Pfad einer Dummy-DWG unter data/geruest oder nil (mit
;; Meldung), wenn sie fehlt. datei = nil -> stilles nil (nichts konfiguriert).
(defun geruest-dummy-finden (datei / pfad)
(if datei
(progn
(setq pfad (geruest-dummy-pfad datei))
(if (and pfad (findfile pfad))
pfad
(progn
(princ (ssg-textf "gr-fehler-dummy" (list (if pfad pfad datei))))
nil
)
)
)
)
)
;; Dummy-DWG als verschachtelten Block bei pt (Drehung 0) einfuegen. Ist die
;; Blockdefinition (Dateiname ohne .dwg) schon in der Zeichnung, wird per
;; Blockname eingefuegt - sonst wuerde die DWG bei jeder Strebe neu gelesen
;; (und ggf. "Block neu definieren?" gefragt). ATTREQ/ATTDIA muss der Aufrufer
;; auf 0 gesetzt haben.
(defun geruest-dummy-einfuegen (pfad pt / bname)
(setq bname (vl-filename-base pfad))
(command "_.INSERT" (if (tblsearch "BLOCK" bname) bname pfad) pt 1 1 0)
)
;; Linie + Querstreben + Muffe + ATTDEFs zu einem Block zusammenfassen und am
;; Startpunkt mit dem Richtungswinkel einfuegen. Im Block laeuft der Traeger
;; entlang +X, die Dummys werden darum ohne Drehung eingefuegt.
;; strebe = Ergebnis von geruest-traeger-strebe-cfg (nil = keine Streben)
;; strebe-pfad = Pfad der Strebe-DWG oder nil -> Rueckfall auf Querlinien
;; muffe-pfad = Pfad der Muffen-DWG oder nil -> keine Muffe; sonst eine
;; Muffe (Basispunkt Muffenmitte) am Linienende
;; Der Aufrufer sichert die Systemvariablen per ssg-start.
;; Rueckgabe: (blockname . entity) oder nil.
(defun geruest-traeger-block-bauen (bg layer netto strebe strebe-pfad muffe-pfad
werte pt winkel / lastEnt lay)
(setvar "ATTREQ" 0)
(setvar "ATTDIA" 0)
(setvar "OSMODE" 0)
;; Drehwinkel wird als Grad an INSERT uebergeben - nur eindeutig mit
;; Winkel-Nullrichtung Ost, gegen den Uhrzeigersinn, Einheit Grad.
(setvar "ANGBASE" 0.0)
(setvar "ANGDIR" 0)
(setvar "AUNITS" 0)
;; Nicht (entlast) - siehe geruest-block-bauen.
(setq lastEnt (ssg-lastent-ohne-attribute))
(setq lay (if (and layer (/= layer "")) layer "0"))
;; Aktiv setzen, damit auch die verschachtelten Dummy-INSERTs auf dem
;; Traeger-Layer landen (CLAYER sichert ssg-start immer mit).
(ssg-make-layer lay (ssg-layer-farbe-fuer-name lay) T)
(entmake (list '(0 . "LINE") (cons 8 lay)
'(10 0.0 0.0 0.0) (list 11 netto 0.0 0.0)))
;; strebe = (dummy laenge querlinie abstand rand-min rand-max)
(if strebe
(foreach x (geruest-traeger-streben netto (nth 3 strebe) (nth 4 strebe) (nth 5 strebe))
(if strebe-pfad
(geruest-dummy-einfuegen strebe-pfad (list x 0.0 0.0))
(geruest-traeger-strebe-zeichnen x (nth 1 strebe) (nth 2 strebe) lay)
)
)
)
(if muffe-pfad
(geruest-dummy-einfuegen muffe-pfad (list netto 0.0 0.0))
)
(geruest-block-abschliessen bg
(ssg-attrib-merge nil *geruest-traeger-attrib-defs*)
werte lastEnt pt (* winkel (/ 180.0 pi)))
)
;; Einfuege-Schleife: Startpunkt, Endpunkt oder Laenge, bis ESC/Enter.
;; wahl = Rueckgabe von geruest-traeger-dialog.
(defun geruest-traeger-einfuegen (bg wahl / variante teileart layer paare farbe
abzug-a abzug-e min-l max-l bez werte-basis
werte weiter p1 eingabe strecke winkel netto
ergebnis anzahl strebe strebe-pfad muffe-pfad
beschriften titel)
(setq variante (nth (car wahl) (ssg-json-get bg "geometrie_varianten")))
;; Bauteilname fuer die Meldungen ("Traeger", "Fahrstrecke")
(setq titel (geruest-titel bg))
(setq farbe (cadr wahl)
abzug-a (caddr wahl)
abzug-e (cadddr wahl)
beschriften (nth 4 wahl))
(setq *geruest-beschr-gemeldet* nil)
(setq teileart (geruest-variante-wert variante bg "teileart"))
(setq layer (geruest-variante-wert variante bg "layer"))
(setq min-l (geruest-attr-feld variante bg "LAENGE" "min"))
(setq max-l (geruest-attr-feld variante bg "LAENGE" "max"))
(setq paare (geruest-teile-paare variante nil))
(setq strebe (geruest-traeger-strebe-cfg variante bg))
;; DWGs einmal je Durchgang aufloesen - eine fehlende Datei wird so nur
;; einmal gemeldet, nicht bei jedem Traeger. Fehlt die Strebe-DWG, werden
;; stattdessen Querlinien gezeichnet; fehlt die Muffe, entfaellt sie.
(setq strebe-pfad (if strebe (geruest-dummy-finden (car strebe))))
(setq muffe-pfad (geruest-dummy-finden (geruest-traeger-muffe-dummy variante bg)))
(setq bez (geruest-traeger-bezeichnung teileart
(geruest-eintrag-name variante) abzug-a abzug-e))
;; Fuer alle Traeger dieses Durchgangs gleich - nur LAENGE aendert sich.
(setq werte-basis
(list (cons "ARTINR" (ssg-join (mapcar 'car paare) ","))
(cons "BEZEICHNUNG" bez)
(cons "TEILEART" teileart)
(cons "LAENGENABZUGANFANG" (geruest-mm-string abzug-a))
(cons "LAENGENABZUGENDE" (geruest-mm-string abzug-e))
;; Block selbstbeschreibend - die Beschriftung (auch spaeter per
;; GERUEST_TRAEGER_BESCHRIFTEN) findet darueber ihre Formatierung
(cons "VARIANTE" (geruest-eintrag-name variante))))
(if (and layer (/= layer ""))
(setq werte-basis (append werte-basis (list (cons "LAYER" layer)))))
(if (/= farbe "")
(setq werte-basis (append werte-basis (list (cons "TEILEFARBE"
(ssg-join (geruest-farbliste paare farbe) ","))))))
(setq weiter T anzahl 0)
(while weiter
(setq strecke nil)
(setq p1 (getpoint (ssg-textf "gr-tr-prompt-start" (list titel))))
(if (null p1)
(setq weiter nil)
(progn
;; 128 = beliebige Eingabe: ein Punkt liefert Laenge UND Richtung,
;; eine getippte Zahl nur die Laenge (Richtung wird nachgefragt).
(initget 128)
(setq eingabe (getpoint p1 (ssg-text "gr-tr-prompt-ende")))
;; Reihenfolge wichtig: nil ist auch listp, und "" darf erst nach dem
;; Punkt-Fall verglichen werden.
(cond
((null eingabe) (setq weiter nil))
((listp eingabe)
;; Laenge in der XY-Ebene, die Linie liegt auf Z des Startpunkts
(setq strecke (distance (list (car p1) (cadr p1))
(list (car eingabe) (cadr eingabe))))
(setq winkel (angle p1 eingabe))
)
((= eingabe "") (setq weiter nil))
((setq strecke (geruest-mm-zahl eingabe))
(setq winkel (getangle p1 (ssg-text "gr-tr-prompt-richtung")))
(if (null winkel) (setq winkel 0.0))
)
(T (princ (ssg-textf "gr-tr-eingabe-ungueltig" (list eingabe))))
)
)
)
(if (and weiter strecke)
(progn
(setq netto (geruest-traeger-laenge strecke abzug-a abzug-e))
(cond
((null netto)
(princ (ssg-textf "gr-tr-laenge-null"
(list (geruest-mm-string strecke)
(geruest-mm-string (+ abzug-a abzug-e)) titel))))
((not (geruest-traeger-pruefen netto min-l max-l))
(princ (ssg-textf "gr-tr-laenge-bereich"
(list (geruest-mm-string netto)
(if min-l (geruest-mm-string min-l) "0")
(if max-l (geruest-mm-string max-l) "-") titel))))
(T
;; Streben-Rand ausserhalb der Grenzen: informieren, aber trotzdem
;; einfuegen (der Abstand zwischen den Streben bleibt immer exakt)
(if strebe (geruest-traeger-rand-pruefen netto strebe))
(setq werte (cons (cons "LAENGE" (geruest-mm-string netto)) werte-basis))
(ssg-start nil '(("OSMODE") ("ATTREQ") ("ATTDIA")
("ANGBASE") ("ANGDIR") ("AUNITS")))
(setq ergebnis (geruest-traeger-block-bauen bg layer netto strebe
strebe-pfad muffe-pfad werte
(geruest-traeger-start p1 winkel abzug-a) winkel))
;; Beschriftung aus dem fertigen Block ableiten (dieselbe Funktion
;; wie GERUEST_TRAEGER_BESCHRIFTEN)
(if (and ergebnis beschriften)
(geruest-traeger-beschriften (cdr ergebnis) bg))
(ssg-end)
(if ergebnis
(progn
(setq anzahl (1+ anzahl))
(princ (ssg-textf "gr-tr-fertig"
(list (geruest-mm-string netto) (car ergebnis) titel)))
)
)
)
)
)
)
)
(princ (ssg-textf "gr-tr-ende" (list (itoa anzahl) titel)))
(princ)
)
;; ============================================================
;; TRAEGER-BESCHRIFTUNG
;; ============================================================
;; Eigenes TEXT-Objekt neben dem Block, z.B. "RT 80/80 L=2650". Es wird
;; AUSSCHLIESSLICH aus dem fertigen Block abgeleitet (Einfuegepunkt, Drehung,
;; Attribute LAENGE und VARIANTE) - darum nutzen das Einfuegen und
;; GERUEST_TRAEGER_BESCHRIFTEN dieselbe Funktion, und ein von Hand
;; verschobener/gedrehter Traeger wird beim Neubeschriften korrekt erfasst.
;; Der Text traegt per XDATA (*geruest-beschr-app*, 1005) den Handle seines
;; Blocks; so wird er beim Neubeschriften ersetzt statt verdoppelt.
;;
;; Formatierung aus geruest.json, "beschriftung" (Variante vor Baugruppe, je
;; Feld): NUR stil, ausrichtung, hoehe - alles andere regelt der Textstil
;; ausserhalb (es wird kein Stil angelegt). Dazu Inhalt (format/text) und
;; Layer.
;; Winkel auf [0, 2pi) normieren.
(defun geruest-winkel-norm (w)
(setq w (rem w (* 2.0 pi)))
(if (< w 0.0) (+ w (* 2.0 pi)) w)
)
;; Textwinkel entlang des Traegers nach DIN-Konvention (lesbar von unten und
;; von rechts): fuer 90 < w <= 270 Grad um 180 Grad gedreht, sonst w.
(defun geruest-text-winkel (w / e)
(setq w (geruest-winkel-norm w) e 1e-9)
(if (and (> w (+ (/ pi 2.0) e)) (<= w (+ (* 1.5 pi) e)))
(geruest-winkel-norm (+ w pi))
w
)
)
;; Ausrichtungs-Code wie bei TEXT "Ausrichten" (T/M/B + L/C/R, z.B. "BC") ->
;; (dxf72 dxf73), nil bei unbekanntem Code.
(defun geruest-ausrichtung-codes (code / h v)
(if (and code (= (type code) 'STR) (= (strlen code) 2))
(progn
(setq code (strcase code))
(setq v (cdr (assoc (substr code 1 1) '(("T" . 3) ("M" . 2) ("B" . 1)))))
(setq h (cdr (assoc (substr code 2 1) '(("L" . 0) ("C" . 1) ("R" . 2)))))
(if (and v h) (list h v))
)
)
)
;; Lage des Textes: (punkt textwinkel dxf72 dxf73). p0 = Traeger-Startpunkt,
;; w = Traegerwinkel, laenge = Traegerlaenge. Der Bezugspunkt liegt auf der
;; Linie: L = in Leserichtung linkes Ende, C = Mitte, R = rechtes Ende. Nach
;; einer 180-Grad-Drehung ist "links" also das Linienende. Unbekannter Code
;; -> BC (die Meldung gibt der Aufrufer aus).
(defun geruest-traeger-text-lage (p0 w laenge code / codes tw p1 links rechts pt)
(setq codes (geruest-ausrichtung-codes code))
(if (null codes) (setq codes '(1 1)))
(setq tw (geruest-text-winkel w))
(setq p1 (polar p0 w laenge))
;; Projektion auf die Leserichtung entscheidet, welches Ende links liegt
(if (<= (+ (* (car p0) (cos tw)) (* (cadr p0) (sin tw)))
(+ (* (car p1) (cos tw)) (* (cadr p1) (sin tw))))
(setq links p0 rechts p1)
(setq links p1 rechts p0)
)
(setq pt (cond ((= (car codes) 0) links)
((= (car codes) 2) rechts)
(T (polar p0 w (/ laenge 2.0)))))
(list pt tw (car codes) (cadr codes))
)
;; Alle Vorkommen von alt in s durch neu ersetzen.
(defun geruest-string-ersetzen (s alt neu / pos)
(setq pos (vl-string-search alt s))
(while pos
(setq s (strcat (substr s 1 pos) neu (substr s (+ pos (strlen alt) 1))))
(setq pos (vl-string-search alt s (+ pos (strlen neu))))
)
s
)
;; Beschriftungstext: %1 = Text der Variante, %2 = Laenge auf ganze mm.
(defun geruest-traeger-text-string (fmt txt laenge)
(geruest-string-ersetzen
(geruest-string-ersetzen fmt "%1" txt)
"%2" (itoa (fix (+ laenge 0.5))))
)
;; Beschriftungs-Konfiguration als Alist (format text stil ausrichtung hoehe
;; layer), je Feld Variante vor Baugruppe vor Code-Standard. Fehlt "text",
;; gilt der Variantenname bzw. ersatz-text (Block ohne bekannte Variante).
;; stil/layer = nil -> aktueller Textstil/Layer.
(defun geruest-traeger-beschr-cfg (variante bg ersatz-text / txt h)
(setq txt (geruest-beschr-feld variante bg "text"))
(if (not (and txt (= (type txt) 'STR) (/= txt "")))
(setq txt (if variante (geruest-eintrag-name variante) ersatz-text)))
(setq h (geruest-beschr-feld variante bg "hoehe"))
(list (cons "format" (cond ((geruest-beschr-feld variante bg "format")) ("%1 L=%2")))
(cons "text" (if txt txt ""))
(cons "stil" (geruest-beschr-feld variante bg "stil"))
(cons "ausrichtung" (cond ((geruest-beschr-feld variante bg "ausrichtung")) ("BC")))
(cons "hoehe" (if (numberp h) (float h) 50.0))
(cons "layer" (geruest-beschr-feld variante bg "layer")))
)
(defun geruest-beschr-feld (variante bg feld)
(geruest-obj-feld variante bg '("beschriftung") feld)
)
;; Handle des Traeger-Blocks aus der XDATA einer Beschriftung (oder nil).
(defun geruest-beschr-handle (ent / xd)
(setq xd (cadr (assoc -3 (entget ent (list *geruest-beschr-app*)))))
(if xd (cdr (assoc 1005 (cdr xd))))
)
;; Alle Beschriftungen der Zeichnung (Auswahlsatz oder nil).
(defun geruest-beschr-alle ( / )
(ssget "_X" (list '(0 . "TEXT") (list -3 (list *geruest-beschr-app*))))
)
;; Beschriftung(en) zu einem Block-Handle loeschen.
(defun geruest-beschr-entfernen (handle / ss i e)
(if (and handle (setq ss (geruest-beschr-alle)))
(progn
(setq i 0)
(repeat (sslength ss)
(setq e (ssname ss i))
(if (equal (geruest-beschr-handle e) handle) (entdel e))
(setq i (1+ i))
)
)
)
)
;; Textstil pruefen: vorhanden -> Name, sonst einmal je Lauf melden und den
;; aktuellen TEXTSTYLE nehmen. *geruest-beschr-gemeldet* setzt der Aufrufer
;; je Lauf zurueck.
(defun geruest-beschr-stil (stil / )
(cond
((and stil (/= stil "") (tblsearch "STYLE" stil)) stil)
(T
(if (and stil (/= stil "") (not (member stil *geruest-beschr-gemeldet*)))
(progn
(princ (ssg-textf "gr-tr-beschr-stil-fehlt" (list stil (getvar "TEXTSTYLE"))))
(setq *geruest-beschr-gemeldet* (cons stil *geruest-beschr-gemeldet*))
)
)
(getvar "TEXTSTYLE")
)
)
)
;; Einen Linien-Block (Traeger, Fahrstrecke) beschriften (eine evtl.
;; vorhandene Beschriftung wird ersetzt). bg = Baugruppe, aus deren JSON die
;; Formatierung kommt. Rueckgabe: "ok", "ersatz" (Block ohne bekannte
;; VARIANTE, mit den Vorgaben der Baugruppe beschriftet) oder nil (nicht
;; beschriftbar).
(defun geruest-traeger-beschriften (blockEnt bg / ed attribs vname idx variante
cfg laenge lage lay handle code erg)
(setq ed (entget blockEnt))
(setq attribs (ssg-attrib-read blockEnt))
(setq laenge (atof (cond ((cdr (assoc "LAENGE" attribs))) ("0"))))
(if (and bg (> laenge 0.0))
(progn
(setq vname (cdr (assoc "VARIANTE" attribs)))
(setq idx (if (and vname (/= vname ""))
(geruest-name-index vname (ssg-json-get bg "geometrie_varianten"))))
(setq variante (if idx (nth idx (ssg-json-get bg "geometrie_varianten"))))
(setq cfg (geruest-traeger-beschr-cfg variante bg
(cond ((cdr (assoc "TEILEART" attribs))) (""))))
(setq code (cdr (assoc "ausrichtung" cfg)))
(if (and (null (geruest-ausrichtung-codes code))
(not (member code *geruest-beschr-gemeldet*)))
(progn
(princ (ssg-textf "gr-tr-beschr-ausrichtung" (list code)))
(setq *geruest-beschr-gemeldet* (cons code *geruest-beschr-gemeldet*))
)
)
(setq lage (geruest-traeger-text-lage (cdr (assoc 10 ed)) (cdr (assoc 50 ed))
laenge code))
(setq lay (cdr (assoc "layer" cfg)))
(if (and lay (/= lay ""))
(ssg-make-layer lay (ssg-layer-farbe-fuer-name lay) nil)
(setq lay (getvar "CLAYER"))
)
(setq handle (cdr (assoc 5 ed)))
(geruest-beschr-entfernen handle)
(if (not (tblsearch "APPID" *geruest-beschr-app*)) (regapp *geruest-beschr-app*))
;; 73 ist immer >= 1, damit gilt Gruppe 11 als Ausrichtungspunkt
(if (entmake (list '(0 . "TEXT")
(cons 8 lay)
(cons 7 (geruest-beschr-stil (cdr (assoc "stil" cfg))))
(cons 10 (nth 0 lage))
(cons 11 (nth 0 lage))
(cons 40 (cdr (assoc "hoehe" cfg)))
(cons 1 (geruest-traeger-text-string
(cdr (assoc "format" cfg))
(cdr (assoc "text" cfg)) laenge))
(cons 50 (nth 1 lage))
(cons 72 (nth 2 lage))
(cons 73 (nth 3 lage))
(list -3 (list *geruest-beschr-app* (cons 1005 handle)))))
(setq erg (if variante "ok" "ersatz"))
)
)
)
erg
)
;; Alle Linien-Bloecke einer Baugruppe (Blockname GR_<id>_*) neu beschriften
;; und Beschriftungen, deren Block nicht mehr existiert, entfernen -
;; unabhaengig von der Checkbox im Einfuegedialog. Verwaiste Texte werden
;; unabhaengig vom Typ entfernt (am Text ist nicht erkennbar, zu welchem
;; Typ der geloeschte Block gehoerte).
(defun geruest-linien-beschriften-alle (bg / ss i n-ok n-ersatz n-fehl n-weg e h
ziel erg titel)
(setq *geruest-beschr-gemeldet* nil)
(setq titel (geruest-titel bg))
(setq n-ok 0 n-ersatz 0 n-fehl 0 n-weg 0)
(ssg-start nil nil)
;; Verwaiste Beschriftungen zuerst entfernen
(if (setq ss (geruest-beschr-alle))
(progn
(setq i 0)
(repeat (sslength ss)
(setq e (ssname ss i))
(setq h (geruest-beschr-handle e))
(setq ziel (if h (handent h)))
(if (not (and ziel (entget ziel)
(= (cdr (assoc 0 (entget ziel))) "INSERT")))
(progn (entdel e) (setq n-weg (1+ n-weg)))
)
(setq i (1+ i))
)
)
)
(if (setq ss (ssget "_X" (list '(0 . "INSERT")
(cons 2 (strcat "GR_" (ssg-json-get bg "id") "_*")))))
(progn
(setq i 0)
(repeat (sslength ss)
(setq erg (geruest-traeger-beschriften (ssname ss i) bg))
(cond ((= erg "ok") (setq n-ok (1+ n-ok)))
((= erg "ersatz") (setq n-ersatz (1+ n-ersatz)))
(T (setq n-fehl (1+ n-fehl))))
(setq i (1+ i))
)
(princ (ssg-textf "gr-tr-beschr-ergebnis"
(list (itoa (+ n-ok n-ersatz)) (itoa n-ersatz) (itoa n-fehl) (itoa n-weg)
titel)))
)
(princ (ssg-textf "gr-tr-beschr-keine" (list titel)))
)
(ssg-end)
(princ)
)
;; Linien-Baugruppe aus ihrer Datendatei holen, Dialog zeigen, einfuegen.
;; id = "id" der Baugruppe, daten = geparste JSON, dcl-datei/dlg-name siehe
;; geruest-traeger-dialog.
(defun geruest-linien-starten (id daten dcl-datei dlg-name / bg wahl)
(setq bg (geruest-baugruppe id daten))
(cond
((null bg)
(princ (ssg-textf "gr-fehler-baugruppe" (list id))))
((null (ssg-json-get bg "geometrie_varianten"))
(princ (ssg-textf "gr-fehler-keine-varianten" (list id))))
((null (setq wahl (geruest-traeger-dialog bg dcl-datei dlg-name)))
(princ (ssg-text "gr-abgebrochen")))
(T (geruest-traeger-einfuegen bg wahl))
)
(princ)
)
;; Beschriftung aller Bloecke einer Linien-Baugruppe (siehe
;; geruest-linien-beschriften-alle).
(defun geruest-linien-beschriften (id daten / bg)
(if (setq bg (geruest-baugruppe id daten))
(geruest-linien-beschriften-alle bg)
(princ (ssg-textf "gr-fehler-baugruppe" (list id)))
)
(princ)
)
(defun c:GERUEST_TRAEGER ( / )
(geruest-linien-starten "Traeger" (geruest-traeger-daten)
"geruest_traeger.dcl" "geruest_traeger")
)
(defun c:GERUEST_TRAEGER_BESCHRIFTEN ( / )
(geruest-linien-beschriften "Traeger" (geruest-traeger-daten))
)
;; Omniflo-Fahrstrecke: dieselbe Linien-Logik, Daten aus omniflo_fahrstrecke.json,
;; eigener Dialog dcl/fahrstrecke.dcl.
(defun c:OMNI_FAHRSTRECKE ( / )
(geruest-linien-starten "Fahrstrecke" (geruest-fahrstrecke-daten)
"fahrstrecke.dcl" "fahrstrecke")
)
(defun c:OMNI_FAHRSTRECKE_BESCHRIFTEN ( / )
(geruest-linien-beschriften "Fahrstrecke" (geruest-fahrstrecke-daten))
)
(princ "\nGeruestInsert.lsp geladen. Befehle: GERUEST, GERUEST_SAEULEN, GERUEST_ROHRABHAENGER, GERUEST_WANDBEFESTIGUNG, GERUEST_TRAEGER, GERUEST_TRAEGER_BESCHRIFTEN, OMNI_FAHRSTRECKE, OMNI_FAHRSTRECKE_BESCHRIFTEN")
(princ)