diff --git a/Lisp/vf_linienzug.lsp b/Lisp/vf_linienzug.lsp index 5201c3f..59ffbcb 100644 --- a/Lisp/vf_linienzug.lsp +++ b/Lisp/vf_linienzug.lsp @@ -1984,10 +1984,12 @@ (unload_dialog dat) (if (= ergebnis 1) (if (= gtyp "0") - ;; GF + Neigung + Separator VOR/NACH (Ja/Nein je "1"/"0") + ;; GF + Neigung + Separator VOR/NACH (Ja/Nein je "1"/"0"). + ;; Der Typ geht als WERT in die Queue (*vfl-m2-linie-werte*), die + ;; Separator-Antworten bleiben Ja/Nein-Ziffern wie ueberall sonst. (vflw-pending-push-all - (list "1" (atof gwert) (if (= gsepvor "1") "1" "2") (if (= gsepnach "1") "1" "2"))) - (vflw-pending-push-all (list "2")))))) ; VF (keine Neigung/Separator) + (list "gf" (atof gwert) (if (= gsepvor "1") "1" "2") (if (= gsepnach "1") "1" "2"))) + (vflw-pending-push-all (list "vf")))))) ; VF (keine Neigung/Separator) (princ)) ;; --- Modus 3, Segment "Gerade": Typ (GF/VF-Ab/VF-Auf/VF-Hor) + Wert --- @@ -2090,10 +2092,12 @@ (unload_dialog dat) (if (= ergebnis 1) (if (and (= gtyp "1") vario-erlaubt) - ;; Vario-Kurve: "2" + Variante ("1"=aussen / "2"=innen; Index+1) - (vflw-pending-push-all (list "2" (itoa (1+ (atoi gvariante))))) - ;; GF-Bogen (oder Vario unzulaessig): "1" (keine Variante) - (vflw-pending-push-all (list "1")))))) + ;; Vario-Kurve + Variante, beide als WERT (*vfl-m2-bogen-werte* / + ;; *vfl-m2-variante-werte*) statt als Menue-Index. + (vflw-pending-push-all + (list "vario-kurve" (if (= gvariante "1") "innen" "aussen"))) + ;; GF-Bogen (oder Vario unzulaessig): keine Variante + (vflw-pending-push-all (list "gf-bogen")))))) (princ)) ;; ============================================================ @@ -5337,12 +5341,36 @@ ;; nil heisst fuer den Aufrufer: ABBRECHEN. Ein Rueckfall auf eine geratene ;; Anfangsvariante waere genau der lautlose Fehlaufbau, den die Wertumstellung ;; verhindern soll - lieber ein harter Abbruch mit Fundstelle. -(defun vfl-m2-anfang-norm (wert / tr) +;; Allgemeiner Normalisierer fuer selbstbeschreibende Antwortwerte. +;; werte = heutige Werte in Optionsreihenfolge, alt = Alist Alt-Ziffer -> Wert +;; (nur fuer Bestandsketten). Rueckgabe nil = UNBEKANNT; der Aufrufer bricht +;; dann ab, statt eine Variante zu raten - ein geratener Wert waere ein +;; lautloser Fehlaufbau. Alt-Ziffern und neue Werte sind disjunkt, die +;; Zuordnung also eindeutig. +(defun vfl-wert-norm (wert werte alt / tr) (cond - ((not (and wert (= (type wert) 'STR))) nil) - ((member wert *vfl-m2-anfang-werte*) wert) - ((setq tr (assoc wert *vfl-m2-anfang-alt*)) (cdr tr)) - (t nil))) + ((not (and wert (= (type wert) 'STR))) nil) + ((member wert werte) wert) + ((setq tr (assoc wert alt)) (cdr tr)) + (t nil))) + +(defun vfl-m2-anfang-norm (wert) + (vfl-wert-norm wert *vfl-m2-anfang-werte* *vfl-m2-anfang-alt*)) + +;; --- Segment-Klassifizierung (Modus 2): Werte statt Menue-Ziffern --- +;; Dieselbe Begruendung wie beim Lauf-Anfang: die Ziffer haengt an der +;; Position im Menue, der Wert an seiner Bedeutung. Ein Umsortieren der +;; Optionen deutete sonst jede gespeicherte Kette still um. +;; Zusatznutzen hier: Typ und Variante benutzten BEIDE "1"/"2" und waren im +;; Journal nicht auseinanderzuhalten (siehe Kommentar bei m2-format-neu) - +;; mit eigenen Wertevorraeten ist das erledigt. +;; Alt -> Neu (nur fuer Bestandsketten), jeweils die alte Optionsreihenfolge. +(setq *vfl-m2-linie-werte* '("gf" "vf")) +(setq *vfl-m2-linie-alt* '(("1" . "gf") ("2" . "vf"))) +(setq *vfl-m2-bogen-werte* '("gf-bogen" "vario-kurve")) +(setq *vfl-m2-bogen-alt* '(("1" . "gf-bogen") ("2" . "vario-kurve"))) +(setq *vfl-m2-variante-werte* '("aussen" "innen")) +(setq *vfl-m2-variante-alt* '(("1" . "aussen") ("2" . "innen"))) ;; Punkt um eine Strecke ENTGEGEN der Fahrtrichtung zurueckversetzen. ;; laenge-planar = horizontale (Grundriss-)Laenge, winkel = Gefaelle in Grad @@ -11663,10 +11691,17 @@ (setq nach-kurve nil nach-vf t)) (t (princ (ssg-text "vfl-m3-typ-gerade")) - (setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2") - (list "GF (Neigungswinkel)" "VF (Bruecke)") 1 - "vfl-m3-typ-gerade")) - (if (= antwort "2") + (setq antwort + (vfl-wert-norm + (vfl-menu-strwert (ssg-text "prompt-wahl-1-2") + (list "GF (Neigungswinkel)" "VF (Bruecke)") + *vfl-m2-linie-werte* 1 "vfl-m3-typ-gerade") + *vfl-m2-linie-werte* *vfl-m2-linie-alt*)) + (if (null antwort) + (progn (vfl-meldung (ssg-textf "vfl-m2-segwert-unbekannt" + (list (itoa (1+ i)) (vfl-replay-kette-text)))) + (exit))) + (if (= antwort "vf") (progn (setq plan (cons (list "Linie" hz laenge "VF" nil) plan)) (setq nach-vf t)) @@ -11719,21 +11754,35 @@ ;; Plan-Phase (m2-format-neu) - am WERT der naechsten Antwort ginge es ;; nicht, denn Typ und Variante benutzen beide "1"/"2". (if (and nach-vf (not m2-format-neu)) - (setq antwort "2") ; Alt-Journal: Vario-Kurve war erzwungen + (setq antwort "vario-kurve") ; Alt-Journal: Vario-Kurve war erzwungen (progn (princ (ssg-text "vfl-m3-typ-bogen")) - (setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2") - (list "GF-Bogen" "Vario-Kurve") - (if nach-vf 2 1) - "vfl-m3-typ-bogen")))) + (setq antwort + (vfl-wert-norm + (vfl-menu-strwert (ssg-text "prompt-wahl-1-2") + (list "GF-Bogen" "Vario-Kurve") + *vfl-m2-bogen-werte* (if nach-vf 2 1) "vfl-m3-typ-bogen") + *vfl-m2-bogen-werte* *vfl-m2-bogen-alt*)) + (if (null antwort) + (progn (vfl-meldung (ssg-textf "vfl-m2-segwert-unbekannt" + (list (itoa (1+ i)) (vfl-replay-kette-text)))) + (exit))))) (setq nach-vf nil) - (if (= antwort "2") + (if (= antwort "vario-kurve") (progn (princ (ssg-text "vfl-m3-variante-frage")) - (setq kv-variante (if (= (vfl-menu (ssg-text "vfl-prompt-wahl-1-2-def2") - (list (ssg-text "vfl-variante-aussen") (ssg-text "vfl-variante-innen")) 2 - "vfl-m3-variante-frage") - "1") "aussen" "innen")) + ;; Die Variante IST schon ein Domaenenwert ("aussen"/"innen") - sie + ;; wandert jetzt unveraendert ins Journal, statt als Index "1"/"2". + (setq kv-variante + (vfl-wert-norm + (vfl-menu-strwert (ssg-text "vfl-prompt-wahl-1-2-def2") + (list (ssg-text "vfl-variante-aussen") (ssg-text "vfl-variante-innen")) + *vfl-m2-variante-werte* 2 "vfl-m3-variante-frage") + *vfl-m2-variante-werte* *vfl-m2-variante-alt*)) + (if (null kv-variante) + (progn (vfl-meldung (ssg-textf "vfl-m2-segwert-unbekannt" + (list (itoa (1+ i)) (vfl-replay-kette-text)))) + (exit))) (setq plan (cons (list "Bogen" hz laenge bwinkel bseite "Vario-Kurve" kv-variante) plan)) (setq nach-kurve t)) ; naechste Gerade automatisch VF (progn diff --git a/Lisp/vf_spec.lsp b/Lisp/vf_spec.lsp index bdb1994..d24a2bb 100644 --- a/Lisp/vf_spec.lsp +++ b/Lisp/vf_spec.lsp @@ -936,6 +936,29 @@ ;; sid = Kennung im Record, dim = "2D"/"3D" oder nil (Sitzung), ;; fehler = Liste der Spec-Fehler (bei nil-Journal die Begruendung). ;; Erwartet den Headless-Rahmen (vsp-headless-an) beim Aufrufer. +;; Bau-Funktion AUS DEM JOURNAL ableiten (Rueckgabe: Funktions-Symbol). +;; Bewusst abgeleitet statt als zusaetzliches Argument: vsp-bau-journal hat +;; zwei Aufrufer, und AutoLISP verlangt exakte Argumentzahl - eine erweiterte +;; Signatur muesste jeden davon nachziehen (siehe CLAUDE.md). Die Ableitung +;; kann der Datenlage ausserdem nicht widersprechen, weil sie GENAU das +;; Journal liest, das gleich abgespielt wird. +;; +;; Unterscheidung: +;; kein SEGS -> Modus 1 (Fragenfluss, Menue-Codes) +;; SEGS + STR "m2v2" -> Modus 2 (die Formatmarke schreibt nur +;; vf-linienzug-modus2, vfl-m2-formatmarke) +;; SEGS ohne "m2v2" -> Modus 3 +;; Ein Fehlgriff faellt sofort auf: der Ergebnis-Record meldet dann offene +;; Eingaben bzw. glieder_soll <> glieder_ist. +(defun vsp-bau-fn-fuer-journal (journal) + (cond + ((not (vfl-journal-hat-segs-p journal)) 'vf-linienzug-modus) + ((vl-some (function (lambda (e) + (and (equal (car e) "STR") (equal (cdr e) "m2v2")))) + journal) + 'vf-linienzug-modus2) + (t 'vf-linienzug-modus3))) + (defun vsp-bau-journal (sid journal dim fehler / rec vor-ent alt-error err offen hl meld ist-glieder soll-glieder diff ist-journal jdiff ent ed) @@ -975,7 +998,7 @@ (setq alt-error *error*) (vfl-journal-reset) (vfl-journal-replay-start journal) - (setq err (vl-catch-all-apply 'vf-linienzug-modus '())) + (setq err (vl-catch-all-apply (vsp-bau-fn-fuer-journal journal) '())) (setq offen (vsp-queue-rest)) ;; Diagnose VOR dem Reset sichern (vfl-journal-reset loescht sie). (setq ist-journal (reverse *vfl-journal*)) diff --git a/lang/de_DE.json b/lang/de_DE.json index ea88085..8749426 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -262,6 +262,7 @@ "vfl-m3-lauf-anfang-opt-umlenk": "\n 2 - Umlenkstation direkt (kein GF1/Separator)", "vfl-m3-lauf-anfang-opt-umlenk-gf1": "\n 1 - Umlenkstation mit GF1 + Separator", "vfl-m2-uebergang-bogen-fehlt": "Uebergangsbogen %1 fehlt in der Bogen-Tabelle - der Block ist nicht ladbar oder hat kein messbares KS. Der Lauf wird ohne seinen Laengenanteil gebaut und faellt dadurch um dessen Laenge zu lang aus.", + "vfl-m2-segwert-unbekannt": "Segment %1 nicht lesbar: Kette %2 enthaelt einen unbekannten Klassifizierungswert. Aufbau abgebrochen - ein geratener Segmenttyp wuerde die Kette falsch neu bauen.", "vfl-m2-eck-korrektur": "\n Eck-Korrektur %1 mm auf Segment %2 (gezeichneter Bogen gegen Fussabdruck des Eck-Bauteils).", "vfl-m2-abschluss-fussabdruck": "\n Abschluss-Fussabdruck %1 mm von Segment %2 abgezogen (Abschlussbogen + Motorstation stehen darauf).", "vfl-m2-as-fussabdruck": "\n AS-Fussabdruck %1 mm von Segment %2 abgezogen (das AS-Element steht selbst darauf).", diff --git a/lang/en_GB.json b/lang/en_GB.json index eb05ad9..0ae7659 100644 --- a/lang/en_GB.json +++ b/lang/en_GB.json @@ -262,6 +262,7 @@ "vfl-m3-lauf-anfang-opt-umlenk": "\n 2 - Reversing station directly (no GF1/separator)", "vfl-m3-lauf-anfang-opt-umlenk-gf1": "\n 1 - Reversing station with GF1 + separator", "vfl-m2-uebergang-bogen-fehlt": "Transition bend %1 is missing from the bend table - the block cannot be loaded or has no measurable KS. The run is built without its length share and therefore comes out too long by that amount.", + "vfl-m2-segwert-unbekannt": "Segment %1 cannot be read: chain %2 contains an unknown classification value. Build aborted - guessing a segment type would rebuild the chain incorrectly.", "vfl-m2-eck-korrektur": "\n Corner correction %1 mm on segment %2 (drawn arc versus footprint of the corner component).", "vfl-m2-abschluss-fussabdruck": "\n Closing footprint %1 mm deducted from segment %2 (closing bend + motor station sit on it).", "vfl-m2-as-fussabdruck": "\n AS footprint %1 mm deducted from segment %2 (the AS element sits on it).", diff --git a/lib/vf_journal_export.py b/lib/vf_journal_export.py index 1ba09f9..005eced 100644 --- a/lib/vf_journal_export.py +++ b/lib/vf_journal_export.py @@ -65,6 +65,11 @@ PREFIX = "[vf_journal_export]" XDATA_APP = "SSG_VF_EDIT" MARKER = "linienzug" +# Marker -> Modus-Nummer. "linienzug" (Modus 1) hat ein Frage-Antwort- +# Protokoll (Grammatik in vf_linienzug_grammatik.py); "linienzug2"/"3" werden +# als Rohtokens ausgegeben - fuer sie gibt es bewusst keine Grammatik, ihre +# SEGS-Eintraege machen sie ohnehin selbst-enthalten. +MARKER_MODUS = {"linienzug": 1, "linienzug2": 2, "linienzug3": 3} # Steuerzeichen-Notation des Eintrags-Trenners im ASCII-DXF (siehe Kopf). TRENNER = "^J" @@ -554,6 +559,58 @@ def baue_testfaelle(inserts, strecken, startpunkt_fix, quelle_datei, return faelle +def baue_tokenfaelle(inserts, quelle_datei): + """Modus-2/3-Ketten als TOKEN-Records (flache Liste, Kopf + Eintraege). + + Warum nicht wie Modus 1 als Frage-Antwort-Protokoll: dafuer braeuchte es + eine Grammatik des Modus-2/3-Fragenflusses, die es (bewusst, siehe + doc/todo/spec-modus2-3.md) nicht gibt. Die Tokens selbst sind aber bereits + aussagekraeftig - vor allem traegt SEGS die komplette Segment-Essenz, womit + die Kette OHNE Zeichnung abspielbar ist. + + Ausgabeformat ist genau das, was tests/test_linienzug.lsp + (c:TEST_LINIENZUG_MODUS2) einliest: + {"art": "PT", "x": .., "y": .., "z": ..} + {"art": "REAL", "wert": ..} {"art": "INT", "wert": ..} + {"art": "STR", "wert": ".."} {"art": "SEGS", "wert": ".."} + Die Metadaten-Zeile (beschreibung/erwartung_*) ist bewusst NICHT dabei - + die wird je Testfall von Hand gepflegt und soll beim Neuerzeugen nicht + stillschweigend ueberschrieben werden. + """ + faelle = [] + for nr, e in enumerate(sorted(inserts, key=lambda i: i["x"]), start=1): + toks = tokens_aus_journal(e["journal_roh"]) + modus = MARKER_MODUS.get(e["marker"], 0) + kopf = { + "test_id": "VF_%s_M%d_%02d" % (TEST_ID_ANLAGE, modus, nr), + "anlagetyp": e["marker"], + "modus": modus, + "anzahl_eingaben": len(toks), + "block": e["block"], + "dxf_handle": e["handle"], + "beschreibung": ("Linienzug Modus %d, %d Journal-Eintraege - " + "Rohtokens, siehe doc/todo/spec-modus2-3.md" + % (modus, len(toks))), + "quelle": ("INSERT %s (Block %s) in %s, XDATA-App %s, Marker %s" + % (e["handle"], e["block"], + os.path.basename(quelle_datei), XDATA_APP, + e["marker"])), + } + faelle.append(kopf) + for art, wert in toks: + if art == "PT": + x, y, z = [float(v) for v in wert.split(",")] + faelle.append({"art": "PT", "x": round(x, RUND), + "y": round(y, RUND), "z": round(z, RUND)}) + elif art == "REAL": + faelle.append({"art": "REAL", "wert": float(wert)}) + elif art == "INT": + faelle.append({"art": "INT", "wert": int(wert)}) + else: + faelle.append({"art": art, "wert": wert}) + return faelle + + def arrays_einzeilig(text): """Rein numerische JSON-Arrays auf eine Zeile ziehen (wie dxf_scan_components.py) - ssg-parse-json-array (Lisp/ssg_core.lsp) liest @@ -598,9 +655,20 @@ def main(): print("%s %d verbundene Strecken im Sivas-Export" % (PREFIX, len(strecken))) - faelle = baue_testfaelle(inserts, strecken, - not args.kein_startpunkt_fix, args.dxf, - args.output) + # Nach Modus trennen: nur Modus 1 hat eine Grammatik. Frueher lief die + # Modus-1-Grammatik auch ueber "linienzug2"/"3" (der startswith-Filter + # laesst sie durch) und brach mit "Token 6: STEP erwartet" ab. + m1 = [e for e in inserts if MARKER_MODUS.get(e["marker"]) == 1] + m23 = [e for e in inserts if MARKER_MODUS.get(e["marker"]) in (2, 3)] + faelle = [] + if m1: + faelle.extend(baue_testfaelle(m1, strecken, + not args.kein_startpunkt_fix, args.dxf, + args.output)) + if m23: + print("%s %d Modus-2/3-Kette(n) als Rohtokens (keine Grammatik, " + "siehe doc/todo/spec-modus2-3.md)" % (PREFIX, len(m23))) + faelle.extend(baue_tokenfaelle(m23, args.dxf)) koepfe = [f for f in faelle if "test_id" in f] for f in koepfe: print("%s %s %s (%s) %d Eingaben" diff --git a/tests/test_unit.lsp b/tests/test_unit.lsp index ad8330e..68e7143 100644 --- a/tests/test_unit.lsp +++ b/tests/test_unit.lsp @@ -552,6 +552,64 @@ (tu-eqf "vfl-projiziere-neigung/waagerecht" 500.0 (car (vfl-projiziere-neigung 500.0 -28.6 0.0))) + ;; --- Bau-Funktion aus dem Journal ableiten (vf_spec.lsp) --- + ;; vsp-bau-journal waehlt die Modus-Funktion anhand des Journals, statt sie + ;; als zusaetzliches Argument zu bekommen (exakte Argumentzahl, siehe + ;; CLAUDE.md). Kein SEGS = Modus 1; SEGS mit der Formatmarke "m2v2" = + ;; Modus 2 (nur vf-linienzug-modus2 schreibt sie); SEGS ohne = Modus 3. + (tu-true "vsp-bau-fn/ohne-segs-ist-modus1" + (eq (vsp-bau-fn-fuer-journal + (list (cons "PT" '(0.0 0.0 0.0)) (cons "STR" "1"))) + 'vf-linienzug-modus)) + (tu-true "vsp-bau-fn/segs-mit-marke-ist-modus2" + (eq (vsp-bau-fn-fuer-journal + (list (cons "PT" '(0.0 0.0 0.0)) (cons "SEGS" '(("Linie"))) + (cons "STR" "m2v2") (cons "STR" "2"))) + 'vf-linienzug-modus2)) + (tu-true "vsp-bau-fn/segs-ohne-marke-ist-modus3" + (eq (vsp-bau-fn-fuer-journal + (list (cons "PT" '(0.0 0.0 0.0)) (cons "SEGS" '(("Linie"))) + (cons "STR" "2"))) + 'vf-linienzug-modus3)) + ;; Die Marke zaehlt nur als STR-Eintrag - ein gleichlautender Wert in einem + ;; anderen Eintragstyp darf nicht faelschlich auf Modus 2 schliessen lassen. + (tu-true "vsp-bau-fn/marke-nur-als-str" + (eq (vsp-bau-fn-fuer-journal + (list (cons "SEGS" '(("Linie"))) (cons "INT" "m2v2"))) + 'vf-linienzug-modus3)) + + ;; --- Segment-Klassifizierung: Wert statt Menue-Ziffer --- + ;; Gemeinsamer Normalisierer fuer alle selbstbeschreibenden Antworten. + ;; Bestandsketten tragen die Alt-Ziffer, neue Ketten den Wert; beide Mengen + ;; sind disjunkt. Unbekanntes gibt nil - der Aufrufer bricht dann ab. + (foreach _p *vfl-m2-linie-alt* + (tu-eq (strcat "vfl-wert-norm/linie-alt-" (car _p)) (cdr _p) + (vfl-wert-norm (car _p) *vfl-m2-linie-werte* *vfl-m2-linie-alt*))) + (foreach _p *vfl-m2-bogen-alt* + (tu-eq (strcat "vfl-wert-norm/bogen-alt-" (car _p)) (cdr _p) + (vfl-wert-norm (car _p) *vfl-m2-bogen-werte* *vfl-m2-bogen-alt*))) + (foreach _p *vfl-m2-variante-alt* + (tu-eq (strcat "vfl-wert-norm/variante-alt-" (car _p)) (cdr _p) + (vfl-wert-norm (car _p) *vfl-m2-variante-werte* *vfl-m2-variante-alt*))) + ;; Neue Werte gehen unveraendert durch. + (foreach _w *vfl-m2-bogen-werte* + (tu-eq (strcat "vfl-wert-norm/bogen-neu-" _w) _w + (vfl-wert-norm _w *vfl-m2-bogen-werte* *vfl-m2-bogen-alt*))) + ;; Jeder Alt-Zielwert muss ein gueltiger heutiger Wert sein, sonst laeuft + ;; eine Bestandskette in den Unbekannt-Abbruch. + (foreach _p *vfl-m2-linie-alt* + (tu-true (strcat "vfl-wert-norm/linie-ziel-gueltig-" (car _p)) + (and (member (cdr _p) *vfl-m2-linie-werte*) t))) + ;; Typ und Variante benutzten frueher BEIDE "1"/"2" und waren im Journal + ;; nicht zu unterscheiden - die neuen Wertevorraete muessen disjunkt sein. + (tu-nil "vfl-wert-norm/bogen-und-variante-disjunkt" + (vl-some (function (lambda (w) (if (member w *vfl-m2-variante-werte*) w))) + *vfl-m2-bogen-werte*)) + (tu-nil "vfl-wert-norm/unbekannt-gibt-nil" + (vfl-wert-norm "3" *vfl-m2-linie-werte* *vfl-m2-linie-alt*)) + (tu-nil "vfl-wert-norm/kein-string" + (vfl-wert-norm 1 *vfl-m2-linie-werte* *vfl-m2-linie-alt*)) + ;; --- ES-Reservierung auf die KS_AUS-Achse --- ;; Vorgabe: quer zur KS_AUS-Achse MUSS 0 sein, laengs darf abweichen. ;; Der reine Laengsanteil reicht dafuer NICHT - er laesst den bauteileigenen