refactor: VF-Linienzug-Journal-Grammatik in lib/vf_linienzug_grammatik.py gebuendelt

lib/vf_journal_export.py (Journal.dekodiere) und lib/vf_spec_export.py
(SpecLeser) implementierten denselben Tokenwalk (Praeambel, Sektions-
schleife, Glied-Dispatch, VF-Einheit-Unterschleife) sowie dieselben
Code-Tabellen (SEITE/VARIANTE/VERTEILUNG/IM_VF/ENDPUNKT/ENDE2/ENDE3/
GEFAELLE/GF_LAENGE_MODUS, MENU_START/MENU_START_AKTUELL/MENU_WEITER,
Grammatik) unabhaengig voneinander - Aenderungen an der Grammatik (wie
im vorigen Commit) mussten von Hand synchron gehalten werden und sind
bereits einmal auseinandergelaufen.

Neuer gemeinsamer Parser-Kern (Kern/lies_journal) plus Sink-Schnittstelle
(SpecSink in vf_spec_export.py, ProtokollSink in vf_journal_export.py)
ersetzt die doppelte Implementierung; Code-Tabellen/Grammatik-Objekte
leben jetzt nur noch in lib/vf_linienzug_grammatik.py. Journal behaelt
nur noch die reine XDATA/DXF-Zerlegung, SpecSchreiber (Spec -> Journal,
Gegenstueck zu Lisp/vf_spec.lsp) bleibt unveraendert in vf_spec_export.py.

Zusaetzlich: pruefe_flachen_freitext schlaegt jetzt hart fehl, wenn
generierter Freitext (quelle/beschreibung) ein rohes Anfuehrungszeichen
enthaelt - der Lisp-seitige Flach-JSON-Leser (ssg-cfg-parse-value) kennt
keine Escapes und wuerde daran die Zeichenkette (und damit die gesamte
restliche Datei) falsch zerlegen.

