diff --git a/doc/json-input-lisp.md b/doc/json-input-lisp.md new file mode 100644 index 0000000..1c90257 --- /dev/null +++ b/doc/json-input-lisp.md @@ -0,0 +1,437 @@ +# JSON-Eingabe fuer den LISP-Bau + +Welche Objekte sich aus einer JSON-Datei an einer gegebenen x/y/z-Koordinate +erzeugen lassen, in welchem Format sie beschrieben werden und welche +LISP-Routine sie baut. Grundlage sind die Testdaten in `tests/testdata/*.json` +und die Treiber in `tests/test_*.lsp`, die sie lesen. + +Die zugehoerigen LISP-Routinen mit Signaturen stehen in +[lisp_methods.md](lisp_methods.md). Diese Seite ersetzt das fruehere +`tests/testdata/object_data.md` (geloescht in Commit `e147b89`, auf das Code- +Kommentare teilweise noch verweisen). + +--- + +## 0. Uebersicht + +| Objekt | Erkennungsmerkmal im JSON | Bau-Routine (Produktion) | Koordinaten-Eingang | Beispiel-Datei | +|---|---|---|---|---| +| [Kreisel (Insert)](#1-kreisel) | `"function": "insert"`, KEIN `block` | `kreisel-insert-script` | `x`,`y`,`z` = Einfuegepunkt | `kreisel_tests.json`, `mubea.json`, `hundm05.json`, `dematic_arvato.json` | +| [Kreisel (Connect)](#12-connect-start--und-endpunkt) | `"function": "connect"` | `kreisel-connect-script` | `start_*`, `end_*` | (nur `test_kreisel.lsp`, z.Z. kein Datensatz) | +| [Einzelblock](#2-einzelbloecke-separator-scanner-btmt-eingangausgang-vario-bausteine) (Separator, Scanner, BTMT, Eingang/Ausgang, Vario-Bausteine) | `"function": "insert"` + `"block"` | direktes `_.INSERT` nach `ssg-ils-block-laden` | `x`,`y`,`z` + `rotation` | `mubea.json`, `hundm05.json`, `dematic_arvato.json` | +| [VarioFoerderer Standard](#3-variofoerderer-standard-und-etage) | `test_id` beginnt mit `VF_`, `typ` fehlt oder `"standard"` | `berechne-alle-winkel` + `variofoerderer-einfuegen` + `vf-block-erstellen` | `x`,`y`,`z` = KS_EIN des AS-Elements | `mubea.json`, `foerderer_tests.json` | +| [VarioFoerderer Etage](#3-variofoerderer-standard-und-etage) | `test_id` `VF_*` + `"typ": "etage"` | `berechne-etage` + `etage-foerderanlage-einfuegen` + `vf-block-erstellen` | wie Standard | `mubea.json` | +| [Gefaellestrecke Modus 1](#4-gefaellestrecke) | `test_id` `GF_*` bzw. `"modus": 1` | `gefaellestrecke-einfuegen` (bzw. `gf-rebuild`) | `start_mm` = KS_EIN des AS-Elements | `mubea.json`, `gefaellestrecke_tests.json` | +| [Gefaellestrecke Modus 2](#42-modus-2---richtung-aus-zwei-punkten) | `"modus": 2` | wie Modus 1, `deltaL`/`hz` aus zwei Punkten | `start_mm` + `linie_start_mm`/`linie_ende_mm` | `gefaellestrecke_tests.json` | +| [Gefaellestrecke Modus 3](#43-modus-3---linienzug) | `"modus": 3` | nur ueber `test_gefaellestrecke.lsp` | `start_mm`, `ende_mm`, `bogen` | `gefaellestrecke_tests.json` | +| [VF-Linienzug-Kette (Spec)](#5-vf-linienzug-kette-spec) | Kopf mit `spec_id`, danach `glied`/`sub` | `vsp-bau-aus-spec` (Replay durch `vf-linienzug-modus`) | `start_punkt` = Kettenanfang | `hundm05.json` | +| [VF-Linienzug (Journal)](#6-vf-linienzug-eingabe-journal) | Kopf mit `test_id`, danach `typ` = `point_abs`/`real`/`string`/... | `vfl-journal-replay-start` + `vf-linienzug-modus` | erstes `point_abs` | `linienzug_tests.json`, `hm_recformat.json` | +| [Omniflo Bogen/Weiche](#71-bogen-und-weiche) | `"type": "bogen"` / `"weiche"` + `sivasnr` | `_.INSERT` der `.dxf` (Kern `omni:insert-dxf` fragt den Punkt interaktiv) | `x`,`y`, Z = `hoehe` | `omniflo_tests.json`, `omniflo_strecke_tests.json` | +| [Omniflo Gerade](#72-aluprofil-gerade) | `"type": "gerade"` + `profil` | `omni:make-gerade` | `x`,`y` -> `x_ende`,`y_ende`, Z = `hoehe` | `omniflo_strecke_tests.json` | +| [Waagrechte Foerderstrecke](#8-waagrechte-foerderstrecke-gerade---noch-ohne-bau-pfad) | `"typ": "gerade"` + `startpunkt` | **kein Bau-Pfad** (wird gemeldet und uebersprungen) | `startpunkt`, `endpunkt` | `dematic_arvato.json` | + +Nicht aus JSON baubar (es gibt keine Testdaten und keinen Treiber): Eckrad, +Geruest, TEF-Foerderer-Linie, TRO-Marker, Connections, Omniflo-Ladeschleife. +Fuer Eckrad und Geruest existiert ein skriptfaehiger Kern, siehe +[lisp_methods.md](lisp_methods.md). + +### Allgemeine Regeln + +- **Einheiten**: Laengen und Koordinaten in **mm**, Winkel in **Grad**. + Fahrtrichtung `hz`: 0 = +X (Ost), 90 = +Y (Nord), 180 = -X, 270 = -Y. +- **Z ist absolut**: `z` (bzw. Z in `start_mm`/`start_punkt`) ist die + Weltkoordinate des Einfuegepunkts, also die Einbauhoehe. +- **Dimension 2D/3D**: Gebaut wird in der aktuellen Dimension + (`ssg-ils-dim-aktuell`: `*ssg-ils-dim*` -> `DXFM_DIM`, Default 3D). + Das Feld `darstellung` in `dematic_arvato.json` ist reine Dokumentation und + wird nicht ausgewertet; `TEST_HUNDM05_2D`/`_3D` erzwingen die Dimension. +- **Flaches JSON**: Die meisten Dateien werden mit `ssg-load-json` gelesen + (zeilenweise, ein Objekt je `{`). Verschachtelte Objekte und mehrzeilige + Arrays darin funktionieren nicht - darum stehen z.B. die Glieder einer + Kette als eigene Objekte hinter ihrem Kopf-Objekt. Ausnahme: + `TEST_DEMATIC_ARVATO` liest mit dem rekursiven `ssg-json-laden`. +- **Reine Doku-Felder**: `comment`, `beschreibung`, `description`, `quelle`, + `row`, `seq`, `gruppe`, `assigned_to`, `hoehe_unbekannt`, `darstellung` + sowie alle `expect_*`/`erwartung_*`-Felder werden beim Bau nicht gelesen. + `expect_*` pruefen die Python-Tests gegen das Ergebnis. +- **Template-Felder** (nur `test_mubea.lsp`): `anzahl`, `delta_x`, `delta_y` + erzeugen aus einem Eintrag `anzahl` Kopien, jeweils um `delta_x`/`delta_y` + versetzt; `test_id` bekommt dann das Suffix `_01` ... `_nn`. Gilt fuer VF, + GF und Einzelbloecke. + +--- + +## 1. Kreisel + +Block `KREISEL_n`, gebaut von `KreiselInsert.lsp`. + +### 1.1 Insert (freier Punkt) + +```json +{ "id": "KR_Kreuz_Q1_0Grad", "function": "insert", + "x": 500, "y": 500, "z": 2500, + "abstand": 2000, "rotation": 0.0, "typ": "STANDARD" } +``` + +| Feld | Pflicht | Bedeutung | Default | +|---|---|---|---| +| `id` | ja (fuer den Treiber) | Kennung im Ergebnis; NICHT die Block-ID (die vergibt `ssg-id-generate`) | - | +| `function` | ja | `"insert"`; ohne `block` = Kreisel | - | +| `x`, `y` | ja | Einfuegepunkt. Liegt auf der Kreiselachse **einen Radius (400 mm) vor der Mitte des Antriebsrads (AN)**, also am aeussersten Punkt der AN-Seite | - | +| `z` | ja | Einbauhoehe; wird als `hoehe` uebergeben und in das Attribut `HOEHE` geschrieben | `kreisel.default_hoehe` (2000) | +| `abstand` | ja | Mittenabstand Antriebsrad - Spannrad | `kreisel.default_laenge` (2300) | +| `rotation` | nein | Drehung der Achse AN -> SP, frei (kein Raster) | 0.0 | +| `typ` | nein | `"STANDARD"` oder `"PIN"` -> Attribut `KREISELART` | `"STANDARD"` | + +Treiber: `kreisel:run-insert-test` (`test_kreisel.lsp`), `mubea:build-kreisel`, +`hundm05:bau-kreisel` (auch fuer `dematic_arvato.json`). + +### 1.2 Connect (Start- und Endpunkt) + +```json +{ "id": "K2", "function": "connect", + "start_x": 0, "start_y": 0, "start_z": 2000, + "end_x": 5000, "end_y": 0, "end_z": 2000, "typ": "STANDARD" } +``` + +`start_*` und `end_*` sind die aeussersten Punkte der AN- bzw. SP-Seite. +`abstand` (= Distanz - Kreiseldurchmesser 800) und `rotation` werden daraus +berechnet. Die Einbauhoehe kommt aus `start_z`. Nur `test_kreisel.lsp` kennt +`connect`; die anderen Treiber bauen ausschliesslich `insert`. + +--- + +## 2. Einzelbloecke (Separator, Scanner, BTMT, Eingang/Ausgang, Vario-Bausteine) + +Jeder Eintrag mit `"function": "insert"` UND `"block"` wird als einzelner +Block eingefuegt, ohne Wrapper-Block und ohne Verkettung. + +```json +{ "id": "HundM05_SCANNER_02", "function": "insert", "block": "Scanner", + "x": 4095.5, "y": 1164.1, "z": 2154, "rotation": 270 } +``` + +| Feld | Pflicht | Bedeutung | Default | +|---|---|---|---| +| `block` | ja | Blockname OHNE `_2D`/`_3D`-Suffix | - | +| `x`, `y`, `z` | ja | Einfuegepunkt = **Basispunkt der Block-DWG** (nicht KS_EIN) | - | +| `rotation` | nein | Drehung um Z | 0 | +| `id` / `test_id` | nein | Kennung im Ergebnis | `SEPARATOR_` bzw. Blockname | + +Ablauf: `ssg-ils-block-laden` loest den Namen auf `data/ils/_.dwg` +auf (3D-Fallback), danach `_.INSERT eff pt "" "" rotation` und +`ssg-id-generate`. **Fehlt die DWG, wird `Separator_SP` eingefuegt.** +Treiber: `mubea:build-separator`, `hundm05:bau-sensor` (auch Dematic). +Das Layer-Attribut `LAYER` wird dabei nicht angewendet; der Block landet auf +dem aktuellen Layer. + +### In den Testdaten verwendete Blocknamen + +| `block` | DWG in `data/ils` | Verwendet in | Hinweis | +|---|---|---|---| +| `Scanner` | `Scanner_2D/_3D.dwg` | mubea, hundm05, dematic_arvato | | +| `Separator_SP` | `Separator_SP_2D/_3D.dwg` | (Fallback) | Block von `ILS_SEPARATOR_INSERT` | +| `S-LP` | **fehlt** | mubea, hundm05, dematic_arvato | wird zu `Separator_SP` | +| `S-SP` | **fehlt** | dematic_arvato | wird zu `Separator_SP` | +| `BTMT-Beladung` | `BTMT-Beladung_2D/_3D.dwg` | mubea, hundm05 | wie `ILS_BTMT_Beladung` | +| `SC_Entladung` | `SC_Entladung_2D/_3D.dwg` | mubea, hundm05 | wie `ILS_BTMT_Entladung` | +| `Eingang` / `Ausgang` | `Eingang_*/Ausgang_*.dwg` | mubea | Symbole wie `ILS_EINGANG_INSERT`/`ILS_AUSGANG_INSERT` | +| `AS_Element_90_links` / `_rechts` | vorhanden | dematic_arvato | loses AS-Element (sonst Teil eines VF/GF) | +| `ES_Element_90_rechts`, `ES_Element_30_rechts` | vorhanden | dematic_arvato | loses ES-Element | +| `Vario_Motorstation_500mm_rechts` | vorhanden | dematic_arvato | lose Motorstation | +| `Vario_Umlenkstation_500mm_rechts` | vorhanden | dematic_arvato | lose Umlenkstation | +| `Vario_Kurve_rechts_30_TEF_aussen` | vorhanden | dematic_arvato | lose Vario-Kurve | + +Grundsaetzlich ist **jeder** Blockname aus `data/ils` erlaubt (es gibt keine +Whitelist). Die losen AS/ES-/Vario-Bausteine bilden aber keine Kette und keinen +`VF_n`/`GF_n`-Block - fuer eine zusammenhaengende Strecke Abschnitt 3-5 nutzen. + +--- + +## 3. VarioFoerderer Standard und Etage + +Block `VF_n`. Eingang sind die **Gesamtmasse** der Anlage; Winkel und +Teillaengen berechnet der Solver. + +```json +{ "test_id": "VF_Schnitt_C", "richtung": "Auf", + "deltaL": 9110, "deltaH": 474, "winkel": 21, + "gf_verteilung": "manuell", "L_GF1": 2298, "seite": "links", + "hz": 0.0, "x": 400, "y": 14120, "z": 1554 } +``` + +| Feld | Pflicht | Bedeutung | Default | +|---|---|---|---| +| `test_id` | ja | muss mit `VF_` beginnen (steuert den Dispatcher in `test_mubea.lsp`) | - | +| `typ` | nein | `"standard"` (AS/ES 90 Grad) oder `"etage"` (AS/ES 30 Grad, uebereinander) | `"standard"` | +| `richtung` | ja | `"Auf"` oder `"Ab"` | - | +| `deltaL` | ja | horizontale Gesamtlaenge inkl. AS/ES, Boegen, GF-Vor-/Nachlauf | - | +| `deltaH` | ja | Hoehendifferenz (Betrag; Vorzeichen kommt aus `richtung`) | - | +| `winkel` | nein | Bogenwinkel erzwingen, nur aus der Kandidatenliste (`vario.bogen_winkel`: 3, 6, 9, 12, 15, 18, 21, 27, 33, 39, 45, 51). Ist er nicht loesbar, wird NICHT gebaut | kleinster gueltiger Winkel | +| `L_GF1` | nein (mubea) | Vorlauf-Laenge (GF vor dem Foerderer); Nachlauf = Rest von `L_GF` | - | +| `gf_verteilung` | nein (foerderer) | `"gleichmaessig"`, `"vorne"`, `"hinten"`; in `mubea.json` nur Doku (dort zaehlt `L_GF1`) | `"gleichmaessig"` | +| `seite` | nein | `"links"`/`"rechts"` (Seite der AS/ES-Elemente) | `"links"` | +| `hz` | nein | Fahrtrichtung | 0.0 | +| `x`, `y`, `z` | ja (mubea) | Startpunkt = **KS_EIN des AS-Elements**; `z` = Einbauhoehe am Anfang | - | + +Hinweise: +- `foerderer_tests.json` hat statt `x/y/z` einen Kopf-Eintrag + `{"y_abstand": 600}`: alle Foerderer starten bei x = 0, z = 0 und werden um + `y_abstand` versetzt. Dieses Format kennt nur `test_foerderer.lsp`. +- `"Ab"` mit `deltaH` < 1 ist geometrisch unmoeglich und wird abgewiesen. +- Der Treiber schreibt nach dem Bau `vf-edit-xdata-schreiben` (Typ + `hz`), + damit der Block per Doppelklick editierbar ist. + +--- + +## 4. Gefaellestrecke + +Block `GF_n`: AS-Element -> Staustrecke (skaliert) -> Separator -> ES-Element. +Eingang ist die **horizontale Laenge**, nicht die Hoehendifferenz - die ergibt +sich aus Laenge und Neigung. + +### 4.1 Modus 1 - Werteingabe + +```json +{ "test_id": "GF_Mubea", "modus": 1, + "deltaL_mm": 9110, "hz_grad": 180.0, "winkel_grad": 2.27, + "start_mm": [9510, 12300, 2028], + "as_seite": "links", "es_seite": "links", + "as_winkel": "90", "es_winkel": "90" } +``` + +| Feld | Pflicht | Bedeutung | Default | +|---|---|---|---| +| `test_id` | ja | in `mubea.json` muss sie mit `GF_` beginnen | - | +| `modus` | nein | `1` | - | +| `deltaL_mm` | ja | horizontale Gesamtlaenge AS + Staustrecke + Separator + ES | - | +| `winkel_grad` | nein | Neigung der Staustrecke | 3.0 | +| `hz_grad` | nein | Fahrtrichtung | 0.0 | +| `start_mm` | ja | `[x, y, z]` = **KS_EIN des AS-Elements**; Z = Einbauhoehe oben | - | +| `as_seite`, `es_seite` | nein | `"links"`/`"rechts"` | `"links"` | +| `as_winkel`, `es_winkel` | nein | `"90"` oder `"30"` (als STRING) | `"90"` | + +Die Staustrecke ergibt sich aus +`L_stau = (deltaL - aus-dx - ein-dx - 300 * cos(w)) / cos(w)` +(300 = `gefaelle.separator_breite`). Eine Ziel-Hoehe gibt es nicht. + +In `gefaellestrecke_tests.json` stehen `deltaL_mm`, `winkel_grad` und +`z_start_mm` in einem gemeinsamen Kopf-Eintrag ohne `test_id` und gelten fuer +alle Faelle der Datei. + +### 4.2 Modus 2 - Richtung aus zwei Punkten + +Wie Modus 1, aber `deltaL` und `hz` werden aus `linie_start_mm` -> +`linie_ende_mm` berechnet (nur X/Y). `start_mm` bleibt der Einfuegepunkt und +ist unabhaengig von der Linie. + +```json +{ "test_id": "M2_hz000", "modus": 2, "start_mm": [0, -35000, 5000], + "linie_start_mm": [0, -35000, 0], "linie_ende_mm": [10000, -35000, 0], + "as_seite": "links", "es_seite": "links", "as_winkel": "90", "es_winkel": "90" } +``` + +### 4.3 Modus 3 - Linienzug + +In der Produktion nicht skriptbar (`gf-linienzug-modus` waehlt vorgezeichnete +LINE/ARC-Objekte per Maus). `test_gefaellestrecke.lsp` zeichnet die Leitlinie +selbst und baut sie nicht-interaktiv nach. Zwei Formen: + +```json +{ "test_id": "M3_Bogen", "modus": 3, + "start_mm": [30000, -50000, 2000], "ende_mm": [38500, -43500, 1800], + "bogen": {"winkel_grad": 90, "radius_mm": 500, "seite": "links"}, + "as_seite": "links", "es_seite": "links", "as_winkel": "90", "es_winkel": "90" } +{ "test_id": "M3_hz270", "modus": 3, "richtung": "Sued", + "start_mm": [0, -50000, 5000], + "deltaH_quelle": "M2_hz000", "deltaL_quelle": "M2_hz000", ... } +``` + +`deltaH_quelle`/`deltaL_quelle` uebernehmen die Masse eines frueher gebauten +Falls. Dieses Format ist an den Treiber gebunden. Fuer neue Anlagen besser +eine VF-Linienzug-Spec (Abschnitt 5) mit `Linie-GF`/`GF-Bogen` verwenden. + +--- + +## 5. VF-Linienzug-Kette (Spec) + +Beliebige Ketten aus GF-Linien, GF-Boegen, Vario-Kurven und VF-Einheiten, +**ohne Dialog** gebaut (`VF_SPEC_BAU`, `TEST_HUNDM05`). Eine Kette = ein +Kopf-Objekt, danach ihre Glieder (`glied`) und deren Unterknoten (`sub`) in +Baureihenfolge. Ergebnis ist ein `VF_n`-Block mit vollem `SSG_VF_EDIT`-Journal +(per Doppelklick editierbar). + +```json +{ "spec_id": "VF_spec_LZ_01", "modus": 1, + "start_punkt": [4912.2, 1230.9, 2154.0], "start_hoehe": 2154.0, + "as": 1, "as_winkel": "90", "as_seite": "links" } +{ "glied": "Linie-GF", "nr": 1, "dl": 2249.3, "hz": 90.0, + "gefaelle": "winkel", "winkel": 3.0, "ende": "nein" } +{ "glied": "Horizontal-VF", "nr": 2, "dl": 3256.9, "gf_verteilung": "einlauf", + "vf_sep_vor": 0, "vf_sep_nach": 0, "vf_ende": "nein", "vf_separator": 0 } +{ "sub": "vario-kurve", "winkel": 90, "seite": "rechts", "variante": "innen" } +{ "sub": "motorstation" } +{ "glied": "ES", "winkel": "90", "seite": "links" } +``` + +**Kopf** + +| Feld | Bedeutung | +|---|---| +| `spec_id` | Kennung der Kette | +| `modus` | `1` (nur Modus 1 hat einen Uebersetzer) | +| `start_punkt` | `[x, y, z]` Kettenanfang (KS_EIN des AS-Elements bzw. erstes Glied) | +| `start_hoehe` | Einbauhoehe am Start | +| `as` | `1` = AS-Element setzen, `0` = nicht | +| `as_winkel` / `as_seite` | `"90"`/`"30"` bzw. `"links"`/`"rechts"` | + +**Glieder** (`glied`) + +| `glied` | wichtigste Felder | +|---|---| +| `Linie-GF` | `dl`, `hz` (nur beim ersten Glied der Kette), `laenge_modus` (`endpunkt`/`hoehe`/`steigend`), `ende` (`ja-mit-es`/`ja-ohne-es`/`nein`), `sep_vor`, `sep_nach`, `gefaelle` (`hoehe`/`winkel`) + `hoehe` bzw. `winkel` | +| `GF-Bogen` | `winkel` (30/60/90), `seite` | +| `Vario-Kurve` | als eigenes Glied (Kettenanfang) vom Uebersetzer noch NICHT unterstuetzt -> Spec-Fehler; Vario-Kurven als `sub` einer VF-Einheit angeben | +| `Horizontal-VF` | `dl`, `eingang_hoehe`, `gf_verteilung` (`haelfte`/`einlauf`), `vf_sep_vor`, `vf_sep_nach`, `vf_ende` (`ja-mit-es`/`nein`/`automatisch`/`zielpunkt-ohne-es`), `vf_separator` (nur bei `vf_ende` = `nein`) + `sub`-Knoten | +| `Linie-VF` | `dl`, `hoehe`, optional `winkel_idx` + `sub`-Knoten | +| `Linie` | `dl`, `hoehe`, `segment_typ` (`GF`/`VF`), optional `winkel_idx` | +| `ES` | `winkel` (`"90"`/`"30"`), `seite` | + +**Unterknoten einer VF-Einheit** (`sub`): `horizontal` (`sep_vor`, `sep_nach`), +`vario-kurve` (`winkel`, `seite`, `variante` `aussen`/`innen`), `auf-ab` +(`richtung` `auf`/`ab`, `hoehe`), `motorstation`, `kettenende` (`es`, +`es_winkel`, `es_seite`). + +Ja/Nein-Felder (`as`, `sep_vor`, `vf_sep_vor`, `es`, ...) als `1`/`0` oder +`"ja"`/`"nein"`; mehrwertige Felder (`ende`, `vf_ende`) nur mit den genannten +Woertern. Vollstaendige Regeln +(Menue-Codes je Frame, `hz` nur einmal, Verhalten bei `winkel_idx`): +[variofoerderer.md, Abschnitt 8b](variofoerderer.md#spec-pfad-kette-aus-eingabedaten-vf_spec_bau). +Die Spec wird nicht von Hand geschrieben, sondern mit +`python lib/vf_spec_export.py ` aus einer +aufgezeichneten Kette erzeugt. Fehlerhafte Specs werden komplett abgewiesen +(`vsp-pruefen` sammelt alle Fehler, gebaut wird dann nichts). + +**Andere Objekte in derselben Datei**: Neben den Ketten darf `hundm05.json` +Kreisel (Abschnitt 1) und Einzelbloecke (Abschnitt 2) enthalten. Erkennbar +sind sie an `function`; `vsp-gruppieren` ueberspringt sie, und +`hundm05:bau-zusatzobjekte` baut sie nach den Ketten. `vf_spec_export.py` +haengt sie beim Neuerzeugen unveraendert ans Dateiende. + +--- + +## 6. VF-Linienzug (Eingabe-Journal) + +Das Rohformat hinter der Spec: die Antworten auf die Fragen des interaktiven +Linienzug-Ablaufs, in Reihenfolge. Nur fuer Regressionstests gedacht - fuer +neue Anlagen die Spec (Abschnitt 5) verwenden. + +```json +{ "test_id": "VF_hundm05_LZ_01", "anlagetyp": "linienzug", "modus": 1, ... } +{ "typ": "point_abs", "wert": [4912.2, 1230.9, 2154.0], "kommentar": "Startpunkt" } +{ "typ": "real", "wert": 2154.0, "kommentar": "Hoehe (Z)" } +{ "typ": "string", "wert": "1", "kommentar": "AS-Element? Ja" } +{ "typ": "step", "wert": "Linie-GF", "kommentar": "Glied-Marker" } +{ "typ": "point_rel", "dL": 2249.3, "hz": 90.0, "kommentar": "Endpunkt" } +{ "typ": "int", "wert": 90, "kommentar": "Vario-Kurve Winkel" } +``` + +`typ`: `point_abs` (absoluter Punkt), `point_rel` (Abstand `dL` + Richtung +`hz`, `hz` nur beim ersten Segment), `real`, `int`, `string` (Menue-Code als +String), `step` (Glied-Marker, beim Replay uebersprungen). Kopf-Feld heisst +`anlagetyp`, nicht `typ`, damit Kopf und Eingabe unterscheidbar bleiben. + +| Datei | Treiber | +|---|---| +| `linienzug_tests.json`, `linienzug_tests_stufe2.json` | `TEST_LINIENZUG`, `TEST_LINIENZUG_STUFE2` | +| `hm_recformat.json` (5 echte HundM-Ketten, erzeugt mit `lib/vf_journal_export.py`) | `TEST_HM_RECFORMAT` (in `alltests.json` abgeschaltet) | + +--- + +## 7. Omniflo + +### 7.1 Bogen und Weiche + +```json +{ "id": "821104025", "type": "bogen", "sivasnr": "821104025", + "x": 219.4, "y": 223.06, "hoehe": 2000, "drehung": 0.0 } +``` + +| Feld | Pflicht | Bedeutung | Default | +|---|---|---|---| +| `type` | ja | `"bogen"` oder `"weiche"` | - | +| `sivasnr` | ja | Sivas-Nummer = Dateiname `%DXFM_OMNIFLO%/.dxf` | - | +| `x`, `y` | ja | Einfuegepunkt (`$INSBASE` der DXF, gesetzt mit `set_einfuegepkt.py`) | - | +| `hoehe` | nein | Z des Einfuegepunkts UND Attribut `HOEHE` | 2000 | +| `drehung` | nein | Drehung + Attribut `DREHUNG` | 0 | + +Es gibt kein `z`: die Hoehe ist zugleich die Z-Koordinate. Gueltige +Sivas-Nummern: `data/json/omniflo_boegen.json`, `omniflo_weichen.json`. +Treiber: `omni:test-insert` (`test_omniflo.lsp`), `omnistr:insert-bogen` +(`test_omniflo_strecke.lsp`). + +### 7.2 Aluprofil-Gerade + +```json +{ "seq": 1, "type": "gerade", "profil": "AP110", "laenge": 1000.0, + "x": 2000.0, "y": 0.0, "x_ende": 3000.0, "y_ende": 0.0, + "drehung": 0.0, "hoehe": 2000, "id": "0003" } +``` + +`profil` = `"AP60"`, `"AP110"` oder `"APG110"`. Die Gerade laeuft von +(`x`,`y`) nach (`x_ende`,`y_ende`) auf Z = `hoehe`; Laenge und Drehung folgen +aus den Punkten (`laenge` ist nur Doku). Gebaut mit `omni:make-gerade`. + +--- + +## 8. Waagrechte Foerderstrecke (`gerade`) - noch ohne Bau-Pfad + +```json +{ "test_id": "GERADE_Arvato_01", "typ": "gerade", "laenge": 8313.39, "hz": 90.0, + "startpunkt": [2334.97, 177818.67, 0.0], "endpunkt": [2334.96, 186132.06, 0.0] } +``` + +Ein Antriebs-/Umlenkstation-Paar ohne Hoehendifferenz, so von +`lib/dxf_scan_components.py` ausgegeben (ein VarioFoerderer ist bei +`deltaH` = 0 nicht loesbar). `TEST_DEMATIC_ARVATO` meldet diese Eintraege und +ueberspringt sie. Passende Primitive waere `insert-hz-scaled-block-by-ks` +(`vf_etage.lsp`) mit `Staustrecke_SP_1000_mm`, siehe +[lisp_methods.md](lisp_methods.md). + +--- + +## 9. Testdaten-Dateien im Ueberblick + +| Datei | Inhalt | Treiber / Befehl | +|---|---|---| +| `kreisel_tests.json` | 8 Kreisel (`insert`) | `TEST_KREISEL` | +| `foerderer_tests.json` | Kopf `y_abstand` + 17 VF Standard | `TEST_FOERDERER` | +| `gefaellestrecke_tests.json` | Kopf + GF Modus 1/2/3 | `TEST_GEFAELLESTRECKE` | +| `omniflo_tests.json` | 27 Boegen, 80 Weichen | `TEST_OMNIFLO` | +| `omniflo_strecke_tests.json` | Streckenzug aus Geraden und Boegen | `TEST_OMNIFLO_STRECKE` | +| `linienzug_tests.json` / `_stufe2.json` | Eingabe-Journal je einer Kette | `TEST_LINIENZUG` / `TEST_LINIENZUG_STUFE2` | +| `hm_recformat.json` | 5 HundM-Ketten als Journal | `TEST_HM_RECFORMAT` | +| `hundm05.json` | 5 HundM-Ketten als Spec + Kreisel, Separatoren, Scanner, BTMT | `TEST_HUNDM05`, `VF_SPEC_BAU` | +| `mubea.json` | Gesamtanlage: Kreisel, VF Standard/Etage, GF-Template (20 Stueck), Separatoren, Scanner, BTMT, Eingang/Ausgang | `TEST_MUBEA` | +| `dematic_arvato.json` | Vorlage aus `dxf_scan_components.py`: Kreisel, Einzelbloecke, `gerade` (Hoehen fehlen) | `TEST_DEMATIC_ARVATO` (abgeschaltet) | +| `hundm05_linienzug_polylines.json` | Polylinien aus `extract_polylines.py` | kein Treiber (Analyse-Daten) | +| `vfl_journal_hm05_abbruch.json` | rohes Journal (`kind`/`value`) eines abgebrochenen Baus | kein Treiber | + +--- + +## 10. Eigene Anlage beschreiben - Kurzanleitung + +1. Kreisel als `insert` (Abschnitt 1): `x/y` am aeussersten Punkt der AN-Seite, + `z` = Einbauhoehe. +2. Einfache Steig-/Fall-Strecken mit fester Form als VF Standard/Etage + (Abschnitt 3) bzw. Gefaellestrecke Modus 1 (Abschnitt 4.1), Startpunkt + jeweils = KS_EIN des AS-Elements. +3. Ketten mit Kurven, mehreren VF-Einheiten oder GF-Boegen als Spec + (Abschnitt 5) - am einfachsten eine Kette einmal interaktiv bauen, mit + `lib/vf_journal_export.py` + `lib/vf_spec_export.py` als Spec exportieren + und Startpunkt/Laengen anpassen. +4. Separatoren, Scanner, BTMT-Stationen, Eingang/Ausgang als Einzelblock + (Abschnitt 2). +5. Aus Kreisel, Einzelbloecken und Specs gemischt baut derzeit nur der + HundM05-Treiber (`TEST_HUNDM05`); fuer VF Standard/Etage + GF zusammen mit + Kreiseln den Mubea-Treiber (`TEST_MUBEA`) als Vorlage nehmen. diff --git a/doc/lisp_methods.md b/doc/lisp_methods.md new file mode 100644 index 0000000..11d78d7 --- /dev/null +++ b/doc/lisp_methods.md @@ -0,0 +1,328 @@ +# LISP-Routinen zum Bau einzelner Einheiten + +Uebersicht ueber die LISP-Funktionen, die eine Einheit (Kreisel, Eckrad, +VarioFoerderer, Gefaellestrecke, Omniflo-Element, Geruest, Einzelblock) +erzeugen, mit Signatur, Parametern, Rueckgabe und Voraussetzungen. + +Zu jedem Element steht der interaktive **Befehl** (was der Anwender aufruft) +und der **skriptfaehige Kern** (ohne `getpoint`/Dialog, aus Code oder JSON +aufrufbar). Das JSON-Format, mit dem die Testtreiber diese Kerne aufrufen, +steht in [json-input-lisp.md](json-input-lisp.md); alle Befehle in +[kommandos.md](kommandos.md). + +Einheiten: mm und Grad. `hz` = Fahrtrichtung in der XY-Ebene, 0 = +X, +90 = +Y. Punkte sind Listen `(x y z)` in Weltkoordinaten. + +--- + +## 0. Uebersicht + +| Einheit | Befehl | Skriptfaehiger Kern | Ergebnis-Block | Modul | +|---|---|---|---|---| +| Kreisel | `KreiselInsert`, `KreiselConnect`, `KreiselQuick` | `kreisel-insert-script`, `kreisel-connect-script` | `KREISEL_n` | `KreiselInsert.lsp` | +| Eckrad | `ILS_Eckrad` | `ils-eckrad-insert` | `ECKRAD_n` | `KreiselInsert.lsp` | +| VF Standard | `StandardVarioFoerderer`, `FOERDERANLAGE` | `berechne-standard` + `variofoerderer-einfuegen` + `vf-block-erstellen` | `VF_n` | `vf_standard.lsp`, `vf_core.lsp` | +| VF Etage | `EtageVarioFoerderer` | `berechne-etage` + `etage-foerderanlage-einfuegen` + `vf-block-erstellen` | `VF_n` | `vf_etage.lsp`, `vf_core.lsp` | +| VF Linienzug | `LinienzugVarioFoerderer` | `vsp-bau-aus-spec` (Spec) bzw. `vfl-journal-replay-start` + `vf-linienzug-modus` | `VF_n` | `vf_linienzug.lsp`, `vf_spec.lsp` | +| Gefaellestrecke | `GEFAELLESTRECKE_EINFACH`/`_3DLINIE` | `gf-rebuild` bzw. `gefaellestrecke-einfuegen` | `GF_n` | `Gefaellestrecke.lsp` | +| Gefaellestrecke Linienzug | `GEFAELLESTRECKE_LINIENZUG` | - (nur interaktiv; Kern `gf-linienzug-einfuegen` braucht analysierte Segmente) | `GF_n` | `Gefaellestrecke.lsp` | +| Separator, Scanner | `ILS_SEPARATOR_INSERT`, `ILS_SCANNER_INSERT` | - (Schleife mit `getpoint`); Kern: `ssg-ils-block-laden` + `_.INSERT` | Einzelblock | `SSG_LIB_Commands.lsp`, `ssg_core.lsp` | +| BTMT, Eingang/Ausgang | `ILS_BTMT_*`, `ILS_EINGANG_INSERT`/`ILS_AUSGANG_INSERT` | wie Separator | Einzelblock | `SSG_LIB_Commands.lsp` | +| Omniflo Bogen/Weiche/TEF | `OMNI_APB_*`, `OMNI_W*_*`, `OMNI_TEF_*` ... | `_.INSERT` der `.dxf` (Kern `omni:insert-dxf` fragt den Punkt) | Einzelblock | `OmniModulInsert.lsp` | +| Omniflo Gerade | `OMNI_AP60`/`AP110`/`APG110` | `omni:make-gerade` | Compound-Block | `OmniModulInsert.lsp` | +| TEF-Foerderer-Linie | `TEF_FOERDERER_LINIE` | `tefl-insert-element` (ein Element) | Einzelbloecke | `TEFInsert.lsp` | +| Geruest (Saeule, Rohrabhaenger, Wandbefestigung) | `GERUEST`, `GERUEST_SAEULEN` ... | `geruest-block-bauen` | `GR__` | `GeruestInsert.lsp` | +| Geruest-Traeger | `GERUEST_TRAEGER` | `geruest-traeger-block-bauen` | `GR__` | `GeruestInsert.lsp` | + +--- + +## 1. Kreisel und Eckrad (`KreiselInsert.lsp`) + +### `kreisel-insert-script (pt abstand rotation typ hoehe)` + +Kreisel an einem freien Punkt. Ruft `draw-module` auf. + +| Parameter | Bedeutung | +|---|---| +| `pt` | Einfuegepunkt: aeusserster Punkt der AN-Seite auf der Achse (einen Radius = 400 mm vor der Mitte des Antriebsrads) | +| `abstand` | Mittenabstand Antriebsrad - Spannrad | +| `rotation` | Drehung der Achse AN -> SP in Grad | +| `typ` | `"STANDARD"` oder `"PIN"` (nil = `"STANDARD"`) -> Attribut `KREISELART` | +| `hoehe` | Einbauhoehe; nil = Z von `pt`, sonst `*kreisel-default-hoehe*` | + +Rueckgabe: Entity des `KREISEL_n`-Blocks oder nil. Setzt `ssg-start`/`ssg-end` +selbst. + +### `kreisel-connect-script (ptStart ptEnd typ hoehe)` + +Kreisel zwischen zwei aeussersten Punkten (AN-Seite, SP-Seite). Berechnet +`abstand` = Distanz - `*kreisel-durchmesser*` (800) und `rotation` aus den +Punkten. Rueckgabe wie oben. + +### `draw-module (basePoint abstand rotation attribs)` + +Gemeinsamer Bau-Kern beider Funktionen: setzt Antriebs- und Spannstation, +Tangenten, Beschriftung, fasst alles zu `KREISEL_n` zusammen, schreibt die +Attribute (`attribs` = Alist, wird mit `*kreisel-attrib-defs*` gemischt). +Direkt nur aufrufen, wenn zusaetzliche Attribute gesetzt werden muessen. + +### `ils-eckrad-insert (pt rotation attribs)` + +Eckrad als Compound-Block `ECKRAD_n`. + +| Parameter | Bedeutung | +|---|---| +| `pt` | **Radmitte** `(x y hoehe)` | +| `rotation` | Drehung in Grad | +| `attribs` | Alist mit Ueberschreibungen, z.B. `(("GERUEST_EINZELMODUL" . "1") ("GERUEST_TYP" . "Schoenenberger Geruest"))`; nil = Defaults | + +`c:ILS_Eckrad` berechnet die Radmitte aus Beruehrpunkt + Radius in Richtung +des zweiten Klicks. Rueckgabe: Entity des Blocks. + +--- + +## 2. VarioFoerderer Standard und Etage (`vf_core.lsp`, `vf_standard.lsp`, `vf_etage.lsp`) + +Ablauf in drei Schritten: **loesen** (Winkel und Teillaengen aus den +Gesamtmassen), **einfuegen** (Bausteine verketten), **Block bilden** +(`VF_n` mit Attributen). Der Dialog-Ablauf `vf-dialog-ablauf` / +`vf-dialog-berechnen-einfuegen` macht dasselbe, oeffnet aber Dialog 2 und ist +darum nicht skriptfaehig. + +### Vorbereitung + +| Funktion | Zweck | +|---|---| +| `init-bibliothek ()` | Bogen-, AS-/ES- und Stationsmasse aus den Bloecken lesen. Setzt `*lib-initialized*`. Wird von `berechne-standard` aufgerufen | +| `init-bibliothek-etage ()` | dasselbe fuer Etage, aufgerufen von `berechne-etage` | +| `vf-set-as-masse (winkel seite)` / `vf-set-es-masse (winkel seite)` | AS-/ES-Masse (`aus-dx`/`aus-dz`, `ein-dx`/`ein-dz`) fuer die Variante `"90"`/`"30"` + `"links"`/`"rechts"` setzen | + +### Loesen + +| Funktion | Rueckgabe | +|---|---| +| `berechne-standard (deltaL deltaH richtung seite)` | `(best-winkel L_GF L_VF ergebnis-liste)` | +| `berechne-alle-winkel (deltaL deltaH richtung feste-hz schon-flach)` | wie oben; `feste-hz` nil = 1600 mm feste Horizontale, `schon-flach` nil | +| `berechne-etage (deltaL deltaH richtung seite)` | wie oben, Etage-Geometrie (setzt die Gefaellebogen-Masse je `seite`) | +| `vf-winkel-solve (...)` | gemeinsamer L_GF/L_VF-Solve beider Typen (intern) | + +- `richtung` = `"Auf"`/`"Ab"`, `deltaH` als Betrag. +- `ergebnis-liste` = je Kandidatenwinkel `(winkel L_GF L_VF gueltig)`. + Kandidaten aus `vario.bogen_winkel` (3 ... 51). Einen bestimmten Winkel + erzwingen: Eintrag mit diesem `winkel` aus der Liste nehmen und + `gueltig`, `L_GF` > 0, `L_VF` > 0 pruefen (so `mubea:build-vario-one`). +- `L_GF` wird danach auf Vorlauf `L_GF1` und Nachlauf `L_GF2` aufgeteilt + (gleichmaessig / vorne / hinten / manuell, `foerderer:split-gf`). + +### Einfuegen + +``` +(variofoerderer-einfuegen deltaL deltaH richtung best-winkel L_GF1 L_GF2 L_VF startpunkt seite hz) +(etage-foerderanlage-einfuegen deltaL deltaH richtung best-winkel L_GF1 L_GF2 L_VF startpunkt seite hz) +``` + +Identische Signatur (Registry-Vertrag `*vf-typ-registry*`). `startpunkt` = +Lage von **KS_EIN des AS-Elements**, Z = Einbauhoehe am Anfang. `seite` = +`"links"`/`"rechts"`. Rueckgabe: Endpunkt (KS_AUS des ES-Elements). +`variofoerderer-einfuegen` ist aus Teilen zusammengesetzt (`vfs-as-teil`, +VF-Eingang/Koerper, `vfs-es-teil`), die auch der Linienzug nutzt. + +### Block bilden + +``` +(setq lastEnt (ssg-lastent-ohne-attribute)) ; VOR dem Einfuegen merken +...einfuegen... +(vf-block-erstellen anlage-typ seite (vf-next-number) n-scanner + hoehe-von hoehe-bis deltaH deltaL L_VF L_GF1 L_GF2 + richtung best-winkel startpunkt lastEnt hz + geruest-einzelmodul geruest-typ) +(vf-edit-xdata-schreiben ent anlage-typ hz) ; editierbar per Doppelklick +``` + +- `anlage-typ` = `"standard"`/`"etage"`; `hoehe-bis` = +deltaH bei `"Auf"`, + -deltaH bei `"Ab"`; `geruest-einzelmodul` `"0"`/`"1"` (nil = `"1"`), + `geruest-typ` einer aus `*ssg-geruest-optionen*`. +- Sammelt alle Objekte seit `lastEnt` zum Block `VF_n`, setzt Layer, Attribute, + ID (`ssg-id-generate`) und Dimension. Rueckgabe: Entity des Blocks. +- `lastEnt` immer mit `ssg-lastent-ohne-attribute` holen, nicht mit + `(entlast)` (siehe `CLAUDE.md`, ssg_core). +- Blocknummer immer ueber `vf-next-number`: eine feste Nummer fuehrt bei der + zweiten Anlage zu einer Rueckfrage von `_.-BLOCK` und einem haengenden Befehl. + +--- + +## 3. VF-Linienzug (`vf_linienzug.lsp`, `vf_spec.lsp`) + +Ketten mit Vario-Kurven, GF-Boegen und mehreren VF-Einheiten. Der Bau-Ablauf +ist ein Frage-Antwort-Assistent; jede Eingabe wird journalisiert und als XDATA +am `VF_n`-Block gespeichert. Nicht-interaktiv baut man ihn, indem man ein +Journal **abspielt**. + +| Funktion | Zweck | +|---|---| +| `vf-linienzug-modus ()` | Modus 1, frei zeichnend. Liest alle Eingaben ueber `vfl-in-point`/`-string`/`-real`/`-int` - aus der Replay-Queue, falls eine aktiv ist | +| `vf-linienzug-modus2 ()` | Modus 2, Pfad + Zielhoehe (Objektauswahl, eigenes Journal `"linienzug2"`) | +| `vfl-journal-replay-start (forward-journal)` | Journal als Antwort-Queue setzen; danach `vf-linienzug-modus` aufrufen | +| `vsp-journal (spec)` | Spec -> Journal (nil bei Spec-Fehlern, Fehler in `*vsp-fehler*`) | +| `vsp-pruefen (spec)` | alle Spec-Fehler sammeln | +| `vsp-bau-aus-spec (spec)` | eine Kette aus einer Spec bauen (headless, Replay, Ergebnis-Record mit `status`, Block, `prompts`, `glieder_soll`/`glieder_ist`) | +| `vsp-bau-liste (specs)` | mehrere Specs, je mit `vl-catch-all-apply` | +| `vsp-bau-datei (datei)` | Spec-Datei lesen (`vsp-json-laden` + `vsp-gruppieren`) und alle Ketten bauen; das ist `VF_SPEC_BAU` | +| `vsp-headless-an ()` / `vsp-headless-aus ()` | Headless-Rahmen: `*vfl-headless*`, Wizard aus, `*ssg-gui-aus*`, zaehlende Eingabe-Stubs | + +Ein Abbruch im Headless-Modus nennt die Stelle (`*vfl-headless-fehler*`), +statt auf eine Live-Eingabe zu warten. Details zum Ablauf und zu den +Journal-Regeln: [variofoerderer.md](variofoerderer.md), Abschnitte 5-8b. + +--- + +## 4. Gefaellestrecke (`Gefaellestrecke.lsp`) + +### `gf-rebuild (deltaL winkel hz-winkel as-seite es-seite startpunkt as-winkel es-winkel geruest-einzelmodul geruest-typ)` + +Der bequemste skriptfaehige Einstieg fuer Modus 1/2: setzt die AS-/ES-Masse, +berechnet die Staustrecke +`L_stau = (deltaL - aus-dx - ein-dx - separator_breite * cos(w)) / cos(w)` +und ruft `gefaellestrecke-einfuegen`. Ohne Zusammenfassung und Rueckfrage +(eigentlich fuer `GEFAELLESTRECKE_EDIT` gedacht). + +| Parameter | Bedeutung | +|---|---| +| `deltaL` | horizontale Gesamtlaenge AS + Staustrecke + Separator + ES | +| `winkel` | Neigung der Staustrecke in Grad (Default im Dialog 3.0) | +| `hz-winkel` | Fahrtrichtung | +| `as-seite`, `es-seite` | `"links"`/`"rechts"` | +| `startpunkt` | **KS_EIN des AS-Elements**, Z = Einbauhoehe oben | +| `as-winkel`, `es-winkel` | `"90"`/`"30"` als STRING (nil = `"90"`) | +| `geruest-einzelmodul`, `geruest-typ` | wie beim VF | + +Rueckgabe: `(ename deltaH deltaL)` oder nil (Staustrecke <= 0.1 mm -> `alert`). + +### `gefaellestrecke-einfuegen (L_stau winkel startpunkt as-seite es-seite hz deltaL-total as-winkel es-winkel geruest-einzelmodul geruest-typ)` + +Eigentlicher Bau: AS-Element, skalierte Staustrecke, Separator, ES-Element, +Block `GF_n` mit Attributen, XDATA `SSG_GF_EDIT` und ID. Erwartet die +fertige Staustreckenlaenge `L_stau` und passend gesetzte AS-/ES-Masse +(`vf-set-as-masse`/`vf-set-es-masse`). Rueckgabe wie `gf-rebuild`. + +### Weitere + +| Funktion | Zweck | +|---|---| +| `gf-modus12-abschluss (... dialog-bestaetigt ...)` | Abschluss Modus 1/2 mit Zusammenfassung; fragt ohne `dialog-bestaetigt` = T per Konsole nach | +| `gf-init-bibliothek ()` | AS-/ES-Masse fuer die GF lesen | +| `gf-linienzug-modus ()` | Modus 3, interaktiv (Auswahl vorhandener LINE/ARC) | +| `gf-linienzug-einfuegen (segmente grade startframe)` | Bau-Kern von Modus 3, erwartet die von `gf-analysiere-kette` aufbereiteten Segmente | + +**Falle `*lib-initialized*`**: `gefaellestrecke-einfuegen` initialisiert die +GF-Masse nur, wenn `*lib-initialized*` nil ist - dasselbe Flag setzt +`init-bibliothek` des VarioFoerderers. Wer in einem Lauf erst VF und dann GF +baut, muss `gf-init-bibliothek` einmal selbst erzwingen (so +`mubea:ensure-gf-init` in `tests/test_mubea.lsp`), sonst rechnet die GF mit +den Vario-Massen und landet auf falscher Hoehe. + +--- + +## 5. Einzelbloecke: Separator, Scanner, BTMT, Eingang/Ausgang + +| Funktion | Zweck | +|---|---| +| `ils-insert-sensor (fname)` | Befehls-Kern Separator/Scanner: fuegt in einer Schleife ein, Punkt per `getpoint`, Drehung per `pause` - **nicht skriptfaehig** | +| `ils-insert-station (rohname)` | dasselbe fuer BTMT-Beladung, SC_Entladung, Eingang, Ausgang; setzt zusaetzlich den Layer aus dem `LAYER`-Attribut | +| `ssg-ils-block-laden (blockname)` | Blockdefinition `data/ils/_.dwg` einmal je Zeichnung laden; Rueckgabe: effektiver Blockname mit Suffix oder nil | +| `ssg-ils-block-datei (blockname)` | nur den Dateipfad aufloesen (3D-Fallback) | +| `ssg-block-layer-vorgabe` | `LAYER`-Attribut eines eingefuegten Blocks als Ziel-Layer anwenden | +| `ssg-id-generate (ent)` | eindeutige `ID` vergeben (nur wenn der Block ein ID-Attribut hat) | + +Skriptfaehig bauen (Muster aus `hundm05:bau-sensor`): + +```lisp +(setq eff (ssg-ils-block-laden "Scanner")) ; nicht per (and ...) verketten! +(command "_.INSERT" eff (list x y z) "" "" rotation) +(while (and (> (getvar "CMDACTIVE") 0) (< n 5)) (command "") (setq n (1+ n))) +(ssg-id-generate (entlast)) +``` + +`ATTREQ`/`ATTDIA` vorher auf 0 setzen. Restliche Prompts gezaehlt mit Enter +beantworten, nicht ungebremst: eine Rueckfrage, die Enter ablehnt, haelt +`CMDACTIVE` sonst dauerhaft > 0. + +--- + +## 6. Omniflo (`OmniModulInsert.lsp`) + +| Funktion | Zweck | +|---|---| +| `omni:insert-dxf (sivasnr-str hoehe drehung)` | `%DXFM_OMNIFLO%/.dxf` einfuegen, `HOEHE`/`DREHUNG` setzen, Layer. **Einfuegepunkt per Maus** (`pause`); Rueckgabe: Einfuegepunkt oder nil | +| `omni:insert-from-entry (dlg-result)` | Dialog-Ergebnis von Bogen/Weiche -> `omni:insert-dxf` | +| `omni:make-gerade (blockname pt pt2 srcAttribs textHeight textGap textEnt)` | **skriptfaehig**: Aluprofil-Gerade `"AP60"`/`"AP110"`/`"APG110"` von `pt` nach `pt2` als Compound-Block mit Text. `srcAttribs` per `omni:read-src-attribs`, `textEnt` nil. Kein `ssg-start` - Aufrufer setzt OSMODE/ATTREQ/ATTDIA = 0. Rueckgabe: Entity | +| `omni:insert-block (blockname laengemax)` | Befehls-Kern der Geraden (fragt `pt`/`pt2` per `getpoint`) | +| `omni:ladeschleifen-dialog (profiltyp init-hoehe)` | Dialog Ladeschleife, danach `omni:insert-dxf` | + +Skriptfaehig fuer Boegen/Weichen: `_.INSERT` der DXF direkt mit Punkt +`(x y hoehe)` und Drehung, danach `ssg-attrib-set-on` mit `HOEHE`/`DREHUNG` +(`omni:test-insert` in `tests/test_omniflo.lsp`). Die festen TEF-/Zubehoer- +Befehle (`OMNI_TEF_*`, `OMNI_STOPPER` ...) rufen `omni:insert-dxf` mit fester +Sivas-Nummer. `OMNI_TV_*` und `OMNI_APBW_*` sind Platzhalter ohne Bau-Logik. + +--- + +## 7. TEF-Foerderer-Linie (`TEFInsert.lsp`) + +| Funktion | Zweck | +|---|---| +| `c:TEF_FOERDERER_LINIE` | Start-/Endpunkt abfragen, Linie zeichnen, 1000-mm-TEF-Geraden (`0_B10030.dxf`) aneinanderreihen; das letzte Stueck wird zurueckversetzt, damit der Foerderer genau am Linienende endet | +| `tefl-insert-element (start richtung-einheit s hz winkel)` | ein Element an Position `s` (mm ab `start`) entlang der Linie, Rz(hz)*Ry(winkel) | +| `tefl-ensure-block-loaded (dxf-pfad)` | Blockdefinition einmal laden | + +--- + +## 8. Geruest (`GeruestInsert.lsp`) + +| Funktion | Zweck | +|---|---| +| `geruest-einfuegen (bg wahl)` | Befehls-Kern Saeulen/Rohrabhaenger/Wandbefestigung: baut aus der Dialog-Auswahl `wahl` = `(variante-index oben unten laenge farbe)` die Werte (ARTINR, BEZEICHNUNG, TEILEART, LAENGE, TEILEFARBE), fragt dann den Punkt per `getpoint` | +| `geruest-block-bauen (bg variante werte pt teile-dwgs)` | **skriptfaehiger Kern**: Dummy-DWG + Teile-DWGs bei `pt`, Block `GR__`. `bg` = Baugruppe aus `geruest.json`, `werte` = Attribut-Alist, `teile-dwgs` aus `geruest-teile-dwgs` | +| `geruest-traeger-einfuegen (bg wahl)` | Befehls-Kern Traeger: Schleife Startpunkt -> Endpunkt oder Laenge | +| `geruest-traeger-block-bauen (bg layer netto strebe strebe-pfad muffe-pfad werte pt winkel)` | **skriptfaehiger Kern** Traeger: LINE (0,0,0)-(netto,0,0), Streben, Muffe, Block bei `pt` mit `winkel` (Radiant). Rueckgabe: `(blockname . entity)` | +| `geruest-traeger-laenge`, `-start`, `-streben`, `-pruefen` | reine Berechnung (Netto-Laenge nach Abzuegen, Startpunkt, Strebenverteilung, min/max) - in `tests/test_unit.lsp` getestet | +| `geruest-traeger-beschriften (blockEnt)` | Beschriftungstext aus dem fertigen Block | +| `geruest-block-abschliessen (bg attribs werte lastEnt pt rot)` | gemeinsamer Schluss aller Geruest-Bloecke (ATTDEFs, Block bilden, einfuegen, ID) | + +Fuer Geruest gibt es noch keinen JSON-Treiber. + +--- + +## 9. Einfuegeprimitiven (`ssg_ks_insert.lsp`, `vf_etage.lsp`) + +Grundbausteine, auf denen VF, GF und Linienzug aufsetzen. Alle legen **KS_EIN** +des Blocks (nicht den Block-Ursprung) auf den Startpunkt und geben den +Weltpunkt von **KS_AUS** zurueck - damit lassen sich Bausteine verketten. + +| Funktion | Zweck | +|---|---| +| `ensure-block-loaded (name)` | Block einmal ins Dokument laden | +| `extract-ks-from-block (block-obj)` | KS_EIN/KS_AUS eines eingefuegten Blocks (VLA-Objekt) lesen, Cache `*ks-cache*` je Blockname | +| `insert-block-by-ks (blockname einfuegepunkt hz)` | Block nur um Z gedreht (AS/ES-Elemente, Stationen) | +| `insert-inclined-scaled-block (blockname startpunkt laenge winkel hz)` | Block in X auf `laenge` skaliert und um `winkel` geneigt, Rz(hz)*Ry(winkel) (Staustrecken) | +| `insert-rotated-block-with-ks (blockname startpunkt winkel block-dx block-dz hz)` | Block gedreht, `block-dx`/`block-dz` nur als Fallback ohne KS_AUS (Boegen) | +| `insert-hz-scaled-block-by-ks (blockname startpunkt laenge hz-winkel vert-winkel)` | waagrechter, auf `laenge` skalierter Block (`Staustrecke_SP_1000_mm`, Massstab `laenge/1000`); Kandidat fuer die noch fehlende waagrechte Foerderstrecke. Bricht bei fehlender Blockdatei mit `(exit)` ab - vorher mit `ssg-ils-block-laden` pruefen | +| `ssg-rot-matrix-zy (chz shz cv sv)` | 4x4-Rotationsmatrix Rz(hz)*Ry(vert) aus Cosinus/Sinus beider Winkel (`ssg_core.lsp`) | + +--- + +## 10. Gemeinsame Regeln fuer eigene Treiber + +- Systemvariablen ueber `ssg-start`/`ssg-end` sichern; `OSMODE` 0, + `ATTREQ` 0, `ATTDIA` 0 setzen, sonst fragt `_.INSERT` nach Attributen. +- Module vorher laden: `(ssg-ensure "KreiselInsert")`, `"VarioFoerderer"`, + `"Gefaellestrecke"`, `"SSG_LIB_Commands"`. +- Die Dimension folgt `ssg-ils-dim-aktuell`; fuer einen festen Lauf + `*ssg-ils-dim*` (bzw. `*vsp-dim-override*` beim Spec-Bau) setzen und danach + zuruecksetzen. +- Rueckgaben nicht mit `and`/`or` verketten - die liefern in AutoLISP nur + T/nil (siehe `CLAUDE.md`, Konventionen). +- Interaktive GUI abschalten: `ssg-gui-aus` bzw. `vsp-headless-an`. +- Vorbilder: `tests/test_mubea.lsp` (Kreisel, VF Standard/Etage, GF, + Einzelbloecke), `tests/test_hundm05.lsp` (Spec-Ketten, Kreisel, + Einzelbloecke), `tests/test_omniflo_strecke.lsp` (Omniflo).