feat: AS-Zufuehrungsachse fuer beide Winkel + GF-Option "Parallel zum Kreisel"
Die AS-Praeambel in Modus 1 fragt jetzt Winkel, Seite UND eine optionale Zufuehrungsachse (2. Punkt) - im Assistenten alles in einem Fenster (vflw_as_achse). Beide Angaben zusammen legen das AS vollstaendig fest: der 2. Punkt gibt die KS_EIN-Achse (Zufuehrung/Fahrtrichtung), links/rechts die AS-Variante und damit die KS_AUS-Richtung der Kette. Bisher galt die Achse nur beim 30-Grad-Element und ERSETZTE dort die Seitenfrage. Darauf setzt die neue 5. Option der gf-laenge-modus-Frage auf, intern "parallel" (vfl-gf-parallel-kreisel): AS -> GF-Stueck -> GF-Bogen aus EINEM Abstandswert, gemessen senkrecht zur Zufuehrungsachse von AS-KS_EIN bis Bogen-KS_AUS. Kein Separator dazwischen, damit der Abstand exakt getroffen wird; die Kettenende-Frage stellt der Zweig selbst NACH dem Bau. Waehlbar nur am Kettenanfang mit AS UND Achse. Der Bogen ist nicht verdrahtet: gewaehlt wird der, der den gemessenen Grundriss-Schwenk des AS-Blocks aufhebt (vf-element-plan-turn) - mit den vorhandenen Bausteinen AS 30 -> 30-Grad-Bogen, AS 90 -> 90-Grad-Bogen, immer zur Gegenseite. Querversatz von AS und Bogen wird gemessen statt gerechnet; die Bogen-Probe laeuft dabei auf dem IDEALISIERTEN 3-Grad-Rahmen, auf dem der echte Bogen spaeter sitzt (auf dem AS-Rahmen gemessen lag der Abstand im BricsCAD-Test um 0.5 mm daneben). Journal: [STR winkel][STR seite][PT p2 | NIL]. Welche Fragen gestellt wurden, wird nicht journalisiert, sondern beim Replay an der Eintragsart erkannt - alte Ketten bleiben ohne Sonderbehandlung editierbar. Die Journal-Werkzeuge (vf_linienzug_grammatik.py, vf_spec_export.py, vf_journal_export.py, vf_spec.lsp, test_hm_recformat.lsp) kennen das optionale Achsen-Token. Tests: 11 neue Unit-Tests fuer die reinen Kerne vfl-par-bogen-waehlen und vfl-par-gf-laenge (tu-tests-vfl-parallel), TEST_UNIT 701/701 gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -322,6 +322,19 @@ class ProtokollSink(Sink):
|
||||
self.add("point_abs", "getpoint Startpunkt der Kette (absoluter "
|
||||
"Weltpunkt, einziger Anker der ganzen Kette)", wert=punkt)
|
||||
|
||||
def as_achse(self, punkt):
|
||||
# Zweiter absoluter Punkt - gibt NUR die Richtung der Zufuehrung,
|
||||
# der Startpunkt bleibt der Sitz des AS-Elements. "nil" heisst: die
|
||||
# Frage wurde gestellt und bewusst uebergangen (die Kettenrichtung
|
||||
# kommt dann aus dem naechsten Klick).
|
||||
if punkt is None:
|
||||
self.add("nil", "getpoint Zufuehrungsachse - 2. Punkt: keiner "
|
||||
"gewaehlt, Richtung kommt aus dem naechsten Klick")
|
||||
else:
|
||||
self.add("point_abs", "getpoint Zufuehrungsachse - 2. Punkt "
|
||||
"(gibt nur die Richtung, der Startpunkt bleibt)",
|
||||
wert=punkt)
|
||||
|
||||
def linie(self, dl, hz, frage):
|
||||
was = _vorlage(_LINIE_TEXT, frage)
|
||||
if hz is None:
|
||||
|
||||
@@ -287,6 +287,12 @@ class Sink(object):
|
||||
def praeambel_start(self):
|
||||
"""Kettenkopf beginnt (Startpunkt/Starthoehe/AS-Element)."""
|
||||
|
||||
def as_achse(self, punkt):
|
||||
"""Zufuehrungsachse des AS-Elements verbraucht (2. Punkt, gibt nur
|
||||
die Richtung). punkt = [x, y, z] oder None, wenn die Kette bewusst
|
||||
OHNE Achse gebaut wurde. Wird gar nicht gerufen, wenn das Journal
|
||||
diese Frage noch nicht kennt (vor 2026-10-21)."""
|
||||
|
||||
def praeambel_ende(self):
|
||||
"""Kettenkopf fertig."""
|
||||
|
||||
@@ -518,9 +524,31 @@ class Kern(object):
|
||||
self.sink.real_feld("hoehe", self.real(), "start_hoehe")
|
||||
if self._ja("as", "AS-Element setzen", "as"):
|
||||
self.sink.roh_feld("as_winkel", self.nimm("STR"), "as_winkel")
|
||||
self._code("as_seite", SEITE, "AS-Seite", "as_seite")
|
||||
# Drei Journal-Formate, unterscheidbar an der Eintragsart (siehe
|
||||
# vfl-frage-as-winkel-seite-oder-achse):
|
||||
# PT direkt nach dem Winkel -> Alt-Achse ohne Seitenfrage
|
||||
# (30-Grad-Element, 2026-10-14
|
||||
# bis 10-20); die Seite wurde
|
||||
# damals aus der Lage des
|
||||
# naechsten Punkts ermittelt.
|
||||
# STR, dann PT bzw. NIL -> Format ab 2026-10-21: Seite UND
|
||||
# (optionale) Zufuehrungsachse.
|
||||
# STR, dann etwas anderes -> Altbestand ohne Achsenfrage.
|
||||
if self.art() == "PT":
|
||||
self._as_achse()
|
||||
else:
|
||||
self._code("as_seite", SEITE, "AS-Seite", "as_seite")
|
||||
if self.art() == "PT":
|
||||
self._as_achse()
|
||||
elif self.art() == "NIL":
|
||||
self.nimm("NIL")
|
||||
self.sink.as_achse(None)
|
||||
self.sink.praeambel_ende()
|
||||
|
||||
def _as_achse(self):
|
||||
"""Zufuehrungsachse: zweiter Punkt, der NUR die Richtung gibt."""
|
||||
self.sink.as_achse([float(v) for v in self.nimm("PT").split(",")])
|
||||
|
||||
def sektionen(self):
|
||||
while self.pos < len(self.tok):
|
||||
label = self.nimm("STEP")
|
||||
|
||||
@@ -87,6 +87,11 @@ def tokens_aus_protokoll(eintraege):
|
||||
tok.append(("STR", str(e["wert"])))
|
||||
elif typ == "step":
|
||||
tok.append(("STEP", str(e["wert"])))
|
||||
elif typ == "nil":
|
||||
# Eine Frage, die gestellt und bewusst uebergangen wurde (bisher
|
||||
# nur die Zufuehrungsachse des AS-Elements) - im Journal ein
|
||||
# "NIL"-Eintrag ohne Wert.
|
||||
tok.append(("NIL", None))
|
||||
else:
|
||||
raise ValueError("unbekannter Eintragstyp: %r" % typ)
|
||||
return tok
|
||||
@@ -255,6 +260,14 @@ class SpecSink(Sink):
|
||||
def punkt_abs(self, punkt):
|
||||
self.ziel["punkt"] = punkt
|
||||
|
||||
def as_achse(self, punkt):
|
||||
# Zufuehrungsachse des AS-Elements. "ohne" ist NICHT dasselbe wie ein
|
||||
# fehlendes Feld: "ohne" heisst "die Frage wurde gestellt und bewusst
|
||||
# uebergangen" (NIL im Journal), ein fehlendes Feld heisst "das
|
||||
# Journal kennt die Frage gar nicht" (vor 2026-10-21). Nur so bleibt
|
||||
# der Rundlauf Journal -> Spec -> Journal tokengleich.
|
||||
self.ziel["as_achse"] = punkt if punkt is not None else "ohne"
|
||||
|
||||
def linie(self, dl, hz, frage):
|
||||
self.ziel["dl"] = dl
|
||||
if hz is not None:
|
||||
@@ -398,6 +411,14 @@ class SpecSchreiber(object):
|
||||
self.raus("STR", str(self.feld(start, "as_winkel")))
|
||||
self.raus("STR", code_aus_wert(SEITE, self.feld(start, "as_seite"),
|
||||
"as_seite"))
|
||||
# Optional, Gegenstueck zu SpecSink.as_achse und zu
|
||||
# vsp-praeambel in Lisp/vf_spec.lsp: Liste -> PT, "ohne" -> NIL,
|
||||
# fehlt -> gar kein Token (Journalformat vor 2026-10-21).
|
||||
achse = start.get("as_achse")
|
||||
if isinstance(achse, (list, tuple)):
|
||||
self.raus("PT", ",".join(_zahl(v) for v in achse))
|
||||
elif achse == "ohne":
|
||||
self.raus("NIL", None)
|
||||
|
||||
def sektion(self, sek, erster, naechste=None):
|
||||
label = self.feld(sek, "glied")
|
||||
@@ -641,6 +662,11 @@ def flache_objekte(spec_id, spec, kopf_extra=None):
|
||||
if start["as"]:
|
||||
kopf["as_winkel"] = start["as_winkel"]
|
||||
kopf["as_seite"] = start["as_seite"]
|
||||
# Nur schreiben, wenn vorhanden - ein fehlendes Feld ist die
|
||||
# bewusste Aussage "Journalformat ohne Achsenfrage" (siehe
|
||||
# SpecSink.as_achse), kein Datenverlust.
|
||||
if "as_achse" in start:
|
||||
kopf["as_achse"] = start["as_achse"]
|
||||
for k, v in (kopf_extra or collections.OrderedDict()).items():
|
||||
kopf[k] = v
|
||||
objekte.append(kopf)
|
||||
|
||||
Reference in New Issue
Block a user