9 reale Separator/Scanner-Koordinaten aus der Anlage in hundm05.json ergaenzt. hundm05:bau-zusatzobjekte (test_hundm05.lsp) baute bisher nur Kreisel-Zusatzobjekte - neue hundm05:bau-sensor-Funktion (analog zu mubea:build-separator-one in test_mubea.lsp) faengt jetzt auch "block"-Eintraege (Separator/Scanner) ab und fuegt sie per _.INSERT ein. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
35 KiB
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/getstringnoetig). Diese Funktionen sind 1:1 aus JSON aufrufbar. - Nicht scriptbar = die Produktions-Funktion fragt Punkte/Optionen
interaktiv ab (
getpoint,getstring,ssgetauf bereits gezeichnete Geometrie). Es gibt inLisp/keinen parametrischen Einstiegspunkt; ein JSON-Testfall kann diese Faelle nur ueber einen eigens geschriebenen Wrapper (wie estests/*.lsptut) 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 |
| 1.2 | Kreisel Connect | kreisel-connect-script (ptStart ptEnd typ hoehe) |
ja | 2 Punkte start_*/end_* |
#12 |
| 2.1 | Gefaellestrecke Modus 1 | gf-modus12-abschluss → gefaellestrecke-einfuegen |
ja | 1 Punkt start_mm |
#21 |
| 2.2 | Gefaellestrecke Modus 2 | gf-modus12-abschluss (deltaL/hz aus 2 Punkten) |
ja | 3 Punkte (linie_*, start_mm) |
#22 |
| 2.3 | Gefaellestrecke Modus 3 | gf-linienzug-modus () |
nein | vorgezeichnete LINE/ARC-Auswahl | #23 |
| 3.1 | VarioFoerderer Standard | variofoerderer-einfuegen (…) |
ja | 1 Punkt startpunkt |
#31 |
| 3.2 | VarioFoerderer Etage | etage-foerderanlage-einfuegen (…) |
ja | 1 Punkt startpunkt |
#32 |
| 3.3 | VarioFoerderer Linienzug | vf-linienzug-modus () |
nein | interaktiver Assistent | #33 |
| 3.4 | Vario_Kette_Merge | vfl-kette-verfolgen (Re-Packaging) |
n/a | 1 Entity-Pick | #34 |
| 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 |
| 4.2 | Omniflo Aluprofil-Gerade | omni:make-gerade (blockname pt pt2 …) |
Kern ja | 2 Punkte pt/pt2 |
#42 |
| 4.3 | Omniflo TEF/Zubehoer | omni:insert-dxf (fester Sivasnr) |
Position ja, Punkt interaktiv | 1 Punkt (nur via Wrapper) | #43 |
| 4.4 | Omniflo TV_/APBW_ | — (DUMMY) | nein | — | #44 |
| 5 | Separator/Scanner/Sensor | ils-insert-sensor (fname) + _.INSERT |
nein (Schleife) | 1 Punkt x,y,z (nur via Wrapper) |
#5 |
| 6 | Waagrechte Foerderstrecke (gerade) |
insert-hz-scaled-block-by-ks (blockname startpunkt laenge hz vert) |
ja | 1 Punkt startpunkt (+ endpunkt als Doku) |
#6 |
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_.INSERTselbst 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 pts 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):
{
"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):
{
"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):
{
"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:
{
"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:
(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.getpointfuer Startpunkt (Kettenanfang, Z = obere Anschlusshoehe) und Endpunkt-Referenz (Z = untere Anschlusshoehe);deltaHwird aus der Differenz der beiden Z-Werte gebildet.getstring-Abfragen fuer AS-/ES-Winkel ("90"/"30") und -Seite ("links"/"rechts") — inhaltlich identisch zu Modus 1/2.- 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):
{
"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)→ wrapptberechne-alle-winkel (deltaL deltaH richtung feste-hz). Kandidatenwinkel (configvario.bogen_winkel, Default):3 6 9 12 15 18 21 27 33 39 45 51Grad — 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):
{
"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 nachseitegegenueber dem AS-Element).
Felder: identisch zu 3.1 (deltaL, deltaH, richtung, winkel,
gf_verteilung, startpunkt, seite, hz). Nur typ unterscheidet sich.
Vollstaendiges 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):
- Startpunkt (
point_abs), Hoehe Z (real) - AS-Element setzen? Ja/Nein, wenn ja: Winkel 90/30, Seite links/rechts
- Schleife "naechstes Element": GF-Bogen | Neue Linie (GF) | Neue Linie
(Ab/Auf VF) | Horizontal-VF | Kettenende — jeweils eigene Unter-Prompts
(Endpunkt
point_relmitdL+hz, Ziel-Hoehe oder Neigungswinkel, Separator vor/nach, GF-Verteilung, Vario-Kurve Winkel/Seite/Variante, ...) - Kettenende: ES-Winkel (90/30) + ES-Seite (links/rechts)
JSON (Prompt-Protokoll, Auszug):
[
{ "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, hm_recformat.json). Zwei
Unterschiede zum handgeschriebenen linienzug_tests.json:
- Zusaetzlicher Eintragstyp
step(Glied-Marker ausvfl-journal-mark, Label = gewaehlter Segmenttyp). Beim Replay uebersprungen (vfl-replay-pop), fuer den Sektions-/Glied-Edit aber der Anker. hzsteht nur beim ersten Segment der Kette in einempoint_rel(vfl-in-abstandjournalisiert die gesnappte Richtung nur bei freier Richtungswahl); jedes weitere Segment erbt die Richtung des Vorgaengers und traegt nurdL. 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_hm_recformat.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 <sivasnr>.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):
{
"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:
{
"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.jsons Separator-Eintraegen):
{
"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:
{
"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_hm_recformat.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
Zwei Dateien beschreiben dieselben 5 Ketten, in zwei Formaten:
| Datei | Inhalt | Test |
|---|---|---|
tests/testdata/hm_recformat.json |
die Aufzeichnung: rohes Eingabe-Journal aus der XDATA der Kundenzeichnung, als Frage-Antwort-Protokoll | TEST_HM_RECFORMAT (in alltests.json abgeschaltet) |
tests/testdata/hundm05.json |
die daraus abgeleitete Spec (Domaenenwerte statt Menue-Codes, 52 statt 281 Objekte) - hier werden weitere Teile der Anlage nachgetragen | TEST_HUNDM05 |
In hundm05.json stehen neben den Ketten auch andere Objekte der Anlage, erkennbar am Feld function - Format wie in den jeweiligen Einzel-Testdaten (ein Kreisel also wie in kreisel_tests.json, Schema in Abschnitt 1). Damit beschreibt EINE Datei die ganze Anlage. Der Ketten-Uebersetzer ueberspringt diese Objekte (vsp-gruppieren), hundm05:bau-zusatzobjekte in tests/test_hundm05.lsp baut sie, und lib/vf_spec_export.py uebernimmt sie beim Neuerzeugen der Spec-Daten. Im Ergebnis-JSON unterscheidet das Feld kind die Objektarten (linienzug / kreisel / sensor). sensor sind Separator/Scanner-Eintraege (erkennbar an block, Schema wie Abschnitt 5/mubea.json) - hundm05:bau-sensor baut sie per direktem _.INSERT nach, analog zu mubea:build-separator-one in tests/test_mubea.lsp (ils-insert-sensor selbst ist nicht scriptbar).
Die Spec ist das Eingabeformat, die Aufzeichnung die Vorlage, an der sie gemessen wird (Rundlauf-Beweis in tests/test_vf_spec.py). Warum beide bleiben: die Aufzeichnung ist die einzige eingecheckte Kopie der Originaldaten (data/polylines.dxf ist zu gross fuer das Repo) UND der einzige Test dafuer, dass ein BESTEHENDER VF_n-Block weiter abspielbar ist - also dass Doppelklick-Edit und 2D/3D-Konvertierung an Altbestand funktionieren. Gebaut wird routinemaessig nur noch aus der Spec, damit nicht zweimal dieselbe Geometrie entsteht.
tests/testdata/hm_recformat.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/hm_recformat.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_hm_recformat.lsp (TEST_HM_RECFORMAT,
TEST_HUNDM05_2D/_3D): der Treiber gruppiert die Objekte
(hmrec:gruppiere), baut daraus das Vorwaerts-Journal
(hmrec: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.