# JSON-Schema fuer tests/testdata/*.json Diese Datei leitet **aus den Produktions-Routinen in `Lisp/`** (nicht aus den Test-Hilfsfunktionen in `tests/*.lsp`) ab, welche Angaben pro Element-Typ noetig sind, um das jeweilige Bauteil programmgesteuert (per JSON) zu erzeugen. Fuer jeden Typ wird die tatsaechlich aufgerufene LISP-Funktion samt Parameterliste referenziert. Jede Objekt-Sektion enthaelt ein **vollstaendiges** JSON-Beispiel: es listet *alle* Werte auf, die man setzen muss, damit die dahinterliegende Produktionsfunktion mit saemtlichen Parametern aufgerufen werden kann — so, als gaebe es keine `tests/test_*.lsp`-Wrapper. Wo Koordinaten (x/y/z) als Eingang noetig sind, stehen sie explizit im Beispiel. Optionale Felder sind mit ihrem Default annotiert; laesst man sie weg, greift der Default. Wichtige Unterscheidung, die sich beim Durcharbeiten herauskristallisiert hat: - **Scriptbar** = es existiert eine Produktions-Funktion, die alle noetigen Werte als normale Parameter entgegennimmt (kein `getpoint`/`getstring` noetig). Diese Funktionen sind 1:1 aus JSON aufrufbar. - **Nicht scriptbar** = die Produktions-Funktion fragt Punkte/Optionen interaktiv ab (`getpoint`, `getstring`, `ssget` auf bereits gezeichnete Geometrie). Es gibt in `Lisp/` keinen parametrischen Einstiegspunkt; ein JSON-Testfall kann diese Faelle nur ueber einen eigens geschriebenen Wrapper (wie es `tests/*.lsp` tut) nicht-interaktiv nachbilden. Einheiten durchgaengig: **mm** fuer Laengen/Koordinaten, **Grad** fuer Winkel (intern wird oft in Radiant gerechnet, die LISP-API-Grenze ist aber immer Grad). Richtung `hz`/`hz_grad`: 0=Ost(+X), 90=Nord(+Y), 180=West(-X), 270=Sued(-Y). --- ## 0. Uebersicht — alle skriptbaren Objekte | # | Objekt | Produktionsfunktion (Bau) | Scriptbar? | Koordinaten-Eingang | Sektion | |---|---|---|---|---|---| | 1.1 | Kreisel Insert | `kreisel-insert-script (pt abstand rotation typ hoehe)` | **ja** | 1 Punkt `x,y,z` | [#11](#11-kreisel-insert-freier-punkt) | | 1.2 | Kreisel Connect | `kreisel-connect-script (ptStart ptEnd typ hoehe)` | **ja** | 2 Punkte `start_*`/`end_*` | [#12](#12-kreisel-connect-start-endpunkt) | | 2.1 | Gefaellestrecke Modus 1 | `gf-modus12-abschluss` → `gefaellestrecke-einfuegen` | **ja** | 1 Punkt `start_mm` | [#21](#21-gefaellestrecke-modus-1--manuelle-werteingabe) | | 2.2 | Gefaellestrecke Modus 2 | `gf-modus12-abschluss` (deltaL/hz aus 2 Punkten) | **ja** | 3 Punkte (`linie_*`, `start_mm`) | [#22](#22-gefaellestrecke-modus-2--3d-linie-als-richtungsreferenz) | | 2.3 | Gefaellestrecke Modus 3 | `gf-linienzug-modus ()` | **nein** | vorgezeichnete LINE/ARC-Auswahl | [#23](#23-gefaellestrecke-modus-3--linienzug--nicht-scriptbar) | | 3.1 | VarioFoerderer Standard | `variofoerderer-einfuegen (…)` | **ja** | 1 Punkt `startpunkt` | [#31](#31-variofoerderer-standard--scriptbar) | | 3.2 | VarioFoerderer Etage | `etage-foerderanlage-einfuegen (…)` | **ja** | 1 Punkt `startpunkt` | [#32](#32-variofoerderer-etage--scriptbar) | | 3.3 | VarioFoerderer Linienzug | `vf-linienzug-modus ()` | **nein** | interaktiver Assistent | [#33](#33-variofoerderer-linienzug--nicht-scriptbar) | | 3.4 | Vario_Kette_Merge | `vfl-kette-verfolgen` (Re-Packaging) | n/a | 1 Entity-Pick | [#34](#34-vario_kette_merge--kein-neubau) | | 4.1 | Omniflo Bogen/Weiche | `omni:insert-dxf (sivasnr hoehe drehung)` + `_.INSERT` | Kern ja, Befehl nein | 1 Punkt `x,y,z` (nur via Wrapper) | [#41](#41-omniflo-boegenweichen) | | 4.2 | Omniflo Aluprofil-Gerade | `omni:make-gerade (blockname pt pt2 …)` | Kern ja | 2 Punkte `pt`/`pt2` | [#42](#42-omniflo-aluprofil-gerade-ap60ap110apg110) | | 4.3 | Omniflo TEF/Zubehoer | `omni:insert-dxf` (fester Sivasnr) | Position ja, Punkt interaktiv | 1 Punkt (nur via Wrapper) | [#43](#43-omniflo-tefzubehoer) | | 4.4 | Omniflo TV_*/APBW_* | — (DUMMY) | **nein** | — | [#44](#44-omniflo-tv_-apbw_) | | 5 | Separator/Scanner/Sensor | `ils-insert-sensor (fname)` + `_.INSERT` | **nein** (Schleife) | 1 Punkt `x,y,z` (nur via Wrapper) | [#5](#5-separatorscannersensor) | | 6 | Waagrechte Foerderstrecke (`gerade`) | `insert-hz-scaled-block-by-ks (blockname startpunkt laenge hz vert)` | **ja** | 1 Punkt `startpunkt` (+ `endpunkt` als Doku) | [#6](#6-waagrechte-foerderstrecke-gerade) | Legende Scriptbar-Spalte: - **ja** — direkt per JSON aufrufbar. - **Kern ja, Befehl nein** — die Bau-Funktion nimmt alle Werte an, aber der ausgelieferte `c:…`-Befehl fragt den Einfuegepunkt per Maus (`pause`) ab; ein JSON-Treiber muss den `_.INSERT` selbst nachbauen. - **nein** — nur interaktiv; JSON beschreibt hoechstens die gewuenschte Geometrie, nicht einen Funktions-Parametersatz. --- ## 1. Kreisel — `Lisp/KreiselInsert.lsp` Scriptbar. Zwei Varianten. ### 1.1 Kreisel Insert (freier Punkt) Produktionsfunktion: `kreisel-insert-script (pt abstand rotation typ hoehe)` | Feld | Typ | Pflicht | Bedeutung | Default | |---|---|---|---|---| | `x`, `y` | real (mm) | **ja** | Einfuegepunkt (Welt-XY), zusammen `pt` | - | | `z` | real (mm) | nein | Z von `pt`; wird verwendet, wenn `hoehe` fehlt | - | | `hoehe` | real (mm) | nein | Ueberschreibt Z; sonst `pt`s Z | `*kreisel-default-hoehe*` (config `kreisel.default_hoehe`, 2000.0) | | `abstand` | real (mm) | nein | Abstand zwischen AN-/SP-Kreiselhaelfte (Kreisel-Baulaenge) | `*kreisel-default-laenge*` (config `kreisel.default_laenge`, 2300.0) | | `rotation` | real (Grad) | nein | freie Rotation, kein Rasterzwang | 0.0 | | `typ` | string | nein | `"STANDARD"` oder `"PIN"` (einzige Optionen aus `kreisel-ask-typ`) | `"STANDARD"` | Konstanten (Config `[kreisel]`): `durchmesser` (800.0mm, fuer Connect-Abstandsberechnung), `default_laenge`, `default_hoehe`. Vollstaendiges JSON (alle Parameter gesetzt): ```json { "id": "K1", "function": "insert", "x": 500, "y": 500, "z": 2500, "hoehe": 2500, "abstand": 2300, "rotation": 0.0, "typ": "STANDARD" } ``` `id` und `expect_*`-Felder (`expect_block_prefix`, `expect_hoehe`, `expect_kreiselart`) sind **keine** Produktionsparameter — reine Test-Erwartungswerte fuer die Ergebnispruefung, nicht von `kreisel-insert-script` konsumiert. ### 1.2 Kreisel Connect (Start-/Endpunkt) Produktionsfunktion: `kreisel-connect-script (ptStart ptEnd typ hoehe)` | Feld | Typ | Pflicht | Bedeutung | Default | |---|---|---|---|---| | `start_x`,`start_y`,`start_z` | real (mm) | **ja** | aeusserster Punkt AN-Seite (Antriebsstation) | - | | `end_x`,`end_y`,`end_z` | real (mm) | **ja** | aeusserster Punkt SP-Seite (Spannstation) | - | | `typ` | string | nein | `"STANDARD"`/`"PIN"` | `"STANDARD"` | | `hoehe` | real (mm) | nein | Einfuegehoehe | Z von `ptStart`, sonst `*kreisel-default-hoehe*` | `abstand`/`rotation` werden **intern** aus den beiden Punkten berechnet (`abstand = distanz - *kreisel-durchmesser*`; Rotation aus dem Winkel der Verbindungslinie) — bei Connect KEIN Eingabefeld. Vollstaendiges JSON (alle Parameter gesetzt): ```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", "hoehe": 2000 } ``` --- ## 2. Gefaellestrecke — `Lisp/Gefaellestrecke.lsp` Drei Modi, siehe Kopfkommentar der Datei. Modus 1+2 teilen sich denselben Abschluss (`gf-modus12-abschluss`) und sind beide scriptbar. Modus 3 (Linienzug) ist in Produktion **nicht scriptbar**. Gemeinsame Bau-Funktion (von allen Modi letztlich aufgerufen): `gefaellestrecke-einfuegen (L_stau winkel startpunkt as-seite es-seite hz deltaL-total as-winkel es-winkel geruest-einzelmodul geruest-typ)` Wichtig: **`deltaL` (horizontale Laenge) ist der Eingang, nicht die Hoehendifferenz.** Bei festem `winkel` legen Laenge und Hoehendifferenz sich gegenseitig fest (`L_stau = (deltaL - aus-dx - ein-dx - sep-x) / cos(rad)`, `sep-x = separator_breite * cos(rad)`, config `gefaelle.separator_breite` Default 300.0). Es gibt **keinen** Parameter fuer eine gewuenschte Ziel-Hoehendifferenz — die ergibt sich aus `L_stau`/`winkel`. ### 2.1 Gefaellestrecke Modus 1 — Manuelle Werteingabe Eingangsfunktion (nach DCL-Dialog `gefaellestrecke.dcl`): `gf-modus12-abschluss (deltaL winkel hz-winkel as-seite es-seite startpunkt as-winkel es-winkel dialog-bestaetigt geruest-einzelmodul geruest-typ)` | Feld | Typ | Pflicht | Bedeutung | Default | |---|---|---|---|---| | `deltaL_mm` | real (mm) | **ja** | horizontale Gesamtlaenge (AS+Staustrecke+Separator+ES) | config `gefaelle.default_delta_l` 5000.0 | | `winkel_grad` | real (Grad) | nein | Gefaelle-Neigungswinkel | config `gefaelle.default_winkel` 3.0 | | `hz_grad` | real (Grad) | nein | Fahrtrichtung (0/90/180/270 ueblich, frei waehlbar) | 0.0 | | `start_mm` | [x,y,z] (mm) | **ja** | Startpunkt; Z = Einfuegehoehe | - | | `as_seite` | string | nein | `"links"`/`"rechts"` (AUS-Element-Seite) | `"links"` | | `es_seite` | string | nein | `"links"`/`"rechts"` (EIN-Element-Seite) | `"links"` | | `as_winkel` | string | nein | `"90"`/`"30"` (AUS-Element-Variante) | `"90"` | | `es_winkel` | string | nein | `"90"`/`"30"` (EIN-Element-Variante) | `"90"` | | `geruest_einzelmodul` | string "0"/"1" | nein | Geruest je Einzelmodul | `"1"` | | `geruest_typ` | string | nein | einer aus `*ssg-geruest-optionen*` (s.u.) | `"Schoenenberger Geruest"` (Index 2) | `*ssg-geruest-optionen*` (aus `ssg_core.lsp`): `"IPE-Geruest abgestuft"`, `"Obergeruest oben"`, `"Schoenenberger Geruest"`, `"nur Doppelrohrtraeger"`. Interner Parameter `dialog-bestaetigt` ist immer `T` (kein Datenfeld). Vollstaendiges JSON (alle Parameter gesetzt): ```json { "test_id": "GF_M1", "modus": 1, "deltaL_mm": 10000, "winkel_grad": 3.0, "hz_grad": 0.0, "start_mm": [0, 0, 5000], "as_seite": "links", "es_seite": "links", "as_winkel": "90", "es_winkel": "90", "geruest_einzelmodul": "1", "geruest_typ": "Schoenenberger Geruest" } ``` Hinweis: In `gefaellestrecke_tests.json` stehen `deltaL_mm`/`winkel_grad`/ `z_start_mm` als gemeinsamer Kopf-Eintrag (ein Objekt ohne `test_id`), weil alle Modus-1-Faelle dieselbe Laenge teilen. Produktionsseitig ist `deltaL` aber pro Aufruf ein normaler, nicht geteilter Parameter — daher hier vollstaendig im Einzelfall aufgefuehrt. ### 2.2 Gefaellestrecke Modus 2 — 3D-Linie als Richtungsreferenz Gleiche Ziel-Funktion (`gf-modus12-abschluss`), aber `deltaL`/`hz-winkel` werden **nicht** direkt angegeben, sondern aus zwei Linienpunkten berechnet: ``` deltaX = ende.x - start.x ; deltaY = ende.y - start.y deltaL = hypot(deltaX, deltaY) hz-winkel = angle(deltaX, deltaY) [Grad, 0=Ost] ``` In Produktion werden diese zwei Punkte interaktiv per `get-line-start-end-points` (Linie in der Zeichnung anklicken) bestimmt — fuer einen JSON-Treiber genuegen aber zwei beliebige Punkte, da die Berechnung rein geometrisch ist. | Feld | Typ | Pflicht | Bedeutung | Default | |---|---|---|---|---| | `linie_start_mm` | [x,y,z] | **ja** | erster Linienpunkt (nur X/Y verwendet) | - | | `linie_ende_mm` | [x,y,z] | **ja** | zweiter Linienpunkt (nur X/Y verwendet) | - | | `start_mm` | [x,y,z] | **ja** | Einfuegepunkt der Kette (unabhaengig von der Linie) | - | | `winkel_grad` | real (Grad) | nein | Neigungswinkel | 3.0 | | `as_seite`,`es_seite` | string | nein | `"links"`/`"rechts"` | `"links"` | | `as_winkel`,`es_winkel` | string | nein | `"90"`/`"30"` | `"90"` | Geruestoptionen sind in Modus 2 (reine Konsoleneingabe) nicht abfragbar, bleiben auf Default (`geruest_einzelmodul="0"`, `geruest_typ="Schoenenberger Geruest"`). Vollstaendiges JSON: ```json { "test_id": "GF_M2", "modus": 2, "winkel_grad": 3.0, "linie_start_mm": [0, 0, 0], "linie_ende_mm": [10000, 0, 0], "start_mm": [0, 0, 5000], "as_seite": "links", "es_seite": "links", "as_winkel": "90", "es_winkel": "90" } ``` ### 2.3 Gefaellestrecke Modus 3 — Linienzug — **nicht scriptbar** Eingangsfunktion: `gf-linienzug-modus ()` — **keine Parameter**. Ablauf in Produktion: 1. `(ssget '((0 . "LINE,ARC")))` — der Nutzer muss die Leitlinie (Kette aus bereits in der Zeichnung vorhandenen LINE-/ARC-Entities) **vorher zeichnen und dann per Maus auswaehlen**. Es gibt keine Funktion, die eine Leitlinie aus Koordinaten/Bogen-Parametern (`start_mm`/`ende_mm`/`bogen`) direkt als Argument entgegennimmt. 2. `getpoint` fuer Startpunkt (Kettenanfang, Z = obere Anschlusshoehe) und Endpunkt-Referenz (Z = untere Anschlusshoehe); `deltaH` wird aus der Differenz der beiden Z-Werte gebildet. 3. `getstring`-Abfragen fuer AS-/ES-Winkel (`"90"`/`"30"`) und -Seite (`"links"`/`"rechts"`) — inhaltlich identisch zu Modus 1/2. 4. Interne Analyse der selektierten Kette (`gf-sortiere-objekte`, `gf-analysiere-kette`) leitet Ein-/Ausgangsrichtung sowie Segment-Laengenkorrekturen ab. JSON beschreibt hier nur die **gewuenschte Leitlinien-Geometrie**, nicht direkte Funktionsparameter. Um Modus 3 aus JSON zu treiben, muss man zuerst die LINE/ARC-Geometrie per `entmake` erzeugen und dann eine eigene (nicht-interaktive) Nachbildung von `gf-linienzug-modus` schreiben — genau das tut `tests/test_gefaellestrecke.lsp`. JSON (Intent-Beschreibung, kein Parametersatz): ```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" } ``` --- ## 3. VarioFoerderer — `Lisp/vf_core.lsp` + `vf_standard.lsp`/`vf_etage.lsp`/`vf_linienzug.lsp` Dispatcher `c:VarioFoerderer` waehlt einen registrierten Anlagetyp (`"standard"`, `"etage"`, `"linienzug"`). Registry-Vertrag fuer scriptbare Typen: ``` berechne-fn (deltaL deltaH richtung seite) -> (best-winkel best-L_GF best-L_VF ergebnis-liste) einfuege-fn (deltaL deltaH richtung best-winkel L_GF1 L_GF2 L_VF startpunkt seite hz) -> endpunkt ``` Der Wrapper `vf-block-erstellen` fasst danach zentral alles zu einem `VF_n`-Block zusammen (kein JSON-Feld dafuer noetig — laeuft automatisch). `best-winkel`, `L_GF1`, `L_GF2`, `L_VF` stammen normalerweise aus dem Solver (`berechne-fn`). Fuer einen scriptbaren Aufruf gibt man daher die **Eingangsgroessen** (`deltaL`, `deltaH`, `richtung`, `seite`, optional `winkel`, `gf_verteilung`) sowie `startpunkt`+`hz` an; der Solver liefert die inneren Laengen/Winkel. Zwingt man `winkel`, wird dieser Kandidat statt der Auto-Wahl verwendet. ### 3.1 VarioFoerderer Standard — scriptbar - Winkel-/Laengen-Solver: `berechne-standard (deltaL deltaH richtung seite)` → wrappt `berechne-alle-winkel (deltaL deltaH richtung feste-hz)`. Kandidatenwinkel (config `vario.bogen_winkel`, Default): `3 6 9 12 15 18 21 27 33 39 45 51` Grad — **nur diese Werte sind moeglich**, kein freier Winkel. - Bau-Funktion: `variofoerderer-einfuegen (deltaL deltaH richtung best-winkel L_GF1 L_GF2 L_VF startpunkt seite hz)` | Feld | Typ | Pflicht | Bedeutung | Default | |---|---|---|---|---| | `deltaL` | real (mm) | **ja** | horizontale Gesamtlaenge | - | | `deltaH` | real (mm) | **ja** | Ziel-Hoehendifferenz (hier IST deltaH ein direkter Eingang, anders als bei Gefaellestrecke!) | - | | `richtung` | string | **ja** | `"Auf"` oder `"Ab"` | - | | `winkel` | int | nein | einer der 12 Kandidatenwinkel; ohne Angabe waehlt der Solver den **kleinsten gueltigen** | (Solver) | | `gf_verteilung` | string | nein | `"gleichmaessig"`/`"vorne"`/`"hinten"` (Modus `0`/`1`/`2`); bestimmt `L_GF1`/`L_GF2` | `"gleichmaessig"` | | `startpunkt` | [x,y,z] (mm) | **ja** | Einfuegepunkt | - | | `seite` | string | nein | `"links"`/`"rechts"` (AS/ES-Element-Seite) | `"rechts"` (Dialog) | | `hz` | real (Grad) | nein | Fahrtrichtung | 0.0 | | `motorseite` | string | nein | `"links"`/`"rechts"` — nur Dialog/Anzeige, kein Geometrie-Parameter von `variofoerderer-einfuegen` | `"links"` | Verteilungsmodi ausfuehrlich (in Produktion per Dialog): `0`=gleichmaessig (beide `L_GF/2`), `1`=alles vorne (Rest hinten), `2`=alles hinten (Rest vorne), `3`=manuell (`L_GF1` frei, `L_GF2`=Rest). Vollstaendiges JSON (alle Parameter gesetzt): ```json { "test_id": "VF_Auf_2000", "typ": "standard", "richtung": "Auf", "deltaL": 7000, "deltaH": 2000, "winkel": 27, "gf_verteilung": "gleichmaessig", "startpunkt": [0, 0, 0], "seite": "rechts", "hz": 0.0, "motorseite": "links" } ``` Minimal genuegt (Solver fuellt den Rest): `richtung`, `deltaL`, `deltaH` (+ `startpunkt`, falls nicht am Ursprung). ### 3.2 VarioFoerderer Etage — scriptbar - Solver: `berechne-etage (deltaL deltaH richtung seite)` → `berechne-winkel-etage (deltaL deltaH richtung)`. Gleiche 12 Kandidatenwinkel wie Standard. - Bau-Funktion: `etage-foerderanlage-einfuegen (deltaL deltaH richtung best-winkel L_GF1 L_GF2 L_VF startpunkt seite hz)` — **identische Signatur** wie Standard (Registry-Vertrag), Unterschied liegt im internen Aufbau (30-Grad-AS/ES-Elemente statt 90, AS/ES direkt uebereinander, zusaetzlicher fixer "Gefaellebogen" je nach `seite` gegenueber dem AS-Element). Felder: identisch zu 3.1 (`deltaL`, `deltaH`, `richtung`, `winkel`, `gf_verteilung`, `startpunkt`, `seite`, `hz`). Nur `typ` unterscheidet sich. Vollstaendiges JSON: ```json { "test_id": "VF_Etage_Auf_2000", "typ": "etage", "richtung": "Auf", "deltaL": 7000, "deltaH": 2000, "winkel": 27, "gf_verteilung": "gleichmaessig", "startpunkt": [0, 0, 0], "seite": "rechts", "hz": 0.0, "motorseite": "links" } ``` ### 3.3 VarioFoerderer Linienzug — **nicht scriptbar** Eingangsfunktion `vf-linienzug-modus ()` — **keine Parameter**, rein interaktiver Mehrschritt-Assistent (Kettenaufbau aus GF-Boegen, Vario-Kurven, VF-Einheiten, Horizontal-Stuecken). Jeder Schritt fragt per `getpoint`/`getstring`/`getint`/`getreal` einzeln ab. Es existiert **keine** einzelne Produktionsfunktion, die eine ganze Kette aus einer Liste von Segment-Beschreibungen entgegennimmt. `linienzug_tests.json` bildet die Prompt-Sequenz als Frage-Antwort-Protokoll ab (`typ`: `point_abs`/`point_rel`/`real`/`string`/`int`, jeweils mit `wert`/`dL`+`hz` und einem `kommentar`, der beschreibt, WELCHE Frage die Antwort beantwortet) — das ist die einzige Moeglichkeit, den interaktiven Ablauf nicht-interaktiv nachzubilden, kein direkter Parametersatz einer Bau-Funktion. Prompt-Reihenfolge (Kurzfassung, Details in `vf_linienzug.lsp`): 1. Startpunkt (`point_abs`), Hoehe Z (`real`) 2. AS-Element setzen? Ja/Nein, wenn ja: Winkel 90/30, Seite links/rechts 3. Schleife "naechstes Element": GF-Bogen | Neue Linie (GF) | Neue Linie (Ab/Auf VF) | Horizontal-VF | Kettenende — jeweils eigene Unter-Prompts (Endpunkt `point_rel` mit `dL`+`hz`, Ziel-Hoehe oder Neigungswinkel, Separator vor/nach, GF-Verteilung, Vario-Kurve Winkel/Seite/Variante, ...) 4. Kettenende: ES-Winkel (90/30) + ES-Seite (links/rechts) JSON (Prompt-Protokoll, Auszug): ```json [ { "typ": "point_abs", "wert": [0, 0, 5000], "kommentar": "Startpunkt + Hoehe Z" }, { "typ": "string", "wert": "90", "kommentar": "AS-Element Winkel 90/30" }, { "typ": "string", "wert": "links", "kommentar": "AS-Element Seite" }, { "typ": "point_rel", "dL": 8000, "hz": 0.0, "kommentar": "Endpunkt naechstes Linien-Segment" }, { "typ": "string", "wert": "90", "kommentar": "ES-Element Winkel" }, { "typ": "string", "wert": "links", "kommentar": "ES-Element Seite" } ] ``` Dasselbe Protokoll steht produktionsseitig als **XDATA am fertigen `VF_n`-Block** (App `SSG_VF_EDIT`, Marker `"linienzug"`, Serialisierung `vfl-entry->string`): jede Eingabe wird beim Bau journalisiert und ist damit wiederabspielbar (`vfl-journal-replay-start` + `vf-linienzug-modus`, genutzt vom Doppelklick-Editieren). Ein Journal aus einer echten Zeichnung laesst sich darum 1:1 in dieses Schema zuruecklesen — genau das tut `lib/vf_journal_export.py` (siehe Abschnitt 8, `hundm05.json`). Zwei Unterschiede zum handgeschriebenen `linienzug_tests.json`: - Zusaetzlicher Eintragstyp `step` (Glied-Marker aus `vfl-journal-mark`, Label = gewaehlter Segmenttyp). Beim Replay uebersprungen (`vfl-replay-pop`), fuer den Sektions-/Glied-Edit aber der Anker. - `hz` steht nur beim **ersten** Segment der Kette in einem `point_rel` (`vfl-in-abstand` journalisiert die gesnappte Richtung nur bei freier Richtungswahl); jedes weitere Segment erbt die Richtung des Vorgaengers und traegt nur `dL`. Winkel von AS/ES stehen als **Wert** (`"90"`/`"30"`), Seiten/Optionen als **Menue-Index** (`"1"`/`"2"`). **Headless-Riegel (Stufe 0, siehe `doc/TODO-plan-vf-interactive.md`)**: der Replay allein garantierte noch keinen prompt-freien Lauf - lief die Queue mitten im Bau leer (Datenquelle passt nicht zum Ablauf), fielen die `vfl-in-*`-Wrapper **stillschweigend** auf Live-Eingabe zurueck und der Lauf blieb an einem `getpoint` haengen. Mit `*vfl-headless*` = T ist das jetzt ein harter Abbruch mit Fundstelle (`vfl-headless-abbruch` -> `*vfl-headless-fehler*` nennt Art der Eingabe, Glied-Nummer und Eingabe-Nummer). Die Meldungen der geometrisch abgewiesenen Sektionen laufen ueber `vfl-meldung` statt `alert` und stehen in `*vfl-meldungen*`, blockieren also nichts mehr. `tests/test_hundm05.lsp` schaltet beides ein; die Diagnose landet im Ergebnis-JSON (`headless_fehler`, `meldungen`, `prompts`). ### 3.4 Vario_Kette_Merge — kein Neubau `c:Vario_Kette_Merge`: benoetigt nur **einen** Start-Entity-Pick (`entsel`), sucht danach selbst alle KS_EIN/KS_AUS-kompatiblen Nachbarbloecke in der ganzen Zeichnung ab (`vfl-kette-verfolgen`, Toleranzen 2mm/100mm) und fasst sie zu einem neuen `VF_n` zusammen. Kein JSON-Parametersatz noetig/moeglich — reine Nachbearbeitung bereits vorhandener Geometrie. --- ## 4. Omniflo — `Lisp/OmniModulInsert.lsp` ### 4.1 Omniflo Boegen/Weichen Kern-Funktion: `omni:insert-dxf (sivasnr-str hoehe drehung)` | Feld | Typ | Pflicht | Bedeutung | Default | |---|---|---|---|---| | `sivasnr` | string | **ja** | Komponenten-ID; **ist gleichzeitig der Dateiname** `.dxf` unter `%DXFM_OMNIFLO%` | - | | `hoehe` | string/real (mm) | nein | **absolute** Z-Koordinate + `HOEHE`-Attribut | config `omniflo.default_hoehe` bzw. hart "2000" | | `drehung` | real (Grad) | nein | feste Rotation | in `c:OMNI_*` interaktiv per Maus | | `x`, `y`, `z` | real (mm) | (Wrapper) | Einfuegepunkt — **kein** Parameter von `omni:insert-dxf`; muss ueber einen eigenen `_.INSERT`-Aufruf gesetzt werden | - | **Wichtig**: Alle echten `c:OMNI_APB_*`/`c:OMNI_W*_*`-Befehle rufen `omni:insert-dxf` **ohne X/Y** auf — der Einfuegepunkt kommt in `_.INSERT` immer per `pause` (Mausklick). Es gibt in Produktion **keinen** Aufrufpfad, der X/Y programmatisch uebergibt; `omni:insert-dxf` selbst akzeptiert keinen Punkt-Parameter. Die Felder `x`/`y`/`z` beschreiben also nur, wo das Element stehen soll — ein JSON-Treiber muss den `_.INSERT` selbst nachbauen (wie `mubea:build-separator` in `tests/test_mubea.lsp`). Sivasnr-Herkunft: `data/json/omniflo_boegen.json` und `data/json/omniflo_weichen.json` (dienen der Dialog-Filterung/Anzeige, nicht der Namensaufloesung — die ist `sivasnr == dateiname`). Vollstaendiges JSON (alle fachlich noetigen Werte inkl. Position): ```json { "id": "821104025", "type": "bogen", "sivasnr": "821104025", "x": 219.4, "y": 223.06, "z": 2000, "hoehe": 2000, "drehung": 0.0 } ``` `description`/`row` sind reine Dokumentations-/Gruppierungsfelder, keine Produktionsparameter. ### 4.2 Omniflo Aluprofil-Gerade (AP60/AP110/APG110) Kern-Funktion: `omni:make-gerade (blockname pt pt2 srcAttribs textHeight textGap textEnt)` | Feld | Typ | Pflicht | Bedeutung | Default | |---|---|---|---|---| | `blockname` | string | **ja** | `"AP60"` / `"AP110"` / `"APG110"` | - | | `pt` | [x,y,z] | **ja** | Startpunkt (absolute Weltkoordinaten) | - | | `pt2` | [x,y,z] | **ja** | Endpunkt; Laenge/Rotation aus Differenz `pt→pt2` berechnet | - | | `textHeight` | real | nein | Textgroesse | config `omniflo.text_height` (100.0) | | `textGap` | real | nein | Textabstand | config `omniflo.text_gap` (20.0) | `srcAttribs` wird aus der Vorlagen-DWG gelesen (`omni:read-src-attribs blockname`); `textEnt` ist ein vorhandener Text-Entity zum Aktualisieren (bei Neuaufbau `nil`). Beide sind keine JSON-Datenfelder. Der oeffentliche Befehl `omni:insert-block (blockname laengemax)` fragt `pt`/`pt2` interaktiv per `getpoint` ab — `omni:make-gerade` selbst ist mit festen Punkten aufrufbar. Vollstaendiges JSON: ```json { "type": "gerade", "blockname": "AP110", "pt": [2000.0, 0.0, 2000.0], "pt2": [3000.0, 0.0, 2000.0], "textHeight": 100.0, "textGap": 20.0 } ``` (In `omniflo_strecke_tests.json` wird die Gerade als `profil`/`x`/`y`/ `x_ende`/`y_ende`/`hoehe` beschrieben — dieselben Punkte, nur andere Feldnamen fuer den Reihen-Layout-Test.) ### 4.3 Omniflo TEF/Zubehoer Stopper, Scanner, Buerstenbremse, Umlenk-/Antriebsstationen: `c:OMNI_STOPPER`/`c:OMNI_SCANNER`/... rufen `omni:insert-dxf` mit **hartcodiertem** `sivasnr-str` (z.B. `"827062022"`) und `hoehe=nil, drehung=nil` auf — Punkt weiterhin interaktiv. Kein Nutzer-JSON-Feld fuer `sivasnr` noetig (fest im Code); Position/Hoehe/Rotation waeren grundsaetzlich scriptbar (wie 4.1), aber nicht ueber die vorhandenen `c:OMNI_*`-Befehle. JSON identisch strukturiert wie 4.1 (mit `sivasnr` fest je Zubehoer-Typ). ### 4.4 Omniflo TV_* / APBW_* Transferwagen-Verbinder (`c:OMNI_TV_*`) und Boegen fuer Weichen (`c:OMNI_APBW_*`): **nicht implementiert** — reine `[DUMMY]`-Platzhalter ohne Bau-Logik, kein Parametersatz. `c:OMNI_UPDATE_ATTRIBS` (in `Lisp/export.lsp`) aktualisiert `HOEHE`/`DREHUNG` aller Omniflo-Elemente aus der Zeichnung — kein Bau-/Einfuege-Befehl, daher kein JSON-Parametersatz. --- ## 5. Separator/Scanner/Sensor — `Lisp/SSG_LIB_Commands.lsp` Funktion: `ils-insert-sensor (fname)` | Feld | Typ | Pflicht | Bedeutung | Default | |---|---|---|---|---| | `block` / `fname` | string | **ja** | Blockdateiname ohne Erweiterung, z.B. `"Separator_SP"`, `"Scanner"`, `"S-LP"` — aufgeloest ueber `ssg-ils-block-datei` (2D/3D-Suffix + Fallback) | - | | `x`, `y`, `z` | real (mm) | (Wrapper) | Einfuegepunkt — **kein** Parameter von `ils-insert-sensor` | - | | `rotation` | real (Grad) | (Wrapper) | Rotation — **kein** Parameter (kommt per `pause`) | - | **Nicht scriptbar**: die Funktion fragt Einfuegepunkte in einer Schleife per `getpoint` ab (`_.INSERT pfad pt "" "" pause` — auch die Rotation kommt per Maus/`pause`), bis der Nutzer mit ENTER/ESC abbricht. Es gibt keinen Parameter fuer Punkt, Anzahl oder Rotation. Ein JSON-Treiber muss den `_.INSERT`-Aufruf direkt selbst nachbilden (Blockdatei-Pfad ueber `ssg-ils-block-datei` bzw. `ssg-ils-block-laden`, dann `_.INSERT pfad pt "" "" rot` mit festem `rot` statt `pause`) — der Ansatz von `mubea:build-separator` in `tests/test_mubea.lsp`. Fachlich noetig sind nur Blockname + Punkt + Rotation. Vollstaendiges JSON (wie in `mubea.json`s Separator-Eintraegen): ```json { "function": "insert", "block": "S-LP", "x": 10310, "y": -14158, "z": 1467, "rotation": 270 } ``` --- ## 6. Waagrechte Foerderstrecke (`gerade`) Produktionsfunktion: `insert-hz-scaled-block-by-ks (blockname startpunkt laenge hz-winkel vert-winkel)` (`Lisp/vf_etage.lsp`) — dieselbe Primitive, mit der der Etage-Foerderer seine waagrechten Abschnitte baut. Skaliert `Staustrecke_SP_1000_mm` auf `laenge` (Massstab = `laenge / 1000`) und richtet sie ueber `KS_EIN`/`KS_AUS` am `startpunkt` aus. Dieser Typ entstand beim Nachbau von `results/ST500592_05.dxf`: ein Antriebs-/Umlenkstation-Paar **ohne** Hoehendifferenz ist fachlich kein VarioFoerderer. Dessen Solver (`berechne-standard` → `berechne-alle-winkel`) muss einen Bogenwinkel aus der Kandidatenliste 3…51 Grad waehlen, was bei `deltaH ≈ 0` nicht aufgeht — der Eintrag waere unbaubar. `lib/dxf_scan_components.py` trennt solche Paare darum ueber `GERADE_MAX_DELTA_H` (10 mm) ab und gibt sie als `gerade` aus. | Feld | Typ | Pflicht | Bedeutung | Default | |---|---|---|---|---| | `test_id` | string | **ja** | Kennung, Praefix `GERADE_` (steuert den Dispatcher) | - | | `typ` | string | nein | `"gerade"` (Dokumentation, nicht ausgewertet) | - | | `laenge` | real (mm) | **ja** | horizontale Laenge; Massstab der Staustrecke | - | | `hz` | real (Grad) | nein | Fahrtrichtung in der XY-Ebene | 0.0 | | `startpunkt` | [x,y,z] (mm) | **ja** | Anfangspunkt (`KS_EIN` wird hierhin gelegt) | - | | `endpunkt` | [x,y,z] (mm) | nein | Endpunkt — reine Dokumentation/Herkunft, **kein** Parameter; die Geometrie folgt aus `startpunkt` + `laenge` + `hz` | - | `vert-winkel` wird vom Treiber fest auf `0` gesetzt (waagrecht). Die Funktion gibt den **Ausgangspunkt** zurueck, nicht die Entity — ein Treiber holt den Block ueber `(entlast)`. Bei fehlender Blockdatei ruft sie `(exit)` auf und wuerde einen Testlauf abbrechen, darum vorher mit `ssg-ils-block-laden` pruefen. Vollstaendiges JSON: ```json { "test_id": "GERADE_HundM05_08", "typ": "gerade", "laenge": 3305.26, "hz": 143.35, "startpunkt": [223150.89, 152953.65, 0.0], "endpunkt": [220499.01, 154926.53, 0.0] } ``` Einen Treiber dafuer gibt es derzeit nicht mehr: `tests/test_hundm05.lsp` spielt seit dem Umbau auf die Linienzug-Journale nur noch Ketten ab (siehe Abschnitt 8); der fruehere `hundm05:build-gerade` steht in der Git-Historie. --- ## 7. Zusammenfassung: scriptbar vs. nicht scriptbar | Element | Produktionsfunktion | Scriptbar? | |---|---|---| | Kreisel Insert | `kreisel-insert-script` | ja | | Kreisel Connect | `kreisel-connect-script` | ja | | Gefaellestrecke Modus 1 | `gf-modus12-abschluss` → `gefaellestrecke-einfuegen` | ja | | Gefaellestrecke Modus 2 | `gf-modus12-abschluss` (deltaL/hz aus 2 Punkten) | ja | | Gefaellestrecke Modus 3 | `gf-linienzug-modus` | **nein** (erfordert vorgezeichnete LINE/ARC-Auswahl) | | VarioFoerderer Standard | `variofoerderer-einfuegen` | ja | | VarioFoerderer Etage | `etage-foerderanlage-einfuegen` | ja | | VarioFoerderer Linienzug | `vf-linienzug-modus` | **nein** (mehrstufiger interaktiver Assistent) | | Vario_Kette_Merge | `vfl-kette-verfolgen` | n/a (Re-Packaging, kein Parametersatz) | | Omniflo Bogen/Weiche | `omni:insert-dxf` (Kern) | Kern ja, **Produktionsbefehle nein** (X/Y immer Maus) | | Omniflo Aluprofil-Gerade | `omni:make-gerade` (Kern) | Kern ja, Befehl `omni:insert-block` fragt Punkte interaktiv | | Omniflo TEF/Zubehoer | `omni:insert-dxf` (fester Sivasnr) | Position/Hoehe/Rotation ja, Punkt-Eingabe interaktiv | | Omniflo TV_*/APBW_* | - | **nein** (nicht implementiert, DUMMY) | | Separator/Scanner | `ils-insert-sensor` | **nein** (Schleife mit `getpoint`/`pause`) | ## 8. HundM05 — die 5 Linienzuege am Kreisel (`hundm05.json`) `tests/testdata/hundm05.json` enthaelt die **Eingabe-Journale der 5 VarioFoerderer-Linienzuege** aus `data/polylines.dxf` (die Anlage HundM, ein Kreisel mit fuenf daran haengenden Ketten). Quelle ist nicht die gezeichnete Geometrie, sondern die XDATA `SSG_VF_EDIT` (Marker `"linienzug"`) an den `VF_n`-Bloecken — also genau die Werte, mit denen die Ketten gebaut wurden. Schema je Eintrag: Kopffelder (`test_id`, `block`, `dxf_handle`, `startpunkt_mm`, `rotation_grad`, `sivas_id`, `beschreibung`, `erwartung_hinweis`, `quelle`) plus die Prompt-Sequenz in `eingaben` — Format wie `linienzug_tests.json`, siehe Abschnitt 3.3 (inkl. `step`-Marker). | test_id | Block | Sivas | Startpunkt (mm) | Kette | |---|---|---|---|---| | VF_hundm05_LZ_01 | VF_87 | 0025 | 4912 / 1231 / 2154 | der komplizierte links: 2x Horizontal-VF, 4 Vario-Kurven (90/90/30/30) | | VF_hundm05_LZ_02 | VF_46 | 0019 | 8572 / 1276 / 2495 | Standardkette | | VF_hundm05_LZ_03 | VF_46 | 0018 | 12916 / 1276 / 2495 | Standardkette | | VF_hundm05_LZ_04 | VF_46 | 0020 | 17202 / 1205 / 2154 | Standardkette (die zuerst gebaute) | | VF_hundm05_LZ_05 | VF_46 | 0008 | 21490 / 1196 / 2495 | Standardkette | Die vier `VF_46`-Bloecke sind **Kopien EINER** gebauten Kette: sie referenzieren dieselbe Blockdefinition und tragen darum Wort fuer Wort dasselbe Journal, inklusive des Startpunkts der zuerst gebauten Kette (17201.78/1205.36/2100 mit Starthoehe 2154). Im JSON ist dieser eine absolute Eintrag je Instanz auf ihren echten Blockeinfuegepunkt gezogen (alle weiteren Eintraege sind relativ), damit jeder Testfall fuer sich abspielbar ist. Achtung bei den Kopien: ihre Block-Attribute (`HOEHE_VON_mm` = 2154, so auch im Sivas-Export) stammen aus dem Original und sind nach dem Verschieben veraltet — die drei Kopien liegen tatsaechlich 340.93 mm hoeher (belegt durch ihre Separator-Z-Werte im Sivas-Export: 2575.43 statt 2234.50). Neu erzeugen: ``` python lib/vf_journal_export.py data/polylines.dxf \ tests/testdata/hundm05.json --csv results/HundM_export.csv ``` Die frueheren Inhalte (Kreisel/Gefaellestrecke/Sensor-Eintraege aus dem Fenster 215000/148000..250000/161000 von `results/ST500592_05.dxf`, erzeugt mit `lib/dxf_scan_components.py`) stehen in der Git-Historie dieser Datei. **Aufbau flach, nicht verschachtelt**: `ssg-load-json`/`ssg-parse-json-array` (`Lisp/ssg_core.lsp`) liest die Datei ZEILENWEISE und behandelt jedes `{` als Objektanfang — Verschachtelung (z.B. `"eingaben": [ {...} ]`) wuerde die Kopf-Objekte verschlucken. Darum steht je Kette ein Kopf-Objekt (traegt `test_id`) und danach ihre Eingaben (tragen `typ`), wie in `linienzug_tests.json`. Der Anlagetyp im Kopf heisst `anlagetyp` und NICHT `typ`, damit Kopf und Eingabe unterscheidbar bleiben. Abgespielt wird das per `tests/test_hundm05.lsp` (`TEST_HUNDM05`, `TEST_HUNDM05_2D`/`_3D`): der Treiber gruppiert die Objekte (`hundm05:gruppiere`), baut daraus das Vorwaerts-Journal (`hundm05:eintrag->journal`) und spielt es je Kette ueber `vfl-journal-replay-start` + `vf-linienzug-modus` ab — denselben Weg nimmt die Produktion beim Editieren/Konvertieren (`vfl-konvertiere-ent`). Bewusst KEINE Eingabe-Mocks wie in `test_linienzug.lsp`: ein Mock muesste zu jedem Segment die Fahrtrichtung kennen, die im Journal nur beim ersten Segment steht.