diff --git a/.gitignore b/.gitignore index 86fe85e..eaacde0 100644 --- a/.gitignore +++ b/.gitignore @@ -190,6 +190,10 @@ data/block_libraries/ils_library.dxf # Tool-Caches .skylos/ +# Eigene Geruest-Konfigurationen (vom Dialog GERUEST_* geschrieben). +# Benutzerdaten - die gepflegte Bibliothek steht in data/json/geruest.json. +cfg/geruest_user.json + # Grosse Test-Zeichnungen (Arbeitsstaende, gehoeren nicht ins Repo) /tests/test.dwg /tests/test.dxf diff --git a/CLAUDE.md b/CLAUDE.md index ba8f525..b7a7371 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -75,7 +75,7 @@ Die `start_briscad.bat` ruft `setenv.bat` auf und startet BricsCAD. | VarioFoerderer | `VarioFoerderer` | FOERDERANLAGE, VARIOFOERDERER_EDIT, Vario_Kette_Merge | | Gefaellestrecke | `Gefaellestrecke` | GEFAELLESTRECKE, GEFAELLESTRECKE_EDIT | | Omniflo (komplett) | `OmniModulInsert` | OMNI_APB_*, OMNI_W*_*, OMNI_AP*, OMNI_TEF_*, OMNI_TV_*, OMNI_EDIT | -| Geruest | `GeruestInsert` | GERUEST_ROHRABHAENGER, GERUEST_SAEULE_2ZOLL, GERUEST_SAEULE_1ZOLL, GERUEST_WANDBEFESTIGUNG | +| Geruest | `GeruestInsert` | GERUEST (ein Dialog fuer alle Bereiche) | | Connections | `Connection_Edit` + `SSG_LIB_Commands` | CONNECTION_INSERT, CONNECTION_EDIT, ILS_EINGANG_INSERT, ILS_AUSGANG_INSERT | | Export | `export` | EXPORTSIVAS, EXPORTCSV, CALLPYTHON | | Konfiguration | `SSG_LIB_Commands` + `DrawPlanquadrate` | SSG_DIM_SWITCH, SSG_SPRACHE, PLANQUADRATE_TOGGLE (DRAW_PLANQUADRATE/DELETE_PLANQUADRATE) | @@ -107,7 +107,7 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`. | `OmniModulInsert.lsp` | OMNI_LOAD, OMNI_APB_*, OMNI_W*_*, OMNI_AP60/AP110, OMNI_TEF_*, OMNI_TV_*, OMNI_APBW_*, OMNI_EDIT | Omniflo-Komponenten: Boegen, Weichen, Verbinder, Transferwagen, Edit-Dialog | | `SSG_LIB_Commands.lsp` | ILS_BTMT_Beladung, ILS_BTMT_Entladung, ILS_SCANNER_INSERT, ILS_SEPARATOR_INSERT, ILS_EINGANG_INSERT, ILS_AUSGANG_INSERT, ILS_ETF_*, ILS_GK*_*, SSG_BLOCKEDIT, SSG_DIM_SWITCH, SSG_SPRACHE | BTMT Be-/Entladung (`ils-insert-station`: wiederholtes Einfuegen von `BTMT-Beladung` bzw. `SC_Entladung`, Drehung interaktiv, Layer aus `LAYER`-Attribut, Dimension via `ssg-ils-block-laden`) + Sensoren (`ils-insert-sensor`) + Eingang-/Ausgang-Symbol (frei platzierbar wie BTMT Be-/Entladung, ueber `ils-insert-station` mit den Rohnamen `Eingang`/`Ausgang`, Bloecke `Eingang_.dwg`/`Ausgang_.dwg` in data/ils) + Dummy-Befehle fuer Menue + Doppelklick/Kontextmenue-Dispatcher + Konfiguration (2D/3D, Sprache) | | `Tro_Edit.lsp` | TRO_INSERT, TRO_EDIT | TRO-Marker (Block `TRO_SYM_`) fuer sps_skel (lib/tro_annotate.py): TRO_INSERT fuegt eine bestehende Blockdefinition an einem gewaehlten Punkt ein und setzt ID/TYPE (bricht ab, wenn der Typ noch keine Blockdefinition in der Zeichnung hat - die entsteht nur ueber tro_annotate.py); TRO_EDIT aendert ID/TYPE eines bestehenden Markers per Dialog. Typliste aus `tro_types.lsp` (von `tro_annotate.py --emit-lisp` nach DXFM_LISP geschrieben). Der Dispatcher SSG_BLOCKEDIT leitet Blocknamen `TRO_*` zu TRO_EDIT. | -| `GeruestInsert.lsp` | GERUEST_ROHRABHAENGER, GERUEST_SAEULE_2ZOLL, GERUEST_SAEULE_1ZOLL, GERUEST_WANDBEFESTIGUNG | Geruest-Baugruppen aus `data/json/geruest.json` konfigurieren und einfuegen. Dialog (`dcl/geruest.dcl`): Basis-Variante aus `geometrie_varianten` (Dropdown), Anbauteile aus `anbaugruppen` (`list_box` mit `multiple_select` - im Projekt der erste), Laenge (gegen `attribute.Laenge` min/max geprueft, Retry-Schleife) und Farbe (`attribute.Farbe`, Freitext bei `frei_option`). Baugruppen ohne `attribute`-Block (Wandbefestigung) bekommen Laenge/Farbe gesperrt. Der Block wird - wie KREISEL_n/VF_n - SELBST zusammengebaut: Dummy-DWG aus `data/geruest` liefert nur Geometrie, das Attribut-Schema kommt aus `*geruest-attrib-defs*` (ARTINR, LAENGE, BEZEICHNUNG, TEILEART, FARBE, LAYER) und wird per `ssg-attrib-merge` mit den Dummy-Tags zusammengefuehrt - sonst verschluckt `ssg-attrib-set-on` stillschweigend jeden Tag, den die Dummy-DWG nicht als ATTDEF kennt. Blockname `GR__` (Kollisionszaehler wie in OmniModulInsert). **Das `GR_`-Praefix ist nicht kosmetisch**: `SSG_BLOCKEDIT` haelt JEDEN Block mit TEILEART-Attribut fuer ein Omniflo-Element, deshalb steht in `SSG_LIB_Commands.lsp` ein `GR_*`-Zweig VOR dem Omniflo-Zweig. ARTINR listet jede Sivas-ID so oft, wie `menge` es vorgibt (benannte Gruppen mit `teile` werden rekursiv aufgeloest); BEZEICHNUNG verkettet mit `" + "`, NICHT mit Komma - die Artikelbezeichnungen enthalten selbst Kommas ("ROHR R 1\" 33,7 x 2,6"). Zuordnung Menue -> JSON laeuft ueber das ASCII-Feld `id`, weil die aeusseren JSON-Schluessel Umlaute bzw. Anfuehrungszeichen tragen. CSV-/Sivas-Export: `pattern_geruest = GR_*` (cfg/export.cfg), `csv:collect-export-blocks`/`ssg-id-collect-blocks` sammeln GR_-Bloecke wie BTMT eigenstaendig; `*geruest-attrib-defs*` traegt zusaetzlich ein leeres `ID`-ATTDEF (Voraussetzung fuer `ssg-id-check-all`, das nur bestehende ATTRIBs beschreiben kann). ARTINR/BEZEICHNUNG/TEILEART/LAENGE landen in export_csv.py/export_sivas.py in festen Spalten, FARBE/LAYER (+ ARTINR bei export_csv.py) im Merkmale-/details-JSON. Noch NICHT enthalten: Edit-Pfad (GERUEST_EDIT) | +| `GeruestInsert.lsp` | GERUEST | Geruest-Baugruppen aus `data/json/geruest.json` konfigurieren und einfuegen. **Ein Dialog fuer alle Bereiche**: oben ein Dropdown `bereich` (Rohrabhaenger / Saeulen / Wandbefestigung, Reihenfolge = Dateireihenfolge, Anzeigename aus dem ASCII-Feld `titel`). DCL hat **kein Reiter-Tile**, und da sich die Teilezeilen je Bereich unterscheiden, schliesst ein Bereichswechsel den Dialog (`done_dialog 4`) und die Schleife in `c:GERUEST` baut ihn neu auf; der Stand jedes Bereichs bleibt in `*geruest-zustand*` erhalten, ein versehentlicher Wechsel verwirft also nichts. 1"- und 2"-Saeulen liegen in EINER Baugruppe `Saeulen` mit gemeinsamer Anbauteil-Liste; **`teileart` und `dummy` duerfen je Geometrie-Variante ueberschrieben werden** (`geruest-variante-wert`, Variante gewinnt vor Baugruppe) - so liefert `R 1"` weiterhin TEILEART `Geruest Saeule 1 Zoll` und die `R 2"`-Varianten `Geruest Saeule 2 Zoll`, die Groessen bleiben im Export also unterscheidbar. **Die Dialogdefinition wird zur Laufzeit erzeugt** (`geruest-dcl-schreiben` -> temporaere Datei per `vl-filename-mktemp`, nach `unload_dialog` wieder geloescht) - es gibt KEINE `dcl/geruest.dcl`. Grund: je Anbauteil wird ein eigenes Mengenfeld gebraucht, und eine DCL-Liste (`list_box`) kann keine Eingabefelder enthalten; DCL kann weder Dateien einbinden noch Tiles zur Laufzeit erzeugen. Inhalt: Basis-Variante aus `geometrie_varianten` (Dropdown), Laenge (gegen `attribute.Laenge` min/max geprueft, Retry-Schleife), Farbe (`attribute.Teilefarbe`, Freitext bei `frei_option`) und **zwei Befestigungslisten** ("oben"/"unten"), die BEIDE alle `anbaugruppen` enthalten - dasselbe Teil kann oben, unten oder gleichzeitig an beiden Positionen sitzen, je mit eigener Stueckzahl (vorbelegt aus `menge`). Baugruppen ohne `attribute`-Block (Wandbefestigung) bekommen Laenge/Farbe gesperrt, Baugruppen ohne Anbauteile bekommen gar keine Spalten (ein leerer DCL-Cluster ist unzulaessig). Im generierten Label wird `"` zu `''` ersetzt (`geruest-dcl-label`) - fuenf Artikelnamen enthalten ein Anfuehrungszeichen, das das DCL-Label sonst vorzeitig beendet und die Datei zerstoert. **Konfigurationen**: gepflegte Bibliothek je Baugruppe unter `konfigurationen` in `geruest.json` (schreibgeschuetzt) plus eigene in `cfg/geruest_user.json` (gitignored, `geruest-user-schreiben`; getrennte Datei, damit Benutzerdaten und versionierte Bibliothek nicht kollidieren). Eigene Eintraege sind in der Liste mit `*` markiert und nur diese loeschbar. Konfigurationen verweisen ueber den ANZEIGENAMEN auf Variante und Anbauteile; ein unbekannter Name wird gemeldet und uebersprungen, statt die Konfiguration scheitern zu lassen. Speichern/Loeschen laufen ueber `done_dialog 2`/`3` (Dialog schliessen, Aktion, neu zeigen) - `getstring` funktioniert aus einem `action_tile`-Rueckruf heraus nicht. Der Block wird - wie KREISEL_n/VF_n - SELBST zusammengebaut: Dummy-DWG aus `data/geruest` liefert nur Geometrie, das Attribut-Schema kommt aus `*geruest-attrib-defs*` (ID, ARTINR, LAENGE, BEZEICHNUNG, TEILEART, TEILEFARBE, LAYER) und wird per `ssg-attrib-merge` mit den Dummy-Tags zusammengefuehrt - sonst verschluckt `ssg-attrib-set-on` stillschweigend jeden Tag, den die Dummy-DWG nicht als ATTDEF kennt. Blockname `GR__` (Kollisionszaehler wie in OmniModulInsert). **Das `GR_`-Praefix ist nicht kosmetisch**: `SSG_BLOCKEDIT` haelt JEDEN Block mit TEILEART-Attribut fuer ein Omniflo-Element, deshalb steht in `SSG_LIB_Commands.lsp` ein `GR_*`-Zweig VOR dem Omniflo-Zweig. ARTINR listet jede Sivas-ID so oft, wie die gewaehlte Stueckzahl es vorgibt - erst Basis, dann Befestigung oben, dann unten; bei einer benannten Gruppe (`teile`) wird das ganze Set n-mal wiederholt, die Mengen INNERHALB des Sets bleiben dabei erhalten (`geruest-artikelnummern-n`). BEZEICHNUNG nennt Position und Menge je Teil ("oben 2x ...") und verkettet mit `" + "`, NICHT mit Komma - die Artikelbezeichnungen enthalten selbst Kommas ("ROHR R 1\" 33,7 x 2,6"). Zuordnung Menue -> JSON laeuft ueber das ASCII-Feld `id`, weil die aeusseren JSON-Schluessel Umlaute bzw. Anfuehrungszeichen tragen. CSV-/Sivas-Export: `pattern_geruest = GR_*` (cfg/export.cfg), `csv:collect-export-blocks`/`ssg-id-collect-blocks` sammeln GR_-Bloecke wie BTMT eigenstaendig; `*geruest-attrib-defs*` traegt zusaetzlich ein leeres `ID`-ATTDEF (Voraussetzung fuer `ssg-id-check-all`, das nur bestehende ATTRIBs beschreiben kann). ARTINR/BEZEICHNUNG/TEILEART/LAENGE landen in export_csv.py/export_sivas.py in festen Spalten, TEILEFARBE/LAYER (+ ARTINR bei export_csv.py) im Merkmale-/details-JSON. Noch NICHT enthalten: Edit-Pfad (GERUEST_EDIT) | | `menu-inventory.lsp` | MenuInventory | BricsCAD Diagnostik (Menuegruppen, Plugins, Startdateien) | | `DrawPlanquadrate.lsp` | DRAW_PLANQUADRATE, DELETE_PLANQUADRATE, PLANQUADRATE_TOGGLE | Zeichnet auf Z=0 ein Planquadrat-Linienraster (senkrecht/waagerecht), im Abstand/Zaehlschema der aktiven `[Planquadrate]`-Konfiguration aus `cfg/export.cfg` (dieselbe Quelle wie `lib/export_planquadrat.py` fuer den CSV-Export). Bereich kommt NICHT aus den Zeichnungs-Extents, sondern aus derselben Blockmenge wie EXPORTCSV (`csv:collect-export-blocks`, export.lsp; siehe `plq-export-bbox`): Ursprung (Index 1) UND obere Grenze kommen BEIDE aus der tatsaechlichen Bounding-Box jedes Blocks (`csv:get-bbox`, wie `bbox_columns()`/`planquadrat_column()`/`_bbox_min()` in `lib/export_csv.py` bzw. `lib/export_planquadrat.py`), NICHT aus dem reinen Einfuegepunkt - bei einem gedrehten/exzentrisch eingefuegten Block (z.B. ein um 90 Grad gedrehter Kreisel) kann das Bounding-Box-Zentrum weit vom Einfuegepunkt abweichen; Ursprung und Zellzuordnung muessen auf derselben Referenz beruhen, sonst driften sie auseinander (realer Bug, behoben). Reine Hilfsgeometrie (Kreisel-Umfahrungsboegen als eigene Linien, Texte) zaehlt bewusst nicht mit, sonst weicht das Raster vom CSV-Export ab. `x_origin`/`y_origin` ueberschreiben den Ursprung, Fallback auf EXTMIN/EXTMAX falls keine Export-Bloecke vorhanden sind bzw. `csv:get-bbox` fuer einen Block scheitert. Spalten-/Zeilenzahl wird IMMER aufgerundet auf volle Rasterschritte, damit alle senkrechten Linien auf der letzten waagerechten enden und umgekehrt - das Raster bildet immer ein geschlossenes Rechteck. Zaehlung (alphabetisch/numerisch je Achse) als Text am linken/unteren Rand. Das fertige Raster (Linien+Texte) wird per `_.-BLOCK`/`_.INSERT` zu einem einzigen Block `ZZ_PLANQUADRATE` bei (0,0,0) zusammengefasst. Layer aus `cfg/layer.cfg` (`[allgemein]` Key `planquadrate`, Fallback `ZZ_Planquadrate`). DRAW_PLANQUADRATE entfernt vor dem Neuaufbau zuerst einen evtl. vorhandenen Block (`plq-block-entfernen`: INSERTs loeschen + `_-PURGE _B`), DELETE_PLANQUADRATE ruft nur diese Entfernung auf. PLANQUADRATE_TOGGLE ("Planquadrate an/aus", Menue: Konfiguration) prueft per `tblsearch`/`ssget "X"`, ob der Block existiert, und dispatcht auf Zeichnen bzw. Loeschen. Lazy geladen ueber `(ssg-ensure "DrawPlanquadrate")`, nicht in der Core-Ladereihenfolge. Die Planquadrat-SPALTE im CSV-Export (`export_csv.py::planquadrat_column`/`resolve_origins`, `lib/export_planquadrat.py::compute_planquadrat`/`_bbox_min`) rechnet ebenfalls durchgaengig auf Bounding-Box-Basis (nicht nur Einfuegepunkt) und liefert eine SPANNE ("A/1-J/1") statt nur einer Zelle, wenn ein Element mehr als eine Zelle ueberdeckt (Kreisel, lange VF-/GF-Ketten). | @@ -123,7 +123,6 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`. | `sprache.dcl` | SSG_SPRACHE (SSG_LIB_Commands.lsp) | | `tro_insert.dcl` | TRO_INSERT (Tro_Edit.lsp) | | `tro_edit.dcl` | TRO_EDIT (Tro_Edit.lsp) | -| `geruest.dcl` | Alle GERUEST_*-Befehle (GeruestInsert.lsp, `geruest-dialog`) - ein Dialog fuer alle Baugruppen, Listen zur Laufzeit gefuellt | | `vfl_edit.dcl` | Linienzug-Editieren/Fortsetzen (vf_linienzug.lsp, `vfl-dlg-position`, aufgerufen aus `vfl-edit-ent`) | | `vf_linienzug_wizard.dcl` | Linienzug-Assistent (Modus 1, 2 UND 3): zwei generische Einzel-Dialoge (Zahl-Eingabe, Mehrfachauswahl; `vflw-zahl`/`vflw-wahl`, aufgerufen aus `vfl-in-real`/`vfl-in-string`/`vfl-in-int` bei aktivem `*vfl-wizard-mode*`) PLUS sieben Gruppen-Dialoge, die mehrere immer unmittelbar aufeinanderfolgende Fragen in einem Bildschirm zusammenfassen (Kettenstart, AS-Element, GF-Bogen, ES-Element, Vario-Kurve, Gefaelle festlegen, Ziel-Hoehe, Horizontales Stueck - inkl. Punktwahl-Button mit X/Y/Z-Anzeige; `vflw-gruppe-*-impl` in vf_linienzug.lsp fuellen dafuer vorab eine kleine Pending-Queue `*vflw-pending*`, die einzelnen `vfl-in-*`-Aufrufe bleiben dabei unveraendert) PLUS drei Segment-Gruppen-Dialoge fuer die segmentweise Klassifizierung in Modus 2/3 (`vflw_seg_linie_m2` = Typ+GF-Neigung, `vflw_seg_linie_m3` = 4-fach-Typ+kontextabh. Wertfeld, `vflw_seg_bogen` = Typ+Variante; `vflw-seg-*-impl`). Beide Modi heben vor der Frage das aktuelle Pfad-Segment (LINE/ARC) per `vfl-seg-highlight` (`redraw`) in der Zeichnung hervor und zeigen "Segment i/n ..." im Dialogkopf. Details: `doc/variofoerderer.md` | diff --git a/Lisp/GeruestInsert.lsp b/Lisp/GeruestInsert.lsp index 2ce823c..16732c8 100644 --- a/Lisp/GeruestInsert.lsp +++ b/Lisp/GeruestInsert.lsp @@ -1,10 +1,20 @@ ;; ============================================================ ;; GeruestInsert.lsp - Geruest-Baugruppen konfigurieren und einfuegen ;; -;; Liest data/json/geruest.json (rekursiv per ssg-json-laden), -;; laesst den Nutzer im Dialog dcl/geruest.dcl eine Basis-Variante, beliebige -;; Anbauteile, Laenge und Farbe waehlen, und fuegt das Ergebnis als Block -;; GR__ ein. +;; Liest data/json/geruest.json (rekursiv per ssg-json-laden), laesst den +;; Nutzer eine Basis-Variante, Anbauteile fuer "Befestigung oben" UND +;; "Befestigung unten" (je mit eigener Stueckzahl), Laenge und Farbe waehlen, +;; und fuegt das Ergebnis als Block GR__ ein. +;; +;; Beide Befestigungslisten enthalten ALLE Anbauteile der Baugruppe - dasselbe +;; Teil kann oben, unten oder gleichzeitig an beiden Positionen sitzen. +;; +;; Die Dialogdefinition wird zur Laufzeit erzeugt (geruest-dcl-schreiben): +;; eine DCL-Liste kann keine Eingabefelder enthalten, ein Mengenfeld je Teil +;; ist also nur mit generierten Zeilen moeglich. +;; +;; 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 @@ -16,8 +26,13 @@ ;; TEILEART-Attribut fuer ein Omniflo-Element (SSG_LIB_Commands.lsp). Der ;; Doppelklick-Dispatcher erkennt Geruest-Bloecke an diesem Praefix. ;; -;; Befehle: GERUEST_ROHRABHAENGER, GERUEST_SAEULE_2ZOLL, -;; GERUEST_SAEULE_1ZOLL, GERUEST_WANDBEFESTIGUNG +;; Alle Bereiche liegen in EINEM Dialog; oben wird per Dropdown zwischen +;; Rohrabhaenger, Saeulen und Wandbefestigung umgeschaltet. DCL hat kein +;; Reiter-Tile, und da sich die Teilezeilen je Bereich unterscheiden, wird der +;; Dialog beim Wechsel geschlossen und neu aufgebaut - der Stand jedes +;; Bereichs bleibt dabei in *geruest-zustand* erhalten. +;; +;; Befehl: GERUEST ;; ============================================================ (vl-load-com) @@ -36,7 +51,7 @@ ("LAENGE" "") ("BEZEICHNUNG" "") ("TEILEART" "") - ("FARBE" "") + ("TEILEFARBE" "") ("LAYER" "") ) ) @@ -89,22 +104,39 @@ *geruest-daten* ) -;; Baugruppe ueber ihr ASCII-Feld "id" suchen. Der aeussere JSON-Schluessel -;; ("Rohrabhaenger" mit Umlaut, "2\" Saeulen" mit Anfuehrungszeichen) taugt -;; dafuer nicht - er ist aus ASCII-Quelltext nicht vergleichbar. -(defun geruest-baugruppe (id / erg) +;; Alle Baugruppen als ((id . objekt) ...) in Dateireihenfolge - diese +;; Reihenfolge bestimmt auch die Reihenfolge im Bereichs-Dropdown. +(defun geruest-baugruppen-liste ( / erg) + (setq erg nil) (foreach eintrag (geruest-daten) - ;; equal statt = : fehlt einer Baugruppe das id-Feld, liefert ssg-json-get - ;; nil - = ist mit nil-Argumenten nicht zuverlaessig. - (if (and (null erg) - (listp (cdr eintrag)) - (equal (ssg-json-get (cdr eintrag) "id") id)) - (setq erg (cdr eintrag)) + (if (listp (cdr eintrag)) + (setq erg (append erg + (list (cons (ssg-json-get (cdr eintrag) "id") (cdr eintrag))))) ) ) erg ) +;; Anzeigename einer Baugruppe fuer Dropdown und Kopfzeile. 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)) +) + ;; ============================================================ ;; TEILE AUSWERTEN @@ -168,37 +200,673 @@ ;; DIALOG ;; ============================================================ -;; "0 3 5" -> (0 3 5). Rueckgabe von get_tile bei multiple_select. -(defun geruest-indizes (s / erg tok i c) - (setq erg nil tok "" i 1) - (if (null s) (setq s "")) - (while (<= i (strlen s)) - (setq c (substr s i 1)) - (if (= c " ") - (progn - (if (/= tok "") (setq erg (cons (atoi tok) erg))) - (setq tok "") - ) - (setq tok (strcat tok c)) - ) +;; 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-menge-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 "") + ) +) + +;; Artikelnummern eines Eintrags bei frei gewaehlter Stueckzahl. +;; Einzelteil: die Sivas-ID anzahl-mal. Benannte Gruppe ("teile"): das +;; komplette Set anzahl-mal - die Mengen INNERHALB des Sets bleiben erhalten +;; (ein Schraubenset bleibt 1 Scheibe + 2 Muttern + 1 Schraube je Satz). +(defun geruest-artikelnummern-n (eintrag anzahl / teile einmal erg i) + (setq teile (ssg-json-get eintrag "teile")) + (if teile + (progn + (setq einmal nil) + (foreach teil teile + (setq einmal (append einmal (geruest-artikelnummern teil))) + ) + (setq erg nil i 0) + (while (< i anzahl) + (setq erg (append erg einmal)) + (setq i (1+ i)) + ) + erg + ) + (geruest-wiederholen + (geruest-id-string (ssg-json-get eintrag "sivas_id")) 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)) ) - (if (/= tok "") (setq erg (cons (atoi tok) erg))) (reverse erg) ) -;; Dialog zeigen. Rueckgabe: -;; (variante-index (anbau-indizes) laenge-string farbe-string) oder nil -(defun geruest-dialog (bg / dcl-pfad dat varianten anbauteile attr laenge-def - farb-opts frei-opt fertig rueckgabe ergebnis - dlg-variante dlg-anbau dlg-laenge dlg-farbe dlg-frei - min-l max-l wert-l farbe-txt) - (setq dcl-pfad (strcat (getenv "DXFM_DCL") "/geruest.dcl")) - (setq varianten (ssg-json-get bg "geometrie_varianten")) - (setq anbauteile (ssg-json-get bg "anbaugruppen")) +(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. +(defun geruest-konfig-index (name / i idx) + (setq i 1 idx 0) + (foreach k *geruest-konfig-liste* + (if (and (= idx 0) (equal (ssg-json-get (cdr k) "name") name)) + (setq idx i)) + (setq i (1+ i)) + ) + idx +) + +;; Positionsliste ("oben"/"unten") einer Konfiguration auf die Anbauteile +;; abbilden. Rueckgabe: ein Eintrag je Anbauteil - nil (nicht gewaehlt) oder +;; die Stueckzahl. Ein unbekannter Artikelname wird gemeldet und +;; uebersprungen, statt die ganze Konfiguration scheitern zu lassen. +(defun geruest-konfig-position (konfig schluessel anbauteile / mengen idx n) + (setq mengen (geruest-nil-liste (length anbauteile))) + (foreach e (ssg-json-get konfig schluessel) + (setq idx (geruest-name-index (ssg-json-get e "artikel") anbauteile)) + (if idx + (progn + (setq n (ssg-json-get e "menge")) + (setq mengen (geruest-liste-setzen mengen idx + (if (and n (numberp n) (> n 0)) (fix n) 1))) + ) + (princ (ssg-textf "gr-konfig-teil-unbekannt" + (list (ssg-json-get e "artikel") + (ssg-json-get konfig "name")))) + ) + ) + mengen +) + + +;; ============================================================ +;; DIALOGDEFINITION ERZEUGEN +;; ============================================================ +;; Die Anbauteil-Zeilen haengen an der Baugruppe, deshalb wird die DCL-Datei +;; pro Aufruf geschrieben statt fest vorgehalten: DCL kann weder Dateien +;; einbinden noch Tiles zur Laufzeit erzeugen, und eine Liste (list_box) kann +;; keine Eingabefelder enthalten - ohne Generierung gaebe es also kein +;; Mengenfeld je Teil. + +;; 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 +) + +;; Eine Befestigungsspalte. Das Mengenfeld steht LINKS vom Ankreuzfeld: die +;; Eingabefelder sind dadurch alle gleich breit und die Teilenamen beginnen +;; auf gleicher Hoehe - 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 (f anbauteile prefix titel / i) + (write-line " : boxed_column {" f) + (write-line (strcat " label = \"" titel "\";") f) + (write-line " alignment = left;" f) + (setq i 0) + (foreach a anbauteile + (write-line " : row {" f) + (write-line " alignment = left;" f) + (write-line " fixed_width = true;" f) + (write-line (strcat " : edit_box { key = \"" prefix "m" (itoa i) + "\"; label = \"\"; edit_width = 3;" + " fixed_width = true; }") f) + (write-line (strcat " : toggle { key = \"" prefix (itoa i) + "\"; label = \"" + (geruest-dcl-label (geruest-eintrag-name a)) + "\"; fixed_width = true; }") f) + (write-line " }" f) + (setq i (1+ i)) + ) + (write-line " }" f) +) + +;; Rueckgabe: Pfad der temporaeren DCL-Datei oder nil. +(defun geruest-dcl-schreiben (anbauteile / pfad f) + (setq pfad (vl-filename-mktemp "geruest" nil ".dcl")) + (setq f (open pfad "w")) + (if (null f) + nil + (progn + (write-line "geruest : dialog {" f) + (write-line " label = \"Geruest-Baugruppe konfigurieren\";" f) + ;; Bereichswechsel (Rohrabhaenger/Saeulen/Wandbefestigung). DCL hat kein + ;; Reiter-Tile, deshalb ein Dropdown; sein Rueckruf schliesst den Dialog + ;; und die Schleife baut ihn mit dem neuen Bereich neu auf - die + ;; Teilezeilen unterscheiden sich je Bereich und lassen sich zur + ;; Laufzeit nicht austauschen. + (write-line " : popup_list { key = \"bereich\"; label = \"Bereich:\"; width = 30; }" f) + (write-line " : text { key = \"kopf\"; label = \"\"; width = 64; }" f) + (write-line " : row {" f) + (write-line " : popup_list { key = \"konfig\"; label = \"Konfiguration:\"; width = 40; }" f) + (write-line " : button { key = \"kspeichern\"; label = \"Speichern...\"; fixed_width = true; }" f) + (write-line " : button { key = \"kloeschen\"; label = \"Loeschen\"; fixed_width = true; }" f) + (write-line " }" f) + (write-line " : boxed_column {" f) + (write-line " label = \"Basis\";" f) + (write-line " : popup_list { key = \"variante\"; label = \"Variante:\"; width = 56; }" f) + (write-line " : row {" f) + (write-line " : edit_box { key = \"laenge\"; label = \"Laenge (mm):\"; edit_width = 10; }" f) + (write-line " : popup_list { key = \"farbe\"; label = \"Farbe:\"; width = 25; }" f) + (write-line " : edit_box { key = \"farbe_frei\"; label = \"eigene:\"; edit_width = 12; }" f) + (write-line " }" f) + (write-line " }" f) + ;; Ohne Anbauteile bleiben die Spalten weg - ein leerer Cluster ist + ;; in DCL nicht zulaessig (betrifft z.B. die 1-Zoll-Saeule). + (if (> (length anbauteile) 0) + (progn + (write-line " : row {" f) + (geruest-dcl-spalte f anbauteile "o" "Befestigung oben (Anzahl / Teil)") + (geruest-dcl-spalte f anbauteile "u" "Befestigung unten (Anzahl / Teil)") + (write-line " }" f) + ) + ) + (write-line " ok_cancel;" f) + (write-line "}" f) + (close f) + pfad + ) + ) +) + + +;; ============================================================ +;; 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). +(defun geruest-dlg-standard ( / erg) + (setq erg nil) + (foreach a *geruest-anbauteile* + (setq erg (append erg (list (cons "0" (itoa (geruest-menge a)))))) + ) + erg +) + +;; Mengenliste (nil oder Zahl je Teil) in den Tile-Zustand umsetzen. +(defun geruest-mengen->tiles (mengen / erg i a) + (setq erg nil i 0) + (foreach m mengen + (setq a (nth i *geruest-anbauteile*)) + (setq erg (append erg + (list (if m + (cons "1" (itoa m)) + (cons "0" (itoa (geruest-menge a))))))) + (setq i (1+ i)) + ) + erg +) + +(defun geruest-dlg-lesen ( / i n) + (setq *geruest-dlg-variante* (get_tile "variante")) + (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-bereich* (get_tile "bereich")) + (setq n (length *geruest-anbauteile*) i 0) + (setq *geruest-dlg-oben* nil *geruest-dlg-unten* nil) + (while (< i n) + (setq *geruest-dlg-oben* + (append *geruest-dlg-oben* + (list (cons (get_tile (strcat "o" (itoa i))) + (get_tile (strcat "om" (itoa i))))))) + (setq *geruest-dlg-unten* + (append *geruest-dlg-unten* + (list (cons (get_tile (strcat "u" (itoa i))) + (get_tile (strcat "um" (itoa i))))))) + (setq i (1+ i)) + ) + (princ) +) + +(defun geruest-dlg-schreiben ( / i n paar) + (set_tile "variante" *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*) + (setq n (length *geruest-anbauteile*) i 0) + (while (< i n) + (setq paar (nth i *geruest-dlg-oben*)) + (set_tile (strcat "o" (itoa i)) (car paar)) + (set_tile (strcat "om" (itoa i)) (cdr paar)) + (setq paar (nth i *geruest-dlg-unten*)) + (set_tile (strcat "u" (itoa i)) (car paar)) + (set_tile (strcat "um" (itoa i)) (cdr paar)) + (setq i (1+ i)) + ) + (princ) +) + +;; Aktuell gewaehlte Farbe als Text. +(defun geruest-dlg-farbwert ( / idx) + (setq idx (atoi *geruest-dlg-farbe*)) + (cond + ((null *geruest-farb-liste*) "") + ((= idx *geruest-frei-idx*) *geruest-dlg-frei*) + ((nth idx *geruest-farb-liste*) (nth idx *geruest-farb-liste*)) + (T "") + ) +) + +;; 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)". +(defun geruest-dlg-konfig-waehlen (idx / k vidx farbe fidx) + (setq *geruest-dlg-konfig* (itoa idx)) + (if (<= idx 0) + (progn + (setq *geruest-dlg-oben* (geruest-dlg-standard)) + (setq *geruest-dlg-unten* (geruest-dlg-standard)) + ) + (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*)) + (if vidx + (setq *geruest-dlg-variante* (itoa vidx)) + (princ (ssg-textf "gr-konfig-variante-unbekannt" + (list (ssg-json-get k "variante") + (ssg-json-get k "name")))) + ) + (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-oben* + (geruest-mengen->tiles + (geruest-konfig-position k "oben" *geruest-anbauteile*))) + (setq *geruest-dlg-unten* + (geruest-mengen->tiles + (geruest-konfig-position k "unten" *geruest-anbauteile*))) + ) + ) + ) + ) + (geruest-dlg-schreiben) + (mode_tile "farbe_frei" + (if (= (atoi *geruest-dlg-farbe*) *geruest-frei-idx*) 0 1)) + (princ) +) + +(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 . menge) ...)) 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-mengen-lesen (zustand / erg i a m ok) + (setq erg nil i 0 ok T) + (foreach paar zustand + (setq a (nth i *geruest-anbauteile*)) + (if (and ok (= (car paar) "1")) + (progn + (setq m (geruest-menge-wert (cdr paar) (geruest-menge 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 "oben" (geruest-dlg-teile *geruest-dlg-oben*)) + (cons "unten" (geruest-dlg-teile *geruest-dlg-unten*)) + ) +) + +(defun geruest-dlg-teile (zustand / erg i a m) + (setq erg nil i 0) + (foreach paar zustand + (setq a (nth i *geruest-anbauteile*)) + (if (= (car paar) "1") + (progn + (setq m (geruest-menge-wert (cdr paar) (geruest-menge a))) + (if m + (setq erg (append erg + (list (list (cons "artikel" (geruest-eintrag-name a)) + (cons "menge" 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")) + "\", \"menge\": " (itoa (ssg-json-get e "menge")) " }" + (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 " \"oben\": [" f) + (geruest-teile-schreiben f (ssg-json-get k "oben")) + (write-line " ]," f) + (write-line " \"unten\": [" f) + (geruest-teile-schreiben f (ssg-json-get k "unten")) + (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") + ) + ) + ) + ) + ) + ) +) + +;; Dialogzustand eines Bereichs sichern bzw. zurueckholen, damit ein +;; Bereichswechsel die Eingaben nicht verwirft. +(defun geruest-zustand-sichern (id) + (setq *geruest-zustand* + (geruest-alist-setzen *geruest-zustand* id + (list *geruest-dlg-variante* *geruest-dlg-laenge* *geruest-dlg-farbe* + *geruest-dlg-frei* *geruest-dlg-konfig* + *geruest-dlg-oben* *geruest-dlg-unten*))) +) + +;; Rueckgabe T, wenn ein passender Zustand gefunden und gesetzt wurde. Die +;; Laengenpruefung faengt den Fall ab, dass die Datendatei zwischen zwei +;; Aufrufen geaendert wurde und die Teilezahl nicht mehr passt. +(defun geruest-zustand-laden (id / z) + (setq z (cdr (assoc id *geruest-zustand*))) + (if (and z (= (length (nth 5 z)) (length *geruest-anbauteile*))) + (progn + (setq *geruest-dlg-variante* (nth 0 z) + *geruest-dlg-laenge* (nth 1 z) + *geruest-dlg-farbe* (nth 2 z) + *geruest-dlg-frei* (nth 3 z) + *geruest-dlg-konfig* (nth 4 z) + *geruest-dlg-oben* (nth 5 z) + *geruest-dlg-unten* (nth 6 z)) + T + ) + ) +) + +;; Dialog fuer einen Bereich zeigen. Rueckgabe: +;; ("OK" variante-index ((idx . menge) ...) ((idx . menge) ...) laenge farbe) +;; - drittes Element = Befestigung oben, viertes = unten +;; ("WECHSEL" bereich-index) - Nutzer hat den Bereich umgestellt +;; nil - abgebrochen +(defun geruest-dialog (bg bereich-idx bereiche / dcl-pfad dat attr laenge-def + farb-opts frei-opt min-l max-l fertig rueckgabe + ergebnis wert-l oben-m unten-m name neu-idx) + (setq *geruest-varianten* (ssg-json-get bg "geometrie_varianten")) + (setq *geruest-anbauteile* (ssg-json-get bg "anbaugruppen")) (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 "Farbe"))) + (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"))) @@ -220,46 +888,68 @@ ) ) - (setq dlg-variante "0" dlg-anbau "" dlg-laenge "" dlg-farbe "0" dlg-frei "") + (setq *geruest-konfig-liste* (geruest-konfigs bg)) + ;; Frueheren Stand dieses Bereichs wiederherstellen, sonst Ausgangszustand + (if (null (geruest-zustand-laden (ssg-json-get bg "id"))) + (progn + (setq *geruest-dlg-variante* "0" *geruest-dlg-laenge* "" + *geruest-dlg-farbe* "0" *geruest-dlg-frei* "" + *geruest-dlg-konfig* "0") + (setq *geruest-dlg-oben* (geruest-dlg-standard)) + (setq *geruest-dlg-unten* (geruest-dlg-standard)) + ) + ) (setq fertig nil rueckgabe nil) - ;; Schleife, damit der Dialog nach einer ungueltigen Laenge mit den - ;; bisherigen Eingaben erneut erscheint (Projektmuster, vgl. vf_linienzug). + ;; Die Dialogdefinition haengt nur an der Teileliste, nicht am Zustand - + ;; einmal erzeugen und erst nach der Schleife loeschen (unload_dialog muss + ;; vorher gelaufen sein). + (setq dcl-pfad (geruest-dcl-schreiben *geruest-anbauteile*)) + + ;; Schleife: ungueltige Eingaben, Speichern und Loeschen zeigen den Dialog + ;; mit dem bisherigen Stand erneut (Projektmuster, vgl. vf_linienzug). (while (not fertig) - (setq dat (load_dialog dcl-pfad)) - (if (or (< dat 0) (not (new_dialog "geruest" dat))) + (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 - (set_tile "kopf" (ssg-textf "gr-dlg-kopf" - (list (ssg-json-get bg "teileart")))) + (start_list "bereich") + (foreach b bereiche (add_list (geruest-titel (cdr b)))) + (end_list) + (set_tile "bereich" (itoa bereich-idx)) + + (set_tile "kopf" (ssg-textf "gr-dlg-kopf" (list (geruest-titel bg)))) + + (start_list "konfig") + (add_list (ssg-text "gr-konfig-keine")) + (foreach k *geruest-konfig-liste* (add_list (geruest-konfig-name k))) + (end_list) + (set_tile "konfig" *geruest-dlg-konfig*) (start_list "variante") - (foreach v varianten (add_list (geruest-eintrag-name v))) + (foreach v *geruest-varianten* (add_list (geruest-eintrag-name v))) (end_list) - (set_tile "variante" dlg-variante) - (start_list "anbau") - (foreach a anbauteile (add_list (geruest-eintrag-name a))) - (end_list) - (if (/= dlg-anbau "") (set_tile "anbau" dlg-anbau)) - - ;; Laenge/Farbe nur bei Baugruppen mit "attribute"-Block - (if laenge-def - (set_tile "laenge" dlg-laenge) - (mode_tile "laenge" 1) - ) (if *geruest-farb-liste* (progn (start_list "farbe") (foreach f *geruest-farb-liste* (add_list f)) (end_list) - (set_tile "farbe" dlg-farbe) - (set_tile "farbe_frei" dlg-frei) + ) + ) + + ;; Alle Tiles aus dem Zustand fuellen (auch Haken und Mengenfelder) + (geruest-dlg-schreiben) + + ;; 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 dlg-farbe) *geruest-frei-idx*) 0 1)) + (if (= (atoi *geruest-dlg-farbe*) *geruest-frei-idx*) 0 1)) (action_tile "farbe" "(mode_tile \"farbe_frei\" (if (= (atoi $value) *geruest-frei-idx*) 0 1))") ) @@ -269,49 +959,96 @@ ) ) - (action_tile "accept" - (strcat "(setq dlg-variante (get_tile \"variante\"))" - "(setq dlg-anbau (get_tile \"anbau\"))" - "(setq dlg-laenge (get_tile \"laenge\"))" - "(setq dlg-farbe (get_tile \"farbe\"))" - "(setq dlg-frei (get_tile \"farbe_frei\"))" - "(done_dialog 1)")) + (action_tile "konfig" "(geruest-dlg-konfig-waehlen (atoi $value))") + ;; Bereichswechsel: Dialog schliessen, die Schleife in c:GERUEST baut + ;; ihn mit dem neuen Bereich neu auf. + (action_tile "bereich" "(geruest-dlg-lesen) (done_dialog 4)") + (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 1) (setq fertig T)) + ((= ergebnis 0) (setq fertig T)) + + ;; Bereichswechsel: Stand dieses Bereichs sichern und an den + ;; Aufrufer zurueckmelden, damit er den neuen Bereich oeffnet. + ((= ergebnis 4) + (setq neu-idx (atoi *geruest-dlg-bereich*)) + (geruest-zustand-sichern (ssg-json-get bg "id")) + (if (= neu-idx bereich-idx) + nil + (progn + (setq rueckgabe (list "WECHSEL" neu-idx)) + (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) + (setq name (getstring T (ssg-text "gr-konfig-name"))) + (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))) + ) + (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)) + ) + ;; Laenge pruefen - Bereichspruefung erfolgt im Projekt immer nach ;; done_dialog, DCL kann das nicht. ((and laenge-def - (progn (setq wert-l (atof dlg-laenge)) - (or (= (ssg-cfg-trim dlg-laenge) "") + (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 dlg-laenge + (list *geruest-dlg-laenge* (if min-l (rtos min-l 2 0) "0") (if max-l (rtos max-l 2 0) "-")))) ) + (T - (setq farbe-txt - (cond - ((null *geruest-farb-liste*) "") - ((= (atoi dlg-farbe) *geruest-frei-idx*) dlg-frei) - (T (nth (atoi dlg-farbe) *geruest-farb-liste*)) + (setq oben-m (geruest-mengen-lesen *geruest-dlg-oben*)) + (setq unten-m (geruest-mengen-lesen *geruest-dlg-unten*)) + (if (or (null oben-m) (null unten-m)) + (alert (ssg-text "gr-alert-menge")) + (progn + (setq rueckgabe + (list "OK" + (atoi *geruest-dlg-variante*) + (cdr oben-m) + (cdr unten-m) + (if laenge-def *geruest-dlg-laenge* "") + (geruest-dlg-farbwert))) + (setq fertig T) ) ) - (setq rueckgabe (list (atoi dlg-variante) - (geruest-indizes dlg-anbau) - (if laenge-def dlg-laenge "") - (if farbe-txt farbe-txt ""))) - (setq fertig T) ) ) ) ) ) + (if dcl-pfad (vl-file-delete dcl-pfad)) rueckgabe ) @@ -334,11 +1071,14 @@ ) ;; Dummy-Geometrie + eigene ATTDEFs zu einem Block zusammenfassen und am -;; gewaehlten Punkt einfuegen. werte = alist der berechneten Attributwerte. +;; gewaehlten Punkt einfuegen. werte = alist der berechneten Attributwerte, +;; variante = gewaehlte Geometrie-Variante (liefert ggf. eine eigene DWG). ;; Rueckgabe: (blockname . entity) oder nil. -(defun geruest-block-bauen (bg werte pt / datei dummy lastEnt dummyEnt e ss - bname blockEnt dummy-attribs attribs) - (setq datei (ssg-json-get bg "dummy")) +(defun geruest-block-bauen (bg variante werte pt / datei dummy lastEnt dummyEnt + e ss bname blockEnt dummy-attribs attribs) + ;; Eine Variante darf eine eigene Dummy-DWG mitbringen; sonst gilt die der + ;; Baugruppe. + (setq datei (geruest-variante-wert variante bg "dummy")) (setq dummy (geruest-dummy-pfad datei)) (if (or (null dummy) (null (findfile dummy))) (progn @@ -410,65 +1150,109 @@ ;; ABLAUF ;; ============================================================ -(defun geruest-run (id / bg wahl varianten anbauteile variante gewaehlt - artnr bezeichnung teile-namen werte pt ergebnis) - (setq bg (geruest-baugruppe id)) - (cond - ((null bg) - (if (geruest-daten) (princ (ssg-textf "gr-fehler-baugruppe" (list id)))) +;; Artikelnummern einer Befestigungsauswahl ((index . menge) ...). +(defun geruest-auswahl-artnr (auswahl anbauteile / erg) + (setq erg nil) + (foreach paar auswahl + (setq erg (append erg + (geruest-artikelnummern-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 oben unten laenge farbe) - die Dialog-Rueckgabe ohne +;; das fuehrende "OK". +(defun geruest-einfuegen (bg wahl / varianten anbauteile variante teileart + artnr bezeichnung teile-namen werte pt ergebnis) + (setq varianten (ssg-json-get bg "geometrie_varianten")) + (setq anbauteile (ssg-json-get bg "anbaugruppen")) + (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 oben, dann unten. Dasselbe Teil an beiden + ;; Positionen erscheint entsprechend mehrfach. + (setq artnr (geruest-artikelnummern variante)) + (setq artnr (append artnr (geruest-auswahl-artnr (cadr wahl) anbauteile))) + (setq artnr (append artnr (geruest-auswahl-artnr (caddr wahl) anbauteile))) + + ;; 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-oben")))) + (setq teile-namen (append teile-namen + (geruest-auswahl-namen (caddr wahl) anbauteile + (ssg-text "gr-position-unten")))) + (setq bezeichnung (ssg-join teile-namen " + ")) + + (setq werte (list (cons "ARTINR" (ssg-join artnr ",")) + (cons "BEZEICHNUNG" bezeichnung) + (cons "TEILEART" teileart))) + (if (/= (nth 3 wahl) "") + (setq werte (append werte (list (cons "LAENGE" (nth 3 wahl)))))) + (if (/= (nth 4 wahl) "") + (setq werte (append werte (list (cons "TEILEFARBE" (nth 4 wahl)))))) + + (setq pt (getpoint (ssg-text "gr-prompt-einfuegepunkt"))) + (if (null pt) + (princ (ssg-text "gr-abgebrochen")) + (progn + (ssg-start "Geruest" '(("OSMODE") ("ATTREQ") ("ATTDIA"))) + (setq ergebnis (geruest-block-bauen bg variante werte pt)) + (ssg-end) + (if ergebnis + (princ (ssg-textf "gr-fertig" (list teileart (car ergebnis)))) + ) ) - ((null (setq varianten (ssg-json-get bg "geometrie_varianten"))) - (princ (ssg-textf "gr-fehler-keine-varianten" (list id))) - ) - (T - (setq anbauteile (ssg-json-get bg "anbaugruppen")) - (setq wahl (geruest-dialog bg)) - (if (null wahl) - (princ (ssg-text "gr-abgebrochen")) - (progn - (setq variante (nth (car wahl) varianten)) - (setq gewaehlt nil) - (foreach i (cadr wahl) - (if (< i (length anbauteile)) - (setq gewaehlt (append gewaehlt (list (nth i anbauteile)))) - ) + ) + (princ) +) + + +;; Ein Dialog fuer alle Geruest-Bereiche. Der Bereichswechsel laeuft ueber +;; diese Schleife, weil der Dialog dafuer neu aufgebaut werden muss. +(defun c:GERUEST ( / bereiche idx bg erg weiter) + (setq *geruest-zustand* nil) + (setq bereiche (geruest-baugruppen-liste)) + (if (null bereiche) + (princ (ssg-text "gr-fehler-keine-baugruppen")) + (progn + (setq idx 0 weiter T) + (while weiter + (setq bg (cdr (nth idx bereiche))) + (cond + ((null (ssg-json-get bg "geometrie_varianten")) + (princ (ssg-textf "gr-fehler-keine-varianten" + (list (ssg-json-get bg "id")))) + (setq weiter nil) ) - - ;; ARTINR: jede Sivas-ID so oft, wie "menge" es vorgibt - (setq artnr (geruest-artikelnummern variante)) - (foreach a gewaehlt - (setq artnr (append artnr (geruest-artikelnummern a))) - ) - - ;; BEZEICHNUNG: mit " + " verketten - die Artikelbezeichnungen - ;; enthalten selbst Kommas ("ROHR R 1\" 33,7 x 2,6"). - (setq teile-namen (list (ssg-json-get bg "teileart") - (geruest-eintrag-name variante))) - (foreach a gewaehlt - (setq teile-namen (append teile-namen - (list (geruest-eintrag-name a)))) - ) - (setq bezeichnung (ssg-join teile-namen " + ")) - - (setq werte (list (cons "ARTINR" (ssg-join artnr ",")) - (cons "BEZEICHNUNG" bezeichnung) - (cons "TEILEART" (ssg-json-get bg "teileart")))) - (if (/= (caddr wahl) "") - (setq werte (append werte (list (cons "LAENGE" (caddr wahl)))))) - (if (/= (cadddr wahl) "") - (setq werte (append werte (list (cons "FARBE" (cadddr wahl)))))) - - (setq pt (getpoint (ssg-text "gr-prompt-einfuegepunkt"))) - (if (null pt) - (princ (ssg-text "gr-abgebrochen")) - (progn - (ssg-start "Geruest" '(("OSMODE") ("ATTREQ") ("ATTDIA"))) - (setq ergebnis (geruest-block-bauen bg werte pt)) - (ssg-end) - (if ergebnis - (princ (ssg-textf "gr-fertig" - (list (ssg-json-get bg "teileart") (car ergebnis)))) - ) + (T + (setq erg (geruest-dialog bg idx bereiche)) + (cond + ((null erg) + (princ (ssg-text "gr-abgebrochen")) + (setq weiter nil)) + ((equal (car erg) "WECHSEL") (setq idx (cadr erg))) + (T + (setq weiter nil) + (geruest-einfuegen bg (cdr erg))) ) ) ) @@ -478,11 +1262,5 @@ (princ) ) - -(defun c:GERUEST_ROHRABHAENGER () (geruest-run "Rohrabhaenger")) -(defun c:GERUEST_SAEULE_2ZOLL () (geruest-run "Saeulen_2Zoll")) -(defun c:GERUEST_SAEULE_1ZOLL () (geruest-run "Saeulen_1Zoll")) -(defun c:GERUEST_WANDBEFESTIGUNG () (geruest-run "Wandbefestigung")) - -(princ "\nGeruestInsert.lsp geladen. Befehle: GERUEST_ROHRABHAENGER, GERUEST_SAEULE_2ZOLL, GERUEST_SAEULE_1ZOLL, GERUEST_WANDBEFESTIGUNG") +(princ "\nGeruestInsert.lsp geladen. Befehl: GERUEST") (princ) diff --git a/Lisp/ssg_core.lsp b/Lisp/ssg_core.lsp index 8972d77..bc040eb 100644 --- a/Lisp/ssg_core.lsp +++ b/Lisp/ssg_core.lsp @@ -1767,6 +1767,33 @@ (cdr (assoc key obj)) ) +;; String fuer die Ausgabe in JSON maskieren (Gegenstueck zu ssg-json-string). +;; Der Backslash MUSS zuerst geprueft werden, sonst wuerde die Maskierung der +;; Anfuehrungszeichen gleich wieder mitmaskiert. +;; Hinweis: csv:json-escape in export.lsp leistet dasselbe, ist aber modul-lokal +;; am Export-Modul - bewusst nicht zusammengelegt, das waere ein eigener Schritt. +(defun ssg-json-escape (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 + (cond + ((= c "\\") "\\\\") + ((= c "\"") "\\\"") + ((= c (chr 10)) "\\n") + ((= c (chr 13)) "\\r") + ((= c (chr 9)) "\\t") + (T c) + ) + ) + ) + (setq i (1+ i)) + ) + erg +) + ;; Verschachteltes JSON-Objekt parsen (1 Ebene tief) ;; {"section": {"key": val, ...}, ...} diff --git a/data/geruest/R2-Saeule_dummy.dwg b/data/geruest/R2-Saeule_dummy.dwg index a0b9d74..7c50828 100644 Binary files a/data/geruest/R2-Saeule_dummy.dwg and b/data/geruest/R2-Saeule_dummy.dwg differ diff --git a/data/geruest/Rohrabhaenger_dummy.dwg b/data/geruest/Rohrabhaenger_dummy.dwg index 1064414..fcecd90 100644 Binary files a/data/geruest/Rohrabhaenger_dummy.dwg and b/data/geruest/Rohrabhaenger_dummy.dwg differ diff --git a/data/json/geruest.json b/data/json/geruest.json index 3ef2d41..85d6b42 100644 --- a/data/json/geruest.json +++ b/data/json/geruest.json @@ -1,6 +1,7 @@ { "Rohrabhänger": { "id": "Rohrabhaenger", + "titel": "Rohrabhaenger", "teileart": "Geruest Rohrabhaenger", "dummy": "Rohrabhaenger_dummy.dwg", "attribute": { @@ -10,11 +11,11 @@ "max": 6000, "einheit": "mm" }, - "Farbe": { + "Teilefarbe": { "typ": "auswahl", "optionen": [ - "RAL7035", - "RAL7030" + "RAL 7035 Lichtgrau", + "RAL 7030 Steingrau" ], "frei_erlaubt": true, "frei_option": "Individuell" @@ -133,50 +134,39 @@ "sivas_id": 922021003, "menge": 1 } - ] - }, - "1\" Säulen": { - "id": "Saeulen_1Zoll", - "teileart": "Geruest Saeule 1 Zoll", - "dummy": "R2-Saeule_dummy.dwg", - "attribute": { - "Laenge": { - "typ": "number", - "min": 0, - "max": 6000, - "einheit": "mm" - }, - "Farbe": { - "typ": "auswahl", - "optionen": [ - "RAL7035", - "RAL7030" - ], - "frei_erlaubt": true, - "frei_option": "Individuell" - } - }, - "geometrie_varianten": [ + ], + "konfigurationen": [ { - "name": "R 1\"", - "teile": [ - { - "artikel": "ROHR R 1\" 33,7 x 2,6", - "sivas_id": 821006026, - "menge": 1 - }, - { - "artikel": "CAD - BODENSÄULE R 1\" - BODENPLATTE KPL.", - "sivas_id": 821004200, - "menge": 1 - } + "name": "Abhaengung Spannpratze / Rohrklammer", + "variante": "ROHR R 1\" 33,7 x 2,6", + "laenge": 2000, + "teilefarbe": "RAL 7035 Lichtgrau", + "oben": [ + { "artikel": "SPANNPRATZE SELBSTSICHERND KPL.", "menge": 1 }, + { "artikel": "Schraubenset", "menge": 1 } + ], + "unten": [ + { "artikel": "ROHRKLAMMER 1\" KPL. VERTIKAL", "menge": 2 } + ] + }, + { + "name": "Abhaengung Duebel / T-Verbindung", + "variante": "ROHR R 1\" 33,7 x 2,6 einseitig gequetscht", + "laenge": 1500, + "teilefarbe": "RAL 7035 Lichtgrau", + "oben": [ + { "artikel": "DURCHSTECKANKER HST M12X115X20", "menge": 2 } + ], + "unten": [ + { "artikel": "ROHRKLAMMER 1\" T-VERBINDUNG KPL.", "menge": 1 } ] } ] }, - "2\" Säulen": { - "id": "Saeulen_2Zoll", - "teileart": "Geruest Saeule 2 Zoll", + "Säulen": { + "id": "Saeulen", + "titel": "Saeulen", + "teileart": "Geruest Saeule", "dummy": "R2-Saeule_dummy.dwg", "attribute": { "Laenge": { @@ -185,11 +175,11 @@ "max": 6000, "einheit": "mm" }, - "Farbe": { + "Teilefarbe": { "typ": "auswahl", "optionen": [ - "RAL7035", - "RAL7030" + "RAL 7035 Lichtgrau", + "RAL 7030 Steingrau" ], "frei_erlaubt": true, "frei_option": "Individuell" @@ -208,8 +198,25 @@ ] }, "geometrie_varianten": [ + { + "name": "R 1\"", + "teileart": "Geruest Saeule 1 Zoll", + "teile": [ + { + "artikel": "ROHR R 1\" 33,7 x 2,6", + "sivas_id": 821006026, + "menge": 1 + }, + { + "artikel": "CAD - BODENSÄULE R 1\" - BODENPLATTE KPL.", + "sivas_id": 821004200, + "menge": 1 + } + ] + }, { "name": "R 2\"", + "teileart": "Geruest Saeule 2 Zoll", "teile": [ { "artikel": "BODENSÄULE R 2\" 60,3 MIT FUSSPLATTE", @@ -225,6 +232,7 @@ }, { "name": "R 2\" Haltearm 1-ARMIG", + "teileart": "Geruest Saeule 2 Zoll", "teile": [ { "artikel": "BODENSÄULE R 2\" 60,3 MIT FUSSPLATTE", @@ -245,6 +253,7 @@ }, { "name": "R 2\" Haltearm 2-ARMIG 90 Grad", + "teileart": "Geruest Saeule 2 Zoll", "teile": [ { "artikel": "BODENSÄULE R 2\" 60,3 MIT FUSSPLATTE", @@ -265,6 +274,7 @@ }, { "name": "R 2\" Haltearm 2-ARMIG 180 Grad", + "teileart": "Geruest Saeule 2 Zoll", "teile": [ { "artikel": "BODENSÄULE R 2\" 60,3 MIT FUSSPLATTE", @@ -285,6 +295,7 @@ }, { "name": "R 2\" Haltearm 3-ARMIG", + "teileart": "Geruest Saeule 2 Zoll", "teile": [ { "artikel": "BODENSÄULE R 2\" 60,3 MIT FUSSPLATTE", @@ -340,10 +351,43 @@ "sivas_id": 922083009, "menge": 2 } + ], + "konfigurationen": [ + { + "name": "Bodensaeule 1 Zoll, Standardhoehe", + "variante": "R 1\"", + "laenge": 1000, + "teilefarbe": "RAL 7035 Lichtgrau", + "oben": [], + "unten": [] + }, + { + "name": "Bodensaeule 2 Zoll, Haltearm 1-armig", + "variante": "R 2\" Haltearm 1-ARMIG", + "laenge": 1200, + "teilefarbe": "RAL 7035 Lichtgrau", + "oben": [ + { "artikel": "KLEMMRING MIT MUFFE EINFACH KPL.", "menge": 2 } + ], + "unten": [] + }, + { + "name": "Bodensaeule 2 Zoll, Haltearm 2-armig 90 Grad", + "variante": "R 2\" Haltearm 2-ARMIG 90 Grad", + "laenge": 1200, + "teilefarbe": "RAL 7035 Lichtgrau", + "oben": [ + { "artikel": "KLEMMRING MIT MUFFE DOPPELT 90 Grad, KPL.", "menge": 2 } + ], + "unten": [ + { "artikel": "QUERSCHELLE F. 1\" UND 2\" ROHR GESCHW./VZ. M. 4X M10X30 KPL.", "menge": 2 } + ] + } ] }, "Wandbefestigung": { "id": "Wandbefestigung", + "titel": "Wandbefestigung", "teileart": "Geruest Wandbefestigung", "dummy": "Rohrabhaenger_dummy.dwg", "geometrie_varianten": [ @@ -414,6 +458,24 @@ } ] } + ], + "konfigurationen": [ + { + "name": "Wandflansch mit Duebeln", + "variante": "WANDFLANSCH EINZELTEIL", + "oben": [ + { "artikel": "Nylondübel 12x60 inkl. Holzschrauben", "menge": 1 } + ], + "unten": [] + }, + { + "name": "Winkel 300 mit Schwerlastanker", + "variante": "WINKEL 60X60X6X300", + "oben": [ + { "artikel": "HST M12X115X20", "menge": 1 } + ], + "unten": [] + } ] } } \ No newline at end of file diff --git a/dcl/geruest.dcl b/dcl/geruest.dcl deleted file mode 100644 index 201871c..0000000 --- a/dcl/geruest.dcl +++ /dev/null @@ -1,61 +0,0 @@ -// ============================================================ -// geruest.dcl - Konfiguration einer Geruest-Baugruppe -// Ein Dialog fuer ALLE Baugruppen (Rohrabhaenger, Saeulen, Wandbefestigung). -// Kopfzeile, Varianten-Dropdown, Farbliste und Anbauteil-Liste werden zur -// Laufzeit aus data/json/geruest.json befuellt - siehe -// Lisp/GeruestInsert.lsp, Abschnitt "DIALOG". -// Baugruppen ohne "attribute"-Block (z.B. Wandbefestigung) bekommen die -// Laengen- und Farbfelder per mode_tile gesperrt. -// ============================================================ - -geruest : dialog { - label = "Geruest-Baugruppe konfigurieren"; - - : text { - key = "kopf"; - label = ""; - width = 64; - } - - : boxed_column { - label = "Basis"; - - : popup_list { - key = "variante"; - label = "Variante:"; - width = 56; - } - - : row { - : edit_box { - key = "laenge"; - label = "Laenge (mm):"; - edit_width = 10; - } - : popup_list { - key = "farbe"; - label = "Farbe:"; - width = 18; - } - : edit_box { - key = "farbe_frei"; - label = "eigene:"; - edit_width = 12; - } - } - } - - : boxed_column { - label = "Anbauteile (Mehrfachauswahl mit Strg)"; - - : list_box { - key = "anbau"; - label = ""; - multiple_select = true; - width = 64; - height = 12; - } - } - - ok_cancel; -} diff --git a/lang/de_DE.json b/lang/de_DE.json index dca1407..ee52218 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -981,6 +981,20 @@ "gr-alert-laenge": "Laenge ungueltig: '%1' mm. Erlaubt: %2 bis %3 mm.", "gr-prompt-einfuegepunkt": "\nEinfuegepunkt waehlen: ", "gr-abgebrochen": "\nAbgebrochen.", - "gr-fertig": "\nGeruest-Baugruppe '%1' eingefuegt (Block %2)." + "gr-fertig": "\nGeruest-Baugruppe '%1' eingefuegt (Block %2).", + "gr-alert-menge": "Ungueltige Stueckzahl. Erlaubt ist eine ganze Zahl ab 1; ein leeres Feld uebernimmt den Vorgabewert.", + "gr-position-oben": "oben", + "gr-position-unten": "unten", + "gr-konfig-keine": "", + "gr-konfig-name": "\nName der Konfiguration: ", + "gr-konfig-gespeichert": "\nKonfiguration '%1' gespeichert.", + "gr-konfig-geloescht": "\nKonfiguration '%1' geloescht.", + "gr-konfig-loeschen-frage": "\nKonfiguration '%1' wirklich loeschen? [Ja/Nein] : ", + "gr-konfig-nichts-gewaehlt": "\nKeine Konfiguration gewaehlt.", + "gr-konfig-bibliothek-fest": "\nKonfigurationen der Bibliothek koennen nicht geloescht werden - nur eigene (mit * markiert).", + "gr-konfig-teil-unbekannt": "\nGeruest: Anbauteil '%1' aus Konfiguration '%2' ist bei dieser Baugruppe unbekannt - uebersprungen.", + "gr-konfig-variante-unbekannt": "\nGeruest: Variante '%1' aus Konfiguration '%2' ist bei dieser Baugruppe unbekannt - Auswahl unveraendert.", + "gr-fehler-schreiben": "\nGeruest: Datei konnte nicht geschrieben werden: %1", + "gr-fehler-keine-baugruppen": "\nGeruest: Die Datendatei enthaelt keine Baugruppen." } } diff --git a/lang/en_GB.json b/lang/en_GB.json index 67a507e..be66089 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -981,6 +981,20 @@ "gr-alert-laenge": "Invalid length: '%1' mm. Allowed: %2 to %3 mm.", "gr-prompt-einfuegepunkt": "\nSelect insertion point: ", "gr-abgebrochen": "\nCancelled.", - "gr-fertig": "\nScaffold assembly '%1' inserted (block %2)." + "gr-fertig": "\nScaffold assembly '%1' inserted (block %2).", + "gr-alert-menge": "Invalid quantity. Enter a whole number of 1 or more; an empty field uses the default.", + "gr-position-oben": "top", + "gr-position-unten": "bottom", + "gr-konfig-keine": "", + "gr-konfig-name": "\nConfiguration name: ", + "gr-konfig-gespeichert": "\nConfiguration '%1' saved.", + "gr-konfig-geloescht": "\nConfiguration '%1' deleted.", + "gr-konfig-loeschen-frage": "\nReally delete configuration '%1'? [Ja/Nein] : ", + "gr-konfig-nichts-gewaehlt": "\nNo configuration selected.", + "gr-konfig-bibliothek-fest": "\nLibrary configurations cannot be deleted - only your own (marked with *).", + "gr-konfig-teil-unbekannt": "\nScaffold: add-on part '%1' from configuration '%2' is unknown for this assembly - skipped.", + "gr-konfig-variante-unbekannt": "\nScaffold: variant '%1' from configuration '%2' is unknown for this assembly - selection unchanged.", + "gr-fehler-schreiben": "\nScaffold: could not write file: %1", + "gr-fehler-keine-baugruppen": "\nScaffold: the data file contains no assemblies." } } diff --git a/menu/SSG_LIB.mnu b/menu/SSG_LIB.mnu index 539922f..cc6d597 100644 --- a/menu/SSG_LIB.mnu +++ b/menu/SSG_LIB.mnu @@ -110,12 +110,7 @@ [~APBW 110 R550/67,5 208x347]^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R550_675_208 [<-~APBW 110 R550/67,5 58x497]^C^C(ssg-ensure "OmniModulInsert") OMNI_APBW_R550_675_58 [<-Element bearbeiten]^C^C(ssg-ensure "OmniModulInsert") OMNI_EDIT -[->Geruest] -[Rohrabhaenger]^C^C(ssg-ensure "GeruestInsert") GERUEST_ROHRABHAENGER -[->Saeulen] -[2 Zoll Saeulen]^C^C(ssg-ensure "GeruestInsert") GERUEST_SAEULE_2ZOLL -[<-1 Zoll Saeulen]^C^C(ssg-ensure "GeruestInsert") GERUEST_SAEULE_1ZOLL -[<-Wandbefestigung]^C^C(ssg-ensure "GeruestInsert") GERUEST_WANDBEFESTIGUNG +[Geruest...]^C^C(ssg-ensure "GeruestInsert") GERUEST [->SPS] [->TRO] [Marker einfuegen]^C^C(ssg-ensure "Tro_Edit") TRO_INSERT diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index 36920bf..6b76493 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -471,12 +471,27 @@ (tu-eq "json/escape-newline" (chr 10) (ssg-json-get (ssg-json-parse "{\"a\": \"\\n\"}") "a")) + ;; ssg-json-escape: Gegenstueck zum Parser + (tu-eq "json-escape/quote" "a\\\"b" (ssg-json-escape "a\"b")) + (tu-eq "json-escape/backslash" "a\\\\b" (ssg-json-escape "a\\b")) + (tu-eq "json-escape/newline" "a\\nb" (ssg-json-escape (strcat "a" (chr 10) "b"))) + (tu-eq "json-escape/leer" "" (ssg-json-escape nil)) + ;; Rundlauf: maskieren -> parsen ergibt den Originaltext zurueck + (tu-eq "json-escape/rundlauf" "ROHRKLAMMER 1\" KPL. VERTIKAL" + (ssg-json-get + (ssg-json-parse + (strcat "{\"a\": \"" + (ssg-json-escape "ROHRKLAMMER 1\" KPL. VERTIKAL") "\"}")) + "a")) + ;; Echte Datendatei: der eigentliche Praxisfall (setq _datei (strcat (getenv "DXFMAKRO") "/data/json/geruest.json")) (if (findfile _datei) (progn (setq _daten (ssg-json-laden _datei)) - (tu-eq "json/geruest-baugruppen" 4 (length _daten)) + ;; Drei Bereiche: Rohrabhaenger, Saeulen (1 und 2 Zoll zusammengefuehrt), + ;; Wandbefestigung + (tu-eq "json/geruest-baugruppen" 3 (length _daten)) ;; Baugruppe ueber das ASCII-Feld "id" finden (aeusserer Schluessel ;; traegt Umlaute bzw. Anfuehrungszeichen) (setq _bg nil) @@ -509,7 +524,8 @@ ;; ------------------------------------------------------------ ;; TESTS: GeruestInsert.lsp - reine Hilfsfunktionen ;; ------------------------------------------------------------ -(defun tu-tests-geruest ( / _eintrag _gruppe) +(defun tu-tests-geruest ( / _eintrag _gruppe _teile _konfig _rec _pfad _f + _zurueck _bgtest _saeulen) (princ "\n[GeruestInsert]") (if (and (null (atoms-family 1 '("GERUEST-ARTIKELNUMMERN"))) (atoms-family 1 '("SSG-ENSURE"))) @@ -518,11 +534,6 @@ (if (null (atoms-family 1 '("GERUEST-ARTIKELNUMMERN"))) (tu-record nil "geruest/modul" "GeruestInsert.lsp nicht geladen") (progn - ;; Indizes aus get_tile bei multiple_select - (tu-eq "geruest-indizes/leer" '() (geruest-indizes "")) - (tu-eq "geruest-indizes/eins" '(0) (geruest-indizes "0")) - (tu-eq "geruest-indizes/drei" '(0 3 5) (geruest-indizes "0 3 5")) - (tu-eq "geruest-id-string/int" "821006026" (geruest-id-string 821006026)) (tu-eq "geruest-id-string/str" "0_B10071" (geruest-id-string "0_B10071")) (tu-eq "geruest-id-string/nil" nil (geruest-id-string nil)) @@ -547,6 +558,126 @@ (tu-eq "geruest-artnr/ohne-menge" '("333") (geruest-artikelnummern (ssg-json-parse "{\"artikel\": \"C\", \"sivas_id\": 333}"))) + + ;; --- Mengenfeld --- + (tu-eq "geruest-zahl-p/ziffern" T (geruest-zahl-p "12")) + (tu-eq "geruest-zahl-p/text" nil (geruest-zahl-p "1a")) + (tu-eq "geruest-zahl-p/leer" nil (geruest-zahl-p "")) + ;; Leeres Feld uebernimmt die JSON-Vorgabe, ungueltiges liefert nil + (tu-eq "geruest-menge-wert/leer" 2 (geruest-menge-wert "" 2)) + (tu-eq "geruest-menge-wert/zahl" 5 (geruest-menge-wert "5" 2)) + (tu-eq "geruest-menge-wert/null" nil (geruest-menge-wert "0" 2)) + (tu-eq "geruest-menge-wert/text" nil (geruest-menge-wert "abc" 2)) + (tu-eq "geruest-menge-wert/negativ" nil (geruest-menge-wert "-3" 2)) + + ;; --- Stueckzahl ersetzt die JSON-Menge (Einzelteil) --- + (tu-eq "geruest-artnr-n/einzelteil" '("825002019" "825002019" "825002019") + (geruest-artikelnummern-n _eintrag 3)) + ;; --- Gruppe: ganzes Set n-mal, Mengen INNERHALB bleiben erhalten --- + (tu-eq "geruest-artnr-n/gruppe" '("111" "222" "222" "111" "222" "222") + (geruest-artikelnummern-n _gruppe 2)) + + ;; --- DCL-Label: Anfuehrungszeichen wuerde die Datei zerstoeren --- + (tu-eq "geruest-dcl-label/quote" "ROHRKLAMMER 1'' KPL. VERTIKAL" + (geruest-dcl-label "ROHRKLAMMER 1\" KPL. VERTIKAL")) + (tu-eq "geruest-dcl-label/ohne" "WINKEL 60X60X6X60" + (geruest-dcl-label "WINKEL 60X60X6X60")) + + ;; --- Hilfslisten --- + (tu-eq "geruest-nil-liste" '(nil nil nil) (geruest-nil-liste 3)) + (tu-eq "geruest-liste-setzen" '(nil 7 nil) + (geruest-liste-setzen (geruest-nil-liste 3) 1 7)) + (tu-eq "geruest-alist-setzen/neu" '(("a" . 1)) + (geruest-alist-setzen nil "a" 1)) + (tu-eq "geruest-alist-setzen/ersetzt" '(("a" . 2)) + (geruest-alist-setzen '(("a" . 1)) "a" 2)) + (tu-eq "geruest-zahl-string/int" "2000" (geruest-zahl-string 2000)) + (tu-eq "geruest-zahl-string/leer" "" (geruest-zahl-string nil)) + + ;; --- Konfiguration auf Anbauteile abbilden --- + (setq _teile (list _eintrag _gruppe)) + (tu-eq "geruest-name-index/treffer" 1 + (geruest-name-index "Schraubenset" _teile)) + (tu-eq "geruest-name-index/fehlt" nil + (geruest-name-index "gibt es nicht" _teile)) + ;; Bekanntes Teil wird uebernommen, unbekanntes uebersprungen (nicht + ;; die ganze Konfiguration verworfen) + (setq _konfig (ssg-json-parse + (strcat "{\"name\": \"K1\", \"oben\": [" + "{\"artikel\": \"Schraubenset\", \"menge\": 3}," + "{\"artikel\": \"unbekannt\", \"menge\": 1}]}"))) + (tu-eq "geruest-konfig-position" '(nil 3) + (geruest-konfig-position _konfig "oben" _teile)) + + ;; --- Variante gewinnt vor Baugruppe (TEILEART/dummy) --- + ;; Die zusammengefuehrte Saeulen-Baugruppe unterscheidet 1 Zoll und + ;; 2 Zoll nur noch ueber das teileart-Feld der Variante. + (setq _bgtest (ssg-json-parse + "{\"teileart\": \"Basis\", \"dummy\": \"basis.dwg\"}")) + (tu-eq "geruest-variante-wert/variante-gewinnt" "Speziell" + (geruest-variante-wert + (ssg-json-parse "{\"teileart\": \"Speziell\"}") _bgtest "teileart")) + (tu-eq "geruest-variante-wert/rueckfall" "Basis" + (geruest-variante-wert + (ssg-json-parse "{\"name\": \"X\"}") _bgtest "teileart")) + (tu-eq "geruest-variante-wert/ohne-variante" "basis.dwg" + (geruest-variante-wert nil _bgtest "dummy")) + + ;; --- Zusammengefuehrte Saeulen-Baugruppe --- + (setq _saeulen nil) + (foreach _e (geruest-baugruppen-liste) + (if (equal (car _e) "Saeulen") (setq _saeulen (cdr _e)))) + (tu-eq "geruest/saeulen-vorhanden" T (if _saeulen T nil)) + (if _saeulen + (progn + (tu-eq "geruest/saeulen-varianten" 6 + (length (ssg-json-get _saeulen "geometrie_varianten"))) + (tu-eq "geruest/saeulen-anbauteile" 7 + (length (ssg-json-get _saeulen "anbaugruppen"))) + (tu-eq "geruest/saeulen-titel" "Saeulen" (geruest-titel _saeulen)) + ;; R 1" ist die erste Variante und traegt die eigene TEILEART + (tu-eq "geruest/saeulen-1zoll-teileart" "Geruest Saeule 1 Zoll" + (geruest-variante-wert + (car (ssg-json-get _saeulen "geometrie_varianten")) + _saeulen "teileart")) + ) + ) + + ;; --- Rundlauf Schreiber -> Parser --- + ;; Prueft den Konfigurations-Schreiber ohne BricsCAD-Dialog. Bewusst in + ;; eine temporaere Datei: ein Test darf cfg/geruest_user.json nicht + ;; ueberschreiben. Die Testdaten enthalten absichtlich Anfuehrungs- + ;; zeichen - genau daran wuerde eine fehlende Maskierung scheitern. + (setq _rec + (list (cons "name" "Mein \"Satz\"") + (cons "variante" "ROHR R 1\" 33,7 x 2,6") + (cons "laenge" "2000") + (cons "teilefarbe" "RAL 7035 Lichtgrau") + (cons "oben" (list (list (cons "artikel" "ROHRKLAMMER 1\" KPL. VERTIKAL") + (cons "menge" 2)))) + (cons "unten" nil))) + (setq _pfad (vl-filename-mktemp "grtest" nil ".json")) + (setq _f (open _pfad "w")) + (write-line "{ \"Test\": [" _f) + (geruest-konfig-liste-schreiben _f (list _rec)) + (write-line "] }" _f) + (close _f) + (setq _zurueck (car (ssg-json-get (ssg-json-laden _pfad) "Test"))) + (vl-file-delete _pfad) + (tu-eq "geruest-schreiber/name" "Mein \"Satz\"" + (ssg-json-get _zurueck "name")) + (tu-eq "geruest-schreiber/variante" "ROHR R 1\" 33,7 x 2,6" + (ssg-json-get _zurueck "variante")) + (tu-eq "geruest-schreiber/laenge" "2000" + (ssg-json-get _zurueck "laenge")) + (tu-eq "geruest-schreiber/farbe" "RAL 7035 Lichtgrau" + (ssg-json-get _zurueck "teilefarbe")) + (tu-eq "geruest-schreiber/oben-artikel" "ROHRKLAMMER 1\" KPL. VERTIKAL" + (ssg-json-get (car (ssg-json-get _zurueck "oben")) "artikel")) + (tu-eq "geruest-schreiber/oben-menge" 2 + (ssg-json-get (car (ssg-json-get _zurueck "oben")) "menge")) + (tu-eq "geruest-schreiber/unten-leer" nil + (ssg-json-get _zurueck "unten")) ) ) )