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:
Yuelin WangandClaude Opus 5 committed 2026-10-22 11:04:16 +02:00
1 parent cccd362bba
commit 1cc163dc11
12 files changed
+815 -150

No files matched your search

+2 -2
View File
@@ -106,7 +106,7 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`.
| `ssg_texttools.lsp` | SSG_TEXT_ERSETZEN, SSG_TEXT_PARALLEL, SSG_TEXT_HOEHE, SSG_TEXT_STIL | Text-Werkzeuge (Ursprung ACADSST `CHGTEXT`/`PARTEXT`/`ALTHOEHE`/`ALTSTIL`): Suchen/Ersetzen in TEXT, MTEXT und Block-Attributen (nutzt `ssg-bt-*`, daher NACH `ssg_blocktools` laden), Text parallel zu Linie/Polylinie/Bogen (lesbar ausgerichtet, Abstand), Hoehe/Stil aendern. Arbeitet in der XY-Ebene des Welt-KS. Reine Kerne `ssg-tt-lesbarer-winkel`, `ssg-tt-versetzt` getestet |
| `ssg_stueckliste.lsp` | SSG_FAEHNCHEN_STUECKLISTE | Positions-Faehnchen + Stuecklistentabelle, komplett AUS DER ZEICHNUNG - es wird keine Datei eingelesen. Der Benutzer waehlt die Elemente aus (`ssg-sl-erkannte-elemente`: `ssget`-Auswahl, Enter ohne Auswahl = alle erkannten im aktuellen Layout; der reine Filter `ssg-sl-elemente-aus-ss` laesst nicht erkannte Objekte still wegfallen, man darf also grosszuegig ein Fenster aufziehen). `ssg-sl-klasse` klassifiziert Blocknamen (`KREISEL_*`/`ECKRAD_*` -> "ILS Kreisel/Eckrad", `VF_*`/`GF_*` -> "ILS Streckengruppe", `GR_*` -> "Rohrgeruest", `AP60*`/`AP110*`/`AP_110*`/`APG110*` -> je eigene Omniflo-Geraden-Gruppe; wirksamer Blockname ueber `ssg-bt-effname`, also auch dynamische Bloecke). Jedes einzelne Element bekommt ein Faehnchen (`ssg-sl-faehnchen`, Rechteck/Kreis/Automation-Rahmen + Text) an der unteren linken Ecke seiner Bounding-Box (aus `csv:get-bbox` - liefert Zentrum+Abmessungen, NICHT Eckpunkte, die Ecke wird im Code daraus berechnet); die Tabelle (`ssg-sl-tabelle-zeichnen`) gruppiert GROB je Elementklasse (keine Sivas-Artikelaufloesung wie `export_sivas.py`) zu einer Zeile mit Anzahl + Laengensumme aus dem `LAENGE`-Attribut (`ssg-sl-gruppieren`/reiner Kern `ssg-sl-gruppieren-werte`), mehrere gleichartige Elemente tragen daher dieselbe Positionsnummer. **Bewusst KEIN Datei-Import wie bei Walz/Bihler**: dort vergab ein externes System die Positionsnummern und lieferte sie als Datei zurueck, die das CAD nur abzeichnete - bei Walz (`MENU/POSITION.LSP`) eine `.pos`/`.zei` aus einem Clipper-Warenwirtschaftssystem mit Koordinaten-Zuordnung, bei Bihler ITB (`Walz_und_Bihler/SBCADBib/ITB-Module_ori.lsp`, `c:bea_pos`) eine `.pos` mit Zuordnung ueber den Entity-Handle. Diese Umwege sind hier nicht nachgebaut (Entscheidung 2026-10): Nummern und Mengen entstehen in dieser Funktion, die Stuecklisten-AUSGABE liefern EXPORTSIVAS/EXPORTCSV aus denselben Block-Attributen. Der alte `c:stueckl` (Laengen-Summierung ueber hartcodierte Walz-Profillayer wie `L-2`=ST, `A-7`=AP110) hat ebenfalls kein Aequivalent im dxfmakros-Datenmodell (Block-Attribute statt Linien-Layer). Braucht `csv:get-bbox`/`csv:get-attrib` aus `export.lsp` und `ssg-bt-effname` aus `ssg_blocktools.lsp` - muss daher NACH beiden geladen werden (siehe `ssg-ensure`-Reihenfolge in `SSG_LIB.mnl`/`ssg_load.lsp`). Reine Kerne `ssg-sl-klasse`, `ssg-sl-gruppieren-werte` in `tests/test_unit.lsp` getestet |
| `KreiselInsert.lsp` | KreiselInsert, KreiselConnect, KreiselRedraw, KreiselQuick, KreiselEdit, KreiselParams, KreiselLabelSetup, KreiselLabelPos, KreiselLabelHoehe, ILS_Eckrad, EckradEdit, KREISEL_WEICHEN | ILS Kreisel und Eckrad komplett in AutoLISP. **Drehrichtungspfeil**: `draw-module` fuegt `data/ils/Drehrichtungspfeil.dwg` auf AN8-Mitte (Block-KS 90 Grad, da die DWG fuer Kreisel Nord->Sued gezeichnet ist; cfg `kreisel.pfeil_winkel`) und SP8-Mitte (+180) mit in den KREISEL_n-Block ein, GUZ = X-Skalierung -1 (reiner Kern `kreisel-pfeil-params`, getestet); Eckrad: ein Pfeil auf der Radmitte mit dem AN8-Winkel (`eckrad-pfeil-params`); bei wiederverwendeter Blockdefinition (KREISEL_n bzw. ECKRAD_n) zieht `kreisel-pfeil-aktualisieren` die Spiegelung per `entmod` nach. **Beruehrpunkte/Weichen** (`kreisel-bp-*`, Befehl KREISEL_WEICHEN, Menue Kreisel-Module -> Weichenpunkte ermitteln): ermittelt aus den Blockparametern (Einfuegepunkt, Drehung, ABSTAND - nicht aus der Geometrie, Modell wie `draw-module`) die Beruehrpunkte aller `KREISEL_*`/`ECKRAD_*` GLEICHER Hoehe (`toleranz_hoehe`): Rad/Rad (`Kreis/Kreis`) und Rad/Laufprofil (`Kreis/Tangente`), nur auf der freien Radseite (aeusserer Halbkreis inkl. Flanken; Eckrad ganz frei), Toleranz `toleranz_beruehrung` (component_defaults.json, Sektion kreisel). Kapsel-Mindestabstand < 2r - tol = KOLLISION (Ueberschneidung), Kreisel Seite an Seite (Beruehrung nur an Flanke/Profil) = KOLLISION `Tangente/Tangente` - beide werden gemeldet, aber nie zur Weiche. Ausgabe: Konsolenliste + Kreis-Marker (Layer `kreisel_weiche`/`kreisel_kollision` aus layer.cfg, XDATA `SSG_KR_WEICHE`, jeder Lauf ersetzt die alten). Der Export schreibt dieselben Punkte als `kreisel_weichen.json` neben `export_raw.json` (`csv:kreisel-weichen-schreiben`, export.lsp); `export_csv.py` uebernimmt sie (`export_neighbors.load_lisp_kreisel_weichen`/`lisp_touch_switches`, Rueckfall auf `compute_kreisel_touch_switches` nur ohne Datei), `export_sivas.py` setzt daraus Merkmal 4403 "Anzahl Weichen ILS". Reine Kerne `kreisel-bp-element`/`-paar`/`-berechnen` in `tests/test_unit.lsp` (`tu-tests-kreisel-bp`, u.a. mit den 11 Einfuegungen aus `tests/testdata/schnittspunkte_kreisel.dxf` -> 10 Beruehrungen) getestet. Eckrad: eigener Dialog `eckrad_edit.dcl` (`eckrad-dialog`, reiner Kern `eckrad-dialog-werte` getestet) beim Einfuegen (ILS_Eckrad, ohne GUI Konsolen-Rueckfall) und per Doppelklick (EckradEdit); kein Abstand/Typ, Drehung als freier Winkel statt fester Kreisel-Ausrichtung. `ils-eckrad-insert` behaelt eine vorhandene NUMMER (Edit -> gleicher Blockname `ECKRAD_n`). Kreiselname: `draw-module` setzt `Bezeichnung` nur noch bei leerem Wert auf die Vorgabe `KreiselN` (eigener Name aus KreiselEdit bleibt erhalten); Beschriftung `kreisel-label-text` = `KREISEL n: <Name> <TYP>_<ABSTAND>` (ohne Namen bzw. bei Vorgabename wie bisher `KREISEL n: <TYP>_<ABSTAND>`); da die Beschriftung als TEXT in der wiederverwendeten Blockdefinition `KREISEL_n_TYP_ABSTAND` steckt, setzt `kreisel-label-aktualisieren` sie dort per `entmod` neu (wirkt auf alle Referenzen der Definition) |
| `VarioFoerderer.lsp` (Wrapper fuer `vf_core.lsp`/`vf_standard.lsp`/`vf_etage.lsp`/`vf_linienzug.lsp`) | FOERDERANLAGE, VARIOFOERDERER_EDIT, VARIOFOERDERER_NACHRUESTEN, Vario_Kette_Merge, VF_SEKTION_RESTORE, VF_WIZARD_AN, VF_WIZARD_AUS | Vario-Foerderanlagen Generator (Typen Standard/Etage/Linienzug); Standard- UND Etage-Typ: Werteingabe per DCL-Dialog, inkl. der vorgelagerten Eingabemodus-Wahl 3D-Linie/Werte manuell (`vfc_eingabemodus`, `vfc-dialog-eingabemodus-waehlen`) - beide Modi laufen seitdem in denselben Basis-/Winkel-Dialog, der Konsolen-Pfad ist nur noch Rueckfall bei abgeschalteter GUI/anderer Sprache. Der Dialog-Ablauf ist zentral in vf_core (`vf-dialog-ablauf`/`vf-dialog-berechnen-einfuegen`, parametrisiert ueber Typ-Registry `*vf-typ-registry*` + Flag `horizontal-p`); `vfs-standard-dialog-ablauf`/`vfe-etage-dialog-ablauf` sind nur noch duenne Wrapper darauf. Beide nutzen dieselben Dialoge `vfs-dialog-eingabe-basis`/`vfs-dialog-winkel-verteilung` und den gemeinsamen L_GF/L_VF-Solve `vf-winkel-solve` (vf_core; berechne-alle-winkel/berechne-winkel-etage teilen ihn); Doppelklick auf VF_n-Block ruft VARIOFOERDERER_EDIT, das per SSG_VF_EDIT-XDATA-Marker ("standard"/"etage"/"linienzug") in den passenden Neuaufbau-Zweig dispatcht (`vfe-edit-ent` fuer Etage, `vfl-edit-ent` fuer Linienzug); Altbestand-Bloecke ohne Marker werden abgewiesen. Linienzug (Modus 1, `vf-linienzug-modus`) zeichnet jede interaktive Eingabe in einem Journal auf (`vfl-in-point`/`-string`/`-real`/`-int`) und schreibt es als XDATA auf den fertigen `VF_n`-Block; ein Abbruch (ESC) loescht dadurch nichts mehr, sondern wickelt die bis dahin gebaute Teil-Geometrie ebenso zu einem Block (`vfl-modus-abbruch-sichern`) - sie bleibt stehen und ist sofort per Doppelklick weiter editierbar/fortsetzbar. Der komplette Linienzug-Hauptstrang (Menue-Fragen von AS-Element bis Kettenende, inkl. GF-Bogen/Vario-Kurve/VF-Einheit-Fortsetzung) laeuft per Default ueber DCL-Dialoge statt Konsole (`*vfl-wizard-mode*`, Befehle VF_WIZARD_AN/VF_WIZARD_AUS): `vfl-in-string`/`-int`/`-real` zeigen bei aktivem Wizard-Modus generische Dialoge aus `dcl/vf_linienzug_wizard.dcl` (Zahl-Eingabe bzw. Mehrfachauswahl aus `vfl-menu`/`vfl-menu-int`) statt `getstring`/`getint`/`getreal` aufzurufen; die ORIGINALEN Konsolen-Fragen (princ + get*, de/en ueber ssg-text) bleiben unveraendert im Code (Fallback bei Fehlern via `vl-catch-all-apply`, siehe `doc/variofoerderer.md`). `vfl-edit-ent` liest das Journal, zeigt die Sektionen ueber einen DCL-Dialog (`vfl-dlg-position`, `dcl/vfl_edit.dcl`) an, kuerzt bei Bedarf auf eine gewaehlte Sektion (stummer Replay der behaltenen Eingaben, umgeht den Wizard-Zweig komplett) und baut danach interaktiv weiter. Linienzug Modus 2 (Pfad+Zielhoehe-Solver, `vf-linienzug-modus2`) nutzt dieselben Wrapper PLUS die neue Objektauswahl-Wrapper-Funktion `vfl-in-selection` (Pfad-Objekte als stabile Entity-Handles journalisiert, neue Journal-Art `"OBJS"`) und schreibt sein Journal unter dem eigenen XDATA-Marker `"linienzug2"`; Doppelklick baut hier NICHT abschnittsweise, sondern per vollem Reset 1:1 aus dem Journal neu auf (`vfl-edit-ent2`, dispatcht aus `vfl-edit-ent` per Marker) - Modus 3 (Vorwaerts-Nachbau) bleibt bewusst reine Konsole (im Code selbst als Legacy/"wird umgebaut" markiert). `Vario_Kette_Merge` (in vf_linienzug.lsp) fuehrt mehrere einzeln gebaute VF_n-Bloecke ab einem gewaehlten Start-Baustein ueber die reale KS_AUS->KS_EIN-Nachbarschaft zu einem Gesamt-Block zusammen (aggregierte Attribute, Luecken-Erkennung). Die interaktiven Editier-Pfade (`vfl-edit-ent` Sektions-Zweig, `vfl-edit-ent2`) rufen den Neuaufbau BEWUSST direkt auf, NICHT ueber `vl-catch-all-apply`: der alte Block ist beim Neuaufbau schon per `entdel` weg, und ein Catch faengt den Abbruch VOR `*error*` ab - der Abbruch-Wickler `vfl-modus-abbruch-sichern` kaeme nie zum Zug und die ganze Kette bliebe als lose Einzelteile ohne Block liegen (Fehlerbild "VF-Block aufgebrochen"). Nur der nicht-interaktive Batch-Konverter `vfl-konvertiere-ent` behaelt den Catch (die Batch-Schleife muss weiterlaufen) und meldet den betroffenen Block. Die Abbruch-Handler setzen zusaetzlich `*ssg-ils-dim*` zurueck, weil der Aufrufer, der das sonst tut, nach einem Abbruch nicht mehr erreicht wird. Recovery-Pfad fuer bereits aufgebrochene Ketten: `VF_SEKTION_RESTORE` (vf_linienzug.lsp) setzt das Ketten-Journal aus der XDATA der losen Objekte zusammen - `SSG_VF_EDIT_SEG` traegt pro Entity den Journal-Abschnitt DER ITERATION, in der es entstand (indiziert mit dem ersten Glied-Index darin; eine VF-Einheit mit eingebetteten Vario-Kurven erzeugt mehrere Glieder in einer Iteration), `SSG_VF_EDIT_PRE` auf den Entities des ersten Gliedes die Praeambel (Startpunkt/Starthoehe/AS). Die `.dbg`-Dateien sind reine AUSGABE und werden von keinem Befehl gelesen (dbgopen legt sie bei jedem Lauf neu an) - Wiederherstellungs-Daten gehoeren in die XDATA. `vf_konstanten.lsp` (von vf_core.lsp vor den drei Submodulen geladen) sLine truncated
| `VarioFoerderer.lsp` (Wrapper fuer `vf_core.lsp`/`vf_standard.lsp`/`vf_etage.lsp`/`vf_linienzug.lsp`) | FOERDERANLAGE, VARIOFOERDERER_EDIT, VARIOFOERDERER_NACHRUESTEN, Vario_Kette_Merge, VF_SEKTION_RESTORE, VF_WIZARD_AN, VF_WIZARD_AUS | Vario-Foerderanlagen Generator (Typen Standard/Etage/Linienzug); Standard- UND Etage-Typ: Werteingabe per DCL-Dialog, inkl. der vorgelagerten Eingabemodus-Wahl 3D-Linie/Werte manuell (`vfc_eingabemodus`, `vfc-dialog-eingabemodus-waehlen`) - beide Modi laufen seitdem in denselben Basis-/Winkel-Dialog, der Konsolen-Pfad ist nur noch Rueckfall bei abgeschalteter GUI/anderer Sprache. Der Dialog-Ablauf ist zentral in vf_core (`vf-dialog-ablauf`/`vf-dialog-berechnen-einfuegen`, parametrisiert ueber Typ-Registry `*vf-typ-registry*` + Flag `horizontal-p`); `vfs-standard-dialog-ablauf`/`vfe-etage-dialog-ablauf` sind nur noch duenne Wrapper darauf. Beide nutzen dieselben Dialoge `vfs-dialog-eingabe-basis`/`vfs-dialog-winkel-verteilung` und den gemeinsamen L_GF/L_VF-Solve `vf-winkel-solve` (vf_core; berechne-alle-winkel/berechne-winkel-etage teilen ihn); Doppelklick auf VF_n-Block ruft VARIOFOERDERER_EDIT, das per SSG_VF_EDIT-XDATA-Marker ("standard"/"etage"/"linienzug") in den passenden Neuaufbau-Zweig dispatcht (`vfe-edit-ent` fuer Etage, `vfl-edit-ent` fuer Linienzug); Altbestand-Bloecke ohne Marker werden abgewiesen. Linienzug (Modus 1, `vf-linienzug-modus`) zeichnet jede interaktive Eingabe in einem Journal auf (`vfl-in-point`/`-string`/`-real`/`-int`) und schreibt es als XDATA auf den fertigen `VF_n`-Block; ein Abbruch (ESC) loescht dadurch nichts mehr, sondern wickelt die bis dahin gebaute Teil-Geometrie ebenso zu einem Block (`vfl-modus-abbruch-sichern`) - sie bleibt stehen und ist sofort per Doppelklick weiter editierbar/fortsetzbar. Der komplette Linienzug-Hauptstrang (Menue-Fragen von AS-Element bis Kettenende, inkl. GF-Bogen/Vario-Kurve/VF-Einheit-Fortsetzung) laeuft per Default ueber DCL-Dialoge statt Konsole (`*vfl-wizard-mode*`, Befehle VF_WIZARD_AN/VF_WIZARD_AUS): `vfl-in-string`/`-int`/`-real` zeigen bei aktivem Wizard-Modus generische Dialoge aus `dcl/vf_linienzug_wizard.dcl` (Zahl-Eingabe bzw. Mehrfachauswahl aus `vfl-menu`/`vfl-menu-int`) statt `getstring`/`getint`/`getreal` aufzurufen; die ORIGINALEN Konsolen-Fragen (princ + get*, de/en ueber ssg-text) bleiben unveraendert im Code (Fallback bei Fehlern via `vl-catch-all-apply`, siehe `doc/variofoerderer.md`). `vfl-edit-ent` liest das Journal, zeigt die Sektionen ueber einen DCL-Dialog (`vfl-dlg-position`, `dcl/vfl_edit.dcl`) an, kuerzt bei Bedarf auf eine gewaehlte Sektion (stummer Replay der behaltenen Eingaben, umgeht den Wizard-Zweig komplett) und baut danach interaktiv weiter. Linienzug Modus 2 (Pfad+Zielhoehe-Solver, `vf-linienzug-modus2`) nutzt dieselben Wrapper PLUS die neue Objektauswahl-Wrapper-Funktion `vfl-in-selection` (Pfad-Objekte als stabile Entity-Handles journalisiert, neue Journal-Art `"OBJS"`) und schreibt sein Journal unter dem eigenen XDATA-Marker `"linienzug2"`; Doppelklick baut hier NICHT abschnittsweise, sondern per vollem Reset 1:1 aus dem Journal neu auf (`vfl-edit-ent2`, dispatcht aus `vfl-edit-ent` per Marker) - Modus 3 (Vorwaerts-Nachbau) bleibt bewusst reine Konsole (im Code selbst als Legacy/"wird umgebaut" markiert). `Vario_Kette_Merge` (in vf_linienzug.lsp) fuehrt mehrere einzeln gebaute VF_n-Bloecke ab einem gewaehlten Start-Baustein ueber die reale KS_AUS->KS_EIN-Nachbarschaft zu einem Gesamt-Block zusammen (aggregierte Attribute, Luecken-Erkennung). Die interaktiven Editier-Pfade (`vfl-edit-ent` Sektions-Zweig, `vfl-edit-ent2`) rufen den Neuaufbau BEWUSST direkt auf, NICHT ueber `vl-catch-all-apply`: der alte Block ist beim Neuaufbau schon per `entdel` weg, und ein Catch faengt den Abbruch VOR `*error*` ab - der Abbruch-Wickler `vfl-modus-abbruch-sichern` kaeme nie zum Zug und die ganze Kette bliebe als lose Einzelteile ohne Block liegen (Fehlerbild "VF-Block aufgebrochen"). Nur der nicht-interaktive Batch-Konverter `vfl-konvertiere-ent` behaelt den Catch (die Batch-Schleife muss weiterlaufen) und meldet den betroffenen Block. Die Abbruch-Handler setzen zusaetzlich `*ssg-ils-dim*` zurueck, weil der Aufrufer, der das sonst tut, nach einem Abbruch nicht mehr erreicht wird. Recovery-Pfad fuer bereits aufgebrochene Ketten: `VF_SEKTION_RESTORE` (vf_linienzug.lsp) setzt das Ketten-Journal aus der XDATA der losen Objekte zusammen - `SSG_VF_EDIT_SEG` traegt pro Entity den Journal-Abschnitt DER ITERATION, in der es entstand (indiziert mit dem ersten Glied-Index darin; eine VF-Einheit mit eingebetteten Vario-Kurven erzeugt mehrere Glieder in einer Iteration), `SSG_VF_EDIT_PRE` auf den Entities des ersten Gliedes die Praeambel (Startpunkt/Starthoehe/AS). Die `.dbg`-Dateien sind reine AUSGABE und werden von keinem Befehl gelesen (dbgopen legt sie bei jedem Lauf neu an) - Wiederherstellungs-Daten gehoeren in die XDATA. `vf_konstanten.lsp` (von vf_core.lsp vor den drei Submodulen geladen) sLine truncated
| `vf_spec.lsp` (von vf_core.lsp NACH vf_linienzug.lsp geladen) | VF_SPEC_BAU | Linienzug-Ketten aus **Eingabedaten** bauen, ohne Dialog und ohne Konsolenfrage (Stufe 1 aus `doc/todo/plan-vf-interactive.md`). Eine **Spec** beschreibt eine Kette in Domaenenwerten (`"links"`, `"winkel"`, `"aussen"`, ja/nein) statt in Menue-Codes; `vsp-journal` uebersetzt sie in ein Eingabe-Journal, `vsp-bau-aus-spec` spielt es per `vfl-journal-replay-start` durch den UNVERAENDERTEN `vf-linienzug-modus` (Replay-Kern `vsp-bau-journal` - nimmt auch ein unveraendert uebernommenes, aufgezeichnetes Journal, z.B. aus `dxf_anlage_export.py`). Damit traegt der fertige Block dasselbe `SSG_VF_EDIT`-Journal wie eine handgebaute Kette und bleibt per Doppelklick editier- und 2D/3D-konvertierbar - kein Journal-Konsument muss etwas von Specs wissen. Wichtige Regeln, die der Uebersetzer kennt: der Menue-Code haengt am Frame (erste Sektion 4 Optionen ohne GF-Bogen, danach 5), `hz` steht nur beim ERSTEN Segment im Journal, und `erst-hor` unterscheidet den horizontalen Erstkoerper einer VF-Einheit (Separator-/Endpunktfragen vorab) vom gewinkelten (keine Frage). Fehlerverhalten: fail fast - `vsp-pruefen` sammelt ALLE Spec-Fehler, gebaut wird dann nichts (nichts wird geraten). Ergebnis-Record je Kette mit `status` (`executed`/`warnung`/`desync`/`abbruch`/`spec-fehler`), Block/Handle/Einfuegepunkt/Attributen, `prompts` (muss 0 sein), `glieder_soll` vs. `glieder_ist` (Desync-Detektor, der auch bei aufgehender Queue greift) und den gesammelten `meldungen`. Headless-Rahmen `vsp-headless-an`/`-aus` (Save/Restore von `*vfl-headless*`, `*vfl-wizard-mode*`, `*ssg-gui-aus*` + zaehlende Eingabe-Stubs). Spec-Datei flach als JSON (`ssg-load-json` liest zeilenweise), erzeugt von `lib/vf_spec_export.py`. Gegenstueck/Referenz in Python: derselbe Emitter, gegen die 5 echten HundM-Ketten rundlaufgeprueft |
| `Gefaellestrecke.lsp` | GEFAELLESTRECKE, GEFAELLESTRECKE_EDIT | Gefaelle-Foerderanlage (AUS -> Staustrecke skaliert -> Separator -> EIN); Modus 1 per DCL-Dialog (Einfuegehoehe, Element-Winkel 30/90); Doppelklick auf GF_n-Block ruft GEFAELLESTRECKE_EDIT (Dialog vorbelegt, Neuaufbau; die Einfuegehoehe kommt aus der aktuellen Z des Einfuegepunkts statt aus XDATA/HOEHE_VON_mm, damit ein in Z verschobener Block per OK direkt auf der neuen Hoehe neu entsteht - gleiches gilt fuer die 2D/3D-Umschaltung `gf-konvertiere-ent`; der GF-Linienzug-Rebuild nutzt weiter die absoluten Segmentpunkte aus XDATA) |
| `export.lsp` | EXPORTSIVAS, EXPORTCSV, OMNI_UPDATE_ATTRIBS, CSV_EXPORT_KONFIG | JSON-Sammlung und Python-Export (Omniflo-Merkmale/Sum-Zeile via lib/export_csv.py). **Gefrorene Layer**: `csv:collect-export-blocks` laesst Bloecke auf gefrorenen Layern aus (ausgeschaltete Layer zaehlen NICHT), Schalter `*csv-gefrorene-ausschliessen*` (Standard an, Toggle im Dialog CSV_EXPORT_KONFIG). Die Hilfsobjekte eines gefrorenen VF_n/GF_n (Separator-/Element-Kopien, Einheiten-/AS-ES-Marker) liegen auf eigenen Layern und werden darum ueber `*csv-ausschluss-enames*` mit ausgelassen (gesetzt in `csv:export-vorbereiten` per `csv:hilfskopien-ausschluss`); erzeugt und nummeriert werden sie trotzdem, damit IDs stabil bleiben. DrawPlanquadrate nutzt dieselbe Sammlung und folgt dem Filter. **Hoehen-Neuberechnung**: erster Schritt von `csv:export-vorbereiten` ist `csv:hoehen-aktualisieren` - in Z verschobene `KREISEL_*`/`ECKRAD_*`/`VF_*`/`GF_*` bekommen ihre Hoehen-Attribute aus der Einfuege-Z neu (reiner Kern `csv:hoehen-werte`, getestet; Formeln/Rundung wie beim Bau: Kreisel HOEHE + MONTAGEHOEHE_m=z/1000, Eckrad HOEHE, GF/VF HOEHE_VON_mm=(fix z), MONTAGEHOEHE_m GF=z/1000 bzw. VF=(VON+BIS)/2000). HOEHE_BIS_mm = VON - (VON_alt - BIS_alt), bewusst NICHT z - DELTA_H_mm (ohne Vorzeichen -> steigende VF falsch, separat abgeschnitten -> 1 mm Abweichung); DELTA_H_mm nur Rueckfall. Nicht verschobene Bloecke (Attribut passt zu z) bleiben unangetastet, damit Bau-Werte und Hand-Korrekturen (z.B. Buehnen-Abzug aus HOEHEN_CHECK) bit-genau erhalten bleiben |
@@ -137,7 +137,7 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`.
| `fahrstrecke.dcl` | VORLAGE fuer OMNI_FAHRSTRECKE (GeruestInsert.lsp, `geruest-traeger-dialog`), gleiche Tile-Keys wie `geruest_traeger.dcl`, Platzhalter `//@VARIANTEN@` |
| `dinformate.dcl` | DIN_FORMATE (DinFormate.lsp) |
| `vfl_edit.dcl` | Linienzug-Editieren/Fortsetzen (vf_linienzug.lsp, `vfl-dlg-position`, aufgerufen aus `vfl-edit-ent`) |
| `vf_linienzug_wizard.dcl` | Linienzug-Assistent (Modus 1, 2 UND 3): zwei generische Einzel-Dialoge (Zahl-Eingabe, Mehrfachauswahl; `vflw-zahl`/`vflw-wahl`, aufgerufen aus `vfl-in-real`/`vfl-in-string`/`vfl-in-int` bei aktivem `*vfl-wizard-mode*`) PLUS sieben Gruppen-Dialoge, die mehrere immer unmittelbar aufeinanderfolgende Fragen in einem Bildschirm zusammenfassen (Kettenstart, AS-Element, GF-Bogen, ES-Element, Vario-Kurve, Gefaelle festlegen, Ziel-Hoehe, Horizontales Stueck - inkl. Punktwahl-Button mit X/Y/Z-Anzeige; `vflw-gruppe-*-impl` in vf_linienzug.lsp fuellen dafuer vorab eine kleine Pending-Queue `*vflw-pending*`, die einzelnen `vfl-in-*`-Aufrufe bleiben dabei unveraendert) PLUS drei Segment-Gruppen-Dialoge fuer die segmentweise Klassifizierung in Modus 2/3 (`vflw_seg_linie_m2` = Typ+GF-Neigung, `vflw_seg_linie_m3` = 4-fach-Typ+kontextabh. Wertfeld, `vflw_seg_bogen` = Typ+Variante; `vflw-seg-*-impl`). Beide Modi heben vor der Frage das aktuelle Pfad-Segment (LINE/ARC) per `vfl-seg-highlight` (`redraw`) in der Zeichnung hervor und zeigen "Segment i/n ..." im Dialogkopf. Details: `doc/variofoerderer.md` |
| `vf_linienzug_wizard.dcl` | Linienzug-Assistent (Modus 1, 2 UND 3): zwei generische Einzel-Dialoge (Zahl-Eingabe, Mehrfachauswahl; `vflw-zahl`/`vflw-wahl`, aufgerufen aus `vfl-in-real`/`vfl-in-string`/`vfl-in-int` bei aktivem `*vfl-wizard-mode*`) PLUS sieben Gruppen-Dialoge, die mehrere immer unmittelbar aufeinanderfolgende Fragen in einem Bildschirm zusammenfassen (Kettenstart, AS-Element, GF-Bogen, ES-Element, Vario-Kurve, Gefaelle festlegen, Ziel-Hoehe, Horizontales Stueck - inkl. Punktwahl-Button mit X/Y/Z-Anzeige; `vflw-gruppe-*-impl` in vf_linienzug.lsp fuellen dafuer vorab eine kleine Pending-Queue `*vflw-pending*`, die einzelnen `vfl-in-*`-Aufrufe bleiben dabei unveraendert). Das AS-Element am Kettenanfang von Modus 1 nutzt die Variante `vflw_as_achse` (Parameter `mit-achse` von `vflw-gruppe-as-impl`): zusaetzlich zu Ja/Nein, Winkel und Seite liegt dort die **Zufuehrungsachse** (2. Punkt, Punktwahl-Button) im selben Fenster, und die Seite ist auch beim 30-Grad-Element waehlbar. Der ES-Fall und die Modi 2/3 bleiben beim unveraenderten `vflw_as` PLUS drei Segment-Gruppen-Dialoge fuer die segmentweise Klassifizierung in Modus 2/3 (`vflw_seg_linie_m2` = Typ+GF-Neigung, `vflw_seg_linie_m3` = 4-fach-Typ+kontextabh. Wertfeld, `vflw_seg_bogen` = Typ+Variante; `vflw-seg-*-impl`). Beide Modi heben vor der Frage das aktuelle Pfad-Segment (LINE/ARC) per `vfl-seg-highlight` (`redraw`) in der Zeichnung hervor und zeigen "Segment i/n ..." im Dialogkopf. Details: `doc/variofoerderer.md` |
## Menue (SSG_LIB.mnu + SSG_LIB.mnl)
+506 -141
View File
@@ -853,57 +853,144 @@
;; im Aufrufer-Code) - beides braucht daher keinen eigenen Parameter.
;; Original-Reihenfolge/-Journal: vfl-in-string (Ja/Nein), dann bei Ja
;; vfl-in-value (Winkel) + vfl-in-string (Seite) - bleibt unveraendert.
(defun vflw-gruppe-as-impl (kopf-key / dat dcl-pfad ergebnis gsetzen gwinkel gseite)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_as" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text kopf-key)))
(set_tile "setzen" "1")
(set_tile "winkel_90" "1")
(set_tile "seite_links" "1")
;; Seitenauswahl ist nur beim 90-Grad-Element wirksam: beim 30-Grad-
;; Element folgt die Seite aus der Zufuehrungsachse, die direkt nach
;; diesem Dialog ueber einen zweiten Punkt abgefragt wird (Nutzer-Vorgabe
;; 2026-10-14). Sie wird darum ausgegraut - und unten auch NICHT in die
;; Pending-Queue gelegt, sonst zoege die Punktfrage sie als vermeintlichen
;; Punkt heraus und erschiene gar nicht.
(action_tile "setzen"
"(mode_tile \"winkel_90\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"winkel_30\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"seite_links\" (if (and (= (get_tile \"setzen\") \"1\") (/= (get_tile \"winkel_30\") \"1\")) 0 1))
(mode_tile \"seite_rechts\" (if (and (= (get_tile \"setzen\") \"1\") (/= (get_tile \"winkel_30\") \"1\")) 0 1))")
(action_tile "winkel_30"
"(mode_tile \"seite_links\" 1)
(mode_tile \"seite_rechts\" 1)")
(action_tile "winkel_90"
"(mode_tile \"seite_links\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"seite_rechts\" (if (= (get_tile \"setzen\") \"1\") 0 1))")
(action_tile "accept"
"(setq gsetzen (get_tile \"setzen\"))
(setq gwinkel (if (= (get_tile \"winkel_30\") \"1\") \"30\" \"90\"))
(setq gseite (if (= (get_tile \"seite_rechts\") \"1\") \"2\" \"1\"))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
(action_tile "back" "(done_dialog 2)")
(setq ergebnis (start_dialog))
(unload_dialog dat)
(cond
((= ergebnis 1)
(if (= gsetzen "1")
;; Winkel-Slot wird von vfl-in-value (AS-Winkel, ueber
;; vf-frage-element-winkel) konsumiert - der erwartet direkt
;; "30"/"90", NICHT den Auswahl-Index wie beim Seite-Slot.
;; Beim 30-Grad-Element wird die Seite NICHT mitgegeben: sie ist im
;; Dialog ausgegraut und folgt aus der Zufuehrungsachse. Ein hier
;; abgelegter Wert wuerde von der nachfolgenden Punktfrage
;; faelschlich als Punkt konsumiert.
(if (= gwinkel "30")
(vflw-pending-push-all (list "1" gwinkel))
(vflw-pending-push-all (list "1" gwinkel gseite)))
(vflw-pending-push-all (list "2"))))
((= ergebnis 2) (setq *vfl-wizard-abbruch-grund* "zurueck"))
(t (setq *vfl-wizard-abbruch-grund* "abbrechen"))
;;
;; achse-basis (NUR das AS-Element am Kettenanfang von Modus 1, Nutzer-Vorgabe
;; 2026-10-21; nil = wie bisher, sonst der Kettenstartpunkt): zeigt
;; zusaetzlich die Zufuehrungsachse (2. Punkt) im SELBEN
;; Bildschirm (Dialog vflw_as_achse) und legt deren Punkt als VIERTEN Wert in
;; die Pending-Queue - Reihenfolge wie im Bau-Ablauf: ja/nein, Winkel, Seite,
;; Achsen-Punkt. 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. Wird KEIN Punkt
;; gewaehlt, geht ein nil in die Queue (Journal-Eintrag "NIL") und die Kette
;; laeuft wie bisher ueber die Richtung des naechsten Klicks.
;; Ohne achse-basis (ES-Element, Modi 2/3) bleibt alles unveraendert -
;; inklusive der ausgegrauten Seite beim 30-Grad-Element.
;;
;; Der Punkt-Button schliesst den Dialog (done_dialog 2) - (getpoint) direkt
;; aus einem action_tile-Callback funktioniert in BricsCAD nicht (siehe
;; vflw-g-punkt-str). Picken laeuft danach auf der Kommandozeile, und MIT
;; Punkt ist der Dialog fertig: er wird NICHT erneut gezeigt (Nutzer-Wunsch
;; 2026-10-22), anders als bei vflw-gruppe-punkt-hoehe-impl, wo nach dem Punkt
;; noch ein Zahlenfeld zu fuellen ist. Nur ein mit ESC abgebrochener Pick
;; zeigt den Dialog wieder. Fuer genau diesen Fall liest schon der Pick-Button
;; die Auswahl aus und der Dialog stellt sie beim Neuaufbau wieder her -
;; new_dialog setzt sonst alle Kacheln auf die DCL-Vorgaben (90/Links) zurueck.
(defun vflw-gruppe-as-impl (kopf-key achse-basis /
mit-achse dat dlg-name ergebnis
gsetzen gwinkel gseite gpunkt fertig)
;; achse-basis traegt BEIDES: nil = kein Achsen-Dialog (ES, Modi 2/3), sonst
;; der Kettenstartpunkt als Basis fuers Gummiband der Punktwahl - genau der
;; Punkt, von dem aus die Zufuehrungsachse gemessen wird.
(setq mit-achse (if achse-basis t nil))
(setq dlg-name (if mit-achse "vflw_as_achse" "vflw_as"))
(setq gsetzen "1" gwinkel "90" gseite "1" gpunkt nil fertig nil)
(while (not fertig)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog dlg-name dat))
(progn (if (and dat (>= dat 0)) (unload_dialog dat)) (setq fertig t))
(progn
(set_tile "kopf" (vflw-clean (ssg-text kopf-key)))
(set_tile "setzen" gsetzen)
(set_tile (if (= gwinkel "30") "winkel_30" "winkel_90") "1")
(set_tile (if (= gseite "2") "seite_rechts" "seite_links") "1")
;; Startpunkt IMMER zeigen - er ist der Ausgangspunkt der Achse und
;; haengt waehrend der Punktwahl am Gummiband. Der 2. Punkt samt
;; daraus entstehender Achsrichtung erscheint nur, wenn der Dialog
;; ueberhaupt noch einmal kommt (abgebrochene Punktwahl, s.u.); nach
;; einem erfolgreichen Pick schliesst der Dialog direkt, die Richtung
;; meldet dann vfl-frage-as-winkel-seite-oder-achse auf der Konsole.
(if mit-achse
(progn
(set_tile "start_anzeige"
(strcat "Startpunkt: " (vflw-g-punkt-str achse-basis)))
(if gpunkt
(set_tile "pt_anzeige"
(strcat (vflw-g-punkt-str gpunkt)
" -> Achse "
(rtos (vfl-planar-hz achse-basis gpunkt) 2 1)
" Grad")))))
;; Seitenauswahl ist OHNE Achsen-Dialog nur beim 90-Grad-Element
;; wirksam: dort folgt die Seite beim 30-Grad-Element aus der
;; Zufuehrungsachse (Nutzer-Vorgabe 2026-10-14). Sie wird darum
;; ausgegraut - und unten auch NICHT in die Pending-Queue gelegt, sonst
;; zoege die Punktfrage sie als vermeintlichen Punkt heraus und
;; erschiene gar nicht. MIT Achsen-Dialog wird die Seite dagegen immer
;; gebraucht (sie ist die AS-Variante, nicht mehr aus der Achse
;; ableitbar) und bleibt daher auch bei 30 Grad waehlbar.
(action_tile "setzen"
(if mit-achse
"(mode_tile \"winkel_90\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"winkel_30\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"seite_links\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"seite_rechts\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"pick\" (if (= (get_tile \"setzen\") \"1\") 0 1))"
"(mode_tile \"winkel_90\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"winkel_30\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"seite_links\" (if (and (= (get_tile \"setzen\") \"1\") (/= (get_tile \"winkel_30\") \"1\")) 0 1))
(mode_tile \"seite_rechts\" (if (and (= (get_tile \"setzen\") \"1\") (/= (get_tile \"winkel_30\") \"1\")) 0 1))"))
(if (not mit-achse)
(progn
(action_tile "winkel_30"
"(mode_tile \"seite_links\" 1)
(mode_tile \"seite_rechts\" 1)")
(action_tile "winkel_90"
"(mode_tile \"seite_links\" (if (= (get_tile \"setzen\") \"1\") 0 1))
(mode_tile \"seite_rechts\" (if (= (get_tile \"setzen\") \"1\") 0 1))")))
(if mit-achse
(action_tile "pick"
"(setq gsetzen (get_tile \"setzen\"))
(setq gwinkel (if (= (get_tile \"winkel_30\") \"1\") \"30\" \"90\"))
(setq gseite (if (= (get_tile \"seite_rechts\") \"1\") \"2\" \"1\"))
(done_dialog 2)"))
(action_tile "accept"
"(setq gsetzen (get_tile \"setzen\"))
(setq gwinkel (if (= (get_tile \"winkel_30\") \"1\") \"30\" \"90\"))
(setq gseite (if (= (get_tile \"seite_rechts\") \"1\") \"2\" \"1\"))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
;; MIT Punkt-Button ist Code 2 belegt -> "Zurueck" nutzt 3 (wie in
;; vflw_punkt_zahl/vflw-gruppe-punkt-hoehe-impl).
(action_tile "back" (if mit-achse "(done_dialog 3)" "(done_dialog 2)"))
(setq ergebnis (start_dialog))
(unload_dialog dat)
;; Punkt-Button: Dialog ist zu, die Zeichnung hat den Fokus, getpoint
;; laeuft normal. MIT Punkt ist danach alles beisammen - der Ablauf
;; geht direkt in den "Ok Weiter"-Zweig, der Dialog wird NICHT noch
;; einmal gezeigt (Nutzer-Wunsch 2026-10-22: er stand vorher zweimal
;; da, einmal vor und einmal nach der Punktwahl - alle uebrigen
;; Angaben stehen beim Druck auf den Button ja schon fest, der Punkt
;; ist die letzte offene). OHNE Punkt (Abbruch mit ESC) wird der
;; Dialog dagegen erneut gezeigt, sonst waere nach einem Fehlklick
;; die ganze Eingabe weg.
(if (and mit-achse (= ergebnis 2))
(progn
(setq gpunkt (vfl-getpoint achse-basis (ssg-text "vfl-prompt-as-achse-p2")))
(if gpunkt (setq ergebnis 1))))
(cond
((= ergebnis 1)
(setq fertig t)
(if (= gsetzen "1")
;; Winkel-Slot wird von vfl-in-value (AS-Winkel, ueber
;; vf-frage-element-winkel) konsumiert - der erwartet direkt
;; "30"/"90", NICHT den Auswahl-Index wie beim Seite-Slot.
(if mit-achse
(vflw-pending-push-all (list "1" gwinkel gseite gpunkt))
;; Ohne Achsen-Dialog beim 30-Grad-Element KEINE Seite
;; mitgeben (siehe Kommentar oben).
(if (= gwinkel "30")
(vflw-pending-push-all (list "1" gwinkel))
(vflw-pending-push-all (list "1" gwinkel gseite))))
(vflw-pending-push-all (list "2"))))
((= ergebnis (if mit-achse 3 2))
(setq fertig t) (setq *vfl-wizard-abbruch-grund* "zurueck"))
;; Code 2 steht hier NUR noch fuer "Punktwahl mit ESC abgebrochen"
;; (mit Punkt ist ergebnis oben auf 1 gesetzt worden): fertig bleibt
;; nil, die Schleife zeigt den Dialog mit den gemerkten Werten
;; erneut - das ist KEIN Abbruch der ganzen Kette.
((and mit-achse (= ergebnis 2)) nil)
(t (setq fertig t) (setq *vfl-wizard-abbruch-grund* "abbrechen"))
)
)
)
)
@@ -1110,16 +1197,22 @@
;; separaten Dialog (vflw-gruppe-gf-steigend-untermodus-impl, weiter unten) -
;; der 3-Optionen-Dialog hier waere dort irrefuehrend (dritte Kachel
;; "Steigend" existiert in diesem Kontext gar nicht mehr).
(defun vflw-gruppe-gf-laenge-modus-impl ( / dat ergebnis gwahl)
;; mit-parallel: T, wenn "Parallel zum Kreisel" hier ueberhaupt baubar ist
;; (Kettenanfang MIT AS-Element UND festgelegter Zufuehrungsachse - ohne
;; Bezugsachse gibt es keinen Abstand). Sonst wird die Kachel deaktiviert,
;; statt sie anzubieten und die Antwort hinterher abzulehnen.
(defun vflw-gruppe-gf-laenge-modus-impl (mit-parallel / dat ergebnis gwahl)
(setq dat (load_dialog (vflw-dcl-pfad)))
(if (not (new_dialog "vflw_gf_laenge_modus" dat))
(if (and dat (>= dat 0)) (unload_dialog dat))
(progn
(set_tile "kopf" (vflw-clean (ssg-text "vfl-gf-laenge-modus-header")))
(if (not mit-parallel) (mode_tile "modus_parallel" 1))
(action_tile "accept"
"(setq gwahl (cond ((= (get_tile \"modus_hoehe\") \"1\") \"2\")
((= (get_tile \"modus_steigend\") \"1\") \"3\")
((= (get_tile \"modus_qc\") \"1\") \"4\")
((= (get_tile \"modus_parallel\") \"1\") \"5\")
(t \"1\")))
(done_dialog 1)")
(action_tile "cancel" "(done_dialog 0)")
@@ -4717,6 +4810,227 @@
)
)
;; ------------------------------------------------------------------
;; "Parallel zum Kreisel weiterbauen" (5. Option der gf-laenge-modus-Frage,
;; intern "parallel"; Nutzer-Vorgabe 2026-10-21): der Regelfall an einer
;; Kreisel-Ausschleusung. Gebaut wird AS -> kurzes GF-Stueck (Standard-
;; Gefaelle) -> GF-Bogen, sodass die Kette danach PARALLEL zur
;; Zufuehrungsachse (= Kreisel-Laufrichtung) weiterlaeuft.
;;
;; EINZIGE Eingabe ist der ABSTAND, gemessen SENKRECHT zur Zufuehrungsachse
;; von KS_EIN des AS-Elements bis KS_AUS des Bogens. AS- und Bogen-Queranteil
;; sind durch die Bausteine fest; die Laenge des GF-Stuecks dazwischen ist die
;; einzige Unbekannte und folgt aus dem Abstand:
;; abstand = quer(AS) + L * sin(hz0 - hz_achse) + quer(Bogen)
;; (L = horizontale Laenge des GF-Stuecks wie bei vfl-insert-gf-segment).
;;
;; Warum hier KEIN Endpunkt geklickt wird: Zufuehrungsachse (2. Punkt) UND
;; AS-Seite stehen seit 2026-10-21 beide schon aus dem AS-Dialog fest (siehe
;; vfl-frage-as-winkel-seite-oder-achse) - die Achse gibt die KS_EIN-Richtung,
;; die Seite die AS-Variante und damit die KS_AUS-Richtung. Die Fahrtrichtung
;; nach dem AS ist dadurch bekannt, bevor irgendetwas gebaut ist.
;;
;; Bogenwahl NICHT fest verdrahtet: gesucht ist der Bogen, der den real
;; gemessenen Grundriss-Schwenk des AS-Blocks GENAU aufhebt
;; (as-turn + bogen-turn = 0, vf-element-plan-turn wie in vfl-qc-linie-mit-gf),
;; immer zur GEGENSEITE der AS-Seite. Mit den vorhandenen Bausteinen ergibt
;; das genau die Vorgabe-Tabelle: AS 30 Grad -> 30-Grad-Bogen, AS 90 Grad ->
;; 90-Grad-Bogen (AS links -> Bogen rechts und umgekehrt). Genau DARUM wird
;; gemessen statt eine Tabelle gepflegt: die Zuordnung folgt den Bloecken,
;; auch wenn ein Element anders vermessen ist als erwartet. Passt kein Bogen
;; in die Toleranz, wird das gemeldet statt schief gebaut.
;;
;; Quer-Anteile werden GEMESSEN, nicht gerechnet: AS ueber sein echtes KS_EIN
;; (der Klickpunkt ist der Block-URSPRUNG, KS_EIN liegt beim Drehteller-
;; Anschluss seitlich versetzt - siehe vfl-block-ks-punkt), der Bogen ueber
;; einen Probe-Bau, der danach wieder geloescht wird (dasselbe Prinzip und
;; derselbe Grund wie in vfl-qc-linie-mit-gf: die Bogen-Bausteine haengen an
;; einem bereits geneigten Rahmen, Messen ist zuverlaessiger als eine weitere
;; Rotationsformel). Der Probe-Bau haengt nur an der ANKUNFTS-Orientierung,
;; nicht an der Position - er liefert daher denselben Versatz wie der spaetere
;; echte Bau hinter dem GF-Stueck.
;;
;; KEIN Separator zwischen GF-Stueck und Bogen (Nutzer-Entscheidung
;; 2026-10-21) - der Abstand wird dadurch exakt getroffen.
;;
;; Die Kettenende-Frage stellt dieser Zweig SELBST, und zwar NACH dem Bau
;; (anders als die Zweige A/B/C, die sie vorab brauchen, um den ES-Fussabdruck
;; von der gepickten Laenge abzuziehen): hier gibt es keine gepickte Laenge,
;; von der etwas abzuziehen waere, und erst nach dem Bogen steht fest, wo die
;; Kette weiterlaeuft.
;;
;; Nutzt - wie vfl-qc-linie-mit-gf - die dynamisch sichtbaren Lokalvariablen
;; des Aufrufers vf-linienzug-modus (frame, p-aktuell, as-seite, as-vorhanden,
;; kettenanfang, fertig, es-seite, anzahl-gf, letzter-typ,
;; gf-kettenende-antwort).
;;
;; Die beiden Rechenkerne stehen bewusst als eigene, zeichnungsfreie
;; Funktionen davor (vfl-par-bogen-waehlen / vfl-par-gf-laenge) - so sind sie
;; in tests/test_unit.lsp ohne Zeichnung pruefbar (tu-tests-vfl-parallel).
;; Welcher Bogen hebt den AS-Schwenk auf? kandidaten ist eine Liste
;; (winkel . plan-turn) der real vorhandenen Bogen-Varianten EINER Seite;
;; gesucht ist der mit dem kleinsten |as-turn + plan-turn| innerhalb von tol.
;; Rueckgabe: Winkel (Int) oder nil, wenn keiner passt.
(defun vfl-par-bogen-waehlen (as-turn kandidaten tol / best best-diff diff kand)
(setq best nil best-diff nil)
(foreach kand kandidaten
(setq diff (abs (+ as-turn (cdr kand))))
(if (or (null best-diff) (< diff best-diff))
(setq best-diff diff best (car kand))))
(if (and best (<= best-diff tol)) best nil))
;; Horizontale Laenge des GF-Stuecks zwischen AS und Bogen.
;; abstand = gewuenschter Abstand (Betrag, senkrecht zur Zufuehrungsachse)
;; perp-as = Querversatz AS-KS_EIN -> AS-KS_AUS (vorzeichenbehaftet)
;; perp-bog = Querversatz des Bogens (KS_EIN -> KS_AUS, vorzeichenbehaftet)
;; k = sin(hz0 - hz-achse), Querversatz je Laengeneinheit GF
;; Die Seite steht ueber die AS-Variante schon fest - sie steckt im Vorzeichen
;; von k. Der Nutzer tippt deshalb einen reinen Betrag, das Ziel-Vorzeichen
;; wird daraus abgeleitet. Rueckgabe: Laenge (kann <= 0 sein, wenn der Abstand
;; kleiner ist als der feste Anteil von AS + Bogen) oder nil, wenn das AS gar
;; nicht aus der Achse schwenkt (k ~ 0, keine Loesung).
(defun vfl-par-gf-laenge (abstand perp-as perp-bog k / s)
(if (< (abs k) 1e-6)
nil
(progn
(setq s (if (> k 0.0) 1.0 -1.0))
(/ (- (* (abs abstand) s) perp-as perp-bog) k))))
(defun vfl-gf-parallel-kreisel ( / as-w as-turn bogen-seite bogen-winkel
kandidaten tol w
abstand abstand-min
checkpoint as-ent p-ein hz-a rad-a nx ny hz0 k
perp-as perp-bogen l-gf perp-ist rad3
acc-gfbogen-save acc-sep-save basis-frame
probe-checkpoint probe-frame dxp dyp
hz-nach erg-bogen)
(if (or (not kettenanfang) (not as-vorhanden) (null *vfl-as-basis-hz*))
(vfl-meldung (ssg-text "vfl-par-nur-kettenanfang"))
(progn
(setq as-w (vfl-as-winkel))
(setq as-turn (vf-element-plan-turn (strcat "AS_Element_" as-w "_" as-seite)))
;; Bogen zur Gegenseite der AS-Seite; Winkel aus den real vorhandenen
;; Varianten, der den AS-Schwenk am besten aufhebt.
(setq bogen-seite (if (= as-seite "rechts") "links" "rechts"))
(setq kandidaten '())
(foreach w *vfk-gf-bogen-winkel*
(setq kandidaten
(cons (cons w (vf-element-plan-turn (gf-bogen-blockname w bogen-seite)))
kandidaten)))
(setq tol 2.0) ; gleiche Toleranz wie in vfl-qc-linie-mit-gf
(setq bogen-winkel (vfl-par-bogen-waehlen as-turn (reverse kandidaten) tol))
(if (null bogen-winkel)
(vfl-meldung (ssg-textf "vfl-par-kein-bogen"
(list (rtos as-turn 2 1) bogen-seite)))
(progn
;; --- Abstand abfragen (VOR dem Bau: ein Abbruch laesst dann
;; garantiert nichts Halbfertiges stehen) ---
(setq abstand (vfl-in-real (ssg-text "vfl-par-prompt-abstand")))
(if (or (null abstand) (<= (abs abstand) 0.0))
(princ (ssg-text "vfl-abgebrochen"))
(progn
(setq checkpoint (ssg-lastent-ohne-attribute))
;; --- AS bauen: KS_EIN-Achse auf die Zufuehrungsachse ---
(setq frame (vfl-insert-as-element-auf-achse "GF" p-aktuell
*vfl-as-basis-hz* as-seite))
(setq as-ent (entlast))
(setq p-ein (if as-ent
(vfl-block-ks-punkt (vlax-ename->vla-object as-ent) "KS_EIN")
nil))
(if (null p-ein) (setq p-ein p-aktuell))
(setq hz-a *vfl-as-basis-hz*)
(setq rad-a (* (float hz-a) (/ pi 180.0)))
(setq nx (- (sin rad-a)) ny (cos rad-a)) ; Normale zur Zufuehrungsachse
(setq hz0 (car (frame->hz-winkel frame))) ; Fahrtrichtung nach dem AS
(setq perp-as (+ (* (- (car (car frame)) (car p-ein)) nx)
(* (- (cadr (car frame)) (cadr p-ein)) ny)))
;; --- Bogen probeweise bauen, Querversatz messen, wieder weg ---
;; WICHTIG - die Probe muss auf GENAU dem Rahmen sitzen, auf dem
;; der echte Bogen spaeter steht, sonst misst sie eine andere
;; Lage. Das ist NICHT der Ausgangsrahmen des AS-Elements: der
;; traegt die reale Neigung des AS-KS_AUS, waehrend der Bogen
;; hinter dem GF-Stueck auf dessen IDEALISIERTEM Rahmen sitzt
;; (make-frame-from-dir + hz-winkel->xu mit genau hz0 und dem
;; Standard-Gefaellewinkel, siehe vfl-insert-gf-segment). Realer
;; Befund 2026-10-22 (BricsCAD): auf dem AS-Rahmen gemessen fiel
;; der Probe-Bogen 31.3 mm statt 22.6 mm - die daraus falsche
;; Verdrehung liess den Abstand um 0.5 mm danebenliegen. Mit dem
;; idealisierten Rahmen sind Probe und echter Bau per
;; Konstruktion identisch.
;; vfl-insert-gf-bogen-block zaehlt intern mit (GF_Bogen_L/R_xx) -
;; die Probe darf diese Zaehler nicht verfaelschen.
(setq basis-frame
(make-frame-from-dir (car frame)
(hz-winkel->xu hz0 (fix *vfk-gefaelle-winkel*))))
(setq acc-gfbogen-save *vfl-acc-gfbogen* acc-sep-save *vfl-acc-separator*)
(setq probe-checkpoint (ssg-lastent-ohne-attribute))
(setq probe-frame (vfl-insert-gf-bogen-block basis-frame bogen-winkel bogen-seite))
(setq dxp (- (car (car probe-frame)) (car (car basis-frame))))
(setq dyp (- (cadr (car probe-frame)) (cadr (car basis-frame))))
(vfl-loesche-seit probe-checkpoint)
(setq *vfl-acc-gfbogen* acc-gfbogen-save *vfl-acc-separator* acc-sep-save)
(setq perp-bogen (+ (* dxp nx) (* dyp ny)))
;; --- GF-Laenge aus dem Abstand loesen (reiner Kern) ---
(setq k (sin (* (- hz0 hz-a) (/ pi 180.0))))
(setq l-gf (vfl-par-gf-laenge abstand perp-as perp-bogen k))
(if (null l-gf)
(progn
(vfl-meldung (ssg-text "vfl-par-kein-versatz"))
(vfl-loesche-seit checkpoint))
(progn
;; Kleinster erreichbarer Abstand = der von AS und Bogen
;; allein (GF-Stueck = 0).
(setq abstand-min (abs (+ perp-as perp-bogen)))
(if (< l-gf 1.0)
(progn
(vfl-meldung (ssg-textf "vfl-par-zu-klein"
(list (rtos (abs abstand) 2 1) (rtos abstand-min 2 1))))
(vfl-loesche-seit checkpoint))
(progn
(setq rad3 (* (float (fix *vfk-gefaelle-winkel*)) (/ pi 180.0)))
(setq frame (vfl-insert-gf-segment (car frame) hz0 l-gf
(fix *vfk-gefaelle-winkel*)))
(vfl-acc-gf-seg (/ l-gf (cos rad3)) (fix *vfk-gefaelle-winkel*))
(setq anzahl-gf (1+ anzahl-gf))
(setq frame (vfl-insert-gf-bogen-block frame bogen-winkel bogen-seite))
(setq p-aktuell (car frame))
(setq letzter-typ "GF")
(setq perp-ist (abs (+ (* (- (car p-aktuell) (car p-ein)) nx)
(* (- (cadr p-aktuell) (cadr p-ein)) ny))))
(princ (ssg-textf "vfl-par-erfolg"
(list (rtos l-gf 2 1) (itoa bogen-winkel) bogen-seite
(rtos perp-ist 2 1))))
;; --- Kettenende/ES (siehe Kopfkommentar: hier NACH dem Bau) ---
(setq hz-nach (car (frame->hz-winkel frame)))
(setq gf-kettenende-antwort (vfl-frage-ist-kettenende))
(cond
((= gf-kettenende-antwort "1")
(setq es-seite (vfl-frage-es-seite nil))
;; Uebergangsbogen pruefen (No-Op, wenn die Kette hier
;; ohnehin auf der Fallend-Basis ankommt) - dieselbe
;; Absicherung wie im gemeinsamen Abschluss der Zweige
;; A/B/C.
(setq erg-bogen (vfl-gf-bogen-uebergang frame *vfk-gefaelle-winkel*))
(setq frame (car erg-bogen))
(setq frame (vfl-insert-es-element "GF" frame hz-nach
*vfk-gefaelle-winkel* (caddr (car frame)) es-seite))
(setq p-aktuell (car frame))
(setq fertig t))
((= gf-kettenende-antwort "2") (setq fertig t))
)
)
)
)
)
)
)
)
)
)
)
)
;; ------------------------------------------------------------------
;; "QC Linie mit GF und U-Form" (Option 5 der gf-laenge-modus-Frage,
;; Nutzer-Vorgabe 2026-09-13, nach mehreren Praezisierungsrunden - inkl. echter
@@ -5227,7 +5541,7 @@
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-es-nein-zielpunkt"))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-es-setzen-frage")))
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-es-setzen-frage" nil)))
(setq es-antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-es-nein-zielpunkt")) 1
"vfl-es-setzen-frage"))
@@ -6589,25 +6903,33 @@
(progn
;; BASISLINIEN-Modus: das AS gehoert mit seinem KS_EIN auf die
;; Zufuehrungsachse (*vfl-as-basis-hz*), nicht mit dem KS_AUS auf die
;; Kettenrichtung. Die Seite ergibt sich aus der Lage des naechsten
;; Kettenpunkts zur Basislinie (Nutzer-Vorgabe 2026-10-14): der liegt in
;; Richtung hz-neu, deltaL entfernt.
;; Kettenrichtung.
;;
;; Die Seite kommt seit 2026-10-21 normalerweise AUS DEM DIALOG
;; (*vfl-as-seite-explizit*) - sie ist die AS-Variante und darf dann
;; nicht mehr ueberschrieben werden. Nur beim Replay eines
;; Alt-Achsen-Journals (30 Grad, 2026-10-14..10-20) wurde sie gar nicht
;; gefragt; dort ergibt sie sich wie bisher aus der Lage des naechsten
;; Kettenpunkts zur Basislinie: der liegt in Richtung hz-neu, deltaL
;; entfernt.
(if *vfl-as-basis-hz*
(progn
(setq rad (* (float hz-neu) (/ pi 180.0)))
(setq ziel (list (+ (car p-aktuell) (* deltaL (cos rad)))
(+ (cadr p-aktuell) (* deltaL (sin rad)))
(caddr p-aktuell)))
(setq as-seite (vfl-seite-aus-lage *vfl-as-basis-p1*
(list (+ (car *vfl-as-basis-p1*)
(cos (* *vfl-as-basis-hz* (/ pi 180.0))))
(+ (cadr *vfl-as-basis-p1*)
(sin (* *vfl-as-basis-hz* (/ pi 180.0))))
0.0)
ziel))
;; Masse auf die jetzt bekannte Seite umstellen (Winkel steht schon).
(vf-set-as-masse (vfl-as-winkel) as-seite)
(princ (ssg-textf "vfl-as-seite-ermittelt" (list as-seite)))
(if (not *vfl-as-seite-explizit*)
(progn
(setq rad (* (float hz-neu) (/ pi 180.0)))
(setq ziel (list (+ (car p-aktuell) (* deltaL (cos rad)))
(+ (cadr p-aktuell) (* deltaL (sin rad)))
(caddr p-aktuell)))
(setq as-seite (vfl-seite-aus-lage *vfl-as-basis-p1*
(list (+ (car *vfl-as-basis-p1*)
(cos (* *vfl-as-basis-hz* (/ pi 180.0))))
(+ (cadr *vfl-as-basis-p1*)
(sin (* *vfl-as-basis-hz* (/ pi 180.0))))
0.0)
ziel))
;; Masse auf die jetzt bekannte Seite umstellen (Winkel steht schon).
(vf-set-as-masse (vfl-as-winkel) as-seite)
(princ (ssg-textf "vfl-as-seite-ermittelt" (list as-seite)))))
;; KS_EIN auf die Zufuehrungsachse legen - NICHT KS_AUS auf hz-neu.
(setq frame (vfl-insert-as-element-auf-achse "GF" p-aktuell
*vfl-as-basis-hz* as-seite)))
@@ -6695,72 +7017,57 @@
(vf-set-as-masse *vfl-as-winkel* as-seite) ; Masse fuer Variante
as-seite)
;; AS-Winkel abfragen und danach ENTWEDER die Seite ODER - nur beim 30-Grad-
;; Element - eine Zufuehrungsachse (Nutzer-Vorgabe 2026-10-14: "bitte erstmal
;; nur fuer 30 Grad").
;; AS-Winkel abfragen, danach die Seite UND - fuer BEIDE Winkel - eine
;; optionale Zufuehrungsachse (Nutzer-Vorgabe 2026-10-21; bis dahin galt die
;; Achse nur fuer das 30-Grad-Element und ERSETZTE dort die Seitenfrage).
;;
;; Warum erst NACH dem Winkel: die Achsenfrage gilt nur fuer AS_Element_30, der
;; Winkel muss also schon feststehen. Der Kettenstartpunkt bleibt davon
;; unberuehrt - er ist und bleibt der echte Sitz des AS-Elements; die Achse
;; entsteht aus ihm plus EINEM weiteren Punkt, der nur die Richtung gibt.
;; Warum beides und warum in dieser Reihenfolge: die zwei Angaben beschreiben
;; verschiedene Dinge und legen das AS erst ZUSAMMEN vollstaendig fest -
;; 2. Punkt -> Richtung der KS_EIN-Achse (die Zufuehrung, z.B. die
;; Kreisel-Laufrichtung); der Startpunkt bleibt der Sitz des
;; AS-Elements, der zweite Punkt gibt nur die Richtung.
;; links/rechts -> AS-Variante und damit die KS_AUS-Richtung, also wohin die
;; Kette nach dem Ausschleusen laeuft.
;; Damit steht die Kettenrichtung schon vor dem ersten Streckenklick fest -
;; Voraussetzung fuer "Parallel zum Kreisel" (vfl-gf-parallel-kreisel), das
;; gar keinen Endpunkt mehr klickt, sondern nur noch einen Abstand bekommt.
;; Die Achse ist optional: ohne Punkt laeuft alles wie frueher ueber die
;; Richtung des naechsten Klicks.
;;
;; JOURNAL - deswegen ist die Reihenfolge so gewaehlt:
;; 90 Grad : [STR winkel] [STR seite] <- Eintrag fuer Eintrag wie bisher
;; 30 + Seite : [STR winkel] [STR seite] <- ebenfalls wie bisher
;; 30 + Achse : [STR winkel] [PT p2]
;; Die Modus-Wahl selbst wird BEWUSST NICHT journalisiert: sie ist aus der Art
;; des naechsten Eintrags eindeutig rekonstruierbar (PT = Achse, STR = Seite).
;; Dadurch bleiben alle aelteren Ketten ohne Sonderbehandlung editierbar.
;; JOURNAL - Eintragsfolge an dieser Stelle:
;; neu : [STR winkel] [STR seite] [PT p2] (ohne Achse: [NIL])
;; 30 + Achse alt : [STR winkel] [PT p2] (2026-10-14..10-20)
;; ganz alt : [STR winkel] [STR seite]
;; Welche Fragen gestellt werden, wird BEWUSST NICHT journalisiert: es ist aus
;; der Art des jeweils naechsten Eintrags eindeutig rekonstruierbar (PT direkt
;; nach dem Winkel = Alt-Achse ohne Seite; PT/NIL nach der Seite = neue Achse;
;; alles andere = Altbestand ohne Achse). Dadurch bleiben alle aelteren Ketten
;; ohne Sonderbehandlung editierbar. Ein neues Glied beginnt immer mit seiner
;; Menue-Antwort (STR) bzw. - im erzwungenen QC-Modus - mit einer Richtung
;; (REAL); ein PT/NIL an dieser Stelle kann daher nur die Achse sein.
;;
;; Setzt *vfl-as-basis-hz*/-p1 (nil, wenn keine Achse gewaehlt wurde).
;; Rueckgabe: Seite. Im Achsen-Modus vorlaeufig "links" - die endgueltige Seite
;; folgt aus der Lage des naechsten Kettenpunkts und wird in
;; vfl-kettenanfang-baustein gesetzt (Laengsverbrauch ist seitengleich, nur das
;; Vorzeichen des Querversatzes dreht).
;; Setzt *vfl-as-basis-hz*/-p1 (nil, wenn keine Achse gewaehlt wurde) und
;; *vfl-as-seite-explizit* (T, wenn die Seite wirklich gefragt wurde - nur
;; dann darf vfl-kettenanfang-baustein sie NICHT mehr aus der Lage des
;; naechsten Punkts ueberschreiben).
;; Rueckgabe: Seite. Beim Alt-Achsen-Journal vorlaeufig "links" - die
;; endgueltige Seite folgt dort aus der Lage des naechsten Kettenpunkts
;; (Laengsverbrauch ist seitengleich, nur das Vorzeichen des Querversatzes
;; dreht).
(defun vfl-frage-as-winkel-seite-oder-achse (seiten-headerkey startpunkt
/ antwort as-seite modus p2)
(setq *vfl-as-basis-hz* nil *vfl-as-basis-p1* nil)
/ antwort as-seite p2 mit-seite mit-achse)
(setq *vfl-as-basis-hz* nil *vfl-as-basis-p1* nil *vfl-as-seite-explizit* nil)
(princ (ssg-text "vf-winkel-aus-header"))
(princ (ssg-text "vf-winkel-90"))
(princ (ssg-text "vf-winkel-30"))
(setq *vfl-as-winkel*
(vfl-in-value "STR"
(function (lambda () (vf-frage-element-winkel "vf-winkel-aus-header")))))
;; Beim 30-Grad-Element wird die Seite NICHT gefragt (im Gruppendialog
;; ausgegraut) - stattdessen direkt die Zufuehrungsachse ueber einen zweiten
;; Punkt. Es gibt also keine Modus-Frage: 30 Grad = Achse, 90 Grad = Seite.
;;
;; AUSNAHME Replay: aeltere 30-Grad-Ketten wurden noch MIT Seitenfrage
;; gebaut, ihr Journal hat an dieser Stelle ein "STR" statt eines "PT".
;; Steht dort ein STR, ist es ein Altbestand und es wird wie frueher die
;; Seite gelesen - sonst liefe die Queue aus dem Takt.
(setq modus (if (= *vfl-as-winkel* "30") "1" "2"))
(if (and (= modus "1") *vfl-replay-queue* (/= (vfl-replay-peek-art) "PT"))
(setq modus "2"))
(if (= modus "1")
(progn
;; Der AS-Gruppendialog legt beim 30-Grad-Element bewusst KEINE Seite in
;; die Pending-Queue (siehe vflw-gruppe-as-impl) - die Queue ist hier
;; also leer und der folgende vfl-in-point fragt wirklich nach dem Punkt.
;; Zweiter Punkt gibt NUR die Richtung der Zufuehrung; der Startpunkt
;; bleibt der Sitz des AS-Elements.
(setq p2 (vfl-in-point startpunkt (ssg-text "vfl-prompt-as-achse-p2")))
(if (or (null p2)
(and (< (abs (- (car p2) (car startpunkt))) 1e-9)
(< (abs (- (cadr p2) (cadr startpunkt))) 1e-9)))
(progn
;; Keine brauchbare Richtung -> auf die Seitenfrage zurueckfallen.
;; (Der PT-Eintrag steht dann trotzdem im Journal und wird beim
;; Replay wieder als "Achse" gelesen - deshalb hier KEIN stiller
;; Rueckfall, sondern Abbruch.)
(princ (ssg-text "vfl-basislinie-punkte-gleich"))
(exit))
(progn
(setq *vfl-as-basis-p1* startpunkt)
(setq *vfl-as-basis-hz* (vfl-planar-hz startpunkt p2))
(princ (ssg-textf "vfl-basislinie-info" (list (rtos *vfl-as-basis-hz* 2 1))))
(setq as-seite "links"))) ; vorlaeufig, s.o.
)
;; --- Seite (links/rechts) ---
;; Uebersprungen NUR beim Replay eines Alt-Achsen-Journals (30 Grad,
;; 2026-10-14..10-20): dort steht direkt hinter dem Winkel der Achsenpunkt.
(setq mit-seite (not (and *vfl-replay-queue* (= (vfl-replay-peek-art) "PT"))))
(if mit-seite
(progn
(princ (ssg-text seiten-headerkey))
(princ (ssg-text "gf-seite-links"))
@@ -6768,7 +7075,34 @@
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "gf-seite-links") (ssg-text "gf-seite-rechts")) 1
seiten-headerkey))
(setq as-seite (if (= antwort "2") "rechts" "links"))))
(setq as-seite (if (= antwort "2") "rechts" "links"))
(setq *vfl-as-seite-explizit* t))
(setq as-seite "links")) ; vorlaeufig, s.o.
;; --- Zufuehrungsachse (2. Punkt, optional) ---
;; Beim Replay nur lesen, wenn an dieser Stelle wirklich ein Achsen-Eintrag
;; steht (PT = Punkt, NIL = bewusst ohne Achse gebaut) - sonst ist es ein
;; Altbestand ohne diese Frage und der naechste Eintrag gehoert schon zum
;; ersten Glied.
(setq mit-achse
(if *vfl-replay-queue*
(if (member (vfl-replay-peek-art) '("PT" "NIL")) t nil)
t))
(if mit-achse
(progn
(setq p2 (vfl-in-point startpunkt (ssg-text "vfl-prompt-as-achse-p2")))
(if (and p2
(or (> (abs (- (car p2) (car startpunkt))) 1e-9)
(> (abs (- (cadr p2) (cadr startpunkt))) 1e-9)))
(progn
(setq *vfl-as-basis-p1* startpunkt)
(setq *vfl-as-basis-hz* (vfl-planar-hz startpunkt p2))
(princ (ssg-textf "vfl-basislinie-info" (list (rtos *vfl-as-basis-hz* 2 1)))))
;; Kein (brauchbarer) Punkt -> keine Achse. KEIN Abbruch mehr: die
;; Achse ist seit 2026-10-21 optional, die Kette laeuft dann wie
;; frueher ueber die Richtung des naechsten Klicks. Der Journal-
;; Eintrag (PT bzw. NIL) steht trotzdem und wird beim Replay wieder
;; genauso gelesen.
(princ (ssg-text "vfl-basislinie-ohne")))))
(vf-set-as-masse *vfl-as-winkel* as-seite)
as-seite)
@@ -7062,7 +7396,7 @@
startpunkt-familie gf-kettenende-antwort
vorab-antwort vorab-es-gewuenscht vorab-es-seite
save-eindx-vorab save-eindz-vorab erg-es vorab-ziel-punkt
rad3-sep sep-eindx-add sep-eindz-add)
rad3-sep sep-eindx-add sep-eindz-add gf-parallel-moeglich)
;; "Zurueck"/"Abbrechen"-Flag ganz am Anfang zuruecksetzen (auch schon vor
;; der ersten Praeambel-Frage) - sonst wuerde ein von einem VORHERIGEN Lauf
;; noch gesetztes Flag (z.B. nach "Abbrechen") den allerersten vfl-in-*-
@@ -7162,7 +7496,10 @@
(setq as-vorhanden nil))
(progn
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-as-setzen-frage")))
;; startpunkt = Zufuehrungsachse (2. Punkt) gehoert hier in DENSELBEN
;; Dialog wie Winkel/Seite (Nutzer-Vorgabe 2026-10-21); der Punkt ist
;; zugleich die Gummiband-Basis der Punktwahl.
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-as-setzen-frage" startpunkt)))
(princ (ssg-text "vfl-as-setzen-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-as-nein"))
@@ -7175,10 +7512,13 @@
;; werden - sonst wuerde ein WINKEL_AS-Attribut den Wert der VORHERIGEN Kette
;; tragen, wenn diesmal kein AS gewaehlt wird.
(setq *vfl-as-winkel* nil *vfl-es-winkel* nil)
;; Winkel, dann Seite ODER (nur bei 30 Grad) eine Zufuehrungsachse - siehe
;; vfl-frage-as-winkel-seite-oder-achse. Im Achsen-Modus entfaellt die
;; Seitenfrage; die Seite folgt aus der Lage des naechsten Kettenpunkts und
;; wird in vfl-kettenanfang-baustein bestimmt.
;; Gilt genauso fuer die Achsen-Globals: ohne AS-Element bleibt sonst die
;; Zufuehrungsachse der VORHERIGEN Kette stehen.
(setq *vfl-as-basis-hz* nil *vfl-as-basis-p1* nil *vfl-as-seite-explizit* nil)
;; Winkel, dann Seite UND eine optionale Zufuehrungsachse (2. Punkt) - siehe
;; vfl-frage-as-winkel-seite-oder-achse. Beide zusammen legen AS-Lage und
;; Kettenrichtung fest; ohne Achse laeuft alles wie frueher ueber die
;; Richtung des naechsten Klicks.
(if as-vorhanden
(setq as-seite (vfl-frage-as-winkel-seite-oder-achse "vfl-aus-seite-header" startpunkt)))
;; ES-Seite wird erst am Kettenende abgefragt (siehe vfl-frage-es-seite),
@@ -7474,18 +7814,38 @@
(setq *vfl-forciere-qc-modus* nil)
)
(progn
(if (vfl-wizard-aktiv) (vl-catch-all-apply 'vflw-gruppe-gf-laenge-modus-impl nil))
;; "Parallel zum Kreisel" braucht AS-Element UND Zufuehrungsachse
;; als Bezug - sonst gibt es keinen Abstand, zu dem parallel
;; gebaut werden koennte.
(setq gf-parallel-moeglich
(and kettenanfang as-vorhanden *vfl-as-basis-hz*))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-gf-laenge-modus-impl (list gf-parallel-moeglich)))
(princ (ssg-text "vfl-gf-laenge-modus-header"))
(princ (ssg-text "vfl-gf-laenge-modus-opt-endpunkt"))
(princ (ssg-text "vfl-gf-laenge-modus-opt-hoehe"))
(princ (ssg-text "vfl-gf-laenge-modus-opt-steigend"))
(princ (ssg-text "vfl-gf-laenge-modus-opt-qc"))
(if gf-parallel-moeglich (princ (ssg-text "vfl-gf-laenge-modus-opt-parallel")))
(setq gf-laenge-modus (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-gf-laenge-modus-opt-endpunkt")
(ssg-text "vfl-gf-laenge-modus-opt-hoehe")
(ssg-text "vfl-gf-laenge-modus-opt-steigend")
(ssg-text "vfl-gf-laenge-modus-opt-qc")) 1
(if gf-parallel-moeglich
(list (ssg-text "vfl-gf-laenge-modus-opt-endpunkt")
(ssg-text "vfl-gf-laenge-modus-opt-hoehe")
(ssg-text "vfl-gf-laenge-modus-opt-steigend")
(ssg-text "vfl-gf-laenge-modus-opt-qc")
(ssg-text "vfl-gf-laenge-modus-opt-parallel"))
(list (ssg-text "vfl-gf-laenge-modus-opt-endpunkt")
(ssg-text "vfl-gf-laenge-modus-opt-hoehe")
(ssg-text "vfl-gf-laenge-modus-opt-steigend")
(ssg-text "vfl-gf-laenge-modus-opt-qc"))) 1
"vfl-gf-laenge-modus-header"))
;; Menue-Antwort "5" (die neue 5. Option) auf einen SPRECHENDEN
;; internen Wert abbilden: "5" ist intern bereits durch die
;; erzwungene U-Form belegt (*vfl-forciere-qc-modus*, Linienzug-
;; Modus 5), die diese Frage ueberspringt und daher nie eine "5"
;; ins Journal schreibt. Journalisiert wird weiterhin die rohe
;; Menue-Antwort; die Abbildung passiert beim Replay identisch.
(if (= gf-laenge-modus "5") (setq gf-laenge-modus "parallel"))
)
)
;; Option 4 ("QC Linie mit GF"): eigenstaendiger, vollstaendig
@@ -7500,6 +7860,11 @@
(cond
((= gf-laenge-modus "4") (vfl-qc-linie-mit-gf))
((= gf-laenge-modus "5") (vfl-qc-linie-mit-gf-uform))
;; "Parallel zum Kreisel": ebenfalls eigenstaendig - baut AS + GF +
;; Bogen aus EINEM Abstandswert und stellt die Kettenende-Frage
;; selbst (sie kommt hier NACH dem Bau, nicht davor: erst nach dem
;; Bogen steht fest, wo die Kette weiterlaeuft).
((= gf-laenge-modus "parallel") (vfl-gf-parallel-kreisel))
(t
(progn
;; NEU: Kettenende/ES JETZT fragen - VOR der Laengen-/Gefaelle-
@@ -8250,7 +8615,7 @@
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-es-nein-zielpunkt"))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-es-setzen-frage")))
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-es-setzen-frage" nil)))
(setq antwort (vfl-menu (ssg-text "prompt-wahl-1-2")
(list (ssg-text "vfl-ja") (ssg-text "vfl-es-nein-zielpunkt")) 1
"vfl-es-setzen-frage"))
@@ -10236,7 +10601,7 @@
"vfl-prompt-hoehe-startpunkt" "gf-status-abgebrochen" "Starthoehe (Z, mm):"))
(setq start-hoehe (caddr startpunkt))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-as-setzen-frage")))
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-as-setzen-frage" nil)))
(princ (ssg-text "vfl-as-setzen-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-m3-as-nein"))
@@ -10257,7 +10622,7 @@
"vfl-m3-prompt-zielhoehe" "gf-status-abgebrochen" "Zielhoehe (Z, mm):"))
(setq end-hoehe (caddr endpunkt))
(if (vfl-wizard-aktiv)
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-es-setzen-frage")))
(vl-catch-all-apply 'vflw-gruppe-as-impl (list "vfl-es-setzen-frage" nil)))
(princ (ssg-text "vfl-es-setzen-frage"))
(princ (ssg-text "vfl-ja"))
(princ (ssg-text "vfl-es-nein-zielpunkt"))
+19 -2
View File
@@ -274,7 +274,18 @@
((and (= (type v) 'STR) (= (strcase v) "AUTO")) (vsp-raus "NIL" nil))
(T (vsp-zahl "REAL" v "eingang_hoehe"))))
(defun vsp-praeambel (kopf / punkt)
;; Praeambel-Tokens: PT(Startpunkt) REAL(Starthoehe) STR(AS ja/nein)
;; [STR(Winkel) STR(Seite) (PT|NIL)(Zufuehrungsachse)].
;;
;; Das Achsen-Feld "as_achse" ist OPTIONAL (Gegenstueck zu
;; vfl-frage-as-winkel-seite-oder-achse und zu praeambel() in
;; lib/vf_linienzug_grammatik.py):
;; [x, y, z] -> PT (Zufuehrungsachse ueber einen zweiten Punkt)
;; "ohne" -> NIL (bewusst ohne Achse gebaut)
;; fehlt -> gar kein Token = Journalformat VOR 2026-10-21. Das replayt
;; unveraendert; die Kettenrichtung kommt dann wie frueher aus
;; dem naechsten Klick.
(defun vsp-praeambel (kopf / punkt achse)
(setq *vsp-wo* "start")
(setq punkt (ssg-val kopf "start_punkt"))
(if (and punkt (listp punkt) (>= (length punkt) 3))
@@ -287,7 +298,13 @@
(if (vsp-ja-p (ssg-val kopf "as"))
(progn
(vsp-wert-str (ssg-val kopf "as_winkel") "as_winkel")
(vsp-code-raus *vsp-tab-seite* (ssg-val kopf "as_seite") "as_seite"))))
(vsp-code-raus *vsp-tab-seite* (ssg-val kopf "as_seite") "as_seite")
(setq achse (ssg-val kopf "as_achse"))
(cond
((and achse (listp achse) (>= (length achse) 3))
(vsp-raus "PT" (mapcar 'float achse)))
((equal achse "ohne") (vsp-raus "NIL" nil))
))))
;; naechste: Felder der NAECHSTEN Top-Level-Sektion (oder nil) - wird nur fuer
;; "Linie-GF" gebraucht (siehe dort). *vsp-es-verbraucht* (Ausgabe-Flag, vom
+80
View File
@@ -181,6 +181,80 @@ vflw_as : dialog {
}
}
// Wie vflw_as, aber ZUSAETZLICH mit der Zufuehrungsachse (2. Punkt) in
// DEMSELBEN Bildschirm - nur fuer das AS-Element am Kettenanfang von Modus 1
// (Nutzer-Vorgabe 2026-10-21: "Achse und links/rechts gehoeren in ein
// Fenster", beide zusammen legen AS-Lage UND Kettenrichtung fest: der 2.
// Punkt gibt die KS_EIN-Achse, links/rechts die AS-Variante und damit die
// KS_AUS-Richtung). Eigener Dialog statt einer dauerhaft ausgegrauten
// Punkt-Zeile in vflw_as - denselben Dialog nutzt auch das ES-Element am
// Kettenende, wo es gar keine Zufuehrungsachse gibt (gleiche Begruendung wie
// bei vflw_winkel_seite vs. vflw_winkel_seite_30_90).
// Punktwahl-Zeile nach dem Muster von vflw_punkt_zahl: der Button schliesst
// den Dialog (done_dialog 2), danach laeuft (getpoint) auf der
// Kommandozeile, danach wird der Dialog neu gezeigt - darum nutzt "back"
// hier Code 3.
vflw_as_achse : dialog {
label = "VarioFoerderer Assistent";
: column {
: text {
key = "kopf";
label = "";
width = 60;
}
: toggle {
key = "setzen";
label = "Ja, setzen";
}
: boxed_radio_column {
label = "Winkel:";
key = "winkel";
: radio_button { key = "winkel_90"; label = "90 Grad"; value = "1"; }
: radio_button { key = "winkel_30"; label = "30 Grad"; }
}
: boxed_radio_column {
label = "Seite (AS schleust nach):";
key = "seite";
: radio_button { key = "seite_links"; label = "Links"; value = "1"; }
: radio_button { key = "seite_rechts"; label = "Rechts"; }
}
: boxed_column {
label = "Zufuehrungsachse (Fahrtrichtung):";
// Startpunkt der Kette - er ist der Sitz des AS-Elements UND der
// Ausgangspunkt der Achse; waehrend der Punktwahl haengt das
// Gummiband an ihm (vfl-getpoint mit Basispunkt).
: text {
key = "start_anzeige";
label = "";
width = 46;
}
: row {
: button {
key = "pick";
label = "2. Punkt waehlen...";
fixed_width = true;
width = 20;
}
: text {
key = "pt_anzeige";
label = "(ohne Achse: Richtung aus dem naechsten Klick)";
width = 46;
}
}
}
}
: row {
alignment = centered;
: button { key = "back"; label = "Zurueck"; fixed_width = true; width = 12; }
spacer;
: button { key = "accept"; label = "Ok Weiter"; is_default = true; fixed_width = true; width = 12; }
spacer;
: button { key = "cancel"; label = "Abbrechen"; is_cancel = true; fixed_width = true; width = 12; }
}
}
// Winkel (30/60/90) + Seite - fuer GF-Bogen, wo alle drei Winkel geometrisch
// gueltig sind. Fuer AS-/ES-Element (wo 60 Grad NIE gueltig ist) stattdessen
// vflw_winkel_seite_30_90 verwenden (siehe vflw-gruppe-winkel-seite-impl/
@@ -402,6 +476,12 @@ vflw_gf_laenge_modus : dialog {
: radio_button { key = "modus_hoehe"; label = "Zielhoehe festlegen (Laenge wird berechnet)"; }
: radio_button { key = "modus_steigend"; label = "Steigende GF (Sonderfall, bis 3 Grad)"; }
: radio_button { key = "modus_qc"; label = "QC Linie mit GF (automatische Auf-Ab-Berechnung)"; }
// Nur am Kettenanfang mit AS-Element UND festgelegter Zufuehrungsachse
// waehlbar - sonst fehlt die Bezugsachse fuer den Abstand. Wird in
// vflw-gruppe-gf-laenge-modus-impl per mode_tile deaktiviert (anders
// als eine NIE waehlbare Kachel ist diese kontextabhaengig, siehe
// "sepnach" in vflw_sep_gf).
: radio_button { key = "modus_parallel"; label = "Parallel zum Kreisel weiterbauen (Abstand)"; }
}
}
+57
View File
@@ -105,6 +105,63 @@ Zwischen-Etappe, es geht in derselben VF-Einheit weiter (Vario-Kurve,
weiteres gerades Stueck, Auf/Ab). "Ja" beendet die VF-Einheit mit einer
Motorstation - optional zusaetzlich mit ES-Element (echtes Kettenende).
### 3a. Neu seit 2026-10-21: Zufuehrungsachse und "Parallel zum Kreisel"
Die Tabelle oben zitiert eine aeltere Aufzeichnung. Zwei Dinge sind seitdem
dazugekommen (aeltere Ketten bleiben unveraendert editierbar - das Journal
erkennt am Eintragstyp, welche Fragen damals gestellt wurden):
**Zufuehrungsachse (2. Punkt) - jetzt bei BEIDEN AS-Winkeln.** Nach Winkel
und Seite (Eintrag 4/5) fragt der AS-Dialog zusaetzlich einen zweiten Punkt
ab; im Assistenten stehen alle drei Angaben in EINEM Fenster
(`vflw_as_achse`). Die beiden Angaben beschreiben verschiedene Dinge und
legen das AS erst zusammen vollstaendig fest:
| Angabe | legt fest |
|---|---|
| 2. Punkt | Richtung der KS_EIN-Achse = die Zufuehrung, z.B. die Kreisel-Laufrichtung. Der Startpunkt bleibt der Sitz des AS-Elements, der zweite Punkt gibt nur die Richtung. |
| links/rechts | AS-Variante und damit die KS_AUS-Richtung, also wohin die Kette nach dem Ausschleusen laeuft. |
Der Dialog zeigt den Startpunkt mit an (er ist der Sitz des AS-Elements UND
der Ausgangspunkt der Achse); waehrend der Punktwahl haengt das Gummiband an
ihm. Mit dem gesetzten Punkt ist der Dialog fertig und schliesst direkt - die
ermittelte Achsrichtung meldet die Konsole. Nur ein mit ESC abgebrochener
Pick zeigt den Dialog noch einmal (mit den vorher gewaehlten Werten), damit
nach einem Fehlklick nichts verloren geht.
Die Achse ist **optional**: ohne Punkt laeuft alles wie frueher ueber die
Richtung des naechsten Klicks. Frueher ersetzte die Achse beim 30-Grad-
Element die Seitenfrage (die Seite wurde aus der Lage des naechsten Punkts
erraten); das gilt nur noch beim Nachbau alter Ketten.
**"Parallel zum Kreisel weiterbauen"** - 5. Option der Frage "Wie soll die
Laenge dieser Gefaellestrecke bestimmt werden?" (Eintrag 30). Sie erscheint
nur am Kettenanfang mit AS-Element UND festgelegter Zufuehrungsachse, weil
sie beides als Bezug braucht. Gebaut wird AS -> kurzes GF-Stueck -> GF-Bogen,
sodass die Kette danach parallel zur Zufuehrungsachse weiterlaeuft. Einzige
Eingabe ist der **Abstand**, gemessen senkrecht zur Zufuehrungsachse von
KS_EIN des AS bis KS_AUS des Bogens; die Laenge des GF-Stuecks dazwischen
wird daraus berechnet. Ein Separator zwischen GF-Stueck und Bogen wird
bewusst NICHT gesetzt, damit der Abstand exakt getroffen wird. Der Bogen wird
nicht geraten, sondern so gewaehlt, dass er den gemessenen Grundriss-Schwenk
des AS-Blocks genau aufhebt - immer zur Gegenseite der AS-Seite:
| AS | Bogen |
|---|---|
| 30 Grad links | 30 Grad rechts |
| 30 Grad rechts | 30 Grad links |
| 90 Grad links | 90 Grad rechts |
| 90 Grad rechts | 90 Grad links |
Diese Tabelle ist das ERGEBNIS der Messung, keine gepflegte Zuordnung im
Code - sie bleibt auch dann richtig, wenn ein Element anders vermessen ist
als hier erwartet.
Direkt nach dem Bogen kommt die gewohnte Frage "Ist das das Kettenende?"
(Ja mit ES / Ja ohne ES / Nein, weiterbauen) - hier ausnahmsweise NACH dem
Bau, weil es keine gepickte Laenge gibt, von der ein ES-Fussabdruck
abzuziehen waere.
## 4. Stufe 1 / Stufe 2 - wie die letzte Strecke berechnet wird
Am Kettenende (Eintrag 61/62) ist die Aufgabe: von der aktuellen Position/
+9 -1
View File
@@ -55,6 +55,13 @@
"vfl-gf-laenge-modus-opt-hoehe": "\n 2 - Zielhoehe festlegen (Laenge wird berechnet)",
"vfl-gf-laenge-modus-opt-steigend": "\n 3 - Steigende GF (Sonderfall, bis 3 Grad)",
"vfl-gf-laenge-modus-opt-qc": "\n 4 - QC Linie mit GF (automatische Auf-Ab-Berechnung)",
"vfl-gf-laenge-modus-opt-parallel": "\n 5 - Parallel zum Kreisel weiterbauen (nur Abstand eingeben)",
"vfl-par-prompt-abstand": "\n\nAbstand zum Kreisel (mm, senkrecht zur Zufuehrungsachse, von AS-KS_EIN bis Bogen-KS_AUS): ",
"vfl-par-nur-kettenanfang": "'Parallel zum Kreisel' geht nur am Kettenanfang mit AS-Element UND festgelegter Zufuehrungsachse (2. Punkt im AS-Dialog).",
"vfl-par-kein-bogen": "Kein GF-Bogen hebt den AS-Schwenk von %1 Grad auf (Seite %2) - parallel zum Kreisel nicht schliessbar.",
"vfl-par-kein-versatz": "Das AS-Element schwenkt nicht aus der Zufuehrungsachse heraus - so laesst sich kein Abstand erreichen.",
"vfl-par-zu-klein": "Abstand %1 mm zu klein: AS und Bogen allein brauchen schon %2 mm. Bitte groesseren Abstand waehlen.",
"vfl-par-erfolg": "\n>>> Parallel zum Kreisel: GF-Stueck %1 mm (horizontal), Bogen %2 Grad %3, erreichter Abstand %4 mm.",
"vfl-qc-nur-kettenanfang": "\nFEHLER: 'QC Linie mit GF' ist nur am Kettenanfang moeglich (braucht eine eigene AS-Seite) - bitte anderen Modus waehlen.",
"vfl-qc-prompt-richtung": "\n\nRichtung waehlen (Gerade, auf der AS-Eintritt und ES-Austritt liegen sollen) - Punkt in Fahrtrichtung anklicken: ",
"vfl-qc-ks-ein-gemessen": "\n>>> AS KS_EIN (Referenzgerade) gemessen: X=%1 Y=%2 Z=%3",
@@ -101,7 +108,8 @@
"vfl-prompt-endpunkt-fahrtrichtung": "\n\nEndpunkt entlang Fahrtrichtung waehlen (bestimmt die Laenge): ",
"vfl-prompt-as-achse-p2": "\n\nZufuehrungsachse - 2. Punkt (gibt nur die Richtung, der Startpunkt bleibt): ",
"vfl-basislinie-punkte-gleich": "\n Die beiden Punkte sind identisch - keine Richtung bestimmbar. Abbruch.",
"vfl-basislinie-info": "\n Zufuehrungsachse: %1 Grad. Die AS-Seite ergibt sich aus der Lage des naechsten Punkts.",
"vfl-basislinie-info": "\n Zufuehrungsachse (Fahrtrichtung): %1 Grad.",
"vfl-basislinie-ohne": "\n Keine Zufuehrungsachse festgelegt - die Richtung ergibt sich aus dem naechsten Klick.",
"vfl-as-seite-ermittelt": "\n AS-Seite aus der Lage des naechsten Punkts: %1",
"vfl-endpunkt-modus-header": "\n\nWie soll der Endpunkt bestimmt werden?",
"vfl-endpunkt-modus-opt-linie": "\n 1 - Ziellinie zeichnen (2 Punkte) - der Schnittpunkt bestimmt die Laenge",
+9 -1
View File
@@ -55,6 +55,13 @@
"vfl-gf-laenge-modus-opt-hoehe": "\n 2 - Set target height (length will be calculated)",
"vfl-gf-laenge-modus-opt-steigend": "\n 3 - Rising GF (special case, up to 3 degrees)",
"vfl-gf-laenge-modus-opt-qc": "\n 4 - QC line with GF (automatic up/down calculation)",
"vfl-gf-laenge-modus-opt-parallel": "\n 5 - Continue parallel to the carousel (distance only)",
"vfl-par-prompt-abstand": "\n\nDistance to the carousel (mm, perpendicular to the feed axis, from AS KS_EIN to bend KS_AUS): ",
"vfl-par-nur-kettenanfang": "'Parallel to the carousel' only works at the start of a chain with an AS element AND a defined feed axis (2nd point in the AS dialog).",
"vfl-par-kein-bogen": "No GF bend cancels the AS plan turn of %1 degrees (side %2) - cannot close parallel to the carousel.",
"vfl-par-kein-versatz": "The AS element does not turn away from the feed axis - no distance can be reached this way.",
"vfl-par-zu-klein": "Distance %1 mm too small: AS and bend alone already need %2 mm. Please choose a larger distance.",
"vfl-par-erfolg": "\n>>> Parallel to the carousel: GF section %1 mm (horizontal), bend %2 degrees %3, resulting distance %4 mm.",
"vfl-qc-nur-kettenanfang": "\nERROR: 'QC line with GF' is only possible at the chain start (needs its own AS side) - please choose a different mode.",
"vfl-qc-prompt-richtung": "\n\nChoose direction (line on which AS entry and ES exit shall lie) - click a point in the travel direction: ",
"vfl-qc-ks-ein-gemessen": "\n>>> AS KS_EIN (reference line) measured: X=%1 Y=%2 Z=%3",
@@ -101,7 +108,8 @@
"vfl-prompt-endpunkt-fahrtrichtung": "\n\nSelect end point along travel direction (defines the length): ",
"vfl-prompt-as-achse-p2": "\n\nFeed axis - 2nd point (defines the direction only, the start point stays): ",
"vfl-basislinie-punkte-gleich": "\n The two points are identical - no direction can be derived. Aborting.",
"vfl-basislinie-info": "\n Feed axis: %1 degrees. The AS side follows from the position of the next point.",
"vfl-basislinie-info": "\n Feed axis (travel direction): %1 degrees.",
"vfl-basislinie-ohne": "\n No feed axis defined - the direction follows from the next click.",
"vfl-as-seite-ermittelt": "\n AS side from the position of the next point: %1",
"vfl-endpunkt-modus-header": "\n\nHow should the end point be determined?",
"vfl-endpunkt-modus-opt-linie": "\n 1 - Draw target line (2 points) - the intersection defines the length",
+13
View File
@@ -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:
+29 -1
View File
@@ -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")
+26
View File
@@ -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)
+10 -1
View File
@@ -31,7 +31,9 @@
;; anzahl_eingaben, block, dxf_handle, startpunkt_mm,
;; rotation_grad, beschreibung, sivas_* , erwartung_hinweis
;; Eingabe-Objekt (traegt "typ"): point_abs | point_rel | real | int |
;; string | step - in exakter Frage-Reihenfolge
;; string | step | nil - in exakter Frage-Reihenfolge
;; ("nil" = Frage gestellt und bewusst uebergangen, z.B.
;; keine Zufuehrungsachse gewaehlt)
;; Ein Kopf-Objekt beginnt eine Kette, die folgenden Eingabe-Objekte gehoeren
;; dazu (hmrec:gruppiere).
;;
@@ -291,6 +293,13 @@
(list (cons "STR" (hmrec:als-string (ssg-val e "wert")))))
((= typ "step")
(list (cons "STEP" (hmrec:als-string (ssg-val e "wert")))))
;; Eine Frage, die gestellt und bewusst uebergangen wurde (bisher nur die
;; Zufuehrungsachse des AS-Elements, siehe ProtokollSink.as_achse in
;; lib/vf_journal_export.py) - im Journal ein wertloser "NIL"-Eintrag.
;; Er MUSS mitlaufen: beim Replay poppt ihn vfl-in-point, sonst zoege
;; dieser Aufruf den naechsten Wert aus der Queue (Desync).
((= typ "nil")
(list (cons "NIL" nil)))
(T nil)
)
)
+55 -1
View File
@@ -399,6 +399,59 @@
)
;; ------------------------------------------------------------
;; TESTS: vf_linienzug.lsp - "Parallel zum Kreisel" (Rechenkerne)
;; ------------------------------------------------------------
;; Die Bau-Funktion vfl-gf-parallel-kreisel braucht eine Zeichnung (sie misst
;; AS und Bogen an echten Bloecken); ihre beiden Rechenkerne sind davon frei
;; und stehen deshalb hier:
;; vfl-par-bogen-waehlen - welcher Bogen hebt den AS-Schwenk auf?
;; vfl-par-gf-laenge - wie lang wird das GF-Stueck dazwischen?
;; Die Plan-Turn-Werte unten sind die Vorzeichen-Konvention aus
;; vf-element-plan-turn (KS_EIN -> KS_AUS in der XY-Ebene, links positiv).
(defun tu-tests-vfl-parallel ( / _b30 _b60 _b90 _kand _l)
(princ "\n[vf_linienzug Parallel zum Kreisel]")
;; --- Bogenwahl: der Bogen muss den AS-Schwenk GENAU aufheben ---
;; Rechts-Boegen schwenken negativ, Links-Boegen positiv.
(setq _b30 '(30 . -30.0) _b60 '(60 . -60.0) _b90 '(90 . -90.0))
(setq _kand (list _b30 _b60 _b90))
;; AS links 30 Grad (Schwenk +30) -> 30-Grad-Bogen nach rechts
(tu-eq "vfl-par-bogen/as30-rechts" 30 (vfl-par-bogen-waehlen 30.0 _kand 2.0))
;; AS links 90 Grad (Schwenk +90) -> 90-Grad-Bogen nach rechts
(tu-eq "vfl-par-bogen/as90-rechts" 90 (vfl-par-bogen-waehlen 90.0 _kand 2.0))
;; Generisch: auch ein 60-Grad-Schwenk findet seinen Bogen - die Zuordnung
;; folgt den gemessenen Bloecken, nicht einer gepflegten Tabelle.
(tu-eq "vfl-par-bogen/as60-rechts" 60 (vfl-par-bogen-waehlen 60.0 _kand 2.0))
;; kleine Messabweichung bleibt in der Toleranz
(tu-eq "vfl-par-bogen/toleranz-ok" 30 (vfl-par-bogen-waehlen 31.2 _kand 2.0))
;; ausserhalb der Toleranz: lieber nil als ein schiefer Bogen
(tu-nil "vfl-par-bogen/zu-weit-weg" (vfl-par-bogen-waehlen 45.0 _kand 2.0))
(tu-nil "vfl-par-bogen/leere-liste" (vfl-par-bogen-waehlen 30.0 '() 2.0))
;; Gegenseite: Links-Boegen heben einen negativen AS-Schwenk auf
(setq _kand (list '(30 . 30.0) '(60 . 60.0) '(90 . 90.0)))
(tu-eq "vfl-par-bogen/as-rechts30-links" 30 (vfl-par-bogen-waehlen -30.0 _kand 2.0))
;; --- GF-Laenge: abstand = perp-as + L*k + perp-bogen ---
;; Glatter Fall: k = 1 (AS schwenkt 90 Grad aus der Achse), feste Anteile
;; zusammen 500 -> fuer 2000 mm Abstand bleiben 1500 mm GF.
(tu-eqf "vfl-par-laenge/k1" 1500.0 (vfl-par-gf-laenge 2000.0 200.0 300.0 1.0))
;; Gegenseite: k negativ -> der Abstand zaehlt auf der anderen Seite, der
;; Betrag der Eingabe bleibt derselbe (Seite kommt aus der AS-Variante).
(tu-eqf "vfl-par-laenge/k-negativ" 1500.0
(vfl-par-gf-laenge 2000.0 -200.0 -300.0 -1.0))
;; Schraege Ausschleusung: k = sin(60 Grad)
(setq _l (vfl-par-gf-laenge 2000.0 0.0 0.0 (sin (/ (* 60.0 pi) 180.0))))
(tu-eqf "vfl-par-laenge/k-sin60" (/ 2000.0 (sin (/ (* 60.0 pi) 180.0))) _l)
;; Abstand kleiner als der feste Anteil -> negative Laenge (der Aufrufer
;; meldet das als "Abstand zu klein", die Rechnung selbst raet nicht).
(tu-true "vfl-par-laenge/zu-klein-negativ"
(< (vfl-par-gf-laenge 100.0 200.0 300.0 1.0) 0.0))
;; AS schwenkt gar nicht aus der Achse -> keine Loesung
(tu-nil "vfl-par-laenge/kein-versatz" (vfl-par-gf-laenge 2000.0 0.0 0.0 0.0))
)
;; ------------------------------------------------------------
;; TESTS: vf_linienzug.lsp - Laenge aus einer Ziellinie (X/Y)
;; ------------------------------------------------------------
@@ -2365,7 +2418,8 @@
(princ "\n================================================================")
(foreach fn '("tu-tests-core" "tu-tests-lang" "tu-tests-id" "tu-tests-vfcore"
"tu-tests-etage" "tu-tests-vfl-es" "tu-tests-vfl-ziellinie"
"tu-tests-etage" "tu-tests-vfl-es" "tu-tests-vfl-parallel"
"tu-tests-vfl-ziellinie"
"tu-tests-export" "tu-tests-hoehen" "tu-tests-gf" "tu-tests-omni" "tu-tests-kreisel" "tu-tests-kreisel-bp"
"tu-tests-sepliste" "tu-tests-json" "tu-tests-geruest"
"tu-tests-sepscan" "tu-tests-vfl-segs" "tu-tests-vfl-abbruch"