From a8fac86754e92b41815c399de47bc72257e8767a Mon Sep 17 00:00:00 2001 From: Michael Stangl Date: Mon, 27 Jul 2026 16:54:04 +0200 Subject: [PATCH] Mubeat Test hinzu --- tests/test_mubea.lsp | 198 +++++++++++++---- tests/test_mubea.py | 8 +- tests/testdata/mubea.json | 236 +------------------- tests/testdata/object_data.md | 391 ++++++++++++++++++++++++++++++++++ 4 files changed, 554 insertions(+), 279 deletions(-) create mode 100644 tests/testdata/object_data.md diff --git a/tests/test_mubea.lsp b/tests/test_mubea.lsp index 04e68a6..c5371e9 100644 --- a/tests/test_mubea.lsp +++ b/tests/test_mubea.lsp @@ -153,52 +153,147 @@ ) -;; --- Gefaellestrecke (GF_*, Modus 1) --- -;; Wie test_gefaellestrecke: Element-Winkel 3 Grad, L_stau berechnet. -;; deltaL wird NICHT mehr fest auf 10000 gesetzt, sondern aus der realen -;; 2D-Distanz zwischen start_mm und ende_mm (aus der DXF abgeleitet) berechnet - -;; sonst haetten (wie im ersten TEST_MUBEA-Lauf beobachtet) alle 20 Strecken -;; die gleiche Default-Laenge statt ihrer tatsaechlichen Laenge. -;; Aus JSON: start_mm, ende_mm, hz_grad, as_seite, es_seite, as_winkel, es_winkel. -(defun mubea:build-gf (e / tid start-mm ende-mm hz as-seite es-seite as-winkel es-winkel - test-deltaL test-winkel test-rad sep-x test-L_stau ent - dx dy) - (setq tid (ssg-val e "test_id") - start-mm (mapcar 'float (ssg-val e "start_mm")) - ende-mm (ssg-val e "ende_mm") - hz (float (ssg-val e "hz_grad")) - as-seite (ssg-val e "as_seite") - es-seite (ssg-val e "es_seite") - as-winkel (ssg-val e "as_winkel") - es-winkel (ssg-val e "es_winkel")) +;; --- Gefaellestrecke: Init-Konflikt umgehen ----------------------------- +;; gefaellestrecke-einfuegen ruft "(if (not *lib-initialized*) (gf-init- +;; bibliothek))" auf (Gefaellestrecke.lsp:1567) - dasselbe Flag, das +;; init-bibliothek (Vario, vf_standard.lsp) fuer SEINE EIGENEN aus-dx/ein-dx/ +;; aus-dz/ein-dz setzt. Da C:TEST_MUBEA zuerst den Vario baut (init- +;; bibliothek setzt *lib-initialized*=T), wuerde gf-init-bibliothek NIE mehr +;; laufen und die Gefaellestrecke mit den (voellig andersartigen) Vario- +;; Bogenmassen statt den echten AS_Element/ES_Element-Massen rechnen - genau +;; das fuehrte zur falschen Zielhoehe im ersten TEST_MUBEA-Lauf. Fix: Flag +;; kurz zuruecksetzen, gf-init-bibliothek EINMAL erzwingen (danach haben +;; aus-dx/ein-dx/aus-dz/ein-dz die korrekten GF-spezifischen Werte). +(defun mubea:ensure-gf-init ( / ) + (if (not *mubea-gf-init-done*) + (progn + (setq *lib-initialized* nil) + (gf-init-bibliothek) + (setq *lib-initialized* t) + (setq *mubea-gf-init-done* T) + ) + ) +) + +;; --- Gefaellestrecke: L_stau <-> Ziel-Hoehendifferenz kalibrieren --------- +;; Bei festem Element-Winkel (3 Grad) ist deltaH linear in L_stau (nur die +;; Staustrecke ist skalierbar). Statt die Formel aus test_gefaellestrecke.lsp +;; zu uebernehmen (die L_stau aus einer ZIEL-LAENGE ableitet, nicht aus einer +;; ZIEL-HOEHE - beides ist bei festem Winkel nicht unabhaengig waehlbar), +;; wird die reale Steigung/Achsenabschnitt per zwei Probe-Bauten gemessen +;; (gefaellestrecke-einfuegen liefert deltaH exakt zurueck) und die Probe- +;; Inserts danach wieder geloescht. Rueckgabe: (steigung . achsenabschnitt). +(defun mubea:gf-calibrate (as-seite es-seite as-winkel es-winkel hz / + probe-pt l1 l2 e1 e2 dz1 dz2 steigung achsenabschnitt) + (mubea:ensure-gf-init) + (setq probe-pt (list 0.0 -500000.0 0.0)) ;; Schrott-Punkt, wird geloescht + (setq l1 3000.0 l2 8000.0) + (setq e1 (gefaellestrecke-einfuegen l1 3.0 probe-pt as-seite es-seite hz + l1 as-winkel es-winkel "0" "Schoenenberger Geruest")) + (setq e2 (gefaellestrecke-einfuegen l2 3.0 probe-pt as-seite es-seite hz + l2 as-winkel es-winkel "0" "Schoenenberger Geruest")) + (setq dz1 (cadr e1) dz2 (cadr e2)) + (setq steigung (/ (- dz2 dz1) (- l2 l1))) + (setq achsenabschnitt (- dz1 (* steigung l1))) + ;; Probe-Bloecke wieder entfernen (nur das INSERT - Blockdefinition bleibt + ;; ungenutzt in der Blocktabelle, erscheint nicht im Modelspace/Zaehlungen) + (if (car e1) (entdel (car e1))) + (if (car e2) (entdel (car e2))) + (princ (strcat "\n [GF-Kalibrierung] L_stau=" (rtos l1 2 0) "->dz=" (rtos dz1 2 1) + " L_stau=" (rtos l2 2 0) "->dz=" (rtos dz2 2 1) + " Steigung=" (rtos steigung 2 6) " Achsenabschnitt=" (rtos achsenabschnitt 2 2))) + (cons steigung achsenabschnitt) +) + + +;; --- Eine einzelne Gefaellestrecke bauen (Modus 1) --- +;; L_stau wird aus der ZIEL-HOEHENDIFFERENZ (start_mm.z - ende_mm.z) ueber die +;; kalibrierte lineare Beziehung berechnet (siehe mubea:gf-calibrate) - NICHT +;; aus der XY-Distanz (bei festem 3-Grad-Winkel legen Laenge und +;; Hoehendifferenz sich gegenseitig fest, beides gleichzeitig aus den realen +;; Koordinaten zu uebernehmen ist geometrisch inkonsistent). +;; Rueckgabe: Ergebnis-JSON-String. +(defun mubea:build-gf-one (tid start-mm ende-mm hz as-seite es-seite as-winkel es-winkel / + ziel-dz kalib steigung achsenabschnitt test-L_stau + real-deltaL dx dy ent) + ;; Kalibrierung einmalig pro (as-seite/es-seite/as-winkel/es-winkel/hz)- + ;; Kombination; fuer alle Kopien mit denselben Parametern wiederverwendet. + (if (null *mubea-gf-kalib-cache*) (setq *mubea-gf-kalib-cache* '())) + (setq kalib (cdr (assoc (list as-seite es-seite as-winkel es-winkel hz) *mubea-gf-kalib-cache*))) + (if (null kalib) + (progn + (setq kalib (mubea:gf-calibrate as-seite es-seite as-winkel es-winkel hz)) + (setq *mubea-gf-kalib-cache* + (cons (cons (list as-seite es-seite as-winkel es-winkel hz) kalib) *mubea-gf-kalib-cache*)) + ) + ) + (setq steigung (car kalib) achsenabschnitt (cdr kalib)) + + (setq ziel-dz (- (caddr start-mm) (caddr ende-mm))) + (setq test-L_stau (/ (- ziel-dz achsenabschnitt) steigung)) + + ;; Nur zu Log-/Attributzwecken: reale XY-Distanz start_mm->ende_mm + (setq dx (- (car ende-mm) (car start-mm)) + dy (- (cadr ende-mm) (cadr start-mm))) + (setq real-deltaL (sqrt (+ (* dx dx) (* dy dy)))) + + (princ (strcat "\n [GF] " tid " -> Start=(" (rtos (car start-mm) 2 0) + "," (rtos (cadr start-mm) 2 0) "," (rtos (caddr start-mm) 2 0) + ") hz=" (rtos hz 2 0) " Ziel-dz=" (rtos ziel-dz 2 1) + " L_stau=" (rtos test-L_stau 2 1))) + (setq ent (car (gefaellestrecke-einfuegen + test-L_stau 3.0 start-mm as-seite es-seite hz + real-deltaL as-winkel es-winkel "0" "Schoenenberger Geruest"))) + (mubea:ent-json tid "gefaellestrecke" ent) +) + +;; --- Gefaellestrecke(n) aus einem Template-Eintrag bauen --- +;; Ein einziger JSON-Eintrag beschreibt per "anzahl" + "delta_y"/"delta_x" eine +;; ganze Reihe identischer Gefaellestrecken (statt 20 Einzeleintraege). Element i +;; (1..anzahl) wird um (i-1)*delta in Y bzw. X versetzt (start_mm UND ende_mm +;; gemeinsam). Fehlen anzahl/delta -> genau eine Strecke am angegebenen Ort. +;; Aus JSON: start_mm, ende_mm, hz_grad, as_seite, es_seite, as_winkel, +;; es_winkel, anzahl (Default 1), delta_y (Default 0), delta_x (Default 0). +;; Rueckgabe: LISTE von Ergebnis-JSON-Strings (eine je Kopie). +(defun mubea:build-gf (e / tid0 start0 ende0 hz as-seite es-seite as-winkel es-winkel + anzahl dy dx i off nr tid s-mm e-mm ergebnisse) + (setq tid0 (ssg-val e "test_id") + start0 (mapcar 'float (ssg-val e "start_mm")) + ende0 (mapcar 'float (ssg-val e "ende_mm")) + hz (float (ssg-val e "hz_grad")) + as-seite (ssg-val e "as_seite") + es-seite (ssg-val e "es_seite") + as-winkel(ssg-val e "as_winkel") + es-winkel(ssg-val e "es_winkel") + anzahl (ssg-val e "anzahl") + dy (ssg-val e "delta_y") + dx (ssg-val e "delta_x")) (if (or (null as-seite) (= as-seite "")) (setq as-seite "links")) (if (or (null es-seite) (= es-seite "")) (setq es-seite "links")) (if (or (null as-winkel) (= as-winkel "")) (setq as-winkel "90")) (if (or (null es-winkel) (= es-winkel "")) (setq es-winkel "90")) + (if (or (null anzahl) (< anzahl 1)) (setq anzahl 1)) + (setq dy (if dy (float dy) 0.0)) + (setq dx (if dx (float dx) 0.0)) - ;; deltaL = reale 2D-Distanz start_mm -> ende_mm; Fallback 10000 falls - ;; ende_mm in einem Eintrag fehlen sollte. - (if ende-mm - (progn - (setq ende-mm (mapcar 'float ende-mm)) - (setq dx (- (car ende-mm) (car start-mm))) - (setq dy (- (cadr ende-mm) (cadr start-mm))) - (setq test-deltaL (sqrt (+ (* dx dx) (* dy dy))))) - (setq test-deltaL 10000.0)) - - (setq test-winkel 3.0 - test-rad (* test-winkel (/ pi 180.0)) - sep-x (* 300.0 (cos test-rad))) - (setq test-L_stau - (/ (- test-deltaL (float aus-dx) (float ein-dx) sep-x) (cos test-rad))) - - (princ (strcat "\n [GF] " tid " -> Start=(" (rtos (car start-mm) 2 0) - "," (rtos (cadr start-mm) 2 0) "," (rtos (caddr start-mm) 2 0) - ") hz=" (rtos hz 2 0) " deltaL=" (rtos test-deltaL 2 1))) - (setq ent (car (gefaellestrecke-einfuegen - test-L_stau test-winkel start-mm as-seite es-seite hz - test-deltaL as-winkel es-winkel "0" "Schoenenberger Geruest"))) - (mubea:ent-json tid "gefaellestrecke" ent) + (setq ergebnisse '() i 0) + (while (< i anzahl) + (setq off (float i)) + (setq nr (1+ i)) + ;; Bei mehreren Kopien fortlaufende ID GF_Mubea_01.. bilden (kompatibel zur + ;; frueheren Einzelbenennung); bei genau einer Kopie den Template-Namen behalten. + (setq tid + (if (> anzahl 1) + (strcat tid0 "_" (if (< nr 10) (strcat "0" (itoa nr)) (itoa nr))) + tid0)) + (setq s-mm (list (+ (car start0) (* dx off)) (+ (cadr start0) (* dy off)) (caddr start0))) + (setq e-mm (list (+ (car ende0) (* dx off)) (+ (cadr ende0) (* dy off)) (caddr ende0))) + (setq ergebnisse + (cons (mubea:build-gf-one tid s-mm e-mm hz as-seite es-seite as-winkel es-winkel) + ergebnisse)) + (setq i (1+ i)) + ) + (reverse ergebnisse) ) @@ -260,7 +355,7 @@ ;; ============================================================ ;; C:TEST_MUBEA - Hauptbefehl ;; ============================================================ -(defun c:TEST_MUBEA ( / json-datei daten eintrag tid blk res +(defun c:TEST_MUBEA ( / json-datei daten eintrag tid blk res r results-list sep-idx anz-ok anz-fehler kind) ;; Benoetigte Feature-Module laden @@ -278,6 +373,11 @@ (if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek)) + ;; Caches fuer GF-Init-Workaround und Hoehenkalibrierung pro Lauf zuruecksetzen + ;; (siehe mubea:ensure-gf-init / mubea:gf-calibrate weiter oben) + (setq *mubea-gf-init-done* nil) + (setq *mubea-gf-kalib-cache* nil) + ;; Testdaten laden (setq json-datei (strcat (getenv "DXFMAKRO") "/tests/testdata/mubea.json")) (if (not (findfile json-datei)) @@ -319,13 +419,17 @@ (T (princ (strcat "\n WARNUNG: Unbekannter Eintrag uebersprungen")) nil))) + ;; mubea:build-gf liefert eine LISTE (Template -> mehrere Strecken), die + ;; uebrigen Builder je einen String. Beides einheitlich einsammeln. (if res (progn - (setq results-list (cons res results-list)) - (if (or (vl-string-search "\"executed\"" res) - (vl-string-search "\"placeholder\"" res)) - (setq anz-ok (1+ anz-ok)) - (setq anz-fehler (1+ anz-fehler)))))) + (if (= (type res) 'STR) (setq res (list res))) + (foreach r res + (setq results-list (cons r results-list)) + (if (or (vl-string-search "\"executed\"" r) + (vl-string-search "\"placeholder\"" r)) + (setq anz-ok (1+ anz-ok)) + (setq anz-fehler (1+ anz-fehler))))))) (princ "\n================================================================") (princ (strcat "\n Ergebnis: " (itoa anz-ok) " OK, " (itoa anz-fehler) " Fehler")) diff --git a/tests/test_mubea.py b/tests/test_mubea.py index fd40dee..e6758a6 100644 --- a/tests/test_mubea.py +++ b/tests/test_mubea.py @@ -33,11 +33,17 @@ def _is_gefaelle(item): return tid.startswith("GF_") +def _gefaelle_count(testdata): + """Anzahl erzeugter Gefaellestrecken: ein GF-Eintrag ist ein Template, das + ueber 'anzahl' expandiert wird (Default 1, falls Feld fehlt).""" + return sum(int(t.get("anzahl", 1)) for t in testdata if _is_gefaelle(t)) + + def _expected_counts(testdata): return { "kreisel": sum(1 for t in testdata if _is_kreisel(t)), "vario": sum(1 for t in testdata if _is_vario(t)), - "gefaellestrecke": sum(1 for t in testdata if _is_gefaelle(t)), + "gefaellestrecke": _gefaelle_count(testdata), "separator": sum(1 for t in testdata if _is_separator(t)), } diff --git a/tests/testdata/mubea.json b/tests/testdata/mubea.json index 92fa674..20a6dda 100644 --- a/tests/testdata/mubea.json +++ b/tests/testdata/mubea.json @@ -92,12 +92,12 @@ "seite": "rechts", "hz": 0.0, "x": 410, - "y": 11526, + "y": 3860, "z": 1475, "comment": "Foerdererwinkel 21 (naechster verfuegbarer Bogenwinkel); Gefaellestrecke vorn L_GF1=500 / hinten L_GF2=4230 (gesamt L_GF=4730); deltaL und L_VF werden beim Bauen fuer winkel=21 und deltaH=553 geloest (L_GF ist linear in deltaL)" }, { - "test_id": "GF_Mubea_01", + "test_id": "GF_Mubea", "modus": 1, "hz_grad": 0.0, "start_mm": [399, 11526, 1941], @@ -106,7 +106,9 @@ "es_seite": "rechts", "as_winkel": "90", "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=11526" + "anzahl": 20, + "delta_y": -800, + "comment": "Template fuer alle Gefaellestrecken: 'anzahl' identische Kopien, je um 'delta_y' (mm) in Y versetzt (start_mm und ende_mm gemeinsam). Erzeugt GF_1..GF_anzahl. x/z aus DXF (EE-R Start hoch=1941, AE DS Ende niedrig=1467); realer DXF-Versatz -799..-800 auf nominal -800 vereinheitlicht." }, { "comment": "Separator S-LP #1 Y-Abgleich mit Gefaellestrecken (Sep-Y=14158, GF_Mubea_01-Y=11526, diff=2632mm, naechstgelegene Reihe, kein enger Treffer)", @@ -148,42 +150,6 @@ "rotation": 180, "assigned_to": "GF_Mubea_01" }, - { - "test_id": "GF_Mubea_02", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, 10726, 1941], - "ende_mm": [9510, 11514, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=12326" - }, - { - "test_id": "GF_Mubea_03", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, 9926, 1941], - "ende_mm": [9510, 10714, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=13126" - }, - { - "test_id": "GF_Mubea_04", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, 9126, 1941], - "ende_mm": [9510, 9914, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=13926" - }, { "comment": "Separator S-LP #11 Y-Abgleich mit Gefaellestrecken (Sep-Y=8777, GF_Mubea_04-Y=9126, diff=349mm, enger Treffer)", "function": "insert", @@ -194,18 +160,6 @@ "rotation": 270, "assigned_to": "GF_Mubea_04" }, - { - "test_id": "GF_Mubea_05", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, 8326, 1941], - "ende_mm": [9510, 9114, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=14726" - }, { "comment": "Separator S-LP #10 Y-Abgleich mit Gefaellestrecken (Sep-Y=7938, GF_Mubea_05-Y=8326, diff=388mm, enger Treffer)", "function": "insert", @@ -216,186 +170,6 @@ "rotation": 270, "assigned_to": "GF_Mubea_05" }, - { - "test_id": "GF_Mubea_06", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, 7526, 1941], - "ende_mm": [9510, 8314, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=15526" - }, - { - "test_id": "GF_Mubea_07", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, 6726, 1941], - "ende_mm": [9510, 7514, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=16326" - }, - { - "test_id": "GF_Mubea_08", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, 5926, 1941], - "ende_mm": [9510, 6714, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=17126" - }, - { - "test_id": "GF_Mubea_09", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, 5126, 1941], - "ende_mm": [9510, 5914, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=17926" - }, - { - "test_id": "GF_Mubea_10", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, 4326, 1941], - "ende_mm": [9510, 5114, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=18726" - }, - { - "test_id": "GF_Mubea_11", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, 3526, 1941], - "ende_mm": [9510, 4314, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=19526" - }, - { - "test_id": "GF_Mubea_12", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, 2726, 1941], - "ende_mm": [9510, 3514, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=20326" - }, - { - "test_id": "GF_Mubea_13", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, 1926, 1941], - "ende_mm": [9510, 2714, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=21126" - }, - { - "test_id": "GF_Mubea_14", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, 1126, 1941], - "ende_mm": [9510, 1914, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=21926" - }, - { - "test_id": "GF_Mubea_15", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, 326, 1941], - "ende_mm": [9510, 1114, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=22726" - }, - { - "test_id": "GF_Mubea_16", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, -473, 1941], - "ende_mm": [9510, 314, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=23526" - }, - { - "test_id": "GF_Mubea_17", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, -1273, 1941], - "ende_mm": [9510, -485, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=24326" - }, - { - "test_id": "GF_Mubea_18", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, -2073, 1941], - "ende_mm": [9510, -1285, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=25126" - }, - { - "test_id": "GF_Mubea_19", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, -2873, 1941], - "ende_mm": [9510, -2085, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=25926" - }, - { - "test_id": "GF_Mubea_20", - "modus": 1, - "hz_grad": 0.0, - "start_mm": [399, -3673, 1941], - "ende_mm": [9510, -2885, 1467], - "as_seite": "rechts", - "es_seite": "rechts", - "as_winkel": "90", - "es_winkel": "90", - "comment": "x,y aus DXF (500573_60_1.dxf, EE-R Insert) abgeleitet; z=1766 unveraendert (Einfuegehoehe). Vorheriger Handwert: x=410, y=26726" - }, { "comment": "Separator S-LP #8 Y-Abgleich mit Gefaellestrecken (Sep-Y=-4823, GF_Mubea_20-Y=-3673, diff=1150mm, naechstgelegene Reihe, kein enger Treffer)", "function": "insert", diff --git a/tests/testdata/object_data.md b/tests/testdata/object_data.md new file mode 100644 index 0000000..de14118 --- /dev/null +++ b/tests/testdata/object_data.md @@ -0,0 +1,391 @@ +# 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. + +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). + +--- + +## 1. Kreisel — `Lisp/KreiselInsert.lsp` + +Scriptbar. Zwei Varianten: + +### 1.1 Insert (freier Punkt + Abstand) + +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 | Hoehe; entfaellt `hoehe`, wird `pt`s Z verwendet | `*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 des Bauteils, kein Rasterzwang (0/90/180/270 sind nur ueblich, nicht erzwungen) | 0.0 | +| `typ` | string | nein | `"STANDARD"` oder `"PIN"` (einzige zwei Optionen aus `kreisel-ask-typ`) | `"STANDARD"` | + +Konstanten (Config `[kreisel]`): `durchmesser` (800.0mm, fuer Connect-Abstandsberechnung), `default_laenge`, `default_hoehe`. + +Minimal-JSON (wie `kreisel_tests.json`): +```json +{ "id": "K1", "function": "insert", + "x": 500, "y": 500, "z": 2500, + "abstand": 2000, "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 Connect (Start-/Endpunkt, Abstand wird berechnet) + +Produktionsfunktion: `kreisel-connect-script (ptStart ptEnd typ hoehe)` + +| Feld | Typ | Pflicht | Bedeutung | +|---|---|---|---| +| `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"` | Default `"STANDARD"` | +| `hoehe` | real (mm) | nein | Default: Z von `ptStart`, sonst `*kreisel-default-hoehe*` | + +`abstand`/`rotation` werden **intern** aus der Distanz beider Punkte +berechnet (`abstand = distanz - *kreisel-durchmesser*`; Rotation aus dem +Winkel der Verbindungslinie) — bei Connect KEIN Eingabefeld. + +--- + +## 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 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` / `deltaL_mm` | real (mm) | **ja** | horizontale Gesamtlaenge (AS+Staustrecke+Separator+ES) | config `gefaelle.default_delta_l` 5000.0 | +| `winkel` / `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 (Dialogfeld) | "1" | +| `geruest_typ` | string | nein | Geruest-Option (`*ssg-geruest-optionen*`) | Default-Index | + +Minimal-JSON (wie `gefaellestrecke_tests.json` Modus 1): +```json +{ "test_id": "M1_hz000", "modus": 1, "hz_grad": 0.0, + "start_mm": [0, 0, 5000], + "as_seite": "links", "es_seite": "links", + "as_winkel": "90", "es_winkel": "90" } +``` +`deltaL_mm`/`winkel_grad`/`z_start_mm` stehen in der Testdatei als +gemeinsamer Kopf-Eintrag (ein Objekt ohne `test_id`) statt pro Testfall, +weil alle Modus-1-Faelle in `gefaellestrecke_tests.json` dieselbe Laenge +teilen — Produktionsseitig ist `deltaL` aber pro Aufruf ein normaler, +nicht geteilter Parameter. + +### 2.2 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 | +|---|---|---|---| +| `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 waehlbar) | +| `winkel`, `as_seite`, `es_seite`, `as_winkel`, `es_winkel` | wie Modus 1 | nein/ja | siehe oben | + +Geruestoptionen sind in Modus 2 (reine Konsoleneingabe) nicht abfragbar, +bleiben auf Default (`"0"`, `"Schoenenberger Geruest"`). + +### 2.3 Modus 3 — Linienzug aus LINE/ARC — **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: {winkel_grad, radius_mm, seite}`, wie in + `gefaellestrecke_tests.json`s `M3_Bogen` zu sehen) 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. + +Um Modus 3 aus JSON zu treiben, muesste man selbst 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`, was hier aber explizit nicht als +Parameterquelle verwendet wurde. Die Felder `start_mm`, `ende_mm`, `bogen` +in `gefaellestrecke_tests.json` beschreiben also die **gewuenschte +Leitlinien-Geometrie**, nicht direkte Funktionsparameter einer +Produktionsroutine. + +--- + +## 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). + +### 3.1 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 | +|---|---|---|---| +| `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 automatisch den **kleinsten gueltigen** Winkel | +| `L_GF1`, `L_GF2` | real (mm) | nein | Aufteilung der Gefaellestrecke vorne/hinten. Falls nicht vorgegeben: `gleichmaessig` = beide `L_GF/2` (Solver-Ergebnis `L_GF` gesamt). In Produktion per Dialog waehlbar: `0`=gleichmaessig, `1`=alles vorne (Rest hinten), `2`=alles hinten (Rest vorne), `3`=manuell (`L_GF1` frei, `L_GF2` = Rest) | +| `startpunkt` (x,y,z) | real (mm) | **ja** | Einfuegepunkt | +| `seite` | string | nein | `"links"`/`"rechts"` (AS/ES-Element-Seite) | Default `"rechts"` (Dialog) | +| `hz` | real (Grad) | nein | Fahrtrichtung | 0.0 | +| `motorseite` | string | nein | `"links"`/`"rechts"` — nur Dialog/Anzeige, kein Geometrie-Parameter von `variofoerderer-einfuegen` selbst | + +Minimal-JSON (wie `foerderer_tests.json`): +```json +{ "test_id": "VF_Auf_1000", "richtung": "Auf", "deltaL": 7000, "deltaH": 1000 } +``` +Optionale Erweiterungsfelder aus derselben Datei: `seite` (Default +`"links"` im Testfall), `winkel` (erzwingt einen bestimmten Kandidaten +statt Auto-Wahl), `gf_verteilung` (`"gleichmaessig"`/`"vorne"`/`"hinten"`, +entspricht Verteilungsmodus `0`/`1`/`2` oben). + +### 3.2 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`, +`L_GF1`/`L_GF2`, `startpunkt`, `seite`, `hz`). + +### 3.3 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 (Startpunkt, Hoehe, +AS/ES-Winkel+Seite, naechstes Element, Linien-Endpunkt via +Fahrtrichtung+Laenge, GF-Verteilung, Separator vor/nach, Kettenende +ja/nein, ...). Es existiert **keine** einzelne Produktionsfunktion, die +eine ganze Kette aus einer Liste von Segment-Beschreibungen entgegennimmt. + +`linienzug_tests.json` bildet genau diese 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/Bedeutung die Antwort hat) — das ist die einzige +Moeglichkeit, den interaktiven Ablauf nicht-interaktiv nachzubilden, kein +direkter Parametersatz einer Bau-Funktion. Die Reihenfolge und Bedeutung +der Prompts (Kurzfassung, siehe `vf_linienzug.lsp` fuer Details): + +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) + +### 3.4 Vario_Kette_Merge — kein Neubau, sondern Re-Packaging + +`c:Vario_Kette_Merge`: benoetigt nur **einen** Start-Entity-Pick +(`entsel`), sucht danach selbst alle KS_EIN/KS_AUS-kompatiblen +Nachbarblöcke in der ganzen Zeichnung ab (`vfl-kette-verfolgen`, +Toleranzen 2mm/100mm) und fasst sie zu einem neuen `VF_n` zusammen. Kein +JSON-Parametersatz noetig/möglich — reine Nachbearbeitung bereits +vorhandener Geometrie. + +--- + +## 4. Omniflo — `Lisp/OmniModulInsert.lsp` + +### 4.1 Boegen/Weichen — Kern scriptbar, Produktions-Befehle NICHT + +Kern-Funktion: `omni:insert-dxf (sivasnr-str hoehe drehung)` + +| Param | Typ | Pflicht | Bedeutung | +|---|---|---|---| +| `sivasnr-str` | string | **ja** | Komponenten-ID; **ist gleichzeitig der Dateiname** `.dxf` unter `%DXFM_OMNIFLO%` — keine separate Namenstabelle | +| `hoehe` | string (mm) | nein | **absolute** Z-Koordinate des Einfuegepunkts + `HOEHE`-Attribut; leer/`nil` -> `config omniflo.default_hoehe` bzw. hart "2000" | +| `drehung` | string (Grad) | nein | feste Rotation; `nil` -> in den echten `c:OMNI_*`-Befehlen wird interaktiv per Maus gedreht | + +**Aber**: Alle echten `c:OMNI_APB_*`/`c:OMNI_W*_*`-Befehle rufen +`omni:insert-dxf` **ohne X/Y-Parameter** 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 auch keinen Punkt-Parameter (der Punkt wird IN der Funktion +interaktiv abgefragt). Die Felder `x`/`y` in `omniflo_tests.json` haben +daher **keine direkte Entsprechung** in einer Produktions-Parameterliste — +sie beschreiben nur, wo das Element im Testlayout stehen soll; ein +JSON-Treiber muesste den `_.INSERT`-Aufruf selbst nachbauen (z.B. wie +`mubea:build-separator` in `tests/test_mubea.lsp` es fuer Separatoren tut). + +Sivasnr-Herkunft: `data/json/omniflo_boegen.json` (Felder u.a. `Sivasnr`, +`ProfilTyp`, `Radius`, `KurvenWinkel`, `Breite`, `Länge`, `SivasnrTEF`, +`Antriebsart`) und `data/json/omniflo_weichen.json` (zusaetzlich +`WeichenTyp`, `Schaltungstyp`, `KurvenRichtung`, `WeichenkörperLänge`) — +diese Tabellen dienen nur der Dialog-Filterung/Anzeige, nicht der +Namensaufloesung selbst (die ist `sivasnr == dateiname`). + +Minimal-JSON, wie es fuer einen scriptbaren Wrapper um `omni:insert-dxf` +sinnvoll waere (entspricht `omniflo_tests.json`): +```json +{ "id": "821104025", "type": "bogen", "sivasnr": "821104025", + "x": 219.4, "y": 223.06, "hoehe": 2000, "drehung": 0.0 } +``` +`description`/`row` sind reine Dokumentations-/Gruppierungsfelder aus der +Testdatei, keine Produktionsparameter. + +### 4.2 Aluprofil-Gerade (AP60/AP110/APG110) — Kern scriptbar + +Kern-Funktion: `omni:make-gerade (blockname pt pt2 srcAttribs textHeight textGap textEnt)` + +| Param | Typ | Pflicht | Bedeutung | +|---|---|---|---| +| `blockname` | string | **ja** | `"AP60"` / `"AP110"` / `"APG110"` | +| `pt`, `pt2` | (x,y,z) | **ja** | Start-/Endpunkt der Geraden (absolute Weltkoordinaten); Laenge/Rotation werden **aus der Differenz berechnet**, keine eigenen Winkel-/Laengenfelder | +| `srcAttribs` | alist | nein | aus Vorlagen-DWG gelesen (`omni:read-src-attribs blockname`); bei `nil` entsteht ein Block ohne ATTDEFs | +| `textHeight`,`textGap` | real | nein | Konfig `omniflo.text_height`(100.0)/`text_gap`(20.0) | + +Der oeffentliche Befehl `omni:insert-block (blockname laengemax)` fragt +`pt`/`pt2` interaktiv per `getpoint` ab und prueft `laengemax` (Config +`omniflo.laengemax_ap60/ap110/apg110`) — `omni:make-gerade` selbst ist +aber mit fest vorgegebenen Punkten aufrufbar, hier waere aus +`linienzug_tests.json`-artigen Daten (`profil`, `x`/`y`, `x_ende`/`y_ende`, +`hoehe`) ein Treiber moeglich, wie es die Eintraege vom Typ `"gerade"` in +`linienzug_tests.json` nahelegen (dort allerdings fuer eine andere +Test-Zwecksetzung — Reihen-Layout, nicht direkter Funktionsaufruf). + +### 4.3 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), nur Position/Hoehe/Rotation waeren +grundsaetzlich scriptbar (siehe 4.1), aber nicht ueber die vorhandenen +`c:OMNI_*`-Befehle. + +### 4.4 TV_* (Transferwagen-Verbinder) / APBW_* (Boegen fuer Weichen) + +**Nicht implementiert.** Alle `c:OMNI_TV_*`- und `c:OMNI_APBW_*`-Befehle +sind reine `[DUMMY]`-Platzhalter ohne echte Bau-Logik — kein +Parametersatz vorhanden, unabhaengig davon was eine Testdatei dafuer +vorsehen wuerde. + +`c:OMNI_UPDATE_ATTRIBS` ist in `Lisp/export.lsp` definiert (aktualisiert +`HOEHE`/`DREHUNG` aller Omniflo-Elemente aus der Zeichnung) — kein +Bau-/Einfuege-Befehl und daher kein JSON-Parametersatz. + +--- + +## 5. Separator/Scanner — `Lisp/SSG_LIB_Commands.lsp` + +Funktion: `ils-insert-sensor (fname)` + +| Param | Typ | Pflicht | Bedeutung | +|---|---|---|---| +| `fname` | string | **ja** | Blockdateiname ohne Erweiterung, z.B. `"Separator_SP"`, `"Scanner"` — aufgeloest ueber `ssg-ils-block-datei` (2D/3D-Suffix + Fallback) | + +**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`) — das ist der Ansatz, den +`tests/test_mubea.lsp`s `mubea:build-separator` verwendet (zur Info, nicht +als Quelle fuer diesen Parametersatz genutzt, da bereits aus +`ils-insert-sensor` selbst ableitbar: Blockname + Punkt + Rotation sind +die einzigen fachlich noetigen Werte). + +Minimal-JSON (wie in `mubea.json`s Separator-Eintraegen): +```json +{ "function": "insert", "block": "S-LP", "x": 10310, "y": -14158, "z": 1467, "rotation": 270 } +``` + +--- + +## 6. 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`) |