diff --git a/CLAUDE.md b/CLAUDE.md index 3c528fe..3d1f06e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -91,14 +91,14 @@ Detaillierte Dokumentation aller Funktionen und Befehle: siehe `Lisp/README.md`. | Datei | Befehle | Beschreibung | |---|---|---| -| `ssg_core.lsp` | - | Kernel: ssg-start/ssg-end, ssg-cfg-or, ssg-attrib-read/write, Layer/Block-Ops. Zentrale Helfer: `ssg-lisp-verzeichnis`/`ssg-lisp-datei-pfad` (Lisp-Pfad-Ermittlung), `ssg-rot-matrix-zy` (4x4 Rz(hz)*Ry(vert)-Rotationsmatrix, clusteruebergreifend), GUI-Aus-Schalter `*ssg-gui-aus*` + `ssg-gui-aus`/`ssg-gui-an`/`ssg-gui-p` (fuer automatische Tests: schaltet DCL-/Wizard-Dialoge ab, Module fallen auf Konsolen-/getXXX-Pfad zurueck) | +| `ssg_core.lsp` | - | Kernel: ssg-start/ssg-end, ssg-cfg-or, ssg-attrib-read/write, Layer/Block-Ops. Zentrale Helfer: `ssg-lisp-verzeichnis`/`ssg-lisp-datei-pfad` (Lisp-Pfad-Ermittlung), `ssg-rot-matrix-zy` (4x4 Rz(hz)*Ry(vert)-Rotationsmatrix, clusteruebergreifend), GUI-Aus-Schalter `*ssg-gui-aus*` + `ssg-gui-aus`/`ssg-gui-an`/`ssg-gui-p` (fuer automatische Tests: schaltet DCL-/Wizard-Dialoge ab, Module fallen auf Konsolen-/getXXX-Pfad zurueck; der Linienzug hat zusaetzlich `*vfl-headless*`, das die Konsolen-Rueckfrage selbst verbietet - siehe VarioFoerderer-Zeile) | | `ssg_ks_insert.lsp` | - | Gemeinsame KS-Extraktion + Block-Einfuegeprimitiven (von VarioFoerderer UND Gefaellestrecke genutzt, frueher in beiden dupliziert): `vec-length`, `ks-line-axis`, `ks-normalize-name`, `ks-relativize`/`-absolutize`, `ensure-block-loaded`, `extract-ks-from-block[-raw]` (Cache `*ks-cache*`), `insert-block-by-ks` (nur Rz), `insert-inclined-scaled-block`/`insert-rotated-block-with-ks` (Rz*Ry). MNL laedt es als Core-Modul VOR Gefaellestrecke/VarioFoerderer; vf_core/Gefaellestrecke haben einen guarded Nachlade-Load fuers isolierte Test-Laden | | `ssg_lang.lsp` | - | Sprachumschaltung Deutsch/Englisch (ssg-text, ssg-textf, *ssg-lang*) | | `ssg_dbg.lsp` | - | Debug-Logging (dbgopen, dbgmsg, dbgflush, dbgclose) | | `ssg_dialog.lsp` | - | DCL-Dialog-Vorlagen und Hilfsfunktionen | | `ssg_layer.lsp` | - | Layer-Verwaltung | | `KreiselInsert.lsp` | KreiselInsert, KreiselConnect, KreiselRedraw, KreiselQuick, KreiselEdit, KreiselParams, KreiselLabelSetup, KreiselLabelPos, KreiselLabelHoehe, ILS_Eckrad | ILS Kreisel und Eckrad komplett in AutoLISP | -| `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. 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) sammelt alle Laengen-/Winkel-/Toleranz-Magic-Numbers aus dem VarioFoerderer-Cluster als dokumentierte `*vfk-*`-Konstanten. Die Migration ist abgeschlossen: vf_core/vf_standard/vf_etage/vf_linienzug referenzieren durchgaengig die zentralen Konstanten statt roher Literale (Winkel, Stations-/Separator-Laengen, Mindestlaengen, AS/ES-Fallback-Versaetze, Text-/Label-Masse) - Ausnahmen sind in `vf_konstanten.lsp` selbst dokumentiert (Werte mit fuehrender Config-Anbindung via `ssg-cfg-or`, itoa-/Integer-Kontexte, Blocknamen-Strings, die 2000.0-mm/m-Umrechnung). `*vfk-gf-bogen-winkel*` (30/60/90, GF-Bogen + Vario-Kurve) und `*vfk-as-es-winkel*` ("30"/"90", AS/ES; bewusst STRING, da 2-wertig und im Journal als STR gefuehrt) sind Beispiele; vf_linienzug hat fuer beide einen Fallback, falls vf_konstanten nicht geladen wurde. Details/Entscheidungsbaum: `doc/variofoerderer.md` | +| `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. 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) sammelt alle Laengen-/Winkel-/Toleranz-Magic-Numbers aus dem VarioFoerderer-Cluster als dokumentierte `*vfk-*`-Konstanten. Die Migration ist abgeschlossen: vf_core/vf_standard/vf_etage/vf_linienzug referenzieren durchgaengig die zentralen Konstanten statt roher Literale (Winkel, Stations-/Separator-Laengen, Mindestlaengen, AS/ES-Fallback-Versaetze, Text-/Label-Masse) - Ausnahmen sind in `vf_konstanten.lsp` selbst dokumentiert (Werte mit fuehrender Config-Anbindung via `ssg-cfg-or`, itoa-/Integer-Kontexte, Blocknamen-Strings, die 2000.0-mm/m-Umrechnung). `*vfk-gf-bogen-winkel*` (30/60/90, GF-Bogen + Vario-Kurve) und `*vfk-as-es-winkel*` ("30"/"90", AS/ES; bewusst STRING, da 2-wertig und im Journal als STR gefuehrt) sind Beispiele; vf_linienzug hat fuer beide einen Fallback, falls vf_konstanten nicht geladen wurde. **Nicht-interaktiver Betrieb**: `*vfl-headless*` (+ `vfl-headless-p`/`-abbruch`/`-notausgang`, Diagnose `*vfl-headless-fehler*`, optionaler Antwort-Hook `*vfl-headless-antwort-fn*`) verbietet jede Live-Eingabe - eine erschoepfte Replay-Queue ist damit ein harter Abbruch MIT Fundstelle (Art der Eingabe, Glied- und Eingabe-Nummer) statt eines stillen Rueckfalls auf `getpoint`/`getreal`; eingebaut in `vfl-in-value`/`-value-p`/`-selection`/`-abstand`. Die Bau-Pfade melden abgewiesene Sektionen ueber `vfl-meldung` (sammelt nach `*vfl-meldungen*` + `dbgmsg`, `alert` nur bei erlaubter GUI, sonst `princ`) statt per blockierendem `alert`; reine Interaktiv-Alerts (fehlendes DCL, "nicht editierbar", "kein Journal") bleiben `alert`. Genutzt von `tests/test_hundm05.lsp`; Fahrplan zur vollen Daten-API in `doc/TODO-plan-vf-interactive.md`. Details/Entscheidungsbaum: `doc/variofoerderer.md` | | `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) | | `export.lsp` | EXPORTSIVAS, EXPORTCSV, OMNI_UPDATE_ATTRIBS | JSON-Sammlung und Python-Export (Omniflo-Merkmale/Sum-Zeile via lib/export_csv.py) | | `OmniModulInsert.lsp` | OMNI_LOAD, OMNI_APB_*, OMNI_W*_*, OMNI_AP60/AP110, OMNI_TEF_*, OMNI_TV_*, OMNI_APBW_*, OMNI_EDIT | Omniflo-Komponenten: Boegen, Weichen, Verbinder, Transferwagen, Edit-Dialog | @@ -164,6 +164,7 @@ Abhaengigkeiten: `ezdxf`, Standard-Library. Benoetigen Umgebungsvariablen (`DXFM | `export_sivas.py` | Erzeugt Sivas-Export-CSV mit Summierungszeilen (ILS Automation, Omniflo Sum). Aufruf: `python export_sivas.py ` | | `export_csv.py` | Erzeugt einfache Item-Liste als CSV (ohne Summierung). Aufruf: `python export_csv.py ` | | `split_block_library.py` | Teilt `ils_library.dxf` in einzelne DXF-Dateien pro Block auf (mit Abhaengigkeiten). Export-Prefixe: Vario_, Staustrecke_, _3D_AS_, _3D_ES_ | +| `vf_journal_export.py` | Liest die Eingabe-Journale der `VF_n`-Bloecke (XDATA-App `SSG_VF_EDIT`, Marker `"linienzug"`) aus einer Zeichnung und schreibt sie als Frage-Antwort-Protokoll im Schema von `tests/testdata/linienzug_tests.json` (Gegenstueck zu `vfl-entry->string`; kein ezdxf, reine Gruppencode-Lesung). Optional `--csv ` fuer Strecken-ID und Erwartungswerte je Kette. Aufruf: `python vf_journal_export.py [--csv ...] [--kein-startpunkt-fix]` — erzeugt `tests/testdata/hundm05.json` aus `data/polylines.dxf` | | `dxf_scan_components.py` | Erkennt ILS-/Omniflo-Komponenten in einer **fremden** Projektzeichnung (ohne unsere Wrapper-Bloecke) und schreibt sie im JSON-Schema aus `tests/testdata/object_data.md` — Grundlage fuer Nachbau-Testfaelle wie `tests/testdata/hundm05.json`. Liest binaere DXF im GB-Bereich streamend per mmap (ezdxf-DOM waere zu gross), loest INSERTs inkl. OCS/Extrusionsrichtung rekursiv in Weltkoordinaten auf, paart AN8/SP8 zu Kreiseln und Antrieb/Umlenkung zu Foerderern. Aufruf: `python dxf_scan_components.py [--fenster X0 Y0 X1 Y1]` — ohne `--fenster` liefert eine Anlagenzeichnung leicht ueber 1300 Eintraege, zuviel fuer einen Testlauf | ### Entwickler-Tools (`lib/`) - kein ezdxf, reine Text-/AutoLISP-Verarbeitung diff --git a/Lisp/vf_core.lsp b/Lisp/vf_core.lsp index f0ceaff..3f45f7d 100644 --- a/Lisp/vf_core.lsp +++ b/Lisp/vf_core.lsp @@ -1131,12 +1131,20 @@ ;; Konsolen-Nummer -> interne Funktion: ;; 2 = fertiger Ziel-Hoehe-Modus (intern vf-linienzug-modus2) ;; 3 = alter Vorwaerts-Nachbau (intern vf-linienzug-modus3, wird umgebaut) - ;; 1/Default = frische manuelle Eingabe (Journal vorher zuruecksetzen). Ein - ;; abgebrochener Bau bleibt als VF_n-Block stehen und wird per Doppelklick - ;; fortgesetzt/editiert (vfl-edit-ent), kein separater Menuepunkt noetig. + ;; 1/Default = frische manuelle Eingabe. Ein abgebrochener Bau bleibt als + ;; VF_n-Block stehen und wird per Doppelklick fortgesetzt/editiert + ;; (vfl-edit-ent), kein separater Menuepunkt noetig. + ;; Journal-Reset gilt fuer JEDEN frischen Bau, nicht nur Modus 1: Modus 2 + ;; schreibt sein Journal ebenfalls als XDATA (Marker "linienzug2"), erbte + ;; ohne Reset aber die Eintraege des Vorlaufs als Praefix - ein spaeterer + ;; Doppelklick spielte dann fremde Eingaben vor. Die Edit-/Konvertier-Pfade + ;; laufen nie hier durch (sie setzen ihre Queue selbst), darum ist das + ;; Hochziehen ein reiner Bugfix. Setzt zugleich *vfl-seg-glied-letzte* + ;; zurueck, das sonst vom Vorlauf stehenbleibt. + (vfl-journal-reset) (cond ((= wahl 2) (vf-linienzug-modus2)) ((= wahl 3) (vf-linienzug-modus3)) - (t (vfl-journal-reset) (vf-linienzug-modus))) + (t (vf-linienzug-modus))) (exit) ) ) diff --git a/Lisp/vf_linienzug.lsp b/Lisp/vf_linienzug.lsp index e102e66..1bad0ec 100644 --- a/Lisp/vf_linienzug.lsp +++ b/Lisp/vf_linienzug.lsp @@ -111,6 +111,103 @@ (defun vfl-as-winkel () (if (boundp '*vfl-as-winkel*) *vfl-as-winkel* "90")) (defun vfl-es-winkel () (if (boundp '*vfl-es-winkel*) *vfl-es-winkel* "90")) +;; ============================================================ +;; TEIL 0a-2: MELDUNGEN (statt alert im Bau-Pfad) +;; ============================================================ +;; Ein (alert ...) im Bau-Pfad blockiert jeden nicht-interaktiven Lauf +;; (Batch-Konvertierung vfl-konvertiere-ent, Testtreiber, Spec-Bau) mit einem +;; modalen Fenster - und der Text ist zugleich die EINZIGE Auskunft darueber, +;; welche Sektion warum abgewiesen wurde. vfl-meldung sammelt ihn deshalb +;; immer ein (*vfl-meldungen*, aelteste zuerst -> Auswertung im Ergebnis- +;; Record), schreibt ihn ins Debug-Log und zeigt ihn nur dann modal, wenn die +;; GUI erlaubt ist (ssg-gui-p, ssg_core.lsp); sonst geht er auf die Konsole. +;; Interaktiv ist das Verhalten damit unveraendert. +;; NUR fuer Meldungen aus dem Bau-Pfad. Die reinen Interaktiv-Alerts (fehlende +;; DCL-Datei, "Glied nicht editierbar", "kein Journal") bleiben alert - sie +;; koennen nur bei echter Benutzerbedienung auftreten. +(if (not (boundp '*vfl-meldungen*)) (setq *vfl-meldungen* nil)) + +(defun vfl-meldung (txt) + ;; Ein fehlender i18n-Schluessel liefert nil - dann wuerde strcat + ;; hier abbrechen, und zwar in dem Moment, in dem ohnehin schon + ;; etwas schiefgelaufen ist. Also abfangen statt nachtraeglich + ;; suchen. + (if (null txt) (setq txt "(Meldung ohne Text)")) + (setq *vfl-meldungen* (append *vfl-meldungen* (list txt))) + (dbgmsg (strcat "MELDUNG: " txt)) + (dbgflush) + (if (ssg-gui-p) + (alert txt) + (princ (strcat "\n" txt))) + txt) + +;; ============================================================ +;; TEIL 0a-3: HEADLESS-NOTAUSGANG +;; ============================================================ +;; Bei aktivem *vfl-headless* darf keine Live-Eingabe mehr stattfinden. Eine +;; erschoepfte Replay-Queue heisst dann: die Datenquelle (aufgezeichnetes +;; Journal bzw. Spec) passt nicht zum tatsaechlichen Bau-Ablauf (Desync) - ein +;; getpoint/getreal/ssget an dieser Stelle wuerde den Batch-Lauf still +;; blockieren statt den Fehler zu zeigen. Bisher fielen die Wrapper +;; STILLSCHWEIGEND auf Live-Eingabe zurueck; genau das wird hier zum harten, +;; lokalisierten Abbruch. +;; +;; *vfl-headless-fehler* traegt danach die Fundstelle (welche Art Eingabe, +;; welches Glied, welche Eingabe-Nummer) und wird vom Aufrufer in den +;; Ergebnis-Record uebernommen. Die Texte sind reine Diagnose (kein +;; ssg-text-Key noetig), sie erscheinen im normalen Betrieb nie. +;; +;; *vfl-headless-antwort-fn*: optionaler Hook, der eine nicht vorhersagbare +;; Rueckfrage doch noch beantworten darf (Beispiel: vfl-waehle-winkel fragt +;; nur dann, wenn mehrere Winkel-Kandidaten geometrisch gueltig sind - das +;; laesst sich vorab nicht immer wissen). Der gelieferte Wert wird normal +;; journalisiert; die Ersetzung wird zusaetzlich als Meldung protokolliert, +;; damit ein solcher Lauf nie als "sauber" durchgeht. +;; Aufruf: (fn was ort) -> Wert. +(if (not (boundp '*vfl-headless*)) (setq *vfl-headless* nil)) +(if (not (boundp '*vfl-headless-fehler*)) (setq *vfl-headless-fehler* nil)) +(if (not (boundp '*vfl-headless-antwort-fn*)) (setq *vfl-headless-antwort-fn* nil)) + +(defun vfl-headless-p () (and (boundp '*vfl-headless*) *vfl-headless*)) + +;; Fundstelle als Text: Glied-Nummer (STEP-Marker im bisherigen Journal) und +;; Nummer der Eingabe, die JETZT faellig gewesen waere (STEP-Marker zaehlen +;; nicht mit, die entstehen beim Replay neu). +(defun vfl-headless-ort ( / steps) + (setq steps (vfl-steps-zaehlen *vfl-journal*)) + (strcat "Glied " (itoa steps) + ", Eingabe " (itoa (1+ (- (length *vfl-journal*) steps))))) + +;; Harter Abbruch ohne Rueckfrage (kehrt NICHT zurueck, (exit) wird vom +;; vl-catch-all-apply des Aufrufers gefangen). Fuer Eingaben, bei denen ein +;; nachgelieferter Wert nicht sinnvoll waere - z.B. eine Objektauswahl: eine +;; Teilauswahl wuerde die Fragenzahl des ganzen Modus-2-Ablaufs still +;; verschieben. +(defun vfl-headless-abbruch (was / ort steps) + (setq steps (vfl-steps-zaehlen *vfl-journal*)) + (setq ort (vfl-headless-ort)) + (setq *vfl-headless-fehler* + (list (cons "was" was) + (cons "glied" steps) + (cons "eingabe" (1+ (- (length *vfl-journal*) steps))) + (cons "ort" ort))) + (vfl-meldung (strcat "HEADLESS-ABBRUCH: keine Eingabedaten mehr fuer [" + was "] - " ort)) + (exit)) + +;; Wie vfl-headless-abbruch, aber der Antwort-Hook darf zuerst uebernehmen. +;; Rueckgabe dann (wert) als 1-elementige Liste - dieselbe Form wie +;; vfl-replay-pop, damit die Aufrufstelle sie unveraendert weiterverwendet. +(defun vfl-headless-notausgang (was / ort v) + (if *vfl-headless-antwort-fn* + (progn + (setq ort (vfl-headless-ort)) + (setq v (vl-catch-all-apply *vfl-headless-antwort-fn* (list was ort))) + (if (vl-catch-all-error-p v) (setq v nil)) + (vfl-meldung (strcat "HEADLESS: Ersatzantwort fuer [" was "] - " ort)) + (list v)) + (vfl-headless-abbruch was))) + ;; Gemeinsame Vorbedingung aller drei Linienzug-Modi: die GF-Segmente/-Boegen ;; nutzen Funktionen aus Gefaellestrecke.lsp (gf-insert-hz-incl-scaled, ;; gf-bogen-blockname, ...). Bei reiner VarioFoerderer-Ladung ohne @@ -120,7 +217,7 @@ ;; ausfuehrlicher als 2/3, daher als Parameter statt vereinheitlicht). (defun vfl-gf-abhaengigkeit-sicherstellen (alert-key) (if (null (car (atoms-family 1 '("GF-INSERT-HZ-INCL-SCALED")))) - (progn (alert (ssg-text alert-key)) (exit))) + (progn (vfl-meldung (ssg-text alert-key)) (exit))) (if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek))) ;; ============================================================ @@ -149,8 +246,16 @@ ;; eines neuen Baus einen zu hohen Index und die Praeambel wuerde nicht ;; geschrieben. (if (not (boundp '*vfl-seg-glied-letzte*)) (setq *vfl-seg-glied-letzte* 0)) +;; Journal und Replay-Queue muessen auch VOR dem ersten +;; vfl-journal-reset lesbar sein: die vfl-in-*-Wrapper und die +;; Headless-Diagnose lesen sie ungeschuetzt. +(if (not (boundp '*vfl-journal*)) (setq *vfl-journal* nil)) +(if (not (boundp '*vfl-replay-queue*)) (setq *vfl-replay-queue* nil)) (defun vfl-journal-reset () + ;; Sammel-Meldungen und Headless-Diagnose gehoeren zum LAUF, nicht zur + ;; Sitzung - sonst tragen sie in einen neuen Bau die Befunde des alten. + (setq *vfl-meldungen* nil *vfl-headless-fehler* nil) (setq *vfl-journal* '() *vfl-replay-queue* nil *vfl-seg-glied-letzte* 0) (if (boundp '*vflw-pending*) (setq *vflw-pending* nil))) @@ -1064,8 +1169,16 @@ (setq v (cons en v)))) (setq v (reverse v)) (if (> fehlt 0) - (alert (ssg-textf "vfl-m3-alert-objekte-fehlen" (list (itoa fehlt)))))) + (progn + (vfl-meldung (ssg-textf "vfl-m3-alert-objekte-fehlen" (list (itoa fehlt)))) + ;; Headless NICHT mit einer Teilauswahl weiterbauen: die Segmentzahl + ;; bestimmt die gesamte Fragenfolge des Modus-2-Ablaufs, ein + ;; fehlendes Pfad-Objekt verschiebt sie still. Ein nachgelieferter + ;; Wert waere hier sinnlos, darum -abbruch statt -notausgang. + (if (vfl-headless-p) (vfl-headless-abbruch "OBJS-unvollstaendig"))))) (progn + ;; Ohne Replay-Daten gibt es headless keine Objektauswahl. + (if (vfl-headless-p) (vfl-headless-abbruch "OBJS")) (setq ss (ssget filter)) (setq v nil) (if ss @@ -1188,6 +1301,10 @@ (setq v (car popped)) (progn (setq popped (vflw-pending-pop)) + ;; Headless: statt still live zu fragen (Batch-Haenger, und der Grund + ;; des Desyncs waere verloren) abbrechen bzw. den Antwort-Hook fragen. + (if (and (null popped) (vfl-headless-p)) + (setq popped (vfl-headless-notausgang kind))) (if popped (setq v (car popped)) (setq v (apply livefn nil))))) (vfl-journal-record (if v kind "NIL") v) v) @@ -1199,6 +1316,8 @@ (setq v (car popped)) (progn (setq popped (vflw-pending-pop)) + (if (and (null popped) (vfl-headless-p)) ; siehe vfl-in-value + (setq popped (vfl-headless-notausgang kind))) (if popped (setq v (car popped)) (setq v (apply livefn nil))))) (vfl-journal-record (if (apply gueltig-p (list v)) kind "NIL") v) v) @@ -2026,11 +2145,14 @@ ;; vl-catch-all-apply abgesichert) - ein Fehler hier darf den eigentlichen ;; Kettenbau nie unterbrechen. (defun vfl-view-refresh ( / ) - (vl-catch-all-apply - (function (lambda () - (command "_.PLAN" "_World") - (command "_.ZOOM" "_Extents"))) - nil) + ;; Headless nur Kosten: die Ansicht sieht niemand, _PLAN/_ZOOM laufen aber + ;; vor JEDER Laengeneingabe (vfl-neue-linie-messen) - im Batch reine Zeit. + (if (not (vfl-headless-p)) + (vl-catch-all-apply + (function (lambda () + (command "_.PLAN" "_World") + (command "_.ZOOM" "_Extents"))) + nil)) (princ)) ;; --- Hilfsfunktionen fuer die VF-Einheit --- @@ -2087,6 +2209,10 @@ ;; ohnehin immer die des Vorgaengers, ein Journal-Eintrag dafuer waere ;; redundant. Rueckgabe: (deltaL . hz) oder nil bei Abbruch/leerer Eingabe. (defun vfl-in-abstand (p-akt hz-vorgabe prompt / p2 rad ux uy deltaL hz-aktuell popped) + ;; Eigene Replay-Logik (zwei Eintraege pro Aufruf), darum ein eigener + ;; Headless-Riegel: leere Queue = Desync, nicht "Nutzer soll picken". + (if (and (null *vfl-replay-queue*) (vfl-headless-p)) + (vfl-headless-abbruch "DL")) (if *vfl-replay-queue* (progn ;; Replay: IMMER nur deltaL aus der Queue lesen (nie einen Punkt). Bei @@ -2100,6 +2226,10 @@ (if (and deltaL (null hz-vorgabe)) (progn (setq popped (vfl-replay-pop)) + ;; Queue endet zwischen DL und Richtung: headless ein Desync, nicht + ;; stillschweigend 0 Grad (das wuerde die ganze Kette verdrehen). + (if (and (null popped) (vfl-headless-p)) + (vfl-headless-abbruch "REAL-hz")) (setq hz-aktuell (if popped (car popped) 0.0))) (setq hz-aktuell hz-vorgabe))) (progn @@ -3520,8 +3650,8 @@ (if (null winkel) (setq winkel gf-max-winkel)) (if (or (<= winkel 0.0) (> winkel gf-max-winkel)) (progn - (alert (ssg-textf "vfl-alert-winkel-ungueltig" - (list (rtos winkel 2 1) (rtos gf-max-winkel 2 1)))) + (vfl-meldung (ssg-textf "vfl-alert-winkel-ungueltig" + (list (rtos winkel 2 1) (rtos gf-max-winkel 2 1)))) (setq gf-ok nil)) (progn (setq richtung "Ab") @@ -3547,7 +3677,7 @@ (setq deltaH (abs deltaH)) (cond ((= richtung "Auf") - (alert (ssg-text "vfl-alert-gf-kann-nicht-steigen")) + (vfl-meldung (ssg-text "vfl-alert-gf-kann-nicht-steigen")) (if dbg-an (dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Linie-GF kann nicht steigen" " (deltaL=" (rtos deltaL 2 0) " deltaH=" (rtos deltaH 2 0) ")"))) @@ -3556,8 +3686,8 @@ (setq winkel (* (atan (/ deltaH deltaL)) (/ 180.0 pi))) (if (> winkel gf-max-winkel) (progn - (alert (ssg-textf "vfl-alert-gefaelle-zu-steil" - (list (rtos winkel 2 1) (rtos gf-max-winkel 2 1)))) + (vfl-meldung (ssg-textf "vfl-alert-gefaelle-zu-steil" + (list (rtos winkel 2 1) (rtos gf-max-winkel 2 1)))) (if dbg-an (dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Linie-GF zu steil (deltaL=" (rtos deltaL 2 0) " deltaH=" (rtos deltaH 2 0) @@ -3647,8 +3777,8 @@ L_GF (nth 2 entscheidung) L_VF (nth 3 entscheidung)) (if (null typ) (progn - (alert (ssg-textf "vfl-alert-vf-nicht-baubar" - (list (rtos deltaL 2 0) (rtos deltaH 2 0) richtung))) + (vfl-meldung (ssg-textf "vfl-alert-vf-nicht-baubar" + (list (rtos deltaL 2 0) (rtos deltaH 2 0) richtung))) (if dbg-an (dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Linie-VF nicht baubar (deltaL=" (rtos deltaL 2 0) " deltaH=" (rtos deltaH 2 0) @@ -3769,8 +3899,8 @@ (if (null typ) (progn - (alert (ssg-textf "vfl-alert-segment-nicht-baubar" - (list (rtos deltaL 2 0) (rtos deltaH 2 0) richtung))) + (vfl-meldung (ssg-textf "vfl-alert-segment-nicht-baubar" + (list (rtos deltaL 2 0) (rtos deltaH 2 0) richtung))) (if dbg-an (dbgmsg (strcat "GEOMETRIE FEHLGESCHLAGEN: Linie (automatisch) nicht baubar" " (deltaL=" (rtos deltaL 2 0) " deltaH=" (rtos deltaH 2 0) @@ -4898,7 +5028,7 @@ (setq ecke-bad (vfl2-pruefe-eckwinkel kette *vfk-eckwinkel-toleranz*)) (if ecke-bad (progn - (alert (ssg-textf "vfl-vwnb-alert-eckwinkel" (list (rtos ecke-bad 2 1)))) + (vfl-meldung (ssg-textf "vfl-vwnb-alert-eckwinkel" (list (rtos ecke-bad 2 1)))) (exit))) ;; --- 5. Setup --- @@ -4918,7 +5048,7 @@ (progn (if (< first-line 0) (setq first-line i)) (setq last-line i))) (setq i (1+ i))) (if (< first-line 0) - (progn (alert (ssg-text "vfl-vwnb-alert-keine-gerade")) (exit))) + (progn (vfl-meldung (ssg-text "vfl-vwnb-alert-keine-gerade")) (exit))) ;; --- 6. Bau-Schleife (Meilenstein 2: GF + VF-Laeufe, Run-State-Machine) --- ;; run-typ: nil / "GF" / "VF". Beim Wechsel GF<->VF wird die VF-Einheit @@ -5026,7 +5156,7 @@ ;; --- GF-Bogen --- (progn (if (null frame) - (progn (alert (ssg-text "vfl-m3-alert-beginnt-bogen")) (exit))) + (progn (vfl-meldung (ssg-text "vfl-m3-alert-beginnt-bogen")) (exit))) (if (= run-typ "VF") ; VF-Lauf vor GF-Bogen schliessen (progn (setq frame (vfl2-vf-close frame (car (frame->hz-winkel frame)))) (setq run-typ nil))) @@ -5286,7 +5416,7 @@ br-winkel br-gf1 br-gf2 br-lvf br-richtn pt letzt-winkel vfl-nummer lastEnt anzahl-gf anzahl-vf hoehe-bis soll-ende ist-ende old-error vfl-ins - member carrier-idx kv-variante seg-hz letzt-koerper-hz + vf-run-member carrier-idx kv-variante seg-hz letzt-koerper-hz vf-first-line vf-last-line climber-span mid-hor z-aftermotor gf2-drop gf2-planar br-lvf this-lvf nach-kurve n-climb n-hor target-climb winkel-list @@ -5415,7 +5545,7 @@ (setq ecke-bad (vfl2-pruefe-eckwinkel kette *vfk-eckwinkel-toleranz*)) (if ecke-bad (progn - (alert (ssg-textf "vfl-m3-alert-eckwinkel" (list (rtos ecke-bad 2 1)))) + (vfl-meldung (ssg-textf "vfl-m3-alert-eckwinkel" (list (rtos ecke-bad 2 1)))) (exit))) ;; --- 5. Klassifizierung (Phase A: nur speichern, nichts bauen) --- @@ -5493,9 +5623,11 @@ ;; (Vario-Kurve gehoert in die VF-Einheit, unterbricht den Lauf also NICHT). (setq vf-start -1 vf-ende -1 vf-count 0 in-vf nil i 0) (foreach e plan - (setq member (or (and (= (car e) "Linie") (= (nth 3 e) "VF")) - (and (= (car e) "Bogen") (= (nth 5 e) "Vario-Kurve")))) - (if member + ;; NICHT "member" nennen: eine gleichnamige Lokale verdeckt in AutoLISP das + ;; Builtin member, das weiter unten im selben Scope gebraucht wird. + (setq vf-run-member (or (and (= (car e) "Linie") (= (nth 3 e) "VF")) + (and (= (car e) "Bogen") (= (nth 5 e) "Vario-Kurve")))) + (if vf-run-member (progn (if (not in-vf) (setq vf-count (1+ vf-count) vf-start i in-vf t)) (setq vf-ende i)) @@ -5647,7 +5779,7 @@ (if (= klass "GF-Bogen") (progn (if (null frame) - (progn (alert (ssg-text "vfl-m3-alert-beginnt-bogen")) (exit))) + (progn (vfl-meldung (ssg-text "vfl-m3-alert-beginnt-bogen")) (exit))) (setq frame (vfl-insert-gf-bogen-block frame bwinkel bseite))) (princ (ssg-text "vfl-m3-kurve-front-uebersprungen"))))) (setq i (1+ i))) diff --git a/doc/TODO-plan-vf-interactive.md b/doc/TODO-plan-vf-interactive.md new file mode 100644 index 0000000..f93a96a --- /dev/null +++ b/doc/TODO-plan-vf-interactive.md @@ -0,0 +1,581 @@ +# TODO/Plan: VF-Linienzug ohne GUI/Konsole aufrufbar machen + +**Stand:** 2026-09-02 · **Status:** Stufe 0 fertig (im CAD noch ungetestet) +**Herkunft:** Planungslauf (Plan-Modus) vom 2026-09-02, hier eingecheckt zur Nachverfolgung. + +## Fortschritt + +| Schritt | Beschreibung | Status | +|---|---|---| +| P0 | `vfl-journal-reset` im Dispatcher hochziehen | **erledigt** | +| P0b | `member`-Shadowing in `vf-linienzug-modus2` | **erledigt** | +| P1 | `vfl-meldung` statt `alert` in den Bau-Pfaden | **erledigt** | +| P2 | `*vfl-headless*`: leere Replay-Queue wird zum Fehler | **erledigt** | +| P3 | Kleinkram (`*vfl-meldungen*`-Reset, `vfl-view-refresh`) | **erledigt** | +| S1 | Stufe 1: `Lisp/vf_spec.lsp` (Spec -> Journal -> Replay) | offen | +| S2 | Stufe 2: echte Funktionstrennung (Schritte 0-11) | offen | +| PY | `lib/vf_journal_export.py`: Grammatik vervollstaendigen | **erledigt** | + +### Was Stufe 0 konkret gebracht hat + +Neue Symbole in `Lisp/vf_linienzug.lsp` (Abschnitte "TEIL 0a-2" und "TEIL 0a-3"): + +| Symbol | Zweck | +|---|---| +| `*vfl-meldungen*` / `vfl-meldung` | Bau-Meldungen sammeln statt blockierend zu alerten (`alert` nur bei `ssg-gui-p`, sonst `princ`) | +| `*vfl-headless*` / `vfl-headless-p` | Schalter "keine Live-Eingabe erlaubt" | +| `vfl-headless-abbruch` / `-notausgang` / `-ort` | harter Abbruch mit Fundstelle bzw. Hook-Variante | +| `*vfl-headless-fehler*` | Diagnose-Alist (`was`/`glied`/`eingabe`/`ort`) | +| `*vfl-headless-antwort-fn*` | optionaler Hook fuer nicht vorhersagbare Rueckfragen | + +Eingebaut an den vier Eingabe-Engstellen (`vfl-in-value`, `vfl-in-value-p`, +`vfl-in-selection` 2x, `vfl-in-abstand` 2x). `vfl-journal-reset` loescht +Meldungen und Diagnose mit; `vfl-view-refresh` ueberspringt headless +`_PLAN`/`_ZOOM`. `tests/test_hundm05.lsp` schaltet `*vfl-headless*` ein +(Save/Restore in `hundm05:stubs-an`/`-aus`), zaehlt erreichte Live-Eingaben +(`*hundm05-prompts*`) und schreibt `prompts`, `headless_fehler`, `meldungen` +ins Ergebnis-JSON; `tests/test_hundm05.py` prueft alle drei. + +Bereits abgesichert, ohne CAD lauffaehig: `tests/test_vf_headless_statisch.py` +(10 Tests) haelt das Design gegen Verrottung - eingechecktes Inventar aller +`alert`/`get*`/`ssget`/`new_dialog`-Fundstellen je Funktion (ein neues +`getreal` in einer Bau-Funktion faellt auf, auch wenn sein Zweig im Test nie +erreicht wird), Praesenz des Riegels in allen vier Wrappern, Diagnose-Reset +in `vfl-journal-reset` und die P0-Reihenfolge im Dispatcher. Der Waechter +wurde gegengeprobt: ein injizierter `alert` im Bau-Pfad wird erkannt, ein +`alert` in einem Kommentar nicht. + +**CAD-Lauf 1 (2026-09-03)**: die erste Kette wurde headless gebaut +(">>> VF chain inserted! <<<", kein Prompt, kein Dialog) - der Riegel und der +Replay funktionieren also. Danach brach der Treiber ab: + +``` +[TEST_HUNDM05] FEHLER in der Kettenschleife: + no function definition +``` + +`entprev` gibt es in AutoLISP nicht (nur `entnext` und `entlast`) - mein +Fehler in `hundm05:last-insert-prefix`, der Suche nach dem fertigen +`VF_`-Block. Erreicht wurde sie erst, weil `entlast` nach dem Bau nicht der +Block ist (danach entstehen noch Beschriftungstexte). Behoben: + +- Suche laeuft **vorwaerts** (`hundm05:suche-vorwaerts`, letzter Treffer + gewinnt), und zwar ab dem Zeichnungsstand VOR dem Bau (`vor-ent`, in + `hundm05:build-linienzug` gemerkt). Das ist schnell und kann nicht den + Block eines FRUEHEREN Baus liefern - deshalb dort bewusst kein + `entlast`-Kurzschluss. +- **Dieselbe Falle in `tests/test_mubea.lsp`** (`mubea:last-insert-prefix`) + mitbehoben. Sie schlug dort nie zu, weil `entlast` bisher immer sofort + traf; `entprev` war nur der nie erreichte Schleifenschritt. +- Die Kettenschleife fangt jetzt **je Kette** (`vl-catch-all-apply` um + `hundm05:build-linienzug`): ein Fehler NACH dem Bau nimmt nicht mehr die + restlichen vier Ketten mit ("0 OK, 0 Fehler" ohne jede Auskunft). +- Neuer Waechter `test_keine_nicht_existierenden_funktionen` in + `tests/test_vf_headless_statisch.py` prueft alle `.lsp` in `Lisp/` und + `tests/` gegen eine Liste erfundener Funktionen (`entprev` u.a.) - genau + diese Fehlerklasse (falscher Name in einem selten erreichten Zweig) kostet + sonst jedes Mal einen CAD-Lauf. Gegengeprobt: ein wieder eingebautes + `entprev` wird gefunden, das Wort im Kommentar nicht. +- `vfl-meldung` verkraftet jetzt `txt` = nil (fehlender i18n-Schluessel + wuerde `strcat` genau dann abbrechen, wenn schon etwas schiefging). + +**Noch offen** (braucht BricsCAD): `TEST_HUNDM05` muss 5x +`"status": "executed"` mit `prompts` 0 liefern, und eine interaktive +Gegenprobe (`FOERDERANLAGE` -> Linienzug Modus 1, Doppelklick-Edit, +`SSG_DIM_SWITCH`) muss unveraendert aussehen. Erst danach kann in +`tests/alltests.json` das `"disabled": true` der hundm05-Zeile weg. + +**Bewusst nicht umgestellt**: `vfl-konvertiere-ent` (Batch-2D/3D) laeuft +weiter ohne `*vfl-headless*` - eine Live-Rueckfrage ist dort bisher gewolltes +Verhalten. Kandidat fuer Stufe 1, wenn der Ergebnis-Record steht. + +## Kontext + +`VF_n`-Ketten entstehen heute nur interaktiv: `vf-linienzug-modus` (Modus 1, +freies Zeichnen), `vf-linienzug-modus2` (Modus 2, Pfad + Zielhoehe) und +`vf-linienzug-modus3` (Modus 3, Vorwaerts-Nachbau, Legacy) fragen jeden Wert +einzeln per `getpoint`/`getstring`/`getint`/`getreal` bzw. per DCL-Wizard ab. +`tests/testdata/object_data.md` Abschnitt 3.3 fuehrt den Linienzug darum als +"nicht scriptbar". + +Ziel: eine Kette soll **aus einem Stapel Eingabedaten plus einer Handvoll +LISP-Funktionen** gebaut werden koennen - ohne Dialog, ohne Konsolenfrage. +Damit werden Regressionstests (siehe `tests/test_hundm05.lsp`, 5 echte Ketten +aus `data/polylines.dxf`), Batch-Nachbauten und der 2D/3D-Umbau reproduzierbar, +und die Bau-Logik wird pro Abschnitt einzeln aufrufbar. + +Entscheidungen des Auftraggebers (fix): +- **Zweistufig**: erst Daten-API (Spec -> Journal -> Replay), dann echte + Trennung der Misch-Funktionen in Frage-Schale + reinen Bauteil. +- **Modus 1 + 2 vollstaendig**, Modus 3 nur duenn gewrappt (ist als "wird + umgebaut" markiert). +- **Eingabeformat**: benannte LISP-Spec (Alist je Abschnitt) + JSON-Loader. +- **Fehlerverhalten**: fail fast mit Ergebnis-Record, nichts raten, kein Dialog. + +Zeilennummern in diesem Dokument beziehen sich auf den Stand bei der Planung +(`Lisp/vf_linienzug.lsp`, `Lisp/vf_core.lsp`) und verschieben sich mit jeder +Aenderung - im Zweifel per Funktionsname suchen. + +--- + +## Teil 1 - Befund: was ist heute schon headless moeglich? + +| Modus | Headless heute? | Mechanismus | Blocker | +|---|---|---|---| +| 1 (frei zeichnend, `vf-linienzug-modus` :3211) | **Ja, in der Praxis** | Journal-Replay: `vfl-journal-replay-start` :1545 fuellt `*vfl-replay-queue*`, alle `vfl-in-*`-Wrapper lesen zuerst daraus. Produktiv genutzt von `vfl-edit-ent` :4322, `vfl-edit-glied` :4190, `vfl-konvertiere-ent` :4292 und von `tests/test_hundm05.lsp` | Alerts nicht abschaltbar (:3523, :3550, :3559, :3650, :3772); **leere Queue faellt still auf Live-Eingabe zurueck** (:1186-1191); Daten muessen in exakter Frage-Reihenfolge vorliegen | +| 2 (Pfad + Zielhoehe, `vf-linienzug-modus2` :5278) | **Ja, gleicher Mechanismus** | Marker `"linienzug2"`, Replay-Einstieg `vfl-edit-ent2` :4397 | Pfad-Objekte (LINE/ARC) muessen in der Zeichnung liegen - das Journal speichert nur Handles (`OBJS`, `handent` :1060); **keine STEP-Marker** -> kein Sektions-Edit; Dispatcher `vf_core.lsp:1139` ruft `vfl-journal-reset` NICHT (Journal des Vorlaufs wird als Praefix mitgeschrieben); Alerts :5418, :5650; lokale Variable `member` :5289/:5496 verdeckt die Builtin-Nutzung :5686/:5759 | +| 3 (Vorwaerts-Nachbau, `vf-linienzug-modus3` :4806) | **Nein** | Alle Eingaben laufen zwar durch die `vfl-in-*`-Wrapper (im Prinzip replaybar), aber: kein `vfl-journal-reset`, **kein** `vfl-journal-xdata-schreiben`, kein Marker, `*error*`-Hook wickelt keine Teil-Geometrie (:4835-4844) | bewusst so, Kommentar :4825-4827; fehlt: Reset + XDATA-Schreiben + Marker + Edit-Zweig | + +Modus-uebergreifende Blocker: +- **40 `alert`-Aufrufe** im Cluster, keiner durch `ssg-gui-p` gesichert; im + Bau-Pfad erreichbar: :3523/:3550/:3559/:3650/:3772 (M1), :5418/:5650 (M2), + :4901/:4921/:5029 (M3), :1067 (Replay: Handles fehlen), :123 (GF-Modul + fehlt). Mehrere mit anschliessendem `(exit)`. +- **`(ssg-gui-aus)` deckt nur zwei Stellen ab**: `vf_core.lsp:632` + (`vf-frage-element-winkel`) und `vf_linienzug.lsp:447` (`vfl-wizard-mode-p` + -> alle `vflw-*`-Dialoge). Die Nicht-Wizard-DCLs (`variofoerderer_basis`, + `variofoerderer_winkel`, `gefaellestrecke`, `vfl_edit_modus`, `vfl_edit` und + die drei wiederverwendeten `vflw_*`-Edit-Dialoge :3962/:4000/:4044) bleiben + modal. +- **Der Dispatcher selbst fragt**: `c:VarioFoerderer` (`vf_core.lsp:1073`) holt + Anlagentyp (`getint` :1108) und Untermodus (`getint` :1130) roh ab. Ein + headless Aufruf muss darum immer direkt `vf-linienzug-modus*` aufrufen. +- Rohe Eingaben ohne Journal in Nebenpfaden: `vfl-praeambel-erfragen` :4445, + `c:VF_SEKTION_RESTORE` :4496, `c:VFL_KS_DIAG` :4622, `c:Vario_Kette_Merge` + :6347. +- **`ATTREQ`/`ATTDIA`**: ohne `0` fragt das `(command "_.INSERT" ...)` in den + Einfuegeprimitiven nach Attributwerten - der wahrscheinlichste Haenger. + +Namensfalle: die `vfl3-*`-Helfer (:5093-5276) gehoeren zu **Modus 2**, die +`vfl2-*`-Helfer (:4703-4804) zu **Modus 3**. + +--- + +## Teil 2 - Invarianten + +| # | Invariante | Sicherung | +|---|---|---| +| I1 | Jeder fertige `VF_n`-Block traegt ein gueltiges `SSG_VF_EDIT`-Journal (Marker `"linienzug"`/`"linienzug2"`) und bleibt per Doppelklick editier- und 2D/3D-konvertierbar | Stufe 1 baut ueber die unveraenderte Modus-Funktion (schreibt :3867 selbst). Stufe 2: **kein Bauteil beruehrt das Journal** - im Frage-Pfad erzeugen es die `vfl-in-*`-Wrapper, im Daten-Pfad wird es einmal aus der Spec erzeugt und vor dem Bau gesetzt | +| I2 | Kein Prompt, kein Dialog | GUI aus + Wizard aus + zaehlende Eingabe-Stubs + `*vfl-headless*` (harter Fehler statt Frage) | +| I3 | Nichts geraten | Leere Queue = Fehler mit Fundstelle; jede abgewiesene Sektion erzeugt eine gesammelte Meldung; Spec-Validierung bricht vor dem ersten Insert ab | +| I4 | Interaktives Verhalten unveraendert | Jede Produktionsaenderung ist ein No-Op, solange `*ssg-gui-aus*` und `*vfl-headless*` nil sind | + +--- + +## Teil 3 - Stufe 0: Vorarbeiten in der Produktion + +Fuenf kleine Aenderungen, alle No-Ops im interaktiven Betrieb. Reihenfolge = +Empfehlung; P0 und P1 sind eigenstaendige Bugfixes und koennen sofort raus. + +**P0 - `vfl-journal-reset` hochziehen** (`Lisp/vf_core.lsp:1139`). Heute nur im +Modus-1-Zweig. Folge: ein frischer Modus-2-Lauf erbt das Journal des +Vorlaufs und schreibt es in die XDATA - ein spaeterer Doppelklick spielt +Muell vor. Reset vor die `cond` ziehen. Reiner Bugfix (die Edit-Pfade laufen +nie hier durch), behebt zugleich ein veraltetes `*vfl-seg-glied-letzte*`. + +**P0b - `member`-Shadowing** in `vf-linienzug-modus2`: lokale Variable +`member` (:5289, gesetzt :5496) gegen Builtin-Nutzung (:5686, :5759) im +selben Scope. Lokale umbenennen (z.B. `vf-run-member`). + +**P1 - `vfl-meldung` statt `alert` in den Bau-Pfaden.** Neuer Helfer neben +`vfl-journal-record` (~:170): Text in `*vfl-meldungen*` sammeln, `dbgmsg` +schreiben, dann `alert` (GUI an) **oder** `princ` (GUI aus). `alert` -> +`vfl-meldung` an genau diesen Stellen: :123, :1067, :3523, :3550, :3559, +:3650, :3772, :5418, :5650, :4901, :4921, :5029. Die reinen +Interaktiv-Alerts (:3968, :4006, :4050, :4266, :4325, :4566, :4593) bleiben. +Ohne P1 blockiert ein Alert den Lauf und der Text - die einzige Auskunft, +*welche* Sektion abgewiesen wurde - ist verloren. + +**P2 - `*vfl-headless*`: leere Queue wird zum Fehler.** Neuer Notausgang +`vfl-headless-notausgang (was)` neben `vfl-replay-pop` (~:210): setzt +`*vfl-headless-fehler*` samt Glied-Index und Eingabe-Nummer (aus +`vfl-steps-zaehlen` :1612 und `(length *vfl-journal*)`), meldet und bricht ab. +Optionaler Hook `*vfl-headless-antwort-fn*` darf einen Wert liefern (wird +journalisiert und als Warnung protokolliert). Vier Einbaustellen, je ein +neuer `cond`-Zweig vor der Live-Eingabe: +- `vfl-in-value` :1185 und `vfl-in-value-p` :1196 - deckt `vfl-in-point`, + `-string`, `-real`, `-int`, `vfl-menu`, `vfl-menu-int`, `vfl-menu-winkel` + und die AS/ES-Winkelfrage ab (rund 90 % aller Eingaben) +- `vfl-in-selection` :1055 (vor dem `ssget` :1069) **und** der + `fehlt > 0`-Zweig :1066: unter headless abbrechen statt mit einer + Teilauswahl weiterzubauen (aendert sonst still die Modus-2-Fragenzahl) +- `vfl-in-abstand` :2113 (eigene Replay-Logik) + +**P3 - Kleinkram**: `*vfl-meldungen*` in `vfl-journal-reset` :157 mitloeschen; +`vfl-view-refresh` :2028 unter headless ueberspringen (`_.PLAN`/`_.ZOOM` vor +*jeder* Laengeneingabe, im Batch nur Kosten). + +--- + +## Teil 4 - Stufe 1: Daten-API (Spec -> Journal -> Replay) + +Neue Datei **`Lisp/vf_spec.lsp`**, geladen aus `vf_core.lsp` direkt nach +`vf_linienzug.lsp` (:1062) - deckt beide Ladewege ab (MNL ueber +`VarioFoerderer.lsp` und `Lisp/ssg_load.lsp`), also **keine** Aenderung an +`menu/SSG_LIB.mnl`. Guarded Nachlade-`load` am Kopf fuers isolierte Testen +(Muster `ssg_ks_insert` in `vf_core.lsp:66`). + +### 4.1 Spec-Form + +```lisp +'(("version" . 1) ("modus" . 1) ("name" . "VF_LZ_01") ("dim" . "3D") + ("start" ("punkt" 4912.0 1231.0 2100.0) ("hoehe" . 2154.0) + ("as" . T) ("as-winkel" . "90") ("as-seite" . "links")) + ("sektionen" + (("glied" . "Linie-GF") ("dl" . 5500.0) ("hz" . 0.0) + ("gefaelle" . "winkel") ("winkel" . 3.0) ("ende" . "nein")) + (("glied" . "Horizontal-VF") ("dl" . 4566.7) + ("gf-verteilung" . "einlauf") ("sep-vor" . nil) ("sep-nach" . nil) + ("weiter" (("sub" . "vario-kurve") ("winkel" . 90) + ("seite" . "links") ("variante" . "aussen")) + (("sub" . "horizontal") ("dl" . 250.0)) + (("sub" . "kettenende") ("es" . T) ("dl" . 6215.0) + ("hoehe" . 4500.0))) + ("ende" ("es-winkel" . "90") ("es-seite" . "rechts"))))) +``` + +Durchgaengig **Domaenenwerte** (`"links"`, `"winkel"`, `"aussen"`, `T`/`nil`), +keine Menue-Codes; rohe Codes (`"1"`/`"2"`) werden als Durchreiche fuer +zurueckgelesene Journale akzeptiert. Glied-Typen: `GF-Bogen`, `Linie-GF`, +`Linie-VF`, `Horizontal-VF`, `Linie`, plus das automatische `ES`-Glied. + +Die variable VF-Einheit (heute bewusst **nicht** im Schema, Kommentar :260-292) +wird als `("weiter" ...)`-Liste modelliert, immer terminiert mit `motorstation` +oder `kettenende`; Sub-Typen `horizontal`, `vario-kurve`, `auf-ab`. + +**Drei Fallen, die der Uebersetzer besitzen muss:** +1. **Menue-Code haengt am Frame**: nur die *erste* Sektion laeuft ohne Frame + (4 Optionen :3396), alle spaeteren mit (5 Optionen :3378). Also + `Linie-GF` = `"1"` in Sektion 1, `"2"` ab Sektion 2 usw.; `GF-Bogen` in + Sektion 1 = Validierungsfehler (nicht angeboten). +2. **`hz` genau einmal**: `vfl-in-abstand` journalisiert die Richtung nur bei + `hz-vorgabe = nil` (:2130) - das ist ausschliesslich das erste Segment der + Kette. `hz` an spaeterer Stelle verschiebt die ganze Queue -> harter + Fehler; `hz` fehlt in Sektion 1 -> harter Fehler. +3. **`gf-verteilung` ist zwischen den Modi invertiert**: Modus 1 :3118 + (`"1"` = haelfte), Modus 2 :5729 (`"2"` = haelfte). Code-Tabelle pro Modus. + +Nicht vorhersagbar bleibt die **Winkelwahl** (`vfl-waehle-winkel` :1656 fragt +nur, wenn mehrere Kandidaten gueltig sind - abhaengig von der real gemessenen +Restlaenge). Loesung: optionales Feld `("winkel-idx" . n)`; fehlt es und der +Lauf braucht es, greift P2 mit einer Fehlermeldung, die Sektion und +Eingabe-Nummer nennt. Kein Raten (I3). + +### 4.2 Uebersetzer + +`vfl-spec-pruefen` (nur Fehlerliste, fragt und baut nie) und +`vfl-spec->journal` / `-m2` / `-m3` (Vorwaerts-Journal). Rueckgabe-Alist +`(("journal" . ...) ("fehler" . ...) ("warnungen" . ...))`. + +Wiederverwendung statt Neubau - diese Bausteine existieren schon: +`vfl-schema-slice-bauen` :326, `vfl-schema-feld-pruefen` :303 (mit den +erlaubten Werten aus `*vfl-glied-schema*` :243, `*vfk-gf-bogen-winkel*`, +`*vfk-as-es-winkel*`), `vfl-gf-bogen-slice-bauen` :3941, `vfl-es-slice-bauen` +:4093, `vfl-vario-slice-bauen` :4110, `vfl-journal-list->string` :1258. +Neu sind nur `Linie-GF`/`Linie-VF`/`Horizontal-VF`/`Linie` und der VF-Fluss. + +Wichtig: `vfl-schema-slice-bauen` loggt Schemaverstoesse und schreibt den Wert +**trotzdem** ("Sicherung geht vor", :324) - richtig fuer den Recovery-Pfad, +falsch hier. Darum prueft `vfl-spec-*` mit `vfl-schema-feld-pruefen` selbst +vorab und ruft die Slice-Bauer erst mit sauberen Werten. `vfl-schema-slice-bauen` +bleibt unangetastet. + +Emitter-Regel fuer den VF-Fluss (macht den Uebersetzer trivial): die +`ist-Endpunkt`-Antwort wird zwar an zwei Stellen gestellt (Schleifenkopf :2560 +und innerhalb `vfl-baue-horizontal-koerper` im Ziel-Modus :2308, von dort als +`vor-antwort` zurueckgegeben), landet in der flachen Queue aber **immer an +derselben linearen Position** - direkt nach den Werten des Vorgaenger-Knotens. +Also je Knoten: `STR ende-code` (`"1"` Motor / `"3"` Kettenende / sonst `"2"`), +bei `"2"` zusaetzlich `STR naechstes-vf` plus die Nutzlast des Sub-Typs. + +### 4.3 Headless-Rahmen und Ergebnis-Record + +```lisp +(vfl-headless-an opt) / (vfl-headless-aus) ; Save/Restore, MUSS immer laufen +(vfl-bau-aus-spec spec opt) ; -> Ergebnis-Record +(vfl-bau-aus-spec-liste specs opt) / (vfl-bau-aus-json datei opt) +(vfl-result->json rec) / (vfl-results-schreiben recs datei) +(c:VF_SPEC_BAU) ; Datei aus DXFM_VF_SPEC, fragt nichts +``` + +`vfl-headless-an`: `*vfl-headless*` T, `*vfl-wizard-mode*` nil, `(ssg-gui-aus)`, +zaehlende Stubs fuer `vfl-getpoint`/`getstring`/`getint`/`getreal`/`getkword`/ +`alert`/`new_dialog` (jeder erhoeht `*vfl-spec-prompts*` und liefert nil), +`ssg-start` mit `OSMODE`/`ATTREQ`/`ATTDIA` = 0, `init-bibliothek` falls noetig. +**`ssget`/`entsel` bewusst NICHT stubben** - `vf-next-number` +(`vf_core.lsp:322`) braucht `(ssget "X" ...)`; die eine Bau-Pfad-`ssget` sitzt +in `vfl-in-selection` und ist ueber P2 abgedeckt. + +Pro Kette: validieren (Fehler -> Status `"spec-fehler"`, nichts gebaut) -> +`*ssg-ils-dim*` **je Kette** neu setzen (der Abbruch-Handler :3286 nullt es) -> +`lastEnt` merken -> `vfl-journal-reset` + `vfl-journal-replay-start` -> +`*error*` sichern, `(vl-catch-all-apply 'vf-linienzug-modus '())`, `*error*` +zurueck (genau die Klammer aus `hundm05:build-linienzug`) -> Record aus den +Globals sammeln -> aufraeumen. + +Bewusster Kompromiss (wie beim Batch-Konverter `vfl-konvertiere-ent` :4292): +weil gefangen wird, laeuft der `*error*`-Handler der Modus-Funktion nicht, die +Teil-Geometrie wird also **nicht** zu einem Block gewickelt. Option +`("bei-fehler" . "behalten"|"loeschen")` entscheidet, ob sie stehenbleibt +(per `c:VF_SEKTION_RESTORE` rettbar, weil `vfl-segment-xdata-sichern` :3855 +pro Iteration schon `SSG_VF_EDIT_SEG`/`_PRE` geschrieben hat) oder ab +`lastEnt` weggeraeumt wird. + +Ergebnis-Record: Feldreihenfolge von `hundm05:result-json` beibehalten +(`test_id`, `kind`, `status`, `dimension`, `block_name`, `block_handle`, +`insert_point`, `actual_attributes`), dazu `eingaben_gesamt`, +`eingaben_offen`, `sektionen_soll`/`sektionen_ist`, `glieder`, `prompts`, +`warnungen`, `fehler_sektion`, `fehler_text`, `ziel_abweichung`, `journal`. +Status: `executed` | `warnung` | `desync` | `abbruch` | `spec-fehler`. + +**Drei unabhaengige Desync-Detektoren** (eine geometrisch abgewiesene Sektion +wirft keinen Fehler, sie verbraucht nur weniger Eintraege): offene +Queue-Werte, Vergleich der erzeugten STEP-Labels (`*vfl-journal*`) gegen die +Glied-Folge der Spec (nennt die erste abweichende Sektion), und die +gesammelten Meldungen aus P1. + +### 4.4 Modus 2 und Modus 3 + +Modus 2: Spec traegt `("pfad-handles" ...)` (Entity-Handles der LINE/ARC-Trasse) +plus `("segmente" ...)` in Pfadreihenfolge. Vor dem Bau wird die Segmentzahl +gegen die aufgeloesten Handles geprueft und bei Fehlbestand abgebrochen - die +Fragenzahl kommt aus `gf-sortiere-objekte`/`gf-analysiere-kette` auf der +*realen* Geometrie. Eine Gerade direkt nach einer Vario-Kurve wird nicht +gefragt (:5449); der Uebersetzer leitet das selbst ab. + +Modus 3: nur `vfl-bau-aus-spec` mit Modus-3-Uebersetzer. **Dokumentierte +Einschraenkung**: I1 gilt nicht (kein Journal-XDATA, also kein Doppelklick-Edit, +keine 2D/3D-Konvertierung). Die volle Nachruestung braeuchte vier gekoppelte +Eingriffe (Reset, XDATA-Schreiben, Marker `"linienzug3"` in der Whitelist +:1340, `vfl-edit-ent3`) in dem Modus, der laut Code ohnehin ersetzt wird - +darum bewusst zurueckgestellt. + +### 4.5 JSON-Form + +`ssg-load-json`/`ssg-parse-json-array` (`ssg_core.lsp:1146/1167`) liest +**zeilenweise und flach**: jedes `{` beginnt ein Objekt, Verschachtelung ist +unmoeglich, Zahlen-Arrays muessen in einer Zeile stehen. Also dieselbe Form wie +`tests/testdata/hundm05.json`: ein Kopf-Objekt (`spec_id`) plus flache +Sektions-Objekte (`glied`), Sub-Objekte (`sub`) und Abschluss (`ende`), +gruppiert wie `hundm05:gruppiere`. Wahrheitswerte als `1`/`0` +(`ssg-cfg-parse-value` kennt kein `true`/`false`). Ein `nr`-Feld pro Sektion +macht handgeschriebene Dateien selbstpruefend. + +--- + +## Teil 5 - Stufe 2: echte Funktionstrennung + +Konvention: reiner Bauteil bekommt Suffix `-bauen` (Geometrie aus Parametern) +bzw. `-setzen` (nur Globals); **die Frage-Schale behaelt den bestehenden +Namen** - dadurch aendert sich keine einzige Aufrufstelle in Modus 1/2/3. + +| heute gemischt | reiner Bauteil (neu) | +|---|---| +| `vfl-baue-horizontal-koerper` :2270 | `vfl-hor-koerper-bauen (frame hz dL sep-vor sep-nach ende-code gf2-laenge)` | +| `vfl-body-abschluss` :2409 | `vfl-body-abschluss-bauen (frame letzt-hz p-umlenk dL hz-neu hoehe-ziel es-gewuenscht)` | +| `vfl-vf-einheit` :2497 | `vfl-vf-eingang-bauen` / `vfl-vf-sub-bauen` / `vfl-vf-ausgang-bauen` / `vfl-vf-einheit-bauen (frame vf-spec)` | +| `vfl-vf-einheit-abschluss` :3112 | `vfl-vf-abschluss-bauen (...)` + `vfl-ziel-report (frame)` | +| `vfl-frage-es-seite` :2956 | `vfl-es-masse-setzen (es-winkel es-seite)` | +| `vfl-waehle-winkel` :1656 | `vfl-winkel-gueltige` + `vfl-winkel-nach-index` | + +`ende-code` ersetzt das Flag `ziel-modus`; der Bauteil gibt dann immer nur +`frame` zurueck (die Doppelrueckgabe `(frame ist-ende-antwort)` existiert nur, +um die *Antwort* nach oben zu geben). + +**Die Winkelfrage sitzt im Solver, nicht in einer Schale.** +`vfl-body-zerlegung` :2357 (also nicht rein, anders als zunaechst +angenommen), `vfl-vf-winkel` :1692, `vfl-vf-entscheidung` :1707 und +`vfl-segment-entscheidung` :1972 rufen alle `vfl-waehle-winkel`. Ein +Parameter wuerde vier Signaturen und alle Aufrufer aendern; stattdessen der +Kanal, den die Datei schon fuer `vfl-menu` nutzt (:1118, dynamisch gebundene +`*vflw-menu-*`): `*vfl-winkel-idx-vorgabe*`. + +**"Alles fragen, dann alles bauen" ist unmoeglich** - und das ist der +zentrale Punkt: `vfl-neue-linie-messen` braucht `(car frame)`, die +Hoehenvorschlaege brauchen `(caddr (car frame))`, und die dL-Reservierung +braucht den *gemessenen* auf_3-Fussabdruck. Deshalb behaelt der interaktive +Treiber seine `while`-Schleife, fragt aber pro Runde **einen** Knoten und baut +ihn sofort ueber denselben reinen Knoten-Bauteil; der Daten-Treiber ist +dieselbe Kette mit `foreach` statt `while`. + +Knotenform: **die rohen Antworten in Frage-Reihenfolge** (nil = nicht gefragt) +plus die abgeleiteten Bauwerte. Dann ist der Serializer ein dummer Flattener. +Dazu gehoert das Feld `dl-verworfen`: `vfl-in-abstand` journalisiert +bedingungslos (:2129), abgewiesene 25-m-Eingaben (:2165) stehen also **im +Journal** - ohne dieses Feld ist Journal -> Spec verlustbehaftet und jeder +Round-Trip-Test scheitert an einer solchen Kette. + +Journal-Invariante im Daten-Pfad: die Spec erzeugt das Journal einmal +(`vfl-spec->journal`), es wird vor dem Bau gesetzt, kein Bauteil ruehrt es an. +Als Dauerbeweis eine `dbgmsg`-Assertion dort, wo beide Erzeuger sich treffen - +in `vf-linienzug-modus` unmittelbar vor `vfl-journal-xdata-schreiben` (:3867) +das aus der Spec serialisierte Journal gegen `(vfl-journal->string)` stellen. +Ohne offene `.dbg`-Datei kostet das nichts, mit Logging beweist es die Treue +bei jedem Bau. + +### Reihenfolge (billig/risikoarm zuerst) + +0. `vfl-ziel-report` aus den zwei byte-identischen Bloecken :3150/:3176 ziehen +1. Winkelwahl (`vfl-winkel-gueltige`/`-nach-index` + `*vfl-winkel-idx-vorgabe*`) +2. ES/AS-Masse (`vfl-es-masse-setzen`) +3. `vfl-hor-koerper-bauen` +4. `vfl-body-abschluss-bauen` +5. VF-Einheit: Eingang/Sub/Ausgang + Schleifenrumpf +6. Daten-Executor fuer die VF-Einheit +7. **Spec + Serializer + Assertion** - dieser Schritt *ist* die Invariante, + vorher nichts ausliefern +8. Einheit-Abschluss +9. Kettenebene, ein Glied-Zweig pro Commit: GF-Bogen -> ES -> Horizontal-VF -> + Linie-GF -> Linie-VF -> Linie +10. `vfl-spec-ausfuehren` (Daten-Pfad ohne Replay-Umweg) +11. Modus 2 - **erst** wenn 2-3 echte `"linienzug2"`-Journale als Referenz + vorliegen (`tests/testdata/hundm05.json` enthaelt ausschliesslich Modus-1- + Ketten); sonst zurueckstellen + +### Wo Geometrie still kippen kann + +- **Die dL-Subtraktionskette** in `vfl-baue-horizontal-koerper` (:2280, :2293, + :2301, :2319/:2323): jeder Schritt mit `(max *vfk-restlaenge-min-clamp* ...)` + geklammert, also **reihenfolgeabhaengig**, und der auf_3-**Insert** sitzt + zwischen Subtraktion 1 und 2 (Fussabdruck gemessen, nicht geschaetzt). Der + Bauteil ist der heutige Rumpf **ohne** die drei `vfl-menu`-Bloecke und + **ohne jede Umsortierung**. Die drei Fragen duerfen nach vorn in die Schale + (keine haengt an `pt`/`dL`), die Subtraktionen nicht. +- `vfl-nach-3grad` :2415/:2643 darf von Schale *und* Bauteil gerufen werden - + es ist idempotent (:2236 prueft `vfl-frame-flach-p`). +- `ein-dx`/`ein-dz`-Nullen (:2588-2593) wandert vom Aufrufer in den Bauteil und + aendert damit das Budget in `berechne-alle-winkel`, also Geometrie. Nur der + "ES nein"-Pfad fuehrt dort durch. +- `ziel-gf2` vs. `L_GF2-bau` im Ausgang (:2706): geht es verloren, entsteht + still die falsche GF2 hinter dem Motor. +- Die `vfl-acc-*`-Aufrufe muessen an derselben Stelle in derselben Reihenfolge + bleiben, sonst vertauschen sich die Komma-Listen `L_VF_m`/`L_GF_m`/`GF_WINKEL` + - in der Zeichnung unsichtbar, im Sivas-Export falsch. +- `*vfl-as-winkel*`/`*vfl-es-winkel*` werden **lazy** von den Blocknamen-Bauern + gelesen (:111/:112) - spaeter gesetzt als heute heisst falscher Block. +- Im Daten-Pfad muss `hz` eines Fortsetzungsknotens "vom Frame erben" (nil) + bedeuten, nie ein gespeicherter Absolutwert: `vfl-insert-vario-kurve-block` + snappt den gemessenen Wert neu (:2838-2841), ein Absolutwert laesst die Kette + driften. +- Die `aus-dx`/`ein-dx`-Save/Restore-Fenster (:1693, :2362, :5125, :5236) sind + heute nie verschachtelt; zwei ineinander und das innere Restore ueberschreibt + das aeussere Save. + +### Ausdruecklich nicht anfassen + +`vf-linienzug-modus3` + alle `vfl2-*`; die `vfl-in-*`-Wrapper und `vfl-menu*`; +`vfl-entry->string` und das XDATA-Layout/die Marker; **alle Journal-Konsumenten** +(brauchen sie Aenderungen, ist die Invariante gebrochen - das ist der +Lackmustest); die reinen `vfs-*`/`berechne-*` in `vf_standard.lsp` (via +`*vf-typ-registry*` mit Standard/Etage geteilt); die `vflw-*`-Wizard-Schicht +und die DCLs; das Buendeln der `*vfl-acc-*` zu einem Record; der +Abbruch-/`*error*`-Pfad. + +--- + +## Teil 6 - Nebenarbeit: Python-Grammatik vervollstaendigen + +`lib/vf_journal_export.py` enthaelt die Journal-Grammatik schon als +Recursive-Descent-Parser (`Journal.dekodiere`), inklusive `vorantwort`- +Mechanik, bedingtem Winkel-`INT` und der `erster_dl`/`REAL hz`-Regel - aber +unvollstaendig: **`Linie-VF` fehlt im Dispatch** (`lauf`, sonst `raise`) und +`vf_einheit` ruft `horizontal_koerper()` unbedingt als ersten Koerper (nur fuer +`Horizontal-VF` richtig, falsch bei gewinkeltem Erstkoerper). Die 5 +HundM-Ketten dekodieren nur, weil ihre Daten diesen Fall nicht enthalten. Beide +Luecken zusammen mit dem LISP-Serializer schliessen: dann pruefen zwei +unabhaengige Implementierungen der gleichen Grammatik einander. + +**Erledigt** (2026-09-02): `Linie-VF` ist im Dispatch (`glied_linie_vf`: DL, +Zielhoehe, optionale Winkelwahl, dann VF-Einheit), und `vf_einheit` bekommt +`erster_horizontal` - das spiegelt die `winkel1`-Verzweigung in +`vfl-vf-einheit`: nur der horizontale Erstkoerper laeuft durch +`vfl-baue-horizontal-koerper` und fragt Separator vor/nach + Endpunkt vorab, +ein gewinkelter wird ohne Frage gebaut. `glied_linie_kettenende` uebergibt +`False` (fuer `typ="VF"` liefert `vfl-segment-entscheidung` immer einen Winkel +> 0). + +Abgesichert von `tests/test_vf_journal_grammatik.py` (9 Tests, ohne CAD und +ohne die 118-MB-DXF: Journale stehen als Token-Listen im Test). Gegenprobe +gemacht: mit dem alten `erster_horizontal`-Verhalten scheitert die Linie-VF- +Tokenfolge (`Token 11: STR erwartet, STEP:ES gefunden`), der Fix ist also +tragend. `tests/testdata/hundm05.json` bleibt nach der Regenerierung +**byte-identisch** - die 5 echten Ketten enthalten keinen der beiden Faelle. + +Offen bleibt der Gegenpart: der LISP-Serializer (`vfl-spec->journal`, Stufe 1). +Erst mit ihm pruefen zwei unabhaengige Implementierungen einander. + +--- + +## Verifikation + +**Stufe 0** (nach P0-P3, interaktiv unveraendert): +- `TEST_HUNDM05` in BricsCAD: 5 Ketten, alle `"status": "executed"`, + `eingaben_offen` 0 - beweist, dass Replay und Journal-XDATA unveraendert sind. +- `python -m pytest tests/test_hundm05.py -q` (Daten-Checks laufen ohne CAD). +- Interaktive Gegenprobe: `FOERDERANLAGE` -> Linienzug Modus 1 mit Wizard, + eine kurze Kette bauen, Doppelklick-Edit, danach `SSG_DIM_SWITCH` - Alert- + Texte und Dialoge muessen identisch aussehen. + +**Stufe 1**: +- Neu `tests/test_vf_spec.lsp` (`C:TEST_VF_SPEC`, Muster `test_vfl_schema.lsp` + mit `tvs-eq`/`tvs-truthy`/`tvs-nil`, in `alltests.json` ohne `"save"`): + Uebersetzer-Unit-Tests ohne Zeichnung - je Glied-Typ das erwartete flache + Journal, die Menue-Code-Tabelle, die `hz`-Regel, die invertierte + `gf-verteilung`, Validierungsfehler. + **Der entscheidende Test**: `tests/testdata/linienzug_tests.json` (eine + vollstaendig kommentierte Kette mit Linie-VF, Horizontal-VF, zwei + Vario-Kurven, Option-3-Kettenende und ES-Glied) in eine Spec uebersetzen und + pruefen, dass `vfl-spec->journal` **dieselbe** Liste liefert. +- Neu `tests/test_vf_spec_bau.lsp` (`C:TEST_VF_SPEC_BAU`, `"save": "dxf"`): + die 5 HundM-Ketten als Spec (`tests/testdata/vf_spec_hundm05.json`) bauen und + das Ergebnis **gegen `tests/output/hundm05_results.json`** stellen - + Attribut fuer Attribut, Einfuegepunkt fuer Einfuegepunkt. Das beweist + Spec -> Journal -> Bau == aufgezeichnetes Journal -> Bau. +- Neu `tests/test_vf_spec.py` + Fixtures in `conftest.py` (Muster der + bestehenden `hundm05_*`-Fixtures mit `pytest.skip`): pro Kette `status` + `executed`, `eingaben_offen` 0, `sektionen_soll` == `sektionen_ist`, + `glieder` == Spec-Glied-Folge, `warnungen` leer, **`prompts` == 0**. +- "Kein Prompt, kein Dialog" in vier Schichten: statischer Waechter + (pytest ohne CAD, vergleicht die Fundstellen von `alert`/`get*`/`ssget`/ + `new_dialog` gegen eine eingecheckte Liste - haelt das Design gegen + Verrottung), zaehlende Stubs (`prompts` im Record als positiver Beweis), + Schalterzustand (`*ssg-gui-aus*` T, `*vfl-wizard-mode*` nil mit + Save/Restore in einer `vl-catch-all-apply`-Klammer), und ein + `tests/test_vf_spec.scr`-Lauf (Muster `test_unit.scr`) mit Sentinel in der + letzten Zeile: fehlt der Sentinel im Log, hat doch ein Prompt geoeffnet. + +**Stufe 2**: nach jedem Schritt `TEST_HUNDM05` + `TEST_VF_SPEC` + `TEST_LINIENZUG`; +nach den Geometrie-Schritten (3, 4, 5, 9) zusaetzlich DXF-Diff Frage-Pfad gegen +Daten-Pfad ueber `lib/dxf_abbild.py`, und Sivas-Export-Vergleich +(`EXPORTSIVAS`) wegen der Reihenfolge in `L_VF_m`/`L_GF_m`. + +--- + +## Getroffene Entscheidungen (aenderbar) + +1. **Modus-2-Pfadobjekte**: die Spec traegt die Entity-Handles. Damit bleibt I1 + exakt und es braucht keinen neuen Marker. Alternative waere ein Marker + `"linienzug2s"` plus Zweig in `vfl-edit-ent2` - erweitert das Markerschema + fuer jeden Leser, daher nicht gewaehlt. +2. **Segment-XDATA im Daten-Pfad**: bleibt erhalten (Paritaet mit dem + interaktiven Pfad), damit `c:VF_SEKTION_RESTORE` auch bei spec-gebauten + Ketten greift. In Stufe 1 automatisch (Bau laeuft durch die Modus-Funktion); + in Stufe 2 braucht der Executor denselben Per-Glied-Hook. +3. **Modus 3**: nur Wrapper, kein Journal-XDATA (zurueckgestellt). +4. **`vf_spec.lsp` als neue Datei** statt Anbau an `vf_linienzug.lsp` (schon + ~6500 Zeilen, groesste Datei im Repo); Abhaengigkeit ist einseitig. + +## Betroffene Dateien + +- `Lisp/vf_linienzug.lsp` - P1/P2/P3-Hooks (:123, :157, :1055, :1066, :1185, + :1196, :2028, :2113, :3523, :3550, :3559, :3650, :3772, :5418, :5650) und + die ganze Stufe 2 +- `Lisp/vf_spec.lsp` - **neu**: Spec, Uebersetzer, Headless-Rahmen, Record +- `Lisp/vf_core.lsp` - P0 (:1139), Ladezeile nach :1062 +- `lib/vf_journal_export.py` - Grammatik vervollstaendigen (`Linie-VF`, + erster Koerper) +- `tests/test_hundm05.lsp` - Prototyp zum Verallgemeinern (`hundm05:gruppiere`, + `-eintrag->journal`, `-result-json`, `-stubs-an`/`-aus`, `-queue-rest`) +- `tests/testdata/linienzug_tests.json` - fixiert den VF-Fluss im Unit-Test +- `tests/testdata/hundm05.json` + `tests/output/hundm05_results.json` - + Referenz fuer die Aequivalenz von Spec-Pfad und Journal-Pfad diff --git a/doc/variofoerderer.md b/doc/variofoerderer.md index 0b744fd..55532be 100644 --- a/doc/variofoerderer.md +++ b/doc/variofoerderer.md @@ -445,6 +445,58 @@ Optionen, bei "Eigene Werte" zusaetzlich L_GF1 als Zahl) -> Bestaetigung --- +## 8b. Nicht-interaktiver Betrieb (Headless) + +Fuer Tests, Batch-Nachbau und den geplanten Spec-Bau (Fahrplan: +`doc/TODO-plan-vf-interactive.md`) muss eine Kette **ohne** Dialog und ohne +Konsolenfrage laufen. Drei Schalter greifen dabei ineinander: + +| Schalter | Datei | Wirkung | +|---|---|---| +| `*ssg-gui-aus*` (`ssg-gui-aus`/`-an`/`ssg-gui-p`) | `ssg_core.lsp` | DCL-/Wizard-Dialoge aus, Module fallen auf ihren Konsolen-/`get*`-Pfad zurueck | +| `*vfl-wizard-mode*` (`VF_WIZARD_AUS`) | `vf_linienzug.lsp` | Linienzug-Assistent aus (die `vflw-*`-Dialoge) | +| `*vfl-headless*` (`vfl-headless-p`) | `vf_linienzug.lsp` | **keine Live-Eingabe mehr**: eine erschoepfte Replay-Queue wird zum harten Abbruch statt zum stillen Rueckfall auf `getpoint`/`getreal` | + +Der dritte Schalter ist der entscheidende. Ein Replay +(`vfl-journal-replay-start`) liefert die Werte nur so lange, wie die Queue +reicht; danach fragten die `vfl-in-*`-Wrapper bisher **stillschweigend** live +weiter - im Batch ein Haenger ohne Hinweis, wo der Ablauf von den Daten +abgewichen ist. Mit `*vfl-headless*` = T bricht statt dessen +`vfl-headless-abbruch` ab und legt in `*vfl-headless-fehler*` die Fundstelle +ab (Art der Eingabe, Glied-Nummer, Eingabe-Nummer). Eingebaut ist der Riegel +an den vier Eingabe-Engstellen: `vfl-in-value`, `vfl-in-value-p` (deckt +`vfl-in-point`/`-string`/`-real`/`-int` und alle `vfl-menu*` ab), +`vfl-in-selection` (Objektauswahl: auch eine unvollstaendige Handle-Aufloesung +bricht ab - eine Teilauswahl wuerde die Fragenzahl des Modus-2-Ablaufs still +verschieben) und `vfl-in-abstand` (eigene Zwei-Eintrags-Logik: DL, und beim +ersten Segment die Richtung). + +Optional darf `*vfl-headless-antwort-fn*` eine Rueckfrage doch noch +beantworten (`(fn was ort)` -> Wert). Gedacht fuer die eine nicht +vorhersagbare Frage: `vfl-waehle-winkel` fragt nur dann, wenn mehrere +Winkel-Kandidaten geometrisch gueltig sind, was von der real gemessenen +Restlaenge abhaengt. Jede so gelieferte Antwort wird protokolliert, damit ein +solcher Lauf nicht als "sauber" durchgeht. + +**Meldungen statt Alerts**: die Bau-Pfade melden abgewiesene Sektionen jetzt +ueber `vfl-meldung` (Text nach `*vfl-meldungen*` + `dbgmsg`, dann `alert` nur +bei erlaubter GUI, sonst `princ`). Interaktiv sieht das unveraendert aus; im +Batch blockiert nichts mehr und der Text - die einzige Auskunft, *welche* +Sektion warum nicht baubar war - bleibt erhalten. Die reinen Interaktiv-Alerts +(fehlende DCL-Datei, "Glied nicht editierbar", "kein Journal") sind bewusst +`alert` geblieben. `vfl-journal-reset` loescht `*vfl-meldungen*` und +`*vfl-headless-fehler*` mit: beides gehoert zum einzelnen Lauf. + +Beispiel: `tests/test_hundm05.lsp` (5 echte Ketten aus `data/polylines.dxf`) +setzt alle drei Schalter mit Save/Restore, ersetzt `getpoint`/`getstring`/ +`getint`/`getreal`/`alert` zusaetzlich durch **zaehlende** Stubs (Netz fuer +Prompts ausserhalb der Wrapper) und schreibt `prompts`, `headless_fehler` und +`meldungen` in `tests/output/hundm05_results.json`. Noch nicht umgestellt: +`vfl-konvertiere-ent` (Batch-2D/3D-Konvertierung) laeuft weiter ohne +`*vfl-headless*` - dort ist eine Live-Rueckfrage bisher gewolltes Verhalten. + +--- + ## 9. Wiederverwendete Bausteine **Geruestoptionen** (`*ssg-geruest-optionen*` in `ssg_core.lsp`, auch bei diff --git a/doc/vf-parameter-struct-analyse.md b/doc/vf-parameter-struct-analyse.md new file mode 100644 index 0000000..a00d68b --- /dev/null +++ b/doc/vf-parameter-struct-analyse.md @@ -0,0 +1,119 @@ +# VarioFoerderer-Cluster — Analyse Parameter-Buendelung (Struct-Potential) + +Bestandsaufnahme der globalen Variablen und Funktionssignaturen in +`Lisp/vf_core.lsp`, `vf_konstanten.lsp`, `vf_standard.lsp`, `vf_etage.lsp` +und `vf_linienzug.lsp` — als Grundlage fuer die Frage, ob sich haeufig +gemeinsam durchgereichte Parameter zu einem Struct (in AutoLISP: eine +benannte Alist) buendeln lassen, um Funktionssignaturen mit 5+ Parametern +zu verkuerzen. Stand: 2026-09-01, rein analytisch, keine Codeaenderung. + +## Ausgangsbefund: globale Variablen + +Die globalen Variablen der drei Module sind **kein** Parameter-Problem: + +- `vf_konstanten.lsp` enthaelt ausschliesslich feste `*vfk-*`-Konstanten + (Winkel, Laengen, Toleranzen), die einmalig per `ssg-cfg-or`/Guard gesetzt + und direkt gelesen werden, nicht als Parameter durchgereicht. +- `vf_core.lsp`/`vf_standard.lsp`/`vf_etage.lsp` haben Init-Flags/Caches + (`*lib-initialized*`, `*ks-cache*`, `bogen-auf`/`bogen-ab`), die Typ-Registry + `*vf-typ-registry*` und wenige AS/ES-Massen-Offsets (`aus-dx` usw.). +- `vf_linienzug.lsp` haelt zusaetzlich Journal-/Replay-State + (`*vfl-journal*`, `*vfl-replay-queue*`), Wizard-Dialog-State + (`*vflw-pending*`, `*vflw-menu-*`) und neun parallele + `*vfl-acc-*`-Globals als "Ketten-Akkumulator" (siehe Abschnitt 4). + +Das eigentliche Problem liegt woanders: dieselben 4-8 thematisch +zusammengehoerigen **Werte werden wortwoertlich durch dutzende +Funktionssignaturen durchgereicht**, weil es dafuer noch keinen +gebuendelten Wert gibt. + +## Bereits vorhandene Struct-Vorbilder im Code + +Diese Muster existieren schon und sind das natuerliche Vorbild fuer eine +Erweiterung (AutoLISP-Konvention: Alist statt echtem Struct): + +- **KS-Frame** `(P xu yu zu)` — Punkt + 3 Achsvektoren als ein Wert + (`vf_core.lsp`, `frame` in `vf_linienzug.lsp`) +- **`prefill`-Alists** (string-keyed, `(cdr (assoc "key" prefill))`) zur + Dialogvorbelegung in `vfs-dialog-eingabe-basis`/`vfs-dialog-winkel-verteilung` +- **`*vf-basis-eingabe-felder*` + `vf-basis-eingabe-entpacken`** — eine + benannte, geordnete Feldliste, generisch per `foreach`/`nth` entpackt +- **`ergebnis-liste`**-Eintraege `(winkel L_GF L_VF gueltig)` aus + `vf-winkel-solve`/`berechne-alle-winkel` +- **`kette-rec`** in `vf_linienzug.lsp` — Positions-Accessor-Funktionen + (`vfl-kette-rec-ename` usw.) ueber eine flache Liste +- **`*vfl-glied-schema*`** — Schema-Tabelle (Label -> Felddefinitionen) als + struct-aehnliche Serialisierung pro Ketten-Glied + +## Funktionen mit den meisten Parametern + +| Funktion | Datei | Anzahl Parameter | +|---|---|---| +| `vf-block-erstellen` | vf_core.lsp | 18 | +| `vf-make-label` | vf_core.lsp | 13 | +| `vf-dialog-berechnen-einfuegen` | vf_core.lsp | 12 | +| `variofoerderer-einfuegen` | vf_standard.lsp | 10 | +| `etage-foerderanlage-einfuegen` | vf_etage.lsp | 10 | +| `vfs-standard-dialog-berechnen-einfuegen` | vf_standard.lsp | 10 | +| `vfe-etage-dialog-berechnen-einfuegen` | vf_etage.lsp | 10 | +| `vfl-block-erstellen` | vf_linienzug.lsp | 10 | +| `vfl-modus-abbruch-sichern` | vf_linienzug.lsp | 9 | +| `vf-winkel-solve` | vf_core.lsp | 8 | +| `vfs-mitte-teil`, `vfl-vf-einheit`, `vfl-vf-einheit-abschluss` | standard/linienzug | je 7 | +| `vfs-vf-koerper`, `vfl-insert-es-element`, `vfl3-es-fueller` | standard/linienzug | je 5-6 | +| die vier `insert-hz-*`-Funktionen | vf_etage.lsp | je 4-6 | + +## Struct-Kandidaten + +1. **`geometrie-basis`** `(deltaL deltaH richtung startpunkt hz seite + [dim geruest-einzelmodul geruest-typ motorseite])` — identisch in + vf_core, vf_standard, vf_etage UND vf_linienzug. Erweitert direkt das + bestehende `*vf-basis-eingabe-felder*`-Muster. Groesster Hebel, da er in + fast jeder Top-Funktion mit vorkommt. +2. **`ergebnis-geometrie`** `(best-winkel L_GF1 L_GF2 L_VF richtung)` — + entspricht der Form der `ergebnis-liste`-Eintraege aus `vf-winkel-solve`; + steckt in `vf-make-label`, `vf-block-erstellen`, `vfs-mitte-teil`, + `vfs-vf-koerper`, `variofoerderer-einfuegen`, `etage-foerderanlage-einfuegen`. +3. **`hoehen`** `(hoehe-von hoehe-bis deltaH)` — klein, wiederholt sich in + `vf-make-label`/`vf-block-erstellen`; evtl. eher in Kandidat 2 + integrieren als eigenstaendig fuehren. +4. **`geruest-optionen`** `(geruest-einzelmodul geruest-typ motorseite)` — + reist durch Dialog-/Edit-/Erstellen-Funktionen; Alternative: direkt in + `geometrie-basis` aufnehmen statt separat zu fuehren. +5. **`ketten-meta`** `(vfl-nummer anzahl-gf anzahl-vf hoehe-von hoehe-bis + delta-l-total as-seite es-seite)` — nur vf_linienzug, betrifft + `vfl-block-erstellen` (10) und `vfl-modus-abbruch-sichern` (9). +6. **Rotation-Paar** `(hz-winkel vert-winkel)` (optional mit vorgerechneten + sin/cos) — die vier `insert-hz-*`-Funktionen in vf_etage.lsp teilen + dieses Paar plus dupliziertes Grad->Bogenmass/sin/cos; ein + gemeinsamer Wert wuerde auch die Code-Duplikation beseitigen, nicht nur + die Parameterzahl. +7. **`*vfl-acc-*`-Akkumulator** — kein Parameter-, sondern ein + State-Problem: neun parallele globale Listen + (`*vfl-acc-lvf*`, `*vfl-acc-lgf*`, `*vfl-acc-gfwinkel*`, + `*vfl-acc-richtung*`, `*vfl-acc-winkel*`, `*vfl-acc-motorseite*`, + `*vfl-acc-gfbogen*`, `*vfl-acc-variokurve*`, `*vfl-acc-separator*`), die + konsequent zusammen gesetzt/gelesen werden. Ein Record statt neun losen + Globals waere robuster gegen Vertipper/vergessenes Zuruecksetzen. + +Mit Kandidat 1+2 allein liesse sich `vf-block-erstellen` von 18 auf +~6-7 Parameter bringen, `vf-make-label` von 13 auf ~5-6, und die gesamte +10-Parameter-Familie auf ~4-5. + +## Einschaetzung + +Es besteht spuerbares Potential, aber mit Vorsicht: `vf-dialog-berechnen-einfuegen` +haengt an der Typ-Registry (`*vf-typ-registry*`, `berechne-fn`/`einfuege-fn`) +und wird von Standard-, Etage- und indirekt Linienzug-Pfaden aufgerufen. Eine +Signaturaenderung dort zieht Aenderungen an allen drei registrierten +Funktionen sowie den Edit-/Rebuild-Pfaden (`vfe-edit-ent`, `vfl-edit-ent`, +Journal-Replay) nach sich. Empfohlene Reihenfolge bei einer Umsetzung: + +1. Risikoarme Blattfunktionen zuerst (Rotation-Paar in vf_etage, + `vf-make-label`). +2. `geometrie-basis`/`ergebnis-geometrie` als groessere, aber isolierbare + Cluster. +3. Die Registry-Signatur (`vf-dialog-berechnen-einfuegen`) und die + Edit-/Replay-Pfade erst zuletzt anfassen. + +Stand jetzt: reine Analyse, keine Umsetzung angestossen. diff --git a/tests/README.md b/tests/README.md index cf8bcc7..e9ee754 100644 --- a/tests/README.md +++ b/tests/README.md @@ -102,7 +102,7 @@ Die Datei `tests/alltests.json` ist die zentrale Registry aller Test-Module. { "name": "omniflo_strecke", "save": "dxf", "module": "OmniModulInsert" }, { "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" }, { "name": "mubea", "save": "dxf", "module": "KreiselInsert" }, - { "name": "hundm05", "save": "dxf", "module": "KreiselInsert", "disabled": true } + { "name": "hundm05", "save": "dxf", "module": "VarioFoerderer", "disabled": true } ] ``` @@ -160,7 +160,14 @@ tests/ test_foerderer.py # pytest-Validierung Foerderer (Status/Mathematik/CSV/Sivas) test_gefaellestrecke.py # pytest-Validierung Gefaellestrecke test_mubea.py # pytest-Validierung Mubea-Gesamtmodell - test_hundm05.py # pytest-Validierung HundM05-Abschnitt + test_hundm05.py # pytest-Validierung HundM05 (5 Linienzuege) + test_vf_journal_grammatik.py # pytest ohne CAD: Journal-Dekoder aus + # lib/vf_journal_export.py gegen handgebaute + # Journale (Linie-VF, gewinkelter Erstkoerper) + test_vf_headless_statisch.py # pytest ohne CAD: statischer Waechter, dass + # im VF-Bau-Pfad kein alert/get*/ssget neu + # auftaucht und der Headless-Riegel an allen + # vfl-in-*-Wrappern sitzt test_unit.lsp / .scr # LISP-Unit-Tests: C:TEST_UNIT (ueber --lisp) testdata/ kreisel_tests.json # Testfall-Definitionen fuer Kreisel diff --git a/tests/alltests.json b/tests/alltests.json index 8abaf0e..4b8e257 100644 --- a/tests/alltests.json +++ b/tests/alltests.json @@ -7,5 +7,5 @@ { "name": "omniflo_strecke", "save": "dxf", "module": "OmniModulInsert" }, { "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" }, { "name": "mubea", "save": "dxf", "module": "KreiselInsert" }, - { "name": "hundm05", "save": "dxf", "module": "KreiselInsert", "disabled": true } + { "name": "hundm05", "save": "dxf", "module": "VarioFoerderer", "disabled": true } ] diff --git a/tests/test_hundm05.lsp b/tests/test_hundm05.lsp index a8b197a..8cfaef9 100644 --- a/tests/test_hundm05.lsp +++ b/tests/test_hundm05.lsp @@ -1,55 +1,120 @@ ;; ============================================================ -;; test_hundm05.lsp - Integrationstest: baut den Kreisel-Abschnitt der -;; Anlage HundM aus der Fremdzeichnung ST500592_05.dxf nach. +;; test_hundm05.lsp - Integrationstest: baut die 5 VarioFoerderer-Linienzuege +;; der Anlage HundM (Kreisel mit fuenf daran haengenden Ketten) nach. ;; -;; Liest tests/testdata/hundm05.json (erzeugt von lib/dxf_scan_components.py -;; mit --fenster 216000 144000 228000 156000) und baut in EINER Zeichnung: -;; - Kreisel (Eintraege mit "id", OHNE "type") Block KREISEL_* -;; "function": "connect" -> kreisel-connect-script (Start-/ -;; Endpunkt, abstand+rotation werden intern berechnet) -;; "function": "insert" -> kreisel-insert-script -;; - Vario (test_id "VF_*") Block VF_* via variofoerderer-einfuegen -;; - Gefaelle (test_id "GF_*") Block GF_* via gefaellestrecke-einfuegen -;; - Gerade (test_id "GERADE_*") waagrechte Foerderstrecke via -;; insert-hz-scaled-block-by-ks (Staustrecke_SP_1000_mm) -;; - Omniflo (Eintraege mit "type") - NICHT gebaut, siehe hundm05:build-omniflo -;; - Sensoren (Eintraege mit "block") via _.INSERT +;; Datenquelle: tests/testdata/hundm05.json, erzeugt von +;; python lib/vf_journal_export.py data/polylines.dxf \ +;; tests/testdata/hundm05.json --csv results/HundM_export.csv +;; Das sind die EINGABE-JOURNALE der echten Ketten - ausgelesen aus der XDATA +;; (App SSG_VF_EDIT, Marker "linienzug") der VF_n-Bloecke in data/polylines.dxf, +;; also genau die Werte, mit denen die Ketten in BricsCAD gebaut wurden. +;; +;; JSON-Aufbau (FLACH, weil ssg-load-json zeilenweise liest - Details in +;; tests/testdata/object_data.md Abschnitt 8): +;; Kopf-Objekt (traegt "test_id"): test_id, anlagetyp, modus, +;; 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 +;; Ein Kopf-Objekt beginnt eine Kette, die folgenden Eingabe-Objekte gehoeren +;; dazu (hundm05:gruppiere). +;; +;; WARUM REPLAY UND KEINE EINGABE-MOCKS (Unterschied zu test_linienzug.lsp): +;; test_linienzug.lsp ersetzt getpoint/getstring/getint/getreal durch Mocks und +;; rechnet Punkte aus "dL"+"hz" selbst aus. Das geht nur, weil +;; linienzug_tests.json zu JEDEM Segment ein "hz" mitfuehrt. Ein echtes Journal +;; tut das nicht: vfl-in-abstand journalisiert die Fahrtrichtung NUR beim +;; allerersten Segment der Kette, jedes weitere erbt sie vom Vorgaenger. Ein +;; Mock kann diese geerbte Richtung nicht kennen - er wuerde einen Punkt in der +;; falschen Richtung liefern und die Projektion in vfl-neue-linie-messen ergaebe +;; eine falsche (bis auf 0 zusammenfallende) Laenge. Darum wird hier derselbe +;; Weg genommen, den die Produktion beim Editieren/Konvertieren nutzt: +;; vfl-journal-replay-start + vf-linienzug-modus (siehe vfl-konvertiere-ent in +;; Lisp/vf_linienzug.lsp). Die vfl-in-*-Wrapper ziehen ihre Werte dann direkt +;; aus der Replay-Queue - inklusive der Sonderregel fuer "hz". +;; +;; Voraussetzungen: +;; - SSG_LIB geladen (VarioFoerderer inkl. vf_linienzug, Gefaellestrecke, +;; ssg_core, ssg_dbg) +;; - Umgebungsvariable DXFMAKRO gesetzt ;; ;; Speichert (via SSG_RUN_ALL_TESTS bei "save":"dxf"): ;; tests/output/hundm05_tests.dxf ;; tests/output/hundm05_results.json (via hundm05:export-results) ;; -;; Unterschiede zu tests/test_mubea.lsp - warum eine eigene Datei und kein -;; gemeinsamer Treiber: -;; 1. Der Kreisel kommt hier als "connect" (zwei Punkte aus dem DXF), bei -;; Mubea als "insert" (Punkt + abstand + rotation von Hand). -;; 2. Die VarioFoerderer nennen ihren Startpunkt "startpunkt" (Liste, wie -;; object_data.md 3.1) statt x/y/z, und sie haben kein "L_GF1" - der -;; L_GF-Split ist hier "gleichmaessig" (Default nach object_data.md). -;; 3. Es gibt den Eintragstyp "gerade", den mubea.json nicht kennt. -;; 4. Eintraege mit "type" (Omniflo) muessen VOR "id" abgefragt werden, weil -;; sie beides tragen - bei Mubea gab es diesen Fall nicht. -;; test_mubea.lsp bleibt dadurch unangetastet und die bestehende Suite -;; unveraendert lauffaehig. -;; -;; Voraussetzungen: -;; - SSG_LIB geladen (KreiselInsert, VarioFoerderer, Gefaellestrecke, -;; SSG_LIB_Commands, ssg_core, vf_core) -;; - Umgebungsvariable DXFMAKRO gesetzt -;; ;; Aufruf in BricsCAD: ;; (load (strcat (getenv "DXFMAKRO") "/tests/test_hundm05.lsp")) -;; TEST_HUNDM05 +;; TEST_HUNDM05 ; in der aktuellen Dimension (ssg-ils-dim-aktuell) +;; TEST_HUNDM05_2D ; erzwungen 2D +;; TEST_HUNDM05_3D ; erzwungen 3D +;; +;; Debug-Datei (nur wenn eingeschaltet): (dbg-schalter-on "hundm05") +;; schreibt hundm05.dbg ins DXFM_LOG-Verzeichnis (Journal je Kette als JSON). ;; ============================================================ -;; --- Ergebnis eines Bau-Schritts als JSON-String erzeugen --- -(defun hundm05:result-json (test-id kind status block-name block-handle insert-point attribs / - json tag val first) +;; ============================================================ +;; Ergebnis-JSON +;; ============================================================ + +;; --- JSON-Hilfen fuer Diagnosefelder (Strings/String-Listen) --- +;; Die extra-Felder in hundm05:result-json werden ROH in das JSON gesetzt, +;; ein Text muss also selbst seine Anfuehrungszeichen mitbringen und die +;; JSON-Sonderzeichen maskieren. +(defun hundm05:json-escape (s / out i c) + (setq out "" i 1) + (while (<= i (strlen s)) + (setq c (substr s i 1)) + (setq out (cond ((= c "\"") (strcat out "\\\"")) + ((= c "\\") (strcat out "\\\\")) + ((= c "\n") (strcat out " ")) + (T (strcat out c)))) + (setq i (1+ i))) + out) + +(defun hundm05:json-str (s) + (if s (strcat "\"" (hundm05:json-escape s) "\"") "null")) + +(defun hundm05:json-liste (lst / s first) + (setq s "[" first T) + (foreach x lst + (if (not first) (setq s (strcat s ", "))) + (setq s (strcat s (hundm05:json-str x)) first nil)) + (strcat s "]")) + +;; --- Headless-Diagnose der Produktion lesen --- +;; vfl-headless-abbruch (Lisp/vf_linienzug.lsp) legt bei erschoepfter +;; Replay-Queue Art der Eingabe, Glied-Nummer und Eingabe-Nummer ab. Das ist +;; die eigentliche Desync-Auskunft - ohne sie weiss man nur, DASS Werte offen +;; blieben, nicht WO der Ablauf abgewichen ist. +(defun hundm05:headless-fehler-text ( / f) + (if (and (boundp '*vfl-headless-fehler*) *vfl-headless-fehler*) + (progn + (setq f *vfl-headless-fehler*) + (strcat (cdr (assoc "was" f)) " @ " (cdr (assoc "ort" f)))) + nil)) + +;; Zaehler der trotz allem erreichten Live-Eingaben (muss 0 bleiben). +(defun hundm05:prompts () + (if (and (boundp '*hundm05-prompts*) *hundm05-prompts*) *hundm05-prompts* 0)) + + +;; "dimension" haelt fest, in welcher Dimension (2D/3D) gebaut wurde. Das ist +;; KEINE Eigenschaft der Eingabedaten: hundm05.json beschreibt die Ketten +;; dimensionsfrei. Womit unsere Makros bauen, entscheidet ssg-ils-dim-aktuell - +;; unsere Bibliothek hat von jedem Block eine _2D- und eine _3D-Variante. +;; extra: Alist zusaetzlicher Felder ((name . wert-string)), wird als +;; JSON-Zahlen/Strings so uebernommen, wie sie uebergeben werden. +(defun hundm05:result-json (test-id kind status block-name block-handle + insert-point attribs extra / + json tag val first) (setq json (strcat " {\n" " \"test_id\": \"" test-id "\",\n" " \"kind\": \"" kind "\",\n" " \"status\": \"" status "\",\n" + " \"dimension\": \"" + (if (and (boundp '*hundm05-dimension*) *hundm05-dimension*) + *hundm05-dimension* "?") "\",\n" " \"block_name\": \"" (if block-name block-name "") "\",\n" " \"block_handle\": \"" (if block-handle block-handle "") "\",\n" " \"insert_point\": [" @@ -58,8 +123,10 @@ (rtos (cadr insert-point) 2 1) ", " (rtos (if (caddr insert-point) (caddr insert-point) 0.0) 2 1)) "0.0, 0.0, 0.0") - "],\n" - " \"actual_attributes\": {")) + "],\n")) + (foreach f extra + (setq json (strcat json " \"" (car f) "\": " (cdr f) ",\n"))) + (setq json (strcat json " \"actual_attributes\": {")) (setq first T) (foreach att attribs (setq tag (car att) val (cdr att)) @@ -72,413 +139,384 @@ ;; --- Aus einer Block-ENAME ein Ergebnis-JSON erzeugen --- -(defun hundm05:ent-json (test-id kind ent / ed) +;; status: "executed" bei sauberem Lauf; ein abweichender Status (z.B. bei +;; einem Queue-Desync) wird durchgereicht, die Block-Angaben bleiben dabei +;; erhalten - sonst waere der betroffene Block im Ergebnis nicht auffindbar. +(defun hundm05:ent-json-status (test-id kind status ent extra / ed) (if ent (progn (setq ed (entget ent)) - (hundm05:result-json test-id kind "executed" + (hundm05:result-json test-id kind status (cdr (assoc 2 ed)) (cdr (assoc 5 ed)) (cdr (assoc 10 ed)) - (ssg-attrib-read ent))) - (hundm05:result-json test-id kind "failed" nil nil nil nil)) + (ssg-attrib-read ent) extra)) + (hundm05:result-json test-id kind "failed" nil nil nil nil extra)) +) + +(defun hundm05:ent-json (test-id kind ent extra) + (hundm05:ent-json-status test-id kind "executed" ent extra) ) -;; --- Letzten INSERT mit Blocknamen-Praefix (rueckwaerts) suchen --- -(defun hundm05:last-insert-prefix (pref / ent found ed bn) - (setq ent (entlast)) - (while (and ent (null found)) - (setq ed (entget ent) bn (cdr (assoc 2 ed))) - (if (and (= (cdr (assoc 0 ed)) "INSERT") bn - (>= (strlen bn) (strlen pref)) - (= (substr bn 1 (strlen pref)) pref)) - (setq found ent)) - (setq ent (entprev ent))) - found +;; --- Letzten INSERT mit Blocknamen-Praefix suchen --- +;; AutoLISP kennt KEIN entprev (nur entnext und entlast) - rueckwaerts durch +;; die Zeichnung laufen geht also nicht. Gesucht wird darum VORWAERTS, wobei +;; der letzte Treffer gewinnt. Ein blosses (entlast) genuegt nicht: nach dem +;; Bau koennen weitere Objekte entstehen (z.B. Beschriftungstexte), entlast +;; ist dann nicht der gesuchte Block - genau daran scheiterte der Testlauf. +;; ab-ent = Stand der Zeichnung VOR dem Bau. Damit prueft die Suche nur die +;; neu entstandenen Objekte: schnell, und sie kann nicht versehentlich den +;; Block eines frueheren Baus liefern (siehe Kommentar bei -ab). +(defun hundm05:insert-prefix-p (ent pref / ed bn) + (setq ed (entget ent) bn (cdr (assoc 2 ed))) + (and ed (= (cdr (assoc 0 ed)) "INSERT") bn + (>= (strlen bn) (strlen pref)) + (= (substr bn 1 (strlen pref)) pref))) + +;; Vorwaertslauf ab ab-ent (nil = ganze Zeichnung); der LETZTE Treffer +;; gewinnt. Der entget-Test faengt ein inzwischen ungueltiges ab-ent ab +;; (entget liefert dann nil, entnext wuerde einen Fehler werfen). +(defun hundm05:suche-vorwaerts (pref ab-ent / ent found) + (setq ent (if (and ab-ent (entget ab-ent)) (entnext ab-ent) (entnext))) + (while ent + (if (hundm05:insert-prefix-p ent pref) (setq found ent)) + (setq ent (entnext ent))) + found) + +(defun hundm05:last-insert-prefix-ab (pref ab-ent / ent) + (if ab-ent + ;; MIT Marker nur die neu entstandenen Objekte pruefen - und BEWUSST + ;; ohne entlast-Kurzschluss: hat dieser Bau keinen Block erzeugt, waere + ;; entlast der Block eines FRUEHEREN Baus und das Ergebnis wuerde der + ;; falschen Kette zugeordnet. Kein Treffer = kein Block, das ist die + ;; richtige Antwort. + (hundm05:suche-vorwaerts pref ab-ent) + (progn + ;; Ohne Marker: erst entlast (der haeufige Fall, das gerade + ;; eingefuegte Objekt), sonst die ganze Zeichnung. + (setq ent (entlast)) + (if (and ent (hundm05:insert-prefix-p ent pref)) + ent + (hundm05:suche-vorwaerts pref nil))))) + +(defun hundm05:last-insert-prefix (pref) + (hundm05:last-insert-prefix-ab pref nil)) + + +;; ============================================================ +;; JSON-Eintraege -> Ketten +;; ============================================================ + +;; Das flache JSON-Array in Ketten gruppieren: ein Objekt mit "test_id" ist +;; ein Kopf und beginnt eine neue Kette, die folgenden Objekte mit "typ" sind +;; ihre Eingaben (Reihenfolge = Frage-Reihenfolge, bleibt erhalten). +;; Rueckgabe: Liste von (kopf eingaben-liste). +(defun hundm05:gruppiere (daten / ketten kopf eingaben) + (setq ketten '() kopf nil eingaben '()) + (foreach obj daten + (cond + ((ssg-val obj "test_id") + (if kopf + (setq ketten (cons (list kopf (reverse eingaben)) ketten))) + (setq kopf obj eingaben '())) + ((ssg-val obj "typ") + (if kopf (setq eingaben (cons obj eingaben)))) + ) + ) + (if kopf (setq ketten (cons (list kopf (reverse eingaben)) ketten))) + (reverse ketten) ) -;; --- Zahl aus JSON lesen, mit Default statt Fehler bei fehlendem Feld --- -;; (float nil) bricht in AutoLISP ab. hundm05.json laesst - anders als -;; mubea.json - bewusst Felder weg (z.B. "winkel" und "L_GF1" beim Vario, damit -;; der Solver selbst waehlt), darum hier grundsaetzlich abgesichert lesen. -(defun hundm05:num (e key default / v) - (setq v (ssg-val e key)) - (if (numberp v) (float v) default) +;; --- Wert als String lesen (auch wenn er als Zahl im JSON steht) --- +;; Die Menue-Antworten sind Strings ("1"/"2"/"90"/"30"). Steht in einer von +;; Hand gepflegten Datei versehentlich 1 statt "1", wuerde ein Zahlenwert in +;; die Replay-Queue wandern und der Vergleich (= antwort "1") schlagen fehl - +;; darum hier immer nach String wandeln. +(defun hundm05:als-string (v) + (cond + ((null v) "") + ((= (type v) 'STR) v) + ((= (type v) 'INT) (itoa v)) + ((= (type v) 'REAL) (rtos v 2 6)) + (T "") + ) ) -;; --- Punkt-Liste aus JSON lesen ("startpunkt"/"start_mm": [x, y, z]) --- -;; Liefert NIEMALS einen Ersatzpunkt. Ein Ursprungs-Fallback (0 0 0) waere hier -;; fatal: er baut die Anlage sichtbar, aber komplett an der falschen Stelle - -;; genau das passiert, wenn das Array in der JSON ueber mehrere Zeilen -;; umgebrochen ist (siehe hundm05:startpunkt). -(defun hundm05:pt (e key / v) - (setq v (ssg-val e key)) - (if (and v (listp v) (>= (length v) 3)) (mapcar 'float v) nil) +;; --- Einen Eingabe-Eintrag in Journal-Eintraege uebersetzen --- +;; Journal-Format (vfl-entry->string in Lisp/vf_linienzug.lsp): +;; ("PT" x y z) / ("REAL" . r) / ("DL" . r) / ("INT" . i) / ("STR" . s) / +;; ("STEP" . label) +;; Ein "point_rel" wird zu EINEM oder ZWEI Eintraegen: immer die Laenge (DL), +;; zusaetzlich die Fahrtrichtung (REAL) nur dort, wo sie im Journal steht +;; (allererstes Segment der Kette, siehe Kopfkommentar). +;; Rueckgabe: Liste von Journal-Eintraegen (kann leer sein). +(defun hundm05:eintrag->journal (e / typ w hz) + (setq typ (ssg-val e "typ")) + (cond + ((= typ "point_abs") + (setq w (ssg-val e "wert")) + (if (and w (listp w) (>= (length w) 3)) + (list (cons "PT" (mapcar 'float w))) + nil)) + ((= typ "point_rel") + (setq w (ssg-val e "dL") hz (ssg-val e "hz")) + (cond + ((not (numberp w)) nil) + ((numberp hz) (list (cons "DL" (float w)) (cons "REAL" (float hz)))) + (T (list (cons "DL" (float w)))))) + ((= typ "real") + (setq w (ssg-val e "wert")) + (if (numberp w) (list (cons "REAL" (float w))) nil)) + ((= typ "int") + (setq w (ssg-val e "wert")) + (if (numberp w) (list (cons "INT" (fix w))) nil)) + ((= typ "string") + (list (cons "STR" (hundm05:als-string (ssg-val e "wert"))))) + ((= typ "step") + (list (cons "STEP" (hundm05:als-string (ssg-val e "wert"))))) + (T nil) + ) ) -;; --- Startpunkt eines Eintrags lesen: Array-Feld, sonst flache x/y/z --- -;; Rueckgabe: Punkt oder nil. Bei nil MUSS der Aufrufer den Eintrag als -;; "failed" melden und darf nichts bauen. +;; --- Alle Eingaben einer Kette in ein Vorwaerts-Journal uebersetzen --- +;; Vorwaerts = Bau-Reihenfolge, genau so erwartet es vfl-journal-replay-start. +(defun hundm05:journal-bauen (eingaben / out) + (setq out '()) + (foreach e eingaben + (foreach j (hundm05:eintrag->journal e) + (setq out (cons j out)))) + (reverse out) +) + + +;; --- Nicht verbrauchte Werte in der Replay-Queue zaehlen --- +;; STEP-Marker zaehlen nicht mit: vfl-replay-pop ueberspringt sie, sie sind +;; also kein offener Wert. Bleibt hier etwas uebrig, ist der Bau-Ablauf vom +;; aufgezeichneten abgewichen (Desync) - das MUSS auffallen. +(defun hundm05:queue-rest ( / n) + (setq n 0) + (if (and (boundp '*vfl-replay-queue*) *vfl-replay-queue*) + (foreach e *vfl-replay-queue* + (if (/= (car e) "STEP") (setq n (1+ n))))) + n +) + + +;; ============================================================ +;; Eine Kette bauen +;; ============================================================ + +;; Spielt das Journal einer Kette ab. Ablauf wie vfl-konvertiere-ent +;; (Lisp/vf_linienzug.lsp): Queue setzen, vf-linienzug-modus laufen lassen, +;; danach den entstandenen VF_n-Block einsammeln. ;; -;; Haeufigste Ursache fuer nil: ssg-parse-json-array (ssg_core.lsp) liest die -;; JSON ZEILENWEISE, und ssg-cfg-parse-array braucht das ganze Array in einer -;; Zeile. Ein von json.dump(indent=2) umgebrochenes Array -;; "start_mm": [ -;; 219540.85, -;; ... -;; ] -;; liefert nil. Die Ausgabe von lib/dxf_scan_components.py zieht Zahlen-Arrays -;; darum wieder auf eine Zeile (arrays_einzeilig). -(defun hundm05:startpunkt (e feld / p) - (setq p (hundm05:pt e feld)) - ;; Ersatzweise flache x/y/z-Felder zulassen (Schema wie mubea.json) - (if (and (null p) (numberp (ssg-val e "x"))) - (setq p (list (hundm05:num e "x" 0.0) (hundm05:num e "y" 0.0) - (hundm05:num e "z" 0.0)))) - p -) +;; vl-catch-all-apply ist hier BEWUSST gesetzt (anders als im interaktiven +;; Editier-Pfad vfl-edit-ent): bricht eine Kette ab, muss die Testschleife +;; ueber die restlichen Ketten weiterlaufen. Der Preis ist derselbe wie beim +;; Batch-Konverter - der *error*-Handler in vf-linienzug-modus (der die +;; Teil-Geometrie zu einem Block wickelt) kommt dann nicht zum Zug und die +;; Teilgeometrie bleibt lose in der Zeichnung liegen. Der Testfall wird darum +;; klar als "failed" samt Fehlertext gemeldet. +;; *error* wird um den Aufruf herum gesichert: nach einem abgefangenen Abbruch +;; steht dort noch der Handler-Lambda von vf-linienzug-modus (es kommt nicht +;; mehr zu dessen (setq *error* old-error)) - ohne Restore wuerde die naechste +;; Kette auf einem Handler mit toten dynamischen Bindungen aufsetzen. +(defun hundm05:build-linienzug (kopf eingaben / tid journal anz offen err ent + alt-error extra hl-fehler meldungen vor-ent) + (setq tid (ssg-val kopf "test_id")) + (setq journal (hundm05:journal-bauen eingaben)) + (setq anz (length journal)) + (princ (strcat "\n [LINIENZUG] " tid " -> " (itoa (length eingaben)) + " Eingaben, " (itoa anz) " Journal-Eintraege")) + (if (ssg-val kopf "beschreibung") + (princ (strcat "\n " (ssg-val kopf "beschreibung")))) + (if (and (boundp '*hundm05-dbg*) *hundm05-dbg*) + (progn + (dbgmsg (strcat "KETTE " tid " JOURNAL " (vfl-journal->json journal))) + (dbgflush))) + (cond + ;; Ohne Journal (oder ohne Startpunkt an erster Stelle) nichts bauen: ein + ;; Replay ohne PT wuerde den Startpunkt interaktiv erfragen und die ganze + ;; Kette an der falschen Stelle aufbauen. + ((null journal) + (princ (strcat "\n FEHLER: " tid " - keine Eingaben lesbar." + " Stehen die Zahlen-Arrays in EINER Zeile?" + " (ssg-cfg-parse-array kann keine umgebrochenen lesen)")) + (hundm05:result-json tid "linienzug" "failed" nil nil nil nil + (list (cons "eingaben_gesamt" (itoa anz)) + (cons "eingaben_offen" (itoa anz))))) -;; --- Meldung fuer einen nicht lesbaren Startpunkt --- -(defun hundm05:kein-startpunkt (tid feld) - (princ (strcat "\n FEHLER: " tid " - Feld \"" feld "\" nicht lesbar." - " Steht das Array in EINER Zeile? ssg-cfg-parse-array kann" - " keine umgebrochenen Arrays lesen. Nichts gebaut.")) + ((/= (car (car journal)) "PT") + (princ (strcat "\n FEHLER: " tid " - erster Eintrag ist kein" + " Startpunkt (point_abs), nichts gebaut.")) + (hundm05:result-json tid "linienzug" "failed" nil nil nil nil + (list (cons "eingaben_gesamt" (itoa anz)) + (cons "eingaben_offen" (itoa anz))))) + + (T + ;; Stand der Zeichnung VOR dem Bau: der fertige VF_-Block wird danach + ;; ab hier gesucht (siehe hundm05:last-insert-prefix-ab). + (setq vor-ent (entlast)) + (setq alt-error *error*) + (vfl-journal-reset) + (vfl-journal-replay-start journal) + (setq err (vl-catch-all-apply 'vf-linienzug-modus '())) + (setq offen (hundm05:queue-rest)) + ;; Diagnose VOR dem Reset sichern: vfl-journal-reset loescht + ;; *vfl-headless-fehler* und *vfl-meldungen* mit (beide gehoeren zum + ;; einzelnen Lauf, nicht zur Sitzung). + (setq hl-fehler (hundm05:headless-fehler-text)) + (setq meldungen (if (boundp '*vfl-meldungen*) *vfl-meldungen* nil)) + (setq *error* alt-error) + (vfl-journal-reset) + (setq extra (list (cons "eingaben_gesamt" (itoa anz)) + (cons "eingaben_offen" (itoa offen)) + (cons "prompts" (itoa (hundm05:prompts))) + (cons "headless_fehler" (hundm05:json-str hl-fehler)) + (cons "meldungen" (hundm05:json-liste meldungen)))) + (cond + ;; Der Headless-Riegel der Produktion hat gegriffen: die Journal-Daten + ;; passen nicht zum tatsaechlichen Bau-Ablauf. Praeziser als "failed", + ;; weil hl-fehler Glied UND Eingabe-Nummer nennt. + (hl-fehler + (princ (strcat "\n DESYNC: " tid " - " hl-fehler)) + (hundm05:ent-json-status tid "linienzug" "desync" + (hundm05:last-insert-prefix-ab "VF_" vor-ent) extra)) + ((vl-catch-all-error-p err) + (princ (strcat "\n FEHLER: " tid " abgebrochen -> " + (vl-catch-all-error-message err))) + (hundm05:result-json tid "linienzug" "failed" nil nil nil nil extra)) + ;; Kette gebaut, aber der Ablauf hat nicht alle aufgezeichneten Werte + ;; abgerufen: die Geometrie kann nicht der Vorlage entsprechen. + ((> offen 0) + (princ (strcat "\n FEHLER: " tid " - " (itoa offen) + " Journal-Werte NICHT verbraucht (Ablauf weicht" + " von der Aufzeichnung ab).")) + (hundm05:ent-json-status tid "linienzug" "desync" + (hundm05:last-insert-prefix-ab "VF_" vor-ent) extra)) + (T + (setq ent (hundm05:last-insert-prefix-ab "VF_" vor-ent)) + (if ent + (princ (strcat " -> Block " (cdr (assoc 2 (entget ent))))) + (princ "\n FEHLER: kein VF_-Block gefunden")) + (hundm05:ent-json tid "linienzug" ent extra))))) ) ;; ============================================================ -;; Bau-Funktionen je Elementtyp +;; Headless-Absicherung: Dialoge aus, leere Queue bricht ab ;; ============================================================ +;; Die Journale sind vollstaendig, es darf also keine Live-Eingabe geben. +;; Falls doch (Desync), wuerde ein getpoint/getstring den Testlauf blockieren +;; und ein alert-Fenster ihn haengen lassen. Darum werden die Eingabe- +;; Funktionen fuer die Dauer des Tests durch Abbruch-Stubs ersetzt (liefern +;; nil = Abbruch, was vf-linienzug-modus sauber beendet) und alert auf die +;; Konsole umgeleitet. Alles mit Save/Restore, siehe hundm05:stubs-aus. +;; Die Stubs haben feste 1- bzw. 2-Arity (AutoLISP kennt keine optionalen +;; Parameter) - genau wie die Mocks in test_linienzug.lsp. Ruft der Bau-Ablauf +;; eine der Funktionen mit anderer Argumentzahl auf, gibt es einen +;; Argumentfehler statt einer Warnung; beides landet im vl-catch-all-apply um +;; vf-linienzug-modus und meldet die Kette als "failed". Wichtig ist nur, dass +;; kein Prompt und kein Dialog den Lauf blockiert. +(defun hundm05:stub-warnung (was) + (setq *hundm05-prompts* (1+ (hundm05:prompts))) + (princ (strcat "\n WARNUNG: unerwartete Live-Eingabe (" was + ") - Replay-Queue leer, Kette wird abgebrochen.")) + nil +) +(defun hundm05:stub-getpoint (a b) (hundm05:stub-warnung "getpoint")) +(defun hundm05:stub-getstring (a) (hundm05:stub-warnung "getstring")) +(defun hundm05:stub-getint (a) (hundm05:stub-warnung "getint")) +(defun hundm05:stub-getreal (a) (hundm05:stub-warnung "getreal")) +(defun hundm05:stub-alert (msg) + (princ (strcat "\n ALERT (unterdrueckt): " (if msg msg ""))) + (princ) +) -;; --- Kreisel: "connect" (Start-/Endpunkt) oder "insert" (Punkt+abstand) --- -;; Der Scanner liefert fuer AN8/SP8-Paare "connect": beide Stationen stehen im -;; DXF, abstand und rotation berechnet kreisel-connect-script daraus selbst -;; (abstand = Distanz - *kreisel-durchmesser*). "insert" bleibt fuer von Hand -;; gepflegte Eintraege moeglich (Schema wie mubea.json). -(defun hundm05:build-kreisel (e / id fn typ hoehe ptStart ptEnd x y z abstand rotation ent) - (setq id (ssg-val e "id") - fn (ssg-val e "function") - typ (ssg-val e "typ") - hoehe (ssg-val e "hoehe")) - (if (null typ) (setq typ "STANDARD")) - (if (numberp hoehe) (setq hoehe (float hoehe)) (setq hoehe nil)) +(defun hundm05:stubs-an ( / ) + (setq *hundm05-alt-getpoint* vfl-getpoint + *hundm05-alt-getstring* getstring + *hundm05-alt-getint* getint + *hundm05-alt-getreal* getreal + *hundm05-alt-alert* alert) + (setq vfl-getpoint hundm05:stub-getpoint + getstring hundm05:stub-getstring + getint hundm05:stub-getint + getreal hundm05:stub-getreal + alert hundm05:stub-alert) + ;; Wizard-Dialoge aus (waehrend eines Replays greifen sie ohnehin nicht, + ;; siehe vfl-wizard-aktiv - aber nach einem Desync wuerde ein Dialog + ;; aufgehen und warten). + (setq *hundm05-alt-wizard* (if (boundp '*vfl-wizard-mode*) *vfl-wizard-mode*)) + (setq *vfl-wizard-mode* nil) + (if (car (atoms-family 1 '("SSG-GUI-AUS"))) (ssg-gui-aus)) + ;; Headless-Riegel der Produktion einschalten: eine erschoepfte Replay-Queue + ;; ist dann ein harter, lokalisierter Abbruch statt eines stillen Rueckfalls + ;; auf Live-Eingabe (siehe vfl-headless-abbruch). Die Stubs oben bleiben als + ;; zweites Netz - sie fangen die Prompts ausserhalb der vfl-in-*-Wrapper + ;; (Modus 3, Nebenpfade) und zaehlen sie. + (setq *hundm05-alt-headless* (if (boundp '*vfl-headless*) *vfl-headless*)) + (setq *vfl-headless* T) + (setq *hundm05-prompts* 0) + (princ) +) - (if (= fn "connect") - (progn - (setq ptStart (list (hundm05:num e "start_x" 0.0) - (hundm05:num e "start_y" 0.0) - (hundm05:num e "start_z" 0.0))) - (setq ptEnd (list (hundm05:num e "end_x" 0.0) - (hundm05:num e "end_y" 0.0) - (hundm05:num e "end_z" 0.0))) - (princ (strcat "\n [KREISEL] " id " connect (" - (rtos (car ptStart) 2 0) "," (rtos (cadr ptStart) 2 0) ") -> (" - (rtos (car ptEnd) 2 0) "," (rtos (cadr ptEnd) 2 0) ")" - " H=" (if hoehe (rtos hoehe 2 0) "-"))) - (setq ent (kreisel-connect-script ptStart ptEnd typ hoehe))) - (progn - (setq x (hundm05:num e "x" 0.0) - y (hundm05:num e "y" 0.0) - z (hundm05:num e "z" 0.0) - abstand (hundm05:num e "abstand" *kreisel-default-laenge*) - rotation (hundm05:num e "rotation" 0.0)) - (princ (strcat "\n [KREISEL] " id " insert (" (rtos x 2 0) "," (rtos y 2 0) - "," (rtos z 2 0) ") A=" (rtos abstand 2 0) - " R=" (rtos rotation 2 1))) - (setq ent (kreisel-insert-script (list x y z) abstand rotation typ - (if hoehe hoehe z))))) - (hundm05:ent-json id "kreisel" ent) +(defun hundm05:stubs-aus ( / ) + (setq vfl-getpoint *hundm05-alt-getpoint* + getstring *hundm05-alt-getstring* + getint *hundm05-alt-getint* + getreal *hundm05-alt-getreal* + alert *hundm05-alt-alert*) + (setq *vfl-wizard-mode* *hundm05-alt-wizard*) + (setq *vfl-headless* *hundm05-alt-headless*) + (if (car (atoms-family 1 '("SSG-GUI-AN"))) (ssg-gui-an)) + (princ) ) -;; --- Vario (VF_*) --- -;; Wie mubea:build-vario-one, aber ohne "winkel"-Zwang und ohne L_GF1-Ziel: -;; hundm05.json gibt nur deltaL/deltaH/richtung/seite/hz vor, den Bogenwinkel -;; waehlt berechne-alle-winkel selbst (kleinster gueltiger Kandidat) und der -;; L_GF-Split ist "gleichmaessig" (beide Haelften L_GF/2, Default nach -;; object_data.md 3.1). Ist "L_GF1" doch gesetzt, wird es als Vorlauf-Ziel -;; verwendet - dann verhaelt sich der Treiber wie der Mubea-Treiber. -(defun hundm05:build-vario (e / tid typ richtung deltaL deltaH seite hz startpunkt - gf1-ziel ergebnis best-winkel L_GF L_VF gf1 gf2 - hoehe-von hoehe-bis lastEnt ent vf-nummer) - (setq tid (ssg-val e "test_id") - typ (ssg-val e "typ") - richtung (ssg-val e "richtung") - deltaL (hundm05:num e "deltaL" 0.0) - deltaH (hundm05:num e "deltaH" 0.0) - seite (ssg-val e "seite") - hz (hundm05:num e "hz" 0.0) - gf1-ziel (ssg-val e "L_GF1")) - (if (or (null typ) (= typ "")) (setq typ "standard")) - (if (or (null seite) (= seite "")) (setq seite "rechts")) - (if (or (null richtung) (= richtung "")) (setq richtung "Auf")) - (setq startpunkt (hundm05:startpunkt e "startpunkt")) - (if (null startpunkt) (hundm05:kein-startpunkt tid "startpunkt")) - - (princ (strcat "\n [VARIO] " tid " (" typ ") -> deltaL=" (rtos deltaL 2 0) - " deltaH=" (rtos deltaH 2 0) " " richtung - " hz=" (rtos hz 2 0))) - - (setq ergebnis - (if (= typ "etage") - (berechne-etage deltaL deltaH richtung seite) - (berechne-alle-winkel deltaL deltaH richtung nil))) - (setq best-winkel (car ergebnis) L_GF (cadr ergebnis) L_VF (caddr ergebnis)) - - ;; startpunkt MUSS mit in die Bedingung: ohne ihn wuerde am Ursprung gebaut. - (if (and startpunkt best-winkel L_GF L_VF (> L_GF 0) (> L_VF 0)) - (progn - ;; L_GF-Split: mit L_GF1-Vorgabe wie bei Mubea (Vorlauf-Ziel), sonst - ;; gleichmaessig auf Vor- und Nachlauf verteilt. - (if (numberp gf1-ziel) - (setq gf1 (min (float gf1-ziel) L_GF)) - (setq gf1 (/ L_GF 2.0))) - (setq gf2 (max 0.0 (- L_GF gf1))) - (princ (strcat " -> Winkel=" (itoa best-winkel) " L_GF=" (rtos L_GF 2 1) - " L_VF=" (rtos L_VF 2 1) " L_GF1=" (rtos gf1 2 1) - " L_GF2=" (rtos gf2 2 1))) - - ;; Absolute Hoehen eintragen. hoehe-von/hoehe-bis gehen NUR in Label und - ;; Attribute (MONTAGEHOEHE_m, HOEHE_VON_mm - vf_core.lsp), die Geometrie - ;; kommt aus startpunkt. test_mubea.lsp uebergibt hier 0 und traegt damit - ;; RELATIVE Hoehen ein; fuer diese Zeichnung stehen die echten Z-Werte im - ;; DXF, also werden sie auch verwendet - sonst exportiert EXPORTCSV eine - ;; falsche Montagehoehe (bei VF_HundM05_11 waeren es 2.914 m statt - ;; -1.028 m). - (setq hoehe-von (caddr startpunkt)) - (setq hoehe-bis (+ hoehe-von (if (= richtung "Auf") deltaH (- deltaH)))) - (setq lastEnt (vf-lastent-ohne-attribute)) - (if (= typ "etage") - (etage-foerderanlage-einfuegen deltaL deltaH richtung best-winkel gf1 gf2 L_VF startpunkt seite hz) - (variofoerderer-einfuegen deltaL deltaH richtung best-winkel gf1 gf2 L_VF startpunkt seite hz)) - ;; Blocknummer wie in der Produktion ueber vf-next-number holen (NICHT - ;; fest auf 1) - sonst trifft das _.BLOCK des zweiten Foerderers auf einen - ;; schon vorhandenen Namen, BricsCAD fragt "Neu definieren?" und - ;; verschluckt die feste Antwortfolge (siehe test_mubea.lsp). - (setq vf-nummer (vf-next-number)) - (setq ent (vf-block-erstellen typ seite vf-nummer 0 hoehe-von hoehe-bis - deltaH deltaL L_VF gf1 gf2 richtung best-winkel - startpunkt lastEnt hz "0" "Schoenenberger Geruest")) - (if (or (null ent) (/= (type ent) 'ENAME)) - (setq ent (hundm05:last-insert-prefix "VF_"))) - ;; SSG_VF_EDIT-XDATA schreiben, sonst verweigert VARIOFOERDERER_EDIT beim - ;; Doppelklick die Bearbeitung (vfs-edit-keine-xdata). - (if (and ent (= (type ent) 'ENAME) (member typ '("standard" "etage"))) - (vf-edit-xdata-schreiben ent typ hz)) - (hundm05:ent-json tid "vario" ent)) - (progn - ;; Ursache benennen, nicht pauschal "nicht loesbar" melden: ein fehlender - ;; Startpunkt hat nichts mit dem Winkel-Solver zu tun, und eine falsche - ;; Diagnose schickt die Fehlersuche in die falsche Richtung. - (if (null startpunkt) - (princ (strcat "\n FEHLER: " tid " nicht gebaut - Startpunkt fehlt" - " (siehe Meldung oben).")) - (princ (strcat "\n FEHLER: " tid " deltaL=" (rtos deltaL 2 0) - " deltaH=" (rtos deltaH 2 0) " nicht loesbar" - " (kein gueltiges L_GF/L_VF)."))) - (hundm05:result-json tid "vario" "failed" nil nil startpunkt nil))) +;; ============================================================ +;; Alle Ketten bauen +;; ============================================================ +;; Eigene Funktion, damit der Aufrufer sie in vl-catch-all-apply klammern und +;; die Eingabe-Stubs GARANTIERT zuruecknehmen kann (ein hier entkommender +;; Fehler wuerde sonst getstring/getpoint/alert der laufenden BricsCAD-Sitzung +;; ersetzt zuruecklassen). +;; Rueckgabe: Ergebnis-Liste in Bau-Reihenfolge. +(defun hundm05:ketten-bauen (ketten / kette res out tid fehlertext) + (setq out '()) + (foreach kette ketten + ;; Dimensions-Override je Kette neu setzen: der Abbruch-Handler in + ;; vf-linienzug-modus setzt *ssg-ils-dim* zurueck, ein Abbruch wuerde die + ;; folgenden Ketten sonst in der falschen Dimension bauen. + (if (and (boundp '*hundm05-dim-override*) *hundm05-dim-override*) + (setq *ssg-ils-dim* *hundm05-dim-override*)) + ;; Je Kette gefangen: ein Fehler NACH dem Bau (z.B. beim Auswerten des + ;; Ergebnisses) darf nicht die restlichen Ketten mitnehmen - sonst steht + ;; am Ende "0 OK, 0 Fehler" und man sieht nicht einmal, welche Kette + ;; gebaut wurde. Der Bau selbst ist in hundm05:build-linienzug bereits + ;; separat gefangen; dieser Riegel hier deckt alles danach ab. + (setq tid (ssg-val (car kette) "test_id")) + (setq res (vl-catch-all-apply 'hundm05:build-linienzug + (list (car kette) (cadr kette)))) + (if (vl-catch-all-error-p res) + (progn + (setq fehlertext (vl-catch-all-error-message res)) + (princ (strcat "\n FEHLER: " tid " - Ausnahme: " fehlertext)) + (setq res (hundm05:result-json tid "linienzug" "failed" + nil nil nil nil + (list (cons "fehler_text" + (hundm05:json-str fehlertext))))))) + (if res (setq out (cons res out)))) + (reverse out) ) -;; --- Gefaellestrecke: Init-Konflikt umgehen ----------------------------- -;; gefaellestrecke-einfuegen prueft "(if (not *lib-initialized*) -;; (gf-init-bibliothek))" - dasselbe Flag, das init-bibliothek (Vario) fuer -;; SEINE eigenen aus-dx/ein-dx setzt. Wird zuerst ein Vario gebaut, kaeme -;; gf-init-bibliothek nie mehr zum Zug und aus-dx/ein-dx blieben die -;; Vario-Werte. Darum einmal je Lauf erzwingen (wie in test_mubea.lsp). -(defun hundm05:ensure-gf-init ( / ) - (if (not *hundm05-gf-init-done*) - (progn - (setq *lib-initialized* nil) - (gf-init-bibliothek) - (setq *lib-initialized* t) - (setq *hundm05-gf-init-done* T))) -) - - -;; --- Eine Gefaellestrecke bauen (Modus 1) --- -;; Aufbau ausschliesslich aus Anfangspunkt und horizontaler Gesamtlaenge -;; deltaL - KEIN Endpunkt (wie Produktion/gefaellestrecke.dcl Modus 1, siehe -;; object_data.md 2.1). L_stau aus deltaL abgeleitet, Formel identisch zu -;; test_gefaellestrecke.lsp/test_mubea.lsp. -(defun hundm05:build-gf (e / tid start-mm deltaL hz as-seite es-seite as-winkel - es-winkel winkel) - (setq tid (ssg-val e "test_id") - start-mm (hundm05:startpunkt e "start_mm") - deltaL (hundm05:num e "deltaL_mm" 0.0) - hz (hundm05:num e "hz_grad" 0.0) - as-seite (ssg-val e "as_seite") - es-seite (ssg-val e "es_seite") - as-winkel(ssg-val e "as_winkel") - es-winkel(ssg-val e "es_winkel") - winkel (hundm05:num e "winkel_grad" 3.0)) - (if (or (null as-seite) (= as-seite "")) (setq as-seite "links")) - (if (or (null es-seite) (= es-seite "")) (setq es-seite "links")) - (if (or (null as-winkel) (= as-winkel "")) (setq as-winkel "90")) - (if (or (null es-winkel) (= es-winkel "")) (setq es-winkel "90")) - - (if (null start-mm) - (progn - (hundm05:kein-startpunkt tid "start_mm") - (hundm05:result-json tid "gefaellestrecke" "failed" nil nil nil nil)) - (hundm05:build-gf-punkt tid start-mm deltaL hz as-seite es-seite - as-winkel es-winkel winkel)) -) - - -;; --- Gefaellestrecke mit gepruefter Startkoordinate bauen --- -(defun hundm05:build-gf-punkt (tid start-mm deltaL hz as-seite es-seite - as-winkel es-winkel winkel / rad sep-x L_stau ent) - (hundm05:ensure-gf-init) - ;; Element-Masse zur gewaehlten AS/ES-Variante setzen, damit aus-dx/ein-dx - ;; zur Variante (90/30 Grad) passen; danach auf 90 zuruecksetzen. - (vf-set-as-masse as-winkel as-seite) - (vf-set-es-masse es-winkel es-seite) - (setq rad (* winkel (/ pi 180.0))) - (setq sep-x (* 300.0 (cos rad))) - (setq L_stau (/ (- deltaL (float aus-dx) (float ein-dx) sep-x) (cos rad))) - - (princ (strcat "\n [GF] " tid " -> Start=(" (rtos (car start-mm) 2 0) - "," (rtos (cadr start-mm) 2 0) "," (rtos (caddr start-mm) 2 0) - ") hz=" (rtos hz 2 0) " deltaL=" (rtos deltaL 2 0) - " winkel=" (rtos winkel 2 2) " L_stau=" (rtos L_stau 2 1))) - - (if (<= L_stau 0.0) - (progn - (princ (strcat " -> uebersprungen: L_stau <= 0 (deltaL " (rtos deltaL 2 0) - " zu kurz fuer AS/ES-Elemente + Separator)")) - (vf-set-as-masse "90" as-seite) - (vf-set-es-masse "90" es-seite) - (hundm05:result-json tid "gefaellestrecke" "failed" nil nil start-mm nil)) - (progn - (setq ent (car (gefaellestrecke-einfuegen - L_stau winkel start-mm as-seite es-seite hz - deltaL as-winkel es-winkel "0" "Schoenenberger Geruest"))) - (vf-set-as-masse "90" as-seite) - (vf-set-es-masse "90" es-seite) - (hundm05:ent-json tid "gefaellestrecke" ent))) -) - - -;; --- Waagrechte Foerderstrecke (GERADE_*) --- -;; Antrieb/Umlenk-Paare ohne Hoehendifferenz sind keine VarioFoerderer (deren -;; Winkel-Solver braucht eine Hoehendifferenz, siehe GERADE_MAX_DELTA_H in -;; lib/dxf_scan_components.py). Gebaut wird mit derselben Produktions-Primitive, -;; die der Etage-Foerderer fuer seine waagrechten Abschnitte nutzt: -;; insert-hz-scaled-block-by-ks skaliert Staustrecke_SP_1000_mm auf die Laenge -;; und richtet sie an hz aus (vert-winkel 0 = waagrecht). -;; Rueckgabe der Primitive ist der Ausgangspunkt, nicht die ENAME - der Block -;; wird darum ueber (entlast) eingesammelt. -(defun hundm05:build-gerade (e / tid laenge hz startpunkt blk ent) - (setq tid (ssg-val e "test_id") - laenge (hundm05:num e "laenge" 0.0) - hz (hundm05:num e "hz" 0.0) - startpunkt (hundm05:startpunkt e "startpunkt")) - (if (null startpunkt) - (hundm05:kein-startpunkt tid "startpunkt") - (princ (strcat "\n [GERADE] " tid " -> Start=(" (rtos (car startpunkt) 2 0) - "," (rtos (cadr startpunkt) 2 0) "," (rtos (caddr startpunkt) 2 0) - ") laenge=" (rtos laenge 2 0) " hz=" (rtos hz 2 0)))) - (if (or (null startpunkt) (<= laenge 0.0)) - (progn - (if startpunkt (princ " -> uebersprungen: laenge <= 0")) - (hundm05:result-json tid "gerade" "failed" nil nil startpunkt nil)) - (progn - ;; Block VORHER laden und pruefen: insert-hz-scaled-block-by-ks ruft bei - ;; fehlendem Block (exit) auf und wuerde den gesamten Testlauf abbrechen. - (setq blk (ssg-ils-block-laden "Staustrecke_SP_1000_mm")) - (if (null blk) - (progn - (princ " -> uebersprungen: Block Staustrecke_SP_1000_mm nicht ladbar") - (hundm05:result-json tid "gerade" "failed" "Staustrecke_SP_1000_mm" - nil startpunkt nil)) - (progn - (insert-hz-scaled-block-by-ks "Staustrecke_SP_1000_mm" - startpunkt laenge hz 0) - (setq ent (entlast)) - (if (and ent (atoms-family 1 '("ssg-id-generate"))) - (ssg-id-generate ent)) - (hundm05:ent-json tid "gerade" ent))))) -) - - -;; --- Omniflo (Eintraege mit "type": "bogen"/"weiche") --- -;; BEWUSST nicht gebaut. omni:insert-dxf (Lisp/OmniModulInsert.lsp) holt den -;; Einfuegepunkt zwingend per (command "_.INSERT" dxf-pfad pause ...) - es gibt -;; in Produktion keinen Aufrufpfad, der X/Y programmgesteuert uebergibt (siehe -;; object_data.md 4.1). Ein JSON-Treiber muesste den _.INSERT selbst nachbauen. -;; Im gewaehlten Abschnitt (Kreisel-Bereich) kommt kein einziger Omniflo-Eintrag -;; vor, darum wird hier nichts auf Verdacht implementiert, was der Testlauf nie -;; ausfuehren koennte. Der eigene Zweig ist trotzdem noetig: Omniflo-Eintraege -;; tragen ein "id"-Feld und wuerden sonst als Kreisel gebaut werden. -(defun hundm05:build-omniflo (e / tid) - (setq tid (cond ((ssg-val e "id")) ((ssg-val e "sivasnr")) ("OMNIFLO"))) - (princ (strcat "\n [OMNIFLO] " tid " -> uebersprungen (nicht scriptbar, " - "siehe object_data.md 4.1)")) - (hundm05:result-json tid "omniflo" "unsupported" - (ssg-val e "sivasnr") nil - (list (hundm05:num e "x" 0.0) (hundm05:num e "y" 0.0) - (hundm05:num e "z" 0.0)) - nil) -) - - -;; --- Ein einzelner Sensor/Block: Blockdefinition laden, dann per Name einfuegen --- -;; Wie mubea:build-separator-one. Der in JSON genannte Block wird ueber -;; ssg-ils-block-laden vorab definiert (loest 2D/3D-Suffix auf); schlaegt das -;; fehl, wird auf Separator_SP zurueckgefallen - genau das greift bei den -;; Blocknamen "S-LP"/"S-SP", die es in data/ils/ nicht gibt. -(defun hundm05:build-sensor (e idx / blockname x y z rot tid eff n pt) - (setq blockname (ssg-val e "block") - x (hundm05:num e "x" 0.0) - y (hundm05:num e "y" 0.0) - z (hundm05:num e "z" 0.0) - rot (hundm05:num e "rotation" 0.0) - tid (cond ((ssg-val e "test_id")) - ((strcat "SENSOR_" (itoa idx))))) - (setq pt (list x y z)) - (princ (strcat "\n [SENSOR] " tid " '" (if blockname blockname "?") - "' -> (" (rtos x 2 0) "," (rtos y 2 0) "," (rtos z 2 0) - ") R=" (rtos rot 2 0))) - ;; NICHT (and blockname (ssg-ils-block-laden ...)) verwenden: AutoLISPs AND - ;; liefert T statt des letzten Wertes, das _.INSERT wuerde dann einen Block - ;; namens T einfuegen (siehe Kommentar in test_mubea.lsp). - (setq eff (if blockname (ssg-ils-block-laden blockname))) - (if (null eff) (setq eff (ssg-ils-block-laden "Separator_SP"))) - (if eff - (progn - (command "_.INSERT" eff pt "" "" rot) - ;; Verbleibende Prompts GEZAEHLT mit Enter abfangen - ein Prompt, der die - ;; Leer-Antwort ablehnt, haelt CMDACTIVE dauerhaft > 0 und eine ungebremste - ;; Schleife wuerde BricsCAD haengen lassen. - (setq n 0) - (while (and (> (getvar "CMDACTIVE") 0) (< n 5)) - (command "") - (setq n (1+ n))) - (if (> (getvar "CMDACTIVE") 0) - (progn - (command) - (princ (strcat "\n WARNUNG: INSERT von '" eff - "' nicht sauber beendet - abgebrochen.")))) - (if (atoms-family 1 '("ssg-id-generate")) - (ssg-id-generate (entlast))) - (hundm05:ent-json tid "sensor" (entlast))) - (progn - ;; Kein Block gefunden -> sichtbaren Platzhalter zeichnen - (if (not (tblsearch "LAYER" "HUNDM05_SENSOR")) - (entmake (list '(0 . "LAYER") '(2 . "HUNDM05_SENSOR") '(70 . 0) '(62 . 1)))) - (entmake (list '(0 . "CIRCLE") (cons 10 pt) (cons 40 150.0) - '(8 . "HUNDM05_SENSOR"))) - (princ " (Block fehlt - Platzhalter)") - (hundm05:result-json tid "sensor" "placeholder" - (if blockname blockname "Separator_SP") nil pt nil))) -) - - -;; --- JSON-Export der Ergebnisse --- +;; ============================================================ +;; JSON-Export der Ergebnisse +;; ============================================================ (defun hundm05:export-results (tests-out-dir / out-json f first) (if (null *hundm05-test-results*) (princ "\n Keine HundM05-Ergebnisse vorhanden.") @@ -504,27 +542,39 @@ ;; ============================================================ ;; C:TEST_HUNDM05 - Hauptbefehl ;; ============================================================ -(defun c:TEST_HUNDM05 ( / json-datei daten eintrag tid res r - results-list sensor-idx anz-ok anz-fehler) - (dbgopen "hundm05_id.dbg" "DXFM_LOG") +(defun c:TEST_HUNDM05 ( / json-datei daten ketten results-list res + anz-ok anz-fehler) + ;; Debug-Datei nur bei eingeschaltetem Schalter: (dbg-schalter-on "hundm05") + (setq *hundm05-dbg* + (and (car (atoms-family 1 '("DBG-SCHALTER-OPEN"))) + (dbg-schalter-open "hundm05" "hundm05.dbg" "DXFM_LOG"))) - ;; Benoetigte Feature-Module laden - (ssg-ensure "KreiselInsert") + ;; Benoetigte Feature-Module laden (Linienzug braucht Vario + Gefaelle) (ssg-ensure "VarioFoerderer") (ssg-ensure "Gefaellestrecke") - (ssg-ensure "SSG_LIB_Commands") + + (if (null (car (atoms-family 1 '("VF-LINIENZUG-MODUS")))) + (progn + (princ "\n[TEST_HUNDM05] FEHLER: vf-linienzug-modus nicht geladen" + " (VarioFoerderer/Gefaellestrecke laden).") + (if *hundm05-dbg* (dbgclose)) + (exit))) (ssg-start "TEST_HUNDM05" '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA"))) (setvar "OSMODE" 0) (setvar "ATTREQ" 0) (setvar "ATTDIA" 0) - ;; Block-Bibliothek (Bogen-/AS-/ES-Masse) initialisieren - fuer Vario+GF noetig + ;; Block-Bibliothek (Bogen-/AS-/ES-Masse) initialisieren (if (or (not *lib-initialized*) (null bogen-auf)) (init-bibliothek)) - ;; Cache fuer den GF-Init-Workaround pro Lauf zuruecksetzen - (setq *hundm05-gf-init-done* nil) + ;; Bau-Dimension festhalten. Die Eingabedaten sind dimensionsfrei; welche + ;; Blockvariante (_2D/_3D) verwendet wird, loest ssg-ils-dim-aktuell auf + ;; (Override *ssg-ils-dim* -> Umgebungsvariable DXFM_DIM -> Default "3D"). + (setq *hundm05-dimension* (ssg-ils-dim-aktuell)) + (princ (strcat "\n Bau-Dimension: " *hundm05-dimension* + " (umschalten: TEST_HUNDM05_2D / TEST_HUNDM05_3D)")) ;; Testdaten laden (setq json-datei (strcat (getenv "DXFMAKRO") "/tests/testdata/hundm05.json")) @@ -532,70 +582,86 @@ (progn (princ (strcat "\n[TEST_HUNDM05] FEHLER: " json-datei " nicht gefunden!")) (ssg-end) - (dbgclose) + (if *hundm05-dbg* (dbgclose)) (exit))) (setq daten (ssg-load-json json-datei)) (if (null daten) (progn (princ "\n[TEST_HUNDM05] FEHLER: Testdaten konnten nicht geladen werden.") (ssg-end) - (dbgclose) + (if *hundm05-dbg* (dbgclose)) + (exit))) + + (setq ketten (hundm05:gruppiere daten)) + (if (null ketten) + (progn + (princ "\n[TEST_HUNDM05] FEHLER: keine Kette gefunden - erwartet werden" + " Kopf-Objekte mit \"test_id\" und Eingaben mit \"typ\".") + (ssg-end) + (if *hundm05-dbg* (dbgclose)) (exit))) (princ "\n\n================================================================") - (princ "\n TEST_HUNDM05 - HundM Kreisel-Abschnitt (ST500592_05.dxf)") - (princ (strcat "\n " (itoa (length daten)) " Eintraege geladen")) + (princ "\n TEST_HUNDM05 - 5 Linienzuege der Anlage HundM (polylines.dxf)") + (princ (strcat "\n " (itoa (length daten)) " JSON-Objekte, " + (itoa (length ketten)) " Ketten")) (princ "\n================================================================") - (setq results-list '() sensor-idx 0 anz-ok 0 anz-fehler 0) + (setq anz-ok 0 anz-fehler 0) + (hundm05:stubs-an) + ;; Stubs MUESSEN auch nach einem unerwarteten Fehler zurueckgenommen werden. + (setq results-list (vl-catch-all-apply 'hundm05:ketten-bauen (list ketten))) + (hundm05:stubs-aus) + (if (vl-catch-all-error-p results-list) + (progn + (princ (strcat "\n[TEST_HUNDM05] FEHLER in der Kettenschleife: " + (vl-catch-all-error-message results-list))) + (setq results-list '()))) - (foreach eintrag daten - (setq tid (ssg-val eintrag "test_id")) - (setq res - (cond - ;; Sensor/Insert: hat "block" - ((ssg-val eintrag "block") - (setq sensor-idx (1+ sensor-idx)) - (hundm05:build-sensor eintrag sensor-idx)) - ;; Omniflo VOR Kreisel pruefen: diese Eintraege tragen "id" UND "type" - ((ssg-val eintrag "type") - (hundm05:build-omniflo eintrag)) - ;; Kreisel: hat "id" (ohne "type") - ((ssg-val eintrag "id") - (hundm05:build-kreisel eintrag)) - ;; Vario: test_id "VF_*" - ((and tid (>= (strlen tid) 3) (= (substr tid 1 3) "VF_")) - (hundm05:build-vario eintrag)) - ;; Gefaellestrecke: test_id "GF_*" - ((and tid (>= (strlen tid) 3) (= (substr tid 1 3) "GF_")) - (hundm05:build-gf eintrag)) - ;; Waagrechte Strecke: test_id "GERADE_*" - ((and tid (>= (strlen tid) 7) (= (substr tid 1 7) "GERADE_")) - (hundm05:build-gerade eintrag)) - (T - (princ "\n WARNUNG: Unbekannter Eintrag uebersprungen") - nil))) - ;; Alle Builder liefern hier je einen String (keine Templates mit "anzahl" - ;; wie bei Mubea) - der Vollstaendigkeit halber trotzdem Listen zulassen. - (if res - (progn - (if (= (type res) 'STR) (setq res (list res))) - (foreach r res - (setq results-list (cons r results-list)) - (if (or (vl-string-search "\"executed\"" r) - (vl-string-search "\"placeholder\"" r) - (vl-string-search "\"unsupported\"" r)) - (setq anz-ok (1+ anz-ok)) - (setq anz-fehler (1+ anz-fehler))))))) + (foreach res results-list + (if (vl-string-search "\"status\": \"executed\"" res) + (setq anz-ok (1+ anz-ok)) + (setq anz-fehler (1+ anz-fehler)))) (princ "\n================================================================") - (princ (strcat "\n Ergebnis: " (itoa anz-ok) " OK, " (itoa anz-fehler) " Fehler")) + (princ (strcat "\n Ergebnis: " (itoa anz-ok) " OK, " + (itoa anz-fehler) " Fehler")) (princ "\n================================================================") - (setq *hundm05-test-results* (reverse results-list)) + (setq *hundm05-test-results* results-list) (ssg-end) (princ "\n TEST_HUNDM05 abgeschlossen.") + (if *hundm05-dbg* (dbgclose)) (princ) - (dbgclose) ) + + +;; ============================================================ +;; Dieselbe Anlage in einer bestimmten Dimension bauen +;; ============================================================ +;; Die Eingabedaten (hundm05.json) sind dimensionsfrei - sie beschreiben die +;; Eingaben, mit denen die Ketten gebaut wurden. Ob daraus flache 2D-Symbole +;; oder 3D-Modelle werden, entscheidet allein die Blockvariante (_2D/_3D), die +;; ssg-ils-dim-aktuell aufloest. Unsere Bibliothek hat beide, also laesst sich +;; dieselbe Anlage ohne Datenaenderung in 2D ODER 3D aufbauen. +;; *hundm05-dim-override* wird in der Kettenschleife je Kette neu gesetzt +;; (siehe dort). +(defun hundm05:mit-dimension (dim / alt ergebnis) + (setq alt (if (boundp '*ssg-ils-dim*) *ssg-ils-dim*)) + (setq *ssg-ils-dim* dim) + (setq *hundm05-dim-override* dim) + (princ (strcat "\n[TEST_HUNDM05] Dimension voruebergehend auf " dim + " gesetzt.")) + (setq ergebnis (c:TEST_HUNDM05)) + ;; Override wieder auf den vorherigen Stand - sonst baut ein folgender Test + ;; unbemerkt in der hier gewaehlten Dimension weiter. + (setq *ssg-ils-dim* alt) + (setq *hundm05-dim-override* nil) + (princ (strcat "\n[TEST_HUNDM05] Dimension zurueckgesetzt auf " + (if alt alt "Umgebung/Default") ".")) + ergebnis +) + +(defun c:TEST_HUNDM05_2D ( / ) (hundm05:mit-dimension "2D")) +(defun c:TEST_HUNDM05_3D ( / ) (hundm05:mit-dimension "3D")) diff --git a/tests/test_hundm05.py b/tests/test_hundm05.py index edacfcb..08f650e 100644 --- a/tests/test_hundm05.py +++ b/tests/test_hundm05.py @@ -1,66 +1,55 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -test_hundm05.py - Validiert den HundM-Kreisel-Abschnitt. +test_hundm05.py - Validiert die 5 VarioFoerderer-Linienzuege der Anlage HundM. -Prueft die von TEST_HUNDM05 (test_hundm05.lsp) erzeugten Ergebnisse: - 1. Alle Eintraege aus hundm05.json wurden verarbeitet (results.json) - 2. Statuswerte (executed / placeholder fuer Sensoren / unsupported Omniflo) - 3. Kreisel-Attribute (HOEHE, KREISELART, Block-Praefix) - 4. Vario: VF_-Block, L_GF gleichmaessig verteilt (kein L_GF1 in den Daten) - 5. DXF-Geometrie: Block-Anzahlen je Typ (ezdxf) +Zwei Ebenen: + 1. Die Testdaten selbst (tests/testdata/hundm05.json): Struktur der + Eingabe-Journale und Lesbarkeit durch den LISP-Parser. + 2. Die von TEST_HUNDM05 (test_hundm05.lsp) erzeugten Ergebnisse + (output/hundm05_results.json + output/hundm05_tests.dxf): jede Kette + gebaut, kein Journal-Wert offen, Attribute passen zu den Eingaben. -Die Testdaten stammen aus lib/dxf_scan_components.py (Fenster -216000 144000 228000 156000 der Zeichnung results/ST500592_05.dxf) und nicht -aus Handarbeit - anders als bei mubea.json. +Die Testdaten stammen aus lib/vf_journal_export.py (Journale der VF_n-Bloecke +aus data/polylines.dxf, XDATA-App SSG_VF_EDIT) und nicht aus Handarbeit - +Schema siehe tests/testdata/object_data.md Abschnitt 8. """ +import json +import os + import pytest +EINGABE_TYPEN = ("point_abs", "point_rel", "real", "int", "string", "step") + + # ============================================================ -# Klassifikation der hundm05.json-Eintraege +# Hilfsfunktionen: flaches JSON in Ketten gruppieren # ============================================================ -# Reihenfolge wie im Dispatcher von test_hundm05.lsp: Omniflo VOR Kreisel, -# weil Omniflo-Eintraege sowohl "id" als auch "type" tragen. +# Gleiche Regel wie hundm05:gruppiere in test_hundm05.lsp: ein Objekt mit +# "test_id" ist ein Kopf und beginnt eine Kette, die folgenden Objekte mit +# "typ" sind ihre Eingaben. -def _is_sensor(item): - return "block" in item +def _ketten(testdata): + ketten = [] + for obj in testdata: + if "test_id" in obj: + ketten.append((obj, [])) + elif "typ" in obj and ketten: + ketten[-1][1].append(obj) + return ketten -def _is_omniflo(item): - return "type" in item and "block" not in item +def _journal_laenge(eingaben): + """Anzahl der Journal-Eintraege, die der Treiber daraus baut: ein + point_rel mit "hz" wird zu ZWEI Eintraegen (DL + REAL).""" + return sum(2 if (e["typ"] == "point_rel" and "hz" in e) else 1 + for e in eingaben) -def _is_kreisel(item): - return "id" in item and "type" not in item and "block" not in item - - -def _hat_prefix(item, prefix): - return str(item.get("test_id", "")).startswith(prefix) - - -def _is_vario(item): - return _hat_prefix(item, "VF_") - - -def _is_gefaelle(item): - return _hat_prefix(item, "GF_") - - -def _is_gerade(item): - return _hat_prefix(item, "GERADE_") - - -def _expected_counts(testdata): - return { - "kreisel": sum(1 for t in testdata if _is_kreisel(t)), - "vario": sum(1 for t in testdata if _is_vario(t)), - "gefaellestrecke": sum(1 for t in testdata if _is_gefaelle(t)), - "gerade": sum(1 for t in testdata if _is_gerade(t)), - "sensor": sum(1 for t in testdata if _is_sensor(t)), - "omniflo": sum(1 for t in testdata if _is_omniflo(t)), - } +def _pfad(*teile): + return os.path.join(os.path.dirname(os.path.abspath(__file__)), *teile) # ============================================================ @@ -69,34 +58,106 @@ def _expected_counts(testdata): class TestHundM05Testdaten: - def test_jeder_eintrag_klassifizierbar(self, hundm05_testdata): - """Kein Eintrag darf durch alle Zweige des Dispatchers fallen.""" - for i, t in enumerate(hundm05_testdata): - erkannt = (_is_sensor(t) or _is_omniflo(t) or _is_kreisel(t) - or _is_vario(t) or _is_gefaelle(t) or _is_gerade(t)) - assert erkannt, \ - f"Eintrag {i} ist keinem Typ zuzuordnen: {sorted(t)}" + def test_fuenf_ketten(self, hundm05_testdata): + """Die Anlage hat fuenf am Kreisel haengende Linienzuege.""" + ketten = _ketten(hundm05_testdata) + assert len(ketten) == 5, \ + f"{len(ketten)} Ketten in hundm05.json, erwartet 5" - def test_kreisel_vorhanden(self, hundm05_testdata): - """Der Abschnitt wurde wegen des Kreisels gewaehlt - er muss drin sein.""" - kreisel = [t for t in hundm05_testdata if _is_kreisel(t)] - assert kreisel, "kein Kreisel in hundm05.json" + def test_jedes_objekt_zuordenbar(self, hundm05_testdata): + """Kein Objekt darf zwischen Kopf und Eingabe fallen - sonst + verschluckt hundm05:gruppiere es stillschweigend.""" + for i, obj in enumerate(hundm05_testdata): + assert ("test_id" in obj) or ("typ" in obj), \ + f"Objekt {i} ist weder Kopf noch Eingabe: {sorted(obj)}" - def test_vario_hat_hoehendifferenz(self, hundm05_testdata): - """VarioFoerderer brauchen deltaH > 0 - der Winkel-Solver findet sonst - keinen Bogenwinkel. Waagrechte Paare gehoeren als 'gerade' ins JSON - (siehe GERADE_MAX_DELTA_H in lib/dxf_scan_components.py).""" - for t in hundm05_testdata: - if _is_vario(t): - assert float(t.get("deltaH", 0)) > 0, \ - f'{t["test_id"]}: deltaH={t.get("deltaH")} - gehoert als ' \ - f'"gerade" klassifiziert' + def test_kopf_felder(self, hundm05_testdata): + for kopf, _ in _ketten(hundm05_testdata): + assert kopf["test_id"].startswith("VF_"), kopf["test_id"] + # "typ" wuerde den Kopf zu einer Eingabe machen (siehe + # hundm05:gruppiere) - der Anlagetyp heisst darum "anlagetyp". + assert "typ" not in kopf, \ + f'{kopf["test_id"]}: Kopf darf kein Feld "typ" tragen' + assert kopf["anlagetyp"] == "linienzug", kopf["test_id"] + assert kopf["modus"] == 1, kopf["test_id"] + assert kopf["block"].startswith("VF_"), kopf["block"] + assert len(kopf["startpunkt_mm"]) == 3, kopf["test_id"] - def test_gerade_hat_laenge(self, hundm05_testdata): - for t in hundm05_testdata: - if _is_gerade(t): - assert float(t.get("laenge", 0)) > 0, \ - f'{t["test_id"]}: laenge={t.get("laenge")}' + def test_anzahl_eingaben_stimmt(self, hundm05_testdata): + """anzahl_eingaben ist die Selbstauskunft des Kopfes - weicht sie ab, + fehlen Eingaben (z.B. durch eine Handaenderung).""" + for kopf, eingaben in _ketten(hundm05_testdata): + assert kopf["anzahl_eingaben"] == len(eingaben), \ + f'{kopf["test_id"]}: anzahl_eingaben=' \ + f'{kopf["anzahl_eingaben"]}, gezaehlt {len(eingaben)}' + + def test_eingabe_typen_bekannt(self, hundm05_testdata): + for kopf, eingaben in _ketten(hundm05_testdata): + for i, e in enumerate(eingaben): + assert e["typ"] in EINGABE_TYPEN, \ + f'{kopf["test_id"]} Eingabe {i}: unbekannter Typ ' \ + f'{e["typ"]}' + assert e.get("kommentar"), \ + f'{kopf["test_id"]} Eingabe {i}: kein Kommentar' + + def test_kette_beginnt_mit_startpunkt(self, hundm05_testdata): + """Erste Eingabe = absoluter Startpunkt, zweite = Starthoehe. Fehlt + der Punkt, wuerde das Replay ihn interaktiv erfragen und die Kette an + der falschen Stelle aufbauen (siehe hundm05:build-linienzug).""" + for kopf, eingaben in _ketten(hundm05_testdata): + assert eingaben[0]["typ"] == "point_abs", kopf["test_id"] + assert eingaben[1]["typ"] == "real", kopf["test_id"] + punkt = eingaben[0]["wert"] + assert len(punkt) == 3, kopf["test_id"] + assert punkt == kopf["startpunkt_mm"], \ + f'{kopf["test_id"]}: point_abs {punkt} != startpunkt_mm ' \ + f'{kopf["startpunkt_mm"]}' + assert eingaben[1]["wert"] == punkt[2], \ + f'{kopf["test_id"]}: Starthoehe {eingaben[1]["wert"]} != ' \ + f'Z des Startpunkts {punkt[2]}' + + def test_hz_nur_beim_ersten_segment(self, hundm05_testdata): + """vfl-in-abstand journalisiert die Fahrtrichtung NUR beim ersten + Segment der Kette; jedes weitere erbt sie vom Vorgaenger. Ein "hz" an + spaeterer Stelle waere ein Wert zu viel in der Replay-Queue und + verschiebt alle folgenden Antworten (Desync).""" + for kopf, eingaben in _ketten(hundm05_testdata): + rel = [e for e in eingaben if e["typ"] == "point_rel"] + assert rel, f'{kopf["test_id"]}: kein Segment' + assert "hz" in rel[0], \ + f'{kopf["test_id"]}: erstes Segment ohne hz' + for i, e in enumerate(rel[1:], start=2): + assert "hz" not in e, \ + f'{kopf["test_id"]}: Segment {i} traegt hz - nur das ' \ + f'erste Segment darf eines haben' + + def test_segmentlaengen_positiv(self, hundm05_testdata): + for kopf, eingaben in _ketten(hundm05_testdata): + for e in eingaben: + if e["typ"] == "point_rel": + assert e["dL"] > 0, f'{kopf["test_id"]}: dL={e["dL"]}' + + def test_menue_antworten_sind_strings(self, hundm05_testdata): + """Menue-Antworten muessen Strings bleiben ("1"/"2"/"90"/"30"): der + Produktionscode vergleicht mit (= antwort "1"), eine Zahl wuerde + stumm in den Default-Zweig laufen.""" + for kopf, eingaben in _ketten(hundm05_testdata): + for i, e in enumerate(eingaben): + if e["typ"] in ("string", "step"): + assert isinstance(e["wert"], str), \ + f'{kopf["test_id"]} Eingabe {i}: {e["wert"]!r} ist ' \ + f'kein String' + + def test_glieder_vorhanden(self, hundm05_testdata): + """Jede Kette hat Glied-Marker (STEP) - sie sind der Anker fuer den + Sektions-/Glied-Edit und die Kurzbeschreibung.""" + for kopf, eingaben in _ketten(hundm05_testdata): + glieder = [e["wert"] for e in eingaben if e["typ"] == "step"] + assert glieder, f'{kopf["test_id"]}: kein STEP-Marker' + for g in glieder: + assert g in ("Linie-GF", "Linie-VF", "Linie", "GF-Bogen", + "Horizontal-VF", "Vario-Kurve", "ES"), \ + f'{kopf["test_id"]}: unbekanntes Glied {g}' def test_arrays_einzeilig(self): """Jedes Array muss komplett in EINER Zeile stehen. @@ -104,14 +165,11 @@ class TestHundM05Testdaten: ssg-parse-json-array (Lisp/ssg_core.lsp) liest die Datei zeilenweise, und ssg-cfg-parse-array braucht das ganze Array in einem String. Ein von json.dump(indent=2) umgebrochenes Array wird zu nil - der Treiber - baut das Element dann nicht an seiner Position. Das ist genau der + baut die Kette dann nicht an ihrer Position. Das ist genau der Fehler, der die Anlage einmal komplett am Ursprung aufgebaut hat, und er ist in der Zeichnung nicht als Datenfehler zu erkennen. """ - import os - pfad = os.path.join(os.path.dirname(os.path.abspath(__file__)), - "testdata", "hundm05.json") - with open(pfad, encoding="utf-8") as fh: + with open(_pfad("testdata", "hundm05.json"), encoding="utf-8") as fh: for nr, zeile in enumerate(fh, start=1): offen = zeile.count("[") - zeile.count("]") # Die aeusserste Klammer des Dokuments darf allein stehen. @@ -121,6 +179,33 @@ class TestHundM05Testdaten: f"Zeile {nr}: Array ueber mehrere Zeilen umgebrochen - " \ f"ssg-cfg-parse-array kann das nicht lesen: {zeile.strip()!r}" + def test_flach_fuer_lisp_parser(self, hundm05_testdata): + """Die Datei muss FLACH bleiben. + + ssg-load-json / ssg-parse-json-array behandelt jedes "{" als + Objektanfang und jedes "}"/"}," als Objektende - es kennt keine + Verschachtelung. Ein Objekt in einem Objekt (oder ein Array von + Objekten wie "eingaben": [...]) wuerde das umgebende Objekt + verschlucken. Dieser Test zaehlt die Objekte so, wie der LISP-Parser + sie zaehlen wuerde, und vergleicht mit dem echten JSON. + """ + objekte = 0 + with open(_pfad("testdata", "hundm05.json"), encoding="utf-8") as fh: + for zeile in fh: + gestrippt = zeile.strip() + if gestrippt == "{": + objekte += 1 + elif gestrippt in ("}", "},"): + continue + elif gestrippt.startswith('"'): + # Ein Wert, der ein Objekt oder eine Objektliste eroeffnet, + # macht die Datei fuer den LISP-Parser unlesbar. + assert not gestrippt.endswith(("{", "[")), \ + f"verschachtelter Wert: {gestrippt!r}" + assert objekte == len(hundm05_testdata), \ + f"LISP-Parser saehe {objekte} Objekte, json.load sieht " \ + f"{len(hundm05_testdata)}" + # ============================================================ # Ergebnis-Pruefung (results.json vs. testdata) @@ -128,100 +213,147 @@ class TestHundM05Testdaten: class TestHundM05Results: - def test_alle_verarbeitet(self, hundm05_testdata, hundm05_results): - """Je Kind muss die erwartete Anzahl an Ergebnissen vorliegen.""" - expected = _expected_counts(hundm05_testdata) - got = {} + def test_je_kette_ein_ergebnis(self, hundm05_testdata, hundm05_results): + soll = [k["test_id"] for k, _ in _ketten(hundm05_testdata)] + ist = [r["test_id"] for r in hundm05_results] + assert ist == soll, f"Ergebnisse {ist}, erwartet {soll}" for r in hundm05_results: - got[r["kind"]] = got.get(r["kind"], 0) + 1 - for kind, n in expected.items(): - assert got.get(kind, 0) == n, \ - f'{kind}: erwartet {n}, verarbeitet {got.get(kind, 0)}' + assert r["kind"] == "linienzug", f'{r["test_id"]}: {r["kind"]}' - def test_status_ok(self, hundm05_results): - """Sensoren duerfen 'placeholder', Omniflo ist 'unsupported' - (omni:insert-dxf ist nicht scriptbar, siehe object_data.md 4.1), - alles andere muss 'executed' sein.""" + def test_status_executed(self, hundm05_results): + """"desync" (Journal-Werte nicht verbraucht) und "failed" (Abbruch) + sind beide echte Fehler - siehe hundm05:build-linienzug.""" for r in hundm05_results: - if r["kind"] == "sensor": - erlaubt = ("executed", "placeholder") - elif r["kind"] == "omniflo": - erlaubt = ("unsupported",) - else: - erlaubt = ("executed",) - assert r["status"] in erlaubt, \ - f'{r["test_id"]} ({r["kind"]}): Status={r["status"]}, ' \ - f'erlaubt {erlaubt}' + assert r["status"] == "executed", \ + f'{r["test_id"]}: Status={r["status"]}' - def test_kreisel_attribute(self, hundm05_testdata, hundm05_results): - """Kreisel: KREISELART und HOEHE stimmen mit der Vorgabe, Blockname - traegt das Praefix KREISEL_. HOEHE wird von kreisel-connect-script aus - 'hoehe' bzw. dem Z des Startpunkts gebildet (rtos ... 2 0, also - ganzzahlig).""" - by_id = {r["test_id"]: r for r in hundm05_results} - for t in hundm05_testdata: - if not _is_kreisel(t): - continue - r = by_id.get(t["id"]) - assert r, f'Kreisel {t["id"]} fehlt im Ergebnis' - assert r["block_name"].startswith("KREISEL_"), \ - f'{t["id"]}: Block "{r["block_name"]}" ohne Praefix KREISEL_' - assert r["actual_attributes"].get("KREISELART") == \ - t.get("typ", "STANDARD"), \ - f'{t["id"]}: KREISELART ist=' \ - f'{r["actual_attributes"].get("KREISELART")}' - soll = t.get("hoehe", t.get("start_z", 0.0)) - assert r["actual_attributes"].get("HOEHE") == str(int(round(soll))), \ - f'{t["id"]}: HOEHE ist={r["actual_attributes"].get("HOEHE")}, ' \ - f'erwartet={int(round(soll))}' + def test_journal_vollstaendig_verbraucht(self, hundm05_testdata, + hundm05_results): + """Alle aufgezeichneten Werte muessen im Bau abgerufen worden sein. + Bleibt etwas offen, ist der Ablauf von der Aufzeichnung abgewichen und + die Geometrie kann der Vorlage nicht entsprechen.""" + soll = {k["test_id"]: _journal_laenge(e) + for k, e in _ketten(hundm05_testdata)} + for r in hundm05_results: + assert "eingaben_offen" in r, ( + f'{r["test_id"]}: kein eingaben_offen im Ergebnis - die Kette ' + f'ist vor der Auswertung abgebrochen (status=' + f'{r.get("status")!r}, {r.get("fehler_text")})') + assert r["eingaben_offen"] == 0, \ + f'{r["test_id"]}: {r["eingaben_offen"]} Journal-Werte offen' + assert r["eingaben_gesamt"] == soll[r["test_id"]], \ + f'{r["test_id"]}: {r["eingaben_gesamt"]} Journal-Eintraege ' \ + f'abgespielt, erwartet {soll[r["test_id"]]}' - def test_kreisel_abstand(self, hundm05_testdata, hundm05_results): - """Bei 'connect' berechnet kreisel-connect-script den Abstand als - Distanz(Start,Ende) - Kreiseldurchmesser (800mm). Der Block muss also - auf dem Startpunkt sitzen.""" - by_id = {r["test_id"]: r for r in hundm05_results} - for t in hundm05_testdata: - if not _is_kreisel(t) or t.get("function") != "connect": - continue - r = by_id.get(t["id"]) - assert r, f'Kreisel {t["id"]} fehlt im Ergebnis' - ip = r["insert_point"] - assert abs(ip[0] - t["start_x"]) < 1.0, \ - f'{t["id"]}: Einfuegepunkt X {ip[0]} != start_x {t["start_x"]}' - assert abs(ip[1] - t["start_y"]) < 1.0, \ - f'{t["id"]}: Einfuegepunkt Y {ip[1]} != start_y {t["start_y"]}' + def test_keine_live_eingabe(self, hundm05_results): + """Kein Prompt, kein Dialog: der Treiber schaltet *vfl-headless* ein + (harter Abbruch statt stillem Rueckfall auf Live-Eingabe, siehe + vfl-headless-abbruch in Lisp/vf_linienzug.lsp) UND ersetzt zusaetzlich + getpoint/getstring/getint/getreal durch zaehlende Stubs. Beide Wege + muessen leer bleiben - "prompts" ist der positive Beweis, dass keine + Eingabe ausserhalb der vfl-in-*-Wrapper erreicht wurde.""" + for r in hundm05_results: + if "prompts" in r: + assert r["prompts"] == 0, \ + f'{r["test_id"]}: {r["prompts"]} Live-Eingabe(n) erreicht' + assert not r.get("headless_fehler"), \ + f'{r["test_id"]}: Headless-Abbruch bei {r["headless_fehler"]}' - def test_vario_block_und_split(self, hundm05_testdata, hundm05_results): - """Vario: VF_-Block, und ohne L_GF1-Vorgabe wird L_GF gleichmaessig auf - Vor- und Nachlauf verteilt (Default nach object_data.md 3.1).""" - by_id = {r["test_id"]: r for r in hundm05_results} - for t in hundm05_testdata: - if not _is_vario(t): - continue - r = by_id.get(t["test_id"]) - assert r, f'Vario {t["test_id"]} fehlt im Ergebnis' + def test_keine_geometrie_meldungen(self, hundm05_results): + """*vfl-meldungen* sammelt die abgewiesenen Sektionen (frueher + blockierende alert-Fenster, jetzt vfl-meldung). Eine Meldung heisst: + ein Glied liess sich geometrisch nicht bauen - der Nachbau weicht dann + von der Vorlage ab, auch wenn das Journal aufgeht.""" + for r in hundm05_results: + assert not r.get("meldungen"), \ + f'{r["test_id"]}: Meldungen aus dem Bau: {r["meldungen"]}' + + def test_vf_block_erzeugt(self, hundm05_results): + for r in hundm05_results: assert r["block_name"].startswith("VF_"), \ - f'{t["test_id"]}: Block "{r["block_name"]}" ist kein VF_-Block' - attrs = r["actual_attributes"] - if "L_GF1" in t or "L_GF_m" not in attrs: - continue - teile = attrs["L_GF_m"].split(",") - assert len(teile) == 2, \ - f'{t["test_id"]}: L_GF_m Format "{attrs["L_GF_m"]}"' - vorne, hinten = float(teile[0]), float(teile[1]) - assert abs(vorne - hinten) < 0.002, \ - f'{t["test_id"]}: L_GF nicht gleichmaessig verteilt ' \ - f'({vorne} / {hinten})' + f'{r["test_id"]}: Block "{r["block_name"]}" ist kein VF_-Block' + assert r["block_handle"], f'{r["test_id"]}: kein Handle' - def test_gefaelle_block(self, hundm05_testdata, hundm05_results): - by_id = {r["test_id"]: r for r in hundm05_results} - for t in hundm05_testdata: - if not _is_gefaelle(t): + def test_dimension_einheitlich(self, hundm05_results): + """Alle Ergebnisse eines Laufs stammen aus EINER Bau-Dimension. + + Die Eingabedaten sind dimensionsfrei; ob _2D- oder _3D-Blockvarianten + verwendet werden, loest ssg-ils-dim-aktuell auf. Ein Lauf, in dem beide + vorkommen, waere ein halb umgeschalteter Zustand (z.B. weil ein + vorheriger Test *ssg-ils-dim* nicht zurueckgesetzt hat). + """ + dims = {r.get("dimension") for r in hundm05_results} + assert len(dims) == 1, f"gemischte Bau-Dimensionen im Lauf: {dims}" + dim = dims.pop() + assert dim in ("2D", "3D"), \ + f'Bau-Dimension "{dim}" ist weder 2D noch 3D' + + def test_startpunkt_getroffen(self, hundm05_testdata, hundm05_results): + """Der VF_n-Block wird mit dem Kettenstartpunkt als Basispunkt + gewickelt (ssg-block-wrap-welt in vfl-block-erstellen) - sein + Einfuegepunkt muss also der Startpunkt aus den Eingaben sein.""" + soll = {k["test_id"]: k["startpunkt_mm"] + for k, _ in _ketten(hundm05_testdata)} + for r in hundm05_results: + p = soll[r["test_id"]] + ist = r["insert_point"] + for achse, i in (("X", 0), ("Y", 1), ("Z", 2)): + assert abs(ist[i] - p[i]) < 1.0, \ + f'{r["test_id"]}: Einfuegepunkt {achse} {ist[i]} != ' \ + f'{p[i]}' + + def test_as_es_attribute_aus_eingaben(self, hundm05_testdata, + hundm05_results): + """WINKEL_AS/SEITE_AS und WINKEL_ES/SEITE_ES muessen die Antworten aus + dem Journal wiedergeben (AS steht in der Praeambel, ES im ES-Glied).""" + seiten = {"1": "links", "2": "rechts"} + nach_id = {r["test_id"]: r for r in hundm05_results} + for kopf, eingaben in _ketten(hundm05_testdata): + r = nach_id[kopf["test_id"]] + attrs = r["actual_attributes"] + # Praeambel: [0] Punkt, [1] Hoehe, [2] AS setzen, [3] Winkel, + # [4] Seite + if eingaben[2]["wert"] == "1": + assert attrs.get("WINKEL_AS") == eingaben[3]["wert"], \ + f'{kopf["test_id"]}: WINKEL_AS=' \ + f'{attrs.get("WINKEL_AS")}, erwartet {eingaben[3]["wert"]}' + assert attrs.get("SEITE_AS") == seiten[eingaben[4]["wert"]], \ + f'{kopf["test_id"]}: SEITE_AS={attrs.get("SEITE_AS")}' + # ES: die beiden Strings direkt nach dem STEP-Marker "ES" + for i, e in enumerate(eingaben): + if e["typ"] == "step" and e["wert"] == "ES": + assert attrs.get("WINKEL_ES") == eingaben[i + 1]["wert"], \ + f'{kopf["test_id"]}: WINKEL_ES=' \ + f'{attrs.get("WINKEL_ES")}' + assert attrs.get("SEITE_ES") == \ + seiten[eingaben[i + 2]["wert"]], \ + f'{kopf["test_id"]}: SEITE_ES={attrs.get("SEITE_ES")}' + break + + def test_vario_kurven_gezaehlt(self, hundm05_testdata, hundm05_results): + """Jede Vario-Kurve im Journal muss in den VF_Bogen_*-Attributen + auftauchen (vfl-insert-vario-kurve-block zaehlt sie mit).""" + nach_id = {r["test_id"]: r for r in hundm05_results} + for kopf, eingaben in _ketten(hundm05_testdata): + soll = sum(1 for e in eingaben + if e["typ"] == "step" and e["wert"] == "Vario-Kurve") + attrs = nach_id[kopf["test_id"]]["actual_attributes"] + ist = sum(int(attrs.get(f"VF_Bogen_{lage}_{w}", 0) or 0) + for lage in ("A", "I") for w in (90, 60, 30)) + assert ist == soll, \ + f'{kopf["test_id"]}: {ist} Vario-Kurven in den Attributen, ' \ + f'{soll} im Journal' + + def test_hoehe_von_ist_starthoehe(self, hundm05_testdata, + hundm05_results): + nach_id = {r["test_id"]: r for r in hundm05_results} + for kopf, eingaben in _ketten(hundm05_testdata): + attrs = nach_id[kopf["test_id"]]["actual_attributes"] + if "HOEHE_VON_mm" not in attrs: continue - r = by_id.get(t["test_id"]) - assert r, f'Gefaellestrecke {t["test_id"]} fehlt im Ergebnis' - assert r["block_name"].startswith("GF_"), \ - f'{t["test_id"]}: Block "{r["block_name"]}" ist kein GF_-Block' + assert abs(int(attrs["HOEHE_VON_mm"]) - eingaben[1]["wert"]) <= 1, \ + f'{kopf["test_id"]}: HOEHE_VON_mm={attrs["HOEHE_VON_mm"]}, ' \ + f'Starthoehe {eingaben[1]["wert"]}' # ============================================================ @@ -230,24 +362,12 @@ class TestHundM05Results: class TestHundM05Geometry: - def _counts(self, dxf): - c = {} - for e in dxf.modelspace(): - if e.dxftype() == "INSERT": - c[e.dxf.name] = c.get(e.dxf.name, 0) + 1 - return c - - def test_block_anzahlen(self, hundm05_dxf, hundm05_testdata): - """Anzahl der KREISEL_/VF_/GF_-Bloecke >= Vorgabe aus hundm05.json.""" - c = self._counts(hundm05_dxf) - exp = _expected_counts(hundm05_testdata) - n_kr = sum(v for n, v in c.items() if n.startswith("KREISEL_")) - n_vf = sum(v for n, v in c.items() if n.startswith("VF_")) - n_gf = sum(v for n, v in c.items() if n.startswith("GF_")) - assert n_kr >= exp["kreisel"], f"KREISEL_: {n_kr} < {exp['kreisel']}" - assert n_vf >= exp["vario"], f"VF_: {n_vf} < {exp['vario']}" - assert n_gf >= exp["gefaellestrecke"], \ - f"GF_: {n_gf} < {exp['gefaellestrecke']}" + def test_block_anzahl(self, hundm05_dxf, hundm05_testdata): + """Je Kette muss ein VF_-Block im Modelspace stehen.""" + n_vf = sum(1 for e in hundm05_dxf.modelspace() + if e.dxftype() == "INSERT" and e.dxf.name.startswith("VF_")) + soll = len(_ketten(hundm05_testdata)) + assert n_vf >= soll, f"VF_: {n_vf} < {soll}" def test_handles_im_modelspace(self, hundm05_dxf, hundm05_results): """Jedes gebaute Block-Handle (executed) liegt im Modelspace.""" diff --git a/tests/test_mubea.lsp b/tests/test_mubea.lsp index 63e9258..3a49229 100644 --- a/tests/test_mubea.lsp +++ b/tests/test_mubea.lsp @@ -69,18 +69,49 @@ ) -;; --- Letzten INSERT mit Blocknamen-Praefix (rueckwaerts) suchen --- -(defun mubea:last-insert-prefix (pref / ent found ed bn) - (setq ent (entlast)) - (while (and ent (null found)) - (setq ed (entget ent) bn (cdr (assoc 2 ed))) - (if (and (= (cdr (assoc 0 ed)) "INSERT") bn - (>= (strlen bn) (strlen pref)) - (= (substr bn 1 (strlen pref)) pref)) - (setq found ent)) - (setq ent (entprev ent))) - found -) +;; --- Letzten INSERT mit Blocknamen-Praefix suchen --- +;; AutoLISP kennt KEIN entprev (nur entnext und entlast) - rueckwaerts durch +;; die Zeichnung laufen geht also nicht. Gesucht wird darum VORWAERTS, wobei +;; der letzte Treffer gewinnt. Ein blosses (entlast) genuegt nicht: nach dem +;; Bau koennen weitere Objekte entstehen (z.B. Beschriftungstexte), entlast +;; ist dann nicht der gesuchte Block - genau daran scheiterte der Testlauf. +;; ab-ent = Stand der Zeichnung VOR dem Bau. Damit prueft die Suche nur die +;; neu entstandenen Objekte: schnell, und sie kann nicht versehentlich den +;; Block eines frueheren Baus liefern (siehe Kommentar bei -ab). +(defun mubea:insert-prefix-p (ent pref / ed bn) + (setq ed (entget ent) bn (cdr (assoc 2 ed))) + (and ed (= (cdr (assoc 0 ed)) "INSERT") bn + (>= (strlen bn) (strlen pref)) + (= (substr bn 1 (strlen pref)) pref))) + +;; Vorwaertslauf ab ab-ent (nil = ganze Zeichnung); der LETZTE Treffer +;; gewinnt. Der entget-Test faengt ein inzwischen ungueltiges ab-ent ab +;; (entget liefert dann nil, entnext wuerde einen Fehler werfen). +(defun mubea:suche-vorwaerts (pref ab-ent / ent found) + (setq ent (if (and ab-ent (entget ab-ent)) (entnext ab-ent) (entnext))) + (while ent + (if (mubea:insert-prefix-p ent pref) (setq found ent)) + (setq ent (entnext ent))) + found) + +(defun mubea:last-insert-prefix-ab (pref ab-ent / ent) + (if ab-ent + ;; MIT Marker nur die neu entstandenen Objekte pruefen - und BEWUSST + ;; ohne entlast-Kurzschluss: hat dieser Bau keinen Block erzeugt, waere + ;; entlast der Block eines FRUEHEREN Baus und das Ergebnis wuerde der + ;; falschen Kette zugeordnet. Kein Treffer = kein Block, das ist die + ;; richtige Antwort. + (mubea:suche-vorwaerts pref ab-ent) + (progn + ;; Ohne Marker: erst entlast (der haeufige Fall, das gerade + ;; eingefuegte Objekt), sonst die ganze Zeichnung. + (setq ent (entlast)) + (if (and ent (mubea:insert-prefix-p ent pref)) + ent + (mubea:suche-vorwaerts pref nil))))) + +(defun mubea:last-insert-prefix (pref) + (mubea:last-insert-prefix-ab pref nil)) ;; ============================================================ diff --git a/tests/test_vf_headless_statisch.py b/tests/test_vf_headless_statisch.py new file mode 100644 index 0000000..1301585 --- /dev/null +++ b/tests/test_vf_headless_statisch.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +test_vf_headless_statisch.py - Statischer Waechter fuer den nicht-interaktiven +Betrieb des VF-Linienzugs. Laeuft ohne BricsCAD und ohne Zeichnung. + +Warum statisch: die dynamischen Beweise (Ergebnis-Record mit "prompts": 0, +"headless_fehler": null in tests/output/hundm05_results.json) sagen nur, dass +der EINE getestete Ablauf sauber lief. Sie schlagen nicht an, wenn jemand +einem selten erreichten Zweig ein neues (alert ...) oder ein rohes +(getreal ...) hinzufuegt. Genau das haelt dieser Test fest: die Fundstellen +sind eingecheckt, jede neue muss hier bewusst eingetragen werden. + +Hintergrund und Fahrplan: doc/TODO-plan-vf-interactive.md, +Mechanik: doc/variofoerderer.md Abschnitt 8b. +""" + +import collections +import io +import os + +import pytest + + +LISP = os.path.join(os.path.dirname(__file__), "..", "Lisp", + "vf_linienzug.lsp") +CORE = os.path.join(os.path.dirname(__file__), "..", "Lisp", "vf_core.lsp") + +# Gesuchte Aufrufe: alles, was den Anwender fragt oder ihm ein Fenster zeigt. +GESUCHT = ("alert", "getstring", "getint", "getreal", "getpoint", "getkword", + "ssget", "entsel", "new_dialog") + + +# ============================================================ +# Minimaler LISP-Scanner +# ============================================================ +# Kein voller Parser: er braucht nur die Klammertiefe (fuer die Grenzen der +# Top-Level-Formen) und muss Strings sowie Kommentare ueberspringen, damit ein +# "alert" im Kommentar oder in einem i18n-Text nicht mitgezaehlt wird. + +def funktions_inventar(quelltext, gesucht=GESUCHT): + """{Funktionsname: Counter({Aufruf: Anzahl})} fuer alle Top-Level-defuns. + Code ausserhalb eines defun laeuft unter dem Namen "". + gesucht = die Aufrufnamen, die gezaehlt werden.""" + inventar = collections.defaultdict(collections.Counter) + tiefe = 0 + aktuell = "" + i = 0 + n = len(quelltext) + while i < n: + c = quelltext[i] + if c == ";": # Kommentar bis Zeilenende + while i < n and quelltext[i] != "\n": + i += 1 + continue + if c == '"': # String ueberspringen + i += 1 + while i < n: + if quelltext[i] == "\\": + i += 2 + continue + if quelltext[i] == '"': + i += 1 + break + i += 1 + continue + if c == "(": + wort = _wort_nach(quelltext, i + 1) + if tiefe == 0: + aktuell = ("" if wort != "defun" + else _wort_nach(quelltext, + i + 1 + len("defun") + 1)) + if wort in gesucht: + inventar[aktuell][wort] += 1 + tiefe += 1 + i += 1 + continue + if c == ")": + tiefe -= 1 + if tiefe <= 0: + tiefe = 0 + aktuell = "" + i += 1 + continue + i += 1 + return inventar + + +def _wort_nach(text, pos): + """Erstes Symbol ab pos (Leerraum davor wird uebersprungen).""" + while pos < len(text) and text[pos] in " \t\r\n": + pos += 1 + ende = pos + while ende < len(text) and text[ende] not in " \t\r\n()\";'": + ende += 1 + return text[pos:ende] + + +def _funktionskoerper(quelltext, name): + """Quelltext einer Top-Level-Funktion (Rohtext, genuegt fuer eine + Namenssuche).""" + start = quelltext.find("(defun %s " % name) + if start < 0: + start = quelltext.find("(defun %s(" % name) + if start < 0: + return None + tiefe, i = 0, start + while i < len(quelltext): + if quelltext[i] == "(": + tiefe += 1 + elif quelltext[i] == ")": + tiefe -= 1 + if tiefe == 0: + return quelltext[start:i + 1] + i += 1 + return quelltext[start:] + + +@pytest.fixture(scope="module") +def quelltext(): + if not os.path.isfile(LISP): + pytest.skip("Lisp/vf_linienzug.lsp nicht gefunden") + return io.open(LISP, encoding="cp1252").read() + + +@pytest.fixture(scope="module") +def inventar(quelltext): + return funktions_inventar(quelltext) + + +# ============================================================ +# 1. alert - nur noch in reinen Interaktiv-Pfaden +# ============================================================ +# Alle Bau-Pfad-Alerts sind zu vfl-meldung geworden (P1 im Fahrplan): der Text +# wird gesammelt statt modal gezeigt, damit ein Batch-Lauf nicht blockiert und +# die Auskunft "welche Sektion war nicht baubar" nicht verloren geht. +# Stehen bleiben darf, was nur bei echter Benutzerbedienung entstehen kann - +# plus vfl-meldung selbst, das den Alert bei erlaubter GUI weiterhin zeigt. +ALERT_ERLAUBT = { + "vfl-meldung": 1, # zeigt den Text nur bei (ssg-gui-p) + "vfl-glied-gf-bogen-dialog": 1, # dcl/vf_linienzug_wizard.dcl fehlt + "vfl-glied-as-es-dialog": 1, # dito + "vfl-glied-vario-dialog": 1, # dito + "vfl-dlg-modus": 1, # dcl/vfl_edit.dcl fehlt + "vfl-dlg-position": 1, # dito + "vfl-edit-glied": 1, # "Glied nicht einzeln editierbar" + "vfl-edit-ent": 1, # "kein Journal am Block" +} + + +def test_alert_nur_in_interaktiv_pfaden(inventar): + ist = {name: z["alert"] for name, z in inventar.items() if z["alert"]} + neu = sorted(k for k in ist if k not in ALERT_ERLAUBT) + assert not neu, ( + "Neue (alert ...)-Aufrufe in %s. Im Bau-Pfad statt alert vfl-meldung " + "verwenden (sammelt den Text und zeigt ihn nur bei erlaubter GUI). " + "Ist der Aufruf wirklich rein interaktiv, hier eintragen." % neu) + assert ist == ALERT_ERLAUBT, ( + "Alert-Inventar veraendert: ist %s, erwartet %s" % (ist, + ALERT_ERLAUBT)) + + +# ============================================================ +# 2. Rohe Eingaben nur in den Wrappern und in Interaktiv-Befehlen +# ============================================================ +# Vollstaendiges Soll-Inventar. Ein neues getreal/getpoint/ssget in einer +# Bau-Funktion faellt damit auf, auch wenn sein Zweig im Test nie erreicht +# wird. Kommentar je Eintrag = warum die Stelle in Ordnung ist. +EINGABE_ERLAUBT = { + # --- die Journal-Wrapper selbst: hier laeuft der Replay durch --- + "vfl-getpoint": {"getpoint": 2}, + "vfl-in-string": {"getstring": 1}, + "vfl-in-int": {"getint": 1}, + "vfl-in-real": {"getreal": 1}, + "vfl-in-selection": {"ssget": 1}, + "vfl-winkel-live": {"getint": 1}, # livefn fuer vfl-in-value + # --- Wizard-Schicht (DCL); bei ssg-gui-aus greift sie nicht --- + "vflw-zahl": {"getreal": 1}, # Fallback ohne Dialog + "vflw-zahl-impl": {"new_dialog": 1}, + "vflw-wahl-impl": {"new_dialog": 1}, + "vflw-gruppe-as-impl": {"new_dialog": 1}, + "vflw-gruppe-gefaelle-impl": {"new_dialog": 1}, + "vflw-gruppe-horizontal-impl": {"new_dialog": 1}, + "vflw-gruppe-punkt-hoehe-impl": {"new_dialog": 1}, + "vflw-gruppe-variokurve-impl": {"new_dialog": 1}, + "vflw-gruppe-winkel-seite-impl": {"new_dialog": 1}, + "vflw-gruppe-ziel-hoehe-impl": {"new_dialog": 1}, + "vflw-seg-bogen-impl": {"new_dialog": 1}, + "vflw-seg-linie-m2-impl": {"new_dialog": 1}, + "vflw-seg-linie-m3-impl": {"new_dialog": 1}, + # --- Edit-Dialoge: nur per Doppelklick erreichbar --- + "vfl-dlg-modus": {"new_dialog": 1, "alert": 1}, + "vfl-dlg-position": {"new_dialog": 1, "alert": 1}, + "vfl-glied-as-es-dialog": {"new_dialog": 1, "alert": 1}, + "vfl-glied-gf-bogen-dialog": {"new_dialog": 1, "alert": 1}, + "vfl-glied-vario-dialog": {"new_dialog": 1, "alert": 1}, + "vfl-edit-ent": {"alert": 1}, + "vfl-edit-glied": {"alert": 1}, + "vfl-meldung": {"alert": 1}, + # --- reine Benutzer-Befehle (kein Bau-Pfad, nicht journalisiert) --- + "vfl-praeambel-erfragen": {"getpoint": 1, "getreal": 1, "getkword": 2}, + "c:VF_SEKTION_RESTORE": {"ssget": 1}, + "c:VFL_KS_DIAG": {"getstring": 1}, + "c:Vario_Kette_Merge": {"entsel": 1}, + # --- Zeichnung durchsuchen, keine Benutzerfrage --- + "vfl-kette-sammle-alle": {"ssget": 1}, +} + + +def test_eingabe_fundstellen_unveraendert(inventar): + ist = {name: dict(z) for name, z in inventar.items() if z} + neu = sorted(k for k in ist if k not in EINGABE_ERLAUBT) + assert not neu, ( + "Neue Eingabe-/Dialog-Aufrufe in %s. Im Bau-Pfad gehoeren Eingaben " + "durch die vfl-in-*-Wrapper (Journal + Replay + Headless-Riegel), " + "nicht direkt an get*/ssget. Ist die Stelle rein interaktiv, hier " + "eintragen." % neu) + weg = sorted(k for k in EINGABE_ERLAUBT if k not in ist) + assert not weg, ("Erwartete Fundstellen verschwunden (umbenannt?): %s" + % weg) + abweichung = {k: (ist[k], EINGABE_ERLAUBT[k]) for k in ist + if ist[k] != EINGABE_ERLAUBT[k]} + assert not abweichung, ("Anzahl der Aufrufe geaendert (ist, erwartet): %s" + % abweichung) + + +# ============================================================ +# 3. Der Headless-Riegel sitzt an allen Eingabe-Engstellen +# ============================================================ +# Jede interaktive Eingabe des Linienzugs laeuft durch einen dieser Wrapper. +# Fehlt dort die Headless-Pruefung, faellt eine erschoepfte Replay-Queue +# wieder still auf Live-Eingabe zurueck - der Fehler, den P2 behoben hat. +RIEGEL_ERWARTET = { + "vfl-in-value": ["vfl-headless-p", "vfl-headless-notausgang"], + "vfl-in-value-p": ["vfl-headless-p", "vfl-headless-notausgang"], + "vfl-in-selection": ["vfl-headless-p", "vfl-headless-abbruch"], + "vfl-in-abstand": ["vfl-headless-p", "vfl-headless-abbruch"], + "vfl-view-refresh": ["vfl-headless-p"], +} + + +@pytest.mark.parametrize("name", sorted(RIEGEL_ERWARTET)) +def test_headless_riegel_vorhanden(quelltext, name): + koerper = _funktionskoerper(quelltext, name) + assert koerper, "Funktion %s nicht gefunden" % name + for gesucht in RIEGEL_ERWARTET[name]: + assert gesucht in koerper, ( + "%s ruft %s nicht - ohne den Riegel fragt der Wrapper bei leerer " + "Replay-Queue wieder live (siehe doc/variofoerderer.md 8b)" + % (name, gesucht)) + + +def test_headless_symbole_definiert(quelltext): + for gesucht in ("(defun vfl-headless-p ", "(defun vfl-headless-abbruch ", + "(defun vfl-headless-notausgang ", + "(defun vfl-headless-ort ", "(defun vfl-meldung ", + "*vfl-headless-antwort-fn*", "*vfl-meldungen*", + "*vfl-headless-fehler*"): + assert gesucht in quelltext, "%s fehlt" % gesucht + + +def test_journal_reset_loescht_diagnose(quelltext): + """Meldungen und Headless-Diagnose gehoeren zum LAUF: bleiben sie stehen, + traegt der naechste Bau die Befunde des vorherigen im Ergebnis.""" + koerper = _funktionskoerper(quelltext, "vfl-journal-reset") + assert koerper + assert "*vfl-meldungen*" in koerper + assert "*vfl-headless-fehler*" in koerper + + +# ============================================================ +# 4. Funktionen, die es in AutoLISP nicht gibt +# ============================================================ +# Ein Tippfehler oder eine erfundene Funktion faellt erst auf, wenn der Zweig +# tatsaechlich durchlaufen wird - im Testlauf hat genau das (entprev ...) +# gekostet: AutoLISP kennt nur entnext und entlast, ein Rueckwaertslauf durch +# die Zeichnung ist nicht moeglich. Solche Faelle sind hier billig zu fangen. +# Erweitern, sobald wieder eine erfundene Funktion auffliegt. +NICHT_EXISTENT = ("entprev", "entfirst", "ssprev", "vl-string-replace") + +LISP_DIRS = ("Lisp", "tests") + + +def test_keine_nicht_existierenden_funktionen(): + wurzel = os.path.join(os.path.dirname(__file__), "..") + treffer = [] + for verz in LISP_DIRS: + pfad = os.path.join(wurzel, verz) + if not os.path.isdir(pfad): + continue + for name in sorted(os.listdir(pfad)): + if not name.lower().endswith(".lsp"): + continue + text = io.open(os.path.join(pfad, name), encoding="cp1252").read() + # Gleicher Scanner, andere Trefferliste - er ueberspringt + # Kommentare und Strings, ein erklaerender Kommentartext wie + # "kennt kein entprev" schlaegt also nicht an. + for wo, zaehler in funktions_inventar(text, + NICHT_EXISTENT).items(): + for fn in sorted(zaehler): + treffer.append("%s/%s: (%s in %s)" % (verz, name, fn, wo)) + assert not treffer, ( + "Aufrufe nicht existierender AutoLISP-Funktionen: %s" % treffer) + + +def test_dispatcher_setzt_journal_zurueck(): + """P0: der Reset muss VOR der Modus-Verzweigung stehen, nicht nur im + Modus-1-Zweig - sonst erbt ein frischer Modus-2-Lauf das Journal des + Vorlaufs und schreibt es in die XDATA.""" + if not os.path.isfile(CORE): + pytest.skip("Lisp/vf_core.lsp nicht gefunden") + text = io.open(CORE, encoding="cp1252").read() + cond = text.find("(cond ((= wahl 2) (vf-linienzug-modus2))") + assert cond >= 0, "Dispatcher-cond nicht gefunden" + reset = text.rfind("(vfl-journal-reset)", 0, cond) + assert reset >= 0, "vfl-journal-reset steht nicht vor der Modus-cond" + zweig = text[cond:text.find("(exit)", cond)] + assert "vfl-journal-reset" not in zweig, ( + "Reset steht (wieder) IN der cond - dann trifft er nur einen Modus") diff --git a/tests/testdata/hundm05.json b/tests/testdata/hundm05.json index b8edcb0..b9ffb11 100644 --- a/tests/testdata/hundm05.json +++ b/tests/testdata/hundm05.json @@ -1,573 +1,1492 @@ [ { - "id": "Kreisel_HundM05_01", - "function": "connect", - "start_x": 216120.19, - "start_y": 151070.0, - "start_z": 0.0, - "end_x": 239701.9, - "end_y": 151070.0, - "end_z": 0.0, - "typ": "STANDARD", - "hoehe": 0.0, - "comment": "aus DXF: AN8 (Layer 1ANTR-800) -> SP8 (Layer 1SP-800), Abstand 23581.71; daraus folgt produktionsseitig abstand = 23581.71 - 800.0 = 22781.71 und rotation = 0.00 Grad. ACHTUNG hoehe: AN8 und SP8 sind in dieser Zeichnung flache 2D-Symbole (nur Linien/Kreis/Text auf Z=0) und auf Z=0 eingefuegt - die Einfuegehoehe des Kreisels steht NICHT im DXF und muss hier von Hand gesetzt werden (vgl. mubea.json, wo z ebenfalls Handarbeit ist)." - }, - { - "test_id": "GF_HundM05_04", + "test_id": "VF_hundm05_LZ_01", + "anlagetyp": "linienzug", "modus": 1, - "deltaL_mm": 2094.45, - "winkel_grad": 48.21, - "hz_grad": 125.48, - "start_mm": [219540.85, 152832.91, 2342.96], - "as_seite": "links", - "es_seite": "links", - "as_winkel": "90", - "es_winkel": "90", - "comment": "aus DXF: 3D_FF_Antrieb_rechts -> Umlenkung FF (Layer _3D_Fingerförderer / z-3), XY-Abstand 2094.45, Neigung 48.21 Grad; Gefaellestrecke, da fallend und Neigung >= 3 Grad. as_/es_seite und -winkel sind Defaults (im DXF nicht ableitbar)." + "anzahl_eingaben": 65, + "block": "VF_87", + "dxf_handle": "1BEB5", + "startpunkt_mm": [4912.239129, 1230.886425, 2154.0], + "rotation_grad": 0.0, + "beschreibung": "Linienzug (Modus 1), Start Z2154: GF-Linie 2249 mm / 3 Grad -> Horizontal-VF 3257 mm -> Vario-Kurve 90 rechts innen -> Vario-Kurve 90 rechts innen -> Horizontal-VF 1210 mm -> Vario-Kurve 30 rechts innen -> Vario-Kurve 30 links innen -> ES 90 links", + "sivas_id": "0025", + "sivas_bezeichnung": "VarioFoerderer :1", + "planquadrat": "B/1", + "erwartung_hinweis": "Sivas-Export (EXPORTSIVAS), Strecke 0025: DeltaL_mm=1934, DeltaH_mm=463, Hoehe_Von_mm=2154, Hoehe_Bis_mm=1690, L_GF_m=1.826,0.4,0.4, L_VF_m=1.859,1.434,0.1,1.83, VF_Winkel=0,0,0,0, Anzahl_Separator=3", + "quelle": "INSERT 1BEB5 (Block VF_87) in polylines.dxf, XDATA-App SSG_VF_EDIT, Marker linienzug; im Journal aufgezeichneter Kettenstart: [4912.239129, 1230.886425, 0.000000]" }, { - "test_id": "VF_HundM05_05", - "typ": "standard", - "richtung": "Auf", - "deltaL": 1694.19, - "deltaH": 2057.58, - "startpunkt": [219542.27, 153175.18, 0.0], - "seite": "rechts", - "hz": 135.86, - "comment": "aus DXF: Antrieb FF -> 3D_FF_Umlenk_rechts (Layer z-3 / _3D_Fingerförderer), XY-Abstand 1694.19, Neigung 50.53 Grad; VarioFoerderer. 'winkel' bewusst nicht gesetzt - der Solver waehlt den kleinsten gueltigen Kandidaten. 'seite' ist Default (im DXF nicht ableitbar)." + "typ": "point_abs", + "wert": [4912.239129, 1230.886425, 2154.0], + "kommentar": "getpoint Startpunkt der Kette (absoluter Weltpunkt, einziger Anker der ganzen Kette)" }, { - "test_id": "GERADE_HundM05_08", - "typ": "gerade", - "laenge": 3305.26, - "hz": 143.35, - "startpunkt": [223150.89, 152953.65, 0.0], - "endpunkt": [220499.01, 154926.53, 0.0], - "comment": "aus DXF: Antrieb FF -> Umlenkung FF (Layer z-3 / z-3), XY-Abstand 3305.26, Neigung 0.00 Grad; waagrechte Foerderstrecke (Hoehendifferenz 0.00 mm < 10 mm) - KEIN VarioFoerderer, da dessen Winkel-Solver eine Hoehendifferenz braucht." + "typ": "real", + "wert": 2154.0, + "kommentar": "getreal Hoehe (Z) Startpunkt -> 2154.0 mm" }, { - "test_id": "GF_HundM05_09", + "typ": "string", + "wert": "1", + "kommentar": "getstring AS-Element setzen? (1=Ja, 2=Nein) -> Ja" + }, + { + "typ": "string", + "wert": "90", + "kommentar": "getstring AS-Element Winkel (Wert direkt, 90 oder 30 Grad) -> 90 Grad" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring AS-Seite (1=links, 2=rechts) -> links" + }, + { + "typ": "step", + "wert": "Linie-GF", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes Element am Kettenanfang (1=Neue Linie GF, 2=Neue Linie Ab/Auf VF, 3=Horizontal-VF, 4=Neue Linie bis Kettenende) -> Neue Linie: GF" + }, + { + "typ": "point_rel", + "dL": 2249.25106, + "hz": 90.0, + "kommentar": "getpoint Endpunkt der GF-Linie: dL=2249.3 mm, gesnappte Fahrtrichtung hz=90 Grad (nur beim ersten Segment der Kette journalisiert)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Gefaelle festlegen (1=ueber Zielhoehe, 2=ueber Neigungswinkel) -> ueber Neigungswinkel" + }, + { + "typ": "real", + "wert": 3.0, + "kommentar": "getreal Neigungswinkel -> 3.0 Grad" + }, + { + "typ": "string", + "wert": "3", + "kommentar": "getstring Ist das Kettenende? (1=Ja mit ES, 2=Ja ohne ES, 3=Nein weiterbauen) -> Nein, weiterbauen" + }, + { + "typ": "step", + "wert": "Horizontal-VF", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "4", + "kommentar": "getstring Naechstes Element (1=GF-Bogen, 2=Neue Linie GF, 3=Neue Linie Ab/Auf VF, 4=Horizontal-VF, 5=Neue Linie bis Kettenende) -> Horizontal-VF" + }, + { + "typ": "point_rel", + "dL": 3256.853781, + "kommentar": "getpoint Endpunkt des horizontalen VF-Stuecks: dL=3256.9 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring GF-Verteilung (1=halbe Staustrecke am Ausgang, 2=alles am Einlauf) -> alles am Einlauf" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Vario-Kurve" + }, + { + "typ": "step", + "wert": "Vario-Kurve", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "int", + "wert": 90, + "kommentar": "getint Vario-Kurve Winkel (30/60/90 Grad, Wert direkt) -> 90 Grad" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Seite (1=links, 2=rechts) -> rechts" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Variante (1=aussen, 2=innen) -> innen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Horizontaler Foerderer" + }, + { + "typ": "point_rel", + "dL": 1434.100265, + "kommentar": "getpoint Endpunkt des horizontalen VF-Stuecks: dL=1434.1 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Vario-Kurve" + }, + { + "typ": "step", + "wert": "Vario-Kurve", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "int", + "wert": 90, + "kommentar": "getint Vario-Kurve Winkel (30/60/90 Grad, Wert direkt) -> 90 Grad" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Seite (1=links, 2=rechts) -> rechts" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Variante (1=aussen, 2=innen) -> innen" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Ja, nur Motorstation" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist das Kettenende? (1=Ja, Separator + ES-Element, 2=Nein, weiterbauen) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator an dieser Stelle einfuegen? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "step", + "wert": "Horizontal-VF", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "4", + "kommentar": "getstring Naechstes Element (1=GF-Bogen, 2=Neue Linie GF, 3=Neue Linie Ab/Auf VF, 4=Horizontal-VF, 5=Neue Linie bis Kettenende) -> Horizontal-VF" + }, + { + "typ": "point_rel", + "dL": 1210.083164, + "kommentar": "getpoint Endpunkt des horizontalen VF-Stuecks: dL=1210.1 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring GF-Verteilung (1=halbe Staustrecke am Ausgang, 2=alles am Einlauf) -> alles am Einlauf" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Vario-Kurve" + }, + { + "typ": "step", + "wert": "Vario-Kurve", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "int", + "wert": 30, + "kommentar": "getint Vario-Kurve Winkel (30/60/90 Grad, Wert direkt) -> 30 Grad" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Seite (1=links, 2=rechts) -> rechts" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Variante (1=aussen, 2=innen) -> innen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Horizontaler Foerderer" + }, + { + "typ": "point_rel", + "dL": 1830.286247, + "kommentar": "getpoint Endpunkt des horizontalen VF-Stuecks: dL=1830.3 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Vario-Kurve" + }, + { + "typ": "step", + "wert": "Vario-Kurve", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "int", + "wert": 30, + "kommentar": "getint Vario-Kurve Winkel (30/60/90 Grad, Wert direkt) -> 30 Grad" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Vario-Kurve Seite (1=links, 2=rechts) -> links" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Variante (1=aussen, 2=innen) -> innen" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Ja, nur Motorstation" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Ist das Kettenende? (1=Ja, Separator + ES-Element, 2=Nein, weiterbauen) -> Ja, Separator + ES-Element" + }, + { + "typ": "step", + "wert": "ES", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "90", + "kommentar": "getstring ES-Element Winkel (Wert direkt, 90 oder 30 Grad) -> 90 Grad" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring ES-Seite (1=links, 2=rechts) -> links" + }, + { + "test_id": "VF_hundm05_LZ_02", + "anlagetyp": "linienzug", "modus": 1, - "deltaL_mm": 3817.56, - "winkel_grad": 6.75, - "hz_grad": 107.51, - "start_mm": [223151.48, 152603.13, 2337.66], - "as_seite": "links", - "es_seite": "links", - "as_winkel": "90", - "es_winkel": "90", - "comment": "aus DXF: 3D_FF_Antrieb_rechts -> 3D_FF_Umlenk_rechts (Layer _3D_Fingerförderer / _3D_Fingerförderer), XY-Abstand 3817.56, Neigung 6.75 Grad; Gefaellestrecke, da fallend und Neigung >= 3 Grad. as_/es_seite und -winkel sind Defaults (im DXF nicht ableitbar)." + "anzahl_eingaben": 54, + "block": "VF_46", + "dxf_handle": "EFD4", + "startpunkt_mm": [8571.95068, 1276.061424, 2494.930831], + "rotation_grad": 0.0, + "beschreibung": "Linienzug (Modus 1), Start Z2495: GF-Linie 3149 mm / 3 Grad -> Horizontal-VF 1575 mm -> Vario-Kurve 90 rechts innen -> Vario-Kurve 90 rechts innen -> Linie bis Kettenende 1153 mm auf Z2154 -> ES 90 links", + "sivas_id": "0019", + "sivas_bezeichnung": "VarioFoerderer :5", + "planquadrat": "D/1", + "erwartung_hinweis": "Sivas-Export (EXPORTSIVAS), Strecke 0019: DeltaL_mm=1788, DeltaH_mm=220, Hoehe_Von_mm=2154, Hoehe_Bis_mm=2374, L_GF_m=2.727,0.4,0.418, L_VF_m=0.177,0.339,0.356,0.548, VF_Winkel=0,0,39,0, Anzahl_Separator=3", + "quelle": "INSERT EFD4 (Block VF_46) in polylines.dxf, XDATA-App SSG_VF_EDIT, Marker linienzug; im Journal aufgezeichneter Kettenstart: [17201.778323, 1205.360995, 2100.000000]" }, { - "test_id": "GERADE_HundM05_10", - "typ": "gerade", - "laenge": 11940.15, - "hz": 33.61, - "startpunkt": [225207.36, 149949.84, 0.0], - "endpunkt": [235151.11, 156559.61, -3.45], - "comment": "aus DXF: Antrieb FF -> Umlenkung FF (Layer z-3 / z-3), XY-Abstand 11940.15, Neigung 0.02 Grad; waagrechte Foerderstrecke (Hoehendifferenz 3.45 mm < 10 mm) - KEIN VarioFoerderer, da dessen Winkel-Solver eine Hoehendifferenz braucht." + "typ": "point_abs", + "wert": [8571.95068, 1276.061424, 2494.930831], + "kommentar": "getpoint Startpunkt der Kette (absoluter Weltpunkt, einziger Anker der ganzen Kette)" }, { - "test_id": "VF_HundM05_11", - "typ": "standard", - "richtung": "Auf", - "deltaL": 11769.21, - "deltaH": 5827.59, - "startpunkt": [225207.48, 149949.9, -3941.61], - "seite": "rechts", - "hz": 32.34, - "comment": "aus DXF: Antrieb FF -> 3D_FF_Umlenk_rechts (Layer z-3 / _3D_Fingerförderer), XY-Abstand 11769.21, Neigung 26.34 Grad; VarioFoerderer. 'winkel' bewusst nicht gesetzt - der Solver waehlt den kleinsten gueltigen Kandidaten. 'seite' ist Default (im DXF nicht ableitbar)." + "typ": "real", + "wert": 2494.930831, + "kommentar": "getreal Hoehe (Z) Startpunkt -> 2494.9 mm" }, { - "test_id": "VF_HundM05_12", - "typ": "standard", - "richtung": "Auf", - "deltaL": 3450.94, - "deltaH": 1885.98, - "startpunkt": [227298.92, 152942.18, 0.0], - "seite": "rechts", - "hz": 106.79, - "comment": "aus DXF: Antrieb FF -> 3D_FF_Umlenk_rechts (Layer z-3 / _3D_Fingerförderer), XY-Abstand 3450.94, Neigung 28.66 Grad; VarioFoerderer. 'winkel' bewusst nicht gesetzt - der Solver waehlt den kleinsten gueltigen Kandidaten. 'seite' ist Default (im DXF nicht ableitbar)." + "typ": "string", + "wert": "1", + "kommentar": "getstring AS-Element setzen? (1=Ja, 2=Nein) -> Ja" }, { - "test_id": "GF_HundM05_13", + "typ": "string", + "wert": "90", + "kommentar": "getstring AS-Element Winkel (Wert direkt, 90 oder 30 Grad) -> 90 Grad" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring AS-Seite (1=links, 2=rechts) -> links" + }, + { + "typ": "step", + "wert": "Linie-GF", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes Element am Kettenanfang (1=Neue Linie GF, 2=Neue Linie Ab/Auf VF, 3=Horizontal-VF, 4=Neue Linie bis Kettenende) -> Neue Linie: GF" + }, + { + "typ": "point_rel", + "dL": 3149.399248, + "hz": 90.0, + "kommentar": "getpoint Endpunkt der GF-Linie: dL=3149.4 mm, gesnappte Fahrtrichtung hz=90 Grad (nur beim ersten Segment der Kette journalisiert)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Gefaelle festlegen (1=ueber Zielhoehe, 2=ueber Neigungswinkel) -> ueber Neigungswinkel" + }, + { + "typ": "real", + "wert": 3.0, + "kommentar": "getreal Neigungswinkel -> 3.0 Grad" + }, + { + "typ": "string", + "wert": "3", + "kommentar": "getstring Ist das Kettenende? (1=Ja mit ES, 2=Ja ohne ES, 3=Nein weiterbauen) -> Nein, weiterbauen" + }, + { + "typ": "step", + "wert": "Horizontal-VF", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "4", + "kommentar": "getstring Naechstes Element (1=GF-Bogen, 2=Neue Linie GF, 3=Neue Linie Ab/Auf VF, 4=Horizontal-VF, 5=Neue Linie bis Kettenende) -> Horizontal-VF" + }, + { + "typ": "point_rel", + "dL": 1574.912802, + "kommentar": "getpoint Endpunkt des horizontalen VF-Stuecks: dL=1574.9 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring GF-Verteilung (1=halbe Staustrecke am Ausgang, 2=alles am Einlauf) -> alles am Einlauf" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Vario-Kurve" + }, + { + "typ": "step", + "wert": "Vario-Kurve", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "int", + "wert": 90, + "kommentar": "getint Vario-Kurve Winkel (30/60/90 Grad, Wert direkt) -> 90 Grad" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Seite (1=links, 2=rechts) -> rechts" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Variante (1=aussen, 2=innen) -> innen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Horizontaler Foerderer" + }, + { + "typ": "point_rel", + "dL": 338.966171, + "kommentar": "getpoint Endpunkt des horizontalen VF-Stuecks: dL=339.0 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Vario-Kurve" + }, + { + "typ": "step", + "wert": "Vario-Kurve", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "int", + "wert": 90, + "kommentar": "getint Vario-Kurve Winkel (30/60/90 Grad, Wert direkt) -> 90 Grad" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Seite (1=links, 2=rechts) -> rechts" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Variante (1=aussen, 2=innen) -> innen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "3", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Auf/Ab-Foerderer" + }, + { + "typ": "point_rel", + "dL": 4528.583638, + "kommentar": "getpoint Endpunkt des Auf/Ab-VF-Segments: dL=4528.6 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "real", + "wert": 2335.0, + "kommentar": "getreal Zielhoehe (Z) am Segmentende -> 2335.0 mm" + }, + { + "typ": "int", + "wert": 4, + "kommentar": "getint Winkelwahl aus den gueltigen Kandidaten (vfl-waehle-winkel) -> Option 4" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Horizontaler Foerderer" + }, + { + "typ": "point_rel", + "dL": 1443.864872, + "kommentar": "getpoint Endpunkt des horizontalen VF-Stuecks: dL=1443.9 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Ja, nur Motorstation" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist das Kettenende? (1=Ja, Separator + ES-Element, 2=Nein, weiterbauen) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator an dieser Stelle einfuegen? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "step", + "wert": "Linie", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "5", + "kommentar": "getstring Naechstes Element (1=GF-Bogen, 2=Neue Linie GF, 3=Neue Linie Ab/Auf VF, 4=Horizontal-VF, 5=Neue Linie bis Kettenende) -> Neue Linie bis Kettenende" + }, + { + "typ": "point_rel", + "dL": 1153.407829, + "kommentar": "getpoint Endpunkt der Linie bis Kettenende (Ziel-XY der Kette): dL=1153.4 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "real", + "wert": 2154.0, + "kommentar": "getreal Zielhoehe (Z) am Kettenende -> 2154.0 mm" + }, + { + "typ": "step", + "wert": "ES", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "90", + "kommentar": "getstring ES-Element Winkel (Wert direkt, 90 oder 30 Grad) -> 90 Grad" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring ES-Seite (1=links, 2=rechts) -> links" + }, + { + "test_id": "VF_hundm05_LZ_03", + "anlagetyp": "linienzug", "modus": 1, - "deltaL_mm": 4078.06, - "winkel_grad": 29.82, - "hz_grad": 104.2, - "start_mm": [227300.62, 152605.32, 2337.74], - "as_seite": "links", - "es_seite": "links", - "as_winkel": "90", - "es_winkel": "90", - "comment": "aus DXF: 3D_FF_Antrieb_rechts -> Umlenkung FF (Layer _3D_Fingerförderer / z-3), XY-Abstand 4078.06, Neigung 29.82 Grad; Gefaellestrecke, da fallend und Neigung >= 3 Grad. as_/es_seite und -winkel sind Defaults (im DXF nicht ableitbar)." + "anzahl_eingaben": 54, + "block": "VF_46", + "dxf_handle": "FA5A", + "startpunkt_mm": [12916.465798, 1276.061424, 2494.930831], + "rotation_grad": 0.0, + "beschreibung": "Linienzug (Modus 1), Start Z2495: GF-Linie 3149 mm / 3 Grad -> Horizontal-VF 1575 mm -> Vario-Kurve 90 rechts innen -> Vario-Kurve 90 rechts innen -> Linie bis Kettenende 1153 mm auf Z2154 -> ES 90 links", + "sivas_id": "0018", + "sivas_bezeichnung": "VarioFoerderer :4", + "planquadrat": "F/1", + "erwartung_hinweis": "Sivas-Export (EXPORTSIVAS), Strecke 0018: DeltaL_mm=1788, DeltaH_mm=220, Hoehe_Von_mm=2154, Hoehe_Bis_mm=2374, L_GF_m=2.727,0.4,0.418, L_VF_m=0.177,0.339,0.356,0.548, VF_Winkel=0,0,39,0, Anzahl_Separator=3", + "quelle": "INSERT FA5A (Block VF_46) in polylines.dxf, XDATA-App SSG_VF_EDIT, Marker linienzug; im Journal aufgezeichneter Kettenstart: [17201.778323, 1205.360995, 2100.000000]" }, { - "function": "insert", - "block": "Vario_Motorstation_500mm_rechts", - "x": 224346.52, - "y": 149549.9, - "z": 21677.04, - "rotation": 90.0, - "comment": "aus DXF: Block 'Antrieb FF', Layer 'z-3', auf Makro-Block 'Vario_Motorstation_500mm_rechts' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Seite=rechts - bei Bedarf hier im JSON korrigieren" + "typ": "point_abs", + "wert": [12916.465798, 1276.061424, 2494.930831], + "kommentar": "getpoint Startpunkt der Kette (absoluter Weltpunkt, einziger Anker der ganzen Kette)" }, { - "function": "insert", - "block": "Vario_Motorstation_500mm_rechts", - "x": 223998.19, - "y": 149549.84, - "z": 2384.8, - "rotation": 180.0, - "comment": "aus DXF: Block '3D_FF_Antrieb_rechts', Layer '_3D_Fingerförderer', auf Makro-Block 'Vario_Motorstation_500mm_rechts' abgebildet" + "typ": "real", + "wert": 2494.930831, + "kommentar": "getreal Hoehe (Z) Startpunkt -> 2494.9 mm" }, { - "function": "insert", - "block": "Vario_Motorstation_500mm_rechts", - "x": 224346.4, - "y": 149549.84, - "z": 0.0, - "rotation": 90.0, - "comment": "aus DXF: Block 'Antrieb FF', Layer 'z-3', auf Makro-Block 'Vario_Motorstation_500mm_rechts' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Seite=rechts - bei Bedarf hier im JSON korrigieren" + "typ": "string", + "wert": "1", + "kommentar": "getstring AS-Element setzen? (1=Ja, 2=Nein) -> Ja" }, { - "function": "insert", - "block": "Vario_Kurve_rechts_30_TEF_aussen", - "x": 219480.78, - "y": 153174.98, - "z": 2230.86, - "rotation": 270.0, - "comment": "aus DXF: Block '3D_FF_Bogen_außen_30°', Layer '_3D_Fingerförderer', auf Makro-Block 'Vario_Kurve_rechts_30_TEF_aussen' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Seite=rechts - bei Bedarf hier im JSON korrigieren" + "typ": "string", + "wert": "90", + "kommentar": "getstring AS-Element Winkel (Wert direkt, 90 oder 30 Grad) -> 90 Grad" }, { - "function": "insert", - "block": "Vario_Kurve_rechts_30_TEF_innen", - "x": 220438.78, - "y": 154981.88, - "z": 1726.02, - "rotation": 90.0, - "comment": "aus DXF: Block '3D_FF_Bogen_innen_30°', Layer '_3D_Fingerförderer', auf Makro-Block 'Vario_Kurve_rechts_30_TEF_innen' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Seite=rechts - bei Bedarf hier im JSON korrigieren" + "typ": "string", + "wert": "1", + "kommentar": "getstring AS-Seite (1=links, 2=rechts) -> links" }, { - "function": "insert", - "block": "Vario_Kurve_rechts_30_TEF_aussen", - "x": 219535.71, - "y": 153172.45, - "z": 2349.22, - "rotation": 270.0, - "comment": "aus DXF: Block '3D_ILS_Bogen_30°', Layer '_3D_ILS_Bogen_30°', auf Makro-Block 'Vario_Kurve_rechts_30_TEF_aussen' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Lage=aussen, Seite=rechts - bei Bedarf hier im JSON korrigieren" + "typ": "step", + "wert": "Linie-GF", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" }, { - "function": "insert", - "block": "Vario_Kurve_rechts_30_TEF_aussen", - "x": 220500.01, - "y": 154973.55, - "z": 1850.0, - "rotation": 90.0, - "comment": "aus DXF: Block '3D_ILS_Bogen_30°', Layer '_3D_ILS_Bogen_30°', auf Makro-Block 'Vario_Kurve_rechts_30_TEF_aussen' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Lage=aussen, Seite=rechts - bei Bedarf hier im JSON korrigieren" + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes Element am Kettenanfang (1=Neue Linie GF, 2=Neue Linie Ab/Auf VF, 3=Horizontal-VF, 4=Neue Linie bis Kettenende) -> Neue Linie: GF" }, { - "function": "insert", - "block": "Vario_Kurve_rechts_30_TEF_aussen", - "x": 222776.13, - "y": 149549.84, - "z": 2335.65, - "rotation": 0.0, - "comment": "aus DXF: Block '3D_ILS_Bogen_30°', Layer '_3D_ILS_Bogen_30°', auf Makro-Block 'Vario_Kurve_rechts_30_TEF_aussen' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Lage=aussen, Seite=rechts - bei Bedarf hier im JSON korrigieren" + "typ": "point_rel", + "dL": 3149.399248, + "hz": 90.0, + "kommentar": "getpoint Endpunkt der GF-Linie: dL=3149.4 mm, gesnappte Fahrtrichtung hz=90 Grad (nur beim ersten Segment der Kette journalisiert)" }, { - "function": "insert", - "block": "Vario_Kurve_rechts_30_TEF_aussen", - "x": 223249.77, - "y": 149949.81, - "z": 2360.47, - "rotation": 0.0, - "comment": "aus DXF: Block '3D_ILS_Bogen_30°', Layer '_3D_ILS_Bogen_30°', auf Makro-Block 'Vario_Kurve_rechts_30_TEF_aussen' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Lage=aussen, Seite=rechts - bei Bedarf hier im JSON korrigieren" + "typ": "string", + "wert": "2", + "kommentar": "getstring Gefaelle festlegen (1=ueber Zielhoehe, 2=ueber Neigungswinkel) -> ueber Neigungswinkel" }, { - "function": "insert", - "block": "AS_Element_90_rechts", - "x": 217765.98, - "y": 151472.13, - "z": 2099.94, - "rotation": 0.0, - "comment": "aus DXF: Block 'Ausschleuselement_V3', Layer '_3D_ILS_AE', auf Makro-Block 'AS_Element_90_rechts' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Winkel=90, Seite=rechts - bei Bedarf hier im JSON korrigieren" + "typ": "real", + "wert": 3.0, + "kommentar": "getreal Neigungswinkel -> 3.0 Grad" }, { - "function": "insert", - "block": "AS_Element_90_rechts", - "x": 221440.62, - "y": 151470.0, - "z": 2067.0, - "rotation": 0.0, - "comment": "aus DXF: Block 'Ausschleuselement_V3', Layer '_3D_ILS_AE', auf Makro-Block 'AS_Element_90_rechts' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Winkel=90, Seite=rechts - bei Bedarf hier im JSON korrigieren" + "typ": "string", + "wert": "3", + "kommentar": "getstring Ist das Kettenende? (1=Ja mit ES, 2=Ja ohne ES, 3=Nein weiterbauen) -> Nein, weiterbauen" }, { - "function": "insert", - "block": "AS_Element_90_rechts", - "x": 225739.77, - "y": 151470.0, - "z": 2067.0, - "rotation": 0.0, - "comment": "aus DXF: Block 'Ausschleuselement_V3', Layer '_3D_ILS_AE', auf Makro-Block 'AS_Element_90_rechts' abgebildet; ACHTUNG geraten (im DXF nicht ableitbar): Winkel=90, Seite=rechts - bei Bedarf hier im JSON korrigieren" + "typ": "step", + "wert": "Horizontal-VF", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" }, { - "function": "insert", - "block": "S-LP", - "x": 216525.22, - "y": 150670.0, - "z": 0.0, - "rotation": 0.0, - "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "string", + "wert": "4", + "kommentar": "getstring Naechstes Element (1=GF-Bogen, 2=Neue Linie GF, 3=Neue Linie Ab/Auf VF, 4=Horizontal-VF, 5=Neue Linie bis Kettenende) -> Horizontal-VF" }, { - "function": "insert", - "block": "S-LP", - "x": 217622.88, - "y": 151470.0, - "z": 0.0, - "rotation": 180.0, - "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "point_rel", + "dL": 1574.912802, + "kommentar": "getpoint Endpunkt des horizontalen VF-Stuecks: dL=1574.9 mm (Fahrtrichtung vom Vorgaenger geerbt)" }, { - "function": "insert", - "block": "S-LP", - "x": 221298.51, - "y": 151470.0, - "z": 0.0, - "rotation": 180.0, - "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "string", + "wert": "2", + "kommentar": "getstring GF-Verteilung (1=halbe Staustrecke am Ausgang, 2=alles am Einlauf) -> alles am Einlauf" }, { - "function": "insert", - "block": "S-LP", - "x": 224908.15, - "y": 150670.0, - "z": 0.0, - "rotation": 0.0, - "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" }, { - "function": "insert", - "block": "S-LP", - "x": 225508.15, - "y": 150670.0, - "z": 0.0, - "rotation": 0.0, - "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" }, { - "function": "insert", - "block": "S-LP", - "x": 225597.66, - "y": 151470.0, - "z": 0.0, - "rotation": 180.0, - "comment": "aus DXF: Block 'S-LP', Layer '5SEP-LP'; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" }, { - "function": "insert", - "block": "S-SP", - "x": 218325.21, - "y": 154009.54, - "z": 0.0, - "rotation": 270.0, - "comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Vario-Kurve" }, { - "function": "insert", - "block": "S-SP", - "x": 219542.38, - "y": 151961.76, - "z": 0.0, - "rotation": 90.0, - "comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "step", + "wert": "Vario-Kurve", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" }, { - "function": "insert", - "block": "S-SP", - "x": 220498.95, - "y": 155645.73, - "z": 0.09, - "rotation": 90.0, - "comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "int", + "wert": 90, + "kommentar": "getint Vario-Kurve Winkel (30/60/90 Grad, Wert direkt) -> 90 Grad" }, { - "function": "insert", - "block": "S-SP", - "x": 220498.95, - "y": 155953.39, - "z": 0.09, - "rotation": 90.0, - "comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Seite (1=links, 2=rechts) -> rechts" }, { - "function": "insert", - "block": "S-SP", - "x": 222000.85, - "y": 155698.7, - "z": 0.0, - "rotation": 270.0, - "comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Variante (1=aussen, 2=innen) -> innen" }, { - "function": "insert", - "block": "S-SP", - "x": 222153.05, - "y": 149875.67, - "z": 0.0, - "rotation": 330.0, - "comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" }, { - "function": "insert", - "block": "S-SP", - "x": 223150.84, - "y": 151972.72, - "z": -7.54, - "rotation": 90.0, - "comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Horizontaler Foerderer" }, { - "function": "insert", - "block": "S-SP", - "x": 223617.86, - "y": 149949.84, - "z": 0.0, - "rotation": 0.0, - "comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "point_rel", + "dL": 338.966171, + "kommentar": "getpoint Endpunkt des horizontalen VF-Stuecks: dL=339.0 mm (Fahrtrichtung vom Vorgaenger geerbt)" }, { - "function": "insert", - "block": "S-SP", - "x": 226300.0, - "y": 155718.69, - "z": 0.0, - "rotation": 270.0, - "comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" }, { - "function": "insert", - "block": "S-SP", - "x": 227300.01, - "y": 151981.61, - "z": -7.54, - "rotation": 90.0, - "comment": "aus DXF: Block 'S-SP', Layer '5SEP-SP'; Block 'S-SP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" }, { - "function": "insert", - "block": "Scanner", - "x": 217622.88, - "y": 151190.0, - "z": 0.0, - "rotation": 270.0, - "comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet" + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" }, { - "function": "insert", - "block": "Scanner", - "x": 220218.95, - "y": 155609.84, - "z": 0.09, - "rotation": 180.0, - "comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet" + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Vario-Kurve" }, { - "function": "insert", - "block": "Scanner", - "x": 221298.51, - "y": 151190.0, - "z": 0.0, - "rotation": 270.0, - "comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet" + "typ": "step", + "wert": "Vario-Kurve", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" }, { - "function": "insert", - "block": "Scanner", - "x": 222013.05, - "y": 149633.18, - "z": 0.0, - "rotation": 240.0, - "comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet" + "typ": "int", + "wert": 90, + "kommentar": "getint Vario-Kurve Winkel (30/60/90 Grad, Wert direkt) -> 90 Grad" }, { - "function": "insert", - "block": "Scanner", - "x": 223617.86, - "y": 149669.84, - "z": 0.0, - "rotation": 270.0, - "comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet" + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Seite (1=links, 2=rechts) -> rechts" }, { - "function": "insert", - "block": "Scanner", - "x": 224882.1, - "y": 150392.28, - "z": 0.0, - "rotation": 270.0, - "comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet" + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Variante (1=aussen, 2=innen) -> innen" }, { - "function": "insert", - "block": "Scanner", - "x": 225597.66, - "y": 151190.0, - "z": 0.0, - "rotation": 270.0, - "comment": "aus DXF: Block 'SCAN', Layer 'ZZSCANNER', auf Makro-Block 'Scanner' abgebildet" + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" }, { - "function": "insert", - "block": "Separator_SP", - "x": 218326.39, - "y": 153912.84, - "z": 2066.06, - "rotation": 0.0, - "comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet" + "typ": "string", + "wert": "3", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Auf/Ab-Foerderer" }, { - "function": "insert", - "block": "Separator_SP", - "x": 219544.61, - "y": 152086.0, - "z": 2318.68, - "rotation": 180.29, - "comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet" + "typ": "point_rel", + "dL": 4528.583638, + "kommentar": "getpoint Endpunkt des Auf/Ab-VF-Segments: dL=4528.6 mm (Fahrtrichtung vom Vorgaenger geerbt)" }, { - "function": "insert", - "block": "Separator_SP", - "x": 220500.0, - "y": 155763.99, - "z": 1876.16, - "rotation": 180.0, - "comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet" + "typ": "real", + "wert": 2335.0, + "kommentar": "getreal Zielhoehe (Z) am Segmentende -> 2335.0 mm" }, { - "function": "insert", - "block": "Separator_SP", - "x": 222001.54, - "y": 155600.71, - "z": 1923.14, - "rotation": 359.97, - "comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet" + "typ": "int", + "wert": 4, + "kommentar": "getint Winkelwahl aus den gueltigen Kandidaten (vfl-waehle-winkel) -> Option 4" }, { - "function": "insert", - "block": "Separator_SP", - "x": 222001.69, - "y": 155900.21, - "z": 1905.78, - "rotation": 359.97, - "comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet" + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" }, { - "function": "insert", - "block": "Separator_SP", - "x": 222201.22, - "y": 149847.53, - "z": 2318.04, - "rotation": 60.49, - "comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet" + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Horizontaler Foerderer" }, { - "function": "insert", - "block": "Separator_SP", - "x": 223149.87, - "y": 152086.02, - "z": 2318.68, - "rotation": 180.29, - "comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet" + "typ": "point_rel", + "dL": 1443.864872, + "kommentar": "getpoint Endpunkt des horizontalen VF-Stuecks: dL=1443.9 mm (Fahrtrichtung vom Vorgaenger geerbt)" }, { - "function": "insert", - "block": "Separator_SP", - "x": 223747.95, - "y": 149950.46, - "z": 2386.66, - "rotation": 90.81, - "comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet" + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" }, { - "function": "insert", - "block": "Separator_SP", - "x": 226300.69, - "y": 155600.71, - "z": 1923.14, - "rotation": 359.97, - "comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet" + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" }, { - "function": "insert", - "block": "Separator_SP", - "x": 226300.84, - "y": 155900.21, - "z": 1905.78, - "rotation": 359.97, - "comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet" + "typ": "string", + "wert": "1", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Ja, nur Motorstation" }, { - "function": "insert", - "block": "Separator_SP", - "x": 227298.05, - "y": 152086.02, - "z": 2318.68, - "rotation": 180.29, - "comment": "aus DXF: Block 'Separator 200000070', Layer '_3D_Separator', auf Makro-Block 'Separator_SP' abgebildet" + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist das Kettenende? (1=Ja, Separator + ES-Element, 2=Nein, weiterbauen) -> Nein, weiterbauen" }, { - "function": "insert", - "block": "S-LP", - "x": 216630.12, - "y": 150670.0, - "z": 2167.0, - "rotation": 90.0, - "comment": "aus DXF: Block 'Separator LP 3D', Layer '_3D_Separator', auf Makro-Block 'S-LP' abgebildet; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator an dieser Stelle einfuegen? (1=Ja, 2=Nein) -> Nein" }, { - "function": "insert", - "block": "S-LP", - "x": 217517.98, - "y": 151470.0, - "z": 2167.0, - "rotation": 270.0, - "comment": "aus DXF: Block 'Separator LP 3D', Layer '_3D_Separator', auf Makro-Block 'S-LP' abgebildet; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "step", + "wert": "Linie", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" }, { - "function": "insert", - "block": "S-LP", - "x": 221193.61, - "y": 151470.0, - "z": 2167.0, - "rotation": 270.0, - "comment": "aus DXF: Block 'Separator LP 3D', Layer '_3D_Separator', auf Makro-Block 'S-LP' abgebildet; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "string", + "wert": "5", + "kommentar": "getstring Naechstes Element (1=GF-Bogen, 2=Neue Linie GF, 3=Neue Linie Ab/Auf VF, 4=Horizontal-VF, 5=Neue Linie bis Kettenende) -> Neue Linie bis Kettenende" }, { - "function": "insert", - "block": "S-LP", - "x": 225013.06, - "y": 150670.0, - "z": 2167.0, - "rotation": 90.0, - "comment": "aus DXF: Block 'Separator LP 3D', Layer '_3D_Separator', auf Makro-Block 'S-LP' abgebildet; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "point_rel", + "dL": 1153.407829, + "kommentar": "getpoint Endpunkt der Linie bis Kettenende (Ziel-XY der Kette): dL=1153.4 mm (Fahrtrichtung vom Vorgaenger geerbt)" }, { - "function": "insert", - "block": "S-LP", - "x": 225492.76, - "y": 151470.0, - "z": 2167.0, - "rotation": 270.0, - "comment": "aus DXF: Block 'Separator LP 3D', Layer '_3D_Separator', auf Makro-Block 'S-LP' abgebildet; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "real", + "wert": 2154.0, + "kommentar": "getreal Zielhoehe (Z) am Kettenende -> 2154.0 mm" }, { - "function": "insert", - "block": "S-LP", - "x": 225613.06, - "y": 150670.0, - "z": 2167.0, - "rotation": 90.0, - "comment": "aus DXF: Block 'Separator LP 3D', Layer '_3D_Separator', auf Makro-Block 'S-LP' abgebildet; Block 'S-LP' ist in data/ils/ NICHT vorhanden - erzeugt im Testlauf einen Platzhalter" + "typ": "step", + "wert": "ES", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "90", + "kommentar": "getstring ES-Element Winkel (Wert direkt, 90 oder 30 Grad) -> 90 Grad" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring ES-Seite (1=links, 2=rechts) -> links" + }, + { + "test_id": "VF_hundm05_LZ_04", + "anlagetyp": "linienzug", + "modus": 1, + "anzahl_eingaben": 54, + "block": "VF_46", + "dxf_handle": "E498", + "startpunkt_mm": [17201.778323, 1205.360995, 2154.0], + "rotation_grad": 0.0, + "beschreibung": "Linienzug (Modus 1), Start Z2154: GF-Linie 3149 mm / 3 Grad -> Horizontal-VF 1575 mm -> Vario-Kurve 90 rechts innen -> Vario-Kurve 90 rechts innen -> Linie bis Kettenende 1153 mm auf Z2154 -> ES 90 links", + "sivas_id": "0020", + "sivas_bezeichnung": "VarioFoerderer :6", + "planquadrat": "H/1", + "erwartung_hinweis": "Sivas-Export (EXPORTSIVAS), Strecke 0020: DeltaL_mm=1788, DeltaH_mm=220, Hoehe_Von_mm=2154, Hoehe_Bis_mm=2374, L_GF_m=2.727,0.4,0.418, L_VF_m=0.177,0.339,0.356,0.548, VF_Winkel=0,0,39,0, Anzahl_Separator=3", + "quelle": "INSERT E498 (Block VF_46) in polylines.dxf, XDATA-App SSG_VF_EDIT, Marker linienzug; im Journal aufgezeichneter Kettenstart: [17201.778323, 1205.360995, 2100.000000]" + }, + { + "typ": "point_abs", + "wert": [17201.778323, 1205.360995, 2154.0], + "kommentar": "getpoint Startpunkt der Kette (absoluter Weltpunkt, einziger Anker der ganzen Kette)" + }, + { + "typ": "real", + "wert": 2154.0, + "kommentar": "getreal Hoehe (Z) Startpunkt -> 2154.0 mm" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring AS-Element setzen? (1=Ja, 2=Nein) -> Ja" + }, + { + "typ": "string", + "wert": "90", + "kommentar": "getstring AS-Element Winkel (Wert direkt, 90 oder 30 Grad) -> 90 Grad" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring AS-Seite (1=links, 2=rechts) -> links" + }, + { + "typ": "step", + "wert": "Linie-GF", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes Element am Kettenanfang (1=Neue Linie GF, 2=Neue Linie Ab/Auf VF, 3=Horizontal-VF, 4=Neue Linie bis Kettenende) -> Neue Linie: GF" + }, + { + "typ": "point_rel", + "dL": 3149.399248, + "hz": 90.0, + "kommentar": "getpoint Endpunkt der GF-Linie: dL=3149.4 mm, gesnappte Fahrtrichtung hz=90 Grad (nur beim ersten Segment der Kette journalisiert)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Gefaelle festlegen (1=ueber Zielhoehe, 2=ueber Neigungswinkel) -> ueber Neigungswinkel" + }, + { + "typ": "real", + "wert": 3.0, + "kommentar": "getreal Neigungswinkel -> 3.0 Grad" + }, + { + "typ": "string", + "wert": "3", + "kommentar": "getstring Ist das Kettenende? (1=Ja mit ES, 2=Ja ohne ES, 3=Nein weiterbauen) -> Nein, weiterbauen" + }, + { + "typ": "step", + "wert": "Horizontal-VF", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "4", + "kommentar": "getstring Naechstes Element (1=GF-Bogen, 2=Neue Linie GF, 3=Neue Linie Ab/Auf VF, 4=Horizontal-VF, 5=Neue Linie bis Kettenende) -> Horizontal-VF" + }, + { + "typ": "point_rel", + "dL": 1574.912802, + "kommentar": "getpoint Endpunkt des horizontalen VF-Stuecks: dL=1574.9 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring GF-Verteilung (1=halbe Staustrecke am Ausgang, 2=alles am Einlauf) -> alles am Einlauf" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Vario-Kurve" + }, + { + "typ": "step", + "wert": "Vario-Kurve", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "int", + "wert": 90, + "kommentar": "getint Vario-Kurve Winkel (30/60/90 Grad, Wert direkt) -> 90 Grad" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Seite (1=links, 2=rechts) -> rechts" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Variante (1=aussen, 2=innen) -> innen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Horizontaler Foerderer" + }, + { + "typ": "point_rel", + "dL": 338.966171, + "kommentar": "getpoint Endpunkt des horizontalen VF-Stuecks: dL=339.0 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Vario-Kurve" + }, + { + "typ": "step", + "wert": "Vario-Kurve", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "int", + "wert": 90, + "kommentar": "getint Vario-Kurve Winkel (30/60/90 Grad, Wert direkt) -> 90 Grad" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Seite (1=links, 2=rechts) -> rechts" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Variante (1=aussen, 2=innen) -> innen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "3", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Auf/Ab-Foerderer" + }, + { + "typ": "point_rel", + "dL": 4528.583638, + "kommentar": "getpoint Endpunkt des Auf/Ab-VF-Segments: dL=4528.6 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "real", + "wert": 2335.0, + "kommentar": "getreal Zielhoehe (Z) am Segmentende -> 2335.0 mm" + }, + { + "typ": "int", + "wert": 4, + "kommentar": "getint Winkelwahl aus den gueltigen Kandidaten (vfl-waehle-winkel) -> Option 4" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Horizontaler Foerderer" + }, + { + "typ": "point_rel", + "dL": 1443.864872, + "kommentar": "getpoint Endpunkt des horizontalen VF-Stuecks: dL=1443.9 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Ja, nur Motorstation" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist das Kettenende? (1=Ja, Separator + ES-Element, 2=Nein, weiterbauen) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator an dieser Stelle einfuegen? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "step", + "wert": "Linie", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "5", + "kommentar": "getstring Naechstes Element (1=GF-Bogen, 2=Neue Linie GF, 3=Neue Linie Ab/Auf VF, 4=Horizontal-VF, 5=Neue Linie bis Kettenende) -> Neue Linie bis Kettenende" + }, + { + "typ": "point_rel", + "dL": 1153.407829, + "kommentar": "getpoint Endpunkt der Linie bis Kettenende (Ziel-XY der Kette): dL=1153.4 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "real", + "wert": 2154.0, + "kommentar": "getreal Zielhoehe (Z) am Kettenende -> 2154.0 mm" + }, + { + "typ": "step", + "wert": "ES", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "90", + "kommentar": "getstring ES-Element Winkel (Wert direkt, 90 oder 30 Grad) -> 90 Grad" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring ES-Seite (1=links, 2=rechts) -> links" + }, + { + "test_id": "VF_hundm05_LZ_05", + "anlagetyp": "linienzug", + "modus": 1, + "anzahl_eingaben": 54, + "block": "VF_46", + "dxf_handle": "104E0", + "startpunkt_mm": [21490.273781, 1195.739767, 2494.930831], + "rotation_grad": 0.0, + "beschreibung": "Linienzug (Modus 1), Start Z2495: GF-Linie 3149 mm / 3 Grad -> Horizontal-VF 1575 mm -> Vario-Kurve 90 rechts innen -> Vario-Kurve 90 rechts innen -> Linie bis Kettenende 1153 mm auf Z2154 -> ES 90 links", + "sivas_id": "0008", + "sivas_bezeichnung": "VarioFoerderer :3", + "planquadrat": "J/1", + "erwartung_hinweis": "Sivas-Export (EXPORTSIVAS), Strecke 0008: DeltaL_mm=1788, DeltaH_mm=220, Hoehe_Von_mm=2154, Hoehe_Bis_mm=2374, L_GF_m=2.727,0.4,0.418, L_VF_m=0.177,0.339,0.356,0.548, VF_Winkel=0,0,39,0, Anzahl_Separator=3", + "quelle": "INSERT 104E0 (Block VF_46) in polylines.dxf, XDATA-App SSG_VF_EDIT, Marker linienzug; im Journal aufgezeichneter Kettenstart: [17201.778323, 1205.360995, 2100.000000]" + }, + { + "typ": "point_abs", + "wert": [21490.273781, 1195.739767, 2494.930831], + "kommentar": "getpoint Startpunkt der Kette (absoluter Weltpunkt, einziger Anker der ganzen Kette)" + }, + { + "typ": "real", + "wert": 2494.930831, + "kommentar": "getreal Hoehe (Z) Startpunkt -> 2494.9 mm" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring AS-Element setzen? (1=Ja, 2=Nein) -> Ja" + }, + { + "typ": "string", + "wert": "90", + "kommentar": "getstring AS-Element Winkel (Wert direkt, 90 oder 30 Grad) -> 90 Grad" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring AS-Seite (1=links, 2=rechts) -> links" + }, + { + "typ": "step", + "wert": "Linie-GF", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes Element am Kettenanfang (1=Neue Linie GF, 2=Neue Linie Ab/Auf VF, 3=Horizontal-VF, 4=Neue Linie bis Kettenende) -> Neue Linie: GF" + }, + { + "typ": "point_rel", + "dL": 3149.399248, + "hz": 90.0, + "kommentar": "getpoint Endpunkt der GF-Linie: dL=3149.4 mm, gesnappte Fahrtrichtung hz=90 Grad (nur beim ersten Segment der Kette journalisiert)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Gefaelle festlegen (1=ueber Zielhoehe, 2=ueber Neigungswinkel) -> ueber Neigungswinkel" + }, + { + "typ": "real", + "wert": 3.0, + "kommentar": "getreal Neigungswinkel -> 3.0 Grad" + }, + { + "typ": "string", + "wert": "3", + "kommentar": "getstring Ist das Kettenende? (1=Ja mit ES, 2=Ja ohne ES, 3=Nein weiterbauen) -> Nein, weiterbauen" + }, + { + "typ": "step", + "wert": "Horizontal-VF", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "4", + "kommentar": "getstring Naechstes Element (1=GF-Bogen, 2=Neue Linie GF, 3=Neue Linie Ab/Auf VF, 4=Horizontal-VF, 5=Neue Linie bis Kettenende) -> Horizontal-VF" + }, + { + "typ": "point_rel", + "dL": 1574.912802, + "kommentar": "getpoint Endpunkt des horizontalen VF-Stuecks: dL=1574.9 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring GF-Verteilung (1=halbe Staustrecke am Ausgang, 2=alles am Einlauf) -> alles am Einlauf" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Vario-Kurve" + }, + { + "typ": "step", + "wert": "Vario-Kurve", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "int", + "wert": 90, + "kommentar": "getint Vario-Kurve Winkel (30/60/90 Grad, Wert direkt) -> 90 Grad" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Seite (1=links, 2=rechts) -> rechts" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Variante (1=aussen, 2=innen) -> innen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Horizontaler Foerderer" + }, + { + "typ": "point_rel", + "dL": 338.966171, + "kommentar": "getpoint Endpunkt des horizontalen VF-Stuecks: dL=339.0 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Vario-Kurve" + }, + { + "typ": "step", + "wert": "Vario-Kurve", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "int", + "wert": 90, + "kommentar": "getint Vario-Kurve Winkel (30/60/90 Grad, Wert direkt) -> 90 Grad" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Seite (1=links, 2=rechts) -> rechts" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Vario-Kurve Variante (1=aussen, 2=innen) -> innen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "3", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Auf/Ab-Foerderer" + }, + { + "typ": "point_rel", + "dL": 4528.583638, + "kommentar": "getpoint Endpunkt des Auf/Ab-VF-Segments: dL=4528.6 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "real", + "wert": 2335.0, + "kommentar": "getreal Zielhoehe (Z) am Segmentende -> 2335.0 mm" + }, + { + "typ": "int", + "wert": 4, + "kommentar": "getint Winkelwahl aus den gueltigen Kandidaten (vfl-waehle-winkel) -> Option 4" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Naechstes in der VF-Einheit (1=Horizontaler Foerderer, 2=Vario-Kurve, 3=Auf/Ab-Foerderer) -> Horizontaler Foerderer" + }, + { + "typ": "point_rel", + "dL": 1443.864872, + "kommentar": "getpoint Endpunkt des horizontalen VF-Stuecks: dL=1443.9 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator VOR dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator NACH dem horizontalen Stueck? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring Ist der Endpunkt der Foerderer? (1=Ja nur Motorstation, 2=Nein weiterbauen, 3=Ja Motorstation + Kettenende) -> Ja, nur Motorstation" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Ist das Kettenende? (1=Ja, Separator + ES-Element, 2=Nein, weiterbauen) -> Nein, weiterbauen" + }, + { + "typ": "string", + "wert": "2", + "kommentar": "getstring Separator an dieser Stelle einfuegen? (1=Ja, 2=Nein) -> Nein" + }, + { + "typ": "step", + "wert": "Linie", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "5", + "kommentar": "getstring Naechstes Element (1=GF-Bogen, 2=Neue Linie GF, 3=Neue Linie Ab/Auf VF, 4=Horizontal-VF, 5=Neue Linie bis Kettenende) -> Neue Linie bis Kettenende" + }, + { + "typ": "point_rel", + "dL": 1153.407829, + "kommentar": "getpoint Endpunkt der Linie bis Kettenende (Ziel-XY der Kette): dL=1153.4 mm (Fahrtrichtung vom Vorgaenger geerbt)" + }, + { + "typ": "real", + "wert": 2154.0, + "kommentar": "getreal Zielhoehe (Z) am Kettenende -> 2154.0 mm" + }, + { + "typ": "step", + "wert": "ES", + "kommentar": "Glied-Marker (vfl-journal-mark) - beim Replay uebersprungen, Label = gewaehlter Segmenttyp" + }, + { + "typ": "string", + "wert": "90", + "kommentar": "getstring ES-Element Winkel (Wert direkt, 90 oder 30 Grad) -> 90 Grad" + }, + { + "typ": "string", + "wert": "1", + "kommentar": "getstring ES-Seite (1=links, 2=rechts) -> links" } ] diff --git a/tests/testdata/object_data.md b/tests/testdata/object_data.md index 3b8ce9e..74b20b6 100644 --- a/tests/testdata/object_data.md +++ b/tests/testdata/object_data.md @@ -401,6 +401,36 @@ JSON (Prompt-Protokoll, Auszug): ] ``` +Dasselbe Protokoll steht produktionsseitig als **XDATA am fertigen +`VF_n`-Block** (App `SSG_VF_EDIT`, Marker `"linienzug"`, Serialisierung +`vfl-entry->string`): jede Eingabe wird beim Bau journalisiert und ist damit +wiederabspielbar (`vfl-journal-replay-start` + `vf-linienzug-modus`, genutzt +vom Doppelklick-Editieren). Ein Journal aus einer echten Zeichnung laesst +sich darum 1:1 in dieses Schema zuruecklesen — genau das tut +`lib/vf_journal_export.py` (siehe Abschnitt 8, `hundm05.json`). Zwei +Unterschiede zum handgeschriebenen `linienzug_tests.json`: + +- Zusaetzlicher Eintragstyp `step` (Glied-Marker aus `vfl-journal-mark`, + Label = gewaehlter Segmenttyp). Beim Replay uebersprungen + (`vfl-replay-pop`), fuer den Sektions-/Glied-Edit aber der Anker. +- `hz` steht nur beim **ersten** Segment der Kette in einem `point_rel` + (`vfl-in-abstand` journalisiert die gesnappte Richtung nur bei freier + Richtungswahl); jedes weitere Segment erbt die Richtung des Vorgaengers + und traegt nur `dL`. Winkel von AS/ES stehen als **Wert** (`"90"`/`"30"`), + Seiten/Optionen als **Menue-Index** (`"1"`/`"2"`). + +**Headless-Riegel (Stufe 0, siehe `doc/TODO-plan-vf-interactive.md`)**: der +Replay allein garantierte noch keinen prompt-freien Lauf - lief die Queue +mitten im Bau leer (Datenquelle passt nicht zum Ablauf), fielen die +`vfl-in-*`-Wrapper **stillschweigend** auf Live-Eingabe zurueck und der Lauf +blieb an einem `getpoint` haengen. Mit `*vfl-headless*` = T ist das jetzt ein +harter Abbruch mit Fundstelle (`vfl-headless-abbruch` -> `*vfl-headless-fehler*` +nennt Art der Eingabe, Glied-Nummer und Eingabe-Nummer). Die Meldungen der +geometrisch abgewiesenen Sektionen laufen ueber `vfl-meldung` statt `alert` und +stehen in `*vfl-meldungen*`, blockieren also nichts mehr. `tests/test_hundm05.lsp` +schaltet beides ein; die Diagnose landet im Ergebnis-JSON +(`headless_fehler`, `meldungen`, `prompts`). + ### 3.4 Vario_Kette_Merge — kein Neubau `c:Vario_Kette_Merge`: benoetigt nur **einen** Start-Entity-Pick (`entsel`), @@ -572,9 +602,9 @@ unbaubar. `lib/dxf_scan_components.py` trennt solche Paare darum ueber gibt den **Ausgangspunkt** zurueck, nicht die Entity — ein Treiber holt den Block ueber `(entlast)`. Bei fehlender Blockdatei ruft sie `(exit)` auf und wuerde einen Testlauf abbrechen, darum vorher mit `ssg-ils-block-laden` -pruefen (so macht es `hundm05:build-gerade` in `tests/test_hundm05.lsp`). +pruefen. -Vollstaendiges JSON (wie in `hundm05.json`): +Vollstaendiges JSON: ```json { "test_id": "GERADE_HundM05_08", @@ -585,6 +615,9 @@ Vollstaendiges JSON (wie in `hundm05.json`): "endpunkt": [220499.01, 154926.53, 0.0] } ``` +Einen Treiber dafuer gibt es derzeit nicht mehr: `tests/test_hundm05.lsp` +spielt seit dem Umbau auf die Linienzug-Journale nur noch Ketten ab (siehe +Abschnitt 8); der fruehere `hundm05:build-gerade` steht in der Git-Historie. --- @@ -606,3 +639,62 @@ Vollstaendiges JSON (wie in `hundm05.json`): | Omniflo TEF/Zubehoer | `omni:insert-dxf` (fester Sivasnr) | Position/Hoehe/Rotation ja, Punkt-Eingabe interaktiv | | Omniflo TV_*/APBW_* | - | **nein** (nicht implementiert, DUMMY) | | Separator/Scanner | `ils-insert-sensor` | **nein** (Schleife mit `getpoint`/`pause`) | + +## 8. HundM05 — die 5 Linienzuege am Kreisel (`hundm05.json`) + +`tests/testdata/hundm05.json` enthaelt die **Eingabe-Journale der 5 +VarioFoerderer-Linienzuege** aus `data/polylines.dxf` (die Anlage HundM, ein +Kreisel mit fuenf daran haengenden Ketten). Quelle ist nicht die gezeichnete +Geometrie, sondern die XDATA `SSG_VF_EDIT` (Marker `"linienzug"`) an den +`VF_n`-Bloecken — also genau die Werte, mit denen die Ketten gebaut wurden. +Schema je Eintrag: Kopffelder (`test_id`, `block`, `dxf_handle`, +`startpunkt_mm`, `rotation_grad`, `sivas_id`, `beschreibung`, +`erwartung_hinweis`, `quelle`) plus die Prompt-Sequenz in `eingaben` — Format +wie `linienzug_tests.json`, siehe Abschnitt 3.3 (inkl. `step`-Marker). + +| test_id | Block | Sivas | Startpunkt (mm) | Kette | +|---|---|---|---|---| +| VF_hundm05_LZ_01 | VF_87 | 0025 | 4912 / 1231 / 2154 | der komplizierte links: 2x Horizontal-VF, 4 Vario-Kurven (90/90/30/30) | +| VF_hundm05_LZ_02 | VF_46 | 0019 | 8572 / 1276 / 2495 | Standardkette | +| VF_hundm05_LZ_03 | VF_46 | 0018 | 12916 / 1276 / 2495 | Standardkette | +| VF_hundm05_LZ_04 | VF_46 | 0020 | 17202 / 1205 / 2154 | Standardkette (die zuerst gebaute) | +| VF_hundm05_LZ_05 | VF_46 | 0008 | 21490 / 1196 / 2495 | Standardkette | + +Die vier `VF_46`-Bloecke sind **Kopien EINER** gebauten Kette: sie referenzieren +dieselbe Blockdefinition und tragen darum Wort fuer Wort dasselbe Journal, +inklusive des Startpunkts der zuerst gebauten Kette (17201.78/1205.36/2100 mit +Starthoehe 2154). Im JSON ist dieser eine absolute Eintrag je Instanz auf ihren +echten Blockeinfuegepunkt gezogen (alle weiteren Eintraege sind relativ), damit +jeder Testfall fuer sich abspielbar ist. Achtung bei den Kopien: ihre +Block-Attribute (`HOEHE_VON_mm` = 2154, so auch im Sivas-Export) stammen aus +dem Original und sind nach dem Verschieben veraltet — die drei Kopien liegen +tatsaechlich 340.93 mm hoeher (belegt durch ihre Separator-Z-Werte im +Sivas-Export: 2575.43 statt 2234.50). + +Neu erzeugen: + +``` +python lib/vf_journal_export.py data/polylines.dxf \ + tests/testdata/hundm05.json --csv results/HundM_export.csv +``` + +Die frueheren Inhalte (Kreisel/Gefaellestrecke/Sensor-Eintraege aus dem +Fenster 215000/148000..250000/161000 von `results/ST500592_05.dxf`, erzeugt mit +`lib/dxf_scan_components.py`) stehen in der Git-Historie dieser Datei. + +**Aufbau flach, nicht verschachtelt**: `ssg-load-json`/`ssg-parse-json-array` +(`Lisp/ssg_core.lsp`) liest die Datei ZEILENWEISE und behandelt jedes `{` als +Objektanfang — Verschachtelung (z.B. `"eingaben": [ {...} ]`) wuerde die +Kopf-Objekte verschlucken. Darum steht je Kette ein Kopf-Objekt (traegt +`test_id`) und danach ihre Eingaben (tragen `typ`), wie in +`linienzug_tests.json`. Der Anlagetyp im Kopf heisst `anlagetyp` und NICHT +`typ`, damit Kopf und Eingabe unterscheidbar bleiben. + +Abgespielt wird das per `tests/test_hundm05.lsp` (`TEST_HUNDM05`, +`TEST_HUNDM05_2D`/`_3D`): der Treiber gruppiert die Objekte +(`hundm05:gruppiere`), baut daraus das Vorwaerts-Journal +(`hundm05:eintrag->journal`) und spielt es je Kette ueber +`vfl-journal-replay-start` + `vf-linienzug-modus` ab — denselben Weg nimmt die +Produktion beim Editieren/Konvertieren (`vfl-konvertiere-ent`). Bewusst KEINE +Eingabe-Mocks wie in `test_linienzug.lsp`: ein Mock muesste zu jedem Segment +die Fahrtrichtung kennen, die im Journal nur beim ersten Segment steht.