diff --git a/CLAUDE.md b/CLAUDE.md index 0b0de11..ba8f525 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -75,6 +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 | | 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) | @@ -92,7 +93,7 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`. | Datei | Befehle | Beschreibung | |---|---|---| -| `ssg_core.lsp` | - | Kernel: ssg-start/ssg-end, ssg-cfg-or, ssg-attrib-read/write, Layer/Block-Ops. Zentrale Helfer: `ssg-lisp-verzeichnis`/`ssg-lisp-datei-pfad` (Lisp-Pfad-Ermittlung), `ssg-rot-matrix-zy` (4x4 Rz(hz)*Ry(vert)-Rotationsmatrix, clusteruebergreifend), GUI-Aus-Schalter `*ssg-gui-aus*` + `ssg-gui-aus`/`ssg-gui-an`/`ssg-gui-p` (fuer automatische Tests: schaltet DCL-/Wizard-Dialoge ab, Module fallen auf Konsolen-/getXXX-Pfad zurueck; der Linienzug hat zusaetzlich `*vfl-headless*`, das die Konsolen-Rueckfrage selbst verbietet - siehe VarioFoerderer-Zeile). **Layer-Zuordnung**: `ssg-layer-cfg-load`/`-reset` (liest `cfg/layer.cfg` lazy), `ssg-layer-cfg-eintrag` (reine Aufloesung "Layername, Farbe" -> `(layer . farbe)`, in `tests/test_unit.lsp` getestet), `ssg-layer-eintrag` (Suchreihenfolge `[ils_]` -> `[allgemein]`), `ssg-layer-anlegen` (Layer mit cfg-Farbe anlegen, optional aktivieren) und `ssg-layer-setzen` (Blockreferenz auf den Baugruppen-Layer legen) - fuer ZUSAMMENGEBAUTE Baugruppen; `ssg-block-layer-vorgabe` (LAYER-Attribut -> `ssg-ils-block-ebene`) fuer nur EINGEFUEGTE Bloecke. Alle liefern nil, wenn nichts konfiguriert ist, und lassen den Layer dann unangetastet | +| `ssg_core.lsp` | - | Kernel: ssg-start/ssg-end, ssg-cfg-or, ssg-attrib-read/write, Layer/Block-Ops. Zentrale Helfer: `ssg-lisp-verzeichnis`/`ssg-lisp-datei-pfad` (Lisp-Pfad-Ermittlung), `ssg-rot-matrix-zy` (4x4 Rz(hz)*Ry(vert)-Rotationsmatrix, clusteruebergreifend), GUI-Aus-Schalter `*ssg-gui-aus*` + `ssg-gui-aus`/`ssg-gui-an`/`ssg-gui-p` (fuer automatische Tests: schaltet DCL-/Wizard-Dialoge ab, Module fallen auf Konsolen-/getXXX-Pfad zurueck; der Linienzug hat zusaetzlich `*vfl-headless*`, das die Konsolen-Rueckfrage selbst verbietet - siehe VarioFoerderer-Zeile). **Layer-Zuordnung**: `ssg-layer-cfg-load`/`-reset` (liest `cfg/layer.cfg` lazy), `ssg-layer-cfg-eintrag` (reine Aufloesung "Layername, Farbe" -> `(layer . farbe)`, in `tests/test_unit.lsp` getestet), `ssg-layer-eintrag` (Suchreihenfolge `[ils_]` -> `[allgemein]`), `ssg-layer-anlegen` (Layer mit cfg-Farbe anlegen, optional aktivieren) und `ssg-layer-setzen` (Blockreferenz auf den Baugruppen-Layer legen) - fuer ZUSAMMENGEBAUTE Baugruppen; `ssg-block-layer-vorgabe` (LAYER-Attribut -> `ssg-ils-block-ebene`) fuer nur EINGEFUEGTE Bloecke. Alle liefern nil, wenn nichts konfiguriert ist, und lassen den Layer dann unangetastet. **JSON**: drei Parser nebeneinander, bewusst nicht zusammengelegt - `ssg-load-json`/`ssg-parse-json-array` (flaches Array von Objekten) und `ssg-cfg-parse-nested` (genau 1 Ebene) sind ZEILENbasiert und koennen weder echte Verschachtelung noch mehrzeilige Arrays, maskierte Anfuehrungszeichen (`\"`) oder Kommas in Strings lesen; ihre Konsumenten (Konfiguration, Sprache, VF-Spec) sind auf dieses Verhalten eingestellt und bleiben unangetastet. Fuer alles andere `ssg-json-laden`/`ssg-json-parse`/`ssg-json-get` (rekursiver Descent-Parser ueber einen Positionszeiger): Objekt -> Alist, Array -> Liste, true -> T, false/null -> nil (beide nil, "Feld fehlt" ist also nicht von "Feld ist false" unterscheidbar), `\uXXXX` nur fuer ASCII. In `tests/test_unit.lsp` (`tu-tests-json`) gegen die echte Geruest-Datendatei getestet. Dazu `ssg-join` (Liste + Trennzeichen -> String) | | `ssg_ks_insert.lsp` | - | Gemeinsame KS-Extraktion + Block-Einfuegeprimitiven (von VarioFoerderer UND Gefaellestrecke genutzt, frueher in beiden dupliziert): `vec-length`, `ks-line-axis`, `ks-normalize-name`, `ks-relativize`/`-absolutize`, `ensure-block-loaded`, `extract-ks-from-block[-raw]` (Cache `*ks-cache*`), `insert-block-by-ks` (nur Rz), `insert-inclined-scaled-block`/`insert-rotated-block-with-ks` (Rz*Ry). MNL laedt es als Core-Modul VOR Gefaellestrecke/VarioFoerderer; vf_core/Gefaellestrecke haben einen guarded Nachlade-Load fuers isolierte Test-Laden | | `ssg_lang.lsp` | - | Sprachumschaltung Deutsch/Englisch (ssg-text, ssg-textf, *ssg-lang*) | | `ssg_dbg.lsp` | - | Debug-Logging (dbgopen, dbgmsg, dbgflush, dbgclose) | @@ -106,6 +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) | | `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). | @@ -121,6 +123,7 @@ 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 new file mode 100644 index 0000000..2ce823c --- /dev/null +++ b/Lisp/GeruestInsert.lsp @@ -0,0 +1,488 @@ +;; ============================================================ +;; 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. +;; +;; 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). +;; +;; 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. +;; +;; Befehle: GERUEST_ROHRABHAENGER, GERUEST_SAEULE_2ZOLL, +;; GERUEST_SAEULE_1ZOLL, GERUEST_WANDBEFESTIGUNG +;; ============================================================ + +(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" "") + ("FARBE" "") + ("LAYER" "") + ) + ) +) + +(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")) + ) +) + +(defun geruest-json-pfad ( / basis) + (if (setq basis (geruest-data-verzeichnis)) + (strcat basis "/json/geruest.json") + ) +) + +(defun geruest-dummy-pfad (datei / basis) + (if (and datei (setq basis (geruest-data-verzeichnis))) + (strcat basis "/geruest/" datei) + ) +) + +;; Datendatei einmal je Zeichnungssitzung laden. +(defun geruest-daten ( / pfad) + (if (null *geruest-daten*) + (progn + (setq pfad (geruest-json-pfad)) + (cond + ((null pfad) (princ (ssg-text "gr-fehler-datenpfad"))) + ((null (findfile pfad)) (princ (ssg-textf "gr-fehler-json-fehlt" (list pfad)))) + (T + (setq *geruest-daten* (ssg-json-laden pfad)) + (if (null *geruest-daten*) + (princ (ssg-textf "gr-fehler-json-lesbar" (list pfad))) + ) + ) + ) + ) + ) + *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) + (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)) + ) + ) + erg +) + + +;; ============================================================ +;; 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") + ) +) + +(defun geruest-menge (eintrag / m) + (setq m (ssg-json-get eintrag "menge")) + (if (and m (numberp m) (> m 0)) (fix m) 1) +) + +;; 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 Artikelnummern eines Eintrags, jede gemaess "menge" wiederholt. +;; Benannte Gruppen ("teile") werden rekursiv aufgeloest. +(defun geruest-artikelnummern (eintrag / erg teile) + (setq erg nil) + (setq teile (ssg-json-get eintrag "teile")) + (if teile + (foreach teil teile + (setq erg (append erg (geruest-artikelnummern teil))) + ) + (setq erg (geruest-wiederholen + (geruest-id-string (ssg-json-get eintrag "sivas_id")) + (geruest-menge eintrag))) + ) + erg +) + + +;; ============================================================ +;; 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)) + ) + (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")) + (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 min-l (if laenge-def (ssg-json-get laenge-def "min"))) + (setq max-l (if laenge-def (ssg-json-get laenge-def "max"))) + + ;; Farboptionen + freie Option ans Listenende; Index davon merkt sich der + ;; Callback, um das Freitextfeld freizuschalten. + (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))) + ) + ) + ) + ) + + (setq dlg-variante "0" dlg-anbau "" dlg-laenge "" dlg-farbe "0" dlg-frei "") + (setq fertig nil rueckgabe nil) + + ;; Schleife, damit der Dialog nach einer ungueltigen Laenge mit den + ;; bisherigen Eingaben erneut erscheint (Projektmuster, vgl. vf_linienzug). + (while (not fertig) + (setq dat (load_dialog dcl-pfad)) + (if (or (< 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 "variante") + (foreach v 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) + (mode_tile "farbe_frei" + (if (= (atoi dlg-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) + ) + ) + + (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 "cancel" "(done_dialog 0)") + (setq ergebnis (start_dialog)) + (unload_dialog dat) + + (cond + ((/= ergebnis 1) (setq fertig T)) + ;; 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) "") + (and min-l (< wert-l min-l)) + (and max-l (> wert-l max-l))))) + (alert (ssg-textf "gr-alert-laenge" + (list 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 rueckgabe (list (atoi dlg-variante) + (geruest-indizes dlg-anbau) + (if laenge-def dlg-laenge "") + (if farbe-txt farbe-txt ""))) + (setq fertig T) + ) + ) + ) + ) + ) + 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. +;; 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")) + (setq dummy (geruest-dummy-pfad datei)) + (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) + (setq lastEnt (entlast)) + + ;; 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)) + + ;; 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*)) + (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 (Dummy-INSERT + 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)) + ;; pause = Drehwinkel interaktiv am Cursor + (command "_.INSERT" bname pt 1 1 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 +;; ============================================================ + +(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)))) + ) + ((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)))) + ) + ) + + ;; 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)))) + ) + ) + ) + ) + ) + ) + ) + (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) diff --git a/Lisp/SSG_LIB_Commands.lsp b/Lisp/SSG_LIB_Commands.lsp index d8698d5..870d10f 100644 --- a/Lisp/SSG_LIB_Commands.lsp +++ b/Lisp/SSG_LIB_Commands.lsp @@ -400,6 +400,14 @@ ) ) + ;; Geruest-Baugruppen -> nativer Attribut-Editor. + ;; MUSS vor dem Omniflo-Zweig stehen: Geruest-Bloecke tragen ein + ;; TEILEART-Attribut und wuerden dort sonst als Omniflo-Element erkannt. + ((wcmatch bname "GR_*") + (princ (ssg-textf "cmd-attribute-bearbeiten" (list bname))) + (command "_.EATTEDIT") + ) + ;; Omniflo-Bloecke -> OMNI_EDIT ;; Erkennung: TEILEART-Attribut vorhanden, AP110-Blockname oder rein numerisch ((progn diff --git a/Lisp/export.lsp b/Lisp/export.lsp index 6680688..6ecbfc1 100644 --- a/Lisp/export.lsp +++ b/Lisp/export.lsp @@ -198,12 +198,18 @@ ;; --- String fuer JSON escapen (Anfuehrungszeichen/Backslash) --- ;; Konvertiert nicht-String-Werte (z.B. T, nil, Zahlen) defensiv in String. +;; ssg-str-replace-all (ssg_lang.lsp) statt vl-string-subst: vl-string-subst +;; ersetzt nur das ERSTE Vorkommen, nicht alle - bei einem Attributwert mit +;; MEHREREN Anfuehrungszeichen (z.B. BEZEICHNUNG einer Geruest-Baugruppe mit +;; zwei Zoll-Angaben wie 'F. 2" UND 1" ROHR') blieb das zweite Vorkommen +;; unescaped stehen und brach die JSON-Struktur beim Python-Parsen (realer +;; Bug, behoben) - export_raw.json/EXPORTCSV. (defun csv:json-escape (s) (if (not (= (type s) 'STR)) (setq s (if s (vl-princ-to-string s) "")) ) - (setq s (vl-string-subst "\\\\" "\\" s)) - (vl-string-subst "\\\"" "\"" s) + (setq s (ssg-str-replace-all "\\\\" "\\" s)) + (ssg-str-replace-all "\\\"" "\"" s) ) @@ -1082,6 +1088,10 @@ ;; ils-insert-station in SSG_LIB_Commands.lsp). Anders als Separator/ ;; Scanner werden sie von KEINEM anderen Element mitgezaehlt - export_csv.py ;; UND export_sivas.py fuehren sie beide als eigene TeileArt-Zeilen. +;; GR_* = Geruest-Baugruppen (siehe pattern_geruest in cfg/export.cfg, +;; GeruestInsert.lsp). Wie BTMT eigenstaendig und von keinem anderen +;; Element mitgezaehlt - export_csv.py UND export_sivas.py fuehren sie +;; beide als eigene TeileArt-Zeilen. ;; Gibt Auswahlsatz zurueck oder nil. (defun csv:collect-export-blocks ( / ss-all ss-out i ename ed bname) (setq ss-all (ssget "X" (list (cons 0 "INSERT")))) @@ -1101,6 +1111,7 @@ (wcmatch bname (export:pattern "pattern_scanner" "Scanner*")) (wcmatch bname (export:pattern "pattern_btmt_beladung" "BTMT-Beladung*")) (wcmatch bname (export:pattern "pattern_btmt_entladung" "SC_Entladung*")) + (wcmatch bname (export:pattern "pattern_geruest" "GR_*")) ;; Rein numerische Namen = Omniflo SivasNr (Boegen/Weichen) ;; Rein LISP-intern (Python klassifiziert Omniflo stattdessen ;; ueber exakten Katalog-Match), daher fest im Code statt in diff --git a/Lisp/ssg_core.lsp b/Lisp/ssg_core.lsp index b69d061..8972d77 100644 --- a/Lisp/ssg_core.lsp +++ b/Lisp/ssg_core.lsp @@ -1561,6 +1561,213 @@ ) +;; ------------------------------------------------------------ +;; STRING-LISTE VERKETTEN +;; ------------------------------------------------------------ +;; (ssg-join '("a" "b" "c") ",") -> "a,b,c"; leere Liste -> "". +;; Alle Elemente muessen Strings sein (Zahlen vorher mit itoa/rtos wandeln). +(defun ssg-join (lst sep / s erst) + (setq s "" erst T) + (foreach x lst + (if erst + (setq s x erst nil) + (setq s (strcat s sep x)) + ) + ) + s +) + + +;; ------------------------------------------------------------ +;; REKURSIVER JSON-PARSER +;; ------------------------------------------------------------ +;; Ergaenzung zu ssg-parse-json-array/ssg-cfg-parse-nested, die BEIDE +;; zeilenbasiert und flach arbeiten und daher prinzipiell weder echte +;; Verschachtelung noch mehrzeilige Arrays, maskierte Anfuehrungszeichen +;; (\") oder Kommas innerhalb von Strings lesen koennen. Die beiden alten +;; Parser bleiben unveraendert - ihre Konsumenten (Konfiguration, Sprache, +;; VF-Spec) sind auf ihr Verhalten eingestellt. +;; +;; Abbildung: Objekt -> Alist (("key" . wert) ...), Array -> Liste, +;; String -> STR, Zahl -> INT/REAL, true -> T, false/null -> nil. +;; Da false und null beide nil ergeben, ist "Feld fehlt" nicht von +;; "Feld ist false" unterscheidbar. +;; \uXXXX wird nur fuer ASCII (< 128) aufgeloest, sonst "?" - echte +;; Umlaute stehen in unseren Daten direkt als UTF-8 in der Datei. + +(defun ssg-json-zeichen ( / ) + (if (<= *ssg-json-pos* *ssg-json-len*) + (substr *ssg-json-txt* *ssg-json-pos* 1) + ) +) + +(defun ssg-json-weiter ( / ) + (setq *ssg-json-pos* (1+ *ssg-json-pos*)) +) + +(defun ssg-json-leerraum ( / c) + (while (and (setq c (ssg-json-zeichen)) + (member c '(" " "\t" "\r" "\n"))) + (ssg-json-weiter) + ) +) + +;; String ab dem oeffnenden Anfuehrungszeichen lesen, Escapes aufloesen. +(defun ssg-json-string ( / c erg fertig code) + (ssg-json-weiter) + (setq erg "" fertig nil) + (while (and (not fertig) (setq c (ssg-json-zeichen))) + (cond + ((= c "\"") (setq fertig T) (ssg-json-weiter)) + ((= c "\\") + (ssg-json-weiter) + (setq c (ssg-json-zeichen)) + ;; equal statt = : nach dem Backslash kann das Textende erreicht sein + ;; (c = nil), und = ist mit nil-Argumenten nicht zuverlaessig. + (cond + ((equal c "n") (setq erg (strcat erg "\n")) (ssg-json-weiter)) + ((equal c "t") (setq erg (strcat erg "\t")) (ssg-json-weiter)) + ((equal c "r") (setq erg (strcat erg "\r")) (ssg-json-weiter)) + ;; AutoLISP kennt nur \\ \" \e \n \r \t - Backspace/Seitenvorschub + ;; daher ueber chr statt als Escape-Literal. + ((equal c "b") (setq erg (strcat erg (chr 8))) (ssg-json-weiter)) + ((equal c "f") (setq erg (strcat erg (chr 12))) (ssg-json-weiter)) + ((equal c "u") + (ssg-json-weiter) + (setq code (ssg-json-hex4)) + (setq erg (strcat erg (if (< code 128) (chr code) "?"))) + ) + ;; \" \\ \/ und alles andere: Zeichen unveraendert uebernehmen + (c (setq erg (strcat erg c)) (ssg-json-weiter)) + ) + ) + (T (setq erg (strcat erg c)) (ssg-json-weiter)) + ) + ) + erg +) + +;; Vier Hex-Ziffern lesen und als Zahl liefern (Position rueckt mit). +(defun ssg-json-hex4 ( / i c code ziffer) + (setq code 0 i 0) + (while (< i 4) + (setq c (ssg-json-zeichen)) + (if c + (progn + (setq ziffer + (cond + ((and (>= (ascii c) 48) (<= (ascii c) 57)) (- (ascii c) 48)) + ((and (>= (ascii c) 97) (<= (ascii c) 102)) (+ 10 (- (ascii c) 97))) + ((and (>= (ascii c) 65) (<= (ascii c) 70)) (+ 10 (- (ascii c) 65))) + (T 0) + ) + ) + (setq code (+ (* code 16) ziffer)) + (ssg-json-weiter) + ) + (setq i 4) + ) + (setq i (1+ i)) + ) + code +) + +;; Zahl bzw. true/false/null bis zum naechsten Trennzeichen lesen. +(defun ssg-json-literal ( / start s c) + (setq start *ssg-json-pos*) + (while (and (setq c (ssg-json-zeichen)) + (not (member c '("," "}" "]" " " "\t" "\r" "\n")))) + (ssg-json-weiter) + ) + (setq s (substr *ssg-json-txt* start (- *ssg-json-pos* start))) + (cond + ((= s "true") T) + ((= s "false") nil) + ((= s "null") nil) + ((vl-string-search "." s) (atof s)) + (T (atoi s)) + ) +) + +(defun ssg-json-objekt ( / erg key c vorher) + (ssg-json-weiter) + (setq erg nil) + (ssg-json-leerraum) + (while (and (setq c (ssg-json-zeichen)) (/= c "}")) + (setq vorher *ssg-json-pos*) + (if (= c "\"") + (progn + (setq key (ssg-json-string)) + (ssg-json-leerraum) + (if (equal (ssg-json-zeichen) ":") (ssg-json-weiter)) + (setq erg (cons (cons key (ssg-json-wert)) erg)) + ) + ;; Komma oder unerwartetes Zeichen ueberspringen + (ssg-json-weiter) + ) + (ssg-json-leerraum) + ;; Schutz gegen Endlosschleife bei fehlerhafter Datei: kommt die Position + ;; nicht voran, wird zwangsweise weitergerueckt. Ein haengendes BricsCAD + ;; waere schlimmer als ein unvollstaendig gelesenes Objekt. + (if (= vorher *ssg-json-pos*) (ssg-json-weiter)) + ) + (if (equal (ssg-json-zeichen) "}") (ssg-json-weiter)) + (reverse erg) +) + +(defun ssg-json-array ( / erg c vorher) + (ssg-json-weiter) + (setq erg nil) + (ssg-json-leerraum) + (while (and (setq c (ssg-json-zeichen)) (/= c "]")) + (setq vorher *ssg-json-pos*) + (if (= c ",") + (ssg-json-weiter) + (setq erg (cons (ssg-json-wert) erg)) + ) + (ssg-json-leerraum) + (if (= vorher *ssg-json-pos*) (ssg-json-weiter)) + ) + (if (equal (ssg-json-zeichen) "]") (ssg-json-weiter)) + (reverse erg) +) + +(defun ssg-json-wert ( / c) + (ssg-json-leerraum) + (setq c (ssg-json-zeichen)) + (cond + ((null c) nil) + ((= c "{") (ssg-json-objekt)) + ((= c "[") (ssg-json-array)) + ((= c "\"") (ssg-json-string)) + (T (ssg-json-literal)) + ) +) + +;; JSON-Text parsen. Rueckgabe: Struktur (siehe Abbildung oben). +(defun ssg-json-parse (text / erg) + (setq *ssg-json-txt* text + *ssg-json-len* (strlen text) + *ssg-json-pos* 1) + (setq erg (ssg-json-wert)) + ;; Quelltext nicht global stehen lassen - eine Datendatei haelt sonst + ;; dauerhaft mehrere KB in einer globalen Variablen fest. + (setq *ssg-json-txt* nil *ssg-json-len* 0 *ssg-json-pos* 1) + erg +) + +;; JSON-Datei parsen. Rueckgabe: Struktur oder nil, wenn die Datei fehlt. +(defun ssg-json-laden (datei / zeilen) + (setq zeilen (ssg-read-file-lines datei)) + (if zeilen (ssg-json-parse (ssg-join zeilen "\n"))) +) + +;; Feld eines geparsten JSON-Objekts lesen. +(defun ssg-json-get (obj key) + (cdr (assoc key obj)) +) + + ;; Verschachteltes JSON-Objekt parsen (1 Ebene tief) ;; {"section": {"key": val, ...}, ...} ;; -> (("section" ("key" . val) ...) ...) diff --git a/Lisp/ssg_id.lsp b/Lisp/ssg_id.lsp index 913fd42..4a83c5e 100644 --- a/Lisp/ssg_id.lsp +++ b/Lisp/ssg_id.lsp @@ -38,6 +38,8 @@ ;; pattern_btmt_entladung in cfg/export.cfg) aus demselben Grund: sonst bleibt ;; ihre ID trotz ATTDEF leer und sie fehlen bei der zeichnungsweiten Duplikat- ;; pruefung/Maximum-Ermittlung (ssg-id-check-all). +;; GR_* (Geruest-Baugruppen, siehe pattern_geruest in cfg/export.cfg) ebenso - +;; das ID-ATTDEF steht seit *geruest-attrib-defs* zur Verfuegung. (defun ssg-id-collect-blocks ( / ss-all ss-out i ename ed bname) (dbgf "ssg-id-collect-blocks") (setq ss-all (ssget "X" (list (cons 0 "INSERT")))) @@ -55,6 +57,7 @@ (wcmatch bname "Vario*,Staustrecke*,AUS_Element*,EIN_Element*,VF_*,GF_*") (wcmatch bname "Separator_SP*,S-LP*,Scanner*") (wcmatch bname "BTMT-Beladung*,SC_Entladung*") + (wcmatch bname "GR_*") (wcmatch bname "#*") ) (ssadd ename ss-out) diff --git a/cfg/export.cfg b/cfg/export.cfg index 8110e3f..f2bbb6d 100644 --- a/cfg/export.cfg +++ b/cfg/export.cfg @@ -36,6 +36,10 @@ # fuehren sie daher beide als eigene TeileArt-Zeilen (export_sivas.py zusaetzlich # in den Summierungszaehlern "Anzahl aller BTBeladung"/"Anzahl aller # Abwurfstationen" der ILS-Automation-Zeile). +# +# pattern_geruest: Geruest-Baugruppen (GeruestInsert.lsp, GR__). +# Eigenstaendig wie BTMT, kein ANZAHL_*-Attribut an einem Carrier - export_csv.py +# UND export_sivas.py fuehren sie als eigene TeileArt-Zeilen. # ============================================================ [blockpattern] @@ -50,6 +54,7 @@ pattern_separator= Separator_SP*, S-LP* pattern_scanner= Scanner* pattern_btmt_beladung= BTMT-Beladung* pattern_btmt_entladung= SC_Entladung* +pattern_geruest= GR_* pattern_ks_subblocks= KS_EIN, KS_AUS, K1, K2, K3, K4 [python] diff --git a/data/geruest/R2-Saeule_dummy.dwg b/data/geruest/R2-Saeule_dummy.dwg new file mode 100644 index 0000000..a0b9d74 Binary files /dev/null and b/data/geruest/R2-Saeule_dummy.dwg differ diff --git a/data/geruest/Rohrabhaenger_dummy.dwg b/data/geruest/Rohrabhaenger_dummy.dwg new file mode 100644 index 0000000..1064414 Binary files /dev/null and b/data/geruest/Rohrabhaenger_dummy.dwg differ diff --git a/data/json/geruest.json b/data/json/geruest.json new file mode 100644 index 0000000..3ef2d41 --- /dev/null +++ b/data/json/geruest.json @@ -0,0 +1,419 @@ +{ + "Rohrabhänger": { + "id": "Rohrabhaenger", + "teileart": "Geruest Rohrabhaenger", + "dummy": "Rohrabhaenger_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": [ + { + "name": "ROHR R 1\" 33,7 x 2,6", + "teile": [ + { + "artikel": "ROHR R 1\" 33,7 x 2,6", + "sivas_id": 821006026, + "menge": 1 + }, + { + "artikel": "CAD - ROHRABH. 1\" NICHT GEQUETSCHT - MONTAGEELEMENTE KPL.", + "sivas_id": 821004202, + "menge": 1 + } + ] + }, + { + "name": "ROHR R 1\" 33,7 x 2,6 einseitig gequetscht", + "teile": [ + { + "artikel": "ROHR R 1\" 33,7 x 2,6 einseitig gequetscht", + "sivas_id": 821006034, + "menge": 1 + }, + { + "artikel": "CAD - ROHRABHÄNGER 1\" EINSEITIG GEQUETSCHT - MONTAGEELEMENTE KPL.", + "sivas_id": 821004203, + "menge": 1 + } + ] + }, + { + "name": "ROHR R 1\" 33,7 x 2,6 beidseitig gequetscht", + "teile": [ + { + "artikel": "ROHR R 1\" 33,7 x 2,6 beidseitig gequetscht", + "sivas_id": 821006035, + "menge": 1 + }, + { + "artikel": "CAD - ROHRABHÄNGER 1\" BEIDSEITIG GEQUETSCHT - MONTAGEELEMENTE KPL.", + "sivas_id": 821004204, + "menge": 1 + } + ] + } + ], + "anbaugruppen": [ + { + "artikel": "SPANNPRATZE ST - KH 8-11 KPL.SELBSTSICHERND", + "sivas_id": 821004120, + "menge": 1 + }, + { + "artikel": "SPANNPRATZE SELBSTSICHERND KPL.", + "sivas_id": 821004058, + "menge": 1 + }, + { + "name": "Schraubenset", + "teile": [ + { + "artikel": "Scheibe DIN 125 - A 10,5 - 140 HV - St", + "sivas_id": 913011010, + "menge": 1 + }, + { + "artikel": "Sechskantmutter DIN 934 - M10 - 8", + "sivas_id": 912011010, + "menge": 2 + }, + { + "artikel": "Sechskantschraube DIN 933 - M10 x 30 - 8.8", + "sivas_id": 911011008, + "menge": 1 + } + ] + }, + { + "artikel": "U-BÜGEL M7 41X49 FÜR PROFILGERÜST - BG", + "sivas_id": 825004049, + "menge": 1 + }, + { + "artikel": "ROHRKLAMMER 1\" KPL. VERTIKAL", + "sivas_id": 825004040, + "menge": 2 + }, + { + "artikel": "WINKEL 60X60X6X300", + "sivas_id": 821006043, + "menge": 1 + }, + { + "artikel": "WINKEL 60X60X6X60", + "sivas_id": 821006042, + "menge": 1 + }, + { + "artikel": "ROHRKLAMMER 1\" T-VERBINDUNG KPL.", + "sivas_id": 825004030, + "menge": 1 + }, + { + "artikel": "ROHRKLAMMER 1\" FLACH UND LANG, KPL.", + "sivas_id": 825002021, + "menge": 1 + }, + { + "artikel": "DURCHSTECKANKER HST M12X115X20", + "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": [ + { + "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 + } + ] + } + ] + }, + "2\" Säulen": { + "id": "Saeulen_2Zoll", + "teileart": "Geruest Saeule 2 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" + } + }, + "einbauwinkel": { + "Platte": [ + 0, + 90 + ], + "Arm": [ + 0, + 90, + 180, + 270 + ] + }, + "geometrie_varianten": [ + { + "name": "R 2\"", + "teile": [ + { + "artikel": "BODENSÄULE R 2\" 60,3 MIT FUSSPLATTE", + "sivas_id": 821006051, + "menge": 1 + }, + { + "artikel": "CAD - BODENSÄULE R 2\" - DURCHSTECKANKER KPL.", + "sivas_id": 821004201, + "menge": 1 + } + ] + }, + { + "name": "R 2\" Haltearm 1-ARMIG", + "teile": [ + { + "artikel": "BODENSÄULE R 2\" 60,3 MIT FUSSPLATTE", + "sivas_id": 821006051, + "menge": 1 + }, + { + "artikel": "CAD - BODENSÄULE R 2\" - DURCHSTECKANKER KPL.", + "sivas_id": 821004201, + "menge": 1 + }, + { + "artikel": "HALTEARM FÜR BODENSÄULE R 2\" 1-ARMIG KPL.", + "sivas_id": 821004051, + "menge": 1 + } + ] + }, + { + "name": "R 2\" Haltearm 2-ARMIG 90 Grad", + "teile": [ + { + "artikel": "BODENSÄULE R 2\" 60,3 MIT FUSSPLATTE", + "sivas_id": 821006051, + "menge": 1 + }, + { + "artikel": "CAD - BODENSÄULE R 2\" - DURCHSTECKANKER KPL.", + "sivas_id": 821004201, + "menge": 1 + }, + { + "artikel": "HALTEARM FÜR BODENSÄULE R 2\" 2-ARMIG 90 Grad KPL.", + "sivas_id": 821004054, + "menge": 1 + } + ] + }, + { + "name": "R 2\" Haltearm 2-ARMIG 180 Grad", + "teile": [ + { + "artikel": "BODENSÄULE R 2\" 60,3 MIT FUSSPLATTE", + "sivas_id": 821006051, + "menge": 1 + }, + { + "artikel": "CAD - BODENSÄULE R 2\" - DURCHSTECKANKER KPL.", + "sivas_id": 821004201, + "menge": 1 + }, + { + "artikel": "HALTEARM FÜR BODENSÄULE R 2\" 2-ARMIG 180 Grad KPL.", + "sivas_id": 821004052, + "menge": 1 + } + ] + }, + { + "name": "R 2\" Haltearm 3-ARMIG", + "teile": [ + { + "artikel": "BODENSÄULE R 2\" 60,3 MIT FUSSPLATTE", + "sivas_id": 821006051, + "menge": 1 + }, + { + "artikel": "CAD - BODENSÄULE R 2\" - DURCHSTECKANKER KPL.", + "sivas_id": 821004201, + "menge": 1 + }, + { + "artikel": "HALTEARM FÜR BODENSÄULE R 2\" 3-ARMIG KPL.", + "sivas_id": 821004053, + "menge": 1 + } + ] + } + ], + "anbaugruppen": [ + { + "artikel": "KLEMMRING MIT MUFFE EINFACH KPL.", + "sivas_id": 825002019, + "menge": 2 + }, + { + "artikel": "KLEMMRING MIT MUFFE DOPPELT 90 Grad, KPL.", + "sivas_id": 825002009, + "menge": 2 + }, + { + "artikel": "KLEMMRING MIT MUFFE DOPPELT 180 Grad, KPL.", + "sivas_id": 825002008, + "menge": 2 + }, + { + "artikel": "KLEMMRING M. MUFFE DREIFACH 90 Grad/180 Grad KPL.", + "sivas_id": 825002007, + "menge": 2 + }, + { + "artikel": "KLEMMRING MIT LASCHE KPL. ZUSAMMENBAUZEICHNUNG", + "sivas_id": 825002020, + "menge": 2 + }, + { + "artikel": "QUERSCHELLE F. 1\" UND 2\" ROHR GESCHW./VZ. M. 4X M10X30 KPL.", + "sivas_id": 825002022, + "menge": 2 + }, + { + "artikel": "KREUZSCHELLE F. 2\" UND 1\" ROHR SCHWENKBAR 360 Grad", + "sivas_id": 922083009, + "menge": 2 + } + ] + }, + "Wandbefestigung": { + "id": "Wandbefestigung", + "teileart": "Geruest Wandbefestigung", + "dummy": "Rohrabhaenger_dummy.dwg", + "geometrie_varianten": [ + { + "name": "WANDFLANSCH EINZELTEIL", + "teile": [ + { + "artikel": "WANDFLANSCH EINZELTEIL", + "sivas_id": 821006031, + "menge": 1 + } + ] + }, + { + "name": "WINKEL 60X60X6X60", + "teile": [ + { + "artikel": "WINKEL 60X60X6X60", + "sivas_id": 821006042, + "menge": 1 + } + ] + }, + { + "name": "WINKEL 60X60X6X300", + "teile": [ + { + "artikel": "WINKEL 60X60X6X300", + "sivas_id": 821006043, + "menge": 1 + } + ] + }, + { + "name": "ROHRSCHELLE 1\" OBERTEIL", + "teile": [ + { + "artikel": "ROHRSCHELLE 1\" OBERTEIL", + "sivas_id": 825006009, + "menge": 1 + } + ] + } + ], + "anbaugruppen": [ + { + "name": "Nylondübel 12x60 inkl. Holzschrauben", + "teile": [ + { + "artikel": "HOLZSCHRAUB,10X70, 6KT DIN 571 VZ", + "sivas_id": 911271027, + "menge": 2 + }, + { + "artikel": "NYLON - DUEBEL 12X60 (TYP S)", + "sivas_id": 922013003, + "menge": 2 + } + ] + }, + { + "name": "HST M12X115X20", + "teile": [ + { + "artikel": "DURCHSTECKANKER HST M12X115X20 WANDABH.", + "sivas_id": 922021003, + "menge": 2 + } + ] + } + ] + } +} \ No newline at end of file diff --git a/dcl/geruest.dcl b/dcl/geruest.dcl new file mode 100644 index 0000000..201871c --- /dev/null +++ b/dcl/geruest.dcl @@ -0,0 +1,61 @@ +// ============================================================ +// 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/doc/csv-export.md b/doc/csv-export.md index 0cfe90b..35cb3c7 100644 --- a/doc/csv-export.md +++ b/doc/csv-export.md @@ -145,6 +145,133 @@ Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingb Insertpoint;K1;K2;K3;K4;Nachbarn;TrackIds;Fehler;Warnungen;Merkmale ``` +- **Position** = Mittenkoordinate x, y, z; **Boundingbox** = Ausdehnung + (Laenge, Breite, Hoehe) in x, y, z — beide aus `csv:get-bbox` + (`vla-getboundingbox`), siehe Phase 1. +- **Insertpoint** = KOS-String (24-Zeichen Base64, Position+Rotation als + Quaternion, `csv:kos-encode`) des Block-Einfuegepunkts selbst. +- **K1-K4** = Positions-Strings der im Block enthaltenen Koordinatensysteme + K1..K4 (12-Zeichen Base64, nur Position, `csv:trans-encode`) — nur bei + Omniflo Bogen/Weiche/Gerade und VF_n/GF_n (K1/K2 aus AS/ES) vorhanden, + sonst leer. +- **Fehler** = `"unverbunden"` (keine Partner, aber mindestens einer noetig), + `"nur ein Partner"` (ein Partner, aber mindestens zwei noetig) oder leer. + Kreisel/Eckrad/Omniflo brauchen mindestens einen Partner, + Gefaellestrecke/Foerderer/Strecke-Modul mindestens zwei + (`export_neighbors.MIN_PARTNER`). +- **Elementnummer** wird NICHT aus der TeileId uebernommen, sondern am Ende + neu von 1 an vergeben, nachdem alle Zeilen aufsteigend nach TeileId + sortiert wurden (`teileid_sort_key` in `process_blocks`). Ohne geloeschte + Elemente stimmen Elementnummer und TeileId dadurch ueberein; geloeschte + Elemente hinterlassen Luecken nur in der TeileId-Folge. Zeilen ohne + numerische TeileId (Omniflo-Sum-Zeile, SEP-/SCN-Fallback-IDs ohne + ID-Attribut) behalten stabil ihre bisherige Reihenfolge und stehen danach. + +### Beispiel: Kreisel mit 2 Separatoren + VarioFoerderer mit 2 Separatoren + +Ausgangslage in der Zeichnung: ein kleiner Kreisel (`KREISEL_1`, ID `0010`) +mit zwei frei aufgesetzten Separatoren auf seiner Bahn, daran andockend ein +`VF_1` mit einer Kette AS -> Separator -> Separator -> ES (IDs `0021`-`0024`). + +**Ausschnitt `export_raw.json`** (von `csv:collect-export-blocks` / +`csv:block-to-json` geschrieben, hier gekuerzt auf die fuer dieses Beispiel +relevanten Felder): + +```json +[ + { + "block_name": "KREISEL_1", + "layer": "ILS_Kreisel", + "handle": "2A1", + "x": 10000.0, "y": 5000.0, "z": 0.0, "rotation": 0.0, + "attribs": {"ID": "0010", "DREHRICHTUNG": "UZS", "ABSTAND": "800"}, + "insertpoint": "AAB3QAAATkAAAAAA...", + "bbox": {"min": [9200.0, 4200.0, 0.0], "max": [10800.0, 5800.0, 0.0]} + }, + { + "block_name": "Separator_SP_300mm", + "layer": "ILS_Sensor", + "handle": "2A5", + "x": 10800.0, "y": 5000.0, "z": 0.0, "rotation": 0.0, + "attribs": {"ID": "0011"}, + "insertpoint": "AAD4QAAATkAAAAAA..." + }, + { + "block_name": "Separator_SP_300mm", + "layer": "ILS_Sensor", + "handle": "2A9", + "x": 10000.0, "y": 5800.0, "z": 0.0, "rotation": 90.0, + "attribs": {"ID": "0012"}, + "insertpoint": "AAB3QAAAxEAAAAAA..." + }, + { + "block_name": "VF_1", + "layer": "ILS_Foerderer", + "handle": "2B0", + "x": 10800.0, "y": 5000.0, "z": 0.0, "rotation": 0.0, + "attribs": {"ID": "0021", "Bezeichnung": ""}, + "insertpoint": "AAD4QAAATkAAAAAA...", + "k1": "AAD4QAAATkAAAAAA...", + "k2": "AAAAQwAATkAAAAAA...", + "bbox": {"min": [10800.0, 4850.0, 0.0], "max": [13000.0, 5150.0, 300.0]}, + "sepliste": [ + ["AS", 1, 10800.0, 5000.0], + ["SEP", 2, 11600.0, 5000.0], + ["SEP", 3, 12300.0, 5000.0], + ["ES", 4, 13000.0, 5000.0] + ] + }, + { + "block_name": "Separator_SP_300mm", + "layer": "ILS_Sensor", + "handle": "2B4", + "x": 11600.0, "y": 5000.0, "z": 0.0, "rotation": 0.0, + "attribs": {"ID": "0022"} + }, + { + "block_name": "Separator_SP_300mm", + "layer": "ILS_Sensor", + "handle": "2B8", + "x": 12300.0, "y": 5000.0, "z": 0.0, "rotation": 0.0, + "attribs": {"ID": "0023"} + } +] +``` + +Die beiden Separatoren `0022`/`0023` sind hier die ECHTEN, top-level +exportierten Separator-Bloecke (identisch mit den `SEP`-Eintraegen der +`sepliste` von `VF_1` — siehe "Die `sepliste`" oben: kein doppeltes Konzept, +zwei Blickwinkel auf dieselben Symbole). + +**Resultierende CSV-Zeilen** (Header + betroffene Zeilen, Spalten +`Insertpoint`/`K3`/`K4`/`Merkmale` fuer die Lesbarkeit gekuerzt): + +```text +Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox;Insertpoint;K1;K2;K3;K4;Nachbarn;TrackIds;Fehler;Warnungen;Merkmale +1;"ILS 2.0 Kreisel";"0010";"Kreisel :1";"B/3";1;"10000.0, 5000.0, 0.0";"1600.0, 1600.0, 0.0";"...";"";"";"";"";"0011, 0012, 0021";"0011, 0012, 0021, 0022, 0023, 0024";"";"";{"Abstand":"800"} +2;"ILS 2.0 Separator";"0011";"Separator :1";"B/3";1;"10800.0, 5000.0, 0.0";"210.0, 150.0, 14.0";"...";"";"";"";"";"0010, 0012";"";"";"";{"Zuordnung":"0010"} +3;"ILS 2.0 Separator";"0012";"Separator :2";"B/3";1;"10000.0, 5800.0, 0.0";"150.0, 210.0, 14.0";"...";"";"";"";"";"0011, 0021";"";"";"";{"Zuordnung":"0010"} +4;"ILS 2.0 VarioFoerderer";"0021";"VarioFoerderer :1";"B/3";1;"11900.0, 5000.0, 0.0";"2200.0, 300.0, 300.0";"...";"...";"...";"";"";"0010, 0024";"0021, 0022, 0023, 0024";"";"";{} +5;"ILS 2.0 Separator";"0022";"Separator :3";"C/4";1;"11600.0, 5000.0, 0.0";"210.0, 150.0, 14.0";"...";"";"";"";"";"";"";"";"";{"Zuordnung":"0021"} +6;"ILS 2.0 Separator";"0023";"Separator :4";"C/4";1;"12300.0, 5000.0, 0.0";"210.0, 150.0, 14.0";"...";"";"";"";"";"";"";"";"";{"Zuordnung":"0021"} +``` + +Zu beachten: + +- Der Kreisel (`0010`) traegt in `TrackIds` den KOMPLETTEN Umlauf inkl. der + VF-Kettenenden (`0021`=AS, `0024`=ES, aus `compute_kreisel_umlauf`), waehrend + seine `Nachbarn`-Spalte nur die unmittelbaren Umlauf-Nachbarn zeigt. +- `VF_1` (`0021`) bekommt seine eigene `TrackIds`-Spalte + (`write_separatorliste_merkmale`) aus der `sepliste`: AS -> SEP -> SEP -> ES + in Baureihenfolge, unabhaengig vom Kreisel-Umlauf. +- Die AS/ES-Enden (`0021`=AS, `0024`=ES) sind hier synthetische Zeilen aus + `build_strecke_schleus_items` (Zeilentyp "ILS Ausschleuselement"/ + "ILS Einschleuselement") — in diesem gekuerzten Beispiel nicht einzeln + aufgefuehrt, aber ihre TeileIds tauchen in `TrackIds` beider Seiten auf. +- Die Separatoren 3 und 5 (`0012`/`0022`) unterscheiden sich nur durch ihre + `Zuordnung`: `0012` haengt am Kreisel, `0022`/`0023` an der VF-Kette — beide + Faelle laufen ueber denselben `pattern_separator`-Blocktyp. + ### 1. Blöcke klassifizieren Eine grosse Schleife über alle rohen JSON-Blöcke ordnet jeden Block anhand diff --git a/lang/de_DE.json b/lang/de_DE.json index 068610a..dca1407 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -969,6 +969,18 @@ "vfl-kette-diag-startbaustein": "\n [Diagnose] KS_AUS Startbaustein (%1): X=%2 Y=%3 Z=%4", "vfl-kette-diag-naechster-kandidat": "\n [Diagnose] Naechster Kandidat: %1 Abstand=%2 mm (Toleranz eng=%3 mm, weit=%4 mm)", "vfl-kette-diag-kein-anderer": "\n [Diagnose] Kein anderer Baustein mit KS_EIN gefunden (insgesamt %1 Bausteine erfasst).", - "vfl-kette-diag-verdaechtig": "\n [Diagnose] VERDAECHTIG: '%1' -> '%2' Abstand=%3 mm (innerhalb Toleranz, aber nicht ~0)" + "vfl-kette-diag-verdaechtig": "\n [Diagnose] VERDAECHTIG: '%1' -> '%2' Abstand=%3 mm (innerhalb Toleranz, aber nicht ~0)", + "gr-fehler-datenpfad": "\nGeruest: Datenverzeichnis unbekannt - DXFM_DATA bzw. DXFMAKRO nicht gesetzt (bin/setenv.bat ausfuehren).", + "gr-fehler-json-fehlt": "\nGeruest: Datendatei nicht gefunden: %1", + "gr-fehler-json-lesbar": "\nGeruest: Datendatei nicht lesbar: %1", + "gr-fehler-baugruppe": "\nGeruest: Baugruppe '%1' steht nicht in der Datendatei.", + "gr-fehler-keine-varianten": "\nGeruest: Baugruppe '%1' hat keine Geometrie-Varianten.", + "gr-fehler-dummy": "\nGeruest: Dummy-Baugruppe nicht gefunden: %1", + "gr-fehler-dcl": "Dialog geruest.dcl konnte nicht geladen werden.", + "gr-dlg-kopf": "%1 konfigurieren", + "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)." } } diff --git a/lang/en_GB.json b/lang/en_GB.json index 49fb868..67a507e 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -969,6 +969,18 @@ "vfl-kette-diag-startbaustein": "\n [Diagnose] KS_AUS start block (%1): X=%2 Y=%3 Z=%4", "vfl-kette-diag-naechster-kandidat": "\n [Diagnose] Next candidate: %1 distance=%2 mm (tolerance tight=%3 mm, wide=%4 mm)", "vfl-kette-diag-kein-anderer": "\n [Diagnose] No other block with KS_EIN found (%1 blocks captured in total).", - "vfl-kette-diag-verdaechtig": "\n [Diagnose] SUSPICIOUS: '%1' -> '%2' distance=%3 mm (within tolerance, but not ~0)" + "vfl-kette-diag-verdaechtig": "\n [Diagnose] SUSPICIOUS: '%1' -> '%2' distance=%3 mm (within tolerance, but not ~0)", + "gr-fehler-datenpfad": "\nScaffold: data directory unknown - DXFM_DATA or DXFMAKRO not set (run bin/setenv.bat).", + "gr-fehler-json-fehlt": "\nScaffold: data file not found: %1", + "gr-fehler-json-lesbar": "\nScaffold: data file not readable: %1", + "gr-fehler-baugruppe": "\nScaffold: assembly '%1' is not in the data file.", + "gr-fehler-keine-varianten": "\nScaffold: assembly '%1' has no geometry variants.", + "gr-fehler-dummy": "\nScaffold: dummy assembly not found: %1", + "gr-fehler-dcl": "Dialog geruest.dcl could not be loaded.", + "gr-dlg-kopf": "Configure %1", + "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)." } } diff --git a/lib/export_csv.py b/lib/export_csv.py index eb68361..41fe681 100644 --- a/lib/export_csv.py +++ b/lib/export_csv.py @@ -3,111 +3,21 @@ """ export_csv.py - Erzeugt eine einfache Item-Liste als CSV. -Fuer Omniflo-Elemente (Bogen/Weiche/Gerade) wird am Ende zusaetzlich eine -"Omniflo Sum"-Zeile mit Anzahl Boegen/Weichentypen und Gesamtlaenge AP110/AP60 -angehaengt (siehe build_omni_sum_merkmale). - -Nach der Kollisions-/Nachbarschaftserkennung wird ausserdem an jedem -Beruehrpunkt zweier Kreisel eine "ILS Weiche"-Zeile angehaengt (siehe -build_kreisel_weiche_items / export_neighbors.compute_kreisel_touch_switches): -Anzahl 1, Position = Beruehrkoordinaten, Nachbarn = die beiden Kreisel-IDs, -K1-K4/Insertpoint leer, Boundingbox = feste Element-Box (element_box_mm, -cfg/export.cfg [Boundingbox]), -mit der Laengsseite senkrecht zur Achse des beruehrenden Kreisels; die TeileId -zaehlt aus der hoechsten bereits vergebenen numerischen ID hoch. - -Anschliessend (TeileId-Zaehlung laeuft ueber die Weichen hinweg weiter) wird -an jedem Ende einer Gefaellestrecke/eines Foerderers, an dem ein Kreisel -anschliesst, ein Aus- (Anfang) bzw. Einschleuselement (Ende) an der Kollisions- -position Kreisel<->Strecke angehaengt (siehe build_strecke_schleus_items / -export_neighbors.compute_strecke_kreisel_schleus) - Format wie die Weiche, -Nachbarn = Kreisel-ID + Strecken-ID. Zugeordnet wird je Streckenende der -naechstgelegene ihrer BBox-Kreisel-Nachbarn. - -Warnungen = Spalte fuer Auffaelligkeiten. Aktuell: eine Strecke sollte genau -zwei Kreisel (Anfang + Ende) beruehren - beruehrt ihre (in x/y gepruefte) -Bounding-Box mehr, steht hier ein Hinweis und es wird zusaetzlich eine -Konsolen-WARNUNG ausgegeben (mit exakten 3D-BBoxen sollte das nicht auftreten). -Scanner-Zeilen bekommen dieselbe Spalte fuer strittige Distanz-Zuordnungen: -liegt ein Scanner ausserhalb jeder Kreisel-/Streckengruppen-/Gefaellestrecke- -Boundingbox und ist der Abstand zum naechsten und zweitnaechsten Carrier fast -gleich gross, schreibt Lisp/count_sep_scan.lsp (cs-zuordnung-lauf, vor dem -Export automatisch aufgerufen) einen Warnungstext mit den IDs beider -Kandidaten in das "warnung"-Feld des Scanner-Blocks im Roh-JSON (siehe -csv:block-to-json in export.lsp) - hier unveraendert uebernommen. - Aufruf: python export_csv.py -CSV-Format: +CSV-Format (Header): Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox; Insertpoint;K1;K2;K3;K4;Nachbarn;TrackIds;Fehler;Warnungen;Merkmale -Position und Boundingbox stammen aus der von export.lsp (csv:get-bbox, per -vla-getboundingbox) ermittelten Bounding-Box je Block: - Position = Mittenkoordinate x, y, z - Boundingbox = Ausdehnung (Laenge, Breite, Hoehe) in x, y, z -Insertpoint = KOS-String (24-Zeichen Base64, Position+Rotation als Quaternion, -siehe csv:kos-encode in export.lsp) des Block-Einfuegepunkts selbst. -K1-K4 = Positions-Strings der im Block enthaltenen Koordinatensysteme K1..K4 -(12-Zeichen Base64, nur Position ohne Rotation, siehe csv:trans-encode; -nur bei Omniflo Bogen/Weiche/Gerade vorhanden, sonst leer). -Planquadrat wird aus der x/y-Koordinate des Blocks berechnet, siehe -export_planquadrat.py ([Planquadrate] in cfg/export.cfg). -Nachbarn = kommaseparierte TeileId-Liste ueberschneidender Elemente (Bounding- -Box-Ueberschneidung in der x/y-Ebene, reines Python ohne externe Abhaengigkeit, -siehe export_neighbors.py). Geprueft wird NICHT global ueber alle Elemente, -sondern in drei getrennten Gruppen: 1) Kreisel/Eckrad gegen Kreisel/Eckrad, -2) Kreisel/Eckrad gegen Gefaellestrecke/Foerderer/Strecke-Modul (diese drei -nicht gegeneinander), 3) Omniflo-Elemente gegen Omniflo-Elemente. Fuer die -Omniflo-Gruppe liefert eine shapely-STRtree-Broad-Phase zunaechst alle Paare -mit ueberschneidenden Bounding-Boxen; anschliessend gelten sie nur dann als -benachbart, wenn sich ihre Anschluss-Koordinatensysteme K1-K4 tatsaechlich -beruehren (ein K-Punkt naeher als omniflo_ks_toleranz_mm, Default 10mm) - -das trennt bloss ueberlappende BBoxen von echten Anschluessen (Geraden fuehren -dafuer beim Export synthetisierte K1/K2, siehe csv:gerade-k-kos-strings). -Toleranzen und (Fallback-)Rastergroesse konfigurierbar ueber [Nachbarschaft] -in cfg/export.cfg. -TrackIds = kommaseparierte TeileId-Liste der Separatoren/BTMT-Beladung/ -BTMT-Entladung (und bei einem Kreisel zusaetzlich der AS/ES-Streckenenden -und Kreisel-Kreisel-Weichen) entlang einer VF_n/GF_n-Strecke bzw. rund um -einen Kreisel, in Baureihenfolge (Strecke) bzw. Umlaufreihenfolge ab Winkel -0 Grad (Kreisel, siehe export_neighbors.compute_kreisel_umlauf) - nur bei -VF_n/GF_n/Kreisel vorhanden, sonst leer. Siehe write_separatorliste_ -merkmale/write_kreisel_separatorliste_merkmale. -Fehler = "unverbunden" (keine Partner, aber mindestens einer noetig), -"nur ein Partner" (ein Partner, aber mindestens zwei noetig) oder leer -(Mindestanzahl erreicht bzw. TeileArt wird nicht geprueft). Kreisel/Eckrad/ -Omniflo-Elemente brauchen mindestens einen Partner, Gefaellestrecke/ -Foerderer/Strecke-Modul mindestens zwei (siehe export_neighbors.py -MIN_PARTNER). Ein Kreisel wird dabei NICHT als eine, sondern als zwei -BBoxen (links/rechts, geteilt entlang der Achse durch Antriebs-/ -Spannstation, siehe export_neighbors.kreisel_half_bboxes) gegen -Gefaellestrecke/Foerderer getestet - deren "Nachbarn"-Spalte zeigt darum -"-L" bzw. "-R" statt nur der Kreisel-TeileId. -Separator/Scanner-Zeilen bekommen zusaetzlich ein berechnetes "Zuordnung"- -Merkmal (Gefaellestrecke > Foerderer > Kreiselhaelfte, per BBox-Ueber- -schneidung - siehe export_neighbors.compute_sensor_zuordnung), das die -ATTRIB-Zuordnung (falls das Attribut ZUORDNUNG in der Zeichnung von Hand -gesetzt wurde) ersetzt, sofern ein Treffer gefunden wird. Scanner-Zeilen -bekommen zusaetzlich ein "Naechster Separator"-Merkmal (raeumlich naechst- -gelegener Separator, euklidischer Abstand der BBox-Mittelpunkte - eigene, -von der Zuordnung unabhaengige Suche, siehe export_neighbors.compute_ -scanner_nearest_separator). -Position, Boundingbox, Planquadrat, Nachbarn und Fehler sind nur fuer -EXPORTCSV vorhanden (nicht EXPORTSIVAS) - siehe csv:run-export. Insertpoint/ -K1-K4 stehen zwar in export_raw.json fuer jeden Export zur Verfuegung (siehe -csv:block-to-json), werden aber ebenfalls nur von diesem Skript (EXPORTCSV) -als CSV-Spalten ausgegeben. +Beispielzeile (Separator, TeileId 0011 an Kreisel 0010): + 2;"ILS 2.0 Separator";"0011";"Separator :1";"B/3";1; + "10800.0, 5000.0, 0.0";"210.0, 150.0, 14.0";"...";"";"";"";""; + "0010, 0012";"";"";"";{"Zuordnung":"0010"} -Reihenfolge/Elementnummer: die Zeilen werden am Ende aufsteigend nach -TeileId sortiert (numerische IDs) und die Elementnummer (erste Spalte) -danach neu von 1 an vergeben (siehe teileid_sort_key in process_blocks) - -wurde kein Element geloescht, stimmen Elementnummer und TeileId dadurch -ueberein, geloeschte Elemente hinterlassen Luecken in der TeileId-Folge. -Zeilen ohne numerische TeileId (Omniflo-Sum-Zeile, SEP-/SCN-Fallback-IDs -ohne ID-Attribut) behalten stabil ihre bisherige Reihenfolge und stehen -danach. +Fuer die vollstaendige Herleitung jeder Spalte (Nachbarn/TrackIds/sepliste/ +Fehler/Warnungen, inkl. eines durchgerechneten Beispiels Kreisel+VarioFoerderer +von export_raw.json bis zur fertigen CSV-Zeile) siehe doc/csv-export.md. """ import json @@ -353,6 +263,23 @@ def build_btmt_merkmale(block): } +def build_geruest_merkmale(block): + """Merkmale-Dict fuer einen GR_-Geruest-Baugruppenblock (siehe + pattern_geruest in cfg/export.cfg, GeruestInsert.lsp *geruest-attrib-defs*). + + ARTINR/LAENGE/BEZEICHNUNG wandern in feste CSV-Spalten (teileid/ + bezeichnung ueben in process_blocks unten), TEILEART/FARBE/LAYER bleiben + als Rest-Attribute in der Merkmale-JSON-Spalte - so wie bei Separator/ + Scanner/BTMT bereits ueblich.""" + attribs = block.get("attribs", {}) + return { + "TeileArt": attribs.get("TEILEART", ""), + "Farbe": attribs.get("FARBE", ""), + "Laenge": attribs.get("LAENGE", ""), + "Layer": attribs.get("LAYER", ""), + } + + def format_sensor_id(prefix, n): """Fallback-TeileId fuer Separator/Scanner-Zeilen im CSV-Export, falls das Block-Attribut "ID" (noch) leer ist. @@ -959,6 +886,7 @@ def process_blocks(blocks, lookup, dbg=None): scanner_count = 0 btmt_beladung_count = 0 btmt_entladung_count = 0 + geruest_count = 0 # Zaehlung fuer die Omniflo-Sum-Zeile (nur Bogen/Weiche/Gerade) cnt_wk = cnt_einzel = cnt_delta = cnt_doppel = cnt_stern = 0 @@ -1231,6 +1159,30 @@ def process_blocks(blocks, lookup, dbg=None): }) continue + # Geruest-Baugruppe (GR__) - eigene TeileArt-Zeile, + # analog zu BTMT: kein ANZAHL_*-Attribut an einem Carrier zaehlt sie + # mit. TeileId ist das echte ID-Attribut (ssg-id-check-all fuellt es + # vor jedem Export, siehe *geruest-attrib-defs*); ARTINR (Sivas-IDs, + # ggf. mehrere kommagetrennt) landet als eigene Spalte in den + # Merkmalen statt in "teileart" - anders als bei Kreisel/BTMT ist + # "TEILEART" hier ein FREIES Attribut (Rohrabhaenger/Saeule/...), keine + # feste Konstante. + if matches_any(bname, BLOCKPATTERNS.get("pattern_geruest", [])): + elem_nr += 1 + geruest_count += 1 + attribs = block.get("attribs", {}) + bezeichnung = attribs.get("BEZEICHNUNG", "").strip() + items.append({ + "nr": elem_nr, + "teileart": "Geruest", + "teileid": attribs.get("ID") or format_sensor_id("GR", geruest_count), + "bezeichnung": bezeichnung if bezeichnung else f"Geruest :{geruest_count}", + "planquadrat": planquadrat_column(block, planquadrat_cfg), + "merkmale": {"ARTINR": attribs.get("ARTINR", ""), **build_geruest_merkmale(block)}, + **bbox_columns(block), + }) + continue + if has_omniflo: elem_nr += 1 items.append({ diff --git a/lib/export_sivas.py b/lib/export_sivas.py index 9aff485..0e3cd26 100644 --- a/lib/export_sivas.py +++ b/lib/export_sivas.py @@ -288,6 +288,21 @@ def build_eckrad_details(block): } +def build_geruest_details(block): + """Merkmale-Dict fuer eine GR_-Geruest-Baugruppe (siehe pattern_geruest in + cfg/export.cfg, GeruestInsert.lsp *geruest-attrib-defs*). ARTINR/ + BEZEICHNUNG/TEILEART/LAENGE wandern in feste Sivas-Spalten (sivasnr/ + bezeichnung/teileart/laenge_mm), FARBE bleibt als Rest-Attribut hier. + LAYER ist reine BricsCAD-Zeichnungsinformation ohne Sivas-Bedeutung und + wird bewusst NICHT mit exportiert (anders als im CSV-Export + export_csv.py, wo sie fuer die Zuordnung in der Zeichnung nuetzlich + bleibt).""" + attribs = block.get("attribs", {}) + return { + "Farbe": attribs.get("FARBE", ""), + } + + # --- Bekannte Bloecke ignorieren --- SKIP_BLOCKS = set(BLOCKPATTERNS.get("pattern_ks_subblocks", ["K1", "K2", "K3", "K4", "KS_EIN", "KS_AUS"])) @@ -339,6 +354,7 @@ def process_blocks(blocks, lookup): btmt_beladung_bezeichnungen = [] btmt_entladung_ids = [] # IDs aller SC_Entladung*-Bloecke (eine gruppierte Zeile) btmt_entladung_bezeichnungen = [] + geruest_list = [] # Liste von GR_-Geruest-Baugruppenbloecken, je einer Zeile counters = { "anzahl_boegen": 0, @@ -369,6 +385,7 @@ def process_blocks(blocks, lookup): "anzahl_scanner": 0, "anzahl_btbeladung": 0, "anzahl_abwurfstationen": 0, + "anzahl_geruest": 0, } for block in blocks: @@ -398,6 +415,18 @@ def process_blocks(blocks, lookup): counters["anzahl_abwurfstationen"] += 1 continue + # --- Geruest-Baugruppe --- + # Anders als BTMT/Separator je Block eine EIGENE Zeile statt gruppiert: + # ARTINR (Sivas-IDs) und Laenge/Farbe koennen von Block zu Block + # unterschiedlich sein (freie Basis-Variante+Anbauteile-Auswahl je + # GERUEST_*-Aufruf), eine Gruppierung wuerde das verschleiern - wie + # bei VF_N/Kreisel oben, die aus demselben Grund ebenfalls einzeln + # bleiben. + if matches_any(bname, BLOCKPATTERNS.get("pattern_geruest", [])): + geruest_list.append(block) + counters["anzahl_geruest"] += 1 + continue + # --- Omniflo Bogen oder Weiche (erkannt via Katalog) --- if bname in lookup: typ, eintrag = lookup[bname] @@ -702,6 +731,40 @@ def process_blocks(blocks, lookup): }) nr += 1 + # Geruest-Baugruppen (GR__) - je Block eine eigene Zeile + # (siehe Kommentar bei geruest_list oben). Sivasnr kommt direkt aus dem + # ARTINR-Attribut - kann mehrere, kommagetrennte Sivas-IDs enthalten + # (Baugruppe aus mehreren Einzelteilen, siehe ssg-join in + # GeruestInsert.lsp) und dieselbe ID mehrfach (menge>1 je Einzelteil). + # Anders als bei den uebrigen Teilearten also KEIN fester Eintrag aus + # ILS_SIVASNR - JEDE Artikelnummer bekommt ihre EIGENE Sivas-Zeile (Sivas + # kennt nur eine Sivasnr je Zeile), gleiche Artikelnummern INNERHALB + # derselben Baugruppe werden dabei zu einer Zeile mit anzahl>1 + # zusammengefasst (wie bogen_groups/weiche_groups oben). Die volle + # LAENGE der Baugruppe steht auf JEDER so entstandenen Zeile - Sivas hat + # kein Konzept einer "gemeinsamen" Laenge ueber mehrere Zeilen hinweg. + for block in geruest_list: + attribs = block.get("attribs", {}) + artinr_list = [a.strip() for a in attribs.get("ARTINR", "").split(",") if a.strip()] + artinr_groups = {} + for artinr in artinr_list: + artinr_groups.setdefault(artinr, 0) + artinr_groups[artinr] += 1 + for artinr, anzahl in artinr_groups.items(): + items.append({ + "nr": nr, + "teileart": attribs.get("TEILEART", "Geruest"), + "sivasnr": artinr, + "sivasnr_quoted": True, + "bezeichnung": attribs.get("BEZEICHNUNG", ""), + "dispgruppe": 20, + "anzahl": anzahl, + "laenge_mm": attribs.get("LAENGE", ""), + "ids": [get_id(block)], + "details": build_geruest_details(block), + }) + nr += 1 + # VarioFoerderer-Compound-Bloecke (VF_N) - je Block eine eigene Zeile mit # vollstaendigen Gefaelle-/Angetrieben-Teilgruppen (immer TYP "Streckengruppe"). for block in vario_list: diff --git a/menu/SSG_LIB.cui b/menu/SSG_LIB.cui index e728c14..39d447f 100644 --- a/menu/SSG_LIB.cui +++ b/menu/SSG_LIB.cui @@ -530,205 +530,229 @@ ^C^C(ssg-ensure "OmniModulInsert") OMNI_EDIT - + + + Rohrabhaenger + ^C^C(ssg-ensure "GeruestInsert") GERUEST_ROHRABHAENGER + + + + + 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 + + + Marker einfuegen ^C^C(ssg-ensure "Tro_Edit") TRO_INSERT - + Marker bearbeiten ^C^C(ssg-ensure "Tro_Edit") TRO_EDIT - + Bearbeiten (Auto) ^C^C(ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT - + Verbindung einfuegen ^C^C(ssg-ensure "Connection_Edit") CONNECTION_INSERT - + Verbindung bearbeiten ^C^C(ssg-ensure "Connection_Edit") CONNECTION_EDIT - + Eingang ^C^C(ssg-ensure "SSG_LIB_Commands") ILS_EINGANG_INSERT - + Ausgang ^C^C(ssg-ensure "SSG_LIB_Commands") ILS_AUSGANG_INSERT - + SPS Schliessen ^C^C - + Export Sivas ^C^C(ssg-ensure "export") EXPORTSIVAS - + Export CSV ^C^C(ssg-ensure "export") EXPORTCSV - + Umschalten 2D und 3D ^C^C(ssg-ensure "SSG_LIB_Commands") SSG_DIM_SWITCH - + Alle Bloecke auf 2D ^C^C(ssg-ensure "SSG_LIB_Commands") SSG_DIM_ALL_2D - + Alle Bloecke auf 3D ^C^C(ssg-ensure "SSG_LIB_Commands") SSG_DIM_ALL_3D - + Sprache ^C^C(ssg-ensure "SSG_LIB_Commands") SSG_SPRACHE - + Planquadrate an/aus ^C^C(ssg-ensure "DrawPlanquadrate") PLANQUADRATE_TOGGLE - + alle Tests ausfuehren und exportieren ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_run_all.lsp")) SSG_RUN_ALL_TESTS_EXPORT - + Tests ausfuehren und stehen lassen ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_run_all.lsp")) SSG_RUN_ALL_TESTS_OFFEN - + Export CSV/Sivas ALL ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_export_all.lsp")) TEST_EXPORT_ALL - + Kreisel ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_kreisel.lsp")) TEST_KREISEL - + Foerderer ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_foerderer.lsp")) TEST_FOERDERER - + Foerderer-Linienzug ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_linienzug.lsp")) TEST_LINIENZUG TEST_LINIENZUG_STUFE2 - + Omniflo ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_omniflo.lsp")) TEST_OMNIFLO - + Omniflo Strecke ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_omniflo_strecke.lsp")) TEST_OMNIFLO_STRECKE - + Gefaellestrecke ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_gefaellestrecke.lsp")) TEST_GEFAELLESTRECKE - + Mubea Gesamtmodell ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_mubea.lsp")) TEST_MUBEA - + HundM05 Kreisel-Abschnitt ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_hundm05.lsp")) TEST_HUNDM05 - + KS 2D/3D pruefen (CSV) ^C^C(load (strcat (getenv "DXFMAKRO") "/Lisp/ks_pruefung.lsp")) KS_PRUEFEN - + Kreisel bearbeiten (ssg-ensure "KreiselInsert") KreiselEdit - + Omniflo Element bearbeiten (ssg-ensure "OmniModulInsert") OMNI_EDIT - + Gefaellestrecke bearbeiten (ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_EDIT - + VarioFoerderer bearbeiten (ssg-ensure "VarioFoerderer") VARIOFOERDERER_EDIT - + TRO-Marker bearbeiten (ssg-ensure "Tro_Edit") TRO_EDIT - + Verbindung bearbeiten (ssg-ensure "Connection_Edit") CONNECTION_EDIT - + SSG Bearbeiten (Auto) (ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT @@ -746,9 +770,10 @@ - - - + + + + ILS @@ -1342,244 +1367,275 @@ - SPS - - + Geruest - Eingang + Rohrabhaenger - + + - Ausgang + Wandbefestigung - - - - - SPS Schliessen - - + - - TRO + + Saeulen - Marker einfuegen - - - - - - Marker bearbeiten + 2 Zoll Saeulen - Bearbeiten (Auto) + 1 Zoll Saeulen - - Connections + + SPS + + - Verbindung einfuegen + Eingang - + - Verbindung bearbeiten - - - - - - Bearbeiten (Auto) - - - - - - - Export - - Export Sivas - - - - - - Export CSV - - - - - - - Konfiguration - - Umschalten 2D und 3D + Ausgang - Alle Bloecke auf 2D + SPS Schliessen + + + TRO - Alle Bloecke auf 3D + Marker einfuegen - + - Sprache + Marker bearbeiten + + + + + + Bearbeiten (Auto) + + + + + + + Connections + + Verbindung einfuegen + + + + + + Verbindung bearbeiten + + + + + + Bearbeiten (Auto) + + + + + + + Export + + Export Sivas - Planquadrate an/aus + Export CSV + + + Konfiguration - Konfiguration Schliessen + Umschalten 2D und 3D - + - - - Tests - alle Tests ausfuehren und exportieren + Alle Bloecke auf 2D - Tests ausfuehren und stehen lassen + Alle Bloecke auf 3D - Export CSV/Sivas ALL + Sprache + + Planquadrate an/aus + + + + + + Konfiguration Schliessen + + + + + + + Tests + + alle Tests ausfuehren und exportieren + + + + + + Tests ausfuehren und stehen lassen + + + + + + Export CSV/Sivas ALL + + + + Kreisel - + Foerderer - + Foerderer-Linienzug - + Omniflo - + Omniflo Strecke - + Gefaellestrecke - + Mubea Gesamtmodell - + HundM05 Kreisel-Abschnitt - + KS 2D/3D pruefen (CSV) - + - + POP501 SSG_LIB Edit Context Kreisel bearbeiten - + Omniflo Element bearbeiten - + Gefaellestrecke bearbeiten - + VarioFoerderer bearbeiten - + TRO-Marker bearbeiten - + Verbindung bearbeiten - + SSG Bearbeiten (Auto) - + diff --git a/menu/SSG_LIB.mnu b/menu/SSG_LIB.mnu index 7294389..539922f 100644 --- a/menu/SSG_LIB.mnu +++ b/menu/SSG_LIB.mnu @@ -110,6 +110,12 @@ [~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 [->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 406e669..36920bf 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -424,6 +424,134 @@ ) +;; ------------------------------------------------------------ +;; TESTS: ssg_core.lsp - rekursiver JSON-Parser + ssg-join +;; ------------------------------------------------------------ +(defun tu-tests-json ( / _p _datei _daten _bg _var _anb) + (princ "\n[ssg_core/json]") + + ;; ssg-join + (tu-eq "ssg-join/leer" "" (ssg-join '() ",")) + (tu-eq "ssg-join/eins" "a" (ssg-join '("a") ",")) + (tu-eq "ssg-join/drei" "a,b,c" (ssg-join '("a" "b" "c") ",")) + (tu-eq "ssg-join/plus" "a + b" (ssg-join '("a" "b") " + ")) + + ;; Skalare und flaches Objekt + (tu-eq "json/objekt-flach" '(("a" . 1)) (ssg-json-parse "{\"a\": 1}")) + (tu-eq "json/zwei-keys" '(("a" . 1) ("b" . 2)) + (ssg-json-parse "{\"a\": 1, \"b\": 2}")) + (tu-eq "json/string" "hallo" (ssg-json-get (ssg-json-parse "{\"a\": \"hallo\"}") "a")) + (tu-eq "json/int" 6000 (ssg-json-get (ssg-json-parse "{\"a\": 6000}") "a")) + (tu-eqf "json/real" 1.5 (ssg-json-get (ssg-json-parse "{\"a\": 1.5}") "a")) + (tu-eq "json/true" T (ssg-json-get (ssg-json-parse "{\"a\": true}") "a")) + (tu-eq "json/false" nil (ssg-json-get (ssg-json-parse "{\"a\": false}") "a")) + (tu-eq "json/null" nil (ssg-json-get (ssg-json-parse "{\"a\": null}") "a")) + + ;; Verschachtelung - genau das, was die alten Zeilen-Parser nicht koennen + (tu-eq "json/objekt-verschachtelt" '(("a" ("b" . 2))) + (ssg-json-parse "{\"a\": {\"b\": 2}}")) + (tu-eq "json/array" '(("a" 1 2 3)) + (ssg-json-parse "{\"a\": [1, 2, 3]}")) + (tu-eq "json/array-objekte" '(("a" (("b" . 1)) (("b" . 2)))) + (ssg-json-parse "{\"a\": [{\"b\": 1}, {\"b\": 2}]}")) + (tu-eq "json/array-leer" '(("a")) (ssg-json-parse "{\"a\": []}")) + + ;; Mehrzeilig (alte Parser verlangen Arrays in EINER Zeile) + (tu-eq "json/mehrzeilig" '(("a" "x" "y")) + (ssg-json-parse "{\n \"a\": [\n \"x\",\n \"y\"\n ]\n}")) + + ;; Maskiertes Anfuehrungszeichen und Komma im String - beides kommt in den + ;; Geruest-Artikelbezeichnungen real vor ("ROHR R 1\" 33,7 x 2,6") + (tu-eq "json/escape-quote" "R 1\" x" + (ssg-json-get (ssg-json-parse "{\"a\": \"R 1\\\" x\"}") "a")) + (tu-eq "json/komma-im-string" "33,7 x 2,6" + (ssg-json-get (ssg-json-parse "{\"a\": \"33,7 x 2,6\"}") "a")) + (tu-eq "json/escape-backslash" "a\\b" + (ssg-json-get (ssg-json-parse "{\"a\": \"a\\\\b\"}") "a")) + (tu-eq "json/escape-newline" (chr 10) + (ssg-json-get (ssg-json-parse "{\"a\": \"\\n\"}") "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)) + ;; Baugruppe ueber das ASCII-Feld "id" finden (aeusserer Schluessel + ;; traegt Umlaute bzw. Anfuehrungszeichen) + (setq _bg nil) + (foreach _e _daten + (if (and (null _bg) (= (ssg-json-get (cdr _e) "id") "Rohrabhaenger")) + (setq _bg (cdr _e)))) + (tu-eq "json/geruest-id-gefunden" T (if _bg T nil)) + (if _bg + (progn + (setq _var (ssg-json-get _bg "geometrie_varianten")) + (setq _anb (ssg-json-get _bg "anbaugruppen")) + (tu-eq "json/geruest-varianten" 3 (length _var)) + (tu-eq "json/geruest-teileart" "Geruest Rohrabhaenger" + (ssg-json-get _bg "teileart")) + (tu-eq "json/geruest-dummy" "Rohrabhaenger_dummy.dwg" + (ssg-json-get _bg "dummy")) + ;; Sivas-ID als Zahl geparst, Laengengrenze aus dem attribute-Block + (tu-eq "json/geruest-sivas" 821006026 + (ssg-json-get (car (ssg-json-get (car _var) "teile")) "sivas_id")) + (tu-eq "json/geruest-max" 6000 + (ssg-json-get (ssg-json-get (ssg-json-get _bg "attribute") "Laenge") "max")) + ) + ) + ) + (tu-record nil "json/geruest-datei" (strcat "nicht gefunden: " _datei)) + ) +) + + +;; ------------------------------------------------------------ +;; TESTS: GeruestInsert.lsp - reine Hilfsfunktionen +;; ------------------------------------------------------------ +(defun tu-tests-geruest ( / _eintrag _gruppe) + (princ "\n[GeruestInsert]") + (if (and (null (atoms-family 1 '("GERUEST-ARTIKELNUMMERN"))) + (atoms-family 1 '("SSG-ENSURE"))) + (ssg-ensure "GeruestInsert") + ) + (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)) + + ;; Einzelteil mit menge 2 -> Artikelnummer zweimal (ARTINR-Regel) + (setq _eintrag (ssg-json-parse + "{\"artikel\": \"X\", \"sivas_id\": 825002019, \"menge\": 2}")) + (tu-eq "geruest-artnr/menge2" '("825002019" "825002019") + (geruest-artikelnummern _eintrag)) + (tu-eq "geruest-name/artikel" "X" (geruest-eintrag-name _eintrag)) + + ;; Benannte Gruppe -> teile rekursiv aufloesen, Mengen beachten + (setq _gruppe (ssg-json-parse + (strcat "{\"name\": \"Schraubenset\", \"teile\": [" + "{\"artikel\": \"A\", \"sivas_id\": 111, \"menge\": 1}," + "{\"artikel\": \"B\", \"sivas_id\": 222, \"menge\": 2}]}"))) + (tu-eq "geruest-artnr/gruppe" '("111" "222" "222") + (geruest-artikelnummern _gruppe)) + (tu-eq "geruest-name/gruppe" "Schraubenset" (geruest-eintrag-name _gruppe)) + + ;; Fehlendes menge-Feld -> 1 + (tu-eq "geruest-artnr/ohne-menge" '("333") + (geruest-artikelnummern + (ssg-json-parse "{\"artikel\": \"C\", \"sivas_id\": 333}"))) + ) + ) +) + + ;; ------------------------------------------------------------ ;; REPORT SCHREIBEN ;; ------------------------------------------------------------ @@ -467,6 +595,8 @@ (tu-tests-omni) (tu-tests-kreisel) (tu-tests-sepliste) + (tu-tests-json) + (tu-tests-geruest) (princ "\n\n----------------------------------------------------------------") (princ (strcat "\n Gesamt: " (itoa (+ *tu-pass* *tu-fail*))