Files
dxfmakros/Lisp/README.md
T
m.stangl 68ad56ec6c [DOC] Lisp/README.md auf aktuellen Code-Stand gebracht
Kern-Ladereihenfolge ergaenzt (ssg_id, ssg_ks_insert, ssg_input), Dummy-Status-Tabelle korrigiert (OMNI_TEF_* ist implementiert, nur OMNI_KettenFoerderer/OMNI_TV_*/OMNI_APBW_* und ein paar ILS-Kurven-Platzhalter sind noch Dummy). Fehlende Module ergaenzt: ssg_id.lsp, ssg_ks_insert.lsp, TEFInsert.lsp, GeruestInsert.lsp, Tro_Edit.lsp, Connection_Edit.lsp, DrawPlanquadrate.lsp. VarioFoerderer-Abschnitt an die Submodul-Aufsplittung (vf_core/vf_standard/vf_etage/vf_linienzug/vf_konstanten/vf_spec) und den Wizard/Journal/Headless-Mechanismus angepasst. SSG_BLOCKEDIT-Dispatchtabelle um GF_*/VF_*/GR_*/TRO_* ergaenzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-09 10:26:10 +02:00

865 lines
48 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 ▸ Anbaugruppen | `OMNI_KettenFoerderer` | `OmniModulInsert.lsp` |
| Omniflo ▸ Verbinder | alle `OMNI_TV_*` (18) | `OmniModulInsert.lsp` |
| 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`), nur `OMNI_KettenFoerderer` ist
weiterhin Dummy.
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_<Dimension>]` → `[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/<blockname>.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/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<N>.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) |
---
### `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>_<links|rechts>` 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" "<richtiger Pfad>")` 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-<modul>`-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-<modul>`-Funktion eine
`tu-eq`/`tu-eqf`/`tu-true`/`tu-nil`-Zeile hinzufügen. Für ein neues Modul eine
eigene `tu-tests-<modul>`-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 an Punkt einfuegen (mit Dialog fuer Parameter) |
| `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 |
---
### `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 `<Sivasnr>.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_KettenFoerderer` | **Dummy** (noch nicht implementiert, im Menue ausgegraut); `OMNI_STOPPER`/`OMNI_SCANNER`/`OMNI_BUERSTENBREMSE` sind implementiert |
| Transferwagen ⚠️ | `OMNI_TV_*` | **Dummy** (noch nicht implementiert, im Menue ausgegraut) |
| 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_<DIM>.dwg`/`Ausgang_<DIM>.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_*` | `BEDIT` (Block-Editor) |
| `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_<id>_<timestamp>` – **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_<Typ>`) 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`.