# Lisp – AutoLISP-Bibliotheken (SSG_LIB) AutoLISP-Routinen für BricsCAD Pro zur automatisierten Erstellung von Förderanlagen-Layouts. Destilliert aus den historischen ACADSST-Makros (Schönenberger Systeme GmbH) und erweitert für die SSG_LIB-Befehle. --- ## Menue einrichten und Module laden Module werden automatisch ueber `SSG_LIB.mnl` geladen, sobald das Menue einmalig in BricsCAD registriert wurde: **Einmalige Einrichtung (BricsCAD-Kommandozeile):** ``` _MENULOAD → menu/SSG_LIB.mnu auswaehlen ``` BricsCAD laedt daraufhin automatisch `SSG_LIB.mnl`, welches alle Module per `ssg-ensure` initialisiert. Kern-Reihenfolge (muss vor den Feature-Modulen stehen): `ssg_core`, `ssg_lang`, `ssg_dbg`, `ssg_dialog`, `ssg_layer`, `ssg_id`, `ssg_ks_insert` (Letzteres wird von Gefaellestrecke UND VarioFoerderer genutzt). Details: siehe `../CLAUDE.md` Abschnitt "Lademechanismus". **Manuell laden (Notfall / Entwicklung, z.B. fuer .scr-Testskripte):** ```lisp (load (strcat (getenv "DXFMAKRO") "/Lisp/ssg_load.lsp")) ``` `ssg_load.lsp` ist der Headless-Loader (genutzt von `tests/test_unit.scr` u.ae.) und laedt dieselbe Kern-Reihenfolge wie `SSG_LIB.mnl`, ohne Menueabhaengigkeit. --- ## Status: noch offene Menueintraege (im Menue ausgegraut) Die folgenden Menueintraege rufen aktuell nur eine Dummy-Funktion auf (Meldung `[DUMMY]`, keine Geometrie). Sie sind in `menu/SSG_LIB.mnu` mit `~` **ausgegraut** und werden erst bei echter Implementierung wieder aktiviert. Der `~`-Marker steht am Label-Anfang, bei Submenue-Schliessern hinter dem `<-` (`[<-~Label]`). | Menuebereich | Eintraege | Definiert in | | --- | --- | --- | | Omniflo ▸ Boegen fuer Weichen | alle `OMNI_APBW_*` (14) | `OmniModulInsert.lsp` | | ILS ▸ Zusatzmodule/Kurven | `ILS_ETF_Etagenfoerderer`, `ILS_K{90,60,30}{L,R}{A,I}` (12), `ILS_Gefaellestrecke` | `SSG_LIB_Commands.lsp` | | Geruest | `GERUEST` (Gattertraeger-Platzhalter, derselbe Befehl wie Saeulen/Rohrabhaenger/Wandbefestigung) | `GeruestInsert.lsp` | `OMNI_TEF_*` (UmlenkR/L, AntriebL/R, TEF Gerade) sind entgegen frueherem Stand **implementiert** (echtes `omni:insert-dxf`). Der Menueeintrag KettenFoerderer ist ebenfalls aktiv (Modul `KettenFoerderer.lsp`, Beschreibung in `CLAUDE.md`). Aktiv (implementiert) sind u.a.: Kreisel, Eckrad, VarioFoerderer (Standard/Etage/ Linienzug), Gefaellestrecke, Geruest (Saeulen/Rohrabhaenger/Wandbefestigung/ Rohr-Geruest-Traeger), TRO-Marker, Connections, BTMT Be-/Entladung, Scanner/ Separator, Omniflo Boegen (APB)/TEF-Elemente/Aluprofil-Geraden (AP)/alle Weichen (W90/W45/WP/WK/WKomb)/Ladeschleifen, Export (Sivas/CSV), Werkzeuge (Block-/ Text-Tools, Planquadrate) und die Tests. --- ## Dateien ### `ssg_core.lsp` – Kernbibliothek Grundfunktionen für Umgebungssicherung, Benutzerinteraktion, Layer, Blöcke, Attribute und JSON-Konfiguration. Muss als erstes geladen werden. **Umgebungssicherung:** | Funktion | Beschreibung | | --- | --- | | `ssg-start titel vars` | Systemvariablen sichern, CMDECHO aus, Undo-Gruppe öffnen | | `ssg-end` | Systemvariablen wiederherstellen, Undo-Gruppe schließen | | `ssg-errhan em` | Fehlerhandler – räumt auf und ruft `ssg-end` auf | **Benutzer-Abfragen:** | Funktion | Beschreibung | | --- | --- | | `ssg-ques text defa` | Ja/Nein-Abfrage; `defa=T` → Default Ja, `nil` → Default Nein | | `ssg-emsg text` | Fehlermeldung mit optionalem Signalton (`ssg-bell`) | **Layer-Hilfsfunktionen:** | Funktion | Beschreibung | | --- | --- | | `ssg-get-layer text` | Layer eines angeklickten Objektes ermitteln | | `ssg-layer-ss name typ` | Auswahlsatz aller Objekte eines Layers (optional nach Typ filtern) | | `ssg-make-layer name farbe aktiv` | Layer anlegen (falls nicht vorhanden) und optional aktivieren | | `ssg-ss-foreach ss func` | Iteration über Auswahlsatz – `func` für jeden Entity-Name | | `ssg-ss-redraw ss modus` | Auswahlsatz hervorheben (3), invertieren (4) oder normal (0) | | `ssg-ss-add src dest` | Auswahlsatz `src` zu `dest` hinzufügen (in-place, mit Highlight) | **Ziel-Layer je Baugruppe (`cfg/layer.cfg`):** Für Baugruppen, die ein Makro aus Einzelteilen **zusammenbaut** (`KREISEL_n`, `ECKRAD_n`, `VF_n`, `GF_n`) und für die selbst gezeichnete Hilfsgeometrie (Kreisel-Tangenten, PIN-Linien, Beschriftungstexte) gibt es keine Blockdatei in `data/` und damit kein `LAYER`-Attribut – ihr Ziel-Layer und die Layerfarbe kommen aus `cfg/layer.cfg`. Nur **eingefügte** Blöcke nehmen dagegen ihr `LAYER`-Attribut als Vorgabe. Alle Funktionen liefern `nil`, wenn nichts konfiguriert ist; der Layer bleibt dann unangetastet (kein Fehler bei fehlender `cfg/layer.cfg`). | Funktion | Beschreibung | | --- | --- | | `ssg-layer-cfg-load` | `cfg/layer.cfg` einmalig (lazy) einlesen, Rückgabe ini-Alist oder `nil` | | `ssg-layer-cfg-reset` | Ladezustand verwerfen (cfg in laufender Sitzung neu einlesen, Tests) | | `ssg-layer-cfg-eintrag ini sektion key` | Reine Aufl./Zerlegung `"Layername, Farbe"` → `(layer . farbe)`; Farbe fehlt → `"7"` | | `ssg-layer-eintrag key` | Ziel-Layer einer Baugruppe: `[ils_]` → `[allgemein]` → `nil` | | `ssg-layer-anlegen key aktiv` | Layer der Baugruppe mit cfg-Farbe anlegen, optional aktivieren; Rückgabe Layername | | `ssg-layer-setzen ent key` | Blockreferenz auf den Ziel-Layer ihrer Baugruppe legen (Layer wird angelegt) | | `ssg-layer-farbe-fuer-name name` | Farbe zu einem **Layernamen** (Abschnitt `[farben]`, sonst passender Baugruppen-Wert, sonst `"7"`) | | `ssg-block-layer-vorgabe ent blockname attribs` | Layer einer nur eingefügten Referenz aus der Block-Vorgabe: `LAYER`-Attribut → `ssg-ils-block-ebene` → nichts | Die **Farbe** greift nur bei der Neuanlage eines Layers – `ssg-make-layer` lässt bestehende Layer unverändert. Layer, die eine Blockdatei aus `data/ils` selbst mitbringt (z. B. `ILS_SP`), legt BricsCAD bereits beim Einfügen des Blocks mit der Farbe *aus der DWG* an; dort wirkt die cfg-Farbe nicht, der Layername des Wrappers aber schon. **Block-Operationen:** | Funktion | Beschreibung | | --- | --- | | `ssg-timestamp` | Zeitstempel als `YYYYMMDDHHMMSS`-String erzeugen | | `ssg-make-blockname pt` | Eindeutigen Blocknamen aus Einfügepunkt + Zeitstempel erzeugen | | `ssg-insert-block layer farbe pfad pt xs ys rot` | Layer anlegen und Block einfügen | | `ssg-make-block pt rotation ss` | Auswahlsatz als Block definieren und gleich einfügen | **ILS-Blockdateien laden (2D/3D):** | Funktion | Beschreibung | | --- | --- | | `ssg-ils-dim-aktuell` | Gültige Dimension: `*ssg-ils-dim*` → `DXFM_DIM` → `"3D"` | | `ssg-ils-blockname[-dim] roh [dim]` | Effektiver Blockname mit Suffix `_2D`/`_3D` | | `ssg-ils-block-datei[-dim] roh [dim]` | Pfad zu `data/ils/.dwg` (3D-Fallback) | | `ssg-ils-block-laden[-dim] roh [dim]` | Blockdefinition sicherstellen; Rückgabe = effektiver Name | | `ssg-block-refresh-reset roh` | Redefine-Sperre lösen (`nil` = alle Blöcke) → DWG wird neu gelesen | Jede Block-DWG wird **einmal pro Zeichnung** aus der Datei neu definiert (Redefine); danach greift eine Sperre (`*ssg-block-refreshed*`, siehe `ssg-block-refreshed-p`/ `-set`). Ohne sie las jede einzelne Element-Einfügung die DWG erneut von der Platte und regenerierte alle bereits platzierten Referenzen – bei den großen 3D-Blöcken (`AS_Element_90_links_3D` ~52 MB) waren Tests wie `TEST_MUBEA` dadurch minutenlang beschäftigt. Wurde eine Block-DWG während der laufenden Sitzung geändert, hebt `ssg-block-refresh-reset` die Sperre auf. **Attribut-Operationen:** | Funktion | Beschreibung | | --- | --- | | `ssg-attrib-set tag wert ersetzen` | Ein Attribut der **letzten** Einfügung setzen oder anfügen | | `ssg-attrib-set-many alist` | Mehrere Attribute der letzten Einfügung auf einmal setzen | | `ssg-attrib-set-on ent alist` | Attribute eines **bestimmten** INSERT-Entity setzen (nicht `entlast`) | | `ssg-attrib-read ent` | Alle Attribute eines INSERT-Entity als Assoziationsliste lesen | | `ssg-attrib-read-dwg pfad` | Block temporär einfügen, ATTRIBs lesen, Block wieder löschen | | `ssg-attrib-defaults defs` | Default-Attribute aus Definitionsliste `((TAG DEFAULT) ...)` erzeugen | | `ssg-attrib-merge given defs` | Übergebene Attribute mit Defaults zusammenführen | | `ssg-attrib-make-defs defs hoehe` | Unsichtbare ATTDEF-Entities erzeugen (für Block-Definition) | | `ssg-attrib-alist-to-defs alist` | Assoziationsliste `(TAG . Wert)` in ATTDEF-Definitionsliste umwandeln | | `ssg-attrib-by-handle handle tag wert ersetzen` | Attribut eines Blocks anhand seines Entity-Handles suchen und ändern | **Dialog-Hilfsfunktionen:** | Funktion | Beschreibung | | --- | --- | | `ssg-dialog-run dcl id init-fn accept-fn` | Generischer DCL-Dialog: laden, initialisieren, starten, entladen | **Zoom-Hilfsfunktionen:** | Funktion | Beschreibung | | --- | --- | | `ssg-zoom-save` | Aktuelle View-Größe merken | | `ssg-zoom-restore` | Auf gemerkte View-Größe zurückschalten (nur wenn verändert) | **JSON-Konfigurationssystem:** Liest `data/json/component_defaults.json` beim Laden über `ssg-load-config`. Die Config wird in der globalen Variable `*ssg-config*` gehalten. | Funktion | Beschreibung | | --- | --- | | `ssg-load-config` | Config aus `$DXFM_DATA/json/component_defaults.json` laden | | `ssg-cfg section key` | Wert aus Config lesen, `nil` wenn nicht vorhanden | | `ssg-cfg-or section key default` | Wert aus Config lesen, `default` als Fallback | Interne Parser-Funktionen (nicht direkt aufrufen): `ssg-read-file-lines`, `ssg-cfg-trim`, `ssg-cfg-split-comma`, `ssg-cfg-parse-value`, `ssg-cfg-parse-array`, `ssg-cfg-parse-kv`, `ssg-cfg-parse-nested` --- ### `ssg_lang.lsp` – Sprachumschaltung (Deutsch/Englisch) Zentrale Textverwaltung fuer benutzersichtbare Meldungen. Kern-Modul, wird direkt nach `ssg_core` geladen. Wird schrittweise auf weitere Module ausgerollt (siehe `doc/plan/translate.md`). | Funktion | Beschreibung | | --- | --- | | `ssg-text key` | Liefert Text zu `key` in `*ssg-lang*`, Fallback `de_DE`, dann Key selbst | | `ssg-textf key werte` | Wie `ssg-text`, ersetzt Platzhalter `%1 %2 ...` durch `werte` (Liste) | Sprache umschalten: Befehl `SSG_SPRACHE` (Menue → Konfiguration → Sprache) setzt `*ssg-lang*` live und persistiert via `(setenv "DXFM_LANG" ...)`. Default ohne `DXFM_LANG` ist `"de_DE"`. --- ### `ssg_dbg.lsp` – Debug-Logging Dateibasiertes Debug-Log mit gepuffertem Schreiben. `dbgf`/`dbg`/`dbgmsg` puffern im Speicher, `dbgreturn` flusht auf die Platte (open/append/close). | Funktion | Beschreibung | | --- | --- | | `dbgopen filename envvar` | Debug-Datei öffnen (Verzeichnis via Umgebungsvariable, z.B. `"DXFM_LOG"`) | | `dbgclose` | Puffer schreiben und Datei schließen | | `dbgon` | Debug-Ausgabe aktivieren | | `dbgoff` | Debug-Ausgabe deaktivieren (verhindert Dateianlage wenn vor `dbgopen`) | | `dbgf funcname` | Funktionseintritt loggen, Einrückung erhöhen | | `dbgreturn retval` | Funktionsaustritt loggen, Puffer flushen, Rückgabewert durchreichen | | `dbg varname` | Variable oder Wert loggen: `(dbg 'x)` oder `(dbg wert)` | | `dbgmsg text` | Freitext-Nachricht loggen | | `dbgp text` | Alias für `dbgmsg` | | `dbgflush` | Puffer sofort schreiben (ohne `dbgreturn`, z.B. in Startup-Code) | Beispiel: ```lisp (dbgopen "test.dbg" "DXFM_LOG") (dbgf "meine-funktion") (dbg 'x) (dbgreturn ergebnis) ; schreibt Puffer auf Platte (dbgclose) ``` #### Zentrale Debug-Schalter (default je nach Schalter) Damit `dbg*`-Aufrufe dauerhaft im Code stehen bleiben können, ohne im Normalbetrieb ungeplant `.dbg`-Dateien zu erzeugen, gibt es eine zentrale Schalter-Sammlung (`*dbg-schalter*`, Symbol → 0/1). Eine Routine öffnet ihre Debug-Datei nur, wenn ihr Schalter eingeschaltet ist. Unbekannte/neue Schalter gelten implizit als 0/aus; die VF-Linienzug-Schalter unten sind bewusst mit Default 1/an vorregistriert (siehe Tabelle). | Funktion | Beschreibung | | --- | --- | | `dbg-schalter-on name` | Schalter `name` einschalten (1) | | `dbg-schalter-off name` | Schalter `name` ausschalten (0) | | `dbg-schalter-p name` | `T` wenn eingeschaltet, sonst `nil` | | `dbg-schalter-open name filename envvar` | Öffnet die Datei **nur** wenn Schalter=1; gibt `T`/`nil` zurück | | `dbg-schalter-liste` | Alle bekannten Schalter + Zustand ausgeben | Bekannte Schalter: | Schalter | Default | Modul | Loggt nach | | --- | --- | --- | --- | | `"vfl-modus1"` | **AN (1)** | Linienzug Modus 1 (manuelle Eingabe, `vf-linienzug-modus`) | `DXFM_LOG/vfl_modus1.dbg` | | `"vfl-modus2"` | **AN (1)** | Linienzug Modus 2 (3D-Objekte + Ziel-Höhe, `vf-linienzug-modus2`) | `DXFM_LOG/vfl_modus2.dbg` | | `"vfl-modus3"` | **AN (1)** | Linienzug Modus 3 (Vorwärts-Nachbau, `vf-linienzug-modus3`) | `DXFM_LOG/vfl_modus3.dbg` | Alle drei Modi loggen automatisch **jede** interaktive Eingabe, weil sie durchgehend über die gemeinsamen Wrapper `vfl-in-point`/`-string`/`-real`/ `-int`/`-value`/`-selection` gehen, die zentral in `vfl-journal-record` protokolliert werden – keine einzelne Aufrufstelle im Bau-Ablauf musste dafür angefasst werden. Da der Default AN ist, wird bei jedem Aufruf von `FOERDERANLAGE` → `linienzug` ohne weiteres Zutun mitgeloggt; bei Bedarf einzeln abschaltbar mit `(dbg-schalter-off "vfl-modus1")` usw. ```lisp ;; Standardmaessig bereits an - nur zur Kontrolle: (dbg-schalter-liste) ;; ... FOERDERANLAGE -> linienzug -> Modus 1/2/3 durchspielen ... ;; -> Datei DXFM_LOG/vfl_modus.dbg enthält Session + alle Eingaben (dbg-schalter-off "vfl-modus1") ; bei Bedarf einzeln abschalten ``` Muster in einer Routine: ```lisp (setq dbg-an (dbg-schalter-open "vfl-modus3" "vfl_modus3.dbg" "DXFM_LOG")) (if dbg-an (dbgmsg "FRAGE: ...")) ;; ... get* ... (if dbg-an (progn (dbgmsg "ANTWORT:") (dbg 'wert) (dbgflush))) ;; am Ende: (if dbg-an (progn (dbgreturn ergebnis) (dbgclose))) ``` --- ### `ssg_dialog.lsp` – Dialog-Pattern für Modul-Einfüge-Makros Kapselt das wiederkehrende DCL-Muster: Dialog mit Vorschaubild, Radio-Auswahl, Block einfügen, Attribute setzen. Setzt `ssg_core.lsp` voraus. | Funktion | Beschreibung | | --- | --- | | `ssg-dia-show-slide tile lib name` | Vorschau-Slide im image-Tile anzeigen (Hintergrundfarbe aus Config: `infrastruktur/dialog_preview_bg_color`) | | `ssg-dia-update-preview tile idx lib liste` | Vorschau anhand Index-String aus Radio-/List-Tile aktualisieren | | `ssg-modul-dialog dcl id sel-tile start-val prev-tile lib slides extra-init extra-read` | Vollständiger Modul-Dialog: lädt DCL, setzt Tiles, verknüpft Vorschau, liefert gewählten Index | | `ssg-insert-modul layer farbe pfad attribs rotation` | Standardsequenz: Layer anlegen → Punkt abfragen → Block einfügen → Attribute setzen | Die Datei enthält außerdem ein auskommentiertes **Template** für neue `c:BEFEHL`-Definitionen nach dem SSG-Muster. --- ### `ssg_layer.lsp` – Interaktive Layer-Verwaltung Benutzerfreundliche BricsCAD-Befehle für Layer-Operationen. Alle Befehle sichern die Umgebung über `ssg-start`/`ssg-end`. Setzt `ssg_core.lsp` voraus. **Hilfsfunktionen:** | Funktion | Beschreibung | | --- | --- | | `ssg-layer-list-all` | Alle Layernamen als Liste | | `ssg-layer-info-all` | Alle Layer als Assoziationslisten (Name, Farbe, Linientyp, Flags) | | `ssg-layer-visible-p name` | Sichtbarkeitsstatus prüfen (`T` = sichtbar) | | `ssg-layer-safe-p name gruppe` | Prüfen ob Layer ausgeschaltet werden darf (nicht aktiver Layer) | | `ssg-layer-group name` | Gruppen-Kennung aus Layernamen extrahieren (Position 2–3) | | `ssg-layer-rename-group name neu` | Neuen Layernamen mit geänderter Gruppen-Kennung berechnen | | `ssg-layer-group-move alt neu` | Alle Objekte einer Layergruppe auf neue Gruppen-Kennung verschieben | **BricsCAD-Befehle:** | Befehl | Beschreibung | | --- | --- | | `c:SSG-LayerSet` | Layer des angeklickten Objektes als aktiven Layer setzen | | `c:SSG-LayerInfo` | Layer des angeklickten Objektes anzeigen | | `c:SSG-LayerMove` | Auswahlsatz auf Layer eines anderen Objektes verschieben | | `c:SSG-LayerCopy` | Auswahlsatz kopieren und Kopie auf Ziel-Layer legen | | `c:SSG-LayerMoveType` | Alle Objekte des gleichen Typs auf dem gleichen Layer verschieben | | `c:SSG-LayerOff` | Layer durch Anklicken ausschalten (Schleife bis Enter) | | `c:SSG-LayerFreeze` | Layer durch Anklicken einfrieren (Schleife bis Enter) | | `c:SSG-FreezeAll` | Alle Layer außer einem einfrieren | | `c:SSG-ThawAll` | Alle Layer auftauen | | `c:SSG-LayerOnAll` | Alle ausgeschalteten Layer einschalten | | `c:SSG-LayerDelete` | Layer mit Sicherheitsabfrage löschen (Objekte + Layer) | --- ### `ssg_input.lsp` – Generische Eingabe-Wrapper Abfragen mit Vorgabewert (`<..>` im Prompt, Enter = Vorgabe). Steht neben den VF-spezifischen `vfl-in-*` (`vf_linienzug.lsp`); diese bleiben unveraendert. Kein Journal, aber ein optionaler Hook. ESC bricht wie ueblich ueber `*error*` ab. | Funktion | Beschreibung | | --- | --- | | `ssg-in-real text default` | Reelle Zahl (`getreal`), Vorgabe ohne ueberfluessige Nullen angezeigt | | `ssg-in-int text default` | Ganzzahl (`getint`) | | `ssg-in-string text default` | String mit Leerzeichen; Enter ohne Eingabe liefert die Vorgabe (ohne Vorgabe `nil`) | | `ssg-in-point text default base` | Punkt, `base` = Gummiband-Basispunkt oder `nil` | | `ssg-in-dist text default base` | Abstand (`getdist`) | | `ssg-in-keyword text schluessel default` | Schluesselwort im `initget`-Format, z.B. `"Alle Auswahl"` | | `ssg-in-yesno text default` | Ja/Nein, **T = Ja**; `default` T = Enter bedeutet Ja. Woerter aus `ssg-text "in-yesno-keys"` | | `ssg-in-default wert default`, `ssg-in-fmt-real`, `ssg-in-fmt-pt`, `ssg-in-prompt`, `ssg-in-first-word`, `ssg-in-second-word` | Reine Hilfsfunktionen (in `tests/test_unit.lsp` getestet) | Hook (z.B. fuer ein Eingabe-Journal): `(setq *ssg-in-hook* 'meine-funktion)` mit `(defun meine-funktion (art prompt wert) ...)`; `art` ist `"REAL"`, `"INT"`, `"STRING"`, `"POINT"`, `"DIST"`, `"KEYWORD"` oder `"YESNO"`. Ein Hook, der einen Fehler wirft, wird abgeschaltet. --- ### `ssg_id.lsp` – Eindeutige Block-IDs Jedes exportierbare Element bekommt ein Attribut `ID` mit einer eindeutigen, vierstelligen Nummer. Kern-Modul (nach `ssg_layer`, vor `ssg_ks_insert`). | Befehl / Funktion | Beschreibung | | --- | --- | | `c:IDGENERATE` | Weist dem zuletzt eingefuegten Block eine neue ID zu | | `c:IDSCHECK` | Prueft alle Bloecke auf doppelte IDs und korrigiert sie | | `ssg-id-format n` | Zahl als 4-stelligen String formatieren (`1` → `"0001"`) | | `ssg-id-max` | Hoechste bereits vergebene ID in der Zeichnung | | `ssg-id-generate ent` | Neue ID erzeugen und auf den Block setzen | | `ssg-id-check-all` | Zeichnungsweit auf Doubletten pruefen und korrigieren | | `ssg-id-collect-blocks` | Alle ID-faehigen INSERT-Entities sammeln (gleiche Muster wie `csv:collect-export-blocks`, inkl. Separator/Scanner/BTMT) | --- ### `ssg_ks_insert.lsp` – KS-Extraktion und Block-Einfuegeprimitiven Gemeinsame Basis fuer VarioFoerderer (`vf_core.lsp`) UND Gefaellestrecke – frueher in beiden Modulen dupliziert, jetzt einzige Quelle. Muss laut MNL **vor** Gefaellestrecke/`vf_core` geladen sein; Kern-Modul. | Funktion | Beschreibung | | --- | --- | | `vec-length v` | Laenge eines 3er-Vektors | | `ks-line-axis laenge` | Achsenkennung (X/Y/Z) aus Linienlaenge der KS-Hilfslinie | | `ks-normalize-name nm` | Historische KS-Blocknamen (`KSYS_EIN`/`-AUS`) auf `KS_EIN`/`KS_AUS` normalisieren | | `ks-relativize` / `ks-absolutize` | KS-Punkte zwischen relativem und Welt-Koordinatensystem umrechnen | | `ensure-block-loaded name` | Block aus der Bibliothek einmalig ins aktive Dokument laden | | `extract-ks-from-block[-raw] name` | KS_EIN/KS_AUS-Punkte eines Blocks ermitteln (Cache `*ks-cache*`) | | `insert-block-by-ks name ks-nr pt` | Block anhand KS-Punkt einfuegen (nur Rz-Rotation) | | `insert-inclined-scaled-block` / `insert-rotated-block-with-ks` | Geneigten/rotierten Block mit KS-Offset einfuegen (Rz*Ry) | | `ks-xy-winkel ks-eintrag` | Richtung der X-Achse eines KS in der XY-Ebene (Grad); `nil` bei senkrechter Achse | | `ks-block-messen name` / `ks-versatz ks-data` | KS eines Blocks messen, ohne bleibende Einfuegung / Versatz KS_AUS-KS_EIN | | `insert-block-ks-skaliert name pt hz skala` | Wie `insert-block-by-ks`, zusaetzlich in X skaliert | | `insert-block-ks-matrix name pt R skala` | Einfuegen mit **freier 3x3-Rotationsmatrix** (`ks-rot-x/-y/-z`), z.B. fuer aufgerichtete Baugruppen | --- ### `ssg_blocktools.lsp` – Block-Werkzeuge Ursprung: ACADSST `TOOLS.LSP` (`BLKANZAHL`, `BLKENTF`), `DINGEN2` (`g-bli`), `ssg.lsp` (`suchen-aendern`). Arbeitet im **aktuellen Layout-Reiter**. Blocknamen werden ueber den wirksamen Namen verglichen (dynamische Bloecke); Muster mit `wcmatch` (`*`, `?`, `#`, ohne Gross-/Kleinschreibung). | Befehl | Beschreibung | | --- | --- | | `SSG_BLOCK_COUNT` | Block anklicken, `Name` fuer ein Muster, oder Enter fuer eine Statistik aller Bloecke | | `SSG_BLOCK_DELETE` | Alle Einfuegungen eines Blocks/Musters loeschen; Rueckfrage (Vorgabe Nein), Einfuegungen auf gesperrten Layern bleiben stehen; eine Undo-Gruppe | | `SSG_ATTR_ERSETZEN` | Attributwerte suchen/ersetzen: Bereich (Alle mit Blockname-Muster / Auswahl), Tag-Muster, Suchtext, Ersatz, Modus Teil (Teilstring) oder Gesamt (ganzer Wert). Zeigt vorab die Anzahl und fragt nach | | Funktion | Beschreibung | | --- | --- | | `ssg-bt-inserts muster` | Entity-Liste aller Einfuegungen im Layout (optional nach Muster) | | `ssg-bt-statistik muster` | `((NAME . ANZAHL) ...)` alphabetisch | | `ssg-bt-attr-ersetzen-ent ent tag suche ersatz teil-p probe-p` | Attribute EINER Einfuegung ersetzen bzw. nur zaehlen (`probe-p`); Rueckgabe Anzahl | | `ssg-bt-attr-ersetzen-liste ents tag suche ersatz teil-p probe-p` | Dasselbe fuer eine Entity-Liste | | `ssg-bt-wert-neu wert suche ersatz teil-p` | Reiner Kern: neuer Wert oder `nil` (unveraendert); beachtet Gross-/Kleinschreibung | | `ssg-bt-name-passt-p`, `ssg-bt-zaehle-namen`, `ssg-bt-muster-escape` | Reine Hilfsfunktionen (getestet) | --- ### `ssg_texttools.lsp` – Text-Werkzeuge Ursprung: ACADSST `CHGTEXT.LSP`, `PARTEXT.LSP`, `ALTHOEHE.LSP`, `ALTSTIL.LSP`. Laden **nach** `ssg_blocktools` (nutzt `ssg-bt-wert-neu` und `ssg-bt-attr-ersetzen-liste`). Arbeitet in der XY-Ebene des Welt-KS. | Befehl | Beschreibung | | --- | --- | | `SSG_TEXT_ERSETZEN` | Suchen/Ersetzen in TEXT, MTEXT **und** Block-Attributen; Bereich Alle (Layout) oder Auswahl; Vorschau mit Anzahl, Rueckfrage | | `SSG_TEXT_PARALLEL` | Linie/Polylinie/Bogen anklicken, Text wird parallel (lesbar, nie auf dem Kopf) mit Abstand gesetzt; Hoehe, Abstand und letzter Text werden fuer die Sitzung gemerkt; Enter beendet | | `SSG_TEXT_HOEHE` | Texthoehe gewaehlter TEXT/MTEXT aendern | | `SSG_TEXT_STIL` | Textstil gewaehlter TEXT/MTEXT aendern (Stil muss existieren) | Reine Kerne (getestet): `ssg-tt-lesbarer-winkel` (Winkel 90 Grad bis 270 Grad um 180 Grad drehen, Ergebnis in [0, 2 pi)), `ssg-tt-versetzt`. Nicht uebernommen aus Walz: `Export`/`Import` von Texten (besser per Python/ezdxf), `KREISTXT`, `TXTBOXED`, `CHTEXT`/`ASCTEXT`/`PTEXT` (Autodesk 1990, durch BricsCAD-Standard ersetzt). --- ### `ssg_load.lsp` – Laderoutine Headless-Loader fuer .scr-Testskripte (z.B. `tests/test_unit.scr`). Laedt alle Kern- und Feature-Module in derselben Reihenfolge wie `SSG_LIB.mnl`, ohne Menueabhaengigkeit. Ermittelt den Pfad automatisch über `findfile`. Ruft direkt nach `ssg_core.lsp` die Funktion `ssg-load-config` auf, damit die JSON-Konfiguration für alle nachfolgenden Module verfügbar ist. --- ### `VarioFoerderer.lsp` – Vario-Förderer (Wrapper) Kompatibilitäts-Wrapper, der `vf_core.lsp` lädt (Plugin-Dispatcher-Architektur über `*vf-typ-registry*`; lädt seinerseits `vf_konstanten.lsp`, `vf_standard.lsp`, `vf_etage.lsp`, `vf_linienzug.lsp`, danach `vf_spec.lsp`). Parametrischer Generator für ILS Vario-Förderanlagen (Typen Standard/Etage/Linienzug). Liest Block-Geometrie aus einer DWG-Bibliothek (`DXFM_BLOCKS`) über die Primitiven aus `ssg_ks_insert.lsp`. Alle Konstanten (Abstände, Winkel, Skalierung) werden über `ssg-cfg-or` (`component_defaults.json`, Sektion `vario`) bzw. die zentralen `*vfk-*`-Konstanten aus `vf_konstanten.lsp` gelesen. **Befehl:** `c:FOERDERANLAGE` / `c:VarioFoerderer` – wählt zuerst den Anlagen-Typ (Standard/Etage/Linienzug): - **Typ „standard“ und „etage“ + Werteingabe:** laufen über denselben, typ-parametrisierten Dialog-Ablauf (`vf-dialog-ablauf` in `vf_core.lsp`; `vfs-standard-dialog-ablauf`/`vfe-etage-dialog-ablauf` sind nur noch duenne Wrapper darauf) mit den Dialogen `vfs-dialog-eingabe-basis`/ `vfs-dialog-winkel-verteilung` ([`dcl/variofoerderer.dcl`](../dcl/variofoerderer.dcl)): Dialog 1 erfasst deltaL, Förderrichtung, Einfügehöhe/deltaH, Fahrtrichtung und Seite/Motorseite; Dialog 2 lässt Winkel und L_GF-Verteilung (vorne/hinten) wählen. Startpunkt (X/Y) wird vorher in der Zeichnung gepickt. - **Typ „standard“/„etage“ + 3D-Linie** laufen ueber denselben Basis-Dialog statt rein interaktiv auf der Konsole. - **Typ „linienzug“** (Streckengruppe, `vf-linienzug-modus` in `vf_linienzug.lsp`) hat drei Modi (manuell/Wizard, 3D-Objekte+Zielhoehe, Vorwaerts-Nachbau) und zeichnet jede Eingabe in einem Journal auf (XDATA `SSG_VF_EDIT` am `VF_n`-Block); Standard laeuft per Default ueber DCL-Dialoge (`VF_WIZARD_AN`/`VF_WIZARD_AUS` schalten um). Details, Journal-Format, Headless-Modus (`*vfl-headless*`) und Spec-basierter Bau ohne Dialog (`vf_spec.lsp`, `VF_SPEC_BAU`): siehe [`../doc/variofoerderer.md`](../doc/variofoerderer.md) und `../CLAUDE.md`. **Bearbeiten (`c:VARIOFOERDERER_EDIT`):** Doppelklick auf einen `VF_n`-Block – bzw. Rechtsklick-Kontextmenü/`SSG_BLOCKEDIT` – dispatcht per XDATA-Marker (`"standard"`/`"etage"`/`"linienzug"`) in den passenden Neuaufbau-Zweig (`vfe-edit-ent` fuer Etage, `vfl-edit-ent` fuer Linienzug) und öffnet dieselben Dialoge, vorbelegt mit den aktuellen Parametern; der Block wird am selben Startpunkt neu aufgebaut (neue `VF_n`-Nummer). Altbestand-Blöcke ohne Marker werden mit einer Meldung abgewiesen, statt sie zu verändern. Winkel und L_GF1 werden aus den Sivas-Attributen (`VF_WINKEL`, `L_GF_m`) vorbelegt. Die Block-Einfuege-Primitiven (`ensure-block-loaded`, `insert-block-by-ks`, `insert-inclined-scaled-block`, `insert-rotated-block-with-ks` u.a.) sind seit der Konsolidierung zentral in [`ssg_ks_insert.lsp`](#ssg_ks_insertlsp--ks-extraktion-und-block-einfuegeprimitiven) definiert (von `vf_core.lsp`, `Gefaellestrecke.lsp` und `TEFInsert.lsp` genutzt). Weitere interne Helfer: `init-bibliothek` (Block-Bibliothek aus DWG laden), `get-line-start-end-points ename`, `punkt-differenz p1 p2`. --- ### `Gefaellestrecke.lsp` – Gefällestrecken-Generator Erzeugt eine einfache Förderanlage mit festem Gefällewinkel: `AUS-Element → Staustrecke (skaliert) → Separator → EIN-Element` Setzt `VarioFoerderer.lsp` voraus (nutzt dessen `init-bibliothek`, `insert-block-by-ks` und weitere Hilfsfunktionen). Alle Defaults (`default_delta_l`, `default_winkel`, `separator_breite`) kommen aus Config-Sektion `gefaelle`. **Befehl:** `c:GEFAELLESTRECKE` Drei Modi: - **Modus 1:** Startpunkt (X/Y) picken, restliche Parameter über den DCL-Dialog [`dcl/gefaellestrecke.dcl`](../dcl/gefaellestrecke.dcl) eingeben (ΔL, Einfügehöhe Z, Gefällewinkel, Fahrtrichtung, AUS-/EIN-Element je Winkel 30/90 und Seite links/rechts). Die Einfügehöhe aus dem Dialog setzt die Z-Koordinate des Startpunkts. - **Modus 2:** Bestehende 3D-Linie wählen (ΔL und Fahrtrichtung werden daraus berechnet), restliche Parameter interaktiv über die Kommandozeile. - **Modus 3:** Linienzug aus LINE-/ARC-Objekten; ARC-Segmente werden durch Gefällebogen-Blöcke ersetzt, Winkel aus ΔH und Geometrie berechnet. **Dialog-Hilfsfunktionen:** `gf-dialog-eingabe` (lädt/liest den DCL-Dialog, liefert `(deltaL winkel hz-winkel as-seite es-seite einfuegehoehe as-winkel es-winkel dim geruest-einzelmodul geruest-typ)` oder `nil` bei Abbruch) und `gf-modus12-abschluss` (gemeinsamer Abschluss für Modus 1+2: setzt die Element-Masse per `vf-set-as-masse`/`vf-set-es-masse`, berechnet die Staustreckenlänge, zeigt die Zusammenfassung und ruft `gefaellestrecke-einfuegen`). Die AUS-/EIN-Element-Varianten (30/90 Grad, links/rechts) liegen als DWG-Blöcke `AS_Element_<30|90>_` bzw. `ES_Element_…` flach in `data/ils/` (Dimension im Dateinamen als Suffix `_2D`/`_3D`, aufgelöst über `ssg-ils-block-datei`); ihre Maße werden direkt aus dem Block gemessen (`vf-element-masse`), es gibt dafür keine JSON-Tabelle. **Bearbeiten (`c:GEFAELLESTRECKE_EDIT`):** Doppelklick auf einen Gefällestrecken-Block (`GF_n`) – bzw. Rechtsklick-Kontextmenü/`SSG_BLOCKEDIT` – öffnet denselben DCL-Dialog, vorbelegt mit den aktuellen Parametern, und baut die Strecke bei OK am selben Startpunkt neu auf. Damit Fahrtrichtung und Element-Winkel (30/90) verlustfrei rekonstruiert werden, schreibt `gefaellestrecke-einfuegen` diese beim Erstellen als XDATA (App `SSG_GF_EDIT`, Helfer `gf-xdata-schreiben`/`gf-xdata-lesen`) auf das `GF_n`-INSERT – bewusst **nicht** in den Sivas-Attributsatz, damit der Export unverändert bleibt. Ältere Blöcke ohne XDATA werden aus den Attributen vorbelegt (Fahrtrichtung 0°, Element-Winkel 90° als Vorgabe). Der Neuaufbau läuft über `gf-rebuild` (nicht-interaktiv, ohne Konsolen-Rückfrage). --- ### `TEFInsert.lsp` – TEF-Geradenstuecke entlang einer Linie **Befehl:** `c:TEF_FOERDERER_LINIE`. Zeichnet eine Linie zwischen zwei gewaehlten Punkten und reiht darauf 1000-mm-TEF-Geradenelemente (`0_B10030` aus `DXFM_OMNIFLO`) aneinander; ist die Linienlaenge kein exaktes Vielfaches, wird ein zusaetzliches, zurueckverschobenes Element eingefuegt, sodass der Foerderer exakt am Linienende endet (keine Luecke, kein abgeschnittenes Element). Die Linie darf geneigt sein (3D) – Elemente werden dann analog zu `vf_core.lsp`/`ssg_ks_insert.lsp` (Rz*Ry-Rotation) gekippt eingefuegt. Nicht zu verwechseln mit den einzelnen `OMNI_TEF_*`-Befehlen in `OmniModulInsert.lsp`. --- ### `export.lsp` – Export-Funktionen Sammelt INSERT-Blöcke aus der Zeichnung und exportiert sie als JSON oder ruft Python-Skripte für den Sivas-Export auf. | Befehl / Funktion | Beschreibung | | --- | --- | | `c:EXPORTSIVAS` | Alle Blöcke als JSON sammeln und `export_sivas.py` aufrufen (Sivas-CSV) | | `c:EXPORTCSV` | Alle Blöcke als JSON sammeln und `export_csv.py` aufrufen (einfache Item-Liste, inkl. Omniflo Bogen/Weiche/Gerade und abschließender "Omniflo Sum"-Zeile) | | `csv:collect-export-blocks` | Alle INSERT-Blöcke der Zeichnung als JSON-Array sammeln | | `csv:run-export py-skript json-datei output-csv` | Python-Skript mit gesammelten Daten aufrufen; legt vorher fuer in VF_n/GF_n/KREISEL_n **verpackte** Separator_SP_2D/_3D-Sub-INSERTs (Sensor-Symbol; siehe `ssg-collect-nested-inserts` in `ssg_core.lsp` - NICHT der interne Staustrecke_Separator_SP-Trennsteg) je eine echte, temporaere Kopie **an derselben Stelle** an (`csv:sep-proxies-erzeugen`, Position/Drehung/Skalierung ueber alle Verschachtelungsebenen hinweg identisch zum Vorbild - kein kosmetischer Versatz mehr, der die geometrische Sensor-Zuordnung verfaelschen wuerde), damit sie ganz normal ueber `ssg-id-check-all`/`csv:collect-export-blocks` erfasst werden, und entfernt sie danach wieder (`csv:sep-proxies-loeschen`). Die verpackten **AS-/ES-Schleuselemente** derselben Kette bekommen stattdessen einen DAUERHAFTEN Marker (`csv:asmark-aktualisieren`, siehe unten); beide Wege teilen sich die Platzierungsrechnung (`csv:nested-welt-records` + `csv:block-einfuegen-welt`). Reihenfolge ist wesentlich: **erst** `ssg-id-check-all` (Wrapper-IDs endgueltig), **dann** die Kopien/Marker (ZUORDNUNG direkt beim Erzeugen), **dann** `csv:ketten-verarbeiten` (IDs in Kettenfolge, Separator-Typ, sepliste-Stellvertreter), **dann** ein zweites `ssg-id-check-all` (Restnummerierung) | | `csv:asmark-aktualisieren` | AS-/ES-Kettenenden sichtbar machen: setzt an jedes verpackte `AS_Element*`/`ES_Element*` einer VF_n/GF_n-Kette ein kleines Rechteck (Block `ZZ_ASES_MARKER`, Groesse aus `cfg/export.cfg` `[Marker]`, Layer aus `cfg/layer.cfg` `ases_marker`), das nach dem Export **stehen bleibt**. Unsichtbare Attribute ID/ZUORDNUNG/TYP/LFDNR/ELEMENT/ARTINR; die `ID` fuellt `ssg-id-check-all` und der naechste Export **uebernimmt sie**, ein AS/ES behaelt seine TeileId also ueber Laeufe hinweg. Baut bei jedem Export alle Marker neu auf (Wiederfinden ueber ZUORDNUNG+TYP+LFDNR) und entfernt verwaiste | | `csv:ketten-verarbeiten sep-proxies element-proxies asmarks` | Je VF_n/GF_n aus DERSELBEN KS-geordneten Kette (`csv:kette-von`, einmal je Lauf berechnet): Einheiten-Proxies (`group_vf_gf`), IDs in Kettenfolge, Separator-Typ VF/GF (`csv:kette-sep-typen` -> `*cs-sep-typ-by-handle*`) und der Stellvertreter jedes `sepliste`-Eintrags (`*csv-sepliste-id-by-handle*`, Key = Wrapper-Handle; `csv:block-to-json` liest dessen ID beim Schreiben als Feld `id`) | | `csv:sepliste-kette-paare eintraege typen` | Zuordnung `sepliste`-Eintrag -> Kettenbaustein ueber die **Reihenfolge je Typ** (k-tes `AS`/`SEP`/`ES` der sepliste = k-tes der KS-Kette), ohne die (teils gespiegelten) sepliste-Koordinaten. Reine Rechenfunktion, getestet in `tests/test_unit.lsp` | | `csv:element-familien` | EINZIGE Tabelle Familie -> Blockmuster (`pattern_nested_*` aus `cfg/export.cfg`) fuer Suche (`csv:pattern-nested-element`) UND Klassifizierung (`csv:element-teileart`, darauf aufbauend `csv:kette-typ`) | | `csv:read-attribs ename` | Attribute eines INSERT als JSON-Objekt lesen | | `c:OMNI_UPDATE_ATTRIBS` | HOEHE/DREHUNG aller Omniflo-Elemente aus der Zeichnung aktualisieren | Der Omniflo-CSV-Export (Bogen/Weiche/Gerade, inkl. Merkmale-Aufbau und Sum-Zeile) läuft seit Entfernung von `c:OMNI_EXPORT` ausschließlich über `c:EXPORTCSV` → `lib/export_csv.py` (`build_bogen_merkmale`, `build_weiche_merkmale`, `build_gerade_merkmale`, `build_omni_sum_merkmale`). **Ausgabeordner:** `c:EXPORTSIVAS`/`c:EXPORTCSV` schreiben immer nach `DXFM_RESULTS`. `TEST_EXPORT_ALL` (siehe `tests/test_export_all.lsp`) leitet waehrend des Testlaufs stattdessen nach `DXFM_TESTOUT` um, über die reine Lisp-Sitzungsvariable `*export-test-override*` (wird von `csv:run-export` zusaetzlich zu `DXFM_RESULTS` abgefragt und danach garantiert wieder auf `nil` zurueckgesetzt, auch bei einem Fehler, ueber `vl-catch-all-apply`). **Wichtig:** Der Umweg über `*export-test-override*` ist bewusst gewaehlt und **kein** `(setenv "DXFM_RESULTS" ...)`. In BricsCAD/AutoCAD schreibt `(setenv ...)` nicht nur in den Windows-Prozess, sondern dauerhaft in die Profil-Registry der Anwendung – der Wert überlebt damit jeden Neustart und jedes `setenv.bat`, bis er erneut per `(setenv)` überschrieben wird. Ein einziger `(setenv "DXFM_RESULTS" ...)`-Aufruf (z. B. ein frueherer, unvollstaendiger Testlauf) würde `DXFM_RESULTS` für alle künftigen Sitzungen unwiderruflich auf den Testordner umbiegen. Falls das bereits passiert ist, hilft nur ein manuelles `(setenv "DXFM_RESULTS" "")` in BricsCAD, um den Profil-Wert zu korrigieren. --- ### `tests.lsp` – Testbefehle Testbefehle für die Python-Integration. Export-Funktionen wurden in `export.lsp` ausgelagert. | Befehl | Beschreibung | | --- | --- | | `c:CALLPYTHON` | `testpycall.py` via `_.SHELL` aufrufen und Rückgabe einlesen (Integrationstest LISP↔Python) | --- ## Tests Zwei Test-Ebenen, beide gesteuert über `bin\run_tests.bat` (Details und Verzeichnisstruktur: [`../tests/README.md`](../tests/README.md)): 1. **LISP-Unit-Tests** (`tests/test_unit.lsp`) – prüfen reine Standardfunktionen direkt in AutoLISP, ohne Zeichnung. 2. **Zeichnungsbasierte Integrationstests** (`tests/test_*.lsp` + `tests/test_*.py`) – erzeugen Blöcke in BricsCAD und validieren das Ergebnis anschließend mit pytest/ezdxf. Module: `test_kreisel`, `test_foerderer`, `test_linienzug`, `test_gefaellestrecke`, `test_omniflo`, `test_omniflo_strecke` und `test_mubea` (Gesamtmodell: Kreisel + Vario + Gefällestrecken + Separatoren aus `tests/testdata/mubea.json` in einer Zeichnung; über `SSG_RUN_ALL_TESTS` bzw. Menü „Tests → Mubea Gesamtmodell"). Welche JSON-Angaben pro Element-Typ nötig sind (abgeleitet aus den Produktions-Routinen, nicht aus den Test-Wrappern), dokumentiert [`../tests/testdata/object_data.md`](../tests/testdata/object_data.md) – inkl. der Unterscheidung scriptbar vs. nur-interaktiv. ### LISP-Unit-Tests (`test_unit.lsp`) Testen **reine Hilfsfunktionen** – solche, die nur aus ihren Argumenten ein Ergebnis berechnen und keine Zeichnungsdatenbank, Auswahlsätze, Dialoge oder Blockdateien brauchen (String-, Zahl-, Vektor-, Listen- und Alist-Helfer). Abgedeckt werden Funktionen aus `ssg_core`, `ssg_lang`, `ssg_id`, `vf_core`, `export`, `Gefaellestrecke`, `OmniModulInsert` und `KreiselInsert`. **Aufruf über die Kommandozeile** (startet BricsCAD headless): ```cmd bin\run_tests.bat --lisp ``` `run_tests.bat --lisp` löscht das alte Ergebnis, startet BricsCAD mit `tests/test_unit.scr` (lädt `ssg_load.lsp` + `test_unit.lsp`, ruft `TEST_UNIT` auf und beendet BricsCAD), gibt danach `tests/output/unit_results.txt` aus und liefert Exit-Code 1, wenn ein Test fehlschlägt oder kein Report entsteht. **Aufruf direkt in BricsCAD** (SSG_LIB geladen): ```lisp (load (strcat (getenv "DXFMAKRO") "/tests/test_unit.lsp")) TEST_UNIT ``` **Aufbau eines Testfalls.** `test_unit.lsp` enthält ein Mini-Framework: jeder Testfall ruft eine Funktion auf und vergleicht das Ergebnis gegen einen erwarteten Wert. Die Assertion-Helfer zählen Treffer/Fehler mit und schreiben je eine `PASS`/`FAIL`-Zeile: | Helfer | Vergleich | | --- | --- | | `tu-eq name erwartet ist` | exakt (`equal`) – Strings, Ganzzahlen, Listen davon | | `tu-eqf name erwartet ist` | numerisch mit Toleranz `1e-6` – Fliesskomma, auch verschachtelte Listen | | `tu-true name ist` | Ergebnis ist nicht `nil` | | `tu-nil name ist` | Ergebnis ist `nil` | Beispiel – Formatierung einer ID und ein Vektor-Kreuzprodukt: ```lisp (tu-eq "ssg-id-format/1" "0001" (ssg-id-format 1)) (tu-eqf "vec3-cross/x-cross-y" '(0.0 0.0 1.0) (vec3-cross '(1.0 0.0 0.0) '(0.0 1.0 0.0))) ``` Die Testfälle sind in `tu-tests-`-Funktionen gruppiert; `c:TEST_UNIT` ruft alle nacheinander auf, gibt eine Zusammenfassung (`Gesamt / PASS / FAIL`) aus und schreibt den Report `unit_results.txt` mit einer abschließenden Zeile `RESULT: OK` bzw. `RESULT: FAIL` (die `run_tests.bat` auswertet). **Neuen Testfall ergänzen:** in der passenden `tu-tests-`-Funktion eine `tu-eq`/`tu-eqf`/`tu-true`/`tu-nil`-Zeile hinzufügen. Für ein neues Modul eine eigene `tu-tests-`-Funktion anlegen und in `c:TEST_UNIT` aufrufen. Getestet werden sollten nur Funktionen ohne Zeichnungs-/Dialog-Umfeld – alles mit `ssget`/`entget`/`entmake`/`command`/`vla-*`/`entsel`/DCL gehört in die zeichnungsbasierten Integrationstests. --- ### `KreiselInsert.lsp` – ILS Kreisel und Eckrad AutoLISP-Implementierung fuer ILS Kreisel und Eckrad. Enthaelt alle Befehle fuer Einfuegen, Verbinden, Neuzeichnen und Bearbeiten. | Befehl | Beschreibung | | --- | --- | | `c:KreiselInsert` | Kreisel-Block einfuegen: Basispunkt (AN-Seite), Abstand als Gummiband-Linie (deren Richtung = Rotation), Hoehe, Kreiselart | | `c:KreiselConnect` | Zwei Punkte waehlen, Kreisel dazwischen einfuegen | | `c:KreiselRedraw` | Bestehenden Kreisel mit neuen Parametern neu zeichnen | | `c:KreiselQuick` | Schnelleinfuegung mit Standardwerten | | `c:KreiselEdit` | Kreisel-Attribute ueber DCL-Dialog bearbeiten | | `c:KreiselParams` | Aktuelle Kreisel-Parameter anzeigen | | `c:KreiselLabelSetup` | Schriftart, Hoehe und Farbe fuer Kreisel-Beschriftung konfigurieren | | `c:KreiselLabelPos` | Label-Offset (dx/dy) setzen | | `c:KreiselLabelHoehe` | Label-Hoehe aendern | | `c:ILS_Eckrad` | Eckrad an Tangentenpunkt einfuegen, Parameter per DCL-Dialog (`eckrad_edit.dcl`; ohne GUI Konsolen-Rueckfall) | | `c:EckradEdit` | Eckrad ueber DCL-Dialog bearbeiten (Bezeichnung, Hoehe, Drehung, Drehrichtung, Geruest; kein Abstand/Typ), Neuaufbau mit gleicher Nummer | | `c:KREISEL_WEICHEN` | Beruehrpunkte (= ILS-Weichen) aller Kreisel/Eckraeder gleicher Hoehe ermitteln: Rad/Rad und Rad/Laufprofil auf der freien Radseite, Toleranz `toleranz_beruehrung`/`toleranz_hoehe` (component_defaults.json). Ueberschneidungen und Seite-an-Seite-Kreisel werden als Kollision gemeldet. Konsolenliste + Kreis-Marker (Layer `kreisel_weiche`/`kreisel_kollision`), ein neuer Lauf ersetzt die Marker. Derselbe Kern (`kreisel-bp-ermitteln`) liefert beim Export `kreisel_weichen.json` fuer `export_csv.py`/`export_sivas.py` | --- ### `OmniModulInsert.lsp` – Omniflo-Komponenten AutoLISP-Implementierung fuer alle Omniflo-Komponenten: Boegen, Weichen, Verbinder, Transferwagen. Liest JSON-Daten aus `omniflo_boegen.json` und `omniflo_weichen.json`. Alle Konstanten via `ssg-cfg-or` aus Config. | Befehlsgruppe | Befehle | Beschreibung | | --- | --- | --- | | Daten | `OMNI_LOAD`, `OMNI_INFO_BOGEN`, `OMNI_INFO_WEICHE` | JSON-Daten laden und abfragen | | Boegen | `OMNI_APB_630_90`, `OMNI_APB_550_675`, `OMNI_APB_630_45`, `OMNI_APB_550_225`, `OMNI_APB_650_180` | Omniflo-Boegen ueber DCL-Dialog einfuegen | | Weichen | `OMNI_W90_*`, `OMNI_W45_*`, `OMNI_WP_*`, `OMNI_WK_*`, `OMNI_WKomb_*` | Omniflo-Weichen (Einfach/Doppel/Dreiwege, Delta, Stern) | | Geraden | `OMNI_AP60`, `OMNI_AP110`, `OMNI_APG110` | Omniflo-Geradenstuecke | | Ladeschleifen | `OMNI_LADESCHLEIFE_AP110` | Ladeschleife aus `omniflo_ladeschleifen.json` ueber DCL-Dialog (`dcl/omniflo_ladeschleifen.dcl`) einfuegen; Daten per `ssg-json-laden` (Beschreibungen enthalten Kommas). Blockdatei `.dwg` unter `DXFM_OMNIFLO` (`omni:insert-dxf` faellt auf `.dwg` zurueck, wenn keine `.dxf` existiert). Per `OMNI_EDIT` editierbar | | TEF Elemente | `OMNI_TEF_UmlenkR/L`, `OMNI_TEF_AntriebL/R`, `OMNI_TEF_Gerade` | Feste TEF-Bauteile ohne Sivas-Auswahl, direkt per `omni:insert-dxf` an gewaehltem Punkt eingefuegt (Dateien unter `DXFM_OMNIFLO`) | | Anbaugruppen | `OMNI_STOPPER`, `OMNI_SCANNER`, `OMNI_BUERSTENBREMSE` | Implementiert | | Verbinder | `OMNI_TV_*` | Verbinder-DWG aus `data/omniflo/Verbinder` (dimensionsunabhaengig, `omni:insert-verbinder` -> `omni:insert-datei`, Punkt + Drehung interaktiv). Zuordnung Befehl -> DWG nach Dateiname, z.B. `OMNI_TV_W_AP60` -> `TV_W-AP60_oben`, `OMNI_TV_ENDPLATTE` -> `VB_ENDP_AP`, `OMNI_TV_W_AP60_KPL` -> `TV_W-AP60_kpl`, `OMNI_TV_ANSCHLAG` -> `VB_ENDP_AP_Spulenzug`, `OMNI_TV_SW_I_AP110` -> `TV_SW_I_SW-AP110`, `OMNI_TV_MUFFE` -> `VB_Muffe`. Alle 18 Eintraege aktiv | | Bogenweichen ⚠️ | `OMNI_APBW_*` | **Dummy** (noch nicht implementiert, im Menue ausgegraut) | | Bearbeiten | `OMNI_EDIT` | Omniflo-Block selektieren und Attribute bearbeiten | Daneben zeichnet [`TEFInsert.lsp`](#tefinsertlsp--tef-geradenstuecke-entlang-einer-linie) (`TEF_FOERDERER_LINIE`) eine Kette von TEF-Geradenelementen (1000 mm) entlang einer frei gewaehlten Linie – nicht zu verwechseln mit den einzelnen `OMNI_TEF_*`-Befehlen oben. --- ### `SSG_LIB_Commands.lsp` – BTMT, Sensoren, Dummy-Befehle, Konfiguration und SSG_BLOCKEDIT Sammeldatei fuer Menue-Befehle: teils Platzhalter fuer noch nicht implementierte Eintraege, teils bereits **echte** Funktionalitaet (BTMT Be-/Entladung, Sensoren, Konfiguration, Doppelklick-Dispatcher). Alle noch offenen Dummy-Menueintraege sind in `menu/SSG_LIB.mnu` mit `~` ausgegraut (siehe Abschnitt ["Status: noch offene Menueintraege"](#status-noch-offene-menueintraege-im-menue-ausgegraut)). **Sensoren einfuegen** (real, ueber `ils-insert-sensor` → wiederholtes `_.INSERT` mit interaktivem Einfuegepunkt/Rotation, Blockdatei via `ssg-ils-block-datei`): | Befehl | Block | Beschreibung | | --- | --- | --- | | `ILS_SCANNER_INSERT` | `Scanner` | Scanner wiederholt einfuegen (ENTER/ESC beendet) | | `ILS_SEPARATOR_INSERT` | `Separator_SP` | Separator wiederholt einfuegen (Menue „Separator einfuegen") | **BTMT Be-/Entladestation einfuegen** (real, ueber `ils-insert-station`): | Befehl | ROH-Block | Datei (3D) | Beschreibung | | --- | --- | --- | --- | | `ILS_BTMT_Beladung` | `BTMT-Beladung` | `BTMT-Beladung_3D.dwg` | Beladestation wiederholt einfuegen (ENTER/ESC beendet) | | `ILS_BTMT_Entladung` | `SC_Entladung` | `SC_Entladung_3D.dwg` | Entladestation wiederholt einfuegen (ENTER/ESC beendet) | Ablauf je Einfuegung: Einfuegepunkt abfragen → `_.INSERT` mit interaktiver Drehung (`pause`) → Layer aus dem `LAYER`-Attribut bzw. der eigenen Ebene der Blockdefinition setzen (`ils-block-layer-setzen`, gleiche Konvention wie `omni:insert-dxf`/`tefl-insert-element`) → `ssg-id-generate`, **falls** der Block ein `ID`-Attribut fuehrt. Die Blockdefinition kommt aus `ssg-ils-block-laden` (aktuelle Dimension `*ssg-ils-dim*` → `DXFM_DIM` → `3D`, DWG wird nur einmal pro Zeichnung gelesen); eingefuegt wird der zurueckgegebene dim-suffigierte Blockname. Fehlt die Datei der gewuenschten Dimension, greift der 3D-Fallback mit Warnung – aktuell fuer `BTMT-Beladung_2D.dwg`; sobald diese Datei in `data/ils/` liegt, wird sie im 2D-Modus ohne Codeaenderung verwendet (`SC_Entladung_2D.dwg` existiert bereits). Beide Stationen werden ueber `pattern_btmt_beladung`/`pattern_btmt_entladung` (`cfg/export.cfg`) von `ssg-id-collect-blocks` (`ssg_id.lsp`) UND von `csv:collect-export-blocks` (`export.lsp`) erfasst und erscheinen im Sivas-/ CSV-Export als eigene TeileArt-Zeilen ("ILS 2.0 BTMT Beladung"/"ILS 2.0 BTMT Entladung" bzw. "BT - Beladung ..."/"Abwurf fuer Klammerbuegel ..."); im Sivas-Export zusaetzlich in den Summenzaehlern "Anzahl aller BTBeladung"/ "Anzahl aller Abwurfstationen" der ILS-Automation-Zeile. **Eingang-/Ausgang-Symbol:** `ILS_EINGANG_INSERT`/`ILS_AUSGANG_INSERT` nutzen dieselbe `ils-insert-station`-Routine wie BTMT Be-/Entladung (frei platzierbar, Blöcke `Eingang_.dwg`/`Ausgang_.dwg` in `data/ils`). Registrierte Dummy-Befehle: | Gruppe | Befehle | | --- | --- | | ILS Zusatzmodule | `ILS_ETF_Etagenfoerderer`| | ILS Kurven angetrieben Aussen | `ILS_K90LA`, `ILS_K90RA`, `ILS_K60LA`, `ILS_K60RA`, `ILS_K30LA`, `ILS_K30RA` | | ILS Kurven angetrieben Innen | `ILS_K90LI`, `ILS_K90RI`, `ILS_K60LI`, `ILS_K60RI`, `ILS_K30LI`, `ILS_K30RI` | | ILS Gefaellestrecken-Kurven | `ILS_Gefaellestrecke` | > `FOERDERANLAGE` ist nicht hier definiert – dieser Befehl ist in `VarioFoerderer.lsp` implementiert. **Konfigurationsbefehle** (Menue → Konfiguration): | Befehl | Beschreibung | | --- | --- | | `SSG_DIM_SWITCH` | Schaltet zwischen 2D/3D-Bloecken um (setzt `DXFM_DIM`, `DXFM_BLOCKS`, `DXFM_OMNIFLO`) | | `SSG_SPRACHE` | Oeffnet Dialog `sprache.dcl`, setzt `*ssg-lang*` und persistiert via `DXFM_LANG` (siehe [`ssg_lang.lsp`](#ssg_langlsp--sprachumschaltung-deutschenglisch)) | **SSG_BLOCKEDIT** – Doppelklick/Kontextmenue-Dispatcher: Erkennt den Blocktyp des selektierten INSERT-Blocks und ruft den passenden Bearbeitungsbefehl auf (Reihenfolge wichtig: `GR_*` **vor** dem Omniflo-Zweig, da Geruest-Bloecke ebenfalls ein TEILEART-Attribut tragen): | Blockmuster | Aktion | | --- | --- | | `KREISEL_*` | `KreiselEdit` | | `ECKRAD_*` | `EckradEdit` | | `GF_*` | `GEFAELLESTRECKE_EDIT` | | `VF_*` | `VARIOFOERDERER_EDIT` | | `GR_*` | Geruest – `BEDIT` (kein eigener Edit-Pfad; siehe `GeruestInsert.lsp`) | | `TRO_*` | `TRO_EDIT` | | Omniflo (TEILEART-Attribut, AP110*, numerisch) | `OMNI_EDIT` | | Sonstige | `BEDIT` (Block-Editor) | --- ### `GeruestInsert.lsp` – Geruest-Baugruppen Rohr-Geruest-Baugruppen (Saeulen, Rohrabhaenger, Wandbefestigung, Rohr-Geruest-Traeger) aus [`data/json/geruest.json`](../data/json/geruest.json) bzw. [`data/json/geruest_traeger.json`](../data/json/geruest_traeger.json) konfigurieren und einfuegen. Die Dialogdefinition wird **zur Laufzeit erzeugt** (`geruest-dcl-schreiben`, temporaere DCL-Datei) – es gibt keine feste `dcl/geruest.dcl`, weil je Anbauteil ein eigenes Mengenfeld gebraucht wird. Ausfuehrliche Beschreibung (Konfigurationen, Attribut-Schema, Querstreben/Muffe, Traeger-Beschriftung): siehe `../CLAUDE.md` Abschnitt "AutoLISP-Module". | Befehl | Beschreibung | | --- | --- | | `c:GERUEST` | Dialog ohne Vorgabe-Bereich (Dropdown Rohrabhaenger/Saeulen/Wandbefestigung) | | `c:GERUEST_SAEULEN` / `c:GERUEST_ROHRABHAENGER` / `c:GERUEST_WANDBEFESTIGUNG` | Dialog mit vorgewaehltem Bereich (derselbe Dialog wie `GERUEST`) | | `c:GERUEST_TRAEGER` | Eigener fester Dialog (`dcl/geruest_traeger.dcl`): Rohr-Geruest-Traeger mit Linien-Logik (Start-/Endpunkt oder Laenge, Laengenabzuege, Querstreben/Muffe-Verteilung) | | `c:GERUEST_TRAEGER_BESCHRIFTEN` | Alle Traeger der Zeichnung neu beschriften (eigenes TEXT-Objekt, verknuepft per XDATA `SSG_GR_BESCHR`) | Block-Praefix `GR__` – **nicht kosmetisch**: `SSG_BLOCKEDIT` muss `GR_*` vor dem Omniflo-Zweig abfangen, da Geruest-Bloecke ebenfalls ein TEILEART-Attribut tragen. Export-Pattern `pattern_geruest = GR_*` (`cfg/export.cfg`). --- ### `Tro_Edit.lsp` – TRO-Marker TRO-Marker (Block `TRO_SYM_`) fuer `sps_skel`/`lib/tro_annotate.py`. | Befehl | Beschreibung | | --- | --- | | `c:TRO_INSERT` | Bestehende Blockdefinition an gewaehltem Punkt einfuegen, setzt ID/TYPE; bricht ab, wenn der Typ noch keine Blockdefinition in der Zeichnung hat (entsteht nur ueber `tro_annotate.py`) | | `c:TRO_EDIT` | ID/TYPE eines bestehenden Markers per Dialog aendern | Typliste kommt aus [`tro_types.lsp`](../Lisp/tro_types.lsp) (von `tro_annotate.py --emit-lisp` nach `DXFM_LISP` geschrieben). `SSG_BLOCKEDIT` leitet Blocknamen `TRO_*` zu `TRO_EDIT`. --- ### `Connection_Edit.lsp` – Verbindungen (SPS) Verbindungslinien zwischen zwei Punkten/Bausteinen fuer SPS-Dokumentation. | Befehl | Beschreibung | | --- | --- | | `c:CONNECTION_INSERT` | Verbindung zwischen zwei Punkten einfuegen (Laenge/Winkel/Mitte werden berechnet) | | `c:CONNECTION_EDIT` | Bestehende Verbindung bearbeiten | --- ### `DrawPlanquadrate.lsp` – Planquadrat-Raster 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 und Ursprung kommen aus der **Bounding-Box** derselben Blockmenge wie `EXPORTCSV` (`csv:collect-export-blocks`), nicht aus dem reinen Einfuegepunkt oder den Zeichnungs-Extents. Lazy geladen ueber `(ssg-ensure "DrawPlanquadrate")`, nicht Teil der Core-Ladereihenfolge. | Befehl | Beschreibung | | --- | --- | | `c:DRAW_PLANQUADRATE` | Raster neu aufbauen (entfernt zuerst einen evtl. vorhandenen Block) | | `c:DELETE_PLANQUADRATE` | Raster-Block entfernen | | `c:PLANQUADRATE_TOGGLE` | Dispatcht auf Zeichnen bzw. Loeschen, je nachdem ob der Block existiert | Das fertige Raster (Linien + Zaehl-Texte) wird zu einem einzigen Block `ZZ_PLANQUADRATE` bei (0,0,0) zusammengefasst; Layer aus `cfg/layer.cfg`.