Files
dxfmakros/tests/testdata/object_data.md
T
s.ayadi 2dd5382060 [FEAT] Weitere Objekte der Anlage in hundm05.json bauen (Kreisel)
tests/testdata/hundm05.json enthaelt jetzt neben den 5 Spec-Ketten auch
andere Objekte der Anlage - erkennbar am Feld "function", im Format der
jeweiligen Einzel-Testdaten (ein Kreisel also wie in kreisel_tests.json).
Damit beschreibt EINE Datei die ganze Anlage.

- tests/test_hundm05.lsp: hundm05:bau-zusatzobjekte sammelt alle Objekte mit
  "function" und baut sie; hundm05:bau-kreisel ruft kreisel-insert-script
  (wie tests/test_kreisel.lsp) und liefert einen Ergebnis-Record in derselben
  Form wie die Ketten. Eigener ssg-start-Rahmen mit ATTREQ/ATTDIA 0, weil
  vsp-bau-datei seinen schon geschlossen hat und (command "_.INSERT" ...)
  sonst nach Attributwerten fragt. Je Objekt gefangen, damit ein Fehler die
  restlichen nicht mitnimmt.
- Lisp/vf_spec.lsp: "kind" ist jetzt ein Record-Feld (Default "linienzug")
  statt eines Literals in vsp-result-json - so schreibt vsp-results-schreiben
  beide Objektarten in EINE Ergebnisdatei.
- lib/vf_spec_export.py: Objekte der Ziel-Datei, die keine Kette beschreiben,
  werden beim Neuschreiben unveraendert ans Dateiende uebernommen. Ohne das
  waere jeder von Hand ergaenzte Eintrag beim naechsten Generatorlauf weg -
  die Datei ist erzeugt UND handgepflegt. spec_aus_flachen_objekten
  ueberspringt sie jetzt statt zu werfen, genau wie vsp-gruppieren in LISP.
- tests/test_hundm05.py: die Ketten-Tests filtern auf kind "linienzug"
  (Records ohne das Feld stammen aus aelteren Laeufen und waren nur Ketten);
  neue Klasse TestZusatzobjekte prueft Status, expect_block_prefix,
  expect_hoehe (Attribut HOEHE), expect_kreiselart (KREISELART) und den
  Einfuegepunkt - dieselben Erwartungsfelder wie tests/test_kreisel.py.
- tests/test_vf_spec.py: neuer Test, dass die Zusatzobjekte die
  Ketten-Zerlegung nicht beeinflussen - auch nicht, wenn sie mitten zwischen
  den Sektionen stehen (der eingetragene Kreisel stand zunaechst inmitten
  der Sub-Knoten von Kette 5; nach dem Regenerieren steht er am Dateiende).

Verifiziert: 118 pytest-Tests gruen, Spec-Regenerierung uebernimmt das
Zusatzobjekt (58 Objekte), beide .lsp lint-sauber. Die Kreisel-Tests warten
auf den naechsten TEST_HUNDM05-Lauf.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 14:49:46 +02:00

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/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
1.2 Kreisel Connect kreisel-connect-script (ptStart ptEnd typ hoehe) ja 2 Punkte start_*/end_* #12
2.1 Gefaellestrecke Modus 1 gf-modus12-abschlussgefaellestrecke-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 _.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 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:

  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):

{
  "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):

{
  "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:

{
  "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):

[
  { "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 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_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-standardberechne-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-abschlussgefaellestrecke-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).

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.