Byte-identischer Output gegen die Baseline geprueft (beide CLIs, echte
Eingaben); pytest tests/test_vf_spec.py + test_vf_journal_grammatik.py
unveraendert: 2 bekannte, vorbestehende Fehlschlaege, 38 bestanden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Samer Ayadi
2026-09-14 11:59:22 +02:00
co-authored by Claude Sonnet 5
parent 1bdecf0678
commit 6d15c6be13
3 changed files with 1023 additions and 610 deletions
+237 -294
View File
@@ -14,6 +14,12 @@ ist die vollstaendige, wiederabspielbare Bau-Anleitung der Kette
vfl-entry->string wieder zurueck und annotiert jeden Wert mit der Frage, die
er beantwortet - abgeleitet aus dem Frageablauf von vf-linienzug-modus.
Den Frageablauf selbst laeuft der GEMEINSAME Parser-Kern
(lib/vf_linienzug_grammatik.py), den sich dieses Skript mit
lib/vf_spec_export.py teilt: dort steht die Grammatik EINMAL, hier nur noch
die ProtokollSink mit den Annotationstexten. Frueher war der Ablauf in beiden
Dateien von Hand nachgebaut und lief regelmaessig auseinander.
Bewusst OHNE ezdxf: gebraucht werden nur INSERT-Kopf (Blockname, Einfuegepunkt,
Drehung) und die 1000er-Gruppen der XDATA - das ist reine Gruppencode-Paar-
Lesung und funktioniert auch bei ASCII-DXF im 100-MB-Bereich.
@@ -47,6 +53,14 @@ import os
import re
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# Grammatik (Code-Tabellen, Menue-Zuordnung) UND Frageablauf kommen aus dem
# gemeinsamen Modul - hier steht nur noch die Annotation dazu. SEITE/VARIANTE
# braucht zusaetzlich die Kurzbeschreibung (kurzbeschreibung): sie liest die
# fertigen Protokolleintraege nach und hat dort nur den rohen Menue-Code.
from vf_linienzug_grammatik import ( # noqa: E402
GRAMMATIK_AUFZEICHNUNG, SEITE, Sink, VARIANTE, lies_journal)
PREFIX = "[vf_journal_export]"
XDATA_APP = "SSG_VF_EDIT"
@@ -60,25 +74,6 @@ RUND = 6
# Anlagenkuerzel in den test_ids (VF_<kuerzel>_LZ_01 ...).
TEST_ID_ANLAGE = "hundm05"
# Antwort-Klartexte der Menues aus vf_linienzug.lsp (nur zur Annotation).
JA_NEIN = {"1": "Ja", "2": "Nein"}
SEITE = {"1": "links", "2": "rechts"}
VARIANTE = {"1": "aussen", "2": "innen"}
GF_MODUS = {"1": "ueber Zielhoehe", "2": "ueber Neigungswinkel"}
ENDPUNKT3 = {"1": "Ja, nur Motorstation", "2": "Nein, weiterbauen",
"3": "Ja, Motorstation + Kettenende am Zielpunkt"}
KETTENENDE3 = {"1": "Ja, mit ES-Element", "2": "Ja, ohne ES-Element",
"3": "Nein, weiterbauen"}
KETTENENDE2 = {"1": "Ja, Separator + ES-Element", "2": "Nein, weiterbauen"}
VERTEILUNG = {"1": "halbe Staustrecke am Ausgang", "2": "alles am Einlauf"}
IM_VF = {"1": "Horizontaler Foerderer", "2": "Vario-Kurve",
"3": "Auf/Ab-Foerderer"}
MENU_START = {"1": "Neue Linie: GF", "2": "Neue Linie: Ab/Auf VF",
"3": "Horizontal-VF", "4": "Neue Linie bis Kettenende"}
MENU_WEITER = {"1": "GF-Bogen", "2": "Neue Linie: GF",
"3": "Neue Linie: Ab/Auf VF", "4": "Horizontal-VF",
"5": "Neue Linie bis Kettenende"}
STEP_KOMMENTAR = ("Glied-Marker (vfl-journal-mark) - beim Replay "
"uebersprungen, Label = gewaehlter Segmenttyp")
@@ -148,50 +143,155 @@ def lese_vf_inserts(dxf_pfad):
# ============================================================
# Journal dekodieren
# ============================================================
# Den Frageablauf laeuft der gemeinsame Kern in lib/vf_linienzug_grammatik.py
# (dieselbe Routine, die lib/vf_spec_export.py fuer die Spec benutzt). Hier
# steht nur noch die ANNOTATION: zu jeder Frage die Konsolen-Vorlage aus
# vf_linienzug.lsp und - wo die Domaenen-Kurzform nicht schon der
# Antworttext ist - der Klartext der Antwort.
class Journal(object):
"""Ein Journal (kind:payload je Zeile) in das Frage-Antwort-Protokoll
uebersetzen. Der Ablauf folgt vf-linienzug-modus (Modus 1):
# Prompt-Vorlagen je Frage-Schluessel (siehe Sink-Doku im Grammatik-Modul).
# Ein Schluessel, nicht der Feldname: dasselbe Feld ("hoehe") beantwortet an
# verschiedenen Stellen verschiedene Fragen.
Praeambel: Startpunkt, Starthoehe, AS-Element (Ja/Nein, Winkel, Seite)
Hauptschleife je Glied: STEP-Marker, Menuewahl, dann die Fragen des
gewaehlten Segmenttyps
Innerhalb einer VF-Einheit (Horizontal-VF / Linie): Separator vor/nach,
"Ist der Endpunkt der Foerderer?", danach Horizontal / Vario-Kurve /
Auf-Ab bis die Einheit schliesst
Kettenende: ES-Glied (Winkel, Seite)
_REAL_FRAGEN = {
"start_hoehe": "getreal Hoehe (Z) Startpunkt -> %.1f mm",
"gf_winkel": "getreal Neigungswinkel -> %.1f Grad",
"gf_hoehe": "getreal Zielhoehe (Z) Linienendpunkt -> %.1f mm",
"vf_zielhoehe": "getreal Zielhoehe (Z) Linienendpunkt -> %.1f mm",
"kettenende_hoehe": "getreal Zielhoehe (Z) am Kettenende -> %.1f mm",
"aufab_hoehe": "getreal Zielhoehe (Z) am Segmentende -> %.1f mm",
"eingang_hoehe": "getreal Ziel-Hoehe fuer den GF1-Einlauf der "
"VF-Einheit (nur bei komplett neu gebautem Eingang) "
"-> %.1f mm",
}
_INT_FRAGEN = {
"gf_bogen_winkel": "getint GF-Bogen Winkel (30/60/90 Grad) -> %d",
"vario_winkel": "getint Vario-Kurve Winkel (30/60/90 Grad, Wert "
"direkt) -> %d Grad",
"winkel_idx": "getint Winkelwahl aus den gueltigen Kandidaten "
"(vfl-waehle-winkel) -> Option %d",
}
# STR-Antworten, deren Inhalt schon der Wert ist (kein Code).
_ROH_FRAGEN = {
"as_winkel": "getstring AS-Element Winkel (Wert direkt, 90 oder 30 "
"Grad) -> %s Grad",
"es_winkel": "getstring ES-Element Winkel (Wert direkt, 90 oder 30 "
"Grad) -> %s Grad",
}
_JA_NEIN_FRAGEN = {
"as": "getstring AS-Element setzen? (1=Ja, 2=Nein) -> %s",
"sep_vor": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, "
"2=Nein) -> %s",
"sep_nach": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, "
"2=Nein) -> %s",
"es": "getstring ES-Element setzen? (1=Ja, 2=Nein - Kette endet direkt "
"am Zielpunkt) -> %s",
"separator": "getstring Separator an dieser Stelle einfuegen? (1=Ja, "
"2=Nein) -> %s",
}
# Die Menue-Antwort auf "Naechstes Element" ist der Glied-Name; sein
# Klartext ist derselbe am Kettenanfang wie spaeter, nur die aufgezaehlten
# Optionen im Fragetext unterscheiden sich. Darum EINE Antworttabelle fuer
# beide Fragen (frueher zwei code-indizierte Tabellen nebeneinander).
_GLIED_TEXT = {"GF-Bogen": "GF-Bogen", "Linie-GF": "Neue Linie: GF",
"Linie-VF": "Neue Linie: Ab/Auf VF",
"Horizontal-VF": "Horizontal-VF",
"Linie": "Neue Linie bis Kettenende"}
# STR-Antworten ueber eine Code-Tabelle: (Vorlage, Klartext je Domaenenwert).
# Steht statt der Klartexte None, ist der Domaenenwert selbst schon der
# Antworttext ("links"/"rechts", "aussen"/"innen").
#
# ACHTUNG: die Fragetexte zaehlen die Menue-Optionen der AUFZEICHNUNG auf
# (4 Optionen am Kettenanfang). Wird das Protokoll je gegen die aktuelle
# Grammatik erzeugt (GRAMMATIK_AKTUELL, 6 Optionen), muss "menu_start" hier
# mitgezogen werden - die Antworten selbst bleiben richtig.
_CODE_FRAGEN = {
"as_seite": ("getstring AS-Seite (1=links, 2=rechts) -> %s", None),
"es_seite": ("getstring ES-Seite (1=links, 2=rechts) -> %s", None),
"gf_bogen_seite": ("getstring GF-Bogen Seite (1=links, 2=rechts) -> %s",
None),
"vario_seite": ("getstring Vario-Kurve Seite (1=links, 2=rechts) -> %s",
None),
"vario_variante": ("getstring Vario-Kurve Variante (1=aussen, 2=innen) "
"-> %s", None),
"gefaelle": ("getstring Gefaelle festlegen (1=ueber Zielhoehe, 2=ueber "
"Neigungswinkel) -> %s",
{"hoehe": "ueber Zielhoehe",
"winkel": "ueber Neigungswinkel"}),
"ende3": ("getstring Ist das Kettenende? (1=Ja mit ES, 2=Ja ohne ES, "
"3=Nein weiterbauen) -> %s",
{"ja-mit-es": "Ja, mit ES-Element",
"ja-ohne-es": "Ja, ohne ES-Element",
"nein": "Nein, weiterbauen"}),
"ende2": ("getstring Ist das Kettenende? (1=Ja, Separator + ES-Element, "
"2=Nein, weiterbauen) -> %s",
{"ja-mit-es": "Ja, Separator + ES-Element",
"nein": "Nein, weiterbauen"}),
"gf_verteilung": ("getstring GF-Verteilung (1=halbe Staustrecke am "
"Ausgang, 2=alles am Einlauf) -> %s",
{"haelfte": "halbe Staustrecke am Ausgang",
"einlauf": "alles am Einlauf"}),
"endpunkt": ("getstring Ist der Endpunkt der Foerderer? (1=Ja nur "
"Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + "
"Kettenende) -> %s",
{"motorstation": "Ja, nur Motorstation",
"weiter": "Nein, weiterbauen",
"kettenende": "Ja, Motorstation + Kettenende am "
"Zielpunkt"}),
"im_vf": ("getstring Naechstes in der VF-Einheit (1=Horizontaler "
"Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> %s",
{"horizontal": "Horizontaler Foerderer",
"vario-kurve": "Vario-Kurve",
"auf-ab": "Auf/Ab-Foerderer"}),
"menu_start": ("getstring Naechstes Element am Kettenanfang (1=Neue "
"Linie GF, 2=Neue Linie Ab/Auf VF, 3=Horizontal-VF, "
"4=Neue Linie bis Kettenende) -> %s", _GLIED_TEXT),
"menu_weiter": ("getstring Naechstes Element (1=GF-Bogen, 2=Neue Linie "
"GF, 3=Neue Linie Ab/Auf VF, 4=Horizontal-VF, 5=Neue "
"Linie bis Kettenende) -> %s", _GLIED_TEXT),
}
# Kontext der Laengeneingabe je Frage-Schluessel (die "%s"-Stelle im
# getpoint-Kommentar).
_LINIE_TEXT = {
"gf_linie": "Endpunkt der GF-Linie",
"vf_linie": "Endpunkt des Ab/Auf-VF-Segments",
"horizontal_vf": "Endpunkt des horizontalen VF-Stuecks",
"horizontal_sub": "Endpunkt des horizontalen VF-Stuecks",
"aufab_sub": "Endpunkt des Auf/Ab-VF-Segments",
"linie_kettenende": "Endpunkt der Linie bis Kettenende (Ziel-XY der "
"Kette)",
"kettenende_ziel": "Zielpunkt des Kettenendes",
}
def _vorlage(tabelle, frage):
if frage not in tabelle:
raise ValueError("keine Annotation fuer die Frage %r hinterlegt "
"(lib/vf_journal_export.py)" % frage)
return tabelle[frage]
class ProtokollSink(Sink):
"""Schreibt aus den Meldungen des Parser-Kerns das annotierte
Frage-Antwort-Protokoll (Schema tests/testdata/hm_recformat.json):
je verbrauchtem Wert ein Eintrag {"typ", Wertfeld(er), "kommentar"}.
Die Reihenfolge der Schluessel im Eintrag ist fest (typ, Wert,
kommentar) - das erzeugte JSON wird von Hand gelesen und mit frueheren
Laeufen verglichen.
"""
def __init__(self, roh):
self.tok = []
for zeile in roh.split(TRENNER):
if zeile:
art, _, wert = zeile.partition(":")
self.tok.append((art, wert))
self.pos = 0
def __init__(self):
self.out = []
self.erster_step = True
self.erster_dl = True
# --- Tokenzugriff ---
def art(self):
return self.tok[self.pos][0] if self.pos < len(self.tok) else None
def label(self):
return self.tok[self.pos][1] if self.pos < len(self.tok) else None
def nimm(self, erwartet):
if self.pos >= len(self.tok):
raise ValueError("Journal endet vor %s" % erwartet)
art, wert = self.tok[self.pos]
if art != erwartet:
raise ValueError("Token %d: %s erwartet, %s:%s gefunden"
% (self.pos, erwartet, art, wert))
self.pos += 1
return wert
# --- Ausgabe ---
def ergebnis(self):
return self.out
def add(self, typ, kommentar, **felder):
eintrag = {"typ": typ}
@@ -199,253 +299,96 @@ class Journal(object):
eintrag["kommentar"] = kommentar
self.out.append(eintrag)
def frage_str(self, kommentar_fmt, tabelle=None):
w = self.nimm("STR")
self.add("string", kommentar_fmt % (tabelle.get(w, "?") if tabelle
else w), wert=w)
return w
# --- Struktur: nur die Glied-Marker stehen im Protokoll ---
def frage_real(self, kommentar_fmt):
w = float(self.nimm("REAL"))
self.add("real", kommentar_fmt % w, wert=w)
return w
def frage_int(self, kommentar_fmt):
w = int(self.nimm("INT"))
self.add("int", kommentar_fmt % w, wert=w)
return w
def marker(self, label):
def sektion_start(self, label):
self.add("step", STEP_KOMMENTAR, wert=label)
def frage_linie(self, was):
"""Segmentlaenge; die gesnappte Fahrtrichtung steht nur beim ersten
Segment der Kette im Journal (vfl-in-abstand), danach erbt jedes
Segment die Richtung des Vorgaengers."""
dl = float(self.nimm("DL"))
if self.erster_dl and self.art() == "REAL":
hz = float(self.nimm("REAL"))
self.erster_dl = False
def marke(self, label):
self.add("step", STEP_KOMMENTAR, wert=label)
def glied_gewaehlt(self, label, code, frage):
vorlage, texte = _vorlage(_CODE_FRAGEN, frage)
self.add("string", vorlage % texte[label], wert=code)
# --- Werte ---
def punkt_abs(self, punkt):
self.add("point_abs", "getpoint Startpunkt der Kette (absoluter "
"Weltpunkt, einziger Anker der ganzen Kette)", wert=punkt)
def linie(self, dl, hz, frage):
was = _vorlage(_LINIE_TEXT, frage)
if hz is None:
self.add("point_rel",
"getpoint %s: dL=%.1f mm (Fahrtrichtung vom "
"Vorgaenger geerbt)" % (was, dl), dL=dl)
else:
self.add("point_rel",
"getpoint %s: dL=%.1f mm, gesnappte Fahrtrichtung "
"hz=%.0f Grad (nur beim ersten Segment der Kette "
"journalisiert)" % (was, dl, hz), dL=dl, hz=hz)
else:
self.erster_dl = False
self.add("point_rel",
"getpoint %s: dL=%.1f mm (Fahrtrichtung vom "
"Vorgaenger geerbt)" % (was, dl), dL=dl)
return dl
# --- Ablauf ---
def real_feld(self, name, wert, frage):
self.add("real", _vorlage(_REAL_FRAGEN, frage) % wert, wert=wert)
def int_feld(self, name, wert, frage):
self.add("int", _vorlage(_INT_FRAGEN, frage) % wert, wert=wert)
def roh_feld(self, name, wert, frage):
self.add("string", _vorlage(_ROH_FRAGEN, frage) % wert, wert=wert)
def ja_nein(self, name, code, wert, frage):
self.add("string",
_vorlage(_JA_NEIN_FRAGEN, frage) % ("Ja" if wert else "Nein"),
wert=code)
def code_feld(self, name, code, wert, frage):
self._code(code, wert, frage)
def wahl(self, name, code, wert, frage):
self._code(code, wert, frage)
def _code(self, code, wert, frage):
"""Eine ueber eine Code-Tabelle beantwortete Frage. Der Domaenenwert
ist entweder selbst schon der Antworttext (links/rechts,
aussen/innen) oder wird ueber die Klartexttabelle der Frage
uebersetzt; im Protokoll steht als "wert" der rohe Journal-Code, so
wie er in der XDATA stand."""
vorlage, texte = _vorlage(_CODE_FRAGEN, frage)
self.add("string", vorlage % (texte[wert] if texte else wert),
wert=code)
# implizit() schreibt nichts: segment_typ und das automatische VF-Ende
# ergeben sich aus dem Ablauf, es wurde dafuer kein Token gelesen.
def tokens_aus_journal(roh):
"""Die Rohform der XDATA ("ART:wert", getrennt durch TRENNER) in
Tokenpaare zerlegen."""
tok = []
for zeile in roh.split(TRENNER):
if zeile:
art, _, wert = zeile.partition(":")
tok.append((art, wert))
return tok
class Journal(object):
"""Ein Journal (kind:payload je Zeile) in das Frage-Antwort-Protokoll
uebersetzen.
Der Ablauf selbst steckt im gemeinsamen Parser-Kern
(lib/vf_linienzug_grammatik.py) - diese Klasse haelt nur noch die
Rohform-Zerlegung und die Sink zusammen.
"""
def __init__(self, roh, grammatik=GRAMMATIK_AUFZEICHNUNG):
self.tok = tokens_aus_journal(roh)
self.grammatik = grammatik
def dekodiere(self):
self.praeambel()
while self.pos < len(self.tok):
label = self.nimm("STEP")
self.marker(label)
if label == "ES":
self.glied_es(marker_gelesen=True)
continue
if self.erster_step:
tabelle, kopf = MENU_START, (
"Naechstes Element am Kettenanfang (1=Neue Linie GF, "
"2=Neue Linie Ab/Auf VF, 3=Horizontal-VF, 4=Neue Linie "
"bis Kettenende) -> %s")
else:
tabelle, kopf = MENU_WEITER, (
"Naechstes Element (1=GF-Bogen, 2=Neue Linie GF, "
"3=Neue Linie Ab/Auf VF, 4=Horizontal-VF, 5=Neue Linie "
"bis Kettenende) -> %s")
self.frage_str("getstring " + kopf, tabelle)
self.erster_step = False
if label == "Linie-GF":
self.glied_linie_gf()
elif label == "Linie-VF":
self.glied_linie_vf()
elif label == "Horizontal-VF":
self.glied_horizontal_vf()
elif label == "Linie":
self.glied_linie_kettenende()
elif label == "GF-Bogen":
self.frage_int("getint GF-Bogen Winkel (30/60/90 Grad) "
"-> %d")
self.frage_str("getstring GF-Bogen Seite (1=links, "
"2=rechts) -> %s", SEITE)
else:
raise ValueError("unbekanntes Glied: %s" % label)
if self.pos != len(self.tok):
raise ValueError("nicht alle Tokens verbraucht (%d von %d)"
% (self.pos, len(self.tok)))
return self.out
def praeambel(self):
punkt = [float(v) for v in self.nimm("PT").split(",")]
self.add("point_abs", "getpoint Startpunkt der Kette (absoluter "
"Weltpunkt, einziger Anker der ganzen Kette)", wert=punkt)
self.frage_real("getreal Hoehe (Z) Startpunkt -> %.1f mm")
if self.frage_str("getstring AS-Element setzen? (1=Ja, 2=Nein) "
"-> %s", JA_NEIN) == "1":
w = self.nimm("STR")
self.add("string", "getstring AS-Element Winkel (Wert direkt, "
"90 oder 30 Grad) -> %s Grad" % w, wert=w)
self.frage_str("getstring AS-Seite (1=links, 2=rechts) -> %s",
SEITE)
def glied_linie_gf(self):
self.frage_linie("Endpunkt der GF-Linie")
if self.frage_str("getstring Gefaelle festlegen (1=ueber Zielhoehe, "
"2=ueber Neigungswinkel) -> %s", GF_MODUS) == "2":
self.frage_real("getreal Neigungswinkel -> %.1f Grad")
else:
self.frage_real("getreal Zielhoehe (Z) Linienendpunkt "
"-> %.1f mm")
if self.frage_str("getstring Ist das Kettenende? (1=Ja mit ES, "
"2=Ja ohne ES, 3=Nein weiterbauen) -> %s",
KETTENENDE3) == "1":
self.glied_es()
def glied_horizontal_vf(self):
"""VF-Einheit mit HORIZONTALEM Anfangskoerper (winkel1=0 in
vfl-vf-einheit): dort stellt vfl-baue-horizontal-koerper im
Ziel-Modus die Separator-/Endpunkt-Fragen VOR der
Laengenberechnung."""
self.frage_linie("Endpunkt des horizontalen VF-Stuecks")
self.vf_einheit(auto_ende=False, erster_horizontal=True)
def glied_linie_vf(self):
""""Neue Linie: Ab/Auf VF" - Laenge, Zielhoehe, ggf. Winkelwahl
(vfl-vf-entscheidung), dann eine VF-Einheit mit GEWINKELTEM
Anfangskoerper. Zu dem Koerper wird nichts gefragt (vfl-vf-einheit
baut ihn direkt), die erste Frage der Einheit ist also die
Endpunkt-Frage der Fortsetzungsschleife."""
self.frage_linie("Endpunkt des Ab/Auf-VF-Segments")
self.frage_real("getreal Zielhoehe (Z) Linienendpunkt -> %.1f mm")
self.frage_winkelwahl()
self.vf_einheit(auto_ende=False, erster_horizontal=False)
def glied_linie_kettenende(self):
""""Neue Linie bis Kettenende": Laenge + Zielhoehe, danach entscheidet
vfl-segment-entscheidung selbst. Reine Gefaellestrecke -> ES-Element
ohne weitere Frage; VF-Einheit -> vf_einheit mit auto-ende."""
self.frage_linie("Endpunkt der Linie bis Kettenende (Ziel-XY der "
"Kette)")
self.frage_real("getreal Zielhoehe (Z) am Kettenende -> %.1f mm")
self.frage_winkelwahl()
if self.art() == "STEP" and self.label() == "ES":
self.glied_es()
elif self.art() == "STR":
# vfl-segment-entscheidung liefert fuer typ="VF" immer einen
# Winkel > 0 (die VF-Winkelstufen beginnen bei 3 Grad), der erste
# Koerper ist hier also nie horizontal.
self.vf_einheit(auto_ende=True, erster_horizontal=False)
def glied_es(self, marker_gelesen=False):
if not marker_gelesen:
label = self.nimm("STEP")
if label != "ES":
raise ValueError("ES-Marker erwartet, %s gefunden" % label)
self.marker(label)
w = self.nimm("STR")
self.add("string", "getstring ES-Element Winkel (Wert direkt, 90 "
"oder 30 Grad) -> %s Grad" % w, wert=w)
self.frage_str("getstring ES-Seite (1=links, 2=rechts) -> %s", SEITE)
def frage_winkelwahl(self):
"""Winkelwahl (vfl-waehle-winkel) steht nur im Journal, wenn mehrere
Kandidatenwinkel gueltig waren - bei genau einem faellt die Frage
weg."""
if self.art() == "INT":
self.frage_int("getint Winkelwahl aus den gueltigen "
"Kandidaten (vfl-waehle-winkel) -> Option %d")
def horizontal_koerper(self):
"""vfl-baue-horizontal-koerper im Ziel-Modus: Separator vor/nach und
die Endpunkt-Frage stehen VOR der Laengenberechnung. Rueckgabe: die
Endpunkt-Antwort, die die Fortsetzungsschleife weiterverwendet."""
self.frage_str("getstring Separator VOR dem horizontalen Stueck? "
"(1=Ja, 2=Nein) -> %s", JA_NEIN)
self.frage_str("getstring Separator NACH dem horizontalen Stueck? "
"(1=Ja, 2=Nein) -> %s", JA_NEIN)
return self.frage_str("getstring Ist der Endpunkt der Foerderer? "
"(1=Ja nur Motorstation, 2=Nein weiterbauen, "
"3=Ja Motorstation + Kettenende) -> %s",
ENDPUNKT3)
def vario_kurve(self):
label = self.nimm("STEP")
if label != "Vario-Kurve":
raise ValueError("Vario-Kurve-Marker erwartet, %s" % label)
self.marker(label)
self.frage_int("getint Vario-Kurve Winkel (30/60/90 Grad, Wert "
"direkt) -> %d Grad")
self.frage_str("getstring Vario-Kurve Seite (1=links, 2=rechts) "
"-> %s", SEITE)
self.frage_str("getstring Vario-Kurve Variante (1=aussen, 2=innen) "
"-> %s", VARIANTE)
def vf_einheit(self, auto_ende, erster_horizontal):
"""Eine VF-Einheit: GF-Verteilung, erster Koerper, dann die
Fortsetzungsschleife (vfl-vf-einheit) bis Motorstation/Kettenende,
danach der Abschluss (vfl-vf-einheit-abschluss).
erster_horizontal spiegelt die Verzweigung ueber winkel1 in
vfl-vf-einheit: nur der horizontale Erstkoerper laeuft durch
vfl-baue-horizontal-koerper und fragt darum Separator vor/nach und
die Endpunkt-Frage schon vorab (deren Antwort die Schleife dann
uebernimmt, statt erneut zu fragen). Ein gewinkelter Erstkoerper
wird ohne jede Frage gebaut."""
self.frage_str("getstring GF-Verteilung (1=halbe Staustrecke am "
"Ausgang, 2=alles am Einlauf) -> %s", VERTEILUNG)
vorantwort = self.horizontal_koerper() if erster_horizontal else None
ziel_ende = False
es_gewuenscht = True
while True:
if vorantwort is None:
antwort = self.frage_str(
"getstring Ist der Endpunkt der Foerderer? (1=Ja nur "
"Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + "
"Kettenende) -> %s", ENDPUNKT3)
else:
antwort, vorantwort = vorantwort, None
if antwort == "1":
break
if antwort == "3":
es_gewuenscht = self.frage_str(
"getstring ES-Element setzen? (1=Ja, 2=Nein - Kette "
"endet direkt am Zielpunkt) -> %s", JA_NEIN) != "2"
self.frage_linie("Zielpunkt des Kettenendes")
self.frage_real("getreal Zielhoehe (Z) am Kettenende "
"-> %.1f mm")
self.frage_winkelwahl()
ziel_ende = True
break
wahl = self.frage_str("getstring Naechstes in der VF-Einheit "
"(1=Horizontaler Foerderer, "
"2=Vario-Kurve, 3=Auf/Ab-Foerderer) "
"-> %s", IM_VF)
if wahl == "1":
self.frage_linie("Endpunkt des horizontalen VF-Stuecks")
vorantwort = self.horizontal_koerper()
elif wahl == "2":
self.vario_kurve()
else:
self.frage_linie("Endpunkt des Auf/Ab-VF-Segments")
self.frage_real("getreal Zielhoehe (Z) am Segmentende "
"-> %.1f mm")
self.frage_winkelwahl()
if ziel_ende and not es_gewuenscht:
return
if auto_ende or ziel_ende:
self.glied_es()
return
if self.frage_str("getstring Ist das Kettenende? (1=Ja, Separator + "
"ES-Element, 2=Nein, weiterbauen) -> %s",
KETTENENDE2) == "1":
self.glied_es()
else:
self.frage_str("getstring Separator an dieser Stelle einfuegen? "
"(1=Ja, 2=Nein) -> %s", JA_NEIN)
return lies_journal(self.tok, ProtokollSink(), self.grammatik)
# ============================================================