;; ============================================================ ;; 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__ 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/.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/.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 = "", 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 = 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 i und 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"); 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 = ""). (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__ 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_-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__*) 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)