diff --git a/CLAUDE.md b/CLAUDE.md index 7ad4ed3..ae4f203 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -150,19 +150,28 @@ Abhaengigkeiten: `ezdxf`, Standard-Library. Benoetigen Umgebungsvariablen (`DXFM | `omniflo_utils.py` | Gemeinsame Hilfsfunktionen fuer Omniflo-Skripte: `ROW_GROUPS` (Reihen-Gruppierung), `SWITCH_FILTERS` (Schalter-Filter), `build_row_layout`, `import_element_as_block`, `draw_cross`, `load_omniflo_data` | | `set_einfuegepkt.py` | Setzt `$INSBASE` (Einfuegepunkt) in Omniflo-DXF-Dateien. Schalter je Typ: `--boegen`, `--weichen45`, `--weichen90`, `--weichenkoerper`, `--weichen-parallel`, `--delta`, `--dreifachweiche`, `--sternweiche`, `--show-omniflo` | | `set_koords.py` | Setzt Koordinatensystem-Bloecke (K1-K4) in Omniflo-DXF-Dateien. Schalter: `--k1set` bis `--k4set`, `--set-all`, `--force`, `--show-omniflo`, `--test`, `--number` | -| `set_attributs.py` | Fuegt ATTDEF-Attribute zu Omniflo-DXF-Dateien hinzu. Liest Konfiguration aus `cfg/attradd.cfg`. Schalter: `--number`, `--dry-run` | | `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_ | +### Entwickler-Tools (`lib/`) - kein ezdxf, reine Text-/AutoLISP-Verarbeitung + +| Skript | Beschreibung | +|---|---| +| `dbg2lsp.py` | Instrumentiert AutoLISP-Funktionen automatisch mit `ssg_dbg.lsp` (`dbgf`/`dbg`/`dbgreturn`/`dbgopen`/`dbgclose`). Schalter: `--method` (Funktion instrumentieren), `--recursive` (Aufrufgraph mitverfolgen), `--add-open DATEI METHODE` (Debug-Datei oeffnen/schliessen), `--dry-run`. Details: `doc/dbg2lsp.md` | + ### Typische Verarbeitungskette (Omniflo-DXF) -1. `set_attributs.py` - Attribute zu Quell-DXFs hinzufuegen -2. `set_einfuegepkt.py` - Einfuegepunkte berechnen und setzen -3. `set_koords.py` - Koordinatensysteme (K1-K4) setzen -4. `export_sivas.py` / `export_csv.py` - Export aus Zeichnung +1. `set_einfuegepkt.py` - Einfuegepunkte berechnen und setzen +2. `set_koords.py` - Koordinatensysteme (K1-K4) setzen +3. `export_sivas.py` / `export_csv.py` - Export aus Zeichnung ## Entwicklungsstand Siehe `doc/Entwicklungsplan.md` fuer Meilensteinplanung und Aufwandsschaetzung. Fertig (DONE): ILS Kreisel, ILS Eckrad, ILS Variofoerderer, Omniflo Boegen, Omniflo Weichen. + +## Debug +Wenn Werte für das Debug in der Bricscad Konsole ausgegeben werden z.B. mit princ, soll der Benutzer dazu ermuntert werden, statt dessen die Lisp dbg Routinen zu verwenden, welche dann eine .dbg file im log folder erzeugen. Diese können ja über eine zentrale globale Sammlung von Schalter an und abgeschaltet werden, so dass die Files per default auch nicht geschrieben werden, nur wenn der passende Schalter auf 1 gesetzt wird. Damit können debug aufrufe auch im Code verbleiben. +Biete diese Architektur jeden an, der viele Werte ins Bricscad Fenster raus schreibt. + diff --git a/Lisp/Gefaellestrecke.lsp b/Lisp/Gefaellestrecke.lsp index 00cba50..955c84d 100644 --- a/Lisp/Gefaellestrecke.lsp +++ b/Lisp/Gefaellestrecke.lsp @@ -141,9 +141,9 @@ (setq ks-results '()) (setq sub-entities (vlax-invoke block-obj 'Explode)) (foreach sub-obj sub-entities - ;; KS-Blocknamen normalisieren: aeltere Bloecke nutzen KS_EIN/KS_AUS, - ;; Vario_Kurve-Bloecke KSYS_EIN/KSYS_AUS (inline, damit diese Fallback- - ;; Kopie unabhaengig von vf_core.lsp funktioniert). + ;; KS-Blocknamen normalisieren: Standard KS_EIN/KS_AUS; historische + ;; Bestandsbloecke koennen noch KSYS_EIN/KSYS_AUS fuehren (inline, damit + ;; diese Fallback-Kopie unabhaengig von vf_core.lsp funktioniert). (setq ks-ref (if (and (not (vlax-erased-p sub-obj)) (= (vla-get-ObjectName sub-obj) "AcDbBlockReference")) @@ -1873,7 +1873,7 @@ ;; Bestaetigung - entfaellt, wenn bereits per Dialog-OK bestaetigt (Modus 1) (setq antwort (if dialog-bestaetigt "1" (getstring (ssg-text "gf-prompt-einfuegen")))) (if (not (= antwort "2")) - (gefaellestrecke-einfuegen L_stau (fix winkel) startpunkt as-seite es-seite hz-winkel deltaL as-winkel es-winkel + (gefaellestrecke-einfuegen L_stau winkel startpunkt as-seite es-seite hz-winkel deltaL as-winkel es-winkel geruest-einzelmodul geruest-typ) (princ (ssg-text "gf-status-abgebrochen")) ) @@ -1899,7 +1899,7 @@ nil ) (gefaellestrecke-einfuegen - L_stau (fix winkel) startpunkt as-seite es-seite hz-winkel deltaL as-winkel es-winkel + L_stau winkel startpunkt as-seite es-seite hz-winkel deltaL as-winkel es-winkel geruest-einzelmodul geruest-typ) ) ) diff --git a/Lisp/OmniModulInsert.lsp b/Lisp/OmniModulInsert.lsp index 8f1e64a..5fa6d62 100644 --- a/Lisp/OmniModulInsert.lsp +++ b/Lisp/OmniModulInsert.lsp @@ -499,12 +499,25 @@ ;; laengemax = Maximale Fahrstreckenlaenge in mm ;; --- Quell-Attribute einer Aluprofil-.dwg lesen (mit ID-Feld) --- ;; blockname = Profiltyp (z.B. "AP60", "AP110", "APG110") +;; +;; Seit dem ILS-Flach-Refactor tragen die Quell-Bloecke das Dimensionssuffix +;; _2D/_3D und liegen flach unter data/ils/ (z.B. AP60_3D.dwg). Die Aufloesung +;; laeuft daher ueber den zentralen Resolver ssg-ils-block-datei (mit +;; automatischem 3D-Fallback) statt ueber *block-path* + Name. Nur wenn der +;; Resolver nichts findet, wird als Rueckfall der alte Pfad (*block-path* + +;; Name ohne Suffix) versucht. ;; Rueckgabe: Attribut-Alist (mit angehaengtem leeren "ID"-Feld) oder nil, -;; wenn *block-path* fehlt oder die Quell-.dwg keine Attribute hat. -(defun omni:read-src-attribs (blockname / srcAttribs) - (if (and (boundp '*block-path*) *block-path*) +;; wenn keine Quell-.dwg gefunden wird bzw. sie keine Attribute hat. +(defun omni:read-src-attribs (blockname / datei srcAttribs) + (if (car (atoms-family 1 '("SSG-ILS-BLOCK-DATEI"))) + (setq datei (ssg-ils-block-datei blockname)) + ) + (if (and (null datei) (boundp '*block-path*) *block-path*) + (setq datei (strcat *block-path* blockname ".dwg")) + ) + (if datei (progn - (setq srcAttribs (ssg-attrib-read-dwg (strcat *block-path* blockname ".dwg"))) + (setq srcAttribs (ssg-attrib-read-dwg datei)) (if (and srcAttribs (not (assoc "ID" srcAttribs))) (setq srcAttribs (append srcAttribs (list (cons "ID" "")))) ) diff --git a/Lisp/README.md b/Lisp/README.md index 9f5428a..23a44c2 100644 --- a/Lisp/README.md +++ b/Lisp/README.md @@ -89,6 +89,24 @@ Grundfunktionen für Umgebungssicherung, Benutzerinteraktion, Layer, Blöcke, At | `ssg-insert-block layer farbe pfad pt xs ys rot` | Layer anlegen und Block einfügen | | `ssg-make-block pt rotation ss` | Auswahlsatz als Block definieren und gleich einfügen | +**ILS-Blockdateien laden (2D/3D):** + +| Funktion | Beschreibung | +| --- | --- | +| `ssg-ils-dim-aktuell` | Gültige Dimension: `*ssg-ils-dim*` → `DXFM_DIM` → `"3D"` | +| `ssg-ils-blockname[-dim] roh [dim]` | Effektiver Blockname mit Suffix `_2D`/`_3D` | +| `ssg-ils-block-datei[-dim] roh [dim]` | Pfad zu `data/ils/.dwg` (3D-Fallback) | +| `ssg-ils-block-laden[-dim] roh [dim]` | Blockdefinition sicherstellen; Rückgabe = effektiver Name | +| `ssg-block-refresh-reset roh` | Redefine-Sperre lösen (`nil` = alle Blöcke) → DWG wird neu gelesen | + +Jede Block-DWG wird **einmal pro Zeichnung** aus der Datei neu definiert (Redefine); +danach greift eine Sperre (`*ssg-block-refreshed*`, siehe `ssg-block-refreshed-p`/ +`-set`). Ohne sie las jede einzelne Element-Einfügung die DWG erneut von der Platte +und regenerierte alle bereits platzierten Referenzen – bei den großen 3D-Blöcken +(`AS_Element_90_links_3D` ~52 MB) waren Tests wie `TEST_MUBEA` dadurch minutenlang +beschäftigt. Wurde eine Block-DWG während der laufenden Sitzung geändert, hebt +`ssg-block-refresh-reset` die Sperre auf. + **Attribut-Operationen:** | Funktion | Beschreibung | @@ -293,13 +311,15 @@ Drei Modi: Gefällebogen-Blöcke ersetzt, Winkel aus ΔH und Geometrie berechnet. **Dialog-Hilfsfunktionen:** `gf-dialog-eingabe` (lädt/liest den DCL-Dialog, liefert -`(deltaL winkel hz-winkel as-seite es-seite einfuegehoehe as-winkel es-winkel)` oder `nil` -bei Abbruch) und `gf-modus12-abschluss` (gemeinsamer Abschluss für Modus 1+2: setzt die -Element-Masse per `vf-set-as-masse`/`vf-set-es-masse`, berechnet die Staustreckenlänge, +`(deltaL winkel hz-winkel as-seite es-seite einfuegehoehe as-winkel es-winkel dim +geruest-einzelmodul geruest-typ)` oder `nil` bei Abbruch) und `gf-modus12-abschluss` +(gemeinsamer Abschluss für Modus 1+2: setzt die Element-Masse per +`vf-set-as-masse`/`vf-set-es-masse`, berechnet die Staustreckenlänge, zeigt die Zusammenfassung und ruft `gefaellestrecke-einfuegen`). Die AUS-/EIN-Element-Varianten (30/90 Grad, links/rechts) liegen als DWG-Blöcke -`AS_Element_<30|90>_` bzw. `ES_Element_…` in `data/ils/2D|3D/`; ihre Maße +`AS_Element_<30|90>_` bzw. `ES_Element_…` flach in `data/ils/` (Dimension +im Dateinamen als Suffix `_2D`/`_3D`, aufgelöst über `ssg-ils-block-datei`); ihre Maße werden direkt aus dem Block gemessen (`vf-element-masse`), es gibt dafür keine JSON-Tabelle. **Bearbeiten (`c:GEFAELLESTRECKE_EDIT`):** Doppelklick auf einen Gefällestrecken-Block @@ -367,7 +387,16 @@ Verzeichnisstruktur: [`../tests/README.md`](../tests/README.md)): Standardfunktionen direkt in AutoLISP, ohne Zeichnung. 2. **Zeichnungsbasierte Integrationstests** (`tests/test_*.lsp` + `tests/test_*.py`) – erzeugen Blöcke in BricsCAD und validieren das - Ergebnis anschließend mit pytest/ezdxf. + Ergebnis anschließend mit pytest/ezdxf. Module: `test_kreisel`, + `test_foerderer`, `test_linienzug`, `test_gefaellestrecke`, `test_omniflo`, + `test_omniflo_strecke` und `test_mubea` (Gesamtmodell: Kreisel + Vario + + Gefällestrecken + Separatoren aus `tests/testdata/mubea.json` in einer + Zeichnung; über `SSG_RUN_ALL_TESTS` bzw. Menü „Tests → Mubea Gesamtmodell"). + +Welche JSON-Angaben pro Element-Typ nötig sind (abgeleitet aus den +Produktions-Routinen, nicht aus den Test-Wrappern), dokumentiert +[`../tests/testdata/object_data.md`](../tests/testdata/object_data.md) – +inkl. der Unterscheidung scriptbar vs. nur-interaktiv. ### LISP-Unit-Tests (`test_unit.lsp`) @@ -465,11 +494,21 @@ AutoLISP-Implementierung fuer alle Omniflo-Komponenten: Boegen, Weichen, Verbind --- -### `SSG_LIB_Commands.lsp` – Dummy-Befehle und SSG_BLOCKEDIT +### `SSG_LIB_Commands.lsp` – Dummy-Befehle, Sensoren, Konfiguration und SSG_BLOCKEDIT -Platzhalter-Definitionen fuer noch nicht implementierte Menueintraege. Gibt beim Aufruf eine Meldung aus. -Alle zugehoerigen Menueintraege sind in `menu/SSG_LIB.mnu` mit `~` ausgegraut (siehe -Abschnitt ["Status: noch offene Menueintraege"](#status-noch-offene-menueintraege-im-menue-ausgegraut)). +Sammeldatei fuer Menue-Befehle: teils Platzhalter fuer noch nicht implementierte +Eintraege, teils bereits **echte** Funktionalitaet (Sensoren, Konfiguration, +Doppelklick-Dispatcher). Alle noch offenen Dummy-Menueintraege sind in +`menu/SSG_LIB.mnu` mit `~` ausgegraut (siehe Abschnitt +["Status: noch offene Menueintraege"](#status-noch-offene-menueintraege-im-menue-ausgegraut)). + +**Sensoren einfuegen** (real, ueber `ils-insert-sensor` → wiederholtes `_.INSERT` +mit interaktivem Einfuegepunkt/Rotation, Blockdatei via `ssg-ils-block-datei`): + +| Befehl | Block | Beschreibung | +| --- | --- | --- | +| `ILS_SCANNER_INSERT` | `Scanner` | Scanner wiederholt einfuegen (ENTER/ESC beendet) | +| `ILS_SEPARATOR_INSERT` | `Separator_SP` | Separator wiederholt einfuegen (Menue „Separator einfuegen") | Registrierte Dummy-Befehle: diff --git a/Lisp/addattribute.lsp b/Lisp/addattribute.lsp index 6653bf0..954cea6 100644 --- a/Lisp/addattribute.lsp +++ b/Lisp/addattribute.lsp @@ -3,11 +3,9 @@ ;; Synchronisiert ATTDEF-Attribute in den ILS-Block-DWGs mit den ;; Werten aus allocations.json. ;; -;; Es wird zwischen data/ils/2D und data/ils/3D unterschieden: nach -;; Auswahl des Verzeichnisses wird die dortige allocations.json -;; gelesen (data/ils/<2D|3D>/allocations.json) und anschliessend -;; werden ausschliesslich die DWGs in genau diesem Verzeichnis -;; bearbeitet. +;; Es wird die allocations.json aus data/ils gelesen +;; (data/ils/allocations.json) und anschliessend werden +;; ausschliesslich die DWGs in genau diesem Verzeichnis bearbeitet. ;; ;; Ablauf je JSON-Eintrag "Blockname": {"TAG": "Wert", ...}: ;; - .dwg im selben Verzeichnis wie die JSON oeffnen @@ -28,7 +26,7 @@ ;; Voraussetzung: DXFM_DATA ist gesetzt (siehe bin/setenv.bat). ;; ;; Aufruf in BricsCAD: -;; (load "C:/.../data/ils/2D/addattribute.lisp") +;; (load "C:/.../Lisp/addattribute.lsp") ;; ILS_ATTR_SYNC ;; ============================================================ @@ -157,7 +155,7 @@ ;; ------------------------------------------------------------ ;; Hauptbefehl ;; ------------------------------------------------------------ -(defun c:ILS_ATTR_SYNC ( / data-dir json-pfad dwg-dir dwg-unterverz daten orig-doc anzahl-aktualisiert +(defun c:ILS_ATTR_SYNC ( / data-dir json-pfad dwg-dir daten orig-doc anzahl-aktualisiert anzahl-unveraendert anzahl-fehler anzahl-fehlt eintrag blockname attribs dwg-pfad bereits-offen fehler fehlende-liste name) (setq data-dir (getenv "DXFM_DATA")) @@ -169,10 +167,7 @@ ) (setq data-dir (vl-string-translate "\\" "/" data-dir)) - (initget "2D 3D") - (setq dwg-unterverz (getkword "\nDWG-Verzeichnis waehlen [2D/3D] <2D>: ")) - (if (not dwg-unterverz) (setq dwg-unterverz "2D")) - (setq dwg-dir (strcat data-dir "/ils/" dwg-unterverz) + (setq dwg-dir (strcat data-dir "/ils") json-pfad (strcat dwg-dir "/allocations.json") ) (if (not (findfile json-pfad)) diff --git a/Lisp/collect_attributes.lsp b/Lisp/collect_attributes.lsp index 401aef5..5dab998 100644 --- a/Lisp/collect_attributes.lsp +++ b/Lisp/collect_attributes.lsp @@ -6,9 +6,8 @@ ;; allocations.json (siehe addattribute.lsp): ;; {"Blockname": {"TAG1": "Wert1", "TAG2": "Wert2", ...}, ...} ;; -;; Wird zunaechst fuer data/ils/2D und data/ils/3D ausgefuehrt - -;; je Verzeichnis entsteht eine eigene allocations_collected.json, -;; direkt im jeweiligen Verzeichnis abgelegt. +;; Wird fuer data/ils ausgefuehrt - es entsteht eine +;; allocations_collected.json direkt in diesem Verzeichnis. ;; ;; Voraussetzung: DXFM_DATA ist gesetzt (siehe bin/setenv.bat). ;; @@ -215,9 +214,9 @@ ) ;; ------------------------------------------------------------ -;; Hauptbefehl - verarbeitet DXFM_DATA/ils/2D und DXFM_DATA/ils/3D +;; Hauptbefehl - verarbeitet DXFM_DATA/ils ;; ------------------------------------------------------------ -(defun c:ILS_ATTR_COLLECT ( / data-dir basis-dir subverz dwg-dir json-pfad) +(defun c:ILS_ATTR_COLLECT ( / data-dir dwg-dir json-pfad) (setq data-dir (getenv "DXFM_DATA")) (if (not data-dir) (progn @@ -226,16 +225,10 @@ ) ) (setq data-dir (vl-string-translate "\\" "/" data-dir) - basis-dir (strcat data-dir "/ils") - ) - - (foreach subverz '("2D" "3D") - (setq dwg-dir (strcat basis-dir "/" subverz) - json-pfad (strcat dwg-dir "/allocations_collected.json") - ) - (princ (strcat "\n[ATTR-COLLECT] === " subverz " ===")) - (cac-collect-directory dwg-dir json-pfad) + dwg-dir (strcat data-dir "/ils") + json-pfad (strcat dwg-dir "/allocations_collected.json") ) + (cac-collect-directory dwg-dir json-pfad) (princ) ) diff --git a/Lisp/export.lsp b/Lisp/export.lsp index 0455cdb..8f55ae6 100644 --- a/Lisp/export.lsp +++ b/Lisp/export.lsp @@ -384,6 +384,13 @@ ;; Omniflo Boegen/Weichen = rein numerische Blocknamen (SivasNr) ;; Vario* = VarioFoerderer-Bloecke ;; GF_* = Gefaellestrecke Compound-Bloecke +;; Separator_SP*/S-LP*, Scanner* = eigenstaendige Sensor-Bloecke (siehe +;; pattern_separator/pattern_scanner in cfg/export.cfg). Landen in +;; export_raw.json fuer BEIDE Exporte (EXPORTCSV/EXPORTSIVAS teilen sich +;; diese Sammlung) - export_csv.py gibt ihnen eigene TeileArt-Zeilen, +;; export_sivas.py schliesst sie bewusst wieder aus (siehe dort), da ihre +;; Anzahl schon ueber ANZAHL_SEPARATOR/ANZAHL_SCANNER an Kreisel/GF/VF +;; gezaehlt wird. ;; Gibt Auswahlsatz zurueck oder nil. (defun csv:collect-export-blocks ( / ss-all ss-out i ename ed bname) (setq ss-all (ssget "X" (list (cons 0 "INSERT")))) @@ -399,6 +406,8 @@ (wcmatch (strcase bname) (export:pattern "pattern_gerade" "AP110*,AP_110*")) (wcmatch bname (export:pattern "pattern_strecke" "Vario*,Staustrecke*,AUS_Element*,EIN_Element*,VF_*,GF_*")) + (wcmatch bname (export:pattern "pattern_separator" "Separator_SP*,S-LP*")) + (wcmatch bname (export:pattern "pattern_scanner" "Scanner*")) ;; Rein numerische Namen = Omniflo SivasNr (Boegen/Weichen) ;; Rein LISP-intern (Python klassifiziert Omniflo stattdessen ;; ueber exakten Katalog-Match), daher fest im Code statt in diff --git a/Lisp/ssg_core.lsp b/Lisp/ssg_core.lsp index 76f03c2..1f27a08 100644 --- a/Lisp/ssg_core.lsp +++ b/Lisp/ssg_core.lsp @@ -370,13 +370,58 @@ (defun ssg-ils-block-datei (blockname / ) (ssg-ils-block-datei-dim blockname (ssg-ils-dim-aktuell))) +;; ------------------------------------------------------------ +;; REDEFINE-SPERRE (eine DWG-Lesung pro Block und Zeichnung) +;; ------------------------------------------------------------ +;; Merkliste der in DIESER Zeichnung schon einmal aus der DWG-Datei neu +;; definierten (redefinierten) Blocknamen. Grundlage der Sperre in +;; ssg-ils-block-laden-dim. Namen werden in Grossschreibung gehalten, weil +;; Blocknamen in CAD case-insensitiv sind. LISP-Variablen sind pro Dokument +;; eigenstaendig, die Liste gilt also automatisch je Zeichnung. +(if (not (boundp '*ssg-block-refreshed*)) (setq *ssg-block-refreshed* nil)) + +(defun ssg-block-refreshed-p (eff / ) + (and (boundp '*ssg-block-refreshed*) + (member (strcase eff) *ssg-block-refreshed*) + t)) + +(defun ssg-block-refreshed-set (eff / ) + (if (not (ssg-block-refreshed-p eff)) + (setq *ssg-block-refreshed* (cons (strcase eff) *ssg-block-refreshed*))) + eff) + +;; Sperre aufheben - erzwingt beim naechsten ssg-ils-block-laden(-dim) ein +;; erneutes Einlesen der DWG. Argument nil: ALLE Bloecke; ROH- oder effektiver +;; Blockname: nur dieser (beide Dimensionen). Noetig, wenn eine Block-DWG +;; waehrend der laufenden Sitzung geaendert wurde. +(defun ssg-block-refresh-reset (blockname / rest n2 n3 nroh) + (if (null blockname) + (setq *ssg-block-refreshed* nil) + (progn + (setq nroh (strcase blockname) + n2 (strcase (ssg-ils-blockname-dim blockname "2D")) + n3 (strcase (ssg-ils-blockname-dim blockname "3D")) + rest '()) + (foreach n *ssg-block-refreshed* + (if (not (or (= n nroh) (= n n2) (= n n3))) + (setq rest (cons n rest)))) + (setq *ssg-block-refreshed* rest))) + (princ)) + ;; Stellt sicher, dass die Block-DEFINITION zum ROH-Namen 'blockname' in einer ;; EXPLIZITEN Dimension geladen ist. Blocknamen tragen das Suffix _2D/_3D ;; (siehe ssg-ils-blockname-dim), daher koennen 2D und 3D gleichzeitig existieren. -;; WICHTIG: Ein bereits vorhandener gleichnamiger Block wird IMMER neu aus der -;; Datei definiert (Redefine), damit sein Inhalt zur aktuellen Datei passt. Sonst -;; bliebe eine veraltete Definition (z.B. mit falschem 2D/3D-Inhalt aus einer -;; frueheren Sitzung) bestehen und wuerde wiederverwendet. +;; Ein bereits vorhandener gleichnamiger Block wird EINMAL PRO ZEICHNUNG neu aus +;; der Datei definiert (Redefine), damit eine veraltete Definition aus einer +;; frueheren Sitzung (z.B. mit falschem 2D/3D-Inhalt) nicht wiederverwendet wird. +;; Jeder WEITERE Aufruf fuer denselben effektiven Blocknamen ueberspringt das +;; Neu-Definieren (siehe *ssg-block-refreshed*): Ohne diese Sperre las jede +;; einzelne Element-Einfuegung die DWG erneut von der Platte - bei den grossen +;; 3D-Bloecken (AS_Element_90_links_3D ~52 MB, ES_Element_90_links_3D ~9 MB) +;; und zusaetzlich einer Regeneration aller bereits platzierten Referenzen +;; dieses Blocks. Bei Tests mit vielen Strecken (TEST_MUBEA, Gefaellestrecke- +;; und Foerderer-Tests) fuehrte das zu minutenlangen Laufzeiten. +;; Zum erzwungenen Neu-Einlesen: ssg-block-refresh-reset. ;; Rueckgabe: effektiver (suffigierter) Blockname oder nil (Datei nicht gefunden). (defun ssg-ils-block-laden-dim (blockname dim / eff datei base temp-obj osm areq adia) (setq eff (ssg-ils-blockname-dim blockname dim)) @@ -385,12 +430,6 @@ nil (progn (setq base (vl-filename-base datei)) - ;; --- TEMP-DIAGNOSE (nach Klaerung wieder entfernen) --- - (princ (strcat "\n[SSG-DBG] roh=" blockname " dim=" dim - " eff=" eff - " existiert-vorher=" (if (tblsearch "BLOCK" eff) "JA" "NEIN") - " datei=" datei)) - ;; --- ENDE TEMP-DIAGNOSE --- ;; Sichtbar machen, wenn die Datei der gewuenschten Dimension fehlt und der ;; 3D-Fallback greift (dann traegt der Block den gewuenschten Namen, aber ;; den Inhalt der anderen Dimension = potenziell falsche Referenz). Jeder @@ -399,38 +438,36 @@ (princ (strcat "\n[SSG] WARNUNG: Blockdatei '" eff ".dwg' fehlt - Fallback auf '" base ".dwg' (Inhalt evtl. andere Dimension)."))) - (if (and (not (tblsearch "BLOCK" eff)) (= (strcase base) (strcase eff))) - ;; Neu UND Dateiname == Blockname -> COM-Weg (kein Prompt). + ;; In dieser Zeichnung bereits aus der Datei aktualisiert UND noch in der + ;; Blocktabelle vorhanden -> nichts zu tun, die Definition ist aktuell. + ;; Die tblsearch-Pruefung faengt ab, dass der Block zwischenzeitlich + ;; entfernt wurde (z.B. per PURGE) und neu geladen werden muss. + (if (and (ssg-block-refreshed-p eff) (tblsearch "BLOCK" eff)) + eff (progn - (setq temp-obj (vla-InsertBlock modelspace - (vlax-3D-point '(0 0 0)) datei 1.0 1.0 1.0 0)) - (vla-Delete temp-obj)) - ;; Sonst (Block existiert bereits ODER Dateiname != Blockname wg. - ;; 3D-Fallback) -> aus Datei (neu) definieren. "_Y" beantwortet die - ;; Redefine-Abfrage, falls der Block bereits vorhanden ist; danach die - ;; laufende Einfuegung sofort abbrechen (Definition ist bereits wirksam). - (progn - (setq areq (getvar "ATTREQ") adia (getvar "ATTDIA") osm (getvar "OSMODE")) - (setvar "ATTREQ" 0)(setvar "ATTDIA" 0)(setvar "OSMODE" 0) - (if (tblsearch "BLOCK" eff) - (command "_.-INSERT" (strcat eff "=" datei) "_Y") ; Redefine bestehender Block - (command "_.-INSERT" (strcat eff "=" datei))) ; neu (kein Redefine-Prompt) - (if (> (getvar "CMDACTIVE") 0) (command)) - (if (> (getvar "CMDACTIVE") 0) (command)) - (setvar "OSMODE" osm)(setvar "ATTREQ" areq)(setvar "ATTDIA" adia))) - ;; --- TEMP-DIAGNOSE: verschachtelte Blocknamen in eff auflisten --- - (if (tblsearch "BLOCK" eff) - (progn - (princ (strcat "\n[SSG-DBG] nested in " eff ":")) - (vl-catch-all-apply - (function (lambda () - (vlax-for e (vla-Item - (vla-get-Blocks (vla-get-ActiveDocument (vlax-get-acad-object))) - eff) - (if (= (vla-get-ObjectName e) "AcDbBlockReference") - (princ (strcat " [" (vla-get-Name e) "]"))))))))) - ;; --- ENDE TEMP-DIAGNOSE --- - eff))) + (if (and (not (tblsearch "BLOCK" eff)) (= (strcase base) (strcase eff))) + ;; Neu UND Dateiname == Blockname -> COM-Weg (kein Prompt). + (progn + (setq temp-obj (vla-InsertBlock modelspace + (vlax-3D-point '(0 0 0)) datei 1.0 1.0 1.0 0)) + (vla-Delete temp-obj)) + ;; Sonst (Block existiert bereits ODER Dateiname != Blockname wg. + ;; 3D-Fallback) -> aus Datei (neu) definieren. "_Y" beantwortet die + ;; Redefine-Abfrage, falls der Block bereits vorhanden ist; danach die + ;; laufende Einfuegung sofort abbrechen (Definition ist bereits wirksam). + (progn + (setq areq (getvar "ATTREQ") adia (getvar "ATTDIA") osm (getvar "OSMODE")) + (setvar "ATTREQ" 0)(setvar "ATTDIA" 0)(setvar "OSMODE" 0) + (if (tblsearch "BLOCK" eff) + (command "_.-INSERT" (strcat eff "=" datei) "_Y") ; Redefine bestehender Block + (command "_.-INSERT" (strcat eff "=" datei))) ; neu (kein Redefine-Prompt) + (if (> (getvar "CMDACTIVE") 0) (command)) + (if (> (getvar "CMDACTIVE") 0) (command)) + (setvar "OSMODE" osm)(setvar "ATTREQ" areq)(setvar "ATTDIA" adia))) + ;; Nur merken, wenn die Definition danach wirklich vorhanden ist - + ;; sonst wuerde ein fehlgeschlagener Ladeversuch dauerhaft gesperrt. + (if (tblsearch "BLOCK" eff) (ssg-block-refreshed-set eff)) + eff))))) ;; Wie ssg-ils-block-laden-dim, aber fuer die AKTUELLE Dimension. (defun ssg-ils-block-laden (blockname / ) diff --git a/Lisp/vf_core.lsp b/Lisp/vf_core.lsp index 4709b6c..ea58e47 100644 --- a/Lisp/vf_core.lsp +++ b/Lisp/vf_core.lsp @@ -106,8 +106,10 @@ (t nil))) ;; Normalisiert die verschiedenen KS-Blocknamen auf "KS_EIN"/"KS_AUS". -;; Die aelteren Bloecke (AS/ES/Gefaellebogen) verwenden KS_EIN/KS_AUS, die -;; Vario_Kurve-Bloecke KSYS_EIN/KSYS_AUS. Rueckgabe nil, wenn kein KS-Block. +;; Standard ist KS_EIN/KS_AUS; historische Bestandsbloecke koennen noch +;; KSYS_EIN/KSYS_AUS fuehren (Vereinheitlichung via ILS_BLOCK_RENAME in +;; block_rename.lsp). Diese Normalisierung bleibt als Sicherheitsnetz fuer +;; solche Altzeichnungen. Rueckgabe nil, wenn kein KS-Block. (defun ks-normalize-name (nm) (cond ((or (= nm "KS_EIN") (= nm "KSYS_EIN")) "KS_EIN") diff --git a/Lisp/vf_linienzug.lsp b/Lisp/vf_linienzug.lsp index e346386..9301e6e 100644 --- a/Lisp/vf_linienzug.lsp +++ b/Lisp/vf_linienzug.lsp @@ -893,8 +893,9 @@ ;; Nicht-interaktiver Kern: Vario-Kurve mit gegebenem Winkel/Seite/Variante ;; einfuegen. Genutzt von der interaktiven Abfrage UND von den Pfad-Modi ;; (Winkel/Seite aus der Geometrie, Variante abgefragt). Rueckgabe: neuer Frame. -;; Die Vario_Kurve-Bloecke fuehren KS_EIN/KS_AUS als KSYS_EIN/KSYS_AUS - -;; extract-ks-from-block-raw normalisiert das (siehe ks-normalize-name). +;; Standard sind KS_EIN/KS_AUS; historische Bestandsbloecke koennen noch +;; KSYS_EIN/KSYS_AUS fuehren - extract-ks-from-block-raw normalisiert das +;; (siehe ks-normalize-name). (defun vfl-insert-vario-kurve-block (frame kwinkel kseite kvariante / blockname hz flach-frame neuer-frame hz-gemessen hz-gesnappt) @@ -924,8 +925,9 @@ ) ;; Vario-Kurve (horizontale Kurve, Neigung bleibt wie im aktuellen Frame). -;; Die Vario_Kurve-Bloecke fuehren KS_EIN/KS_AUS als KSYS_EIN/KSYS_AUS - -;; extract-ks-from-block-raw normalisiert das (siehe ks-normalize-name). +;; Standard sind KS_EIN/KS_AUS; historische Bestandsbloecke koennen noch +;; KSYS_EIN/KSYS_AUS fuehren - extract-ks-from-block-raw normalisiert das +;; (siehe ks-normalize-name). ;; Interaktiv (i18n): Vario-Kurve-Winkel + Seite + Variante abfragen, dann Kern. (defun vfl-insert-vario-kurve (frame / kwinkel kseite kvariante antwort) (princ (ssg-text "vfl-variokurve-winkel-header")) diff --git a/bin/set_attributs.bat b/bin/dbg2lsp.bat similarity index 52% rename from bin/set_attributs.bat rename to bin/dbg2lsp.bat index c048e8c..1b40db3 100644 --- a/bin/set_attributs.bat +++ b/bin/dbg2lsp.bat @@ -1,8 +1,9 @@ @echo off REM ================================================================ -REM Fuegt Attribute (ATTDEF) zu Omniflo DXF-Dateien hinzu +REM Instrumentiert AutoLISP-Funktionen mit ssg_dbg.lsp +REM (dbgf/dbg/dbgreturn/dbgopen/dbgclose) - siehe doc/dbg2lsp.md REM ================================================================ call "%~dp0setenv.bat" -python "%DXFM_LIB%\set_attributs.py" %* +python "%DXFM_LIB%\dbg2lsp.py" %* diff --git a/cfg/attradd.cfg b/cfg/attradd.cfg index 6804de8..c153f65 100644 --- a/cfg/attradd.cfg +++ b/cfg/attradd.cfg @@ -96,10 +96,5 @@ LAYER = "A-2" HOEHE = "" DREHUNG = "" - -[container] -0_BG071090 = 0_B10071, 0_B10090, 0_B10090 -0_BG080090 = 0_B10080, 0_B10090, -0_BG081090 = 0_B10081, 0_B10090, -0_BG090090 = 0_B10090, 0_B10090, -0_BG190090 = 0_B10090, 0_B10090 +# Hinweis: Die [container]-Sektion (0_BG-Aufloesung fuer SivasNummer) liegt +# jetzt in cfg/export.cfg und wird von set_attributs.py von dort gelesen. diff --git a/cfg/export.cfg b/cfg/export.cfg index e7b0f7d..2ff5962 100644 --- a/cfg/export.cfg +++ b/cfg/export.cfg @@ -16,6 +16,18 @@ # pattern_strecke_module-Keys sind Teilmengen davon fuer die feinere # TeileArt-KLASSIFIKATION in Python (eigene Zeile je Teileart) - # Aenderungen an Blocknamen bitte in beiden Stellen nachziehen. +# +# pattern_separator/pattern_scanner: eigenstaendige Sensor-Bloecke +# (Separator_SP/S-LP, Scanner - siehe ils-insert-sensor/mubea:build-separator-one). +# Werden von Lisp's Block-SAMMLUNG mitgesammelt (siehe export.lsp) und von +# export_csv.py als eigene TeileArt-Zeilen mit einer beim Export erzeugten +# laufenden ID ausgegeben (die Bloecke selbst haben KEIN ID-Attribut - anders +# als Kreisel/GF/VF -, daher keine ssg-id-generate-Persistenz). export_sivas.py +# schliesst dieselben Bloecke bewusst aus (siehe SEP_SCANNER_PATTERNS dort): +# ihre Stueckzahl wird im Sivas-Export bereits ueber die ANZAHL_SEPARATOR/ +# ANZAHL_SCANNER-Attribute der zugehoerigen Kreisel/Gefaellestrecke/ +# VarioFoerderer-Bloecke gezaehlt - ein zusaetzlicher eigener Eintrag wuerde +# doppelt zaehlen. # ============================================================ [blockpattern] @@ -26,6 +38,8 @@ pattern_strecke= Vario*, Staustrecke*, AUS_Element*, EIN_Element*, VF_ pattern_variofoerderer= VF_* pattern_gefaellestrecke= GF_* pattern_strecke_module= Vario*, Staustrecke*, AUS_Element*, EIN_Element* +pattern_separator= Separator_SP*, S-LP* +pattern_scanner= Scanner* pattern_ks_subblocks= KS_EIN, KS_AUS, K1, K2, K3, K4 [Planquadrate] @@ -56,11 +70,11 @@ y_description=alphabetisch # Buchstaben von unten nach oben, 4m pro Zeile format= {x}/{y} # Formatierung der Planquadrate, {y} und {x} werden durch die berechneten gerundeten Werte der Objektkoordinaten ersetzt [Planquadrate_AN] -# z.B. A/1 +# z.B. A/1 (x = Alphabetisch, y = Numerisch) x_step=2 x_description=alphabetisch # Buchstaben, von links nach rechts, 2m pro Spalte y_step=2 -y_description=alphabetisch # Zahlen von unten nach oben, 2m pro Zeile +y_description=numerisch # Zahlen von unten nach oben, 2m pro Zeile format= {x}/{y} [Planquadrate_NN] @@ -87,9 +101,17 @@ toleranz_mm=200 # als die ueblichen Omniflo-Bauteile sein (AP110-Geraden ca. 2000mm). omniflo_zellgroesse_mm=3000 +# Kollisions-/Nachbarschafts-Debugprotokoll: 0/leer = aus, 1 = schreibt eine +# .dbg-Datei nach DXFM_LOG/export_collision.dbg (bzw. /logs/, falls +# DXFM_LOG nicht gesetzt ist). Alternativ ein expliziter Dateipfad. Das Log +# protokolliert je Element die Bounding-Box, die Gruppenzuordnung und jeden +# Ueberschneidungstest - zur Diagnose, warum (keine) Nachbarn gefunden werden. +debug_log=1 + [Boundingbox] # Mindesthoehe (Z-Ausdehnung) der von export.lsp (csv:get-bbox, per # vla-getboundingbox) ermittelten Bounding-Box. Flache 2D-Bloecke (Draufsicht) # haben oft dz=0 - wird auf diesen Wert angehoben. Wert in mm. Default 1, falls # nicht gesetzt. -bb_minimum_height_mm=1 \ No newline at end of file +bb_minimum_height_mm=1 + diff --git a/data/ils/AN8_2D.dwg b/data/ils/AN8_2D.dwg index 6754a0b..9adff39 100644 Binary files a/data/ils/AN8_2D.dwg and b/data/ils/AN8_2D.dwg differ diff --git a/data/ils/AP110_2D.dwg b/data/ils/AP110_2D.dwg new file mode 100644 index 0000000..57fb5e1 Binary files /dev/null and b/data/ils/AP110_2D.dwg differ diff --git a/data/ils/AP60_2D.dwg b/data/ils/AP60_2D.dwg new file mode 100644 index 0000000..1f83755 Binary files /dev/null and b/data/ils/AP60_2D.dwg differ diff --git a/data/ils/APG110_2D.dwg b/data/ils/APG110_2D.dwg new file mode 100644 index 0000000..6b3fa8f Binary files /dev/null and b/data/ils/APG110_2D.dwg differ diff --git a/data/ils/AS_Element_30_links_3D.dwg b/data/ils/AS_Element_30_links_3D.dwg index 67a1135..7742f76 100644 Binary files a/data/ils/AS_Element_30_links_3D.dwg and b/data/ils/AS_Element_30_links_3D.dwg differ diff --git a/data/ils/AS_Element_30_rechts_2D.dwg b/data/ils/AS_Element_30_rechts_2D.dwg index 1443342..2bbbce6 100644 Binary files a/data/ils/AS_Element_30_rechts_2D.dwg and b/data/ils/AS_Element_30_rechts_2D.dwg differ diff --git a/data/ils/AS_Element_30_rechts_3D.dwg b/data/ils/AS_Element_30_rechts_3D.dwg index a4be107..07843d8 100644 Binary files a/data/ils/AS_Element_30_rechts_3D.dwg and b/data/ils/AS_Element_30_rechts_3D.dwg differ diff --git a/data/ils/AS_Element_90_links_2D.dwg b/data/ils/AS_Element_90_links_2D.dwg index ad5c21a..def0a85 100644 Binary files a/data/ils/AS_Element_90_links_2D.dwg and b/data/ils/AS_Element_90_links_2D.dwg differ diff --git a/data/ils/AS_Element_90_rechts_2D.dwg b/data/ils/AS_Element_90_rechts_2D.dwg index 5bf44c4..9e5cc58 100644 Binary files a/data/ils/AS_Element_90_rechts_2D.dwg and b/data/ils/AS_Element_90_rechts_2D.dwg differ diff --git a/data/ils/BTMT-Beladung_3D.dwg b/data/ils/BTMT-Beladung_3D.dwg index 6b4c8a0..5ac3ddc 100644 Binary files a/data/ils/BTMT-Beladung_3D.dwg and b/data/ils/BTMT-Beladung_3D.dwg differ diff --git a/data/ils/ES_Element_30_links_2D.dwg b/data/ils/ES_Element_30_links_2D.dwg index 03c056e..75d33d9 100644 Binary files a/data/ils/ES_Element_30_links_2D.dwg and b/data/ils/ES_Element_30_links_2D.dwg differ diff --git a/data/ils/ES_Element_30_rechts_2D.dwg b/data/ils/ES_Element_30_rechts_2D.dwg index 9f69d5e..90a7d7a 100644 Binary files a/data/ils/ES_Element_30_rechts_2D.dwg and b/data/ils/ES_Element_30_rechts_2D.dwg differ diff --git a/data/ils/ES_Element_90_links_2D.dwg b/data/ils/ES_Element_90_links_2D.dwg index 9db90ff..94c4de8 100644 Binary files a/data/ils/ES_Element_90_links_2D.dwg and b/data/ils/ES_Element_90_links_2D.dwg differ diff --git a/data/ils/ES_Element_90_links_3D.dwg b/data/ils/ES_Element_90_links_3D.dwg index 219b57f..7fce6bc 100644 Binary files a/data/ils/ES_Element_90_links_3D.dwg and b/data/ils/ES_Element_90_links_3D.dwg differ diff --git a/data/ils/ES_Element_90_rechts_2D.dwg b/data/ils/ES_Element_90_rechts_2D.dwg index 2ad6b93..fc98107 100644 Binary files a/data/ils/ES_Element_90_rechts_2D.dwg and b/data/ils/ES_Element_90_rechts_2D.dwg differ diff --git a/data/ils/ES_Element_90_rechts_3D.dwg b/data/ils/ES_Element_90_rechts_3D.dwg index c72934a..184afb2 100644 Binary files a/data/ils/ES_Element_90_rechts_3D.dwg and b/data/ils/ES_Element_90_rechts_3D.dwg differ diff --git a/data/ils/Gefaellebogen_links_30_R500_2D.dwg b/data/ils/Gefaellebogen_links_30_R500_2D.dwg index 86aa919..38b5d81 100644 Binary files a/data/ils/Gefaellebogen_links_30_R500_2D.dwg and b/data/ils/Gefaellebogen_links_30_R500_2D.dwg differ diff --git a/data/ils/Gefaellebogen_links_60_R500_2D.dwg b/data/ils/Gefaellebogen_links_60_R500_2D.dwg index 750fc4f..95f1143 100644 Binary files a/data/ils/Gefaellebogen_links_60_R500_2D.dwg and b/data/ils/Gefaellebogen_links_60_R500_2D.dwg differ diff --git a/data/ils/Gefaellebogen_links_90_R500_2D.dwg b/data/ils/Gefaellebogen_links_90_R500_2D.dwg index eaa7bfa..34a4cc1 100644 Binary files a/data/ils/Gefaellebogen_links_90_R500_2D.dwg and b/data/ils/Gefaellebogen_links_90_R500_2D.dwg differ diff --git a/data/ils/Gefaellebogen_rechts_30_R500_2D.dwg b/data/ils/Gefaellebogen_rechts_30_R500_2D.dwg index d468173..6e0f70f 100644 Binary files a/data/ils/Gefaellebogen_rechts_30_R500_2D.dwg and b/data/ils/Gefaellebogen_rechts_30_R500_2D.dwg differ diff --git a/data/ils/Gefaellebogen_rechts_60_R500_2D.dwg b/data/ils/Gefaellebogen_rechts_60_R500_2D.dwg index fb8776f..9c0350e 100644 Binary files a/data/ils/Gefaellebogen_rechts_60_R500_2D.dwg and b/data/ils/Gefaellebogen_rechts_60_R500_2D.dwg differ diff --git a/data/ils/Gefaellebogen_rechts_90_R500_2D.dwg b/data/ils/Gefaellebogen_rechts_90_R500_2D.dwg index cb3d099..e5fc0bf 100644 Binary files a/data/ils/Gefaellebogen_rechts_90_R500_2D.dwg and b/data/ils/Gefaellebogen_rechts_90_R500_2D.dwg differ diff --git a/data/ils/Modulweiche_RG_links_Passiv_2D.dwg b/data/ils/Modulweiche_RG_links_Passiv_2D.dwg index 04f4b31..76d3cdf 100644 Binary files a/data/ils/Modulweiche_RG_links_Passiv_2D.dwg and b/data/ils/Modulweiche_RG_links_Passiv_2D.dwg differ diff --git a/data/ils/Modulweiche_RG_links_Passiv_3D.dwg b/data/ils/Modulweiche_RG_links_Passiv_3D.dwg index 04f4b31..d4c206e 100644 Binary files a/data/ils/Modulweiche_RG_links_Passiv_3D.dwg and b/data/ils/Modulweiche_RG_links_Passiv_3D.dwg differ diff --git a/data/ils/Modulweiche_RG_links_Pneumatik_2D.dwg b/data/ils/Modulweiche_RG_links_Pneumatik_2D.dwg index 2915de9..979794f 100644 Binary files a/data/ils/Modulweiche_RG_links_Pneumatik_2D.dwg and b/data/ils/Modulweiche_RG_links_Pneumatik_2D.dwg differ diff --git a/data/ils/Modulweiche_RG_links_Pneumatik_3D.dwg b/data/ils/Modulweiche_RG_links_Pneumatik_3D.dwg index 2915de9..fcddb5c 100644 Binary files a/data/ils/Modulweiche_RG_links_Pneumatik_3D.dwg and b/data/ils/Modulweiche_RG_links_Pneumatik_3D.dwg differ diff --git a/data/ils/Modulweiche_RG_links_Zylinder_2D.dwg b/data/ils/Modulweiche_RG_links_Zylinder_2D.dwg index 6efcdd5..f08e568 100644 Binary files a/data/ils/Modulweiche_RG_links_Zylinder_2D.dwg and b/data/ils/Modulweiche_RG_links_Zylinder_2D.dwg differ diff --git a/data/ils/Modulweiche_RG_links_Zylinder_3D.dwg b/data/ils/Modulweiche_RG_links_Zylinder_3D.dwg index 6efcdd5..9a6917f 100644 Binary files a/data/ils/Modulweiche_RG_links_Zylinder_3D.dwg and b/data/ils/Modulweiche_RG_links_Zylinder_3D.dwg differ diff --git a/data/ils/Modulweiche_RG_rechts_Passiv_2D.dwg b/data/ils/Modulweiche_RG_rechts_Passiv_2D.dwg index 1bf5e59..9d1f07d 100644 Binary files a/data/ils/Modulweiche_RG_rechts_Passiv_2D.dwg and b/data/ils/Modulweiche_RG_rechts_Passiv_2D.dwg differ diff --git a/data/ils/Modulweiche_RG_rechts_Passiv_3D.dwg b/data/ils/Modulweiche_RG_rechts_Passiv_3D.dwg index 1bf5e59..8627ec3 100644 Binary files a/data/ils/Modulweiche_RG_rechts_Passiv_3D.dwg and b/data/ils/Modulweiche_RG_rechts_Passiv_3D.dwg differ diff --git a/data/ils/Modulweiche_RG_rechts_Pneumatik_2D.dwg b/data/ils/Modulweiche_RG_rechts_Pneumatik_2D.dwg index 7709ca8..093686f 100644 Binary files a/data/ils/Modulweiche_RG_rechts_Pneumatik_2D.dwg and b/data/ils/Modulweiche_RG_rechts_Pneumatik_2D.dwg differ diff --git a/data/ils/Modulweiche_RG_rechts_Pneumatik_3D.dwg b/data/ils/Modulweiche_RG_rechts_Pneumatik_3D.dwg index 7709ca8..4c757a4 100644 Binary files a/data/ils/Modulweiche_RG_rechts_Pneumatik_3D.dwg and b/data/ils/Modulweiche_RG_rechts_Pneumatik_3D.dwg differ diff --git a/data/ils/Modulweiche_RG_rechts_Zylinder_2D.dwg b/data/ils/Modulweiche_RG_rechts_Zylinder_2D.dwg index 40dd502..0269fa3 100644 Binary files a/data/ils/Modulweiche_RG_rechts_Zylinder_2D.dwg and b/data/ils/Modulweiche_RG_rechts_Zylinder_2D.dwg differ diff --git a/data/ils/Modulweiche_RG_rechts_Zylinder_3D.dwg b/data/ils/Modulweiche_RG_rechts_Zylinder_3D.dwg index 40dd502..6496aaa 100644 Binary files a/data/ils/Modulweiche_RG_rechts_Zylinder_3D.dwg and b/data/ils/Modulweiche_RG_rechts_Zylinder_3D.dwg differ diff --git a/data/ils/Modulweiche_RR_Passiv_2D.dwg b/data/ils/Modulweiche_RR_Passiv_2D.dwg index a3d0b5d..139a41a 100644 Binary files a/data/ils/Modulweiche_RR_Passiv_2D.dwg and b/data/ils/Modulweiche_RR_Passiv_2D.dwg differ diff --git a/data/ils/Modulweiche_RR_Passiv_3D.dwg b/data/ils/Modulweiche_RR_Passiv_3D.dwg index a3d0b5d..b7d034b 100644 Binary files a/data/ils/Modulweiche_RR_Passiv_3D.dwg and b/data/ils/Modulweiche_RR_Passiv_3D.dwg differ diff --git a/data/ils/Modulweiche_RR_Pneumatik_2D.dwg b/data/ils/Modulweiche_RR_Pneumatik_2D.dwg index 09b24f7..d3a2f51 100644 Binary files a/data/ils/Modulweiche_RR_Pneumatik_2D.dwg and b/data/ils/Modulweiche_RR_Pneumatik_2D.dwg differ diff --git a/data/ils/Modulweiche_RR_Pneumatik_3D.dwg b/data/ils/Modulweiche_RR_Pneumatik_3D.dwg index 09b24f7..4182221 100644 Binary files a/data/ils/Modulweiche_RR_Pneumatik_3D.dwg and b/data/ils/Modulweiche_RR_Pneumatik_3D.dwg differ diff --git a/data/ils/Modulweiche_RR_Zylinder_2D.dwg b/data/ils/Modulweiche_RR_Zylinder_2D.dwg index 171f963..752ab6e 100644 Binary files a/data/ils/Modulweiche_RR_Zylinder_2D.dwg and b/data/ils/Modulweiche_RR_Zylinder_2D.dwg differ diff --git a/data/ils/Modulweiche_RR_Zylinder_3D.dwg b/data/ils/Modulweiche_RR_Zylinder_3D.dwg index 171f963..4812483 100644 Binary files a/data/ils/Modulweiche_RR_Zylinder_3D.dwg and b/data/ils/Modulweiche_RR_Zylinder_3D.dwg differ diff --git a/data/ils/SC_Entladung_2D.dwg b/data/ils/SC_Entladung_2D.dwg index 4618d81..97b2f24 100644 Binary files a/data/ils/SC_Entladung_2D.dwg and b/data/ils/SC_Entladung_2D.dwg differ diff --git a/data/ils/SC_Entladung_3D.dwg b/data/ils/SC_Entladung_3D.dwg index 5e8436d..bae3810 100644 Binary files a/data/ils/SC_Entladung_3D.dwg and b/data/ils/SC_Entladung_3D.dwg differ diff --git a/data/ils/SP8_2D.dwg b/data/ils/SP8_2D.dwg index 78bac4b..426fd81 100644 Binary files a/data/ils/SP8_2D.dwg and b/data/ils/SP8_2D.dwg differ diff --git a/data/ils/Scanner_2D.dwg b/data/ils/Scanner_2D.dwg index df80344..a190780 100644 Binary files a/data/ils/Scanner_2D.dwg and b/data/ils/Scanner_2D.dwg differ diff --git a/data/ils/Scanner_3D.dwg b/data/ils/Scanner_3D.dwg index c9b1bd0..c2afd3f 100644 Binary files a/data/ils/Scanner_3D.dwg and b/data/ils/Scanner_3D.dwg differ diff --git a/data/ils/Separator_SP_2D.dwg b/data/ils/Separator_SP_2D.dwg index fe021d9..0704170 100644 Binary files a/data/ils/Separator_SP_2D.dwg and b/data/ils/Separator_SP_2D.dwg differ diff --git a/data/ils/Separator_SP_3D.dwg b/data/ils/Separator_SP_3D.dwg index 019f25d..caa221a 100644 Binary files a/data/ils/Separator_SP_3D.dwg and b/data/ils/Separator_SP_3D.dwg differ diff --git a/data/ils/Staustrecke_SP_1000_mm_2D.dwg b/data/ils/Staustrecke_SP_1000_mm_2D.dwg index 00117ec..8e23716 100644 Binary files a/data/ils/Staustrecke_SP_1000_mm_2D.dwg and b/data/ils/Staustrecke_SP_1000_mm_2D.dwg differ diff --git a/data/ils/Staustrecke_SP_1000_mm_3D.dwg b/data/ils/Staustrecke_SP_1000_mm_3D.dwg index 20fdcea..76af4c8 100644 Binary files a/data/ils/Staustrecke_SP_1000_mm_3D.dwg and b/data/ils/Staustrecke_SP_1000_mm_3D.dwg differ diff --git a/data/ils/Staustrecke_SP_Vario_links_1000_mm_2D.dwg b/data/ils/Staustrecke_SP_Vario_links_1000_mm_2D.dwg index 6f70dc7..34556d1 100644 Binary files a/data/ils/Staustrecke_SP_Vario_links_1000_mm_2D.dwg and b/data/ils/Staustrecke_SP_Vario_links_1000_mm_2D.dwg differ diff --git a/data/ils/Staustrecke_SP_Vario_links_1000_mm_3D.dwg b/data/ils/Staustrecke_SP_Vario_links_1000_mm_3D.dwg index 9fd2d81..9c2c59f 100644 Binary files a/data/ils/Staustrecke_SP_Vario_links_1000_mm_3D.dwg and b/data/ils/Staustrecke_SP_Vario_links_1000_mm_3D.dwg differ diff --git a/data/ils/Staustrecke_SP_Vario_rechts_1000_mm_2D.dwg b/data/ils/Staustrecke_SP_Vario_rechts_1000_mm_2D.dwg index 2360f67..b024bfc 100644 Binary files a/data/ils/Staustrecke_SP_Vario_rechts_1000_mm_2D.dwg and b/data/ils/Staustrecke_SP_Vario_rechts_1000_mm_2D.dwg differ diff --git a/data/ils/Staustrecke_SP_Vario_rechts_1000_mm_3D.dwg b/data/ils/Staustrecke_SP_Vario_rechts_1000_mm_3D.dwg index 525fc82..aee0a82 100644 Binary files a/data/ils/Staustrecke_SP_Vario_rechts_1000_mm_3D.dwg and b/data/ils/Staustrecke_SP_Vario_rechts_1000_mm_3D.dwg differ diff --git a/data/ils/Staustrecke_Separator_SP_300_mm_2D.dwg b/data/ils/Staustrecke_Separator_SP_300_mm_2D.dwg index 7397a96..c2b183c 100644 Binary files a/data/ils/Staustrecke_Separator_SP_300_mm_2D.dwg and b/data/ils/Staustrecke_Separator_SP_300_mm_2D.dwg differ diff --git a/data/ils/Staustrecke_Separator_SP_300_mm_3D.dwg b/data/ils/Staustrecke_Separator_SP_300_mm_3D.dwg index 8fd22c2..73bdbcd 100644 Binary files a/data/ils/Staustrecke_Separator_SP_300_mm_3D.dwg and b/data/ils/Staustrecke_Separator_SP_300_mm_3D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_12_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_ab_12_TEF_rechts_2D.dwg index cf50945..76727fa 100644 Binary files a/data/ils/Vario_Bogen_ab_12_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_ab_12_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_12_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_ab_12_TEF_rechts_3D.dwg index 040112a..136c695 100644 Binary files a/data/ils/Vario_Bogen_ab_12_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_ab_12_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_15_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_ab_15_TEF_rechts_2D.dwg index a4efc4f..4e9fc3f 100644 Binary files a/data/ils/Vario_Bogen_ab_15_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_ab_15_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_15_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_ab_15_TEF_rechts_3D.dwg index 642a9b9..d459bc3 100644 Binary files a/data/ils/Vario_Bogen_ab_15_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_ab_15_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_18_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_ab_18_TEF_rechts_2D.dwg index 06b89c0..8c8dd86 100644 Binary files a/data/ils/Vario_Bogen_ab_18_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_ab_18_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_18_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_ab_18_TEF_rechts_3D.dwg index 170aa1f..8ccfc52 100644 Binary files a/data/ils/Vario_Bogen_ab_18_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_ab_18_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_21_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_ab_21_TEF_rechts_2D.dwg index df6ae19..5181c4f 100644 Binary files a/data/ils/Vario_Bogen_ab_21_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_ab_21_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_21_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_ab_21_TEF_rechts_3D.dwg index 8e28d9f..34e3780 100644 Binary files a/data/ils/Vario_Bogen_ab_21_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_ab_21_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_27_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_ab_27_TEF_rechts_2D.dwg index e1b180c..0b0d765 100644 Binary files a/data/ils/Vario_Bogen_ab_27_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_ab_27_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_27_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_ab_27_TEF_rechts_3D.dwg index 31d742a..771a72d 100644 Binary files a/data/ils/Vario_Bogen_ab_27_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_ab_27_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_33_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_ab_33_TEF_rechts_2D.dwg index 4789c0a..1d329ab 100644 Binary files a/data/ils/Vario_Bogen_ab_33_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_ab_33_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_33_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_ab_33_TEF_rechts_3D.dwg index fc43b7c..2e17fc1 100644 Binary files a/data/ils/Vario_Bogen_ab_33_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_ab_33_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_39_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_ab_39_TEF_rechts_2D.dwg index c23273b..075e155 100644 Binary files a/data/ils/Vario_Bogen_ab_39_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_ab_39_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_39_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_ab_39_TEF_rechts_3D.dwg index 09d3f46..e857adb 100644 Binary files a/data/ils/Vario_Bogen_ab_39_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_ab_39_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_3_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_ab_3_TEF_rechts_2D.dwg index 171e468..4a742c1 100644 Binary files a/data/ils/Vario_Bogen_ab_3_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_ab_3_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_3_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_ab_3_TEF_rechts_3D.dwg index 6c7e79f..31f05bb 100644 Binary files a/data/ils/Vario_Bogen_ab_3_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_ab_3_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_45_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_ab_45_TEF_rechts_2D.dwg index 3fa528c..e01b15c 100644 Binary files a/data/ils/Vario_Bogen_ab_45_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_ab_45_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_45_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_ab_45_TEF_rechts_3D.dwg index a8cbb65..9813f37 100644 Binary files a/data/ils/Vario_Bogen_ab_45_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_ab_45_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_51_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_ab_51_TEF_rechts_2D.dwg index 530b051..6440443 100644 Binary files a/data/ils/Vario_Bogen_ab_51_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_ab_51_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_51_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_ab_51_TEF_rechts_3D.dwg index 8d07336..ee37128 100644 Binary files a/data/ils/Vario_Bogen_ab_51_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_ab_51_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_6_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_ab_6_TEF_rechts_2D.dwg index 343ce2e..c5f0683 100644 Binary files a/data/ils/Vario_Bogen_ab_6_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_ab_6_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_6_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_ab_6_TEF_rechts_3D.dwg index bfaa878..b6e2759 100644 Binary files a/data/ils/Vario_Bogen_ab_6_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_ab_6_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_9_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_ab_9_TEF_rechts_2D.dwg index dabd1c1..1ab2bad 100644 Binary files a/data/ils/Vario_Bogen_ab_9_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_ab_9_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_ab_9_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_ab_9_TEF_rechts_3D.dwg index aebd0b8..76b2521 100644 Binary files a/data/ils/Vario_Bogen_ab_9_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_ab_9_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_12_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_auf_12_TEF_rechts_2D.dwg index ada1fc2..6b61744 100644 Binary files a/data/ils/Vario_Bogen_auf_12_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_auf_12_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_12_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_auf_12_TEF_rechts_3D.dwg index e4d7b66..637db1e 100644 Binary files a/data/ils/Vario_Bogen_auf_12_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_auf_12_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_15_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_auf_15_TEF_rechts_2D.dwg index 7d58bb8..8dc1793 100644 Binary files a/data/ils/Vario_Bogen_auf_15_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_auf_15_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_15_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_auf_15_TEF_rechts_3D.dwg index e4cf17c..ff95eaa 100644 Binary files a/data/ils/Vario_Bogen_auf_15_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_auf_15_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_18_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_auf_18_TEF_rechts_2D.dwg index beb7a77..a313076 100644 Binary files a/data/ils/Vario_Bogen_auf_18_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_auf_18_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_18_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_auf_18_TEF_rechts_3D.dwg index cb09ec6..40732d3 100644 Binary files a/data/ils/Vario_Bogen_auf_18_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_auf_18_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_21_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_auf_21_TEF_rechts_2D.dwg index 7abb31a..7d6704e 100644 Binary files a/data/ils/Vario_Bogen_auf_21_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_auf_21_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_21_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_auf_21_TEF_rechts_3D.dwg index 8b4a85e..5788f08 100644 Binary files a/data/ils/Vario_Bogen_auf_21_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_auf_21_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_27_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_auf_27_TEF_rechts_2D.dwg index f1988ce..6fe72b8 100644 Binary files a/data/ils/Vario_Bogen_auf_27_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_auf_27_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_27_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_auf_27_TEF_rechts_3D.dwg index f1d29ff..8ada581 100644 Binary files a/data/ils/Vario_Bogen_auf_27_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_auf_27_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_33_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_auf_33_TEF_rechts_2D.dwg index 43eb1f0..c2b0fc5 100644 Binary files a/data/ils/Vario_Bogen_auf_33_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_auf_33_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_33_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_auf_33_TEF_rechts_3D.dwg index 1edf1aa..779a08e 100644 Binary files a/data/ils/Vario_Bogen_auf_33_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_auf_33_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_39_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_auf_39_TEF_rechts_2D.dwg index bf5ea0c..dee60a9 100644 Binary files a/data/ils/Vario_Bogen_auf_39_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_auf_39_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_39_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_auf_39_TEF_rechts_3D.dwg index b9faada..a8f9aa1 100644 Binary files a/data/ils/Vario_Bogen_auf_39_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_auf_39_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_3_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_auf_3_TEF_rechts_2D.dwg index 99b404b..d80332c 100644 Binary files a/data/ils/Vario_Bogen_auf_3_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_auf_3_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_3_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_auf_3_TEF_rechts_3D.dwg index 366e6e4..3e2509e 100644 Binary files a/data/ils/Vario_Bogen_auf_3_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_auf_3_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_45_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_auf_45_TEF_rechts_2D.dwg index 01e0439..9954e45 100644 Binary files a/data/ils/Vario_Bogen_auf_45_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_auf_45_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_45_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_auf_45_TEF_rechts_3D.dwg index d776ff0..4735a60 100644 Binary files a/data/ils/Vario_Bogen_auf_45_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_auf_45_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_51_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_auf_51_TEF_rechts_2D.dwg index 3b2e75a..f181c36 100644 Binary files a/data/ils/Vario_Bogen_auf_51_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_auf_51_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_51_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_auf_51_TEF_rechts_3D.dwg index 69a7be2..428726f 100644 Binary files a/data/ils/Vario_Bogen_auf_51_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_auf_51_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_6_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_auf_6_TEF_rechts_2D.dwg index 12bc7a1..a9cb5f0 100644 Binary files a/data/ils/Vario_Bogen_auf_6_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_auf_6_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_6_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_auf_6_TEF_rechts_3D.dwg index 6545fb5..25350c5 100644 Binary files a/data/ils/Vario_Bogen_auf_6_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_auf_6_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_9_TEF_rechts_2D.dwg b/data/ils/Vario_Bogen_auf_9_TEF_rechts_2D.dwg index b8a855f..996bf87 100644 Binary files a/data/ils/Vario_Bogen_auf_9_TEF_rechts_2D.dwg and b/data/ils/Vario_Bogen_auf_9_TEF_rechts_2D.dwg differ diff --git a/data/ils/Vario_Bogen_auf_9_TEF_rechts_3D.dwg b/data/ils/Vario_Bogen_auf_9_TEF_rechts_3D.dwg index 3668a5e..6738e54 100644 Binary files a/data/ils/Vario_Bogen_auf_9_TEF_rechts_3D.dwg and b/data/ils/Vario_Bogen_auf_9_TEF_rechts_3D.dwg differ diff --git a/data/ils/Vario_Kurve_links_30_TEF_aussen_2D.dwg b/data/ils/Vario_Kurve_links_30_TEF_aussen_2D.dwg index 2bcccb7..2700029 100644 Binary files a/data/ils/Vario_Kurve_links_30_TEF_aussen_2D.dwg and b/data/ils/Vario_Kurve_links_30_TEF_aussen_2D.dwg differ diff --git a/data/ils/Vario_Kurve_links_30_TEF_aussen_3D.dwg b/data/ils/Vario_Kurve_links_30_TEF_aussen_3D.dwg index c0ef882..4824591 100644 Binary files a/data/ils/Vario_Kurve_links_30_TEF_aussen_3D.dwg and b/data/ils/Vario_Kurve_links_30_TEF_aussen_3D.dwg differ diff --git a/data/ils/Vario_Kurve_links_30_TEF_innen_2D.dwg b/data/ils/Vario_Kurve_links_30_TEF_innen_2D.dwg index e235504..6a678bc 100644 Binary files a/data/ils/Vario_Kurve_links_30_TEF_innen_2D.dwg and b/data/ils/Vario_Kurve_links_30_TEF_innen_2D.dwg differ diff --git a/data/ils/Vario_Kurve_links_30_TEF_innen_3D.dwg b/data/ils/Vario_Kurve_links_30_TEF_innen_3D.dwg index 750a202..a08d432 100644 Binary files a/data/ils/Vario_Kurve_links_30_TEF_innen_3D.dwg and b/data/ils/Vario_Kurve_links_30_TEF_innen_3D.dwg differ diff --git a/data/ils/Vario_Kurve_links_60_TEF_aussen_2D.dwg b/data/ils/Vario_Kurve_links_60_TEF_aussen_2D.dwg index 99ed7f4..ba6ad64 100644 Binary files a/data/ils/Vario_Kurve_links_60_TEF_aussen_2D.dwg and b/data/ils/Vario_Kurve_links_60_TEF_aussen_2D.dwg differ diff --git a/data/ils/Vario_Kurve_links_60_TEF_aussen_3D.dwg b/data/ils/Vario_Kurve_links_60_TEF_aussen_3D.dwg index 5b9154a..1b75d8f 100644 Binary files a/data/ils/Vario_Kurve_links_60_TEF_aussen_3D.dwg and b/data/ils/Vario_Kurve_links_60_TEF_aussen_3D.dwg differ diff --git a/data/ils/Vario_Kurve_links_60_TEF_innen_2D.dwg b/data/ils/Vario_Kurve_links_60_TEF_innen_2D.dwg index 2f9893b..6e4f3c3 100644 Binary files a/data/ils/Vario_Kurve_links_60_TEF_innen_2D.dwg and b/data/ils/Vario_Kurve_links_60_TEF_innen_2D.dwg differ diff --git a/data/ils/Vario_Kurve_links_60_TEF_innen_3D.dwg b/data/ils/Vario_Kurve_links_60_TEF_innen_3D.dwg index 27042e1..956b81d 100644 Binary files a/data/ils/Vario_Kurve_links_60_TEF_innen_3D.dwg and b/data/ils/Vario_Kurve_links_60_TEF_innen_3D.dwg differ diff --git a/data/ils/Vario_Kurve_links_90_TEF_aussen_2D.dwg b/data/ils/Vario_Kurve_links_90_TEF_aussen_2D.dwg index f25e3ab..b95fd84 100644 Binary files a/data/ils/Vario_Kurve_links_90_TEF_aussen_2D.dwg and b/data/ils/Vario_Kurve_links_90_TEF_aussen_2D.dwg differ diff --git a/data/ils/Vario_Kurve_links_90_TEF_aussen_3D.dwg b/data/ils/Vario_Kurve_links_90_TEF_aussen_3D.dwg index 0bcd676..689ddef 100644 Binary files a/data/ils/Vario_Kurve_links_90_TEF_aussen_3D.dwg and b/data/ils/Vario_Kurve_links_90_TEF_aussen_3D.dwg differ diff --git a/data/ils/Vario_Kurve_links_90_TEF_innen_2D.dwg b/data/ils/Vario_Kurve_links_90_TEF_innen_2D.dwg index f921c4a..be87805 100644 Binary files a/data/ils/Vario_Kurve_links_90_TEF_innen_2D.dwg and b/data/ils/Vario_Kurve_links_90_TEF_innen_2D.dwg differ diff --git a/data/ils/Vario_Kurve_links_90_TEF_innen_3D.dwg b/data/ils/Vario_Kurve_links_90_TEF_innen_3D.dwg index efd8d6d..ec3c3e6 100644 Binary files a/data/ils/Vario_Kurve_links_90_TEF_innen_3D.dwg and b/data/ils/Vario_Kurve_links_90_TEF_innen_3D.dwg differ diff --git a/data/ils/Vario_Kurve_rechts_30_TEF_aussen_2D.dwg b/data/ils/Vario_Kurve_rechts_30_TEF_aussen_2D.dwg index a6238cb..dea3279 100644 Binary files a/data/ils/Vario_Kurve_rechts_30_TEF_aussen_2D.dwg and b/data/ils/Vario_Kurve_rechts_30_TEF_aussen_2D.dwg differ diff --git a/data/ils/Vario_Kurve_rechts_30_TEF_aussen_3D.dwg b/data/ils/Vario_Kurve_rechts_30_TEF_aussen_3D.dwg index ab78086..cb8182c 100644 Binary files a/data/ils/Vario_Kurve_rechts_30_TEF_aussen_3D.dwg and b/data/ils/Vario_Kurve_rechts_30_TEF_aussen_3D.dwg differ diff --git a/data/ils/Vario_Kurve_rechts_30_TEF_innen_2D.dwg b/data/ils/Vario_Kurve_rechts_30_TEF_innen_2D.dwg index d9ea655..026643b 100644 Binary files a/data/ils/Vario_Kurve_rechts_30_TEF_innen_2D.dwg and b/data/ils/Vario_Kurve_rechts_30_TEF_innen_2D.dwg differ diff --git a/data/ils/Vario_Kurve_rechts_30_TEF_innen_3D.dwg b/data/ils/Vario_Kurve_rechts_30_TEF_innen_3D.dwg index 5327f5b..094274c 100644 Binary files a/data/ils/Vario_Kurve_rechts_30_TEF_innen_3D.dwg and b/data/ils/Vario_Kurve_rechts_30_TEF_innen_3D.dwg differ diff --git a/data/ils/Vario_Kurve_rechts_60_TEF_aussen_2D.dwg b/data/ils/Vario_Kurve_rechts_60_TEF_aussen_2D.dwg index 961ed8b..f34c2a5 100644 Binary files a/data/ils/Vario_Kurve_rechts_60_TEF_aussen_2D.dwg and b/data/ils/Vario_Kurve_rechts_60_TEF_aussen_2D.dwg differ diff --git a/data/ils/Vario_Kurve_rechts_60_TEF_aussen_3D.dwg b/data/ils/Vario_Kurve_rechts_60_TEF_aussen_3D.dwg index 35355ea..b0bbf54 100644 Binary files a/data/ils/Vario_Kurve_rechts_60_TEF_aussen_3D.dwg and b/data/ils/Vario_Kurve_rechts_60_TEF_aussen_3D.dwg differ diff --git a/data/ils/Vario_Kurve_rechts_60_TEF_innen_2D.dwg b/data/ils/Vario_Kurve_rechts_60_TEF_innen_2D.dwg index 5f8e38e..1bf15de 100644 Binary files a/data/ils/Vario_Kurve_rechts_60_TEF_innen_2D.dwg and b/data/ils/Vario_Kurve_rechts_60_TEF_innen_2D.dwg differ diff --git a/data/ils/Vario_Kurve_rechts_60_TEF_innen_3D.dwg b/data/ils/Vario_Kurve_rechts_60_TEF_innen_3D.dwg index 4ea0933..2f83b13 100644 Binary files a/data/ils/Vario_Kurve_rechts_60_TEF_innen_3D.dwg and b/data/ils/Vario_Kurve_rechts_60_TEF_innen_3D.dwg differ diff --git a/data/ils/Vario_Kurve_rechts_90_TEF_aussen_2D.dwg b/data/ils/Vario_Kurve_rechts_90_TEF_aussen_2D.dwg index 914b2f4..82f99af 100644 Binary files a/data/ils/Vario_Kurve_rechts_90_TEF_aussen_2D.dwg and b/data/ils/Vario_Kurve_rechts_90_TEF_aussen_2D.dwg differ diff --git a/data/ils/Vario_Kurve_rechts_90_TEF_aussen_3D.dwg b/data/ils/Vario_Kurve_rechts_90_TEF_aussen_3D.dwg index fb6f22f..4b4c28c 100644 Binary files a/data/ils/Vario_Kurve_rechts_90_TEF_aussen_3D.dwg and b/data/ils/Vario_Kurve_rechts_90_TEF_aussen_3D.dwg differ diff --git a/data/ils/Vario_Kurve_rechts_90_TEF_innen_2D.dwg b/data/ils/Vario_Kurve_rechts_90_TEF_innen_2D.dwg index 9ca865b..2ec611c 100644 Binary files a/data/ils/Vario_Kurve_rechts_90_TEF_innen_2D.dwg and b/data/ils/Vario_Kurve_rechts_90_TEF_innen_2D.dwg differ diff --git a/data/ils/Vario_Kurve_rechts_90_TEF_innen_3D.dwg b/data/ils/Vario_Kurve_rechts_90_TEF_innen_3D.dwg index ddc3519..84d5e3d 100644 Binary files a/data/ils/Vario_Kurve_rechts_90_TEF_innen_3D.dwg and b/data/ils/Vario_Kurve_rechts_90_TEF_innen_3D.dwg differ diff --git a/data/ils/Vario_Motorstation_500mm_links_2D.dwg b/data/ils/Vario_Motorstation_500mm_links_2D.dwg index c028296..bb4a5e0 100644 Binary files a/data/ils/Vario_Motorstation_500mm_links_2D.dwg and b/data/ils/Vario_Motorstation_500mm_links_2D.dwg differ diff --git a/data/ils/Vario_Motorstation_500mm_links_3D.dwg b/data/ils/Vario_Motorstation_500mm_links_3D.dwg index 643150f..71cc82f 100644 Binary files a/data/ils/Vario_Motorstation_500mm_links_3D.dwg and b/data/ils/Vario_Motorstation_500mm_links_3D.dwg differ diff --git a/data/ils/Vario_Motorstation_500mm_rechts_2D.dwg b/data/ils/Vario_Motorstation_500mm_rechts_2D.dwg index 39095b5..94660a6 100644 Binary files a/data/ils/Vario_Motorstation_500mm_rechts_2D.dwg and b/data/ils/Vario_Motorstation_500mm_rechts_2D.dwg differ diff --git a/data/ils/Vario_Motorstation_500mm_rechts_3D.dwg b/data/ils/Vario_Motorstation_500mm_rechts_3D.dwg index dcc87aa..f1e41b9 100644 Binary files a/data/ils/Vario_Motorstation_500mm_rechts_3D.dwg and b/data/ils/Vario_Motorstation_500mm_rechts_3D.dwg differ diff --git a/data/ils/Vario_Staustrecke_SP_1000_mm_3D.dwg b/data/ils/Vario_Staustrecke_SP_1000_mm_3D.dwg index 43dd190..4c85643 100644 Binary files a/data/ils/Vario_Staustrecke_SP_1000_mm_3D.dwg and b/data/ils/Vario_Staustrecke_SP_1000_mm_3D.dwg differ diff --git a/data/ils/Vario_Umlenkstation_500mm_links_2D.dwg b/data/ils/Vario_Umlenkstation_500mm_links_2D.dwg index 1a2c993..28462d2 100644 Binary files a/data/ils/Vario_Umlenkstation_500mm_links_2D.dwg and b/data/ils/Vario_Umlenkstation_500mm_links_2D.dwg differ diff --git a/data/ils/Vario_Umlenkstation_500mm_links_3D.dwg b/data/ils/Vario_Umlenkstation_500mm_links_3D.dwg index 2c79c1f..0d593f2 100644 Binary files a/data/ils/Vario_Umlenkstation_500mm_links_3D.dwg and b/data/ils/Vario_Umlenkstation_500mm_links_3D.dwg differ diff --git a/data/ils/Vario_Umlenkstation_500mm_rechts_2D.dwg b/data/ils/Vario_Umlenkstation_500mm_rechts_2D.dwg index 705b0c5..db13742 100644 Binary files a/data/ils/Vario_Umlenkstation_500mm_rechts_2D.dwg and b/data/ils/Vario_Umlenkstation_500mm_rechts_2D.dwg differ diff --git a/data/ils/Vario_Umlenkstation_500mm_rechts_3D.dwg b/data/ils/Vario_Umlenkstation_500mm_rechts_3D.dwg index 66e2a56..7fe33eb 100644 Binary files a/data/ils/Vario_Umlenkstation_500mm_rechts_3D.dwg and b/data/ils/Vario_Umlenkstation_500mm_rechts_3D.dwg differ diff --git a/data/ils/Vereinzeler_BHS_links_3D.dwg b/data/ils/Vereinzeler_BHS_links_3D.dwg index 55d1efd..1bdfd00 100644 Binary files a/data/ils/Vereinzeler_BHS_links_3D.dwg and b/data/ils/Vereinzeler_BHS_links_3D.dwg differ diff --git a/data/ils/Vereinzeler_BHS_rechts_3D.dwg b/data/ils/Vereinzeler_BHS_rechts_3D.dwg index 964254a..701c2d9 100644 Binary files a/data/ils/Vereinzeler_BHS_rechts_3D.dwg and b/data/ils/Vereinzeler_BHS_rechts_3D.dwg differ diff --git a/doc/dbg2lsp.md b/doc/dbg2lsp.md new file mode 100644 index 0000000..996631a --- /dev/null +++ b/doc/dbg2lsp.md @@ -0,0 +1,206 @@ +# dbg2lsp + +Instrumentiert AutoLISP-Funktionen (`.lsp`) automatisch mit den `ssg_dbg.lsp`-Routinen +(`dbgf`, `dbg`, `dbgreturn`, `dbgopen`, `dbgclose`) - so wie es beim manuellen Debuggen +bereits von Hand gemacht wird (siehe z.B. `Lisp/KreiselInsert.lsp::ils-eckrad-insert`). +Erspart das muehsame und fehleranfaellige manuelle Einfuegen dieser Aufrufe in +lange Aufrufketten (Kreisel/VarioFoerderer/Gefaellestrecke/Separator-Scanner o.ae.), +bevor ein Testlauf in BricsCAD gestartet wird. + +- [Aufruf](#aufruf) +- [Was wird eingefuegt](#was-wird-eingefuegt) + - [--method](#--method) + - [--recursive](#--recursive) + - [--add-open](#--add-open) +- [Schalter](#schalter) +- [Typischer Ablauf](#typischer-ablauf) +- [Beispiele](#beispiele) +- [Funktionsweise / Aufloesung](#funktionsweise--aufloesung) +- [Grenzen](#grenzen) +- [Idempotenz](#idempotenz) + +## Aufruf + +``` +bin\dbg2lsp.bat [--method NAME ...] [--recursive] [--add-open DATEI METHODE] [Schalter] +``` + +Das Batch-Skript ruft `setenv.bat` auf und startet `lib/dbg2lsp.py` mit den +uebergebenen Argumenten. `` dient als Anker (ihr Verzeichnis wird immer +mitdurchsucht) - die gesuchte Funktion muss nicht zwingend in genau dieser Datei +stehen, siehe [Funktionsweise](#funktionsweise--aufloesung). + +## Was wird eingefuegt + +### --method + +Fuer die angegebene Funktion (mehrfach angebbar) wird direkt als erste Rumpf-Form + +```lisp +(dbgf "funktionsname") +(dbg 'parameter1) +(dbg 'parameter2) +... +``` + +eingefuegt (ein `dbg`-Aufruf je Eingabeparameter - die Locals nach `/` in der +Parameterliste NICHT, das sind keine Eingabewerte), und die **letzte Form des +Funktionsrumpfs** wird eingeklammert: + +```lisp +(dbgreturn ) +``` + +`dbgreturn` loggt und liefert den Rueckgabewert unveraendert weiter (siehe +`Lisp/ssg_dbg.lsp`) - das Verhalten der Funktion aendert sich dadurch nicht. + +### --recursive + +Verfolgt zusaetzlich den Aufrufgraphen: jede Funktion, die im (Original-)Rumpf +aufgerufen wird - auch beliebig verschachtelt, z.B. in einem `if`/`progn`, aber +NICHT innerhalb einer `'quotierten Liste` (die ist reine Datenliste, z.B. +`'(("OSMODE") ("CECOLOR"))`) - wird ebenfalls mit `--method`-Logik instrumentiert, +sofern sich ihr `(defun ...)` in einem der durchsuchten Verzeichnisse findet. + +AutoLISP-/BricsCAD-Builtins (`princ`, `command`, `strcat`, `rtos`, `entmake`, `vla-*`, ...) +haben dort naturgemaess kein `defun` und werden dadurch automatisch **nicht** angefasst - +es gibt keine Builtin-Blacklist zu pflegen. Die `ssg_dbg.lsp`-Funktionen selbst +(`dbgf`, `dbg`, `dbgp`, `dbgmsg`, `dbgreturn`, `dbgopen`, `dbgclose`, `dbgflush`, `dbgon`, `dbgoff` +und ihre internen `dbg-*`-Helfer) sind fest ausgeschlossen, damit eine bereits +instrumentierte Funktion nicht dazu fuehrt, dass das Debug-Framework sich selbst +instrumentiert. + +Bereits instrumentierte Funktionen werden uebersprungen (siehe [Idempotenz](#idempotenz)), +ihr Rumpf wird aber trotzdem nach weiteren, noch nicht instrumentierten Aufrufen +durchsucht - die Rekursion bricht dadurch nicht an einer schon bearbeiteten Stelle ab. + +### --add-open + +Fuegt in der angegebenen Methode (typischerweise ein `c:BEFEHL`-Kommando, das +einen ganzen Testlauf umspannt) ein: + +```lisp +(dbgopen "" "") +``` + +als erste Rumpf-Form, sowie + +```lisp +(dbgclose) +``` + +vor **jedem** `(exit)`-Aufruf im Rumpf (auch verschachtelt) und zusaetzlich am +Ende des Rumpfs - ausser die letzte Rumpf-Form ist bereits selbst ein `(exit)` +(dann waere ein zweites `dbgclose` ohnehin unerreichbarer Code). + +`--add-open DATEI METHODE` ist unabhaengig von `--method`/`--recursive` nutzbar +und mehrfach angebbar; alle drei Mechanismen lassen sich in einem Aufruf kombinieren. + +## Schalter + +| Schalter | Beschreibung | +|---|---| +| `datei` (Pflicht, Position 1) | Anker-`.lsp`-Datei; ihr Verzeichnis wird immer mitdurchsucht | +| `--method NAME` | Funktion mit `dbgf`/`dbg`/`dbgreturn` instrumentieren (mehrfach angebbar) | +| `--recursive` | Auch alle im Rumpf aufgerufenen projekteigenen Funktionen instrumentieren | +| `--search-dir DIR` | Weiteres Verzeichnis nach `.lsp`-Dateien durchsuchen (mehrfach angebbar). Default: `Lisp/` und das Verzeichnis von `datei` | +| `--max-depth N` | Maximale Rekursionstiefe fuer `--recursive` (Default: `3`) | +| `--exclude NAME` | Funktionsname von `--recursive` ausschliessen (mehrfach angebbar) | +| `--add-open DATEI METHODE` | `dbgopen`/`dbgclose` in `METHODE` (aus `DATEI`) einfuegen (mehrfach angebbar) | +| `--dbg-file NAME` | Dateiname fuer `(dbgopen ...)`. Default: aus dem `--add-open`-Methodennamen abgeleitet (`c:TEST_MUBEA` -> `test_mubea.dbg`) | +| `--dbg-envvar NAME` | Umgebungsvariable fuer `(dbgopen ...)` (Default: `DXFM_LOG`) | +| `--force` | Auch instrumentieren, wenn bereits `(dbgf ...)`/`(dbgopen ...)` vorhanden zu sein scheint | +| `--dry-run` | Nur ein Diff anzeigen, keine Dateien schreiben | + +## Typischer Ablauf + +1. **Immer zuerst `--dry-run`** laufen lassen und das Diff pruefen (mehrere Dateien + koennen betroffen sein, siehe `--recursive`). +2. Ohne `--dry-run` erneut aufrufen - die Dateien werden direkt (in-place) geschrieben. +3. In BricsCAD `(load ...)` neu laden bzw. den Testlauf starten. +4. Die `.dbg`-Datei (per `--add-open` geoeffnet, Pfad = `%DXFM_LOG%\<--dbg-file>`) + auswerten. +5. Da die Dateien in-place geschrieben werden und ueber Git versioniert sind: nach + dem Debuggen die Debug-Aufrufe entweder manuell entfernen oder die Datei(en) + per `git checkout -- ` verwerfen (das Werkzeug selbst hat keinen + "--remove"-Schalter). + +## Beispiele + +Eine einzelne Funktion instrumentieren: + +``` +bin\dbg2lsp.bat tests\test_mubea.lsp --method mubea:build-separator-one --dry-run +``` + +Eine Funktion UND alles, was sie aufruft (bis Tiefe 3, Default): + +``` +bin\dbg2lsp.bat tests\test_mubea.lsp --method mubea:build-kreisel --recursive --dry-run +``` + +Mehrere Einstiegspunkte in einem Lauf, plus Oeffnen/Schliessen der Debug-Datei +im umgebenden Testbefehl - das entspricht genau der Instrumentierung, die fuer +den urspruenglichen Scanner-Bug in `TEST_MUBEA` von Hand gemacht wurde: + +``` +bin\dbg2lsp.bat tests\test_mubea.lsp ^ + --method mubea:build-kreisel ^ + --method mubea:build-vario-one ^ + --method mubea:build-gf-one ^ + --method mubea:build-separator-one ^ + --recursive ^ + --add-open tests\test_mubea.lsp c:TEST_MUBEA +``` + +Bestimmte generische Helfer von der Rekursion ausschliessen (z.B. um `ssg-start`/`ssg-end` +nicht mit anzufassen): + +``` +bin\dbg2lsp.bat tests\test_mubea.lsp --method mubea:build-kreisel --recursive ^ + --exclude ssg-start --exclude ssg-end +``` + +## Funktionsweise / Aufloesung + +- `datei` wird zuerst relativ zum aktuellen Arbeitsverzeichnis, sonst relativ zur + Projektwurzel (erkannt an `.git/` bzw. `Lisp/`, ausgehend von `datei` nach oben + gesucht) aufgeloest. +- Durchsucht werden: das Verzeichnis von `datei`, `Lisp/` (relativ zur Projektwurzel), + jedes `--search-dir` sowie das Verzeichnis jeder `--add-open`-Datei - jeweils + rekursiv nach `*.lsp`. +- Aus all diesen Dateien wird EIN Index `Funktionsname -> (defun ...)` aufgebaut + (case-insensitiv, wie AutoLISP-Symbole zur Laufzeit). `--method`/`--add-open` + muessen daher nicht in `datei` selbst stehen, sondern werden ueberall gefunden. +- Klammer-/String-/Kommentar-Erkennung ist zeichenweise implementiert (kein + vollstaendiger Lisp-Reader) - genuegt aber, um Top-Level-`(defun ...)`-Bloecke, + ihre Parameterliste und die letzte Rumpf-Form zuverlaessig zu erkennen, auch bei + mehrzeiligen Parameterlisten mit Locals nach `/`. +- Alle Aenderungen werden zunaechst als Liste von reinen Text-Einfuegungen + gesammelt (keine Loeschungen) und erst am Ende je Datei angewandt - Positionen + bleiben dadurch unabhaengig von der Bearbeitungsreihenfolge gueltig. + +## Grenzen + +- **Nur die letzte Rumpf-Form wird zu `dbgreturn`.** Fruehe `(exit)`-Ausstiege + MITTEN im Rumpf werden von `--method` nicht mit einem eigenen `dbgreturn` + versehen (nur `--add-open` behandelt `(exit)`-Stellen explizit, dort aber nur + mit `dbgclose`, nicht mit einem geloggten Rueckgabewert). +- **Kein "--remove".** Rueckbau erfolgt manuell oder per `git checkout -- `. +- **`--recursive` kann viele Dateien anfassen** - projekteigene Helfer wie + `ssg-start`/`ssg-val`/`ssg-attrib-merge` werden mitinstrumentiert, sobald sie im + Aufrufgraphen auftauchen. `--max-depth` und `--exclude` begrenzen das gezielt; + ohne `--dry-run`-Kontrolle vorher nicht blind auf grosse Funktionen mit hoher + `--max-depth` loslassen. +- **Mehrfache Definitionen desselben Namens** in unterschiedlichen Dateien: es + gewinnt der erste beim Durchsuchen gefundene (Dateireihenfolge nicht garantiert + sortiert-stabil ueber Verzeichnisgrenzen) - in dieser Codebasis kommt das + ueblicherweise nicht vor. + +## Idempotenz + +Vor dem Einfuegen prueft das Werkzeug, ob die erste Rumpf-Form bereits `(dbgf ...)` +(fuer `--method`) bzw. `(dbgopen ...)` (fuer `--add-open`) ist, und ueberspringt die +Funktion in dem Fall mit einer `[skip]`-Meldung. Mit `--force` wird trotzdem erneut +eingefuegt (fuehrt bei wiederholtem `--force`-Aufruf zu doppelten `dbgf`/`dbg`-Zeilen - +in dem Fall vorher besser die Datei zuruecksetzen). diff --git a/doc/tools.md b/doc/tools.md index 519e38e..92efe5d 100644 --- a/doc/tools.md +++ b/doc/tools.md @@ -35,18 +35,6 @@ Diese Werkzeuge ermöglichen die massenhafte Aufbereitung von einzelnen dxf Date - [--number SIVASNR](#--number-sivasnr-1) - [Uebersicht K-Zuordnung](#uebersicht-k-zuordnung) - [Umgebungsvariablen](#umgebungsvariablen-1) -- [set\_attributs](#set_attributs) - - [Aufruf](#aufruf-2) - - [Datenquellen](#datenquellen-2) - - [Ablauf](#ablauf-2) - - [Konfiguration (attradd.cfg)](#konfiguration-attraddcfg) - - [Attribute Omniflo Kurve (Bogen)](#attribute-omniflo-kurve-bogen) - - [Attribute Omniflo Weiche](#attribute-omniflo-weiche) - - [CSV-Export-Felder (Merkmale)](#csv-export-felder-merkmale) - - [Schalter](#schalter-2) - - [--number SIVASNR / -n SIVASNR](#--number-sivasnr---n-sivasnr) - - [--dry-run / -d](#--dry-run---d) - - [Umgebungsvariablen](#umgebungsvariablen-2) # set_einfuegepunkt @@ -274,153 +262,3 @@ Nur die angegebene 9-stellige Sivasnr verarbeiten. Kombinierbar mit jedem Schalt |---|---| | `DXFM_DATA` | Pfad zu `data/` (JSON + DXF Quelldateien) | | `DXFM_RESULTS` | Pfad zu `results/` (Ausgabe) | - ---- - -# set_attributs - -Fuegt Attribute (ATTDEF) zu allen Omniflo DXF-Dateien hinzu und verpackt die -gesamte Geometrie in eine benannte Blockdefinition. Die Attribut-Tags und deren -Werte werden ueber eine Konfigurationsdatei gesteuert. - -**Voraussetzung:** Die Quelldateien in `data/omniflo/` sollten zuvor durch -`set_einfuegepkt.py` verarbeitet worden sein, damit `$INSBASE` korrekt gesetzt ist. - -## Aufruf - -``` -python lib/set_attributs.py [--number SIVASNR] [--dry-run] -``` - -## Datenquellen - -| Pfad | Inhalt | -|---|---| -| `cfg/attradd.cfg` | Attribut-Konfiguration (Tags und Quellfelder je Elementtyp) | -| `data/json/omniflo_boegen.json` | Liste aller Boegen mit Metadaten | -| `data/json/omniflo_weichen.json` | Liste aller Weichen mit Metadaten | -| `data/omniflo/*.dxf` | DXF-Quelldateien (Basis: Ausgabe von `set_einfuegepkt.py`) | - -## Ablauf - -1. Konfiguration aus `cfg/attradd.cfg` lesen -2. JSON-Daten laden und Lookup-Tabelle (Sivasnr -> Typ + Eintrag) erstellen -3. Fuer jede Sivasnr die DXF-Datei oeffnen, bestehende ATTDEFs entfernen -4. Neue ATTDEFs gemaess Config einfuegen (Layer `ATTRIB`, unterhalb der Geometrie) -5. Alle Entities (Geometrie + ATTDEFs) in eine Blockdefinition verschieben: - - Blockname = Sivasnummer (identisch mit dem Dateinamen ohne `.dxf`) - - Basispunkt der Blockdefinition = `$INSBASE` aus dem DXF-Header -6. Im Modelspace einen INSERT auf den neuen Block einfuegen, mit ATTRIB-Entities - befuellt aus den ATTDEF-Standardwerten (ermoeglicht Attributanzeige per Klick in BricsCAD) -7. Ergebnis nach `results/omniflo/` speichern - -## Ausgabestruktur (DXF) - -``` -BLOCKS - BLOCK "" (Basispunkt = $INSBASE) - - ATTDEF BESCHR = "..." - ATTDEF ARTINR = "..." - ... - ENDBLK - -ENTITIES (Modelspace) - INSERT "" at (0, 0) - ATTRIB BESCHR = "..." - ATTRIB ARTINR = "..." - ... - SEQEND -``` - -Der INSERT im Modelspace traegt die konkreten Attributwerte und ist in BricsCAD -per Klick editierbar. Die Blockdefinition mit ATTDEFs dient als Vorlage fuer -den Block-Import via `import_element_as_block` (omniflo_utils.py). - -## Konfiguration (attradd.cfg) - -Die Config-Datei ist im INI-Format mit Abschnitten je Elementtyp. -Quellwerte koennen sein: -- **JSON-Feldname** (z.B. `ProfilTyp`) - Wert wird aus dem JSON-Eintrag gelesen -- **Fester Text** in Anfuehrungszeichen (z.B. `"Omniflo Kurve"`) -- **Platzhalter** `{sivasnr}` - wird durch die Sivasnr ersetzt - -### Attribute Omniflo Kurve (Bogen) - -| ATTDEF-Tag | Quelle | Beschreibung | -| --- | --- | --- | -| `BESCHR` | `ProfilTyp` | Profilbezeichnung aus JSON | -| `ARTINR` | `Sivasnr` | Sivas-Artikelnummer | -| `TEILEART` | `"Omniflo Kurve"` | Fester Typname | -| `RADIUS` | `Radius` | Kurvenradius in mm | -| `WINKEL` | `KurvenWinkel` | Kurvenwinkel in Grad | -| `SIVASNR_TEF` | `SivasnrTEF` | Sivas-Nr. des TEF-Antriebs (leer = kein Antrieb) | -| `LAYER` | *(aus [layer])* | Layer-Name gemaess Kategorisierung | - -### Attribute Omniflo Weiche - -| ATTDEF-Tag | Quelle | Beschreibung | -| --- | --- | --- | -| `BESCHR` | `ProfilTyp` | Profilbezeichnung aus JSON | -| `ARTINR` | `Sivasnr` | Sivas-Artikelnummer | -| `TEILEART` | `"Omniflo Weiche"` | Fester Typname | -| `WEICHENTYP` | `WeichenTyp` | Weichentyp (Einzelweiche, Doppelweiche, Dreiwegeweiche, ...) | -| `WINKEL` | `KurvenWinkel` | Kurvenwinkel in Grad | -| `RICHTUNG` | `KurvenRichtung` | Richtungskennung (1=Links, 2=Rechts, 3=Beide, 7=Dreiweg) | -| `SIVASNR_TEF` | `SivasnrTEF` | Sivas-Nr. des TEF-Antriebs (leer = kein Antrieb) | -| `LAYER` | *(aus [layer])* | Layer-Name gemaess Kategorisierung | - -### CSV-Export-Felder (Merkmale) - -Die Attribute werden beim CSV-Export (`export_csv.py`, aufgerufen ueber `c:EXPORTCSV`) wie folgt in CSV-Merkmale ueberfuehrt. -CSV-Format: `Elementnummer;TeileArt;TeileId;Bezeichnung;Anzahl;Merkmale` - -**Omniflo Kurve:** - -| Merkmal | Quelle | Typ | -| --- | --- | --- | -| `Kurvenwinkel` | JSON `KurvenWinkel` | float | -| `Radius` | JSON `Radius` | float | -| `Höhe` | Attribut `HOEHE` / Z-Koordinate | string | -| `Drehung` | Attribut `DREHUNG` / CAD-Rotation | float | -| `SivasNummer` | JSON `Sivasnr` | string | - -**Omniflo Weiche:** - -| Merkmal | Quelle | Typ | -| --- | --- | --- | -| `Weichentyp` | JSON `WeichenTyp` | string | -| `Richtung` | JSON `KurvenRichtung` | string | -| `Weichenwinkel` | JSON `KurvenWinkel` | float | -| `Höhe` | Attribut `HOEHE` / Z-Koordinate | string | -| `Drehung` | Attribut `DREHUNG` / CAD-Rotation | float | -| `Antrieb Kurve` | `SivasnrTEF` != null | bool | -| `SivasNummer` | JSON `Sivasnr` | string | - - -Höhe und Drehung werden beim Export selbst aus den Blockinformationen entnommen und dann ins json geschrieben. - -| ATTDEF-Tag | Quelle | Beschreibung | -| --- | --- | --- | -| `HOEHE` | `"2000"` | Montagehoehe in mm (wird beim Export aus Z-Koordinate aktualisiert) | -| `DREHUNG` | `"0"` | Drehwinkel in Grad (wird beim Export aus CAD-Rotation aktualisiert) | - -## Schalter - -### --number SIVASNR / -n SIVASNR - -Nur die angegebene Sivasnr verarbeiten. - -### --dry-run / -d - -Nur anzeigen welche Attribute gesetzt wuerden, ohne DXF-Dateien zu schreiben. - -## Umgebungsvariablen - -| Variable | Verwendung | -|---|---| -| `DXFM_DATA` | Pfad zu `data/` (JSON + DXF Quelldateien) | -| `DXFM_CFG` | Pfad zu `cfg/` (attradd.cfg) | -| `DXFM_RESULTS` | Pfad zu `results/` (Ausgabe nach `results/omniflo/`) | - -Alle drei haben Fallback auf relative Pfade vom Projektverzeichnis. diff --git a/lib/dbg2lsp.py b/lib/dbg2lsp.py new file mode 100644 index 0000000..cc21d74 --- /dev/null +++ b/lib/dbg2lsp.py @@ -0,0 +1,725 @@ +""" +Instrumentiert AutoLISP-Funktionen (.lsp) automatisch mit den ssg_dbg.lsp- +Routinen (dbgf, dbg, dbgreturn, dbgopen, dbgclose) - so wie es beim manuellen +Debuggen (siehe z.B. Lisp/KreiselInsert.lsp::ils-eckrad-insert) bereits +gemacht wird. + +Fuer eine gegebene Funktion (--method) wird eingefuegt: + - (dbgf "funktionsname") direkt als erste Form im Rumpf + - (dbg 'parameter) je Eingabeparameter (die Locals nach "/" NICHT) + - (dbgreturn ) die letzte Form des Rumpfs wird + eingeklammert, damit ihr Rueckgabe- + wert mitgeloggt wird + +Mit --recursive wird zusaetzlich jede Funktion instrumentiert, die im Rumpf +(auch verschachtelt, aber NICHT innerhalb von 'quotierten Listen) aufgerufen +wird - vorausgesetzt, ihr (defun ...) laesst sich in einem der --search-dir- +Verzeichnisse finden (Bibliotheks-/AutoLISP-Builtins wie princ, command, +strcat etc. haben dort kein defun und werden dadurch automatisch NICHT +angefasst). Der Aufrufgraph wird rekursiv weiterverfolgt (mit --max-depth +begrenzt), einmal besuchte Funktionen werden nicht doppelt instrumentiert. + +Mit --add-open DATEI METHODE wird in der angegebenen Methode zusaetzlich + - (dbgopen "" "") als erste Rumpf-Form + - (dbgclose) vor jedem (exit)-Aufruf im Rumpf + UND am Ende des Rumpfs (falls die + letzte Form nicht bereits (exit) ist) +eingefuegt - fuer den Einstiegspunkt (typischerweise ein c:BEFEHL-Kommando), +der die .dbg-Datei fuer einen ganzen Testlauf oeffnen/schliessen soll. +--add-open ist unabhaengig von --method/--recursive nutzbar und mehrfach +angebbar. + +Aufruf-Beispiele: + + python dbg2lsp.py tests/test_mubea.lsp --method mubea:build-separator-one + + python dbg2lsp.py tests/test_mubea.lsp --method mubea:build-kreisel --recursive + + python dbg2lsp.py tests/test_mubea.lsp --add-open tests/test_mubea.lsp c:TEST_MUBEA + + python dbg2lsp.py tests/test_mubea.lsp --method mubea:build-kreisel --recursive \\ + --add-open tests/test_mubea.lsp c:TEST_MUBEA --dry-run + +Details, Grenzen und weitere Beispiele: siehe doc/dbg2lsp.md +""" + +import argparse +import difflib +import os +import sys + +# --------------------------------------------------------------------------- +# ssg_dbg.lsp-Funktionen, die NIE instrumentiert oder in den Aufrufgraphen +# aufgenommen werden duerfen - sonst wuerde --recursive versuchen, dbgf +# selbst mit dbgf zu instrumentieren, sobald es auf eine bereits (manuell +# oder durch einen frueheren Lauf) instrumentierte Funktion trifft. +# --------------------------------------------------------------------------- +DBG_FRAMEWORK_NAMES = { + "dbgf", "dbg", "dbgp", "dbgmsg", "dbgreturn", "dbgopen", "dbgclose", + "dbgflush", "dbgon", "dbgoff", + "dbg-flush", "dbg-bufwrite", "dbg-tabs", "dbg-tostring", "dbg-list-tostring", +} + +DEFAULT_DBG_ENVVAR = "DXFM_LOG" + + +# --------------------------------------------------------------------------- +# Zeichenweises Scannen mit String-/Kommentar-Bewusstsein (identische Logik +# wie beim manuellen Klammer-Balance-Check waehrend der Mubea-Instrumentierung) +# --------------------------------------------------------------------------- + +class LispSyntaxError(Exception): + pass + + +def _skip_string(text, i): + """i zeigt auf das oeffnende '\"'. Gibt den Index NACH dem schliessenden '\"' zurueck.""" + n = len(text) + i += 1 + while i < n: + c = text[i] + if c == "\\": + i += 2 + continue + if c == '"': + return i + 1 + i += 1 + raise LispSyntaxError("Nicht geschlossener String ab Position %d" % i) + + +def _skip_line_comment(text, i): + nl = text.find("\n", i) + return len(text) if nl == -1 else nl + 1 + + +def find_matching_paren(text, open_idx): + """open_idx zeigt auf '('. Gibt den Index der zugehoerigen ')' zurueck.""" + n = len(text) + if text[open_idx] != "(": + raise LispSyntaxError("Position %d ist keine '('" % open_idx) + depth = 0 + i = open_idx + while i < n: + c = text[i] + if c == '"': + i = _skip_string(text, i) + continue + if c == ";": + i = _skip_line_comment(text, i) + continue + if c == "(": + depth += 1 + elif c == ")": + depth -= 1 + if depth == 0: + return i + i += 1 + raise LispSyntaxError("Keine schliessende Klammer fuer '(' ab Position %d gefunden" % open_idx) + + +def _skip_ws_and_comments(text, i): + n = len(text) + while i < n: + c = text[i] + if c in " \t\r\n": + i += 1 + continue + if c == ";": + i = _skip_line_comment(text, i) + continue + break + return i + + +_TOKEN_STOP_CHARS = set(" \t\r\n()\";'") + + +def read_next_token(text, i, limit): + """Liest ab i (nach Whitespace/Kommentaren) das naechste Token (Symbol/Zahl) + bis zum naechsten Trennzeichen. Gibt (token, end_index) oder (None, i) zurueck, + wenn an Position i eine '(' / '"' / EOF folgt (kein einfaches Token).""" + i = _skip_ws_and_comments(text, i) + if i >= limit: + return None, i + if text[i] in "(\"'": + return None, i + start = i + while i < limit and text[i] not in _TOKEN_STOP_CHARS: + i += 1 + if i == start: + return None, i + return text[start:i], i + + +def iter_top_level_spans(text, start, end): + """Liefert (form_start, form_end) fuer jede balancierte Top-Level-Form + zwischen start und end (end exklusiv, typischerweise die Position der + schliessenden ')' des umgebenden defun). Reine Atome (Symbole/Zahlen ohne + umschliessende Klammer) werden ebenfalls als Form erkannt.""" + spans = [] + i = start + while i < end: + i = _skip_ws_and_comments(text, i) + if i >= end: + break + c = text[i] + if c == "(": + close = find_matching_paren(text, i) + spans.append((i, close + 1)) + i = close + 1 + elif c == "'": + # Quotiertes Atom/Liste - als eine Form ab dem Apostroph behandeln. + j = i + 1 + if j < end and text[j] == "(": + close = find_matching_paren(text, j) + spans.append((i, close + 1)) + i = close + 1 + else: + tok, tend = read_next_token(text, j, end) + spans.append((i, tend)) + i = tend + elif c == '"': + send = _skip_string(text, i) + spans.append((i, send)) + i = send + else: + tok, tend = read_next_token(text, i, end) + if tok is None: + i += 1 + else: + spans.append((i, tend)) + i = tend + return spans + + +# --------------------------------------------------------------------------- +# (defun ...) - Erkennung +# --------------------------------------------------------------------------- + +class DefunInfo: + def __init__(self, name, path, defun_start, defun_end, + arglist_start, arglist_end, body_start, body_end, + params, locals_): + self.name = name + self.path = path + self.defun_start = defun_start + self.defun_end = defun_end # Position NACH der schliessenden ')' des defun + self.arglist_start = arglist_start + self.arglist_end = arglist_end + self.body_start = body_start + self.body_end = body_end # Position der schliessenden ')' des defun (exklusiv) + self.params = params + self.locals = locals_ + + def __repr__(self): + return "DefunInfo(%r in %s)" % (self.name, self.path) + + +def _parse_arglist(text, arglist_start, arglist_end): + """arglist_start zeigt auf '(' der Parameterliste, arglist_end auf die + zugehoerige ')'. Gibt (params, locals) zurueck - locals ist alles nach + einem "/"-Trenner (leer, falls keiner vorhanden).""" + params = [] + locals_ = [] + seen_slash = False + i = arglist_start + 1 + while i < arglist_end: + tok, tend = read_next_token(text, i, arglist_end) + if tok is None: + # z.B. eine verschachtelte Liste als Defaultwert (in AutoLISP + # eigentlich nicht ueblich) - ueberspringen statt abzubrechen. + i = _skip_ws_and_comments(text, i) + if i < arglist_end and text[i] == "(": + i = find_matching_paren(text, i) + 1 + continue + i += 1 + continue + if tok == "/": + seen_slash = True + elif seen_slash: + locals_.append(tok) + else: + params.append(tok) + i = tend + return params, locals_ + + +def find_defuns_in_text(text, path): + """Findet alle (defun NAME (...) ...) Top-Level-Definitionen im Text.""" + defuns = [] + i = 0 + n = len(text) + while i < n: + c = text[i] + if c == '"': + i = _skip_string(text, i) + continue + if c == ";": + i = _skip_line_comment(text, i) + continue + if c == "(": + close = find_matching_paren(text, i) + head_tok, head_end = read_next_token(text, i + 1, close) + if head_tok is not None and head_tok.lower() == "defun": + name_tok, name_end = read_next_token(text, head_end, close) + if name_tok: + al_start = _skip_ws_and_comments(text, name_end) + if al_start < close and text[al_start] == "(": + al_end = find_matching_paren(text, al_start) + params, locals_ = _parse_arglist(text, al_start, al_end) + body_start = al_end + 1 + defuns.append(DefunInfo( + name=name_tok, path=path, + defun_start=i, defun_end=close + 1, + arglist_start=al_start, arglist_end=al_end, + body_start=body_start, body_end=close, + params=params, locals_=locals_, + )) + i = close + 1 + continue + i += 1 + return defuns + + +def build_defun_index(paths): + """path -> Dateitext (einmal eingelesen) und name.lower() -> DefunInfo + (erster Treffer gewinnt; mehrfach definierte Namen sind in dieser + Code-Basis nicht vorgesehen).""" + file_texts = {} + index = {} + for path in paths: + with open(path, "r", encoding="utf-8") as f: + text = f.read() + file_texts[path] = text + for d in find_defuns_in_text(text, path): + index.setdefault(d.name.lower(), d) + return file_texts, index + + +# --------------------------------------------------------------------------- +# Aufruf-Erkennung (fuer --recursive und --add-open/exit-Suche) +# --------------------------------------------------------------------------- + +def scan_calls(text, start, end): + """Liefert eine Liste von (head_name, call_start, call_end) fuer jede + NICHT quotierte Aufruf-Form (...) im Bereich [start, end) - beliebig + verschachtelt, aber nicht innerhalb von 'quotierten Listen (deren Inhalt + ist reine Datenliste, dort kann nichts "aufgerufen" werden).""" + calls = [] + + def walk(s, e): + i = s + while i < e: + c = text[i] + if c == '"': + i = _skip_string(text, i) + continue + if c == ";": + i = _skip_line_comment(text, i) + continue + if c == "'": + j = i + 1 + if j < e and text[j] == "(": + # quotierte Liste: reine Daten, nicht weiter absteigen + close = find_matching_paren(text, j) + i = close + 1 + continue + i = j + continue + if c == "(": + close = find_matching_paren(text, i) + head_tok, head_end = read_next_token(text, i + 1, close) + if head_tok and head_tok[0] not in "0123456789.\"'": + calls.append((head_tok, i, close + 1)) + walk(i + 1, close) + i = close + 1 + continue + i += 1 + + walk(start, end) + return calls + + +# --------------------------------------------------------------------------- +# Hilfsfunktionen fuer Einfuegetexte +# --------------------------------------------------------------------------- + +def _line_indent(text, pos): + """Fuehrender Whitespace der Zeile, in der pos liegt (fuer die Einrueckung + neu eingefuegter Debug-Zeilen).""" + nl = text.rfind("\n", 0, pos) + line_start = nl + 1 + i = line_start + while i < pos and text[i] in " \t": + i += 1 + indent = text[line_start:i] + return indent if indent else " " + + +def _body_indent(text, body_start, body_end): + """Einrueckung der ERSTEN echten Rumpf-Form. NICHT einfach _line_indent + auf body_start anwenden: bei mehrzeiligen Parameterlisten (z.B. + "(defun foo (a b /\n c d)" - "/"-Locals auf eigener Zeile) liegt + body_start mitten in der Fortsetzungszeile der Parameterliste, deren + Ausrichtung nichts mit der eigentlichen Rumpf-Einrueckung zu tun hat.""" + first = _skip_ws_and_comments(text, body_start) + if first >= body_end: + return " " + return _line_indent(text, first) + + +def _already_has_call(text, body_start, body_end, head_name): + spans = iter_top_level_spans(text, body_start, body_end) + if not spans: + return False + fs, fe = spans[0] + if text[fs] != "(": + return False + tok, _ = read_next_token(text, fs + 1, fe) + return bool(tok) and tok.lower() == head_name.lower() + + +# --------------------------------------------------------------------------- +# Edit-Sammlung: (position, prioritaet, text) - reine Einfuegungen, angewandt +# in absteigender Position (und bei Gleichstand nach Prioritaet), damit +# fruehere Positionen gueltig bleiben. +# --------------------------------------------------------------------------- + +class EditSet: + def __init__(self): + self.edits = {} # path -> list[(pos, prio, text)] + + def insert(self, path, pos, prio, text): + self.edits.setdefault(path, []).append((pos, prio, text)) + + def apply(self, file_texts): + """Gibt {path: neuer_text} fuer alle Dateien mit mindestens einer + Einfuegung zurueck.""" + result = {} + for path, items in self.edits.items(): + text = file_texts[path] + # Bei gleicher Position: kleinere Prioritaet zuerst im Ergebnis- + # text (prio dient als "vor/nach"-Reihenfolge an derselben Stelle). + items_sorted = sorted(items, key=lambda t: (t[0], t[1])) + out = [] + last = 0 + # Gruppieren nach Position, damit mehrere Einfuegungen an + # derselben Stelle in Prioritaets-Reihenfolge zusammenkommen. + grouped = {} + order = [] + for pos, prio, ins_text in items_sorted: + if pos not in grouped: + grouped[pos] = [] + order.append(pos) + grouped[pos].append(ins_text) + for pos in order: + out.append(text[last:pos]) + out.extend(grouped[pos]) + last = pos + out.append(text[last:]) + result[path] = "".join(out) + return result + + +# --------------------------------------------------------------------------- +# Instrumentierung +# --------------------------------------------------------------------------- + +def instrument_method(defun, file_texts, edits, force=False, log=print): + """Fuegt (dbgf ...), (dbg 'param) je Parameter und (dbgreturn ...) um die + letzte Rumpf-Form ein.""" + text = file_texts[defun.path] + + if not force and _already_has_call(text, defun.body_start, defun.body_end, "dbgf"): + log(" [skip] %s (%s) ist bereits instrumentiert (dbgf vorhanden) - --force zum Erzwingen" % + (defun.name, defun.path)) + return False + + indent = _body_indent(text, defun.body_start, defun.body_end) + lines = ['(dbgf "%s")' % defun.name] + for p in defun.params: + lines.append("(dbg '%s)" % p) + # Kein Trailing-Newline/-Indent: body_start hat im Original bereits sein + # eigenes "\n" vor der ersten Rumpf-Form, das bleibt unveraendert + # stehen und schliesst direkt an die letzte hier eingefuegte Zeile an. + insertion = "\n" + "\n".join(indent + l for l in lines) + edits.insert(defun.path, defun.body_start, 10, insertion) + + spans = iter_top_level_spans(text, defun.body_start, defun.body_end) + if spans: + last_start, last_end = spans[-1] + edits.insert(defun.path, last_start, 20, "(dbgreturn ") + edits.insert(defun.path, last_end, 21, ")") + else: + log(" [warn] %s (%s) hat einen leeren Rumpf - kein dbgreturn eingefuegt" % + (defun.name, defun.path)) + + log(" [ok] %s (%s): dbgf + %d x dbg + dbgreturn" % + (defun.name, defun.path, len(defun.params))) + return True + + +def instrument_add_open(defun, file_texts, edits, dbg_file, dbg_envvar, force=False, log=print): + """Fuegt (dbgopen "dbg_file" "dbg_envvar") als erste Rumpf-Form ein sowie + (dbgclose) vor jedem (exit)-Aufruf und (sofern die letzte Rumpf-Form nicht + selbst ein (exit) ist) am Ende des Rumpfs.""" + text = file_texts[defun.path] + + if not force and _already_has_call(text, defun.body_start, defun.body_end, "dbgopen"): + log(" [skip] %s (%s) hat bereits (dbgopen ...) - --force zum Erzwingen" % + (defun.name, defun.path)) + return False + + indent = _body_indent(text, defun.body_start, defun.body_end) + # Kein Trailing-Newline/-Indent (siehe instrument_method): die naechste + # Einfuegung an derselben Position (z.B. dbgf durch --method) oder das + # Original selbst liefert das fuehrende "\n" bereits mit. + open_line = "\n" + indent + '(dbgopen "%s" "%s")' % (dbg_file, dbg_envvar) + edits.insert(defun.path, defun.body_start, 5, open_line) + + calls = scan_calls(text, defun.body_start, defun.body_end) + exit_calls = [c for c in calls if c[0].lower() == "exit"] + for _, cstart, _cend in exit_calls: + cindent = _line_indent(text, cstart) + edits.insert(defun.path, cstart, 15, "(dbgclose)\n" + cindent) + + spans = iter_top_level_spans(text, defun.body_start, defun.body_end) + last_is_exit = False + if spans: + last_start, last_end = spans[-1] + tok, _ = read_next_token(text, last_start + 1, last_end) if text[last_start] == "(" else (None, 0) + last_is_exit = bool(tok) and tok.lower() == "exit" + if not last_is_exit: + edits.insert(defun.path, defun.body_end, 25, indent + "(dbgclose)\n") + + log(" [ok] %s (%s): dbgopen + dbgclose (%d exit-Stellen + Rumpfende)" % + (defun.name, defun.path, len(exit_calls))) + return True + + +# --------------------------------------------------------------------------- +# Rekursion ueber den Aufrufgraphen +# --------------------------------------------------------------------------- + +def instrument_recursive(start_defun, index, file_texts, edits, max_depth, exclude, force, log): + visited = set() + + def recurse(defun, depth): + key = defun.name.lower() + if key in visited: + return + visited.add(key) + + instrument_method(defun, file_texts, edits, force=force, log=log) + + if depth >= max_depth: + log(" [info] max-depth (%d) bei %s erreicht - keine weiteren Aufrufe verfolgt" % + (max_depth, defun.name)) + return + + text = file_texts[defun.path] + calls = scan_calls(text, defun.body_start, defun.body_end) + seen_here = set() + for head, _s, _e in calls: + head_l = head.lower() + if head_l in seen_here: + continue + seen_here.add(head_l) + if head_l in DBG_FRAMEWORK_NAMES or head_l in exclude: + continue + callee = index.get(head_l) + if callee is None or callee.name.lower() == key: + continue + recurse(callee, depth + 1) + + recurse(start_defun, 0) + + +# --------------------------------------------------------------------------- +# Pfad-/Umgebungshilfen +# --------------------------------------------------------------------------- + +def find_repo_root(start_path): + cur = os.path.abspath(start_path) + if os.path.isfile(cur): + cur = os.path.dirname(cur) + while True: + if os.path.isdir(os.path.join(cur, ".git")) or os.path.isdir(os.path.join(cur, "Lisp")): + return cur + parent = os.path.dirname(cur) + if parent == cur: + return None + cur = parent + + +def resolve_file(path, repo_root): + if os.path.isfile(path): + return os.path.abspath(path) + if repo_root: + candidate = os.path.join(repo_root, path) + if os.path.isfile(candidate): + return os.path.abspath(candidate) + return None + + +def collect_lsp_files(dirs): + paths = [] + seen = set() + for d in dirs: + if not os.path.isdir(d): + continue + for root, _dirnames, filenames in os.walk(d): + for fn in filenames: + if fn.lower().endswith(".lsp"): + full = os.path.abspath(os.path.join(root, fn)) + if full not in seen: + seen.add(full) + paths.append(full) + return paths + + +def dbg_file_name_for(method_name): + """Leitet einen .dbg-Dateinamen aus einem Methodennamen ab, z.B. + "c:TEST_MUBEA" -> "test_mubea.dbg", "mubea:build-kreisel" -> "build-kreisel.dbg".""" + base = method_name.split(":")[-1] + return base.lower() + ".dbg" + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + +def main(argv=None): + parser = argparse.ArgumentParser( + prog="dbg2lsp.py", + description="Instrumentiert AutoLISP-Funktionen mit ssg_dbg.lsp (dbgf/dbg/dbgreturn/dbgopen/dbgclose).", + ) + parser.add_argument("file", help="Anker-.lsp-Datei (Verzeichnis wird immer mit durchsucht)") + parser.add_argument("--method", action="append", default=[], + metavar="NAME", + help="Funktionsname, der mit dbgf/dbg/dbgreturn instrumentiert wird. Mehrfach angebbar.") + parser.add_argument("--recursive", action="store_true", + help="Auch alle im Rumpf aufgerufenen projekteigenen Funktionen instrumentieren (rekursiv)") + parser.add_argument("--search-dir", action="append", default=[], + metavar="DIR", + help="Weiteres Verzeichnis nach .lsp-Dateien durchsuchen (fuer --recursive). " + "Default: Lisp/ und das Verzeichnis von FILE.") + parser.add_argument("--max-depth", type=int, default=3, + help="Maximale Rekursionstiefe fuer --recursive (Default: 3)") + parser.add_argument("--exclude", action="append", default=[], + metavar="NAME", + help="Funktionsname von --recursive ausschliessen (mehrfach angebbar)") + parser.add_argument("--add-open", nargs=2, action="append", default=[], + metavar=("DATEI", "METHODE"), + help="(dbgopen ...)/(dbgclose) in METHODE (aus DATEI) einfuegen. Mehrfach angebbar.") + parser.add_argument("--dbg-file", default=None, + help="Dateiname fuer (dbgopen ...) (Default: aus dem --add-open-Methodennamen abgeleitet, z.B. test_mubea.dbg)") + parser.add_argument("--dbg-envvar", default=DEFAULT_DBG_ENVVAR, + help='Umgebungsvariable fuer (dbgopen ...) (Default: "%s")' % DEFAULT_DBG_ENVVAR) + parser.add_argument("--force", action="store_true", + help="Auch instrumentieren, wenn bereits (dbgf ...)/(dbgopen ...) vorhanden zu sein scheint") + parser.add_argument("--dry-run", action="store_true", + help="Nur ein Diff anzeigen, keine Dateien schreiben") + args = parser.parse_args(argv) + + repo_root = find_repo_root(args.file) or find_repo_root(".") + main_file = resolve_file(args.file, repo_root) + if main_file is None: + print("FEHLER: Datei nicht gefunden: %s" % args.file, file=sys.stderr) + return 1 + + search_dirs = [os.path.dirname(main_file)] + if repo_root: + search_dirs.append(os.path.join(repo_root, "Lisp")) + for d in args.search_dir: + resolved = d if os.path.isdir(d) else os.path.join(repo_root or ".", d) + search_dirs.append(resolved) + for f, _m in args.add_open: + resolved = resolve_file(f, repo_root) + if resolved: + search_dirs.append(os.path.dirname(resolved)) + + lsp_files = set(collect_lsp_files(search_dirs)) + lsp_files.add(main_file) + lsp_files = sorted(lsp_files) + + file_texts, index = build_defun_index(lsp_files) + + if not args.method and not args.add_open: + print("FEHLER: mindestens --method oder --add-open angeben.", file=sys.stderr) + return 1 + + edits = EditSet() + exclude = {n.lower() for n in args.exclude} + touched_any = False + + for method_name in args.method: + defun = index.get(method_name.lower()) + if defun is None: + print("FEHLER: Funktion '%s' nicht gefunden (durchsucht: %s)" % + (method_name, ", ".join(lsp_files)), file=sys.stderr) + continue + print("== --method %s ==" % method_name) + if args.recursive: + instrument_recursive(defun, index, file_texts, edits, + max_depth=args.max_depth, exclude=exclude, + force=args.force, log=print) + else: + instrument_method(defun, file_texts, edits, force=args.force, log=print) + touched_any = True + + for file_arg, method_name in args.add_open: + resolved = resolve_file(file_arg, repo_root) + if resolved is None: + print("FEHLER: Datei nicht gefunden: %s" % file_arg, file=sys.stderr) + continue + # Falls die Datei noch nicht im Index ist (z.B. ausserhalb der + # Search-Dirs), jetzt einlesen und mit aufnehmen. + if resolved not in file_texts: + with open(resolved, "r", encoding="utf-8") as f: + file_texts[resolved] = f.read() + for d in find_defuns_in_text(file_texts[resolved], resolved): + index.setdefault(d.name.lower(), d) + defun = index.get(method_name.lower()) + if defun is None or defun.path != resolved: + # ggf. gleichnamige Funktion aus anderer Datei ignorieren - + # explizit in der genannten Datei suchen. + local_defuns = find_defuns_in_text(file_texts[resolved], resolved) + defun = next((d for d in local_defuns if d.name.lower() == method_name.lower()), None) + if defun is None: + print("FEHLER: Funktion '%s' nicht in %s gefunden" % (method_name, file_arg), file=sys.stderr) + continue + print("== --add-open %s %s ==" % (file_arg, method_name)) + dbg_file = args.dbg_file or dbg_file_name_for(method_name) + instrument_add_open(defun, file_texts, edits, dbg_file=dbg_file, dbg_envvar=args.dbg_envvar, + force=args.force, log=print) + touched_any = True + + if not touched_any: + return 1 + + changed = edits.apply(file_texts) + if not changed: + print("Keine Aenderungen vorgenommen.") + return 0 + + for path, new_text in changed.items(): + old_text = file_texts[path] + if args.dry_run: + rel = os.path.relpath(path, repo_root) if repo_root else path + diff = difflib.unified_diff( + old_text.splitlines(keepends=True), + new_text.splitlines(keepends=True), + fromfile=rel, tofile=rel, + ) + sys.stdout.writelines(diff) + else: + with open(path, "w", encoding="utf-8", newline="") as f: + f.write(new_text) + print("Geschrieben: %s" % path) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/lib/export_csv.py b/lib/export_csv.py index 7debb7a..8efd884 100644 --- a/lib/export_csv.py +++ b/lib/export_csv.py @@ -55,6 +55,7 @@ from export_planquadrat import load_planquadrat_config, resolve_origins, compute from export_neighbors import ( load_neighbor_tolerance_mm, load_omniflo_cell_size_mm, + load_collision_debug_target, compute_neighbor_ids, compute_neighbor_errors, ) @@ -63,6 +64,7 @@ BLOCKPATTERNS = load_patterns() PLANQUADRAT_CFG = load_planquadrat_config() NEIGHBOR_TOLERANCE_MM = load_neighbor_tolerance_mm() NEIGHBOR_OMNIFLO_CELL_SIZE_MM = load_omniflo_cell_size_mm() +NEIGHBOR_DEBUG_TARGET = load_collision_debug_target() # --------------------------------------------------------------------------- @@ -186,6 +188,33 @@ def build_gerade_merkmale(block): } +def build_sensor_merkmale(block): + """Merkmale-Dict fuer einen eigenstaendigen Separator_SP/S-LP- oder + Scanner-Block (siehe pattern_separator/pattern_scanner in cfg/export.cfg). + Diese Bloecke haben (anders als Kreisel/GF/VF) kein ID-Attribut - + ZUORDNUNG/ARTINR/LAYER sind die einzigen ATTDEFs (siehe ils-insert-sensor/ + mubea:build-separator-one).""" + attribs = block.get("attribs", {}) + return { + "Zuordnung": attribs.get("ZUORDNUNG", ""), + "ARTINR": attribs.get("ARTINR", ""), + "Höhe": get_hoehe(block), + "Drehung": get_drehung(block), + } + + +def format_sensor_id(prefix, n): + """Erzeugt eine laufende TeileId fuer Separator/Scanner-Zeilen im CSV-Export. + + Diese Bloecke haben KEIN persistiertes ID-Attribut in der Zeichnung + (anders als Kreisel/GF/VF, deren "ID" von ssg-id-generate/ssg-id-check-all + beim Insert bzw. vor jedem Export vergeben wird) - der Praefix macht das + sichtbar: eine rein fuer diesen Exportlauf erzeugte, NICHT ueber mehrere + Exportlaeufe hinweg stabile laufende Nummer, keine echte Block-ID. + """ + return f"{prefix}-{n:04d}" + + # --------------------------------------------------------------------------- # Omniflo Sum-Zeile (Anzahl Boegen/Weichentypen, Gesamtlaenge AP110/AP60) # --------------------------------------------------------------------------- @@ -311,7 +340,7 @@ SKIP_BLOCKS = set(BLOCKPATTERNS.get("pattern_ks_subblocks", ["K1", "K2", "K3", " # Bloecke verarbeiten (einfache Liste, keine Summierung) # --------------------------------------------------------------------------- -def process_blocks(blocks, lookup): +def process_blocks(blocks, lookup, dbg=None): # x_origin_mm/y_origin_mm einmalig fuer diesen Exportlauf aufloesen (siehe # export_planquadrat.resolve_origins): automatisch aus den Bloecken, falls # nicht explizit in [Planquadrate] konfiguriert. @@ -327,6 +356,8 @@ def process_blocks(blocks, lookup): vf_count = 0 gf_count = 0 strecke_modul_count = 0 + separator_count = 0 + scanner_count = 0 # Zaehlung fuer die Omniflo-Sum-Zeile (nur Bogen/Weiche/Gerade) cnt_wk = cnt_einzel = cnt_delta = cnt_doppel = cnt_stern = 0 @@ -477,6 +508,40 @@ def process_blocks(blocks, lookup): }) continue + # Separator (Separator_SP/S-LP) - eigene TeileArt-Zeile mit laufender + # ID (siehe format_sensor_id; kein echtes ID-Attribut am Block, anders + # als Kreisel/GF/VF). EXPORTSIVAS schliesst denselben Blockpattern + # bewusst aus (siehe export_sivas.py) - dort zaehlt bereits + # ANZAHL_SEPARATOR am zugehoerigen Kreisel/GF/VF. + if matches_any(bname, BLOCKPATTERNS.get("pattern_separator", [])): + elem_nr += 1 + separator_count += 1 + items.append({ + "nr": elem_nr, + "teileart": "ILS 2.0 Separator", + "teileid": format_sensor_id("SEP", separator_count), + "bezeichnung": f"Separator :{separator_count}", + "planquadrat": planquadrat_column(block, planquadrat_cfg), + "merkmale": build_sensor_merkmale(block), + **bbox_columns(block), + }) + continue + + # Scanner - eigene TeileArt-Zeile, sonst analog zu Separator oben. + if matches_any(bname, BLOCKPATTERNS.get("pattern_scanner", [])): + elem_nr += 1 + scanner_count += 1 + items.append({ + "nr": elem_nr, + "teileart": "ILS 2.0 Scanner", + "teileid": format_sensor_id("SCN", scanner_count), + "bezeichnung": f"Scanner :{scanner_count}", + "planquadrat": planquadrat_column(block, planquadrat_cfg), + "merkmale": build_sensor_merkmale(block), + **bbox_columns(block), + }) + continue + if has_omniflo: elem_nr += 1 items.append({ @@ -498,7 +563,7 @@ def process_blocks(blocks, lookup): }) neighbor_ids = compute_neighbor_ids( - items, NEIGHBOR_TOLERANCE_MM, NEIGHBOR_OMNIFLO_CELL_SIZE_MM) + items, NEIGHBOR_TOLERANCE_MM, NEIGHBOR_OMNIFLO_CELL_SIZE_MM, dbg=dbg) neighbor_errors = compute_neighbor_errors(items, neighbor_ids) for item, nachbarn, fehler in zip(items, neighbor_ids, neighbor_errors): item["nachbarn"] = nachbarn @@ -558,7 +623,31 @@ def main(): print(f"[export_csv] {len(blocks)} Bloecke geladen, " f"{len(boegen)} Boegen, {len(weichen)} Weichen im Katalog.") - items = process_blocks(blocks, lookup) + # Optionales Kollisions-/Nachbarschafts-Debuglog (siehe [Nachbarschaft] -> + # debug_log in cfg/export.cfg). dbg ist ein Callable(str), das eine Zeile + # in die .dbg-Datei schreibt - oder None, wenn das Log deaktiviert ist. + dbg_file = None + dbg = None + if NEIGHBOR_DEBUG_TARGET: + try: + os.makedirs(os.path.dirname(NEIGHBOR_DEBUG_TARGET), exist_ok=True) + dbg_file = open(NEIGHBOR_DEBUG_TARGET, "w", encoding="utf-8") + dbg = lambda msg: dbg_file.write(msg + "\n") + dbg(f"# Kollisions-/Nachbarschafts-Debuglog (export_csv.py)") + dbg(f"# Quelle: {raw_json_path}") + dbg(f"# Bloecke im Roh-JSON: {len(blocks)}") + dbg("") + except OSError as exc: + print(f"[export_csv] WARNUNG: Debuglog nicht schreibbar " + f"({NEIGHBOR_DEBUG_TARGET}): {exc}") + dbg_file = None + dbg = None + + items = process_blocks(blocks, lookup, dbg=dbg) + + if dbg_file: + dbg_file.close() + print(f"[export_csv] Kollisions-Debuglog geschrieben: {NEIGHBOR_DEBUG_TARGET}") header = ("Elementnummer;TeileArt;TeileId;Bezeichnung;Planquadrat;Anzahl;Position;Boundingbox;" "Insertpoint;K1;K2;K3;K4;Nachbarn;Fehler;Merkmale") diff --git a/lib/export_neighbors.py b/lib/export_neighbors.py index 76a84d0..e442182 100644 --- a/lib/export_neighbors.py +++ b/lib/export_neighbors.py @@ -33,6 +33,8 @@ MIN_PARTNER) - Ergebnis ist die CSV-Spalte "Fehler" in export_csv.py. Nur von export_csv.py genutzt (EXPORTCSV), nicht von export_sivas.py. """ +import os + from export_blockpatterns import cfg_path_from_env, load_export_cfg KREISEL_TEILEARTEN = {"ILS 2.0 Kreisel", "ILS 2.0 Eckrad"} @@ -72,6 +74,30 @@ def load_omniflo_cell_size_mm(cfg_path=None): return parser.getfloat("Nachbarschaft", "omniflo_zellgroesse_mm", fallback=3000.0) +def load_collision_debug_target(cfg_path=None): + """Zielpfad fuer das Kollisions-/Nachbarschafts-Debuglog aus export.cfg. + + [Nachbarschaft] -> debug_log: leer/0/false/no/off = aus (Rueckgabe None). + 1/true/yes/on = DXFM_LOG/export_collision.dbg (bzw. /logs/, falls + DXFM_LOG nicht gesetzt ist). Jeder andere Wert wird als expliziter + Dateipfad interpretiert. + """ + if cfg_path is None: + cfg_path = cfg_path_from_env() + parser = load_export_cfg(cfg_path) + raw = parser.get("Nachbarschaft", "debug_log", fallback="").strip() + if not raw or raw.lower() in ("0", "false", "no", "off", "nein", "aus"): + return None + if raw.lower() in ("1", "true", "yes", "on", "ja", "ein"): + log_dir = os.environ.get("DXFM_LOG") + if not log_dir: + # cfg_path zeigt auf /cfg/export.cfg -> /logs + log_dir = os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(cfg_path))), "logs") + return os.path.join(log_dir, "export_collision.dbg") + return raw # expliziter Pfad + + def _bounds(bbox, half_tol): """(minx, maxx, miny, maxy) einer Bounding-Box, je Seite um half_tol erweitert.""" return ( @@ -94,7 +120,17 @@ def _add_neighbor(result, idx_a, idx_b, id_a, id_b): result[idx_b].append(id_a) -def _test_group_pairs(group, result): +def _fmt_bounds(b): + """(minx, maxx, miny, maxy) kompakt fuer das Debuglog.""" + return f"[x {b[0]:.0f}..{b[1]:.0f} | y {b[2]:.0f}..{b[3]:.0f}]" + + +def _dbg_pair(dbg, id_a, id_b, overlap): + if dbg: + dbg(f" {id_a} <-> {id_b}: {'NACHBARN' if overlap else 'kein Kontakt'}") + + +def _test_group_pairs(group, result, dbg=None): """Alle Paare EINER Gruppe gegeneinander testen (O(n^2) - fuer Kreisel/ Eckrad-Stueckzahlen pro Zeichnung unkritisch).""" n = len(group) @@ -102,16 +138,20 @@ def _test_group_pairs(group, result): idx_a, bounds_a, id_a = group[a] for b in range(a + 1, n): idx_b, bounds_b, id_b = group[b] - if _overlaps(bounds_a, bounds_b): + overlap = _overlaps(bounds_a, bounds_b) + _dbg_pair(dbg, id_a, id_b, overlap) + if overlap: _add_neighbor(result, idx_a, idx_b, id_a, id_b) -def _test_group_against_group(group_a, group_b, result): +def _test_group_against_group(group_a, group_b, result, dbg=None): """Jedes Element aus group_a gegen jedes Element aus group_b testen. group_b wird NICHT gegen sich selbst getestet (Aufgabe des Aufrufers).""" for idx_a, bounds_a, id_a in group_a: for idx_b, bounds_b, id_b in group_b: - if _overlaps(bounds_a, bounds_b): + overlap = _overlaps(bounds_a, bounds_b) + _dbg_pair(dbg, id_a, id_b, overlap) + if overlap: _add_neighbor(result, idx_a, idx_b, id_a, id_b) @@ -120,7 +160,7 @@ def _grid_cell(bbox, cell_size_mm): return (int(bbox["cx"] // cell_size_mm), int(bbox["cy"] // cell_size_mm)) -def _test_omniflo_grid(group, cell_size_mm, result): +def _test_omniflo_grid(group, cell_size_mm, result, dbg=None): """Omniflo-Elemente nur gegen Elemente in derselben oder einer der 8 angrenzenden Rasterzellen testen (3x3-Nachbarschaft) statt gegen alle - haelt den Aufwand bei mehreren hundert Omniflo-Elementen niedrig, ohne @@ -130,6 +170,10 @@ def _test_omniflo_grid(group, cell_size_mm, result): idx, bounds, teileid, bbox = entry buckets.setdefault(_grid_cell(bbox, cell_size_mm), []).append(entry) + if dbg: + dbg(f" {len(buckets)} belegte Rasterzelle(n): " + + ", ".join(f"{cell}={len(v)}" for cell, v in sorted(buckets.items()))) + checked = set() for (cx, cy), cell_items in buckets.items(): candidates = [] @@ -145,11 +189,13 @@ def _test_omniflo_grid(group, cell_size_mm, result): if pair in checked: continue checked.add(pair) - if _overlaps(bounds_a, bounds_b): + overlap = _overlaps(bounds_a, bounds_b) + _dbg_pair(dbg, id_a, id_b, overlap) + if overlap: _add_neighbor(result, idx_a, idx_b, id_a, id_b) -def compute_neighbor_ids(items, tolerance_mm, omniflo_cell_size_mm=3000.0): +def compute_neighbor_ids(items, tolerance_mm, omniflo_cell_size_mm=3000.0, dbg=None): """Ermittelt je Item die IDs (item["teileid"]) benachbarter Elemente. items = Liste von dict mit "teileart", "teileid" und optional "_bbox" @@ -157,6 +203,9 @@ def compute_neighbor_ids(items, tolerance_mm, omniflo_cell_size_mm=3000.0): ohne _bbox (z.B. die synthetische Omniflo-Sum-Zeile) bleiben ohne Nachbarn. + dbg = optionale Callable(str) fuer ein Debugprotokoll (Klassifikation, + Gruppen, jeder Ueberschneidungstest, Ergebnis). None = kein Protokoll. + Rueckgabe: Liste von kommaseparierten Nachbar-ID-Strings, positionsgleich zu items (nicht ueber die ID dedupliziert, da TeileId nicht zwingend eindeutig ist). @@ -167,32 +216,79 @@ def compute_neighbor_ids(items, tolerance_mm, omniflo_cell_size_mm=3000.0): kreisel_group = [] strecken_group = [] omniflo_group = [] + skipped = 0 + + if dbg: + dbg("=== Nachbarschafts-/Kollisionserkennung ===") + dbg(f"Toleranz={tolerance_mm}mm (Bounding-Box je Seite +{half_tol}mm erweitert), " + f"Omniflo-Zellgroesse={omniflo_cell_size_mm}mm") + dbg(f"Elemente gesamt: {len(items)}") + dbg("") + dbg("Klassifikation je Element:") for idx, item in enumerate(items): bbox = item.get("_bbox") if not bbox: + skipped += 1 + if dbg: + dbg(f" idx={idx} teileid={item.get('teileid', '')!r} " + f"teileart={item.get('teileart', '')!r} -> UEBERSPRUNGEN " + f"(keine Bounding-Box)") continue teileart = item.get("teileart", "") teileid = item.get("teileid", "") bounds = _bounds(bbox, half_tol) if teileart in KREISEL_TEILEARTEN: + gruppe = "Kreisel/Eckrad" kreisel_group.append((idx, bounds, teileid)) elif teileart in STRECKEN_TEILEARTEN: + gruppe = "Strecke/Foerderer" strecken_group.append((idx, bounds, teileid)) elif teileart in OMNIFLO_TEILEARTEN: + gruppe = "Omniflo" omniflo_group.append((idx, bounds, teileid, bbox)) + else: + gruppe = "(keine Gruppe - wird nicht geprueft)" + if dbg: + dbg(f" idx={idx} teileid={teileid!r} teileart={teileart!r} " + f"-> {gruppe}; bbox(cx={bbox.get('cx', 0):.0f},cy={bbox.get('cy', 0):.0f}," + f"dx={bbox.get('dx', 0):.0f},dy={bbox.get('dy', 0):.0f}) " + f"bounds={_fmt_bounds(bounds)}") + if dbg: + dbg("") + dbg(f"Gruppengroessen: Kreisel/Eckrad={len(kreisel_group)}, " + f"Strecke/Foerderer={len(strecken_group)}, Omniflo={len(omniflo_group)}, " + f"ohne Bounding-Box={skipped}") + dbg("") + dbg("[1] Kreisel/Eckrad gegen Kreisel/Eckrad:") # 1. Kreisel/Eckrad gegen Kreisel/Eckrad - _test_group_pairs(kreisel_group, result) + _test_group_pairs(kreisel_group, result, dbg) + if dbg: + dbg("[2] Kreisel/Eckrad gegen Strecke/Foerderer/Gefaellestrecke:") # 2. Kreisel/Eckrad gegen Gefaellestrecke/Foerderer/Strecke-Modul - # diese drei Kategorien nicht gegeneinander (siehe Modul-Docstring) - _test_group_against_group(kreisel_group, strecken_group, result) + _test_group_against_group(kreisel_group, strecken_group, result, dbg) + if dbg: + dbg("[3] Omniflo gegen Omniflo (rasterbasiert vorgefiltert):") # 3. Omniflo gegen Omniflo, rasterbasiert vorgefiltert - _test_omniflo_grid(omniflo_group, omniflo_cell_size_mm, result) + _test_omniflo_grid(omniflo_group, omniflo_cell_size_mm, result, dbg) - return [", ".join(neighbor_ids) for neighbor_ids in result] + neighbor_id_lists = [", ".join(neighbor_ids) for neighbor_ids in result] + + if dbg: + dbg("") + dbg("Ergebnis je Element:") + for idx, item in enumerate(items): + dbg(f" idx={idx} teileid={item.get('teileid', '')!r}: " + f"{len(result[idx])} Nachbar(n) [{neighbor_id_lists[idx]}]") + dbg("") + dbg(f"Summe gerichteter Nachbarschaftsbeziehungen: " + f"{sum(len(r) for r in result)}") + + return neighbor_id_lists def compute_neighbor_errors(items, neighbor_ids): diff --git a/lib/export_sivas.py b/lib/export_sivas.py index 1d77046..8973be4 100644 --- a/lib/export_sivas.py +++ b/lib/export_sivas.py @@ -255,6 +255,17 @@ def build_eckrad_details(block): # --- Bekannte Bloecke ignorieren --- SKIP_BLOCKS = set(BLOCKPATTERNS.get("pattern_ks_subblocks", ["K1", "K2", "K3", "K4", "KS_EIN", "KS_AUS"])) +# Eigenstaendige Separator_SP/S-LP- und Scanner-Bloecke werden von der Lisp- +# Sammlung (csv:collect-export-blocks) zwar mit in export_raw.json geschrieben +# (export_csv.py gibt ihnen dort eigene TeileArt-Zeilen), aber HIER bewusst +# NICHT in eine eigene Sivas-Zeile uebernommen: ihre Stueckzahl wird bereits +# ueber die ANZAHL_SEPARATOR/ANZAHL_SCANNER-Attribute des zugehoerigen +# Kreisel/GF/VF-Blocks gezaehlt (siehe kreisel_signature/build_*_details +# oben) - ein zusaetzlicher eigener Eintrag wuerde doppelt zaehlen. +SEP_SCANNER_PATTERNS = ( + BLOCKPATTERNS.get("pattern_separator", []) + BLOCKPATTERNS.get("pattern_scanner", []) +) + # Sivas-Nummern und Bezeichnungen je ILS-Elementtyp # Hinweis: "ils_kurve", sind aktuell nur # Typnamen-Platzhalter - die zugehoerigen Menuebefehle sind noch Dummies @@ -330,6 +341,10 @@ def process_blocks(blocks, lookup): if bname in SKIP_BLOCKS: continue + # Separator/Scanner: siehe Kommentar bei SEP_SCANNER_PATTERNS oben. + if matches_any(bname, SEP_SCANNER_PATTERNS): + continue + # --- Omniflo Bogen oder Weiche (erkannt via Katalog) --- if bname in lookup: typ, eintrag = lookup[bname] diff --git a/lib/set_attributs.py b/lib/set_attributs.py deleted file mode 100644 index dd8ab6b..0000000 --- a/lib/set_attributs.py +++ /dev/null @@ -1,469 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -set_attributs.py - Fuegt Attribute (ATTDEF) zu Omniflo DXF-Dateien hinzu - und verpackt die Geometrie in eine benannte Blockdefinition. - -Vorgehen pro DXF-Datei: - 1. DXF-Quelldatei lesen (Basis: Ausgabe von set_einfuegepkt.py, damit - $INSBASE bereits gesetzt ist). - 2. Vorhandene ATTDEF-Entities aus dem Modelspace entfernen (Re-Run-sicher). - 3. Neue ATTDEFs gemaess cfg/attradd.cfg und JSON-Daten einfuegen. - Attributwerte werden aus omniflo_boegen.json / omniflo_weichen.json - aufgeloest; der Layer-Name kommt aus dem [layer]-Abschnitt der Config. - 4. Ergebnis in results/omniflo/.dxf speichern. - -Hinweis zur Blockstruktur: - Die Geometrie und ATTDEFs verbleiben direkt im Modelspace (flache Struktur). - BricsCAD erstellt beim Einfuegen via LISP (_.INSERT .dxf) automatisch - einen Block mit dem Dateinamen (= Sivasnr) und befuellt die ATTDEFs mit den - Standardwerten als ATTRIBs. Jedes eingefuegte Element wird als eigenstaendiger - Block im Ziel-DWG referenziert. - -Voraussetzung: - Die Quelldateien in data/omniflo/ sollten bereits durch set_einfuegepkt.py - verarbeitet worden sein, damit $INSBASE korrekt gesetzt ist. - -Eingaben: - cfg/attradd.cfg - Attribut-Konfiguration - data/json/omniflo_boegen.json - Bogendaten (Masse, Sivasnr, ...) - data/json/omniflo_weichen.json - Weichendaten - data/omniflo/.dxf - Quell-DXF-Dateien - -Ausgabe: - results/omniflo/.dxf - DXF mit Blockdefinition + INSERT - -Umgebungsvariablen: - DXFM_DATA - Pfad zum data/-Verzeichnis - DXFM_CFG - Pfad zum cfg/-Verzeichnis - DXFM_RESULTS - Pfad zum results/-Verzeichnis - -Aufruf: - python lib/set_attributs.py [--number SIVASNR] [--dry-run] [--type 2D|3D] -""" - -import argparse -import configparser -import json -import os -import re -import sys - -import ezdxf -from ezdxf import bbox -from ezdxf.enums import TextEntityAlignment - - -# --------------------------------------------------------------------------- -# Konfiguration lesen -# --------------------------------------------------------------------------- - -def load_config(cfg_path): - """ - Liest attradd.cfg und gibt ein Dict zurueck: - { - "bogen": [("BESCHR", "ProfilTyp"), ("ARTINR", "Sivasnr"), ...], - "weiche": [("BESCHR", "ProfilTyp"), ...], - "layer": {"bogen": "OF Boegen A-2", "weiche_90": "Weiche 90 Grad A-2", ...}, - "container": {"0_BG071090": "0_B10071, 0_B10090, 0_B10090", ...}, - } - """ - config = configparser.ConfigParser() - config.optionxform = str # Case-sensitive Keys - config.read(cfg_path, encoding="utf-8") - - result = {} - for section in config.sections(): - if section == "layer": - # Layer-Sektion als einfaches Dict speichern - layer_map = {} - for key, val in config.items(section): - val = val.strip() - if val.startswith('"') and val.endswith('"'): - val = val[1:-1] - layer_map[key] = val - result["layer"] = layer_map - elif section == "container": - # Container-Sektion: 0_BGxxxxxx -> bereinigte, kommaseparierte Liste - container_map = {} - for key, val in config.items(section): - parts = [p.strip() for p in val.split(",") if p.strip()] - container_map[key] = ", ".join(parts) - result["container"] = container_map - else: - attrs = [] - for tag, source in config.items(section): - attrs.append((tag, source.strip())) - result[section] = attrs - - return result - - -KOMBI_TYPEN = {"Deltaweiche", "Sternweiche", "Dreifachweiche"} - - -def get_layer_for_entry(typ, json_entry, layer_map): - """ - Bestimmt den Layer-Namen anhand von Elementtyp und JSON-Daten. - Gibt den Layer-String zurueck oder "" wenn kein Mapping vorhanden. - """ - if not layer_map: - return "" - - if typ == "bogen": - return layer_map.get("bogen", "") - - # Weiche: Kategorie anhand KurvenWinkel und WeichenTyp bestimmen - winkel = json_entry.get("KurvenWinkel", 0) - weichentyp = json_entry.get("WeichenTyp", "") - - # Weichenkombinationen (Deltaweiche, Sternweiche, Dreifachweiche) - if weichentyp in KOMBI_TYPEN: - return layer_map.get("weichenkombination", "") - - # Weichenkoerper (KurvenWinkel = 22.5) - if winkel == 22.5: - return layer_map.get("weichenkoerper", "") - - # Weiche 90 Grad - if winkel == 90: - return layer_map.get("weiche_90", "") - - # Weiche 45 Grad - if winkel == 45: - return layer_map.get("weiche_45", "") - - # Weichenbatterien / Parallel (KurvenWinkel = 0) - if winkel == 0: - return layer_map.get("weichenbatterie", "") - - return "" - - -CONTAINER_PATTERN = re.compile(r"0_BG\d+") - - -def resolve_container_refs(text, container_map): - """ - Ersetzt jedes Vorkommen von 0_BGxxxxxx in text durch die zugehoerige - kommaseparierte Liste aus [container]. Kommt das Muster mehrfach vor, - wird jedes Vorkommen einzeln ersetzt. Unbekannte 0_BG-Werte bleiben - unveraendert stehen. - - Anschliessend werden verbleibende "+"-Trenner (z.B. zwischen Artikelnr. - und 0_BG-Referenz) durch ", " ersetzt, damit SivasNummer ausschliesslich - eine kommaseparierte Liste ist. - """ - if container_map and "0_BG" in text: - text = CONTAINER_PATTERN.sub( - lambda m: container_map.get(m.group(0), m.group(0)), text - ) - if "+" in text: - parts = [p.strip() for p in text.split("+") if p.strip()] - text = ", ".join(parts) - return text - - -def resolve_value(source, json_entry, sivasnr, container_map=None): - """ - Loest einen Quell-Ausdruck aus der Config auf: - - "fester Text" -> fester Text - - {sivasnr} -> SivasNr-Wert - - Feldname -> Wert aus json_entry - - Wird das Feld "Sivasnr" aufgeloest, werden enthaltene 0_BGxxxxxx-Muster - anhand von [container] durch die dort hinterlegte kommaseparierte Liste - ersetzt und verbleibende "+"-Trenner durch ", " ersetzt, sodass das - Ergebnis ausschliesslich eine kommaseparierte Liste ist (keine "+" mehr). - """ - source = source.strip() - - # Fester Wert in Anfuehrungszeichen - if source.startswith('"') and source.endswith('"'): - return source[1:-1] - - # Platzhalter - if source == "{sivasnr}": - return str(sivasnr) - - # JSON-Feld - val = json_entry.get(source) - if val is None: - return "" - val = str(val) - if source == "Sivasnr": - val = resolve_container_refs(val, container_map) - return val - - -# --------------------------------------------------------------------------- -# JSON-Daten laden -# --------------------------------------------------------------------------- - -def load_json(path): - with open(path, "r", encoding="utf-8") as f: - return json.load(f) - - -def build_lookup(boegen, weichen): - """Dict: SivasNr (String) -> (typ, eintrag)""" - lookup = {} - for b in boegen: - lookup[str(b["Sivasnr"])] = ("bogen", b) - for w in weichen: - lookup[str(w["Sivasnr"])] = ("weiche", w) - return lookup - - -# --------------------------------------------------------------------------- -# ATTDEF zu DXF hinzufuegen -# --------------------------------------------------------------------------- - -ATTDEF_SPACING = -8.0 # Delta Y zwischen Attributzeilen -ATTDEF_HEIGHT = 5.0 -ATTDEF_LAYER = "ATTRIB" - - -def wrap_entities_in_block(doc, block_name): - """ - Verschiebt alle Entities aus dem Modelspace in eine Blockdefinition - mit dem Namen block_name. - - Im Modelspace wird ein INSERT erzeugt, dem ATTRIB-Entities hinzugefuegt - werden - mit den Standardwerten aus den ATTDEFs der Blockdefinition. - Dadurch zeigt BricsCAD beim Klick auf den Block die Attributwerte an. - Vorhandene Blockdefinition mit gleichem Namen wird ersetzt (fuer Re-Runs). - """ - msp = doc.modelspace() - - # Einfuegepunkt aus DXF-Header lesen (gesetzt von set_einfuegepkt.py) - base_point = doc.header.get("$INSBASE", (0, 0, 0)) - - # Vorhandene Blockdefinition entfernen - if block_name in doc.blocks: - doc.blocks.delete_block(block_name, safe=False) - - blk = doc.blocks.new(block_name, base_point=base_point) - - for entity in list(msp): - msp.move_to_layout(entity, blk) - - # INSERT im Modelspace - blkref = msp.add_blockref(block_name, (0, 0)) - - # ATTRIB-Entities aus ATTDEFs befuellen (Werte aus ATTDEF-Standardtext) - for attdef in (e for e in blk if e.dxftype() == "ATTDEF"): - blkref.add_attrib( - tag=attdef.dxf.tag, - text=attdef.dxf.text, - insert=attdef.dxf.insert, - dxfattribs={ - "layer": attdef.dxf.layer, - "height": attdef.dxf.height, - "flags": attdef.dxf.flags, - }, - ) - - -def add_attdefs_to_dxf(dxf_path, output_path, attr_defs, json_entry, sivasnr, - layer_value="", container_map=None): - """ - Oeffnet eine DXF-Datei, fuegt ATTDEF-Entities in den Modelspace ein - und speichert das Ergebnis. - - attr_defs: Liste von (TAG, source_expression) aus der Config - json_entry: Dict mit den JSON-Daten fuer dieses Element - layer_value: Layer-Name aus [layer]-Sektion (wird als LAYER-ATTDEF gesetzt) - container_map: Dict aus [container]-Sektion fuer 0_BG-Aufloesung in Sivasnr - """ - doc = ezdxf.readfile(dxf_path) - msp = doc.modelspace() - - # ATTRIB-Layer anlegen falls nicht vorhanden - if ATTDEF_LAYER not in doc.layers: - doc.layers.add(ATTDEF_LAYER, color=7) - - # Bestehende ATTDEFs entfernen (fuer Re-Runs) - existing = [e for e in msp if e.dxftype() == "ATTDEF"] - for e in existing: - msp.delete_entity(e) - - # Bounding Box der Geometrie berechnen (ohne ATTDEFs) - geo_entities = [e for e in msp if e.dxftype() != "ATTDEF"] - extents = bbox.extents(geo_entities) - if extents.has_data: - x_center = (extents.extmin.x + extents.extmax.x) / 2.0 - y_start = extents.extmin.y + ATTDEF_SPACING - else: - x_center = 0.0 - y_start = -50.0 - - # Alle ATTDEFs sammeln: Config-Attribute + LAYER aus [layer]-Sektion - all_defs = list(attr_defs) - if layer_value: - all_defs.append(("LAYER", f'"{layer_value}"')) - - # ATTDEFs hinzufuegen (zentriert unter der Geometrie) - y_pos = y_start - count = 0 - for tag, source in all_defs: - default_value = resolve_value(source, json_entry, sivasnr, container_map) - prompt = tag - - msp.add_attdef( - tag=tag, - text=default_value, - insert=(x_center, y_pos), - dxfattribs={ - "layer": ATTDEF_LAYER, - "height": ATTDEF_HEIGHT, - "prompt": prompt, - "flags": 1, # unsichtbar (1), nicht konstant -> erzeugt ATTRIB-Entities beim INSERT - }, - ) - y_pos += ATTDEF_SPACING - count += 1 - - doc.saveas(output_path) - return count - - -# --------------------------------------------------------------------------- -# Hauptprogramm -# --------------------------------------------------------------------------- - -def process_all(data_dir, cfg_path, results_dir, single_nr=None, dry_run=False): - """Verarbeitet alle Omniflo DXF-Dateien.""" - - # Config laden - attr_config = load_config(cfg_path) - layer_map = attr_config.pop("layer", {}) - container_map = attr_config.pop("container", {}) - print(f"[set_attributs] Config geladen: {list(attr_config.keys())}") - if layer_map: - print(f" [layer] {len(layer_map)} Zuordnungen: " - f"{', '.join(f'{k}={v}' for k, v in layer_map.items())}") - for section, attrs in attr_config.items(): - tags = [a[0] for a in attrs] - print(f" [{section}] Attribute: {', '.join(tags)}") - - # JSON-Daten laden - boegen = load_json(os.path.join(data_dir, "json", "omniflo_boegen.json")) - weichen = load_json(os.path.join(data_dir, "json", "omniflo_weichen.json")) - geraden_path = os.path.join(data_dir, "json", "omniflo_geraden.json") - geraden = load_json(geraden_path) if os.path.exists(geraden_path) else [] - lookup = build_lookup(boegen, weichen) - for g in geraden: - lookup[str(g["Sivasnr"])] = ("gerade", g) - print(f"[set_attributs] {len(boegen)} Boegen, {len(weichen)} Weichen, " - f"{len(geraden)} Geraden geladen.") - - # Ausgabeverzeichnis - out_dir = os.path.join(results_dir, "omniflo") - os.makedirs(out_dir, exist_ok=True) - - dxfm_dim = os.environ.get("DXFM_DIM") - if dxfm_dim: - omniflo_dir = os.path.join(data_dir, "omniflo", dxfm_dim) - else: - omniflo_dir = os.environ.get("DXFM_OMNIFLO") or os.path.join(data_dir, "omniflo") - if not os.path.isdir(omniflo_dir) or not any( - f.lower().endswith(".dxf") for f in os.listdir(omniflo_dir) - ): - print(f"[set_attributs] FEHLER: Im Quellverzeichnis '{omniflo_dir}' " - f"wurden keine DXF-Dateien gefunden. eventuell DXFM_DIM=2D in Umgebung setzen") - sys.exit(1) - - processed = 0 - skipped = 0 - errors = 0 - - # Alle bekannten SivasNr verarbeiten - for sivasnr, (typ, entry) in sorted(lookup.items()): - if single_nr and sivasnr != single_nr: - continue - - # Quelldatei finden - dxf_name = f"{sivasnr}.dxf" - dxf_path = os.path.join(omniflo_dir, dxf_name) - if not os.path.exists(dxf_path): - skipped += 1 - continue - - # Attribute fuer diesen Typ - if typ not in attr_config: - print(f" WARNUNG: Kein Config-Abschnitt fuer Typ '{typ}', " - f"ueberspringe {sivasnr}") - skipped += 1 - continue - - attr_defs = attr_config[typ] - layer_value = get_layer_for_entry(typ, entry, layer_map) - output_path = os.path.join(out_dir, dxf_name) - - if dry_run: - values = {tag: resolve_value(src, entry, sivasnr, container_map) - for tag, src in attr_defs} - if layer_value: - values["LAYER"] = layer_value - print(f" [DRY] {sivasnr} ({typ}): {values}") - processed += 1 - continue - - try: - count = add_attdefs_to_dxf(dxf_path, output_path, attr_defs, - entry, sivasnr, - layer_value=layer_value, - container_map=container_map) - processed += 1 - print(f" OK: {sivasnr} ({typ}) -> {count} Attribute -> {output_path}") - except Exception as e: - errors += 1 - print(f" FEHLER: {sivasnr}: {e}") - - print(f"\n[set_attributs] Fertig: {processed} verarbeitet, " - f"{skipped} uebersprungen, {errors} Fehler.") - if not dry_run: - print(f"[set_attributs] Ausgabe in: {out_dir}") - - -def main(): - parser = argparse.ArgumentParser( - description="Fuegt Attribute (ATTDEF) zu Omniflo DXF-Dateien hinzu.") - parser.add_argument("--number", "-n", type=str, default=None, - help="Nur diese SivasNr verarbeiten") - parser.add_argument("--dry-run", "-d", action="store_true", - help="Nur anzeigen, nicht schreiben") - parser.add_argument("--type", "-t", choices=["2D", "3D"], default="2D", - help="Ueberschreibt DXFM_DIM und damit das " - "Quellverzeichnis data/omniflo/ (Default: 2D)") - args = parser.parse_args() - - # Umgebungsvariablen - os.environ["DXFM_DIM"] = args.type - data_dir = os.environ.get("DXFM_DATA") - cfg_dir = os.environ.get("DXFM_CFG") - results_dir = os.environ.get("DXFM_RESULTS") - - # Fallback: relative Pfade vom Projektverzeichnis - if not data_dir: - base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - data_dir = os.path.join(base, "data") - if not cfg_dir: - base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - cfg_dir = os.path.join(base, "cfg") - if not results_dir: - base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - results_dir = os.path.join(base, "results") - - cfg_path = os.path.join(cfg_dir, "attradd.cfg") - if not os.path.exists(cfg_path): - print(f"[set_attributs] FEHLER: Config nicht gefunden: {cfg_path}") - sys.exit(1) - - process_all(data_dir, cfg_path, results_dir, - single_nr=args.number, dry_run=args.dry_run) - - -if __name__ == "__main__": - main() diff --git a/menu/SSG_LIB.cui b/menu/SSG_LIB.cui index 6b1d71d..1f395d4 100644 --- a/menu/SSG_LIB.cui +++ b/menu/SSG_LIB.cui @@ -687,42 +687,48 @@ + + Mubea Gesamtmodell + ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_mubea.lsp")) TEST_MUBEA + + + KS 2D/3D pruefen (CSV) ^C^C(load (strcat (getenv "DXFMAKRO") "/Lisp/ks_pruefung.lsp")) KS_PRUEFEN - + Export CSV/Sivas ALL ^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_export_all.lsp")) TEST_EXPORT_ALL - + Kreisel bearbeiten (ssg-ensure "KreiselInsert") KreiselEdit - + Omniflo Element bearbeiten (ssg-ensure "OmniModulInsert") OMNI_EDIT - + Gefaellestrecke bearbeiten (ssg-ensure "Gefaellestrecke") GEFAELLESTRECKE_EDIT - + VarioFoerderer bearbeiten (ssg-ensure "VarioFoerderer") VARIOFOERDERER_EDIT - + SSG Bearbeiten (Auto) (ssg-ensure "SSG_LIB_Commands") SSG_BLOCKEDIT @@ -1501,50 +1507,56 @@ - KS 2D/3D pruefen (CSV) + Mubea Gesamtmodell - Export CSV/Sivas ALL + KS 2D/3D pruefen (CSV) + + Export CSV/Sivas ALL + + + + - + POP501 SSG_LIB Edit Context Kreisel bearbeiten - + Omniflo Element bearbeiten - + Gefaellestrecke bearbeiten - + VarioFoerderer bearbeiten - + SSG Bearbeiten (Auto) - + diff --git a/menu/SSG_LIB.mnu b/menu/SSG_LIB.mnu index 0948989..baadb3a 100644 --- a/menu/SSG_LIB.mnu +++ b/menu/SSG_LIB.mnu @@ -142,6 +142,7 @@ [Foerderer-Linienzug]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_linienzug.lsp")) TEST_LINIENZUG [Omniflo]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_omniflo.lsp")) TEST_OMNIFLO [Gefaellestrecke]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_gefaellestrecke.lsp")) TEST_GEFAELLESTRECKE +[Mubea Gesamtmodell]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_mubea.lsp")) TEST_MUBEA [KS 2D/3D pruefen (CSV)]^C^C(load (strcat (getenv "DXFMAKRO") "/Lisp/ks_pruefung.lsp")) KS_PRUEFEN [<-Export CSV/Sivas ALL]^C^C(load (strcat (getenv "DXFMAKRO") "/tests/test_export_all.lsp")) TEST_EXPORT_ALL diff --git a/tests/alltests.json b/tests/alltests.json index e1838ae..422d492 100644 --- a/tests/alltests.json +++ b/tests/alltests.json @@ -4,5 +4,6 @@ { "name": "linienzug", "save": "dwg", "module": "VarioFoerderer" }, { "name": "omniflo", "save": "dxf", "module": "OmniModulInsert" }, { "name": "omniflo_strecke", "save": "dxf", "module": "OmniModulInsert" }, - { "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" } + { "name": "gefaellestrecke", "save": "dwg", "module": "Gefaellestrecke" }, + { "name": "mubea", "save": "dxf", "module": "KreiselInsert" } ] diff --git a/tests/conftest.py b/tests/conftest.py index 9112419..6eea4aa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -157,3 +157,33 @@ def gefaellestrecke_dxf(): if not os.path.exists(path): pytest.skip("gefaellestrecke_tests.dxf nicht vorhanden - TESTRUN in BricsCAD ausfuehren") return ezdxf.readfile(path) + + +# --- Mubea Fixtures --- + +@pytest.fixture +def mubea_testdata(): + """Laedt die Mubea-Testdefinitionen aus testdata/mubea.json (alle Eintraege).""" + path = os.path.join(_testdata_dir(), "mubea.json") + return _load_json(path) + + +@pytest.fixture +def mubea_results(): + """Laedt die Mubea-Ergebnisse aus output/mubea_results.json.""" + path = os.path.join(_output_dir(), "mubea_results.json") + if not os.path.exists(path): + pytest.skip("mubea_results.json nicht vorhanden - TEST_MUBEA in BricsCAD ausfuehren") + data = _load_json(path) + if not data: + pytest.skip("mubea_results.json ist leer") + return data + + +@pytest.fixture +def mubea_dxf(): + """Laedt die erzeugte Mubea-DXF aus output/mubea_tests.dxf.""" + path = os.path.join(_output_dir(), "mubea_tests.dxf") + if not os.path.exists(path): + pytest.skip("mubea_tests.dxf nicht vorhanden - TESTRUN (save:dxf) in BricsCAD ausfuehren") + return ezdxf.readfile(path) diff --git a/tests/test_mubea.lsp b/tests/test_mubea.lsp new file mode 100644 index 0000000..3e71d42 --- /dev/null +++ b/tests/test_mubea.lsp @@ -0,0 +1,529 @@ +;; ============================================================ +;; test_mubea.lsp - Integrationstest: erzeugt das Mubea-Gesamtmodell +;; +;; Liest tests/testdata/mubea.json und baut in EINER Zeichnung: +;; - Kreisel (Eintraege mit "id", Block KREISEL_*) via kreisel-insert-script +;; - Vario (test_id "VF_*") Block VF_* via variofoerderer-einfuegen +;; + vf-block-erstellen +;; (deltaL/L_VF werden fuer winkel+deltaH geloest, L_GF Split 500/4230; +;; "anzahl"/"delta_y"/"delta_x" wie bei Gefaelle: Template->mehrere +;; Foerderer mit fortlaufend suffixiertem test_id, _01.._anzahl) +;; - Gefaelle (test_id "GF_*", Modus 1) Block GF_* via gefaellestrecke-einfuegen +;; - Separator (Eintraege mit "block") Block S-LP/Separator_SP via _.INSERT +;; +;; Speichert (via SSG_RUN_ALL_TESTS bei "save":"dxf"): +;; tests/output/mubea_tests.dxf +;; tests/output/mubea_results.json (via mubea:export-results) +;; +;; 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_mubea.lsp")) +;; TEST_MUBEA +;; ============================================================ + + +;; --- Ergebnis eines Bau-Schritts als JSON-String erzeugen --- +(defun mubea:result-json (test-id kind status block-name block-handle insert-point attribs / + json tag val first) + (setq json (strcat " {\n" + " \"test_id\": \"" test-id "\",\n" + " \"kind\": \"" kind "\",\n" + " \"status\": \"" status "\",\n" + " \"block_name\": \"" (if block-name block-name "") "\",\n" + " \"block_handle\": \"" (if block-handle block-handle "") "\",\n" + " \"insert_point\": [" + (if insert-point + (strcat (rtos (car insert-point) 2 1) ", " + (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\": {")) + (setq first T) + (foreach att attribs + (setq tag (car att) val (cdr att)) + (if (not first) (setq json (strcat json ","))) + (setq json (strcat json "\n \"" tag "\": \"" (if val val "") "\"")) + (setq first nil)) + (setq json (strcat json "\n }\n }")) + json +) + + +;; --- Aus einer Block-ENAME ein Ergebnis-JSON erzeugen --- +(defun mubea:ent-json (test-id kind ent / ed) + (if ent + (progn + (setq ed (entget ent)) + (mubea:result-json test-id kind "executed" + (cdr (assoc 2 ed)) (cdr (assoc 5 ed)) (cdr (assoc 10 ed)) + (ssg-attrib-read ent))) + (mubea:result-json test-id kind "failed" nil nil nil nil)) +) + + +;; --- 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 +) + + +;; ============================================================ +;; Bau-Funktionen je Elementtyp +;; ============================================================ + +;; --- Kreisel (wie test_kreisel: kreisel-insert-script) --- +(defun mubea:build-kreisel (e / id x y z abstand rotation typ pt ent) + (setq id (ssg-val e "id") + x (float (ssg-val e "x")) + y (float (ssg-val e "y")) + z (float (ssg-val e "z")) + abstand (float (ssg-val e "abstand")) + rotation (float (ssg-val e "rotation")) + typ (ssg-val e "typ")) + (if (null typ) (setq typ "STANDARD")) + (setq pt (list x y z)) + (princ (strcat "\n [KREISEL] " id " -> (" (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 pt abstand rotation typ z)) + (mubea:ent-json id "kreisel" ent) +) + + +;; --- Vario (VF_*): L_GF/L_VF fuer gegebenes deltaL+deltaH+winkel loesen, dann bauen --- +;; deltaL ist (wie bei der Gefaellestrecke) die horizontale PROJIZIERTE +;; Gesamtlaenge des VF (Vorlauf L_GF1 + Foerderer L_VF + Nachlauf L_GF2 + +;; Boegen/AS-ES-Elemente), analog foerderer_tests.json/object_data.md 3.1 - +;; KEIN abgeleiteter Wert. "winkel" erzwingt einen bestimmten Bogenwinkel aus +;; der Kandidatenliste von berechne-alle-winkel (wie foerderer:winkel- +;; erzwingen in test_foerderer.lsp), statt den automatisch kleinsten +;; gueltigen Winkel zu nehmen. L_GF1 aus JSON ist der Vorlauf-Zielwert; +;; L_GF2 (Nachlauf) ist der Rest (L_GF_gesamt - L_GF1). +(defun mubea:build-vario-one (tid richtung deltaL deltaH winkel-soll gf1-ziel seite hz + startpunkt / ergebnis winkel-treffer best-winkel L_GF L_VF + gf1 gf2 hoehe-bis lastEnt ent vf-nummer) + (if (or (null seite) (= seite "")) (setq seite "links")) + (if (null hz) (setq hz 0.0) (setq hz (float hz))) + (princ (strcat "\n [VARIO] " tid " -> deltaL=" (rtos deltaL 2 0) + " deltaH=" (rtos deltaH 2 0) + " Winkel(soll)=" (if winkel-soll (itoa (fix winkel-soll)) "auto") + " L_GF1-Ziel=" (rtos gf1-ziel 2 0))) + + (setq ergebnis (berechne-alle-winkel deltaL deltaH richtung nil)) + (setq best-winkel (car ergebnis) L_GF (cadr ergebnis) L_VF (caddr ergebnis)) + (if (numberp winkel-soll) + (progn + (setq winkel-treffer + (car (vl-remove-if-not '(lambda (a) (= (car a) (fix winkel-soll))) + (cadddr ergebnis)))) + (if (and winkel-treffer (cadddr winkel-treffer) + (numberp (cadr winkel-treffer)) (numberp (caddr winkel-treffer)) + (> (cadr winkel-treffer) 0) (> (caddr winkel-treffer) 0)) + (setq best-winkel (car winkel-treffer) + L_GF (cadr winkel-treffer) + L_VF (caddr winkel-treffer)) + (setq best-winkel nil L_GF nil L_VF nil)))) + + (if (and best-winkel L_GF L_VF (> L_GF 0) (> L_VF 0)) + (progn + (setq gf1 (min gf1-ziel L_GF)) + (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))) + + (setq hoehe-bis (if (= richtung "Auf") deltaH (- deltaH))) + (setq lastEnt (vf-lastent-ohne-attribute)) + (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): vf-next-number ueberspringt bereits belegte VF_n-Nummern. Mit + ;; einer festen 1 traf das _.-BLOCK des ZWEITEN Foerderers auf den schon + ;; vorhandenen Namen "VF_1" -> BricsCAD fragt "Neu definieren?", verschluckt + ;; damit Basispunkt/Auswahlsatz der festen Antwortfolge, meldet "Eintrag + ;; kann nicht erkannt werden" und der Befehl bleibt offen (Haenger). + (setq vf-nummer (vf-next-number)) + (setq ent (vf-block-erstellen "standard" seite vf-nummer 0 0 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 (mubea:last-insert-prefix "VF_"))) + ;; SSG_VF_EDIT-XDATA schreiben (wie vfs-standard-dialog-berechnen- + ;; einfuegen) - sonst erkennt VARIOFOERDERER_EDIT den Block beim + ;; Doppelklick nicht als "per Dialog erstellt" und verweigert die + ;; Bearbeitung (vfs-edit-keine-xdata). + (if (and ent (= (type ent) 'ENAME)) (vfs-xdata-schreiben ent hz)) + (mubea:ent-json tid "vario" ent)) + (progn + (princ (strcat "\n FEHLER: deltaL=" (rtos deltaL 2 0) + " bei Winkel " (if winkel-soll (rtos winkel-soll 2 0) "auto") + " nicht loesbar (kein gueltiges L_GF/L_VF).")) + (mubea:result-json tid "vario" "failed" nil nil nil nil))) +) + +;; --- Vario(s) aus einem Template-Eintrag bauen --- +;; Wie mubea:build-gf: "anzahl" identische Kopien, je um "delta_y"/"delta_x" (mm) +;; versetzt (nur startpunkt x/y - Default 1/0/0 -> genau ein Foerderer wie bisher). +;; Bei mehreren Kopien wird "test_id" fortlaufend suffixiert (_01.._anzahl), +;; bei genau einer Kopie bleibt der Template-Name erhalten (kompatibel zur +;; frueheren Einzelbenennung). +;; Rueckgabe: LISTE von Ergebnis-JSON-Strings (eine je Kopie). +(defun mubea:build-vario (e / tid0 richtung deltaL deltaH winkel-soll gf1-ziel seite hz + start0 anzahl dy dx i off nr tid startpunkt ergebnisse) + (setq tid0 (ssg-val e "test_id") + richtung (ssg-val e "richtung") + deltaL (float (ssg-val e "deltaL")) + deltaH (float (ssg-val e "deltaH")) + winkel-soll(ssg-val e "winkel") + gf1-ziel (float (ssg-val e "L_GF1")) + seite (ssg-val e "seite") + hz (ssg-val e "hz") + start0 (list (float (ssg-val e "x")) (float (ssg-val e "y")) (float (ssg-val e "z"))) + anzahl (ssg-val e "anzahl") + dy (ssg-val e "delta_y") + dx (ssg-val e "delta_x")) + (if (or (null anzahl) (< anzahl 1)) (setq anzahl 1)) + (setq dy (if dy (float dy) 0.0)) + (setq dx (if dx (float dx) 0.0)) + + (setq ergebnisse '() i 0) + (while (< i anzahl) + (setq off (float i)) + (setq nr (1+ i)) + (setq tid + (if (> anzahl 1) + (strcat tid0 "_" (if (< nr 10) (strcat "0" (itoa nr)) (itoa nr))) + tid0)) + (setq startpunkt (list (+ (car start0) (* dx off)) (+ (cadr start0) (* dy off)) (caddr start0))) + (setq ergebnisse + (cons (mubea:build-vario-one tid richtung deltaL deltaH winkel-soll gf1-ziel seite hz startpunkt) + ergebnisse)) + (setq i (1+ i)) + ) + (reverse ergebnisse) +) + + +;; --- Gefaellestrecke: Init-Konflikt umgehen ----------------------------- +;; gefaellestrecke-einfuegen ruft "(if (not *lib-initialized*) (gf-init- +;; bibliothek))" auf (Gefaellestrecke.lsp:1567) - dasselbe Flag, das +;; init-bibliothek (Vario, vf_standard.lsp) fuer SEINE EIGENEN aus-dx/ein-dx/ +;; aus-dz/ein-dz setzt. Da C:TEST_MUBEA zuerst den Vario baut (init- +;; bibliothek setzt *lib-initialized*=T), wuerde gf-init-bibliothek NIE mehr +;; laufen und die Gefaellestrecke mit den (voellig andersartigen) Vario- +;; Bogenmassen statt den echten AS_Element/ES_Element-Massen rechnen - genau +;; das fuehrte zur falschen Zielhoehe im ersten TEST_MUBEA-Lauf. Fix: Flag +;; kurz zuruecksetzen, gf-init-bibliothek EINMAL erzwingen (danach haben +;; aus-dx/ein-dx/aus-dz/ein-dz die korrekten GF-spezifischen Werte). +(defun mubea:ensure-gf-init ( / ) + (if (not *mubea-gf-init-done*) + (progn + (setq *lib-initialized* nil) + (gf-init-bibliothek) + (setq *lib-initialized* t) + (setq *mubea-gf-init-done* T) + ) + ) +) + +;; --- Eine einzelne Gefaellestrecke bauen (Modus 1) --- +;; Aufbau AUSSCHLIESSLICH aus Anfangspunkt (start-mm) und horizontaler +;; Gesamtlaenge deltaL - KEIN Endpunkt (wie Produktion/gefaellestrecke.dcl +;; Modus 1, siehe object_data.md 2.1). L_stau wird aus deltaL abgeleitet +;; (identische Formel wie test_gefaellestrecke.lsp): +;; L_stau = (deltaL - aus-dx - ein-dx - sep_x) / cos(v) +;; sep_x = 300 * cos(v), v = Element-Winkel (winkel_grad, Default 3 Grad) +;; Die Zielhoehe ergibt sich aus L_stau/Winkel (kein Ziel-Hoehen-Parameter). +;; Rueckgabe: Ergebnis-JSON-String. +(defun mubea:build-gf-one (tid start-mm deltaL hz as-seite es-seite as-winkel es-winkel winkel / + rad sep-x test-L_stau ent) + (mubea:ensure-gf-init) + ;; Element-Masse fuer die gewaehlte AS/ES-Variante setzen, damit aus-dx/ein-dx + ;; zur Variante (90/30 Grad) passen. Danach wieder 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 test-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 test-L_stau 2 1))) + (setq ent (car (gefaellestrecke-einfuegen + test-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) + (mubea:ent-json tid "gefaellestrecke" ent) +) + +;; --- Gefaellestrecke(n) aus einem Template-Eintrag bauen --- +;; Ein einziger JSON-Eintrag beschreibt per "anzahl" + "delta_y"/"delta_x" eine +;; ganze Reihe identischer Gefaellestrecken (statt 20 Einzeleintraege). Element i +;; (1..anzahl) wird um (i-1)*delta in Y bzw. X versetzt (nur start_mm - es gibt +;; keinen Endpunkt). Fehlen anzahl/delta -> genau eine Strecke am angegebenen Ort. +;; Aus JSON: start_mm, deltaL_mm, hz_grad, as_seite, es_seite, as_winkel, +;; es_winkel, winkel_grad (Default 3.0), anzahl (Default 1), delta_y +;; (Default 0), delta_x (Default 0). +;; Rueckgabe: LISTE von Ergebnis-JSON-Strings (eine je Kopie). +(defun mubea:build-gf (e / tid0 start0 deltaL hz as-seite es-seite as-winkel es-winkel + winkel anzahl dy dx i off nr tid s-mm ergebnisse) + (setq tid0 (ssg-val e "test_id") + start0 (mapcar 'float (ssg-val e "start_mm")) + deltaL (float (ssg-val e "deltaL_mm")) + hz (float (ssg-val e "hz_grad")) + 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 (ssg-val e "winkel_grad") + anzahl (ssg-val e "anzahl") + dy (ssg-val e "delta_y") + dx (ssg-val e "delta_x")) + (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")) + (setq winkel (if winkel (float winkel) 3.0)) + (if (or (null anzahl) (< anzahl 1)) (setq anzahl 1)) + (setq dy (if dy (float dy) 0.0)) + (setq dx (if dx (float dx) 0.0)) + + (setq ergebnisse '() i 0) + (while (< i anzahl) + (setq off (float i)) + (setq nr (1+ i)) + ;; Bei mehreren Kopien fortlaufende ID GF_Mubea_01.. bilden (kompatibel zur + ;; frueheren Einzelbenennung); bei genau einer Kopie den Template-Namen behalten. + (setq tid + (if (> anzahl 1) + (strcat tid0 "_" (if (< nr 10) (strcat "0" (itoa nr)) (itoa nr))) + tid0)) + (setq s-mm (list (+ (car start0) (* dx off)) (+ (cadr start0) (* dy off)) (caddr start0))) + (setq ergebnisse + (cons (mubea:build-gf-one tid s-mm deltaL hz as-seite es-seite as-winkel es-winkel winkel) + ergebnisse)) + (setq i (1+ i)) + ) + (reverse ergebnisse) +) + + +;; --- Ein einzelner Separator: Blockdefinition laden, dann per Name einfuegen --- +;; Verwendet den in JSON genannten Block; faellt auf Separator_SP zurueck +;; (Block der "Separator einfuegen"-Routine ILS_SEPARATOR_INSERT). +;; Nutzt ssg-ils-block-laden (wie Kreisel/GF/VF), um den Block vorab zu +;; definieren, dann klassisches "_.INSERT eff pt "" "" rot" (wie ueberall sonst +;; im Projekt, z.B. omni:insert-dxf/KreiselInsert). NEUE Bloecke (z.B. "Scanner") +;; koennen dabei eine zusaetzliche Rueckfrage einschieben, die die feste +;; Antwortfolge verschiebt und das INSERT nicht sauber beendet (CMDACTIVE bleibt +;; > 0) - dieselbe Absicherung wie in ssg-ils-block-laden-dim (Zeilen 418f.) und +;; omni:insert-dxf (Zeile 980): nach dem INSERT verbleibende Prompts mit Leer- +;; Antworten (Enter) abfangen, bis der Befehl sauber beendet ist. +(defun mubea:build-separator-one (tid blockname pt rot / eff n) + (princ (strcat "\n [SEP] " tid " '" (if blockname blockname "?") + "' -> (" (rtos (car pt) 2 0) "," (rtos (cadr pt) 2 0) "," (rtos (caddr pt) 2 0) + ") R=" (rtos rot 2 0))) + ;; NICHT (and blockname (ssg-ils-block-laden blockname)) verwenden: AutoLISPs + ;; AND liefert bei mehreren wahren Ausdruecken T zurueck, NICHT den letzten + ;; Wert (anders als z.B. in Common Lisp/Scheme) - eff waere dann bei einem + ;; erfolgreich direkt gefundenen Block (z.B. "Scanner", das NICHT ueber den + ;; Separator_SP-Fallback laeuft) das Symbol T statt des Blocknamen-Strings, + ;; und das anschliessende "_.INSERT" wuerde einen Block namens T einfuegen. + (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 mit Enter abfangen - aber GEZAEHLT. Ein Prompt, der + ;; eine Leer-Antwort ablehnt ("Eintrag kann nicht erkannt werden. Bitte + ;; versuchen Sie es erneut."), haelt CMDACTIVE dauerhaft > 0: eine + ;; ungebremste (while (> CMDACTIVE 0) (command "")) Schleife laeuft dort + ;; endlos und BricsCAD haengt. Danach hart abbrechen und melden. + (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.")))) + (mubea:ent-json tid "separator" (entlast))) + (progn + ;; Kein Block gefunden -> sichtbaren Platzhalter zeichnen + (if (not (tblsearch "LAYER" "MUBEA_SEPARATOR")) + (entmake (list '(0 . "LAYER") '(2 . "MUBEA_SEPARATOR") '(70 . 0) '(62 . 1)))) + (entmake (list '(0 . "CIRCLE") (cons 10 pt) (cons 40 150.0) '(8 . "MUBEA_SEPARATOR"))) + (princ " (Block fehlt - Platzhalter)") + (mubea:result-json tid "separator" "placeholder" + (if blockname blockname "S-LP") nil pt nil))) +) + +;; --- Separator(en) aus einem Eintrag bauen (Eintraege mit "block") --- +;; Wie mubea:build-gf: "anzahl" identische Kopien, je um "delta_y"/"delta_x" +;; (mm) versetzt (Default 1/0/0 -> genau ein Separator am angegebenen Ort). +;; Namensbasis fuer die Kopien: "test_id", falls angegeben (erzeugt +;; "_01".."_anzahl"), sonst Fallback "SEPARATOR_" wie +;; bisher (Einzel-Separatoren ohne "test_id" bleiben dadurch unveraendert). +;; "assigned_to" bleibt reine Dokumentation und wird hier NICHT zur Namens- +;; bildung verwendet, da mehrere Separatoren denselben assigned_to-Wert teilen +;; koennen (z.B. mehrere S-LP-Eintraege mit assigned_to "GF_Mubea_01"). +;; Rueckgabe: LISTE von Ergebnis-JSON-Strings (eine je Kopie). +(defun mubea:build-separator (e idx / blockname x0 y0 z rot tid0 anzahl dy dx + i nr tid pt ergebnisse) + (setq blockname (ssg-val e "block") + x0 (float (ssg-val e "x")) + y0 (float (ssg-val e "y")) + z (float (ssg-val e "z")) + rot (ssg-val e "rotation") + tid0 (cond ((ssg-val e "test_id")) ((strcat "SEPARATOR_" (itoa idx)))) + anzahl (ssg-val e "anzahl") + dy (ssg-val e "delta_y") + dx (ssg-val e "delta_x")) + (if (null rot) (setq rot 0)) + (setq rot (float rot)) + (if (or (null anzahl) (< anzahl 1)) (setq anzahl 1)) + (setq dy (if dy (float dy) 0.0)) + (setq dx (if dx (float dx) 0.0)) + + (setq ergebnisse '() i 0) + (while (< i anzahl) + (setq nr (1+ i)) + (setq tid + (if (> anzahl 1) + (strcat tid0 "_" (if (< nr 10) (strcat "0" (itoa nr)) (itoa nr))) + tid0)) + (setq pt (list (+ x0 (* dx (float i))) (+ y0 (* dy (float i))) z)) + (setq ergebnisse (cons (mubea:build-separator-one tid blockname pt rot) ergebnisse)) + (setq i (1+ i)) + ) + (reverse ergebnisse) +) + + +;; --- JSON-Export der Mubea-Ergebnisse --- +(defun mubea:export-results (tests-out-dir / out-json f first) + (if (null *mubea-test-results*) + (princ "\n Keine Mubea-Ergebnisse vorhanden.") + (progn + (vl-mkdir tests-out-dir) + (setq out-json (strcat tests-out-dir "/mubea_results.json")) + (setq f (open out-json "w")) + (if f + (progn + (write-line "[" f) + (setq first T) + (foreach r *mubea-test-results* + (if (not first) (write-line "," f)) + (write-line r f) + (setq first nil)) + (write-line "]" f) + (close f) + (princ (strcat "\n Ergebnisse: " out-json))) + (princ (strcat "\n FEHLER: Kann " out-json " nicht schreiben!"))))) +) + + +;; ============================================================ +;; C:TEST_MUBEA - Hauptbefehl +;; ============================================================ +(defun c:TEST_MUBEA ( / json-datei daten eintrag tid blk res r + results-list sep-idx anz-ok anz-fehler kind) + + ;; Benoetigte Feature-Module laden + (ssg-ensure "KreiselInsert") + (ssg-ensure "VarioFoerderer") + (ssg-ensure "Gefaellestrecke") + (ssg-ensure "SSG_LIB_Commands") + + (ssg-start "TEST_MUBEA" '(("OSMODE") ("CECOLOR") ("ATTREQ") ("ATTDIA"))) + (setvar "OSMODE" 0) + (setvar "ATTREQ" 0) + (setvar "ATTDIA" 0) + + ;; Block-Bibliothek (Bogen-/AS-/ES-Masse) initialisieren - fuer Vario+GF noetig + (if (or (not *lib-initialized*) (null bogen-auf)) + (init-bibliothek)) + + ;; Cache fuer GF-Init-Workaround pro Lauf zuruecksetzen + ;; (siehe mubea:ensure-gf-init weiter oben) + (setq *mubea-gf-init-done* nil) + + ;; Testdaten laden + (setq json-datei (strcat (getenv "DXFMAKRO") "/tests/testdata/mubea.json")) + (if (not (findfile json-datei)) + (progn + (princ (strcat "\n[TEST_MUBEA] FEHLER: " json-datei " nicht gefunden!")) + (ssg-end) + (exit))) + (setq daten (ssg-load-json json-datei)) + (if (null daten) + (progn + (princ "\n[TEST_MUBEA] FEHLER: Testdaten konnten nicht geladen werden.") + (ssg-end) + (exit))) + + (princ "\n\n================================================================") + (princ "\n TEST_MUBEA - Mubea-Gesamtmodell") + (princ (strcat "\n " (itoa (length daten)) " Eintraege geladen")) + (princ "\n================================================================") + + (setq results-list '() sep-idx 0 anz-ok 0 anz-fehler 0) + + (foreach eintrag daten + (setq tid (ssg-val eintrag "test_id")) + (setq res + (cond + ;; Separator: hat "block" + ((ssg-val eintrag "block") + (setq sep-idx (1+ sep-idx)) + (mubea:build-separator eintrag sep-idx)) + ;; Kreisel: hat "id" + ((ssg-val eintrag "id") + (mubea:build-kreisel eintrag)) + ;; Vario: test_id "VF_*" + ((and tid (>= (strlen tid) 3) (= (substr tid 1 3) "VF_")) + (mubea:build-vario eintrag)) + ;; Gefaellestrecke: test_id "GF_*" + ((and tid (>= (strlen tid) 3) (= (substr tid 1 3) "GF_")) + (mubea:build-gf eintrag)) + (T + (princ (strcat "\n WARNUNG: Unbekannter Eintrag uebersprungen")) + nil))) + ;; mubea:build-gf liefert eine LISTE (Template -> mehrere Strecken), die + ;; uebrigen Builder je einen String. Beides einheitlich einsammeln. + (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)) + (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 "\n================================================================") + + (setq *mubea-test-results* (reverse results-list)) + + (ssg-end) + (princ "\n TEST_MUBEA abgeschlossen.") + (princ) +) diff --git a/tests/test_mubea.py b/tests/test_mubea.py new file mode 100644 index 0000000..b806ec8 --- /dev/null +++ b/tests/test_mubea.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +test_mubea.py - Validiert das Mubea-Gesamtmodell. + +Prueft die von TEST_MUBEA (test_mubea.lsp) erzeugten Ergebnisse: + 1. Alle Eintraege aus mubea.json wurden gebaut (results.json) + 2. Statuswerte (executed / placeholder fuer Separatoren) + 3. Kreisel-Attribute (HOEHE, KREISELART, Block-Praefix) + 4. Vario: VF_-Block mit VF_WINKEL=21 und L_GF-Split 500/4230 + 5. DXF-Geometrie: Block-Anzahlen je Typ (ezdxf) +""" + +import pytest + + +# ============================================================ +# Klassifikation der mubea.json-Eintraege (analog test_mubea.lsp) +# ============================================================ + +def _is_separator(item): + return "block" in item + +def _is_kreisel(item): + return "id" in item and "block" not in item + +def _is_vario(item): + tid = item.get("test_id", "") + return tid.startswith("VF_") + +def _is_gefaelle(item): + tid = item.get("test_id", "") + return tid.startswith("GF_") + + +def _vario_count(testdata): + """Wie _gefaelle_count: ein VF-Eintrag ist ein Template, das ueber 'anzahl' + expandiert wird (Default 1, falls Feld fehlt).""" + return sum(int(t.get("anzahl", 1)) for t in testdata if _is_vario(t)) + + +def _gefaelle_count(testdata): + """Anzahl erzeugter Gefaellestrecken: ein GF-Eintrag ist ein Template, das + ueber 'anzahl' expandiert wird (Default 1, falls Feld fehlt).""" + return sum(int(t.get("anzahl", 1)) for t in testdata if _is_gefaelle(t)) + + +def _separator_count(testdata): + """Wie _gefaelle_count: ein Separator-Eintrag kann per 'anzahl' mehrere + identische, versetzte Kopien erzeugen (Default 1, falls Feld fehlt).""" + return sum(int(t.get("anzahl", 1)) for t in testdata if _is_separator(t)) + + +def _expected_counts(testdata): + return { + "kreisel": sum(1 for t in testdata if _is_kreisel(t)), + "vario": _vario_count(testdata), + "gefaellestrecke": _gefaelle_count(testdata), + "separator": _separator_count(testdata), + } + + +# ============================================================ +# Ergebnis-Pruefung (results.json vs. testdata) +# ============================================================ + +class TestMubeaResults: + + def test_alle_gebaut(self, mubea_testdata, mubea_results): + """Je Kind muss die erwartete Anzahl an Ergebnissen vorliegen.""" + expected = _expected_counts(mubea_testdata) + got = {} + for r in mubea_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}, gebaut {got.get(kind, 0)}' + + def test_status_ok(self, mubea_results): + """Alle Ergebnisse muessen 'executed' sein (Separatoren duerfen 'placeholder').""" + for r in mubea_results: + if r["kind"] == "separator": + assert r["status"] in ("executed", "placeholder"), \ + f'{r["test_id"]}: Status={r["status"]}' + else: + assert r["status"] == "executed", \ + f'{r["test_id"]}: Status={r["status"]}' + + def test_kreisel_attribute(self, mubea_testdata, mubea_results): + """Kreisel: HOEHE / KREISELART / Block-Praefix wie in mubea.json erwartet.""" + by_id = {r["test_id"]: r for r in mubea_results} + for t in mubea_testdata: + if not _is_kreisel(t): + continue + r = by_id.get(t["id"]) + assert r, f'Kreisel {t["id"]} fehlt im Ergebnis' + if "expect_hoehe" in t: + assert r["actual_attributes"].get("HOEHE") == t["expect_hoehe"], \ + f'{t["id"]}: HOEHE ist={r["actual_attributes"].get("HOEHE")}, erwartet={t["expect_hoehe"]}' + if "expect_kreiselart" in t: + assert r["actual_attributes"].get("KREISELART") == t["expect_kreiselart"], \ + f'{t["id"]}: KREISELART ist={r["actual_attributes"].get("KREISELART")}' + prefix = t.get("expect_block_prefix", "KREISEL_") + assert r["block_name"].startswith(prefix), \ + f'{t["id"]}: Block "{r["block_name"]}" ohne Praefix "{prefix}"' + + def test_vario_winkel_und_split(self, mubea_testdata, mubea_results): + """Vario: VF_WINKEL entspricht Vorgabe, L_GF-Vorlauf entspricht L_GF1-Ziel, + Vorlauf+Nachlauf (L_GF_m) ergeben zusammen die aus deltaL geloeste Gesamtlaenge.""" + by_id = {r["test_id"]: r for r in mubea_results} + for t in mubea_testdata: + if not _is_vario(t): + continue + r = by_id.get(t["test_id"]) + assert r, f'Vario {t["test_id"]} fehlt im Ergebnis' + attrs = r["actual_attributes"] + assert r["block_name"].startswith("VF_"), \ + f'{t["test_id"]}: Block "{r["block_name"]}" ist kein VF_-Block' + if "winkel" in t: + assert attrs.get("VF_WINKEL") == str(t["winkel"]), \ + f'{t["test_id"]}: VF_WINKEL ist={attrs.get("VF_WINKEL")}, erwartet={t["winkel"]}' + # L_GF_m = "vorne,hinten" in Metern; L_GF1 (Vorlauf-Ziel) muss exakt + # getroffen werden, L_GF2 (Nachlauf) ist der aus deltaL geloeste Rest. + if "L_GF1" in t and "L_GF_m" in attrs: + teile = attrs["L_GF_m"].split(",") + assert len(teile) == 2, f'{t["test_id"]}: L_GF_m Format "{attrs["L_GF_m"]}"' + v = float(teile[0]) * 1000.0 + assert abs(v - t["L_GF1"]) < 1.0, \ + f'{t["test_id"]}: L_GF1 vorn ist={v}, erwartet={t["L_GF1"]}' + + +# ============================================================ +# DXF Geometrie-Pruefung (ezdxf) +# ============================================================ + +class TestMubeaGeometry: + + 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, mubea_dxf, mubea_testdata): + """Anzahl der KREISEL_/VF_/GF_-Bloecke >= Vorgabe aus mubea.json.""" + c = self._counts(mubea_dxf) + 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_")) + exp = _expected_counts(mubea_testdata) + 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_handles_im_modelspace(self, mubea_dxf, mubea_results): + """Jedes gebaute Block-Handle (executed) liegt im Modelspace.""" + handles = {e.dxf.handle for e in mubea_dxf.modelspace() + if e.dxftype() == "INSERT"} + for r in mubea_results: + if r["status"] == "executed" and r["block_handle"]: + assert r["block_handle"] in handles, \ + f'{r["test_id"]}: Handle {r["block_handle"]} nicht im Modelspace' diff --git a/tests/testdata/mubea.json b/tests/testdata/mubea.json new file mode 100644 index 0000000..4e2d457 --- /dev/null +++ b/tests/testdata/mubea.json @@ -0,0 +1,212 @@ +[ + { + "id": "Kreisel_unten", + "function": "insert", + "x": 400, + "y": -5478, + "z": 1467, + "abstand": 5000, + "rotation": 0.0, + "typ": "STANDARD", + "expect_block_prefix": "KREISEL_", + "expect_hoehe": "1467", + "expect_kreiselart": "STANDARD", + "comment": "x,y aus DXF (500573_60_1.dxf, SP8 Insert #2 (x=-40865.2521,y=5878.2466), raw) abgeleitet; z=1467 unveraendert (Einfuegehoehe, im 2D-Plan nicht enthalten). Vorheriger Handwert: x=800, y=14137" + }, + { + "id": "Kreisel_links", + "function": "insert", + "x": 0, + "y": -5878, + "z": 1467, + "abstand": 20015, + "rotation": 90.0, + "typ": "STANDARD", + "expect_block_prefix": "KREISEL_", + "expect_hoehe": "1467", + "expect_kreiselart": "STANDARD", + "comment": "x,y aus DXF (500573_60_1.dxf, SP8 Insert #1 (x=-50775.7283,y=5878.2466), raw) abgeleitet; z=1467 unveraendert (Einfuegehoehe, im 2D-Plan nicht enthalten). Vorheriger Handwert: x=0, y=14137" + }, + { + "comment": "Separator links Kreisel_links zugeordnet", + "function": "insert", + "block": "S-LP", + "x": -400, + "y": 13980, + "z": 1467, + "rotation": 270, + "assigned_to": "Kreisel_links" + }, + { + "comment": "Separator linken Kreisel unten", + "function": "insert", + "block": "S-LP", + "x": 400, + "y": -3815, + "z": 1467, + "rotation": 90, + "assigned_to": "Kreisel_links" + }, + { + "comment": "Scanner linken Kreisel unten", + "function": "insert", + "block": "Scanner", + "x": 400, + "y": -3925, + "z": 1467, + "rotation": 0, + "assigned_to": "Kreisel_links" + }, + { + "id": "Kreisel_rechts", + "function": "insert", + "x": 9910, + "y": -5878, + "z": 1941, + "abstand": 20015, + "rotation": 90.0, + "typ": "STANDARD", + "expect_block_prefix": "KREISEL_", + "expect_hoehe": "1941", + "expect_kreiselart": "STANDARD", + "comment": "x,y aus DXF (500573_60_1.dxf, AN8 Insert (x=-40865.2521,y=-14812.3513), raw) abgeleitet; z=1941 unveraendert (Einfuegehoehe, im 2D-Plan nicht enthalten). Vorheriger Handwert: x=9910, y=14137" + }, + { + "comment": "Separator 1 Qualitaetskontrolle linker Kreisel", + "function": "insert", + "block": "S-LP", + "x": -400, + "y": -4410, + "z": 1467, + "rotation": 270, + "assigned_to": "Qualitaetskontrolle" + }, + { + "test_id": "VF_Schnitt_C", + "richtung": "Auf", + "deltaL": 9110, + "deltaH": 474, + "winkel": 21, + "gf_verteilung": "manuell", + "L_GF1": 2298, + "seite": "links", + "hz": 0.0, + "x": 400, + "y": 14120, + "z": 1467, + "comment": "oberer Förderer für die Leertrolleys" + }, + { + "test_id": "VF_Schnitt_E", + "richtung": "Auf", + "deltaL": 9110, + "deltaH": 474, + "winkel": 21, + "gf_verteilung": "manuell", + "L_GF1": 2298, + "seite": "links", + "hz": 0.0, + "x": 400, + "y": -4133, + "z": 1467, + "comment": "unterer Förderer für die Teile" + }, + { + "comment": "Separator Förderer oben", + "function": "insert", + "block": "S-LP", + "x": 8922, + "y": 13509, + "z": 1467, + "rotation": 180, + "assigned_to": "VF_Schnitt_C" + }, + { + "test_id": "GF_Mubea", + "modus": 1, + "deltaL_mm": 9110, + "hz_grad": 180.0, + "winkel_grad": 2.27, + "start_mm": [9510, 12300, 1941], + "as_seite": "links", + "es_seite": "links", + "as_winkel": "90", + "es_winkel": "90", + "anzahl": 20, + "delta_y": -800, + "comment": "Template fuer alle Gefaellestrecken (Modus 1): 'anzahl' identische Kopien, je um 'delta_y' (mm) in Y versetzt (nur start_mm). Erzeugt GF_Mubea_01..GF_Mubea_anzahl. Aufbau ausschliesslich aus Anfangspunkt start_mm und der horizontalen Gesamtlaenge deltaL_mm - KEIN Endpunkt (wie Produktion/gefaellestrecke.dcl Modus 1). winkel_grad steuert den Gefaellewinkel (Default 3.0, falls Feld fehlt), Zielhoehe ergibt sich daraus. start_mm.x/z aus DXF (EE-R Start hoch z=1941); deltaL_mm ~ reale XY-Distanz Start->AE-DS im DXF." + }, + { + "test_id": "GF_Mubea", + "comment": "Separator S-LP an jeder Gefaellestrecken-Reihe: 'anzahl' identische Kopien, je um 'delta_y' (mm) in Y versetzt - analog zum GF_Mubea-Template. Erzeugt Ergebnis-test_id GF_Mubea_01..GF_Mubea_anzahl (kind=separator, unterscheidbar von der gleichnamigen Gefaellestrecke ueber 'kind').", + "function": "insert", + "block": "S-LP", + "x": 970, + "y": 12300, + "z": 1560, + "rotation": 0, + "anzahl": 20, + "delta_y": -800, + "assigned_to": "GF_Mubea" + }, + { + "comment": "Separator 1 unterer Kreisel", + "function": "insert", + "block": "S-LP", + "x": 1415, + "y": -5078, + "z": 1467, + "rotation": 0, + "assigned_to": "Kreisel_unten" + }, + { + "comment": "Separator 2 unterer Kreisel", + "function": "insert", + "block": "S-LP", + "x": 5415, + "y": -5878, + "z": 1467, + "rotation": 180, + "assigned_to": "Kreisel_unten" + }, + { + "comment": "Separator S-LP #11 Y-Abgleich mit Gefaellestrecken (Sep-Y=8777, GF_Mubea_04-Y=9126, diff=349mm, enger Treffer)", + "function": "insert", + "block": "S-LP", + "x": 9510, + "y": 14000, + "z": 1467, + "rotation": 270, + "assigned_to": "Kreisel_rechts" + }, + { + "comment": "Separator Eingang 1 Rechter_Kreisel", + "function": "insert", + "block": "S-LP", + "x": 10310, + "y": -500, + "z": 1941, + "rotation": 90, + "assigned_to": "Kreisel_rechts" + }, + { + "comment": "Separator Eingang 2 Rechter_Kreisel", + "function": "insert", + "block": "S-LP", + "x": 10310, + "y": 100, + "z": 1941, + "rotation": 90, + "assigned_to": "Kreisel_rechts" + }, + { + "comment": "Separator S-LP #8 Y-Abgleich mit Gefaellestrecken (Sep-Y=-4823, GF_Mubea_20-Y=-3673, diff=1150mm, naechstgelegene Reihe, kein enger Treffer)", + "function": "insert", + "block": "S-LP", + "x": 9510, + "y": -4823, + "z": 1941, + "rotation": 270, + "assigned_to": "GF_Mubea_20" + } +] diff --git a/tests/testdata/object_data.md b/tests/testdata/object_data.md new file mode 100644 index 0000000..de14118 --- /dev/null +++ b/tests/testdata/object_data.md @@ -0,0 +1,391 @@ +# JSON-Schema fuer tests/testdata/*.json + +Diese Datei leitet **aus den Produktions-Routinen in `Lisp/`** (nicht aus den +Test-Hilfsfunktionen in `tests/*.lsp`) ab, welche Angaben pro Element-Typ +noetig sind, um das jeweilige Bauteil programmgesteuert (per JSON) zu +erzeugen. Fuer jeden Typ wird die tatsaechlich aufgerufene LISP-Funktion samt +Parameterliste referenziert. + +Wichtige Unterscheidung, die sich beim Durcharbeiten herauskristallisiert hat: + +- **Scriptbar** = es existiert eine Produktions-Funktion, die alle noetigen + Werte als normale Parameter entgegennimmt (kein `getpoint`/`getstring` noetig). + Diese Funktionen sind 1:1 aus JSON aufrufbar. +- **Nicht scriptbar** = die Produktions-Funktion fragt Punkte/Optionen + interaktiv ab (`getpoint`, `getstring`, `ssget` auf bereits gezeichnete + Geometrie). Es gibt in `Lisp/` keinen parametrischen Einstiegspunkt; ein + JSON-Testfall kann diese Faelle nur ueber einen eigens geschriebenen + Wrapper (wie es `tests/*.lsp` tut) nicht-interaktiv nachbilden. + +Einheiten durchgaengig: **mm** fuer Laengen/Koordinaten, **Grad** fuer Winkel +(intern wird oft in Radiant gerechnet, die LISP-API-Grenze ist aber immer +Grad). Richtung `hz`/`hz_grad`: 0=Ost(+X), 90=Nord(+Y), 180=West(-X), +270=Sued(-Y). + +--- + +## 1. Kreisel — `Lisp/KreiselInsert.lsp` + +Scriptbar. Zwei Varianten: + +### 1.1 Insert (freier Punkt + Abstand) + +Produktionsfunktion: `kreisel-insert-script (pt abstand rotation typ hoehe)` + +| Feld | Typ | Pflicht | Bedeutung | Default | +|---|---|---|---|---| +| `x`, `y` | real (mm) | **ja** | Einfuegepunkt (Welt-XY), zusammen `pt` | - | +| `z` | real (mm) | nein | Hoehe; entfaellt `hoehe`, wird `pt`s Z verwendet | `*kreisel-default-hoehe*` (config `kreisel.default_hoehe`, 2000.0) | +| `abstand` | real (mm) | nein | Abstand zwischen AN-/SP-Kreiselhaelfte (Kreisel-Baulaenge) | `*kreisel-default-laenge*` (config `kreisel.default_laenge`, 2300.0) | +| `rotation` | real (Grad) | nein | freie Rotation des Bauteils, kein Rasterzwang (0/90/180/270 sind nur ueblich, nicht erzwungen) | 0.0 | +| `typ` | string | nein | `"STANDARD"` oder `"PIN"` (einzige zwei Optionen aus `kreisel-ask-typ`) | `"STANDARD"` | + +Konstanten (Config `[kreisel]`): `durchmesser` (800.0mm, fuer Connect-Abstandsberechnung), `default_laenge`, `default_hoehe`. + +Minimal-JSON (wie `kreisel_tests.json`): +```json +{ "id": "K1", "function": "insert", + "x": 500, "y": 500, "z": 2500, + "abstand": 2000, "rotation": 0.0, "typ": "STANDARD" } +``` +`id` und `expect_*`-Felder (`expect_block_prefix`, `expect_hoehe`, +`expect_kreiselart`) sind **keine** Produktionsparameter — reine +Test-Erwartungswerte fuer die Ergebnispruefung, nicht von +`kreisel-insert-script` konsumiert. + +### 1.2 Connect (Start-/Endpunkt, Abstand wird berechnet) + +Produktionsfunktion: `kreisel-connect-script (ptStart ptEnd typ hoehe)` + +| Feld | Typ | Pflicht | Bedeutung | +|---|---|---|---| +| `start_x`,`start_y`,`start_z` | real (mm) | **ja** | aeusserster Punkt AN-Seite (Antriebsstation) | +| `end_x`,`end_y`,`end_z` | real (mm) | **ja** | aeusserster Punkt SP-Seite (Spannstation) | +| `typ` | string | nein | `"STANDARD"`/`"PIN"` | Default `"STANDARD"` | +| `hoehe` | real (mm) | nein | Default: Z von `ptStart`, sonst `*kreisel-default-hoehe*` | + +`abstand`/`rotation` werden **intern** aus der Distanz beider Punkte +berechnet (`abstand = distanz - *kreisel-durchmesser*`; Rotation aus dem +Winkel der Verbindungslinie) — bei Connect KEIN Eingabefeld. + +--- + +## 2. Gefaellestrecke — `Lisp/Gefaellestrecke.lsp` + +Drei Modi, siehe Kopfkommentar der Datei. Modus 1+2 teilen sich denselben +Abschluss (`gf-modus12-abschluss`) und sind beide scriptbar. Modus 3 +(Linienzug) ist in Produktion **nicht scriptbar**. + +Gemeinsame Bau-Funktion (von allen Modi letztlich aufgerufen): +`gefaellestrecke-einfuegen (L_stau winkel startpunkt as-seite es-seite hz deltaL-total as-winkel es-winkel geruest-einzelmodul geruest-typ)` + +Wichtig: **`deltaL` (horizontale Laenge) ist der Eingang, nicht die +Hoehendifferenz.** Bei festem `winkel` legen Laenge und Hoehendifferenz +sich gegenseitig fest (`L_stau = (deltaL - aus-dx - ein-dx - sep-x) / cos(rad)`, +`sep-x = separator_breite * cos(rad)`, config `gefaelle.separator_breite` +Default 300.0). Es gibt **keinen** Parameter fuer eine gewuenschte +Ziel-Hoehendifferenz — die ergibt sich aus `L_stau`/`winkel`. + +### 2.1 Modus 1 — Manuelle Werteingabe + +Eingangsfunktion (nach DCL-Dialog `gefaellestrecke.dcl`): `gf-modus12-abschluss (deltaL winkel hz-winkel as-seite es-seite startpunkt as-winkel es-winkel dialog-bestaetigt geruest-einzelmodul geruest-typ)` + +| Feld | Typ | Pflicht | Bedeutung | Default | +|---|---|---|---|---| +| `deltaL` / `deltaL_mm` | real (mm) | **ja** | horizontale Gesamtlaenge (AS+Staustrecke+Separator+ES) | config `gefaelle.default_delta_l` 5000.0 | +| `winkel` / `winkel_grad` | real (Grad) | nein | Gefaelle-Neigungswinkel | config `gefaelle.default_winkel` 3.0 | +| `hz_grad` | real (Grad) | nein | Fahrtrichtung (0/90/180/270 ueblich, frei waehlbar) | 0.0 | +| `start_mm` | [x,y,z] (mm) | **ja** | Startpunkt; Z = Einfuegehoehe | - | +| `as_seite` | string | nein | `"links"`/`"rechts"` (AUS-Element-Seite) | `"links"` | +| `es_seite` | string | nein | `"links"`/`"rechts"` (EIN-Element-Seite) | `"links"` | +| `as_winkel` | string | nein | `"90"`/`"30"` (AUS-Element-Variante) | `"90"` | +| `es_winkel` | string | nein | `"90"`/`"30"` (EIN-Element-Variante) | `"90"` | +| `geruest_einzelmodul` | string "0"/"1" | nein | Geruest je Einzelmodul (Dialogfeld) | "1" | +| `geruest_typ` | string | nein | Geruest-Option (`*ssg-geruest-optionen*`) | Default-Index | + +Minimal-JSON (wie `gefaellestrecke_tests.json` Modus 1): +```json +{ "test_id": "M1_hz000", "modus": 1, "hz_grad": 0.0, + "start_mm": [0, 0, 5000], + "as_seite": "links", "es_seite": "links", + "as_winkel": "90", "es_winkel": "90" } +``` +`deltaL_mm`/`winkel_grad`/`z_start_mm` stehen in der Testdatei als +gemeinsamer Kopf-Eintrag (ein Objekt ohne `test_id`) statt pro Testfall, +weil alle Modus-1-Faelle in `gefaellestrecke_tests.json` dieselbe Laenge +teilen — Produktionsseitig ist `deltaL` aber pro Aufruf ein normaler, +nicht geteilter Parameter. + +### 2.2 Modus 2 — 3D-Linie als Richtungsreferenz + +Gleiche Ziel-Funktion (`gf-modus12-abschluss`), aber `deltaL`/`hz-winkel` +werden **nicht** direkt angegeben, sondern aus zwei Linienpunkten +berechnet: +``` +deltaX = ende.x - start.x ; deltaY = ende.y - start.y +deltaL = hypot(deltaX, deltaY) +hz-winkel = angle(deltaX, deltaY) [Grad, 0=Ost] +``` +In Produktion werden diese zwei Punkte interaktiv per `get-line-start-end-points` +(Linie in der Zeichnung anklicken) bestimmt — fuer einen JSON-Treiber +genuegen aber zwei beliebige Punkte, da die Berechnung rein geometrisch ist. + +| Feld | Typ | Pflicht | Bedeutung | +|---|---|---|---| +| `linie_start_mm` | [x,y,z] | **ja** | erster Linienpunkt (nur X/Y verwendet) | +| `linie_ende_mm` | [x,y,z] | **ja** | zweiter Linienpunkt (nur X/Y verwendet) | +| `start_mm` | [x,y,z] | **ja** | Einfuegepunkt der Kette (unabhaengig von der Linie waehlbar) | +| `winkel`, `as_seite`, `es_seite`, `as_winkel`, `es_winkel` | wie Modus 1 | nein/ja | siehe oben | + +Geruestoptionen sind in Modus 2 (reine Konsoleneingabe) nicht abfragbar, +bleiben auf Default (`"0"`, `"Schoenenberger Geruest"`). + +### 2.3 Modus 3 — Linienzug aus LINE/ARC — **nicht scriptbar** + +Eingangsfunktion: `gf-linienzug-modus ()` — **keine Parameter**. + +Ablauf in Produktion: +1. `(ssget '((0 . "LINE,ARC")))` — der Nutzer muss die Leitlinie + (Kette aus bereits in der Zeichnung vorhandenen LINE-/ARC-Entities) + **vorher zeichnen und dann per Maus auswaehlen**. Es gibt keine + Funktion, die eine Leitlinie aus Koordinaten/Bogen-Parametern + (`start_mm`/`ende_mm`/`bogen: {winkel_grad, radius_mm, seite}`, wie in + `gefaellestrecke_tests.json`s `M3_Bogen` zu sehen) direkt als Argument + entgegennimmt. +2. `getpoint` fuer Startpunkt (Kettenanfang, Z = obere Anschlusshoehe) und + Endpunkt-Referenz (Z = untere Anschlusshoehe); `deltaH` wird aus der + Differenz der beiden Z-Werte gebildet. +3. `getstring`-Abfragen fuer AS-/ES-Winkel (`"90"`/`"30"`) und -Seite + (`"links"`/`"rechts"`) — inhaltlich identisch zu Modus 1/2. +4. Interne Analyse der selektierten Kette (`gf-sortiere-objekte`, + `gf-analysiere-kette`) leitet Ein-/Ausgangsrichtung sowie + Segment-Laengenkorrekturen ab. + +Um Modus 3 aus JSON zu treiben, muesste man selbst zuerst die LINE/ARC- +Geometrie per `entmake` erzeugen und dann eine eigene (nicht-interaktive) +Nachbildung von `gf-linienzug-modus` schreiben — genau das tut +`tests/test_gefaellestrecke.lsp`, was hier aber explizit nicht als +Parameterquelle verwendet wurde. Die Felder `start_mm`, `ende_mm`, `bogen` +in `gefaellestrecke_tests.json` beschreiben also die **gewuenschte +Leitlinien-Geometrie**, nicht direkte Funktionsparameter einer +Produktionsroutine. + +--- + +## 3. VarioFoerderer — `Lisp/vf_core.lsp` + `vf_standard.lsp`/`vf_etage.lsp`/`vf_linienzug.lsp` + +Dispatcher `c:VarioFoerderer` waehlt einen registrierten Anlagetyp +(`"standard"`, `"etage"`, `"linienzug"`). Registry-Vertrag fuer +scriptbare Typen: +``` +berechne-fn (deltaL deltaH richtung seite) -> (best-winkel best-L_GF best-L_VF ergebnis-liste) +einfuege-fn (deltaL deltaH richtung best-winkel L_GF1 L_GF2 L_VF startpunkt seite hz) -> endpunkt +``` +Der Wrapper `vf-block-erstellen` fasst danach zentral alles zu einem +`VF_n`-Block zusammen (kein JSON-Feld dafuer noetig — laeuft automatisch). + +### 3.1 Standard — scriptbar + +- Winkel-/Laengen-Solver: `berechne-standard (deltaL deltaH richtung seite)` → wrappt `berechne-alle-winkel (deltaL deltaH richtung feste-hz)`. + Kandidatenwinkel (config `vario.bogen_winkel`, Default): + `3 6 9 12 15 18 21 27 33 39 45 51` Grad — **nur diese Werte sind + moeglich**, kein freier Winkel. +- Bau-Funktion: `variofoerderer-einfuegen (deltaL deltaH richtung best-winkel L_GF1 L_GF2 L_VF startpunkt seite hz)` + +| Feld | Typ | Pflicht | Bedeutung | +|---|---|---|---| +| `deltaL` | real (mm) | **ja** | horizontale Gesamtlaenge | +| `deltaH` | real (mm) | **ja** | Ziel-Hoehendifferenz (hier IST deltaH ein direkter Eingang, anders als bei Gefaellestrecke!) | +| `richtung` | string | **ja** | `"Auf"` oder `"Ab"` | +| `winkel` | int | nein | einer der 12 Kandidatenwinkel; ohne Angabe waehlt der Solver automatisch den **kleinsten gueltigen** Winkel | +| `L_GF1`, `L_GF2` | real (mm) | nein | Aufteilung der Gefaellestrecke vorne/hinten. Falls nicht vorgegeben: `gleichmaessig` = beide `L_GF/2` (Solver-Ergebnis `L_GF` gesamt). In Produktion per Dialog waehlbar: `0`=gleichmaessig, `1`=alles vorne (Rest hinten), `2`=alles hinten (Rest vorne), `3`=manuell (`L_GF1` frei, `L_GF2` = Rest) | +| `startpunkt` (x,y,z) | real (mm) | **ja** | Einfuegepunkt | +| `seite` | string | nein | `"links"`/`"rechts"` (AS/ES-Element-Seite) | Default `"rechts"` (Dialog) | +| `hz` | real (Grad) | nein | Fahrtrichtung | 0.0 | +| `motorseite` | string | nein | `"links"`/`"rechts"` — nur Dialog/Anzeige, kein Geometrie-Parameter von `variofoerderer-einfuegen` selbst | + +Minimal-JSON (wie `foerderer_tests.json`): +```json +{ "test_id": "VF_Auf_1000", "richtung": "Auf", "deltaL": 7000, "deltaH": 1000 } +``` +Optionale Erweiterungsfelder aus derselben Datei: `seite` (Default +`"links"` im Testfall), `winkel` (erzwingt einen bestimmten Kandidaten +statt Auto-Wahl), `gf_verteilung` (`"gleichmaessig"`/`"vorne"`/`"hinten"`, +entspricht Verteilungsmodus `0`/`1`/`2` oben). + +### 3.2 Etage — scriptbar + +- Solver: `berechne-etage (deltaL deltaH richtung seite)` → `berechne-winkel-etage (deltaL deltaH richtung)`. Gleiche 12 Kandidatenwinkel wie Standard. +- Bau-Funktion: `etage-foerderanlage-einfuegen (deltaL deltaH richtung best-winkel L_GF1 L_GF2 L_VF startpunkt seite hz)` + — **identische Signatur** wie Standard (Registry-Vertrag), Unterschied + liegt im internen Aufbau (30-Grad-AS/ES-Elemente statt 90, AS/ES direkt + uebereinander, zusaetzlicher fixer "Gefaellebogen" je nach `seite` + gegenueber dem AS-Element). + +Felder: identisch zu 3.1 (`deltaL`, `deltaH`, `richtung`, `winkel`, +`L_GF1`/`L_GF2`, `startpunkt`, `seite`, `hz`). + +### 3.3 Linienzug — **nicht scriptbar** + +Eingangsfunktion `vf-linienzug-modus ()` — **keine Parameter**, rein +interaktiver Mehrschritt-Assistent (Kettenaufbau aus GF-Boegen, +Vario-Kurven, VF-Einheiten, Horizontal-Stuecken). Jeder Schritt fragt per +`getpoint`/`getstring`/`getint`/`getreal` einzeln ab (Startpunkt, Hoehe, +AS/ES-Winkel+Seite, naechstes Element, Linien-Endpunkt via +Fahrtrichtung+Laenge, GF-Verteilung, Separator vor/nach, Kettenende +ja/nein, ...). Es existiert **keine** einzelne Produktionsfunktion, die +eine ganze Kette aus einer Liste von Segment-Beschreibungen entgegennimmt. + +`linienzug_tests.json` bildet genau diese Prompt-Sequenz als +Frage-Antwort-Protokoll ab (`typ`: `point_abs`/`point_rel`/`real`/ +`string`/`int`, jeweils mit `wert`/`dL`+`hz` und einem `kommentar`, der +beschreibt, WELCHE Frage/Bedeutung die Antwort hat) — das ist die einzige +Moeglichkeit, den interaktiven Ablauf nicht-interaktiv nachzubilden, kein +direkter Parametersatz einer Bau-Funktion. Die Reihenfolge und Bedeutung +der Prompts (Kurzfassung, siehe `vf_linienzug.lsp` fuer Details): + +1. Startpunkt (`point_abs`), Hoehe Z (`real`) +2. AS-Element setzen? Ja/Nein, wenn ja: Winkel 90/30, Seite links/rechts +3. Schleife "naechstes Element": GF-Bogen | Neue Linie (GF) | Neue Linie + (Ab/Auf VF) | Horizontal-VF | Kettenende — jeweils eigene Unter-Prompts + (Endpunkt `point_rel` mit `dL`+`hz`, Ziel-Hoehe oder Neigungswinkel, + Separator vor/nach, GF-Verteilung, Vario-Kurve Winkel/Seite/Variante, ...) +4. Kettenende: ES-Winkel (90/30) + ES-Seite (links/rechts) + +### 3.4 Vario_Kette_Merge — kein Neubau, sondern Re-Packaging + +`c:Vario_Kette_Merge`: benoetigt nur **einen** Start-Entity-Pick +(`entsel`), sucht danach selbst alle KS_EIN/KS_AUS-kompatiblen +Nachbarblöcke in der ganzen Zeichnung ab (`vfl-kette-verfolgen`, +Toleranzen 2mm/100mm) und fasst sie zu einem neuen `VF_n` zusammen. Kein +JSON-Parametersatz noetig/möglich — reine Nachbearbeitung bereits +vorhandener Geometrie. + +--- + +## 4. Omniflo — `Lisp/OmniModulInsert.lsp` + +### 4.1 Boegen/Weichen — Kern scriptbar, Produktions-Befehle NICHT + +Kern-Funktion: `omni:insert-dxf (sivasnr-str hoehe drehung)` + +| Param | Typ | Pflicht | Bedeutung | +|---|---|---|---| +| `sivasnr-str` | string | **ja** | Komponenten-ID; **ist gleichzeitig der Dateiname** `.dxf` unter `%DXFM_OMNIFLO%` — keine separate Namenstabelle | +| `hoehe` | string (mm) | nein | **absolute** Z-Koordinate des Einfuegepunkts + `HOEHE`-Attribut; leer/`nil` -> `config omniflo.default_hoehe` bzw. hart "2000" | +| `drehung` | string (Grad) | nein | feste Rotation; `nil` -> in den echten `c:OMNI_*`-Befehlen wird interaktiv per Maus gedreht | + +**Aber**: Alle echten `c:OMNI_APB_*`/`c:OMNI_W*_*`-Befehle rufen +`omni:insert-dxf` **ohne X/Y-Parameter** auf — der Einfuegepunkt kommt in +`_.INSERT` immer per `pause` (Mausklick). Es gibt in Produktion **keinen** +Aufrufpfad, der X/Y programmatisch uebergibt; `omni:insert-dxf` selbst +akzeptiert auch keinen Punkt-Parameter (der Punkt wird IN der Funktion +interaktiv abgefragt). Die Felder `x`/`y` in `omniflo_tests.json` haben +daher **keine direkte Entsprechung** in einer Produktions-Parameterliste — +sie beschreiben nur, wo das Element im Testlayout stehen soll; ein +JSON-Treiber muesste den `_.INSERT`-Aufruf selbst nachbauen (z.B. wie +`mubea:build-separator` in `tests/test_mubea.lsp` es fuer Separatoren tut). + +Sivasnr-Herkunft: `data/json/omniflo_boegen.json` (Felder u.a. `Sivasnr`, +`ProfilTyp`, `Radius`, `KurvenWinkel`, `Breite`, `Länge`, `SivasnrTEF`, +`Antriebsart`) und `data/json/omniflo_weichen.json` (zusaetzlich +`WeichenTyp`, `Schaltungstyp`, `KurvenRichtung`, `WeichenkörperLänge`) — +diese Tabellen dienen nur der Dialog-Filterung/Anzeige, nicht der +Namensaufloesung selbst (die ist `sivasnr == dateiname`). + +Minimal-JSON, wie es fuer einen scriptbaren Wrapper um `omni:insert-dxf` +sinnvoll waere (entspricht `omniflo_tests.json`): +```json +{ "id": "821104025", "type": "bogen", "sivasnr": "821104025", + "x": 219.4, "y": 223.06, "hoehe": 2000, "drehung": 0.0 } +``` +`description`/`row` sind reine Dokumentations-/Gruppierungsfelder aus der +Testdatei, keine Produktionsparameter. + +### 4.2 Aluprofil-Gerade (AP60/AP110/APG110) — Kern scriptbar + +Kern-Funktion: `omni:make-gerade (blockname pt pt2 srcAttribs textHeight textGap textEnt)` + +| Param | Typ | Pflicht | Bedeutung | +|---|---|---|---| +| `blockname` | string | **ja** | `"AP60"` / `"AP110"` / `"APG110"` | +| `pt`, `pt2` | (x,y,z) | **ja** | Start-/Endpunkt der Geraden (absolute Weltkoordinaten); Laenge/Rotation werden **aus der Differenz berechnet**, keine eigenen Winkel-/Laengenfelder | +| `srcAttribs` | alist | nein | aus Vorlagen-DWG gelesen (`omni:read-src-attribs blockname`); bei `nil` entsteht ein Block ohne ATTDEFs | +| `textHeight`,`textGap` | real | nein | Konfig `omniflo.text_height`(100.0)/`text_gap`(20.0) | + +Der oeffentliche Befehl `omni:insert-block (blockname laengemax)` fragt +`pt`/`pt2` interaktiv per `getpoint` ab und prueft `laengemax` (Config +`omniflo.laengemax_ap60/ap110/apg110`) — `omni:make-gerade` selbst ist +aber mit fest vorgegebenen Punkten aufrufbar, hier waere aus +`linienzug_tests.json`-artigen Daten (`profil`, `x`/`y`, `x_ende`/`y_ende`, +`hoehe`) ein Treiber moeglich, wie es die Eintraege vom Typ `"gerade"` in +`linienzug_tests.json` nahelegen (dort allerdings fuer eine andere +Test-Zwecksetzung — Reihen-Layout, nicht direkter Funktionsaufruf). + +### 4.3 TEF/Zubehoer (Stopper, Scanner, Buerstenbremse, Umlenk-/Antriebsstationen) + +`c:OMNI_STOPPER`/`c:OMNI_SCANNER`/... rufen `omni:insert-dxf` mit +**hartcodiertem** `sivasnr-str` (z.B. `"827062022"`) und `hoehe=nil, +drehung=nil` auf — Punkt weiterhin interaktiv. Kein Nutzer-JSON-Feld fuer +`sivasnr` noetig (fest im Code), nur Position/Hoehe/Rotation waeren +grundsaetzlich scriptbar (siehe 4.1), aber nicht ueber die vorhandenen +`c:OMNI_*`-Befehle. + +### 4.4 TV_* (Transferwagen-Verbinder) / APBW_* (Boegen fuer Weichen) + +**Nicht implementiert.** Alle `c:OMNI_TV_*`- und `c:OMNI_APBW_*`-Befehle +sind reine `[DUMMY]`-Platzhalter ohne echte Bau-Logik — kein +Parametersatz vorhanden, unabhaengig davon was eine Testdatei dafuer +vorsehen wuerde. + +`c:OMNI_UPDATE_ATTRIBS` ist in `Lisp/export.lsp` definiert (aktualisiert +`HOEHE`/`DREHUNG` aller Omniflo-Elemente aus der Zeichnung) — kein +Bau-/Einfuege-Befehl und daher kein JSON-Parametersatz. + +--- + +## 5. Separator/Scanner — `Lisp/SSG_LIB_Commands.lsp` + +Funktion: `ils-insert-sensor (fname)` + +| Param | Typ | Pflicht | Bedeutung | +|---|---|---|---| +| `fname` | string | **ja** | Blockdateiname ohne Erweiterung, z.B. `"Separator_SP"`, `"Scanner"` — aufgeloest ueber `ssg-ils-block-datei` (2D/3D-Suffix + Fallback) | + +**Nicht scriptbar**: die Funktion fragt Einfuegepunkte in einer Schleife +per `getpoint` ab (`_.INSERT pfad pt "" "" pause` — auch die Rotation +kommt per Maus/`pause`), bis der Nutzer mit ENTER/ESC abbricht. Es gibt +keinen Parameter fuer Punkt, Anzahl oder Rotation. Ein JSON-Treiber muss +den `_.INSERT`-Aufruf direkt selbst nachbilden (Blockdatei-Pfad ueber +`ssg-ils-block-datei` bzw. `ssg-ils-block-laden`, dann `_.INSERT pfad pt "" "" rot` +mit festem `rot` statt `pause`) — das ist der Ansatz, den +`tests/test_mubea.lsp`s `mubea:build-separator` verwendet (zur Info, nicht +als Quelle fuer diesen Parametersatz genutzt, da bereits aus +`ils-insert-sensor` selbst ableitbar: Blockname + Punkt + Rotation sind +die einzigen fachlich noetigen Werte). + +Minimal-JSON (wie in `mubea.json`s Separator-Eintraegen): +```json +{ "function": "insert", "block": "S-LP", "x": 10310, "y": -14158, "z": 1467, "rotation": 270 } +``` + +--- + +## 6. Zusammenfassung: scriptbar vs. nicht scriptbar + +| Element | Produktionsfunktion | Scriptbar? | +|---|---|---| +| Kreisel Insert | `kreisel-insert-script` | ja | +| Kreisel Connect | `kreisel-connect-script` | ja | +| Gefaellestrecke Modus 1 | `gf-modus12-abschluss` → `gefaellestrecke-einfuegen` | ja | +| Gefaellestrecke Modus 2 | `gf-modus12-abschluss` (deltaL/hz aus 2 Punkten) | ja | +| Gefaellestrecke Modus 3 | `gf-linienzug-modus` | **nein** (erfordert vorgezeichnete LINE/ARC-Auswahl) | +| VarioFoerderer Standard | `variofoerderer-einfuegen` | ja | +| VarioFoerderer Etage | `etage-foerderanlage-einfuegen` | ja | +| VarioFoerderer Linienzug | `vf-linienzug-modus` | **nein** (mehrstufiger interaktiver Assistent) | +| Vario_Kette_Merge | `vfl-kette-verfolgen` | n/a (Re-Packaging, kein Parametersatz) | +| Omniflo Bogen/Weiche | `omni:insert-dxf` (Kern) | Kern ja, **Produktionsbefehle nein** (X/Y immer Maus) | +| Omniflo Aluprofil-Gerade | `omni:make-gerade` (Kern) | Kern ja, Befehl `omni:insert-block` fragt Punkte interaktiv | +| 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`) |